Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

"Duplicate Layer" does not copy some custom properties #60884

Closed
2 tasks done
Alex-Kent opened this issue Mar 6, 2025 · 2 comments · Fixed by #60897
Closed
2 tasks done

"Duplicate Layer" does not copy some custom properties #60884

Alex-Kent opened this issue Mar 6, 2025 · 2 comments · Fixed by #60897
Labels
Bug Either a bug report, or a bug fix. Let's hope for the latter!

Comments

@Alex-Kent
Copy link
Contributor

Alex-Kent commented Mar 6, 2025

What is the bug or the crash?

Observed behavior
When "Duplicate Layer" is used any custom properties set for the original layer via the layer tree interface are not copied to the new layer.

Expected behavior
All custom properties set for the original layer should also be set in the duplicate layer.

Additional notes
If "Copy Layer" or "Copy Group" followed by "Paste Layer/Group" is used then all custom properties are preserved in the new layers/groups.

It appears that there are two sets of custom properties for each layer/group: those set via the layer tree (in QgsProject.instance().layerTreeRoot()) and those set via the set of map layers (in QgsProject.instance().mapLayers()). For copy/paste custom properties for both are carried over; for duplicate only those in the latter are.

Steps to reproduce the issue

  1. Create a new empty project
  2. Create a new Temporary Scratch Layer (of any geometry type)
  3. Open the Python console (Plugins→Python Console) and run:
def ShowValues():
    print("Via QgsProject.instance().mapLayers().values():")
    for layer in QgsProject.instance().mapLayers().values():
        name = layer.id()
        print(f"{name}: map-layers-key = { layer.customProperty('map-layers-key') }")
        print(f"{name}: layer-tree-key = { layer.customProperty('layer-tree-key') }")
    print()
    print("Via QgsProject.instance().layerTreeRoot().children():")
    for layer in QgsProject.instance().layerTreeRoot().children():
        name = layer.layerId()
        print(f"{name}: map-layers-key = { layer.customProperty('map-layers-key') }")
        print(f"{name}: layer-tree-key = { layer.customProperty('layer-tree-key') }")
  1. Show the values:
ShowValues()

Via QgsProject.instance().mapLayers().values():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

Via QgsProject.instance().layerTreeRoot().children():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

[The specific layer IDs shown will be different from those in this example.]

  1. Set a custom property via the map layers interface:
for layer in QgsProject.instance().mapLayers().values():
    layer.setCustomProperty("map-layers-key", "map layers value")

ShowValues()

Via QgsProject.instance().mapLayers().values():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = map layers value
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

Via QgsProject.instance().layerTreeRoot().children():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

  1. Set a custom property via the layer tree interface:
for layer in QgsProject.instance().layerTreeRoot().children():
    layer.setCustomProperty("layer-tree-key", "layer tree value")

ShowValues()

Via QgsProject.instance().mapLayers().values():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = map layers value
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

Via QgsProject.instance().layerTreeRoot().children():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = layer tree value

  1. Copy and paste the layer:
  • Select the layer in the Layers panel
  • In the top menu select Layer→Copy Layer
  • In the top menu select Layer→Paste Layer/Group
  1. Show the values:
ShowValues()

Via QgsProject.instance().mapLayers().values():
New_scratch_layer_e7cb95fb_f982_48d1_86b2_a3a23d9c184c: map-layers-key = map layers value
New_scratch_layer_e7cb95fb_f982_48d1_86b2_a3a23d9c184c: layer-tree-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = map layers value
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

Via QgsProject.instance().layerTreeRoot().children():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = layer tree value
New_scratch_layer_e7cb95fb_f982_48d1_86b2_a3a23d9c184c: map-layers-key = None
New_scratch_layer_e7cb95fb_f982_48d1_86b2_a3a23d9c184c: layer-tree-key = layer tree value

All custom properties are present in the copied layer (this is the expected behavior).

  1. Delete the newly-created layer (the second layer in the Layers list)

  2. Duplicate the original layer:

  • Right-click on the layer in the Layers panel and select Duplicate Layer
  1. Show the values:
ShowValues()

Via QgsProject.instance().mapLayers().values():
New_scratch_layer_1225a646_ca6e_4b7e_b346_3875b76e034d: map-layers-key = map layers value
New_scratch_layer_1225a646_ca6e_4b7e_b346_3875b76e034d: layer-tree-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = map layers value
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = None

