Appearance
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
airgapparam drives both adistanceconstraint between rotor-OD and stator-ID and aradiusconstraint 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?) → number—app/src/lib/fea2d/executor/index.ts:160. - Parameters panel slider:
app/src/components/fea2d/ParametersPanel.tsx— oneParamRowperparamcall detected in the script. - Tool button (smart-dim): not yet wired — there is no dedicated smart-dim tool in
CanvasToolbar.tsxas of . Dimensional constraints today are emitted from the constraint palette (distance/radius/diameter/angle) acting on the current selection, or written directly in MotorScript. fxbutton to bind a variable on a dimensional constraint: not yet wired in the Properties drop-down —valVarRefis 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
- User grabs a
ParamRowslider thumb.onDragBeginfires →beginVariableDragincrementsctx.dragFrameDepthso history coalesces (store/params.ts:49-51). - Drag tick. Slider input fires; the panel calls
patchParamValue(code, lineNumber, newValue)to rewrite the numeric literal in theparam(...)call (ParametersPanel.tsx:23-47), then dispatchescommitParamScript(newCode). - Debounce.
commitParamScriptstashes the patched code inparamPendingCodeand (re)arms a 150 mssetTimeout(store/params.ts:88-99). Successive ticks within 150 ms overwrite the pending payload — only the most recent one is committed. - Preview during drag. The slider thumb tracks the cursor immediately (local React state in
ParamRow). The canvas geometry refreshes only when the debounce fires andsetScriptre-runs the script. - Cancel options. Esc / blur / pointer-leave during drag are not wired as explicit cancels —
endVariableDragalways 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. - Finalize on pointer-up:
onDragEnd→endVariableDragflushesparamPendingCodeviasetScript({preserveSelection: true})and decrementsdragFrameDepth. One history entry lands for the whole gesture.
4b. Smart-dimension authoring (target flow — partially wired)
- Select smart-dim tool (target: toolbar slot; today: select a dimensional kind in the constraint palette after selecting 2 entities).
- Click entity 1. Preview: entity highlighted as the first reference.
- Click entity 2. Preview: a ghost dimension annotation between the two references, snapped to a default offset.
- Value popover appears. A numeric input prefilled with the measured value, plus an
fxbutton (target — not yet rendered). - Enter value OR press
fxand pick a Variable from the list → constraint emitted. Iffxwas used, the constraint is created withvalVarRef: <var.id>(constraint-schema.ts:120-129); otherwise the numeric literal is written intovalue. - Finalize on Enter. Cancel options: Esc aborts mid-flow; selecting another tool aborts.
5. Committed state
For a param call:
- One
ParamDefrecord is appended toparamCallsat executor time (executor/index.ts:201) and surfaced to the Parameters panel. - The returned
numberis 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
Variablerecord{ id, name, value, min, max, step, unit, type }lives inscene.geometry.variables.
For a smart dimension:
- A
Constraintof kinddistance/distanceH/distanceV/radius/diameter/angleis appended toscene.geometry.constraintswithvalueset to the snapshot numeric value ANDvalVarRefoptionally set to aVariable.id. WhenvalVarRefis set, the solver readsvariables.find(v => v.id === valVarRef).valueat solve time and ignoresvalue(constraint-schema.ts:122-128). - MotorScript statement: a call like
distance(p1, p2, vars.airgap)(variable-bound) ordistance(p1, p2, 5)(literal). Reverse-codegen patches that literal in place (reverse-codegen.ts:553+).
6. Constraints / interactions
| Surface | Behavior when valVarRef is set |
|---|---|
| Solver | reads variables[valVarRef].value, ignores Constraint.value |
Properties drop-down ConstraintRow | renders a lock icon + variable name instead of the editable numeric input (ConstraintRow.tsx:205-217) |
Canvas ConstraintBadge | renders (varName) below the glyph (ConstraintBadge.tsx:320-336) |
| Drag-commit | dragging 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 edit | same as drag-commit — patches the Variable, not the script literal |
updateVariable solver bridge | iterates 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
| Input | Behavior |
|---|---|
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 variable | Solver 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 rollback | Script 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.
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.
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.