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 Compound object they can be treated as a unit - i.e. moved() or exported.

To create an assembly in build123d, one needs to create a tree of parts by simply assigning either a Compound object’s parent or children attributes. To illustrate the process, we’ll extend the Joint Tutorial.

Assigning Labels

In order keep track of objects one can assign a label to all Shape objects. Here we’ll assign labels to all of the components that will be part of the box assembly:

box.label = "box"
lid.label = "lid"
hinge_outer.label = "outer hinge"
hinge_inner.label = "inner hinge"
m6_screw.label = "M6 screw"

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 Compound object and assigning appropriate parent and children attributes as shown here:

box_assembly = Compound(label="assembly", children=[box, lid, hinge_inner, hinge_outer])

To display the topology of an assembly Compound, the show_topology() method can be used as follows:

print(box_assembly.show_topology())

which results in:

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 Compound one can change either children or parent attributes.

m6_screw.parent = box_assembly
print(box_assembly.show_topology())

and now the screw is part of the assembly.

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 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 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 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:

_images/reference_assembly.svg
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:

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 Shape class a sub-class of anytree’s NodeMixin class. Doing so adds the following attributes to 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 Compound object, the children attribute will have to be reassigned.