Via QgsProject.instance().layerTreeRoot().children():
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: map-layers-key = None
New_scratch_layer_f5cae7b8_e1a0_4a04_803c_476d3baebdb6: layer-tree-key = layer tree value
New_scratch_layer_1225a646_ca6e_4b7e_b346_3875b76e034d: map-layers-key = None
New_scratch_layer_1225a646_ca6e_4b7e_b346_3875b76e034d: layer-tree-key = None

Only the value set via the map layers interface is present in the duplicate layer.

The value set via the layer tree interface is not present in the duplicate layer (but it should be present).

Versions

QGIS version 3.22.4-Białowieża QGIS code branch Release 3.22
Qt version 5.15.3
Python version 3.10.3
OS version Ubuntu 22.04.5 LTS

This version of QGIS is the latest version for this [actively-supported] Ubuntu LTS release.


Edit: I also built QGIS from source, behavior is the same.

QGIS version 3.43.0-Master
QGIS code revision 9a1d453
 
Libraries
Qt version 5.15.3
Python version 3.10.12
OS version Ubuntu 22.04.5 LTS

Supported QGIS version

  • I'm running a supported QGIS version according to the roadmap.

New profile

Additional context

I encountered this while working on improvements to the Layer Color Plugin. That plugin uses custom properties (via the layer tree interface) to store layer colors.

@Alex-Kent
Copy link
Contributor Author

I've found the calls that handle this (and a potential fix, see the end of the comment).

For copy/paste (correct behavior):

In method void QgisApp::copyLayer():

bool saved = QgsLayerDefinition::exportLayerDefinition( doc, mLayerTreeView->selectedNodes(), errorMessage, readWriteContext );

In method void QgisApp::pasteLayer():

bool loaded = QgsLayerDefinition::loadLayerDefinition( doc, QgsProject::instance(), root, errorMessage, readWriteContext, insertionMethod, &insertionPoint );

For duplicate layer (incorrect behavior):

In method void QgisApp::duplicateLayers( const QList<QgsMapLayer *> &lyrList ):

For Vector layers:

dupLayer = vlayer->clone();

For PointCloud, Raster, VectorTile, Mesh, Annotation, and TiledScene layers:
dupLayer = selectedLyr->clone();

The actual code that copies the custom properties appears to be:

In method void QgsMapLayer::clone( QgsMapLayer *layer ) const:

layer->setCustomProperties( mCustomProperties );

However this only copies the properties for the QgsMapLayer; it does not copy those of the associated QgsLayerTreeLayer.

After the QgsMapLayer has been cloned it is manually added to the layer tree as a QgsLayerTreeLayer. Next any necessary additional layer-specific tasks are performed:

QGIS/src/app/qgisapp.cpp

Lines 11766 to 11779 in 9a1d453

// always set duplicated layers to not visible so layer can be configured before being turned on
nodeDupLayer->setItemVisibilityChecked( false );
// duplicate the layer style
QString errMsg;
QDomDocument style;
QgsReadWriteContext context;
selectedLyr->exportNamedStyle( style, errMsg, context );
if ( errMsg.isEmpty() )
dupLayer->importNamedStyle( style, errMsg );
if ( !errMsg.isEmpty() )
visibleMessageBar()->pushMessage( errMsg, tr( "Cannot copy style to duplicated layer." ), Qgis::MessageLevel::Critical );
else if ( qobject_cast<QgsVectorLayer *>( dupLayer ) )
visibleMessageBar()->pushMessage( tr( "Layer duplication complete" ), dupLayer->providerType() != QLatin1String( "memory" ) ? tr( "Note that it's using the same data source." ) : QString(), Qgis::MessageLevel::Info );

Perhaps the custom properties for the new QgsLayerTreeLayer could be set here [line 11768], maybe something like:

    // duplicate the layer tree layer's custom properties
    for ( const QString &key : nodeSelectedLyr->customProperties() )
    {
        nodeDupLayer->setCustomProperty(key, nodeSelectedLyr->customProperty(key));
    }

[The above code has not been tested.]

Thoughts?

@Alex-Kent
Copy link
Contributor Author

I downloaded the current QGIS source (9a1d453) and tested the fix I suggested above. The fix worked properly and resolves this issue. The linked PR (#60897) contains this fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Either a bug report, or a bug fix. Let's hope for the latter!
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant