Open jakobgager opened 3 years ago
can you share the UI file?
I've added the complete plugin directory. test.zip
in your UI file:
<customwidgets>
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgsmaplayercombobox.h</header>
</customwidget>
</customwidgets>
The header line should read qgis.gui
instead of qgsmaplayercombobox.h
.
This replacement is handled by this code: https://github.com/qgis/QGIS/blob/master/python/custom_widgets/qgis_customwidgets.py
And this file is normally installed in the Osgeo4W install folder in \python\PyQt5\uic\widget-plugins
.
Can you check that the file is present?
Maybe @jef-n can bring some light here.
Yes, the file is present and looks like the one from Github.
If I modify the ui file as you suggested I still get the same NoSuchClassError.
@jakobgager have you solved this problem? I also met this problem
Similar issue: https://stackoverflow.com/questions/55510469/pyuic5-unknown-c-class-qfontdatabase
This is very likely a PyQt5 issue.
Removing this part of the code
<property name="filters">
<set>QgsMapLayerProxyModel::HasGeometry|QgsMapLayerProxyModel::LineLayer|QgsMapLayerProxyModel::MeshLayer|QgsMapLayerProxyModel::NoGeometry|QgsMapLayerProxyModel::PluginLayer|QgsMapLayerProxyModel::PointLayer|QgsMapLayerProxyModel::PolygonLayer|QgsMapLayerProxyModel::VectorLayer|QgsMapLayerProxyModel::VectorTileLayer</set>
</property>
solves the issue.
If I remove the shown part of the code, I end up with the "default" settings which obviously works (see initial post). However I loose the filters which I wanted to use. The SO question looks really similar, however to use the proposed solution one has to have more idea about Qt customization, i.e. how to add the QgsMapLayerProxyModel class to Qt.
In the meantime I found a workaround. Instead of specifying the filter in Qt Designer (which doesn't work'), it is possible to set the filters when running the plugin. Simpy add a line like:
self.dlg.mMapLayer.setFilters(QgsMapLayerProxyModel.LineLayer)
in your plugin.py > run method and and add the respective import:
from qgis.core import QgsMapLayerProxyModel
@jakobgager Can we re-open this? The proposed workaround hinges on having a self.dlg object in the first place, which I don't, I'm using a docked widget. Instead of a workaround for every kind of interaction, it would be better to actually address the issue. The question I still have is: why isn't this working in the first place? Isn't QgsMapLayerProxyModel
a thing in the QGIS C++ API and if not, what should it be?
I'm totally fine with reopening this issue, as a this is obviously a bug and as you posted my workaround is not generally applicable. Can you try to reproduce the issue with the upcoming release (3.18)?
Ah good question. So far I had been bumping into this on 3.16 but I'll try again with 3.18
I have a little trouble finding 3.18 in any of https://qgis.org/ubuntu-nightly-release, https://qgis.org/ubuntu-nightly-ltr or https://qgis.org/ubuntu-nightly. Is this installable as pre-built binary or do I build from source?
3.18 is in packaging since 02-19 (see qgis homepage). So maybe we just wait for some more days to get the new release.
:+1:
Yeah, still broken in 3.18:
I think I found the root of the issue. Pyuic defines a hardcoded set of classes for built-in widgets and needs to somehow get info on any external types. To add new classes to the converter, there is a widget-plugins
mechanism. If one goes to PyQt5/uic/widget-plugins
and adds the following file to it:
#whatever_name.py
pluginType = MODULE
def moduleInformation():
return "qgis.core", ("QgsFieldProxyModel", )
this will allow for pyuic to correctly compile the ui. There are some caveats however:
There is a C:\OSGeo4W64\apps\qgis\python\PyQt5\uic\widget-plugins
that declares qgis widgets (in qgis_customwidgets.py). Adding my .py file there didn't do the trick.
Adding it to C:\OSGeo4W64\apps\Python37\lib\site-packages\PyQt5\uic\widget-plugins
did work, however it doesn't seem to be a good solution (because it clutters original puyic distribution).
I suppose it should actually live in the first path and even be fused into qgis_customwidgets.py but I didn't have time to try it out and I'm not sure I'm going to succeed because I'm not familiar enough with how QGIS is built and how it handles imports and stuff. I hope someone more competent will investigate it and integrate a proper solution into the product.
My setup: Win10, QGIS 3.14
compiling resources using pb_tool, running it from a pyvenv with include-system-site-packages=true
. Activate.bat
is modified to setup env vars for qgis, pyqt etc.
Successfully tested on compiled resources (pyuic5 compiles a Widget that has a Field with QgsFieldProxyModel.Numeric
filter, QGIS successfully runs and shows the plugin). Didn't test running the plugin with raw .ui files but I assume those would work too since they use the same compiler.
Edit: I was dealing with a different class missing than the OP so anyone who wants to employ this solution as a hack for their current workflow, please keep in mind what classes you need to mention in your whatever_name.py
file.
This is still an issue in both 3.28.10 and 3.32.3 ...
I just made a change in master which might affect or solve the situation. Anyone is able to test? (in tomorrow's nightly build)
This is still an issue in 3.36.0.
When the filters property is set to "All" on the QgsMapLayerComboBox in Qt Designer, the following lines are added to the resulting .ui file:
<widget class="QgsMapLayerComboBox" name="mMapLayerComboBox">
<property name="filters">
<set>Qgis::All</set>
</property>
</widget>
And on the next QGIS startup I get the following traceback:
Couldn't load plugin <My Plugin>
PyQt5.uic.exceptions.NoSuchClassError: Unknown C++ class: Qgis
Traceback (most recent call last):
<LINES BEFORE LOADING UI FILE>
FORM_CLASS, _ = uic.loadUiType(Path(__file__).parents[1] / 'ui' / 'wea_buffers_diag.ui')
File "C:\PROGRA~1/QGIS33~1.0/apps/qgis/./python\qgis\PyQt\uic\__init__.py", line 36, in __loadUiType
return __PyQtLoadUiType(*args, **kwargs)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\__init__.py", line 200, in loadUiType
winfo = compiler.UICompiler().compileUi(uifile, code_string, from_imports,
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\Compiler\compiler.py", line 111, in compileUi
w = self.parse(input_stream, resource_suffix)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 1037, in parse
actor(elem)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 828, in createUserInterface
self.traverseWidgetTree(elem)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 806, in traverseWidgetTree
handler(self, child)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 503, in createLayout
self.traverseWidgetTree(elem)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 806, in traverseWidgetTree
handler(self, child)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 546, in handleItem
self.traverseWidgetTree(elem)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 806, in traverseWidgetTree
handler(self, child)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 264, in createWidget
self.stack.push(self.setupObject(widget_class, parent, elem))
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\uiparser.py", line 230, in setupObject
self.wprops.setProperties(obj, branch)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\properties.py", line 415, in setProperties
prop_value = self.convert(prop, widget)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\properties.py", line 378, in convert
return func(prop[0], **args)
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\properties.py", line 120, in _set
expr = [self._pyEnumMember(v) for v in prop.text.split('|')]
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\properties.py", line 120, in
expr = [self._pyEnumMember(v) for v in prop.text.split('|')]
File "C:\PROGRA~1\QGIS33~1.0\apps\Python39\lib\site-packages\PyQt5\uic\properties.py", line 115, in _pyEnumMember
raise NoSuchClassError(prefix)
PyQt5.uic.exceptions.NoSuchClassError: Unknown C++ class: Qgis
Python version: 3.9.18 (heads/master:5eba59e, Feb 1 2024, 20:02:10) [MSC v.1929 64 bit (AMD64)]
QGIS version: 3.36.0-Maidenhead Maidenhead, 09951dc0
In QGIS 3.14.15 a QgsMapLayerComboBox with enabled filters leads to a NoSuchClassError: Unknown C++ class: QgsMapLayerProxyModel
How to Reproduce
Use the shipped Qt Designer to modify the simple dialog created by means of the Plugin Builder plugin and add the QgsMapLayerComboBox with default settings.
Switch to QGIS, load the plugin an test. Everything should work fine![image](https://user-images.githubusercontent.com/1677740/91424951-c5b69780-e85a-11ea-8f39-f5158647cd41.png)
Now go back to QT Designer and change the filter settings.
In QGIS reload the plugin --> this creates the following stacktrace
I'm using QGIS 3.14.15 on Win 10
QGIS version 3.14.15-Pi QGIS code revision d5114d2cfa Compiled against Qt 5.11.2 Running against Qt 5.11.2 Compiled against GDAL/OGR 3.0.4 Running against GDAL/OGR 3.0.4 Compiled against GEOS 3.8.1-CAPI-1.13.3 Running against GEOS 3.8.1-CAPI-1.13.3 Compiled against SQLite 3.29.0 Running against SQLite 3.29.0 PostgreSQL Client Version 11.5 SpatiaLite Version 4.3.0 QWT Version 6.1.3 QScintilla2 Version 2.10.8 Compiled against PROJ 6.3.2 Running against PROJ Rel. 6.3.2, May 1st, 2020 OS Version Windows 10 (10.0) Active python plugins BezierEditing; DataPlotly; Generalizer3; OSMDownloader; pluginbuilder3; plugin_reloader; qgis2web; QuickOSM; db_manager; processing; test