Skip to content

Commit e4284bc

Browse files
committed
Add a feature tree
Add helper functions to either print a tree of ACP objects, or print it. In the documentation, show the tree structure, and explain how it affects the access and creation of objects. To implement the feature tree, record metadata when creating mapping properties (value type, and read-only status), and use a separate _exposed_grpc_property subclass to indicate it is a mapping.
1 parent 5222628 commit e4284bc

File tree

14 files changed

+525
-98
lines changed

14 files changed

+525
-98
lines changed

doc/source/api/example_helpers.rst

-11
This file was deleted.

doc/source/api/extras.rst

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Extras
2+
------
3+
4+
Example helpers
5+
~~~~~~~~~~~~~~~
6+
7+
.. currentmodule:: ansys.acp.core.extras.example_helpers
8+
9+
.. autosummary::
10+
:toctree: _autosummary
11+
12+
ExampleKeys
13+
get_example_file
14+
set_plot_theme
15+
16+
17+
Feature tree
18+
~~~~~~~~~~~~
19+
20+
.. currentmodule:: ansys.acp.core.extras.feature_tree
21+
22+
.. autosummary::
23+
:toctree: _autosummary
24+
25+
get_feature_tree
26+
print_feature_tree

doc/source/api/index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ API reference
2525
other_utils
2626
dpf_integration_helpers
2727
mechanical_integration_helpers
28-
example_helpers
28+
extras
2929
internal
3030
{% else %}
3131
The API reference is not available in this documentation build.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
Feature tree
2+
------------
3+
4+
.. testsetup::
5+
6+
import ansys.acp.core as pyacp
7+
8+
acp = pyacp.launch_acp()
9+
model = acp.import_model("../tests/data/minimal_complete_model_no_matml_link.acph5")
10+
11+
The following tree shows the hierarchy of PyACP objects:
12+
13+
.. doctest::
14+
15+
>>> pyacp.extras.feature_tree.print_feature_tree()
16+
Model
17+
├── Material
18+
├── Fabric
19+
├── Stackup
20+
├── SubLaminate
21+
├── ElementSet
22+
├── EdgeSet
23+
├── CADGeometry
24+
│ └── CADComponent (read-only)
25+
├── VirtualGeometry
26+
├── Rosette
27+
├── LookUpTable1D
28+
│ └── LookUpTable1DColumn
29+
├── LookUpTable3D
30+
│ └── LookUpTable3DColumn
31+
├── ParallelSelectionRule
32+
├── CylindricalSelectionRule
33+
├── SphericalSelectionRule
34+
├── TubeSelectionRule
35+
├── CutOffSelectionRule
36+
├── GeometricalSelectionRule
37+
├── VariableOffsetSelectionRule
38+
├── BooleanSelectionRule
39+
├── OrientedSelectionSet
40+
├── ModelingGroup
41+
│ ├── ModelingPly
42+
│ │ └── ProductionPly (read-only)
43+
│ │ └── AnalysisPly (read-only)
44+
│ ├── InterfaceLayer
45+
│ └── ButtJointSequence
46+
├── ImportedModelingGroup
47+
│ └── ImportedModelingPly
48+
│ └── ImportedProductionPly (read-only)
49+
│ └── ImportedAnalysisPly (read-only)
50+
├── SamplingPoint
51+
├── SectionCut
52+
├── SolidModel
53+
│ ├── ExtrusionGuide
54+
│ ├── SnapToGeometry
55+
│ ├── SolidElementSet (read-only)
56+
│ ├── CutOffGeometry
57+
│ ├── AnalysisPly (read-only)
58+
│ └── InterfaceLayer (read-only)
59+
├── ImportedSolidModel
60+
│ ├── SolidElementSet (read-only)
61+
│ ├── CutOffGeometry
62+
│ ├── LayupMappingObject
63+
│ │ ├── AnalysisPly (read-only)
64+
│ │ └── ImportedAnalysisPly (read-only)
65+
│ ├── AnalysisPly (read-only)
66+
│ └── ImportedAnalysisPly (read-only)
67+
├── Sensor
68+
└── FieldDefinition
69+
<BLANKLINE>
70+
71+
72+
This structure determines how objects can be created, accessed, and stored in the model.
73+
74+
75+
For example, :class:`ModelingPly` is a child of :class:`ModelingGroup`, which is a child of :class:`Model`.
76+
To access a specific modeling ply, you can traverse this tree hierarchy:
77+
78+
.. doctest::
79+
80+
>>> model
81+
<Model with name 'ACP Model'>
82+
>>> model.modeling_groups
83+
<MutableMapping[ModelingGroup] with keys ['ModelingGroup.1']>
84+
>>> modeling_group = model.modeling_groups["ModelingGroup.1"]
85+
>>> modeling_group.modeling_plies
86+
<MutableMapping[ModelingPly] with keys ['ModelingPly.1']>
87+
>>> modeling_ply = modeling_group.modeling_plies["ModelingPly.1"]
88+
>>> modeling_ply
89+
<ModelingPly with id 'ModelingPly.1'>
90+
91+
To create a new modeling ply, you can use the :meth:`ModelingGroup.create_modeling_ply` method:
92+
93+
.. doctest::
94+
95+
>>> new_ply = modeling_group.create_modeling_ply(name="New Ply")
96+
>>> new_ply
97+
<ModelingPly with id 'New Ply'>
98+
99+
When cloning and storing a modeling ply, the ``parent`` argument must be a :class:`ModelingGroup` object:
100+
101+
.. doctest::
102+
103+
>>> other_modeling_group = model.create_modeling_group()
104+
>>> cloned_ply = modeling_ply.clone()
105+
>>> cloned_ply
106+
<ModelingPly with id ''>
107+
>>> cloned_ply.store(parent=other_modeling_group)
108+
>>> cloned_ply
109+
<ModelingPly with id 'ModelingPly.2'>

