Appearance
Polygon
1. Identity
A polygon is a regular n-sided closed shape whose n vertices lie on a common circle (the circumscribed circle), inscribed about a user-picked centre node, with n equal-length sides and n distance constraints from centre to each vertex that together keep the figure regular under solver edits.
2. When to use it
- Stator/rotor outline approximations where a regular n-gon is the design intent (3-, 4-, 6-, 8-sided mounting plates, hex magnet pockets).
- Construction scaffolding for n-fold symmetric layouts where each side later carries its own boundary or material region.
- Parametric primitive whose size is driven by a variable (
=R) so a single named radius drives all n vertices. - A quick "polygon-of-radius-R" stamp at a chosen centre with no follow-up clicks.
3. Inputs
- Tool button: toolbar slot for the regular-polygon icon; sets
activeTool === 'polygon'(Canvas/index.tsx:1623,:2346). - Keyboard shortcut: not yet wired (no entry in the tool-dispatch table around
Canvas/index.tsx:2716). - MotorScript builtin:
polygon(cx, cy, radius, sides, rotationRad?)(executor/builtins/primitives.ts:375).rotationRaddefaults to0. Throws on non-finite args,sides < 3, orradius <= 0. - Command Palette (Cmd+K): not yet wired.
- Context menu: not yet wired.
- Reverse codegen:
codegenPolygon(codegen.ts:199) emits the matchingpolygon(...)statement on commit.
4. State machine
The flow is one click + a modal dialog, not two canvas clicks. The dialog gathers sides / radius / rotation atomically.
- Click 1 — centre point: any world-space click while
activeTool === 'polygon'; snaps to existing nodes/grid via the standard click pipeline. The world coords are stashed inpolygonCentrestate (Canvas/index.tsx:2346-2347).- Preview after step 1: cursor crosshair only; no ghost polygon is rendered (no live radius/rotation rubber-band). The status hint reads
"Polygon — centre / Add point"(Canvas/index.tsx:2848). - Cancel options: Esc, tool switch, or clicking the dialog's Cancel — all route through
setPolygonCentre(null)(Canvas/index.tsx:469,:2897,:4594).
- Preview after step 1: cursor crosshair only; no ghost polygon is rendered (no live radius/rotation rubber-band). The status hint reads
- Dialog — parameters:
PolygonDialogopens withopen = !!polygonCentre(Canvas/index.tsx:4584-4586). Fields:Sides— integer, min 3, default 6.Radius— locale-parsed numeric, must be> 0, default 10. May be a parameter expression (radiusVarRef).Rotation— degrees, default 0; converted to radians before being passed tocreatePolygon.- Preview during dialog: none — the canvas is unchanged until Confirm.
- Cancel options: dialog
Cancel, Esc, or backdrop-close — all callonCancel→setPolygonCentre(null).
- Finalize: Submit on the dialog form (
PolygonDialog.tsx:42-51) callsaddPolygon(cx, cy, radius, sides, rotRad)(Canvas/index.tsx:4590-4592), which delegates tocreatePolygonintools/polygon-tool.ts:65.polygonCentreis cleared.
5. Committed state
After Confirm, createPolygon (tools/polygon-tool.ts:65-245) atomically appends:
- 1 construction
Nodeat(cx, cy)— the centre — unless an existing node already sits withinTOL.GEOMETRICof that point, in which case the existing node is reused andPolygonFeature.centreNodeIdis recorded as''sodeletePolygonwill not remove a shared user node (tools/polygon-tool.ts:124-143,:217-228). nvertexNodes on the circumscribed circle at anglesrotationRad + k·2π/nfork = 0…n−1(tools/polygon-tool.ts:107-111).nSegments in order0→1, 1→2, …, (n−1)→0(tools/polygon-tool.ts:146-156).(n−1)equalconstraints — side-0 against each of side-1…side-(n−1) (tools/polygon-tool.ts:184-197).ndistanceconstraints — centre → each vertex, all carrying the literal radius value, or thevalVarRefvariable name when the radius came from a parameter expression (tools/polygon-tool.ts:203-214).- 1
PolygonFeaturerecord owning all the above ids (scene-model.ts:522-539). - 1 MotorScript statement
polygon(cx, cy, radius, sides, rotationRad?)viacodegenPolygon(codegen.ts:199-208);rotationRadargument omitted when zero.
Total constraint count is 2n − 1. Parametric freedom retained: position (2 DOF) + rotation (1 DOF) + scale (1 DOF) — the centre→vert distance is left unconstrained as the size handle (tools/polygon-tool.ts:20-26).
6. Constraints / interactions
| Constraint | Behavior on this primitive |
|---|---|
| equal | Auto-emitted (n−1)× between sides; user may also equate to external segments. |
| distance | Auto-emitted n× from centre to each vertex (the size handle); user may smart-dim a single distance to lock size. |
| horizontal / vertical | Accepted on any one side; rotates the whole polygon because the equal-length constraints propagate. |
| coincident | Vertex or centre may be made coincident with external nodes; centre is a real Node and thus a first-class constraint target. |
| angle | Accepted between a polygon side and an external segment; rotates the polygon. |
Interactions with related features:
- Fillet: a polygon corner can be filleted; the fillet replaces the corner node with the tangent-point pair and a fillet arc. The polygon's distance/equal constraints continue to reference the original vertex nodes.
- Crossing: a polygon side may participate in a crossing with another segment — standard segment behaviour.
- Modify → Move/Rotate: dragging any vertex or the centre node is permitted; the regular-polygon constraints will keep the shape regular as the solver re-runs.
- Modify → Delete: deleting the feature removes the
nsegments, all2n−1owned constraints, and the centre node only when the polygon created it (centreNodeId !== '') and no sibling polygon's constraints still reference it (tools/polygon-tool.ts:259-296). - Copy & Array:
replicatePolygons(transforms.ts:961-1073) clones the feature record underapplyCloneandapplyCloneMirror. All vertex nodes of the source must be in the selection or the feature handle is dropped (segments are still cloned as loose primitives). The cloned polygon re-emits its own(n−1)equal +ndistance constraints; the centre node is materialised at the transformed centre (deduped against any existing node).
7. Failure modes
createPolygon is total — every failure path returns the input scene unchanged with empty result ids; no toast, no partial geometry, no rollback needed:
- n < 3 (or non-integer, or
n > 1000): early return (tools/polygon-tool.ts:95-104). ThePolygonDialogrejects submission first (PolygonDialog.tsx:47), so this only surfaces via the MotorScript builtin, which throws (executor/builtins/primitives.ts:379). - radius ≤ 0: same early return; dialog rejects (
PolygonDialog.tsx:48); builtin throws (executor/builtins/primitives.ts:380). - Non-finite cx/cy/radius/rotationRad/sides: early return (
tools/polygon-tool.ts:77-90); builtin throws (executor/builtins/primitives.ts:377). - Centre lookup miss after
addNode(should not happen — defensive): early return (tools/polygon-tool.ts:134-143). - Vertex node lookup miss after
addSegmentdedup (defensive; the Euclidean tolerance fix in I-A-3 closed the original window): early return (tools/polygon-tool.ts:164-174). - Solver divergence: the parametric is well-posed by construction (position + rotation + size free), so divergence typically indicates an over-constraint from external user constraints. Standard solver-failure UI applies — failed ids surface in
failedConstraintIds.
8. Figures
• v1
╱ ╲
v2• •v0 ← rotationRad = 0 places v0 on +X from centre
│ • │ ← centre node C (construction, real Node)
v3• •v5
╲ ╱ ── distance(C, v0) is the free size handle
• v4 ── distance(C, v_k) == distance(C, v0) for k=1..5
── equal(side_0, side_k) for k=1..5Figure 1: Committed hexagon (n=6) at centre C with radius line C→v0 highlighted. Capture conditions: empty scene, click centre at (0, 0), dialog Sides=6, Radius=10, Rotation=0.
Figure 1: PolygonDialog after click 1 — centre node placed on canvas, modal dialog gathers sides, radius, and rotation atomically.
Figure 2: Committed hexagon — 6 vertex nodes, 1 centre node, 6 sides; constraint glyphs mark the 5 equal-side ties and the 6 centre-to-vertex distance constraints.
9. Known bugs
None known. > Note: PolygonFeature doc-comment in scene-model.ts:532-537 says "next sides entries: distance from centre to each vertex" but also parenthetically states "(actually: (sides−1) distance constraints …)". The implementation in tools/polygon-tool.ts:203-214 emits sides distance constraints (one per vertex, including vert). The first bullet of the comment is correct; the parenthetical is stale. Worth a docstring cleanup but not a behavioral bug.
10. Class API
In the current model, polygons are class instances rather than plain objects. Like RectFeature, PolygonFeature is a thin owner — it holds id tuples only; the physical records live in the Scene-level Map indices.
- Class file:
app/src/lib/fea2d/model/PolygonFeature.ts - Constructor:
new PolygonFeature(id, sides: number, segmentIds: readonly SegmentId[], nodeIds: readonly NodeId[], centreNodeId: NodeId | '', constraintIds: readonly ConstraintId[])— validatessides >= 3and integer;segmentIds.length === sides;nodeIds.length === sides; permitsconstraintIds.length === 0(legacy fixtures) or=== 2 * sides - 1(authored from the polygon tool).centreNodeId === ''represents "no owned centre". - Snapshot type:
PolygonFeatureSnapshot(app/src/lib/fea2d/model/snapshots/PolygonFeature.ts). - Lifecycle methods:
toJSON: PolygonFeatureSnapshotstatic fromJSON(snap, _scene?): PolygonFeatureclone: PolygonFeatureequals(other: SceneEntity): boolean
- Polymorphic surface (
SceneFeature):referencesEntity(id: string): boolean— true whenidmatches the feature id, the (optional) centre node, or any owned segment / node / constraint id.isStale(_scene: unknown): boolean— returnsfalse(delete + regenerate, no re-eval).
Public fields are readonly: PolygonFeature is a thin owner — every mutation is delete + regenerate.