Combining parts
Parts become a project when they are combined by internal nodes. An internal node’s render() does not return a solid — it returns a list of child node instances, after saying where each one sits at rest. What moves, and the values wired between parts in a driven machine, belong to the assembly’s simulate(); see Animating with time.
There are two types of internal nodes:
AssemblyNode — the children are separate parts that can move relative to each other. This is the node you’ll use the most, and the subject of this page.
FusionNode — the children are fused into one rigid, inseparable piece. Covered in Fusing parts.
Both take part in the node tree the same way: an assembly can contain leaf nodes, fusions and other assemblies.
An internal node whose children are declared in its class body may let render() return nothing: the children are then the declared ones, and render() only positions them. That form, with parameters that flow from the root and units that repeat, is Declaring a machine. This page shows the constructor form it builds on.
The simple clock
Let’s make a very simple clock, as a proof of concept, mixing together CadQuery and SolidPython. This example continues through Animating with time and Test-driven CAD.
Create a new file myproject/clock_base.py and create a CadQueryNode:
import cadquery as cq
from solid_node.node import CadQueryNode
class ClockBase(CadQueryNode):
def render(self):
wp = cq.Workplane("XY")
return wp.circle(100).extrude(2)
if __name__ == '__cq_main__':
show_object(ClockBase().render())
Rendered — the clock base:
Now, a file myproject/pointer.py with a Solid2Node:
from solid_node.node import Solid2Node
from solid2 import cube, cylinder, translate
class Pointer(Solid2Node):
def render(self):
return translate(-5, -5, 3)(
cube(10, 90, 10)
)
Rendered — the pointer:
And at myproject/myproject.py, an AssemblyNode:
from solid_node.node import AssemblyNode
from .clock_base import ClockBase
from .pointer import Pointer
class SimpleClock(AssemblyNode):
def __init__(self):
self.base = ClockBase()
self.pointer = Pointer()
super().__init__()
def render(self):
return [self.base, self.pointer]
Rendered — base and pointer assembled (still, for now):
Now in the viewer you should see a round clock base with a pointer.
Children are instance attributes
Children are created in __init__, as instance attributes — this is
the rule to follow in every assembly, for two reasons:
Children must keep their identity across renders, and each instance of the assembly must own its own children. Class attributes would be shared by every instance of the assembly, so two instances placed in different positions would be applying placement operations to the same objects.
A child is named after the instance attribute that holds it: in the clock above, self.base and self.pointer show up in the viewer tree as
baseandpointer. This is what keeps two children of the same class apart — two Pointer() instances held as self.hours and self.minutes are two distinct nodes, namedhoursandminutes.
You can always override the derived name by passing name= to the
constructor, and children held in a list get indexed names
(planets-0, planets-1, …). The full naming rules — and how
node identity relates to build caching — are in
Names, the node tree and caching.
Assemblies declare the machine’s inputs
The assembly is also where a machine’s named inputs live. A driver
is a class attribute, read back as an ordinary attribute in
simulate():
from solid_node.simulation import Driver
class Axis(AssemblyNode):
position = Driver(default=20.0, range=(0.0, 160.0), unit='mm')
def render(self):
return [self.rail, self.carriage]
def simulate(self):
self.carriage.translate([self.position, 0, 0])
The viewer turns drivers into sliders and declared instructions into
buttons, scoped to the assembly layer that declares them: an
assembly’s controls appear when it is focused, so declare each input
on the assembly the input belongs to — an axis’s travel on the axis,
the whole machine’s Home on the machine. An assembly also wires
values between its children with connect(), binding an expression
over its drivers to a child’s port. Drivers, qualified ids, ports and
instructions are the subject of Driving a machine.
Assemblies are testable
Because an assembly knows its parts and their placements, the
test framework can interrogate it as a whole: that
parts do not interfere, that they form the connections the design
intends — and, with assertAssemblySupported, that the assembly
actually rests on the ground and balances under gravity instead of
floating where the code put it.