Model springs, belts and cables

Every rigid leaf promises a part that holds still: whatever the machine does, the geometry is the same solid moved around. A valve spring compressed by its cam, a timing belt tensioned by an idler, a cable loom dragged along by a carriage do not work that way. Their shape, not just their placement, is a function of where the machine is.

A flexible leaf

A MolejoNode is that kind of part. Its render() returns a swept shape, a closed profile carried along a path, described analytically with molejo, with the moving dimensions left as parameters fed through ports:

from molejo import Circle, Helix, P, Shape
from machinome.node import MolejoNode
from machinome.motion.ports import TranslationalPort

class ValveSpring(MolejoNode):

    height = TranslationalPort(unit='mm')

    def render(self):
        return Shape(
            profile=Circle(radius=2.0),
            path=[Helix(radius=14.0, turns=6.5, height=P.height)],
            path_samples=240,
            profile_samples=16,
        )

P.height is molejo’s way of saying “this dimension is a parameter named height”. The wire radius, coil radius and turn count are numbers, because they describe the spring you would buy; the free height is left open, because that is the thing the machine moves.

Mind where the shape sits: molejo paths start at the node’s origin, so a helix winds about an axis offset by its coil radius. Place the node, or author the path, with that in mind.

Feed the parameters through ports

A flexible part never receives its moving values through its constructor. It declares one port per shape parameter, under the same name, and the owning assembly binds them in simulate():

from machinome.node import AssemblyNode
from machinome.simulation import Driver

FREE_HEIGHT = 46.8

class Valvetrain(AssemblyNode):

    lift = Driver(default=0.0, range=(0.0, 12.0), unit='mm')

    retainer = Retainer()
    spring = ValveSpring()

    def simulate(self):
        self.spring.height = FREE_HEIGHT - self.lift
        self.retainer.translate([0, 0, FREE_HEIGHT - self.lift])

The port’s attribute name is the parameter’s name, and the two sets must match exactly: a parameter with no port, or a port no parameter reads, fails naming the node and both sets before any geometry is produced. A port nobody connected fails too; it is never quietly defaulted.

Values through the constructor are the one thing that would not work. Constructor arguments key a node’s build artifacts, so a value that changes every frame would create a new part every frame. Ports keep the identity structural: two ValveSpring() instances are one part, whatever each is doing. Dimensions that describe a different spring, a thicker wire, another coil count, do belong in the constructor.

Under a running root, bound the mechanical joint that compresses the spring rather than the spring’s height port: a port has no stop of its own, and the height then follows the joint by a relation (Running mechanics).

In the viewer and in tests

A flexible part travels into the viewer as its shape specification, not as a mesh, and the browser evaluates it on the frames the value changed. A document holding one declares version 3 or above. OpenSCAD has no live evaluator, so the SCAD output and the OpenSCAD snapshot get a still: the part evaluated at the bound state.

A flexible part is exact: shape() gives the OCCT solid for the state currently bound, so a spring at a given lift answers interference and fit questions on real boundary geometry. Where the sweep has no closed form, a helix, a spline, the solid is approximated and shape_tolerance reports the approximation (0.0 when every surface is analytic). On the exact kernel a flexible comparison costs about thirty times a mesh comparison; Run tests fast is the answer for the development loop.

What a flexible part is not

It cannot be fused: a fusion makes one printed solid of its children, and a part that deforms is not part of one, so the fusion refuses it naming both nodes. It is not a printed piece and never appears in the pieces inventory. And it has no time of its own: its shape follows the values its parent binds, and nothing else.

molejo is an ordinary dependency of Machinome, installed with it and pinned by minor version, because a molejo minor carries the shape specification version the documents name. A RepRap printer on machinome.org’s Foundry (see Examples) has belts, springs and a filament path modelled this way.