qgis / QGIS

QGIS is a free, open source, cross platform (lin/win/mac) geographical information system (GIS)
https://qgis.org
GNU General Public License v2.0
10.09k stars 2.93k forks source link

QGIS 3.14.15: QgsMapLayerComboBox with enabled filters -> NoSuchClassError: Unknown C++ class: QgsMapLayerProxyModel #38472

Open jakobgager opened 3 years ago

jakobgager commented 3 years ago

In QGIS 3.14.15 a QgsMapLayerComboBox with enabled filters leads to a NoSuchClassError: Unknown C++ class: QgsMapLayerProxyModel

How to Reproduce

  1. 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.

  2. Switch to QGIS, load the plugin an test. Everything should work fine image

  3. Now go back to QT Designer and change the filter settings.

  4. In QGIS reload the plugin --> this creates the following stacktrace

Traceback (most recent call last): File "D:/PROGRA~1/QGIS3.14/apps/qgis/./python\qgis\utils.py", line 334, in _startPlugin plugins[packageName] = package.classFactory(iface) File "C:/Users/z003btpd/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\test__init.py", line 35, in classFactory from .test import test File "D:/PROGRA~1/QGIS3.14/apps/qgis/./python\qgis\utils.py", line 743, in _import mod = _builtin_import(name, globals, locals, fromlist, level) File "C:/Users/z003btpd/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\test\test.py", line 31, in from .test_dialog import testDialog File "D:/PROGRA~1/QGIS3.14/apps/qgis/./python\qgis\utils.py", line 743, in _import mod = _builtin_import(name, globals, locals, fromlist, level) File "C:/Users/z003btpd/AppData/Roaming/QGIS/QGIS3\profiles\default/python/plugins\test\test_dialog.py", line 32, in os.path.dirname(file), 'test_dialog_base.ui')) File "D:/PROGRA~1/QGIS3.14/apps/qgis/./python\qgis\PyQt\uic\init.py", line 36, in loadUiType return PyQtLoadUiType(*args, **kwargs) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\init__.py", line 198, in loadUiType winfo = compiler.UICompiler().compileUi(uifile, code_string, from_imports, resource_suffix, import_from) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\Compiler\compiler.py", line 110, in compileUi w = self.parse(input_stream, resource_suffix) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\uiparser.py", line 1021, in parse actor(elem) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\uiparser.py", line 828, in createUserInterface self.traverseWidgetTree(elem) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\uiparser.py", line 806, in traverseWidgetTree handler(self, child) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\uiparser.py", line 264, in createWidget self.stack.push(self.setupObject(widget_class, parent, elem)) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\uiparser.py", line 230, in setupObject self.wprops.setProperties(obj, branch) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\properties.py", line 415, in setProperties prop_value = self.convert(prop, widget) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\properties.py", line 378, in convert return func(prop[0], **args) File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\properties.py", line 120, in _set expr = [self._pyEnumMember(v) for v in prop.text.split('|')] File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\properties.py", line 120, in expr = [self._pyEnumMember(v) for v in prop.text.split('|')] File "D:\PROGRA~1\QGIS3.14\apps\Python37\lib\site-packages\PyQt5\uic\properties.py", line 115, in _pyEnumMember raise NoSuchClassError(prefix) PyQt5.uic.exceptions.NoSuchClassError: Unknown C++ class: QgsMapLayerProxyModel

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

3nids commented 3 years ago

can you share the UI file?

jakobgager commented 3 years ago

I've added the complete plugin directory. test.zip

3nids commented 3 years ago

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.

jakobgager commented 3 years ago

Yes, the file is present and looks like the one from Github. image

If I modify the ui file as you suggested I still get the same NoSuchClassError.

lyf6 commented 3 years ago

@jakobgager have you solved this problem? I also met this problem

3nids commented 3 years ago

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.

jakobgager commented 3 years ago

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.

jakobgager commented 3 years ago

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
reinvantveer commented 3 years ago

@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?

jakobgager commented 3 years ago

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)?

reinvantveer commented 3 years ago

Ah good question. So far I had been bumping into this on 3.16 but I'll try again with 3.18

reinvantveer commented 3 years ago

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?

jakobgager commented 3 years ago

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.

reinvantveer commented 3 years ago

:+1:

reinvantveer commented 3 years ago

Yeah, still broken in 3.18: qt-designer-qgis-3 18 qgis-3 18-qgsmaplayerproxymodel-class-error-

Sergey-Kl commented 2 years ago

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.

H-W-LANGE commented 9 months ago

This is still an issue in both 3.28.10 and 3.32.3 ...

3nids commented 9 months ago

I just made a change in master which might affect or solve the situation. Anyone is able to test? (in tomorrow's nightly build)

freeshrugsxd commented 4 months ago

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