.. _assembly: ########## Assemblies ########## Most CAD designs consist of more than one part which are naturally arranged in some type of assembly. Once parts have been assembled in a :class:`~topology.Compound` object they can be treated as a unit - i.e. :meth:`~topology.Shape.moved` or exported. To create an assembly in build123d, one needs to create a tree of parts by simply assigning either a :class:`~topology.Compound` object's ``parent`` or ``children`` attributes. To illustrate the process, we'll extend the :ref:`Joint Tutorial `. **************** Assigning Labels **************** In order keep track of objects one can assign a ``label`` to all :class:`~topology.Shape` objects. Here we'll assign labels to all of the components that will be part of the box assembly: .. literalinclude:: tutorial_joints.py :start-after: [Add labels] :end-before: [Create assembly] The labels are just strings with no further limitations (they don't have to be unique within the assembly). **************************** Create the Assembly Compound **************************** Creation of the assembly is done by simply creating a :class:`~topology.Compound` object and assigning appropriate ``parent`` and ``children`` attributes as shown here: .. literalinclude:: tutorial_joints.py :start-after: [Create assembly] :end-before: [Display assembly] To display the topology of an assembly :class:`~topology.Compound`, the :meth:`~topology.Shape.show_topology` method can be used as follows: .. literalinclude:: tutorial_joints.py :start-after: [Display assembly] :end-before: [Add to the assembly by assigning the parent attribute of an object] which results in: .. code:: assembly Compound at 0x7fc8ee235760, Location(p=(0, 0, 0), o=(-0, 0, -0)) ├── box Compound at 0x7fc8ee2188b0, Location(p=(0, 0, 50), o=(-0, 0, -0)) ├── lid Compound at 0x7fc8ee228460, Location(p=(-26, 0, 181), o=(-180, 30, -0)) ├── inner hinge Hinge at 0x7fc9292c3f70, Location(p=(-119, 60, 122), o=(90, 0, -150)) └── outer hinge Hinge at 0x7fc9292c3f40, Location(p=(-150, 60, 50), o=(90, 0, 90)) To add to an assembly :class:`~topology.Compound` one can change either ``children`` or ``parent`` attributes. .. literalinclude:: tutorial_joints.py :start-after: [Add to the assembly by assigning the parent attribute of an object] :end-before: [Check that the components in the assembly don't intersect] and now the screw is part of the assembly. .. code:: assembly Compound at 0x7fc8ee235760, Location(p=(0, 0, 0), o=(-0, 0, -0)) ├── box Compound at 0x7fc8ee2188b0, Location(p=(0, 0, 50), o=(-0, 0, -0)) ├── lid Compound at 0x7fc8ee228460, Location(p=(-26, 0, 181), o=(-180, 30, -0)) ├── inner hinge Hinge at 0x7fc9292c3f70, Location(p=(-119, 60, 122), o=(90, 0, -150)) ├── outer hinge Hinge at 0x7fc9292c3f40, Location(p=(-150, 60, 50), o=(90, 0, 90)) └── M6 screw Compound at 0x7fc8ee235310, Location(p=(-157, -40, 70), o=(-0, -90, -60)) .. _shallow_copy: ********************************* Shallow vs. Deep Copies of Shapes ********************************* Build123d supports the standard python ``copy`` module which provides two different types of copy operations ``copy.copy()`` and ``copy.deepcopy()``. Build123d's implementation of ``deepcopy()`` for the :class:`~topology.Shape` class (e.g. ``Solid``, ``Face``, etc.) does just that, creates a complete copy of the original all the way down to the CAD object. ``deepcopy`` is therefore suited to the case where the copy will be subsequently modified to become its own unique item. However, when building an assembly a common use case is to include many instances of an object, each one identical but in a different location. This is where ``copy.copy()`` is very useful as it copies all of the :class:`~topology.Shape` except for the actual CAD object which instead is a reference to the original (OpenCascade refers this as a ``TShape``). As it's a reference any changes to the original will be seen in all of the shallow copies. Consider this example where 100 screws are added to an assembly: .. image:: reference_assembly.svg :align: center .. code:: screw = Compound.import_step("M6-1x12-countersunk-screw.step") locs = HexLocations(6, 10, 10).local_locations screw_copies = [copy.deepcopy(screw).locate(loc) for loc in locs] copy_assembly = Compound(children=screw_copies) copy_assembly.export_step("copy_assembly.step") which takes about 5 seconds to run (on an older computer) and produces a file of size 51938 KB. However, if a shallow copy is used instead: .. code:: screw = Compound.import_step("M6-1x12-countersunk-screw.step") locs = HexLocations(6, 10, 10).local_locations screw_references = [copy.copy(screw).locate(loc) for loc in locs] reference_assembly = Compound(children=screw_references) reference_assembly.export_step("reference_assembly.step") this takes about ¼ second and produces a file of size 550 KB - just over 1% of the size of the ``deepcopy()`` version and only 12% larger than the screw's step file. Using ``copy.copy()`` to create references to the original CAD object for assemblies can substantially reduce the time and resources used to create and store that assembly. ************************ Shapes are Anytree Nodes ************************ The build123d assembly constructs are built using the python `anytree `_ package by making the build123d :class:`~topology.Shape` class a sub-class of anytree's ``NodeMixin`` class. Doing so adds the following attributes to :class:`~topology.Shape`: * ``parent`` - Parent Node. On set, the node is detached from any previous parent node and attached to the new node. * ``children`` - Tuple of all child nodes. * ``path`` - Path of this ``Node``. * ``iter_path_reverse`` - Iterate up the tree from the current node. * ``ancestors`` - All parent nodes and their parent nodes. * ``descendants`` - All child nodes and all their child nodes. * ``root`` - Tree Root Node. * ``siblings`` - Tuple of nodes with the same parent. * ``leaves`` - Tuple of all leaf nodes. * ``is_leaf`` - ``Node`` has no children (External Node). * ``is_root`` - ``Node`` is tree root. * ``height`` - Number of edges on the longest path to a leaf ``Node``. * ``depth`` - Number of edges to the root ``Node``. .. note:: Changing the ``children`` attribute Any iterator can be assigned to the ``children`` attribute but subsequently the children are stored as immutable ``tuple`` objects. To add a child to an existing :class:`~topology.Compound` object, the ``children`` attribute will have to be reassigned.