Fusing parts

Sometimes several nodes describe one piece. A knob, for example, can be modeled as a shaft plus a grip: two simple solids, easier to write and read separately — but in reality it’s a single rigid part, printed in one go. That’s what a FusionNode is for: its children are fused into one mesh.

Let’s build that knob. Create root/knob_shaft.py:

from solid_node.node import Solid2Node
from solid2 import cylinder

class KnobShaft(Solid2Node):

    fn = 64

    def render(self):
        return cylinder(r=4, h=30)

Rendered — the shaft:

Then root/knob_grip.py — a tapered grip, with a small indicator mark on top:

from solid_node.node import Solid2Node
from solid2 import cube, cylinder, translate

class KnobGrip(Solid2Node):

    fn = 64

    def render(self):
        grip = translate(0, 0, 28)(
            cylinder(r1=16, r2=12, h=14)
        )
        mark = translate(-1, -14, 41)(
            cube(2, 12, 2)
        )
        return grip + mark

Rendered — the grip:

And the fusion, at root/knob.py:

from solid_node.node import FusionNode
from .knob_shaft import KnobShaft
from .knob_grip import KnobGrip

class Knob(FusionNode):

    def render(self):
        return [KnobShaft(), KnobGrip()]

The shaft and grip are fused into one rigid mesh:

Unlike an assembly, a fusion consumes its children into a single mesh — they don’t need to keep their identity across renders, so creating them directly in render() is fine.

Since the result of a fusion is rigid, a FusionNode cannot use self.time (it raises an exception) — animate it from the AssemblyNode that contains it instead.

Fusions in assemblies

A FusionNode takes part in the node tree like any other node, so it can be a child of an AssemblyNode — and this is where fusing pays off: the whole knob is placed, rotated and tested as one part.

Let’s mount the knob on a panel and turn it. At root/__init__.py:

from solid_node.node import AssemblyNode, Solid2Node
from solid2 import cube, translate
from .knob import Knob

class Panel(Solid2Node):

    def render(self):
        return translate(-40, -40, -3)(
            cube(80, 80, 3)
        )

class VolumeControl(AssemblyNode):

    def __init__(self):
        self.panel = Panel()
        self.knob = Knob()
        super().__init__()

    def render(self):
        self.knob.rotate(-270 * self.time, [0, 0, 1])
        return [self.panel, self.knob]

NODE = VolumeControl

Press play to turn the volume up:

This file defines two node classes, so the module-level NODE marker tells Solid Node which one is the node of this file — without it, loading the file fails with an AmbiguousNodeError. See Names, the node tree and caching.