Copy tree objects#

The recursive_copy() function is a powerful tool for copying ACP tree objects. It allows you to:

  • copy objects within the same model or across models

  • copy the entire layup, or just a specific branch of the model tree

  • control how links between objects are handled

The following sections explain the parameters of the recursive_copy() function by applying them to an example model.

Copy to the same model#

This section shows how to use recursive_copy() to duplicate objects within the same ACP model.

Model setup#

To get started, launch an ACP instance and load a model:

Code:

import pathlib
import tempfile

import ansys.acp.core as pyacp
from ansys.acp.core.extras import ExampleKeys, get_example_file

acp = pyacp.launch_acp()
tempdir = tempfile.TemporaryDirectory()
input_file = get_example_file(
    ExampleKeys.MINIMAL_PLATE_ACPH5, pathlib.Path(tempdir.name)
)

model = acp.import_model(input_file)

The model structure is as follows:

Code:

pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'

At the start of each example, the model is deleted and rel-loaded to get back to the initial state:

Code:

acp.clear()
model = acp.import_model(input_file)

Copy one object#

The objects to be copied are passed to recursive_copy() in the source_objects parameter. For example, the following code copies the fabric Fabric.1:

Code:

fabric = model.fabrics["Fabric.1"]

res = pyacp.recursive_copy(
    source_objects=[fabric],
    parent_mapping={model: model},
    linked_object_handling="keep",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'Fabric.1' to 'Fabric.2'

The return value of recursive_copy() is a dictionary that maps the pre-existing objects to their newly created copies. In the code above, this is used to print what has been copied.

The model now has the following structure:

Code:

pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   ├── 'Fabric.1'
│   └── 'Fabric.2'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'

The linked_object_handling="keep" parameter indicates that links from the fabric to other objects in the tree should be preserved. This means that the new fabric will still have the same material assigned:

Code:

print(model.fabrics["Fabric.2"].material.id)

Output:

Structural Steel

Copy multiple objects#

Code:

acp.clear()
model = acp.import_model(input_file)

The source_objects parameter can include multiple objects. The following example copies the fabric Fabric.1 and the element set All_Elements:

Code:

fabric = model.fabrics["Fabric.1"]
element_set = model.element_sets["All_Elements"]

res = pyacp.recursive_copy(
    source_objects=[fabric, element_set],
    parent_mapping={model: model},
    linked_object_handling="keep",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'All_Elements' to 'All_Elements.2'
Copied 'Fabric.1' to 'Fabric.2'

This is the model tree after copying:

Code:

pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   ├── 'Fabric.1'
│   └── 'Fabric.2'
├── Element Sets
│   ├── 'All_Elements'
│   └── 'All_Elements.2'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'

Copy an object and its children#

Code:

acp.clear()
model = acp.import_model(input_file)

When an object has children in the ACP model tree, these are automatically included in the copy. The following example copies the modeling group ModelingGroup.1 and its children:

Code:

modeling_group = model.modeling_groups["ModelingGroup.1"]
res = pyacp.recursive_copy(
    source_objects=[modeling_group],
    parent_mapping={model: model},
    linked_object_handling="keep",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'ModelingGroup.1' to 'ModelingGroup.2'
Copied 'ModelingPly.1' to 'ModelingPly.2'

Code:

pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    ├── 'ModelingGroup.1'
    │   └── Modeling Plies
    │       └── 'ModelingPly.1'
    │           └── Production Plies
    │               └── 'ProductionPly'
    │                   └── Analysis Plies
    │                       └── 'P1L1__ModelingPly.1'
    └── 'ModelingGroup.2'
        └── Modeling Plies
            └── 'ModelingPly.2'

You may notice that the production and analysis plies have not been copied. This is because these are read-only objects which are generated on update. After a model update, they are present:

Code:

model.update()
pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    ├── 'ModelingGroup.1'
    │   └── Modeling Plies
    │       └── 'ModelingPly.1'
    │           └── Production Plies
    │               └── 'ProductionPly'
    │                   └── Analysis Plies
    │                       └── 'P1L1__ModelingPly.1'
    └── 'ModelingGroup.2'
        └── Modeling Plies
            └── 'ModelingPly.2'
                └── Production Plies
                    └── 'ProductionPly.2'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.2'

Copy to a different location#

Code:

acp.clear()
model = acp.import_model(input_file)

The parent_mapping parameter controls where in the model tree the copied objects are placed. The keys of the dictionary are the original parent objects, and the values are the new parent objects. This means that children of the original parent will be copied to the new parent.

Note

The key and value of the parent_mapping dictionary must generally (with some exceptions) be of the same type. For example, a ModelingPly object always has a ModelingGroup as its parent. For more details, consult the Feature tree section of the user guide.

The following example copies a the modeling ply ModelingPly.1 into its own parent, ModelingGroup.1.

Code:

modeling_group_1 = model.modeling_groups["ModelingGroup.1"]
modeling_ply = modeling_group_1.modeling_plies["ModelingPly.1"]

res = pyacp.recursive_copy(
    source_objects=[modeling_ply],
    parent_mapping={modeling_group_1: modeling_group_1},
    linked_object_handling="keep",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'ModelingPly.1' to 'ModelingPly.2'

This results in the following model tree:

Code:

model.update()
pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            ├── 'ModelingPly.1'
            │   └── Production Plies
            │       └── 'ProductionPly'
            │           └── Analysis Plies
            │               └── 'P1L1__ModelingPly.1'
            └── 'ModelingPly.2'
                └── Production Plies
                    └── 'ProductionPly.2'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.2'

By changing the value in the parent_mapping dictionary, you can instead copy it to a new modeling group:

Code:

modeling_group_2 = model.create_modeling_group(name="New Modeling Group")

res = pyacp.recursive_copy(
    source_objects=[modeling_ply],
    parent_mapping={modeling_group_1: modeling_group_2},
    linked_object_handling="keep",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'ModelingPly.1' to 'ModelingPly.3'

Code:

model.update()
pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    ├── 'ModelingGroup.1'
    │   └── Modeling Plies
    │       ├── 'ModelingPly.1'
    │       │   └── Production Plies
    │       │       └── 'ProductionPly'
    │       │           └── Analysis Plies
    │       │               └── 'P1L1__ModelingPly.1'
    │       └── 'ModelingPly.2'
    │           └── Production Plies
    │               └── 'ProductionPly.2'
    │                   └── Analysis Plies
    │                       └── 'P1L1__ModelingPly.2'
    └── 'New Modeling Group'
        └── Modeling Plies
            └── 'ModelingPly.3'
                └── Production Plies
                    └── 'ProductionPly.3'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.3'

Copy linked objects#

Code:

acp.clear()
model = acp.import_model(input_file)

Instead of keeping or discarding links to other objects, you can also copy the linked objects. This is done by setting the linked_object_handling parameter to copy. The following example copies the fabric Fabric.1 and its linked material Structural Steel:

Code:

fabric = model.fabrics["Fabric.1"]
res = pyacp.recursive_copy(
    source_objects=[fabric],
    parent_mapping={model: model},
    linked_object_handling="copy",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'Structural Steel' to 'Structural Steel.2'
Copied 'Fabric.1' to 'Fabric.2'

Code:

pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   ├── 'Structural Steel'
│   └── 'Structural Steel.2'
├── Fabrics
│   ├── 'Fabric.1'
│   └── 'Fabric.2'
├── Element Sets
│   └── 'All_Elements'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'

The copied fabric uses the copied material:

Code:

print(model.fabrics["Fabric.2"].material.id)

Output:

Structural Steel.2

Copy linked objects recursively#

Code:

acp.clear()
model = acp.import_model(input_file)

The copy of linked objects is recursive. In the following example, the modeling group ModelingGroup.1 is used as a source object. Since its child modeling ply ModelingPly.1 has a linked fabric, this fabric and its linked material are also copied. Similarly, the oriented selection set and its linked element set and rosette are copied:

Code:

modeling_group = model.modeling_groups["ModelingGroup.1"]
res = pyacp.recursive_copy(
    source_objects=[modeling_group],
    parent_mapping={model: model},
    linked_object_handling="copy",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'Structural Steel' to 'Structural Steel.2'
Copied 'Global Coordinate System' to 'Global Coordinate System.2'
Copied 'All_Elements' to 'All_Elements.2'
Copied 'Fabric.1' to 'Fabric.2'
Copied 'OrientedSelectionSet.1' to 'OrientedSelectionSet.2'
Copied 'ModelingGroup.1' to 'ModelingGroup.2'
Copied 'ModelingPly.1' to 'ModelingPly.2'

Code:

model.update()
pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   ├── 'Structural Steel'
│   └── 'Structural Steel.2'
├── Fabrics
│   ├── 'Fabric.1'
│   └── 'Fabric.2'
├── Element Sets
│   ├── 'All_Elements'
│   └── 'All_Elements.2'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   ├── 'Global Coordinate System'
│   └── 'Global Coordinate System.2'
├── Oriented Selection Sets
│   ├── 'OrientedSelectionSet.1'
│   └── 'OrientedSelectionSet.2'
└── Modeling Groups
    ├── 'ModelingGroup.1'
    │   └── Modeling Plies
    │       └── 'ModelingPly.1'
    │           └── Production Plies
    │               └── 'ProductionPly'
    │                   └── Analysis Plies
    │                       └── 'P1L1__ModelingPly.1'
    └── 'ModelingGroup.2'
        └── Modeling Plies
            └── 'ModelingPly.2'
                └── Production Plies
                    └── 'ProductionPly.2'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.2'

Control the copy of linked objects#

Code:

acp.clear()
model = acp.import_model(input_file)

To avoid copying a specific linked object, you can add it (as both key and value) to the parent_mapping dictionary. The following example copies the modeling group ModelingGroup.1 and its children, but does not copy the material Structural Steel and rosette Global Coordinate System:

Code:

material = model.materials["Structural Steel"]
modeling_group = model.modeling_groups["ModelingGroup.1"]
rosette = model.rosettes["Global Coordinate System"]

res = pyacp.recursive_copy(
    source_objects=[modeling_group],
    parent_mapping={model: model, material: material, rosette: rosette},
    linked_object_handling="copy",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'All_Elements' to 'All_Elements.2'
Copied 'Fabric.1' to 'Fabric.2'
Copied 'OrientedSelectionSet.1' to 'OrientedSelectionSet.2'
Copied 'ModelingGroup.1' to 'ModelingGroup.2'
Copied 'ModelingPly.1' to 'ModelingPly.2'

Code:

model.update()
pyacp.print_model(model, show_lines=True, label_by_id=True)

Output:

'ACP Model'
├── Materials
│   └── 'Structural Steel'
├── Fabrics
│   ├── 'Fabric.1'
│   └── 'Fabric.2'
├── Element Sets
│   ├── 'All_Elements'
│   └── 'All_Elements.2'
├── Edge Sets
│   └── 'ns_edge'
├── Rosettes
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   ├── 'OrientedSelectionSet.1'
│   └── 'OrientedSelectionSet.2'
└── Modeling Groups
    ├── 'ModelingGroup.1'
    │   └── Modeling Plies
    │       └── 'ModelingPly.1'
    │           └── Production Plies
    │               └── 'ProductionPly'
    │                   └── Analysis Plies
    │                       └── 'P1L1__ModelingPly.1'
    └── 'ModelingGroup.2'
        └── Modeling Plies
            └── 'ModelingPly.2'
                └── Production Plies
                    └── 'ProductionPly.2'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.2'

Copy to a different model#

Copying objects to a different model works exactly the same as within the same model, with one exception: Since the linked objects do not exist on the target model, linked_object_handling="keep" is not allowed. Only linked_object_handling="discard" and linked_object_handling="copy" are possible.

Model setup#

For the subsequent examples, a second model is created:

Code:

input_file_2 = get_example_file(
    ExampleKeys.MINIMAL_PLATE_CDB, pathlib.Path(tempdir.name)
)

acp.clear()
source_model = acp.import_model(input_file)
target_model = acp.import_model(
    input_file_2, name="New ACP Model", format="ansys:cdb", unit_system="SI"
)

pyacp.print_model(target_model, show_lines=True, label_by_id=True)

Output:

'New ACP Model'
├── Materials
│   ├── '1'
│   ├── '2'
│   ├── '3'
│   ├── '4'
│   ├── '5'
│   └── '6'
├── Element Sets
│   ├── 'All_Elements'
│   ├── 'BOTTOM_LEFT'
│   ├── 'FRONT'
│   ├── 'MIDDLE'
│   ├── 'TAIL'
│   ├── 'TOP_RIGHT'
│   └── '_CM_EXT_SEC_0'
├── Edge Sets
│   ├── 'ED_FRONT'
│   └── 'ED_TAIL'
└── Rosettes
    ├── '12'
    └── '13'

Copy the entire layup#

The following example copies all tree objects from the source model to the target model. All children of the source_model are copied, but the model itself is not copied since it is present in the parent_mapping dictionary:

Code:

res = pyacp.recursive_copy(
    source_objects=[source_model],
    parent_mapping={source_model: target_model},
    linked_object_handling="copy",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'Structural Steel' to 'Structural Steel'
Copied 'Global Coordinate System' to 'Global Coordinate System'
Copied 'All_Elements' to 'All_Elements.2'
Copied 'Fabric.1' to 'Fabric.1'
Copied 'OrientedSelectionSet.1' to 'OrientedSelectionSet.1'
Copied 'ModelingGroup.1' to 'ModelingGroup.1'
Copied 'ModelingPly.1' to 'ModelingPly.1'
Copied 'ns_edge' to 'ns_edge'

Code:

target_model.update()
pyacp.print_model(target_model, show_lines=True, label_by_id=True)

Output:

'New ACP Model'
├── Materials
│   ├── '1'
│   ├── '2'
│   ├── '3'
│   ├── '4'
│   ├── '5'
│   ├── '6'
│   └── 'Structural Steel'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   ├── 'All_Elements'
│   ├── 'BOTTOM_LEFT'
│   ├── 'FRONT'
│   ├── 'MIDDLE'
│   ├── 'TAIL'
│   ├── 'TOP_RIGHT'
│   ├── '_CM_EXT_SEC_0'
│   └── 'All_Elements.2'
├── Edge Sets
│   ├── 'ED_FRONT'
│   ├── 'ED_TAIL'
│   └── 'ns_edge'
├── Rosettes
│   ├── '12'
│   ├── '13'
│   └── 'Global Coordinate System'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'

Control the copy of linked objects#

Code:

acp.clear()
source_model = acp.import_model(input_file)
target_model = acp.import_model(
    input_file_2, name="New ACP Model", format="ansys:cdb", unit_system="SI"
)

As with the copy within the same model, the parent_mapping dictionary can be used to limit the copy of linked objects. The following example copies the entire layup, except for the material Structural Steel, element set All_Elements, edge set ns_edge, and rosette Global Coordinate System:

Code:

res = pyacp.recursive_copy(
    source_objects=[source_model],
    parent_mapping={
        source_model: target_model,
        source_model.materials["Structural Steel"]: target_model.materials["1"],
        source_model.element_sets["All_Elements"]: target_model.element_sets[
            "All_Elements"
        ],
        source_model.edge_sets["ns_edge"]: target_model.edge_sets["ED_TAIL"],
        source_model.rosettes["Global Coordinate System"]: target_model.rosettes["12"],
    },
    linked_object_handling="copy",
)
for source, target in res.items():
    print(f"Copied '{source.id}' to '{target.id}'")

Output:

Copied 'Fabric.1' to 'Fabric.1'
Copied 'OrientedSelectionSet.1' to 'OrientedSelectionSet.1'
Copied 'ModelingGroup.1' to 'ModelingGroup.1'
Copied 'ModelingPly.1' to 'ModelingPly.1'

Code:

target_model.update()
pyacp.print_model(target_model, show_lines=True, label_by_id=True)

Output:

'New ACP Model'
├── Materials
│   ├── '1'
│   ├── '2'
│   ├── '3'
│   ├── '4'
│   ├── '5'
│   └── '6'
├── Fabrics
│   └── 'Fabric.1'
├── Element Sets
│   ├── 'All_Elements'
│   ├── 'BOTTOM_LEFT'
│   ├── 'FRONT'
│   ├── 'MIDDLE'
│   ├── 'TAIL'
│   ├── 'TOP_RIGHT'
│   └── '_CM_EXT_SEC_0'
├── Edge Sets
│   ├── 'ED_FRONT'
│   └── 'ED_TAIL'
├── Rosettes
│   ├── '12'
│   └── '13'
├── Oriented Selection Sets
│   └── 'OrientedSelectionSet.1'
└── Modeling Groups
    └── 'ModelingGroup.1'
        └── Modeling Plies
            └── 'ModelingPly.1'
                └── Production Plies
                    └── 'ProductionPly'
                        └── Analysis Plies
                            └── 'P1L1__ModelingPly.1'