4

I am creating a layout with two maps, several boxes, picture, ... The problem turn up when I add a legend where I modified the layers. For that I create a list with layers in my map, after that, I remove the OpenStreetMap layer and I add the rest to a new list. Then, I create a new layer tree and add the new layers. Everything is ok, but when I use legend.model().setRootGroup(root) and show the layout (Project >> Layout >> my_layout), an unexpected problem appears, and QGIS blow up.

Does anyone know what is my error?

Here is the main part of my code. The rest part works correctly, but the legend don't.

project = QgsProject.instance()
manager = project.layoutManager()
#Funciones varias
##Función para añadir boxes al layout
def addItemBox(layoutName, name, x, y, width, height):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 box = QgsLayoutItemShape(layout)
 box.setShapeType(QgsLayoutItemShape.Rectangle)
 box.setId(name)
 layout.addLayoutItem(box)
 box.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
 box.attemptResize(QgsLayoutSize(width, height, QgsUnitTypes.LayoutMillimeters))
 print(f'-> {name} was added')
##Función para añadir imágenes al layout
def addItemPicture(layoutName, name, path, x, y, width, height):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 picture = QgsLayoutItemPicture(layout)
 #### ...(r'...path a la imagen...') ####
 picture.setPicturePath(path)
 picture.setId(name)
 layout.addLayoutItem(picture)
 picture.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
 picture.attemptResize(QgsLayoutSize(width, height, QgsUnitTypes.LayoutMillimeters))
 print(f'-> {name} was added')
##Función para añadir etiquetas al layout
def addItemLabel(layoutName, name, text, x, y, width, height):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 label = QgsLayoutItemLabel(layout)
 label.setId(name)
 label.setText(text)
 layout.addLayoutItem(label)
 label.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
 label.attemptResize(QgsLayoutSize(width, height, QgsUnitTypes.LayoutMillimeters))
 print(f'-> {name} was added')
##Función de exportación del layout configurado como atlas previamente
def exportAtlas(layoutName):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 print(layout.name())
 exporter = QgsLayoutExporter(layout)
 exporter.exportToPdf(layout.atlas(),r'C:\Users\alvaro.garcia.daroca\Documents\guxhagen.pdf',QgsLayoutExporter.PdfExportSettings())
 print('\nExport End')
##Función que añade la escala al mapa
def addScaleBar(layoutName, map):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 scalebar = QgsLayoutItemScaleBar(layout)
 scalebar.setLinkedMap(map)
 scalebar.setId('escala mapa')
 scalebar.setStyle('Single Box')
 scalebar.setUnitLabel('m')
 scalebar.setSegmentSizeMode(1)
 scalebar.setMaximumBarWidth(100)
 scalebar.applyDefaultSize()
 layout.addItem(scalebar)
 scalebar.attemptMove(QgsLayoutPoint(15, 230, QgsUnitTypes.LayoutMillimeters))
 scalebar.attemptResize(QgsLayoutSize(30, 15, QgsUnitTypes.LayoutMillimeters))
##Función mapa princiapal
def addMap(layoutName, id, atlas, x, y, w, h):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 map = QgsLayoutItemMap(layout)
 map.setRect(20, 20, 20, 20)
 canvas = iface.mapCanvas()
 map.setExtent(canvas.extent())
 map.setId(id) #set name id
 layout.addLayoutItem(map)
 map.setAtlasDriven(atlas) #set control by atlas (True or False)
 map.setFrameEnabled(True)
 map.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
 map.attemptMove(QgsLayoutPoint(x, y, QgsUnitTypes.LayoutMillimeters))
 map.attemptResize(QgsLayoutSize(w, h, QgsUnitTypes.LayoutMillimeters))
 #only add scale to the main map
 if id == 'mapa principal':
 addScaleBar(layoutName, map)
 print(f'-> {id} was added')
##Función que genera la leyenda
def addLegend(layoutName):
 layout = manager.layoutByName(layoutName) #selecciona el layout a trabajar
 #create legend
 checked_layers = [layer.name() for layer in project.layerTreeRoot().children() if layer.isVisible()]
 #remove OpenStreetMap layer from Legend
 checked_layers.remove('OpenStreetMap')
 #print('Legend:', checked_layers)
 layersToAdd = [layer for layer in project.mapLayers().values() if layer.name() in checked_layers]
 root = QgsLayerTree()
 for layer in layersToAdd:
 root.addLayer(layer)
 #add legend and format it
 legend = QgsLayoutItemLegend(layout)
 ##### The problem is here I think #####
 legend.model().setRootGroup(root)
 
 legend.setId('Legend')
 layout.addLayoutItem(legend)
 legend.setFrameEnabled(True)
 legend.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
 legend.setTitle('Legend')
 legend.attemptMove(QgsLayoutPoint(308, 115, QgsUnitTypes.LayoutMillimeters))
 legend.attemptResize(QgsLayoutSize(104, 40, QgsUnitTypes.LayoutMillimeters))
 print(f'-> legend: ({checked_layers}) was added')