doc/source/user_guide/concepts/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ These guides explain the PyACP concepts.
88
.. toctree::
99
:maxdepth: 2
1010

11+
feature_tree
1112
material_property_sets
1213
store

doc/source/user_guide/concepts/store.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ You can make changes to the unstored material, but they are lost when the Python
6565

6666
>>> material.density.rho = 8000
6767

68-
To store a material in an ACP model, call its :meth:`store <.Material.store>` method. The ``parent`` parameter determines where in the model the material is stored. In this case, the parent is the model itself.
68+
To store a material in an ACP model, call its :meth:`store <.Material.store>` method. The ``parent`` parameter determines where in the model the material is stored. In this case, the parent is the model itself. See :ref:`feature_tree` for more information on how to determine the ``parent`` object.
6969

7070
.. doctest::
7171

doc/source/user_guide/howto/print_model.rst

+63-63
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@ You can print the tree structure using the :func:`.print_model` function:
2222

2323
>>> pyacp.print_model(model)
2424
'ACP Model'
25-
Materials
26-
'Structural Steel'
27-
Fabrics
28-
'Fabric.1'
29-
Element Sets
30-
'All_Elements'
31-
Edge Sets
32-
'ns_edge'
33-
Rosettes
34-
'Global Coordinate System'
35-
Oriented Selection Sets
36-
'OrientedSelectionSet.1'
37-
Modeling Groups
38-
'ModelingGroup.1'
39-
Modeling Plies
40-
'ModelingPly.1'
41-
Production Plies
42-
'P1__ModelingPly.1'
43-
Analysis Plies
44-
'P1L1__ModelingPly.1'
25+
├── Materials
26+
└── 'Structural Steel'
27+
├── Fabrics
28+
└── 'Fabric.1'
29+
├── Element Sets
30+
└── 'All_Elements'
31+
├── Edge Sets
32+
└── 'ns_edge'
33+
├── Rosettes
34+
└── 'Global Coordinate System'
35+
├── Oriented Selection Sets
36+
└── 'OrientedSelectionSet.1'
37+
└── Modeling Groups
38+
└── 'ModelingGroup.1'
39+
└── Modeling Plies
40+
└── 'ModelingPly.1'
41+
└── Production Plies
42+
└── 'P1__ModelingPly.1'
43+
└── Analysis Plies
44+
└── 'P1L1__ModelingPly.1'
4545
<BLANKLINE>
4646

4747

@@ -70,47 +70,47 @@ The ``hide_empty`` label can be set to ``False`` to also show empty groups:
7070

