Add persistent sketch-constraint system (ocs_gcs port) and named-parameters handoff docs

Ports FreeCAD's planegcs 2D geometric constraint solver to Rust as a new
crates/ocs_gcs workspace member, and wires it into the document/scene model
as a persistent SketchConstraintSet: Coincident, Horizontal/Vertical,
Parallel/Perpendicular, Equal, Tangent, Distance, Angle and Radius
constraints that re-solve live (including mid-drag, not just on release),
render glyphs and a DOF badge, flag conflicts with a one-click resolver
pill, and round-trip through save/load via an XRecord blob.

Also includes docs/command_reference.md (a statically generated reference
of every registered command and its prompt sequence) and
docs/named_parameters_design.md + docs/backlog.md, which hand off the next
queued feature (a named-parameter/expression table) and consolidate every
deliberate scope-down and open gap found along the way, so a fresh session
has a complete starting point.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Felix RiestraandClaude Sonnet 5 committed 2026-09-08 22:19:23 +02:00
1 parent a39c7b8f71
commit 63a74b19a9
63 files changed
+12193 -49

No files matched your search

+148
View File
@@ -0,0 +1,148 @@
# Backlog — deferred items, known gaps, and scope-downs
A consolidated list of things noticed but deliberately not fixed/built, so
they aren't lost between sessions. Pulled from `docs/
parametric_system_design.md`'s own "open questions" log, the `ocs_gcs` port
plan, and things found while doing unrelated work this session. Not a bug
tracker for the whole app — scoped to what surfaced during the parametric
constraint system project and its surrounding investigation.
## Parametric constraint system — deliberate scope-downs (documented, not accidental)
1. **Stage 9 undo is two presses, not one.** Adding a persistent constraint
and the geometry move it triggers are two separate undo/redo entries
(`HistorySnapshot::SketchConstraints` pushed adjacent to, not merged into,
the geometry `Delta`). The design doc's recommended §5.1(b) "live
XRecord" approach would fix this but depends on stage 4 (save/load),
which itself was scoped down (next item) — revisit both together if this
is ever worth fixing.
2. **Stage 4 does not resolve on load.** Opening a file with saved
constraints restores them but does not immediately re-solve — the DOF
badge and any conflict state only appear after the next edit that touches
an affected scope. Deliberate: resolving on load risked desyncing from
the `DerivedCaches` bundle `on_file_opened` installs right after, without
budget this session to verify that's safe.
3. **Stage 11's conflict resolver is a one-at-a-time pill, not the full
SketchXpert-style panel.** Clicking "⚠ n conflicting" removes one flagged
constraint and re-solves; there's no named-candidate list or cyclable live
preview before committing (design doc §6.4 Phase C). This is the single
largest UX gap remaining in the constraint system relative to the
original plan.
## `ocs_gcs` solver — not ported
- `SlopeAtBSplineKnot` (tangent continuity across a spline knot) — needs
knot-multiplicity reconstruction for a narrower use case than
`PointOnBSpline`, which is ported. Deferred, not silently dropped.
- `InternalAlignmentPoint2Ellipse` / `InternalAlignmentPoint2Hyperbola` —
FreeCAD-Sketcher-internal bookkeeping for *creating* conic geometry
interactively, not for constraining existing geometry. Lower priority.
- `EllipticalArcRangeToEndPoints` — dead code upstream (a `ConstraintType`
enum value with no implementing class anywhere in current planegcs
source), not a gap in this port.
- **Golden-fixture testing against the locally-installed FreeCAD.app**
(testing-strategy "Oracle A") was never wired up. Everything in `ocs_gcs`
is verified only against Oracle B (closed-form + finite-difference). Still
a real asset sitting unused if higher-confidence validation is ever
wanted.
- **Performance at scale is untested.** The "only touched scopes rebuild"
locality argument (design doc §4.2) is architecturally sound but has no
profiling behind it on a large, heavily-constrained drawing.
## Constraint types not exposed in the app's UI (even though some primitives exist)
`ConstraintKind` (`src/scene/sketch_constraints.rs`) currently has:
Coincident, Horizontal, Vertical, Parallel, Perpendicular, Equal, Distance,
Angle, Radius, Tangent. AutoCAD's own Geometric Constraints panel additionally
has **Fix**, **Concentric**, **Collinear**, **Symmetric**, **Smooth** — none
of these have a `ConstraintKind` variant here. Some are buildable from
already-ported `ocs_gcs` primitives (e.g. Concentric ≈ `C2CDistance` at zero
distance) without new solver math; `Fix` would need a different mechanism
entirely (pinning a point's params non-free rather than adding a constraint
row) and doesn't fit the current `ConstraintKind` model at all.
## Newly discovered this session (not previously tracked anywhere)
- **`CCONSTRAINT`, `TCONSTRAINT`, and their four siblings
(`HCONSTRAINT`/`VCONSTRAINT`/`PCONSTRAINT`/`QCONSTRAINT`/`ECONSTRAINT`) have
no `inventory::submit!(CommandRegistration)` registration.** Found while
generating `docs/command_reference.md`. All seven work correctly when
invoked (ribbon button or exact typed name) but don't appear in the
command-line's autocomplete or in the MCP server's `commands` listing,
unlike almost every other command in the app. Pre-existing across the
whole constraint-command family, not something this session's Tangent/
Coincident additions broke uniquely — just never noticed before. Cheap fix
(one `inventory::submit!` block) whenever someone wants to spend five
minutes on it.
- **No live link from a sketch to 3D solid features.**
`SolidHistorySweep`/`Revolve`/etc. (`src/scene/model/solid_history.rs`)
hold `sweep_entity: Option<EmbeddedEntity>` — a geometry copy snapshotted
at creation time, not a `Handle` back into the sketch. Dragging or
re-solving a sketch that was previously extruded/revolved/swept does not
update the resulting solid. This is the real "feature-history tree" gap
discussed when scoping FreeCAD/AutoCAD parity — a substantially bigger
project than the named-parameters work (see `docs/
named_parameters_design.md` §3), not something to fold into it by
accident.
- **No named-parameter/expression sheet** (AutoCAD's Parameters Manager /
FreeCAD's Spreadsheet equivalent). This is the next feature queued up —
see `docs/named_parameters_design.md` for the handoff context.
- **No end-user manual exists anywhere in the repo.** Checked `docs/`
thoroughly — everything there is developer-facing (plugin architecture,
this parametric design doc, tessellation notes, native-vs-web diffing,
release notes, automation scripts) plus localized top-level README
translations. No in-app Help link points anywhere either. Not started;
would be a real, separate writing effort if wanted.
- **No theme install/import/export mechanism.** Theming is entirely in-app:
pick one of iced's built-in themes, or hand-tune one "Custom" slot via six
hex-color inputs (`src/ui/window/options.rs`, `UiThemePalette` in
`src/app/config.rs`). No theme file format, no way to author and share a
theme, only one custom palette remembered at a time.
- **No touch/multi-touch input support**, relevant to the wasm build running
on a tablet (e.g. iPad). Grepped the whole codebase for touch/gesture
handling — none exists; all input assumes mouse+keyboard, relying on the
browser's synthetic mouse-event emulation for taps. No pinch-zoom, no
two-finger pan, and OSNAP/grip-drag precision (already tight even with a
mouse — see the Coincident epsilon note below) would be materially worse
with a fingertip.
## Testing/tooling notes for future live-verification sessions
- **Coincident's success path could not be reliably live-verified via
synthetic mouse clicks** — its resolver epsilon (`COINCIDENT_EPSILON_SQ =
1.0e-12`, ~1e-6 world units, reused from stage 8's `infer_coincident_refs`)
is tighter than this session's `left_click`/OSNAP automation could
reliably hit, even with Endpoint snap enabled. Concluded to be an
environment limitation, not a code defect — automated tests cover the
resolver logic directly — but worth knowing before assuming a future
automated click-through will succeed where this session's didn't.
- **`open_application`/`request_access` always resolve to the
`/Applications`-installed app bundle**, regardless of which binary was
most recently built. To live-test a code change, build a release binary
and swap it into a **scratch copy** of the installed bundle (e.g.
`/tmp/ocs_livetest/OpenCADStudio.app`), keeping the original small
launcher (`CFBundleExecutable = "OpenCADStudio"`) intact, then
`codesign --force --deep -s -` to re-sign ad-hoc before `open`-ing it.
This reuses the real bundle identity (`io.github.HakanSeven12.OpenCadStudio`)
so access requests correctly target it. A standalone bundle with a fresh
bundle id gets silently `user_denied` — not a viable shortcut. The debug
binary run bare via `nohup` never presented a controllable window either
— use a release build.
- **`target/debug` can balloon fast from repeated live-testing rebuilds** —
hit 35GB and caused a real "No space left on device" build failure this
session. `cargo clean` reclaimed 64GB. Worth a proactive `cargo clean` (or
checking `df`) before a long live-testing stretch, not just after it
breaks.
- Synthetic input reliability, generally: Escape frequently failed to cancel
an active command (use Return instead); LINE clicks sometimes produced
stray zero-length duplicate entities; selection clicks often missed thin
line bodies or landed on a midpoint-edit-box zone (use a crossing-window
drag-select instead of precise body clicks).
## Legal / process
- **LGPL-2.1-or-later review for `ocs_gcs` is still open** (original plan
§6). Non-blocking for continued development, but must happen before any
public release containing it — separate-relinkable-module posture vs.
broader relicensing, notice/attribution obligations under GPLv3
distribution.
File diff suppressed because it is too large. Load diff
+136
View File
@@ -0,0 +1,136 @@
# Named Parameters — Design & Handoff
**Status: not started.** This document is the starting context for a new
coding session (mirrors how `docs/parametric_system_design.md` was used for
the sketch-constraint-solver project — read that document's own "Progress"
section for the sibling feature this one builds alongside). The plan file at
`/Users/felix/.claude/plans/snoopy-finding-matsumoto.md` indexes both.
## 1. What this is, and why
OpenCADStudio now has real sketch-level parametric constraints (Coincident,
Horizontal/Vertical, Parallel/Perpendicular, Equal, Tangent, Distance, Angle,
Radius — see `docs/parametric_system_design.md`, fully built and tested).
What it does **not** have is a way to drive several of those dimensional
values from one shared, named, formula-capable source — FreeCAD calls this
its Spreadsheet workbench; AutoCAD calls it the **Parameters Manager**
(`PARAMETERS` command, since AutoCAD 2010) — a table of named variables
(`hole_dia`, `plate_len`, …) that a dimensional constraint's value can
reference instead of a bare literal, so editing the named value ripples to
every constraint that references it. This is explicitly the AutoCAD-parity
gap identified in the "AutoCAD equivalent" discussion this session (see
§7 for that framing) — we are not inventing a new concept, we're filling in
a specific, well-precedented one.
This is a genuinely separate feature from the constraint solver itself: it
needs a new document-level object, an expression parser, and a change to how
driving values are stored — not new `ocs_gcs` solver math.
## 2. Prior architecture decisions (from design discussion, not yet coded)
- **New document-level table object**, one per document (not per-scope/sketch
— parameters are meant to be shared across a whole drawing), holding
`name -> literal-or-formula`. Likely home: mirror
`src/scene/sketch_persist.rs`'s pattern exactly — an `XRecord`/`Dictionary`
blob on a well-known owner (that file's `XRECORD_KEY` /
`materialize_*_for_save` / `load_*_from_document` shape is the direct
precedent to copy, including the version-prefixed bincode envelope and the
hook points into `on_file_opened` / `prepare_native_save` / the wasm save
path / the automation `"new"`/`"open"` ops).
- **Expression parser/evaluator** — a small formula language (`2 * hole_dia +
1.5`), with dependency-cycle detection (A referencing B referencing A must
be rejected, not silently infinite-loop or panic). No existing dependency
in the workspace does this — check crates.io options (e.g. `evalexpr`,
`meval`) against the license stack (GPLv3 app; anything permissive/LGPL is
fine) before hand-rolling one.
- **Driving-value call sites become "literal or named reference"**. The
concrete, known change: `SketchConstraint::driving_param` is currently
`Option<f64>` (`src/scene/sketch_constraints.rs:97`), consumed directly in
`sketch_solve.rs` (`build_constraint`, lines ~184/190/195, one per
Distance/Angle/Radius) via `sys.add_param(c.driving_param?, ...)`. This
would become something like `DrivingValue::Literal(f64) |
DrivingValue::Named(String)`, resolved against the parameter table at
solve time (same rebuild-from-scratch-per-trigger model the constraint
solver already uses — no incremental re-evaluation needed, consistent with
`refresh_sketch_constraints`'s existing "full rebuild per trigger, not
incremental" design choice).
- **Solid-history properties are a second, separate call-site family** —
`src/scene/model/solid_history.rs`'s `PROP_LENGTH`/`PROP_HEIGHT`/etc. are
plain `f64` fields on structs like `SolidHistoryBox` (e.g. `value.length`,
set via the generic property-editing dispatch at `solid_history.rs:2239`).
Whether named parameters should reach these too (so a box's height can
reference the same `plate_thickness` a sketch dimension uses) is a scope
decision for this session to make explicitly, not assume — see open
questions below. If yes, it's the same "literal or reference" change
repeated across a different, larger set of call sites (many more `PROP_*`
constants than there are `ConstraintKind`s).
## 3. Explicitly out of scope for this feature (confirmed in prior discussion)
- **Live sketch-to-3D linking** (extrude/sweep/loft tracking a sketch's
constraint solve) is a separate, larger project. Confirmed this session:
`SolidHistorySweep`/`Revolve`/etc. hold `sweep_entity: Option<EmbeddedEntity>`
— a **snapshotted copy** of the profile geometry, not a live `Handle` back
into the sketch (`src/scene/model/solid_history.rs`, grep `sweep_entity`).
Named parameters can reference solid-history properties (per §2's open
question) without needing this link — that's a narrower, additive step.
Do not conflate the two.
## 4. Suggested staged approach
Mirroring what worked well for the constraint-solver project (`docs/
parametric_system_design.md`'s own staged plan): small, independently
testable stages, each with its own automated tests, progress notes appended
to this document (not a separate changelog) after each stage, live UI
verification where the stage has new UI surface.
1. Data model: the parameter table type + expression parser, unit-tested in
isolation (parse, evaluate, cycle detection) with no document/UI
integration yet.
2. Persistence spike: XRecord round-trip for the new table, following
`sketch_persist.rs`'s exact precedent — prove save/load byte-fidelity
before wiring anything else to it (this is the same order stage 2 of the
constraint-solver plan used, and it caught real assumptions early there).
3. Wire `SketchConstraint::driving_param` to resolve through the table
(Distance/Angle/Radius constraints only, the three existing call sites).
4. UI: a parameters panel (add/rename/delete a named value, edit its
formula, see its resolved number and any error) — likely the
`src/ui/style/dimstyle.rs`-pattern panel already used elsewhere in this
codebase, and a way to pick "use a named parameter" instead of typing a
literal wherever a Distance/Angle/Radius constraint's value is entered
today (`src/modules/draw/constrain/value.rs`'s `DistanceConstraintCommand`
/ `AngleConstraintCommand`).
5. (If scoped in per §2/§3) extend to `solid_history.rs`'s `PROP_*` values.
## 5. Open questions to resolve before/while coding (not resolved by this doc)
1. **Solid-history scope** — does v1 include primitive solid dimensions
(box length, cylinder radius, …), or sketch dimensional constraints only?
Affects how much of §4's stage 5 is in scope.
2. **Expression language surface** — arithmetic only, or also functions
(`min`, `sqrt`, trig)? AutoCAD's Parameters Manager supports a fairly rich
set; matching all of it is not required for v1 but the parser's grammar
should be chosen so functions can be added later without a rewrite.
3. **Undo granularity for a parameter edit** — a named-parameter edit can
ripple through many constraints across many scopes in one edit, similar
to (bigger than) a single constraint's own resolve. Should probably reuse
the same `record_undo_before`/one-history-entry-per-trigger model
`refresh_sketch_constraints` already established, but confirm rather than
assume.
4. **Where does the parameter table live for multi-document workflows?**
Per-document (confirmed in §2) — but does a block/xref carry its own
independent table, or only the top-level document? `sketch_persist.rs`'s
per-`BlockRecord` XRecord scanning pattern suggests per-block-record is
natural to copy, but sketch scopes and a shared parameter table are not
the same kind of thing (scopes are naturally per-sketch; parameters are
naturally document-wide) — don't copy the pattern mechanically without
checking this distinction holds.
5. **Naming collisions / reserved words** — needs a validation rule (valid
identifier characters, no collision with another parameter, sensible
error on a malformed formula) before the UI stage.
## 6. Progress
Nothing built yet. Update this section (not a separate file) after each
stage, matching the reporting pattern used throughout
`docs/parametric_system_design.md`'s own "Progress" section.
File diff suppressed because it is too large. Load diff