Every dimension in a Rig Cad project can be a number or a formula. The formula language is small, but it decides whether a model survives a resize, whether a configurator shows the right label, and whether a customer can order something that cannot print. This is the reference for that language: where expressions live, what they can say, and the three conventions that catch almost everyone.

Where expressions live

There are two homes for a formula. Project variables are declared in the Variables panel of the Editor Guide and can reference each other. Node parameters are the rows in Properties that you switch from a typed literal to expression mode; a bound row evaluates every time the tree solves.

Table: Where a formula can be written and what it may reference.

Context Example Can reference
Project variable wallWidth = (outerDiameter - holeDiameter) / 2 Other variables, node values
Node option options.radius bound to holeDiameter / 2 Variables, other nodes by name
Node transform transform.position.x bound to cos(rad(notchAngle)) * outerDiameter / 2 Variables, other nodes
Post-processing option components.csg.postProcessing[0].options.thickness bound to shellThickness Variables
Path point path.points[2].y bound to cupHeight Variables
Validator holeDiameter <= outerDiameter - 4 Variables

A node is addressed by its name: Body.radius, Body.position.x, Body.bbox.max.z, Body.size.y. From another node, an option is Body.options.radius; on the node itself it is just options.radius. That is why node names must be identifiers: letters, digits and underscores, unique in the project, not colliding with a variable name. A node called Hex Socket cannot be referenced by anything.

The grammar in one table

The evaluator is a hardened mathjs instance. Operators are + - * / ^ %, unary minus and parentheses, with ^ binding tighter than * and /, which bind tighter than + and -. Comparisons, &&, ||, ! and the ternary cond ? a : b are all available, so a whole gate fits in one row: showHandle && slotCount > 0.

Table: The function vocabulary.

Group Functions
Trigonometry sin cos tan asin acos atan atan2 rad deg
Arithmetic abs ceil floor round sqrt cbrt pow exp log log10 min max sign mod clamp
Geometry vec dist dist2 mag mid lerp dot cross arcLen chord sagitta sectorArea circleArea circ
Text strcat format string number
Collections arr and array literals
Constants pi e phi tau

Numbers are doubles. Results are rounded for transport to six decimals at or above 1 and six significant digits below it, so a sub-micron residual survives instead of reading as zero. vec(x, y, z) builds a vector, + - * / are overloaded for vectors, and a node transform reads as a vector too: (Body.position + vec(1, 0, 0)).x. Vector times vector is rejected as ambiguous; use dot() or cross().

Degrees in, radians inside

Every angle path in Rig Cad is degrees: transform.rotation.z, an angle-typed variable, the rotation input of every tool. Every trigonometric function takes radians. sin(90) is therefore 0.894, because it is reading 90 radians.

Bridge the two explicitly. rad(x) converts degrees to radians and deg(x) converts back, so sin(rad(90)) is 1 and deg(pi) is 180. The literal form 90 deg is also accepted and means rad(90), but deg is a function here, so x deg is not valid for a variable; write rad(x).

The companion project places an alignment notch on the rim of a spacer with exactly this pattern:

transform.position.x = cos(rad(notchAngle)) * outerDiameter / 2
transform.position.y = sin(rad(notchAngle)) * outerDiameter / 2
transform.rotation.z = notchAngle

The position needs rad() because it goes through cos and sin; the rotation does not, because it is a degree path. Mixing those two up is the single most common expression bug, and it produces a notch that lands somewhere plausible-looking but wrong. For how rotations compose, see Transforms and Reference Frames.

Text: strcat, format, and the plus sign

+ is numeric. Joining two strings with it fails with Cannot convert ... to a number. Join text with strcat(a, b, ...) or its short alias s(a, b, ...), and render a number as text with format(value, precision):

strcat(labelText, " ", format(holeDiameter, 0))   →  "SPACER 12"
strcat("M", majorDiameter, "x", threadPitch)       →  "M8x1.25"

String literals inside an expression must be quoted. In a variable, the expression gyroid is a reference to a variable named gyroid; the expression "gyroid" is the word. The same rule applies to select and pattern defaults: a select variable whose value is a string has the expression "sheet", while its selectOptions values remain plain JSON strings.

The demo binds options.text of an Extrude Text node to a strcat and exposes the same formula as a readout, so the configurator shows exactly the string the geometry received. Text as Geometry covers what happens to that string once it becomes outlines.

