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

+43
View File
@@ -240,6 +240,17 @@ impl DocumentTab {
.and_then(|index| self.block_edits.get_mut(index))
}
/// The [`crate::scene::sketch_constraints::SketchScope`] a new persistent
/// constraint should attach to right now — whatever's actually being
/// edited: the open block definition if a BEDIT session is active, model
/// space otherwise.
pub(super) fn current_sketch_scope(&self) -> crate::scene::sketch_constraints::SketchScope {
match self.active_block_edit_session() {
Some(session) => crate::scene::sketch_constraints::SketchScope::Block(session.br_handle),
None => crate::scene::sketch_constraints::SketchScope::ModelSpace,
}
}
/// The active WCS↔UCS converter for this tab — identity when no UCS is set.
/// Every consumer that needs UCS-relative coordinates goes through this.
pub(super) fn ucs_xform(&self) -> super::helpers::UcsXform {
@@ -646,6 +657,7 @@ impl DocumentTab {
pub(super) enum HistorySnapshot {
Delta(DeltaSnapshot),
ObjectVisibility(ObjectVisibilitySnapshot),
SketchConstraints(SketchConstraintsSnapshot),
}
impl HistorySnapshot {
@@ -653,6 +665,7 @@ impl HistorySnapshot {
match self {
HistorySnapshot::Delta(d) => &d.label,
HistorySnapshot::ObjectVisibility(v) => &v.label,
HistorySnapshot::SketchConstraints(s) => &s.label,
}
}
@@ -699,10 +712,40 @@ impl HistorySnapshot {
.saturating_add(v.selected_after.len())
.saturating_mul(16)
.saturating_add(v.label.len()),
HistorySnapshot::SketchConstraints(s) => s
.before
.as_ref()
.map_or(0, |set| set.constraints.len())
.saturating_add(s.after.as_ref().map_or(0, |set| set.constraints.len()))
.saturating_mul(96) // rough per-constraint size (a handful of refs + an id/kind/f64)
.saturating_add(s.label.len()),
}
}
}
/// Undo/redo image for one persistent-constraint edit (design doc §5.1/§9):
/// a `SketchConstraintSet` add/remove, captured as a whole-set before/after
/// pair rather than a per-constraint diff — sketches are small, so cloning
/// the whole set is cheap, and it sidesteps needing a separate "undo one
/// constraint add" vs. "undo one constraint remove" representation. Kept as
/// its own `HistorySnapshot` variant (not folded into `DeltaSnapshot`) since
/// `SketchConstraintSet` lives on `Scene`, not in the entity/object store
/// `DeltaSnapshot` already covers — see the design doc's §5.1 discussion of
/// why this needs its own path. A geometry-changing constraint add (the
/// common case) pushes this *alongside* its own `DeltaSnapshot` entry rather
/// than merged into one atomic step — two adjacent undo presses instead of
/// one, a deliberate scope-down from the design doc's recommended "live
/// XRecord" approach (§5.1(b)), which needs stage 4 (save/load) wired up
/// first; this variant needs nothing beyond what already exists.
#[derive(Clone)]
pub(super) struct SketchConstraintsSnapshot {
pub(super) scope: crate::scene::sketch_constraints::SketchScope,
/// `None` when the scope had no constraint set at all yet.
pub(super) before: Option<crate::scene::sketch_constraints::SketchConstraintSet>,
pub(super) after: Option<crate::scene::sketch_constraints::SketchConstraintSet>,
pub(super) label: String,
}
/// Symmetric undo/redo image for session-only object visibility. It contains
/// no document entity data and therefore can never make isolation serializable.
#[derive(Clone)]