Names, the node tree and caching
Every node has a name, which addresses it in the viewer tree and in tests, and a build identity, which decides which cached STL file backs it. They are independent, and each has simple rules.
Node names
A node’s name defaults to its class name. When a node becomes a child of another node, the name is derived from the attribute the parent holds it under:
class SimpleClock(AssemblyNode):
def __init__(self):
self.base = ClockBase()
self.pointer = Pointer()
super().__init__()
Here the children appear in the tree as base and pointer, not
ClockBase and Pointer. This is what keeps two same-class
siblings apart — self.hours = Pointer() and self.minutes =
Pointer() are distinct nodes named hours and minutes.
Children held in a list or tuple attribute get indexed names:
self.planets = [Planet(i) for i in range(3)]
# named planets-0, planets-1, planets-2
An explicit name= passed to the constructor always wins over the
derived name. Attributes starting with an underscore are ignored by
the derivation.
The same attribute-derived path is what qualifies a driver id
(Driving a machine). Two instances of one axis class
held as self.x_axis and self.y_axis publish their same-named
driver as x_axis.position and y_axis.position — one string,
identical in the exported document’s driver table, in set_state, in
instruction targets and in a simulation’s state. The id must be a
legal expression identifier, which the indexed list names above are
not (planets-0 would parse as a subtraction): a driver-declaring
node held in a list makes the tree unqualifiable, and it fails loudly
naming the offending segment rather than letting two siblings share
one value.
Node references
A node is named by reference: a qualifier (package.module:Class), a file path, or a file path plus class (path/to/file.py:Class). With no reference, a node-scoped command like solid develop operates on the project’s model, declared as model = “package.module:Class” under [tool.solid-node] in pyproject.toml — what solid new writes for you.
A bare path resolves to the single node class defined in that file. When a file defines several — like the panel-plus-assembly file in Fusing parts — name the one you mean:
class Panel(Solid2Node):
...
class VolumeControl(AssemblyNode):
...
A bare path to that file fails with an AmbiguousNodeError instead of silently picking one; name the class you mean, either by qualifier (panel_and_knob:VolumeControl) or hybrid path (panel_and_knob.py:VolumeControl). See the command line reference for the full reference grammar. Test classes (Test-driven CAD) resolve independently and may each declare the node they bind to.
Build identity and caching
Solid Node caches every generated artifact in the build directory
(_build by default, see SOLID_BUILD_DIR in the command
line reference): the SCAD and STL of OpenSCAD-family parts, a
.brep with the exact geometry beside the STL of OCCT-backed parts,
and a .dxf cut profile beside each sheet part — and rebuilds a part
only when its source or its parameters change.
“Changed” is decided by stamps, exactly. Source modification times are
read as integer nanoseconds and artifacts are stamped with the very
value that was read, so freshness is decided by exact equality, no
tolerance window: an artifact is current when its stamp matches its
sources, stale otherwise. (When a copy or checkout disturbs mtimes,
a content check rescues the artifact rather than rebuilding the
world.) One part tracks more than its parameters: an
imported STL leaf also counts its declaring Python
module among its sources, because its adjust() hook can change the
geometry without any constructor argument moving.
The cache key of a node instance is derived from its constructor arguments: a node built as Gear(teeth=20) and one built as Gear(teeth=21) are two different artifacts, while two Gear(teeth=20) instances share one — the geometry is the same, so it is built once, no matter how many times the part appears in the assembly, or under which names.
A node that declares its parameters (Declaring a machine) is keyed the same way, by the framework: the class plus
the resolved value of every declared parameter, so no keyword can be
forgotten. Identical units from repeat() share one key and one
artifact.
Consequences worth knowing:
Renaming a node (
name=or the holding attribute) never invalidates its cache — names address the tree, they are not part of the build identity.Any parameter change, however deeply buried in a long value, produces a new artifact; stale geometry cannot be served for a same-named node with different parameters.
Parameter values of any size are safe — the key embeds a bounded readable prefix plus a hash, not the values verbatim.
Driver and port values are deliberately not part of the build identity. A part’s placement and a flexible part’s pose change with every bound snapshot; keying artifacts on them would mint a new part per frame. Identity stays structural — constructor arguments — and state stays in the snapshot.