Variable types and what each one is for

Table: Variable types. Min, max and step are configurator hints; they do not clamp expressions.

Type Use it for Notes
range Continuous dimensions a user drags Give min, max, step and a unit
number Counts and spinner values Step 1 for counts
angle Rotations and clock positions Degrees; feed trig through rad()
boolean Feature gates Bind to options.enabled
string Personalisation text maxLength guards the geometry
select Modes and materials Value can be a number, so a material list can carry density
pattern, font, image, color, vector Pickers Filtered by selectionFilter
expression Hidden helper formulas No min or max, no handle, usually hidden
readout Customer-facing calculated values Optional pretext and posttext; never a handle

Two flags shape the configurator. showInConfigurator: false hides internals such as wallWidth while leaving them available to expressions. isPrimary marks the handful of controls that matter most so an embed can show them first. Groups collect related variables under a title and can carry a visibilityExpression, which is how a whole section of controls appears only when a mode is selected.

Derived variables, readouts, and validators

A robust project separates what the user types from what the geometry needs. The spacer has three user inputs and derives the rest:

wallWidth    = (outerDiameter - holeDiameter) / 2
volumeMm3    = pi * ((outerDiameter / 2)^2 - (holeDiameter / 2)^2) * thickness
massEstimate = round(volumeMm3 / 1000 * material * 100) / 100   (readout, "Mass ~ " … " g")

material is a select whose values are densities in g/cm³, so choosing PETG changes the readout without any other edit. The same habit at product scale, with order inputs, production settings, derived helpers and validation signals as four distinct classes, is the subject of Beyond Sliders.

A validator is a boolean expression plus a message attached to one variable. The demo refuses a hole that leaves less than 2 mm of wall:

holeDiameter <= outerDiameter - 4   →  "Hole leaves less than 2 mm of wall."

Readouts are excluded from the unused-variable check by design, because being displayed is their use. Every other variable you define and never bind shows up in that list; read it, because an unbound driver leaves a model that looks right and a configurator with a dead slider.

Control-point handles

A number, range or angle variable can carry a handle: a draggable gizmo in the viewport that writes the value. The simplest form is { axis: "z" }, which parks the handle at the raw value along that axis. It is exactly right for thickness and exactly wrong for a diameter, because a diameter handle at the raw value sits twice as far out as the wall it drives.

For those, give the handle a forward mapping and its inverse. The spacer's outerDiameter uses forwardX: "v / 2" to place the gizmo on the rim, valueExpr: "x * 2" to turn a drag back into a diameter, and translateAxes: {x: true, y: false, z: false} so it cannot wander off its axis. anchorRootId names the node whose transform the handle follows; without it the handle lives in the world frame. Angular handles add orbitCenter and radius, and draw their own ring.

The rule that matters: valueExpr must not reference the handle's own variable, or the mapping collapses. Test the pair with the expression evaluator before binding it.

Expressions reference demo
Project

Expressions reference demo

@p12/expressions-reference-demo
Expressions reference demo
Expressions reference demo

Drag outerDiameter and thickness with their handles, rotate the notch with notchAngle, switch material and watch the mass readout, and push holeDiameter past the validator to see the message.

@p12/expressions-reference-demo
Drag outerDiameter and thickness with their handles, rotate the notch with notchAngle, switch material and watch the mass readout, and push holeDiameter past the validator to see the message.

What is deliberately unsupported

Units are rejected: 2 mm + 3 mm and unit("5 mm") report unsupported-feature, because the project already works in millimetres and the one accepted unit form is the degree literal. Complex results are rejected, so guard a domain instead of relying on them: sqrt(max(x, 0)). Matrices are rejected as values; use an array literal or arr(...). A short list of tokens is refused before evaluation at all, including evaluate, compile, import, range, function, => and ;.

instance.i, instance.count and instance.t exist only inside a Parametric Repeat while the worker expands the copies. They cannot be evaluated in the Variables panel, so test the surrounding arithmetic with a literal index and read Repeats and Arrays for what they unlock.

Where to go next

The mounting tab in Constructive Solid Geometry in Rig Cad is the smallest complete example of a derived cutter, and Oversize the Cutter is the rule that derived value enforces. Profiles whose points are expression-bound are the subject of Sketches to Solids. When the variables are in place and you want to publish them as controls, Publishing a Configurator covers releases and embeds.