##Función para crear layouts
def createLayout(layoutName):
 layout = QgsPrintLayout(project)
 #remove duplicated layouts
 layout_list = manager.printLayouts() #return a list of layouts
 for layout in layout_list:
 if layout.name() == layoutName:
 manager.removeLayout(layout)
 layout = QgsPrintLayout(project)
 layout.initializeDefaults() #initialize void layout
 #modify page format
 pc = layout.pageCollection()
 pc.pages()[0].setPageSize('A3', QgsLayoutItemPage.Orientation.Landscape)
 layout.setName(layoutName)
 manager.addLayout(layout)
 #add items to the layout
 addItemBox(layoutName, 'margen', 5, 5, 410, 287)
 addItemBox(layoutName, 'cajetín general', 8, 249, 404, 40)
 addItemBox(layoutName, 'cajetín ubicación', 48, 252, 168, 34)
 addItemBox(layoutName, 'cajetín fecha', 219, 252, 34, 34)
 addItemBox(layoutName, 'cajetín descripción', 256, 252, 110, 34)
 addMap(layoutName, 'mapa principal', True, 8, 8, 297, 238)
 addMap(layoutName, 'mapa secundario', False, 308, 8, 104, 104)
 addItemPicture(layoutName, 'north arrow',r'C:\Users\alvaro.garcia.daroca\Documents\Qgis - Daroca\Telefónica - pruebas\North_Arrow.svg', 15, 15, 20, 20)
 addItemPicture(layoutName, 'logo UGG', r'C:\Users\alvaro.garcia.daroca\Documents\Qgis - Daroca\Telefónica - pruebas\UGG_logo.png', 369, 252, 40, 40)
 addItemPicture(layoutName, 'QR UGG',r'C:\Users\alvaro.garcia.daroca\Documents\Qgis - Daroca\Telefónica - pruebas\QR_UGG.png', 11, 252, 34, 34)
 print('\n-> **Layout was created')
#call function
createLayout('layout_atlas')
 ́ ́ ́
Kadir Şahbaz
78.6k57 gold badges260 silver badges407 bronze badges
asked Oct 25, 2021 at 8:42

1 Answer 1

9

I tested your code by running your addLegend() method in the Python console and I also experienced a crash. Interestingly, when I removed the logic from the function and simply ran the lines outside of a function definition there was no crash. As soon I moved the logic inside a function, it crashed.

Try the below for a slightly different approach by removing unchecked layers from the existing legend model layer tree rather than creating a new QgsLayerTree object and setting it to the existing model. This worked for me inside a function without crashing QGIS.

def addLegend(layoutName):
# project = QgsProject.instance()
# manager = project.layoutManager()
 layout = project.layoutManager().layoutByName(layoutName)
 
 checked_lyrs = [l.name() for l in QgsProject().instance().layerTreeRoot().children() if l.isVisible()]
 checked_lyrs.remove('OpenStreetMap')
 lyrsToRemove = [l for l in project.mapLayers().values() if l.name() not in checked_lyrs]
 legend = QgsLayoutItemLegend(layout)
 #setAutoUpdateModel to false otherwise main layer tree view will also be modified
 legend.setAutoUpdateModel(False)
 root = legend.model().rootGroup()
 for l in lyrsToRemove:
 root.removeLayer(l)
 legend.adjustBoxSize()
 legend.setId('Legend')
 layout.addLayoutItem(legend)
 legend.setFrameEnabled(True)
 legend.setFrameStrokeWidth(QgsLayoutMeasurement(0.3))
 legend.setTitle('Legend')
 legend.attemptMove(QgsLayoutPoint(308, 115, QgsUnitTypes.LayoutMillimeters))
 legend.attemptResize(QgsLayoutSize(104, 40, QgsUnitTypes.LayoutMillimeters))
addLegend('Test Layout')
answered Oct 25, 2021 at 11:23
2
  • 1
    I only needed a new point of view, remove instead of create just like that. Thank you so much, I was going crazy with this problem. Commented Oct 25, 2021 at 13:06
  • @Álvaro García Daroca, you're very welcome. It's strange indeed, tbh I don't get why your original code worked line-by-line, but crashed when run from a function! I think I may have encountered a similar problem before though as I had saved the snippet of code which worked. Anyway, glad your problem is solved :-) Commented Oct 25, 2021 at 13:25

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.