I am trying to implement a custom renderer with Python in QGIS. I have put together a small toy example based on the code from the Creating Custom Renderers section of the PyQGIS Cookbook.
To get this code to work, I had to override the renderFeature
function from the QgsFeatureRenderer
class. I believe this should not be necessary, because I am essentially just re-implementing the original implementation. However, if I remove the implementation of renderFeature
from my code and try to use the renderer in a version of QGIS that was built using CMAKE_BUILD_TYPE=Debug
, the following assert failure is thrown:
Fatal: ASSERT failure in QgsFeatureRenderer::renderFeature: "renderFeature called in a different thread - use a cloned renderer instead", file ../src/core/symbology/qgsrenderer.cpp, line 122
Note that in a QGIS version without the Debug
build type, the code works even with the renderFeature
function removed. I assume the assertion is there for a reason, though, so ignoring this seems like the wrong thing to do.
What am I missing? Which renderer do I have to clone to avoid this error?
Steps to reproduce:
- Start a version of QGIS built with the
Debug
build type - Paste the code below into the Python console and hit Enter
- Load a points layer
- In the layer styling panel, select the
Test renderer
- QGIS should crash
from qgis.core import QgsWkbTypes, QgsSymbol, QgsFeatureRenderer
from qgis.gui import QgsRendererWidget
from PyQt5.QtGui import QColor
class TestRenderer(QgsFeatureRenderer):
def __init__(self):
QgsFeatureRenderer.__init__(self, "TestRenderer")
self.sym = QgsSymbol.defaultSymbol(QgsWkbTypes.geometryType(QgsWkbTypes.Point))
self.sym.setColor(QColor('blue'))
def symbolForFeature(self, feature):
return self.sym
def startRender(self, context, vlayer):
self.sym.startRender(context)
####
# The following function is basically identical to the default implementation of renderFeature
# However, if I don't implement it here, QGIS crashes and I get the following
# error message:
# "renderFeature called in a different thread - use a cloned renderer instead"
####
#def renderFeature(self, feature, context, layer, selected, drawVertexMarker):
# s = self.symbolForFeature(feature)
# s.renderFeature(feature, context, layer, selected, drawVertexMarker)
# return True
def stopRender(self, context):
self.sym.stopRender(context)
def usedAttributes(self, context):
return []
def clone(self):
return TestRenderer()
class TestRendererWidget(QgsRendererWidget):
def __init__(self, layer, style, renderer):
QgsRendererWidget.__init__(self, layer, style)
if renderer is None or renderer.type() != "TestRenderer":
self.r = TestRenderer()
else:
self.r = renderer
def renderer(self):
return self.r
from qgis.core import QgsRendererAbstractMetadata, QgsApplication
class TestRendererMetadata(QgsRendererAbstractMetadata):
def __init__(self):
QgsRendererAbstractMetadata.__init__(self, "TestRenderer", "Test renderer")
def createRenderer(self, element):
return TestRenderer()
def createRendererWidget(self, layer, style, renderer):
return TestRendererWidget(layer, style, renderer)
QgsApplication.rendererRegistry().addRenderer(TestRendererMetadata())
super().startRender(context)
andsuper().stopRender(context)
fixes the problem. Adding_ = super().usedAttributes(context)
to theusedAttributes
override causes aRecursionError: maximum recursion depth exceeded while calling a Python object
, which I assume happens becauseusedAttributes
is a pure virtual function that does not have an implementation in the base class. Leaving the override as is (simplyreturn []
) works fine though. Thanks for the quick help!