Skip to content

Constraints ​

Rules relate numeric parameters ({ entity, prop } for plugin properties, { entity, geom } for built-in geometry such as a.x or radius) and anchors ({ entity, anchor }: start, end, mid, center, plugin anchors).

Rule kinds ​

KindMeaning
fix, fixPointa parameter or anchor has a value (a "lock")
equal, allEqual, ratio, equalSpacingrelations between parameters
linear`Σ coef·param (=
coincident, horizontal, vertical, distanceanchors
pointOnLine, pointLineDistance, pointOnCircleincidence
length, equalLength, parallel, perpendicular, anglelines
concentric, radius, equalRadius, tangentLineCircle, tangentCirclescircles and arcs
expressiona typed expression on one entity (lhs op rhs)

Plugin types add their own rules (templates) that apply to every instance. Unsupported equation classes are reported as unsupported — never silently ignored.

Strengths and priorities ​

required rules are hard: a commit never violates them. strong, medium and weak rules are preferences, satisfied in that order as far as the hard rules allow. Parameters also have a stay priority (low, normal, high) that says which values should change first when something must give — a door's offset (low) slides before its width (high).

How solving works ​

The engine builds a graph of variables and rules and solves only the connected component affected by a change:

  • purely linear components use a Cassowary solver (kasuari) with scaled rows and deterministic tie-breaking. While you drag, it is kept alive between pointer moves: the drag target becomes a Cassowary edit variable and each move is an incremental re-optimization (15–67× faster than a fresh solve on a 50–200 box chain, same results). Nonlinear or structurally changing problems fall back to a full solve automatically; DragPreview.incremental tells which path answered;
  • nonlinear components use a sequential quadratic programming (SQP) solver with exact gradients, constraint curvature, sparse linear algebra, presolve elimination and an active set for inequalities. A typed value or hard drag target far from the current geometry is first approached along the constraint manifold and then enforced exactly, so linkages move continuously instead of flipping to another branch.

Every commit reports what the solver did in CommitReport.solver (iterations, attempts, variables, rules, components).

Hard rules are constraints, not penalties. After solving, an independent checker re-evaluates every hard rule on the values that would be stored; only then is the change committed.

Results ​

StatusMeaning
solvedevery rule holds; the component is fully determined
underconstrainedevery rule holds; dof degrees of freedom remain
conflictingthe hard rules cannot hold together (certain or suspected)
notConvergedthe numeric solver did not reach the tolerance in its budget
cancelledthe request was cancelled
unsupporteda rule class is not supported

Diagnostics name the rules (IDs, labels, plugin templates), the edited parameters and the entities involved. User locks are never removed automatically. For linear conflicts, nearest gives the closest feasible value of each requested edit — the shelf example explains that 130 cm is impossible and offers 140 cm.

Tolerances ​

Four tolerances are kept apart: the model tolerance (1 nm absolute, for geometric comparisons), the solver tolerance (rule residuals, scaled per dimension), the flatten tolerance (¼ device pixel for drawing curves) and the screen tolerance (pick and snap radii in CSS pixels, converted with the current zoom).

Cancellation ​

ts
const ctrl = new AbortController()
const p = engine.apply(tx, { signal: ctrl.signal })
ctrl.abort() // takes effect between solver steps; the document stays unchanged

MIT OR Apache-2.0