7171
>>> pyacp.print_model(model, hide_empty=False)
7272
'ACP Model'
73-
Materials
74-
'Structural Steel'
75-
Fabrics
76-
'Fabric.1'
77-
Stackups
78-
Sublaminates
79-
Element Sets
80-
'All_Elements'
81-
Edge Sets
82-
'ns_edge'
83-
Cad Geometries
84-
Virtual Geometries
85-
Rosettes
86-
'Global Coordinate System'
87-
Lookup Tables 1d
88-
Lookup Tables 3d
89-
Parallel Selection Rules
90-
Cylindrical Selection Rules
91-
Spherical Selection Rules
92-
Tube Selection Rules
93-
Cut Off Selection Rules
94-
Geometrical Selection Rules
95-
Variable Offset Selection Rules
96-
Boolean Selection Rules
97-
Oriented Selection Sets
98-
'OrientedSelectionSet.1'
99-
Modeling Groups
100-
'ModelingGroup.1'
101-
Modeling Plies
102-
'ModelingPly.1'
103-
Production Plies
104-
'P1__ModelingPly.1'
105-
Analysis Plies
106-
'P1L1__ModelingPly.1'
107-
Interface Layers
108-
Butt Joint Sequences
109-
Imported Modeling Groups
110-
Sampling Points
111-
Section Cuts
112-
Solid Models
113-
Imported Solid Models
114-
Sensors
115-
Field Definitions
73+
├── Materials
74+
└── 'Structural Steel'
75+
├── Fabrics
76+
└── 'Fabric.1'
77+
├── Stackups
78+
├── Sublaminates
79+
├── Element Sets
80+
└── 'All_Elements'
81+
├── Edge Sets
82+
└── 'ns_edge'
83+
├── Cad Geometries
84+
├── Virtual Geometries
85+
├── Rosettes
86+
└── 'Global Coordinate System'
87+
├── Lookup Tables 1d
88+
├── Lookup Tables 3d
89+
├── Parallel Selection Rules
90+
├── Cylindrical Selection Rules
91+
├── Spherical Selection Rules
92+
├── Tube Selection Rules
93+
├── Cut Off Selection Rules
94+
├── Geometrical Selection Rules
95+
├── Variable Offset Selection Rules
96+
├── Boolean Selection Rules
97+
├── Oriented Selection Sets
98+
└── 'OrientedSelectionSet.1'
99+
├── Modeling Groups
100+
└── 'ModelingGroup.1'
101+
├── Modeling Plies
102+
└── 'ModelingPly.1'
103+
└── Production Plies
104+
└── 'P1__ModelingPly.1'
105+
└── Analysis Plies
106+
└── 'P1L1__ModelingPly.1'
107+
├── Interface Layers
108+
└── Butt Joint Sequences
109+
├── Imported Modeling Groups
110+
├── Sampling Points
111+
├── Section Cuts
112+
├── Solid Models
113+
├── Imported Solid Models
114+
├── Sensors
115+
└── Field Definitions
116116
<BLANKLINE>

src/ansys/acp/core/_model_printer.py

+42-11
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,45 @@ def __init__(self, label: str, children: list["Node"] | None = None):
4545
self.label = label
4646
self.children: list["Node"] = children if children else []
4747

48-
def __str__(self, level: int | None = 0) -> str:
49-
assert level is not None
50-
four_spaces = " "
51-
ret = four_spaces * level + self.label + os.linesep
52-
for child in self.children:
53-
ret += child.__str__(level + 1)
54-
return ret
55-
56-
57-
def print_model(model: Model, *, hide_empty: bool = True) -> None:
48+
def __str__(self) -> str:
49+
return self._to_string(_prefix="", show_lines=True)
50+
51+
def _to_string(
52+
self,
53+
*,
54+
show_lines: bool,
55+
_prefix: str = "",
56+
_is_last_child: bool = True,
57+
_is_root: bool = True,
58+
) -> str:
59+
if show_lines:
60+
elbow = "└── "
61+
line = "│ "
62+
tee = "├── "
63+
empty = " "
64+
else:
65+
elbow = line = tee = empty = " " * 4
66+
if _is_root:
67+
res = _prefix + self.label + os.linesep
68+
else:
69+
res = _prefix + (elbow if _is_last_child else tee) + self.label + os.linesep
70+
for i, child in enumerate(self.children):
71+
if _is_root:
72+
new_prefix = _prefix
73+
elif _is_last_child:
74+
new_prefix = _prefix + empty
75+
else:
76+
new_prefix = _prefix + line
77+
res += child._to_string(
78+
_prefix=new_prefix,
79+
show_lines=show_lines,
80+
_is_last_child=(i == len(self.children) - 1),
81+
_is_root=False,
82+
)
83+
return res
84+
85+
86+
def print_model(model: Model, *, hide_empty: bool = True, show_lines: bool = True) -> None:
5887
"""Print a tree representation of the model.
5988
6089
Parameters
@@ -63,9 +92,11 @@ def print_model(model: Model, *, hide_empty: bool = True) -> None:
6392
pyACP model
6493
hide_empty :
6594
Whether to hide empty collections.
95+
show_lines :
96+
Whether to show lines connecting the nodes.
6697
6798
"""
68-
return print(get_model_tree(model, hide_empty=hide_empty))
99+
return print(get_model_tree(model, hide_empty=hide_empty)._to_string(show_lines=show_lines))
69100

70101

71102
def get_model_tree(model: Model, *, hide_empty: bool = True) -> Node:

0 commit comments

Comments
 (0)