Skip to content

Parameters and Smart Dimensions

1. Identity

A parameter is a named numeric value declared in MotorScript via param(...) that surfaces as a slider in the Parameters panel; a smart dimension is a dimensional constraint whose value is bound to a parameter (or other Variable) through the constraint's valVarRef field rather than a frozen literal.

2. When to use it

  • Sweeping a stator-OD or air-gap value to see geometry update live without re-typing the script.
  • Exposing a small set of "design knobs" (slot depth, tooth width, magnet thickness) to a downstream user who should not edit the script.
  • Wiring one slider to many constraints — e.g. one airgap param drives both a distance constraint between rotor-OD and stator-ID and a radius constraint on a stress-relief arc.
  • Keeping a parametric link alive across drag-commits: if a distance is valVarRef-bound, dragging the constrained node patches the variable (which re-runs the script) instead of overwriting the literal in the script.

3. Inputs

  • MotorScript builtin: param(default, label?, unit?, min?, max?, step?) → numberapp/src/lib/fea2d/executor/index.ts:160.
  • Parameters panel slider: app/src/components/fea2d/ParametersPanel.tsx — one ParamRow per param call detected in the script.
  • Tool button (smart-dim): not yet wired — there is no dedicated smart-dim tool in CanvasToolbar.tsx as of . Dimensional constraints today are emitted from the constraint palette (distance / radius / diameter / angle) acting on the current selection, or written directly in MotorScript.
  • fx button to bind a variable on a dimensional constraint: not yet wired in the Properties drop-down — valVarRef is currently set only from MotorScript-authored constraints. The Properties drop-down recognises the binding (ConstraintRow.tsx:200-217) and the Badge displays it (ConstraintBadge.tsx:317-333), but no UI affordance creates the binding interactively.
  • Keyboard shortcut: not yet wired.
  • Command Palette: not yet wired.

4. State machine

4a. Parameter slider drag

  1. User grabs a ParamRow slider thumb. onDragBegin fires → beginVariableDrag increments ctx.dragFrameDepth so history coalesces (store/params.ts:49-51).
  2. Drag tick. Slider input fires; the panel calls patchParamValue(code, lineNumber, newValue) to rewrite the numeric literal in the param(...) call (ParametersPanel.tsx:23-47), then dispatches commitParamScript(newCode).
  3. Debounce. commitParamScript stashes the patched code in paramPendingCode and (re)arms a 150 ms setTimeout (store/params.ts:88-99). Successive ticks within 150 ms overwrite the pending payload — only the most recent one is committed.
  4. Preview during drag. The slider thumb tracks the cursor immediately (local React state in ParamRow). The canvas geometry refreshes only when the debounce fires and setScript re-runs the script.
  5. Cancel options. Esc / blur / pointer-leave during drag are not wired as explicit cancels — endVariableDrag always flushes the pending payload synchronously (store/params.ts:52-74) so that a sub-150 ms drag's final value lands instead of snapping back.
  6. Finalize on pointer-up: onDragEndendVariableDrag flushes paramPendingCode via setScript({preserveSelection: true}) and decrements dragFrameDepth. One history entry lands for the whole gesture.

4b. Smart-dimension authoring (target flow — partially wired)

  1. Select smart-dim tool (target: toolbar slot; today: select a dimensional kind in the constraint palette after selecting 2 entities).
  2. Click entity 1. Preview: entity highlighted as the first reference.
  3. Click entity 2. Preview: a ghost dimension annotation between the two references, snapped to a default offset.
  4. Value popover appears. A numeric input prefilled with the measured value, plus an fx button (target — not yet rendered).
  5. Enter value OR press fx and pick a Variable from the list → constraint emitted. If fx was used, the constraint is created with valVarRef: <var.id> (constraint-schema.ts:120-129); otherwise the numeric literal is written into value.
  6. Finalize on Enter. Cancel options: Esc aborts mid-flow; selecting another tool aborts.

5. Committed state

For a param call:

  • One ParamDef record is appended to paramCalls at executor time (executor/index.ts:201) and surfaced to the Parameters panel.
  • The returned number is just the default value — the param has no scene-level identity beyond its line in the script.

For a vars.foo reference (the Variable mechanism, scene-model.ts:747-757):

  • A Variable record { id, name, value, min, max, step, unit, type } lives in scene.geometry.variables.

For a smart dimension:

  • A Constraint of kind distance / distanceH / distanceV / radius / diameter / angle is appended to scene.geometry.constraints with value set to the snapshot numeric value AND valVarRef optionally set to a Variable.id. When valVarRef is set, the solver reads variables.find(v => v.id === valVarRef).value at solve time and ignores value (constraint-schema.ts:122-128).
  • MotorScript statement: a call like distance(p1, p2, vars.airgap) (variable-bound) or distance(p1, p2, 5) (literal). Reverse-codegen patches that literal in place (reverse-codegen.ts:553+).

6. Constraints / interactions

SurfaceBehavior when valVarRef is set
Solverreads variables[valVarRef].value, ignores Constraint.value
Properties drop-down ConstraintRowrenders a lock icon + variable name instead of the editable numeric input (ConstraintRow.tsx:205-217)
Canvas ConstraintBadgerenders (varName) below the glyph (ConstraintBadge.tsx:320-336)
Drag-commitdragging a node referenced by a bound dimension does NOT call reverseCodegenPatchConstraintValue; instead updateVariable(valVarRef, { value: newValue }) is invoked, which re-solves and debounce-re-runs the script (store/params.ts:107-125)
Properties drop-down value editsame as drag-commit — patches the Variable, not the script literal
updateVariable solver bridgeiterates constraints; any with valVarRef === id triggers solveScene synchronously, then a 150 ms-debounced runScript for expression-bound geometry (store/params.ts:113-124)

7. Failure modes

InputBehavior
param(NaN, ...)executor throws param: default value must be finite (executor/index.ts:171) — script error surfaced; the previous valid scene state is rolled back.
param(5, "w", "mm", 10, 0) (max < min)executor throws param: max (0) must be >= min (10).
param(5, "w", "mm", 10, 20) (default outside range)executor throws param: default (5) must be >= min (10).
param(1, "w", "mm", 0, 10, 0) (non-positive step)executor throws param: step must be a positive finite number when provided.
param(1, 5, "Width") (number where label expected)executor throws param: label must be a string with a hint about EU-comma decimal typing (executor/index.ts:192-196).
Smart-dim on entities that don't accept the kind (e.g. radius on a segment)Constraint emission rejected at palette dispatch — toast: "radius constraint requires an arc or circle".
valVarRef points to a deleted variableSolver falls back to Constraint.value (the last snapshot); the Properties drop-down hides the lock icon and reverts to literal editing on next selection. Badge shows no (varName) marker. No exception is thrown.
Slider drag during script-error rollbackScript is currently invalid, so the scene is the last-valid snapshot. Drag still patches the literal in the (broken) script text, debounce fires, setScript re-parses, error persists or clears depending on the new value. The slider thumb tracks the cursor regardless.
Short drag (< 150 ms)endVariableDrag flushes paramPendingCode synchronously so the final value lands (store/params.ts:62-73) — guards against snap-back on very short drags.

8. Figures

  airgap [mm]   ●────────●─────────────── 0.5
                            (slider, dragging right)

   ┌────────────────────────────────────────┐
   │                                        │
   │     ●────────────────●                 │
   │     p1               p2                │
   │       │←  distance = 0.5 (airgap) →│   │
   │                                        │
   │  ── slider moves right ──▶             │
   │                                        │
   │     ●─────────────────────●            │
   │     p1                    p2           │
   │       │←  distance = 0.9 (airgap) →│   │
   │                                        │
   └────────────────────────────────────────┘

Figure 1: a valVarRef-bound distance constraint between p1 and p2 lengthens live as the airgap parameter slider is dragged. Capture conditions: script declares airgap = param(0.5, "Airgap", "mm", 0.1, 2.0), two points and a distance(p1, p2, vars.airgap) constraint; drag the slider thumb from 0.5 to 0.9 within one gesture.

thickness= 5valVarRefthickness

Figure 1: parameter slider thickness = 5 at the top is bound to the distance constraint between two nodes at the bottom via valVarRef; the constraint value text renders the parameter name in violet instead of a literal.

distancethickness(driven)distance10(literal)

Figure 2: Properties drop-down view — top row is driven (lock icon + thickness in violet, no numeric input); bottom row is a literal-valued constraint with an editable numeric input.

9. Known bugs

None known.

10. Class API

In the current model, Variable is a class instance (app/src/lib/fea2d/model/Variable.ts) and the smart-dim's valVarRef resolution happens through Constraint.resolveValue(scene) on the abstract base — see Constraints / Class API. The dimensional constraint subclasses (DistanceConstraint, AngleConstraint, etc.) hold valVarRef: VariableId | null directly on the instance; resolveValue reads the referenced Variable.value when the ref is set and the variable exists, otherwise falls back to the literal value field.

Legacy plain-shape access (scene.geometry.variables, scene.geometry.constraints) still works via the compatibility getters on Scene — the Properties drop-down and inspector code paths are unchanged.

motordevs studio — geometry editor specification