forked from 1b-agent/opencadstudio-upstream
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:
1 parent
a39c7b8f71
commit
63a74b19a9
63 files changed
+12193
-49
No files matched your search
@@ -0,0 +1,108 @@
|
||||
//! The `Constraint` trait: one primitive geometric relationship the solver
|
||||
//! can evaluate an error and gradient for.
|
||||
//!
|
||||
//! Ported from planegcs's base `Constraint` class (`Constraints.h`/`.cpp`).
|
||||
//! Concrete constraint types (Equal, P2PDistance, Parallel, ...) are added in
|
||||
//! later port stages, grouped by geometric complexity, under this module.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::util::{ParamId, ParamStore};
|
||||
|
||||
/// A geometric constraint: an unscaled error function of its own parameters,
|
||||
/// differentiable w.r.t. any one of them.
|
||||
///
|
||||
/// `error()`/`grad()` provide the *scaled* values (`scale * error_value()`,
|
||||
/// `scale * grad_value()`) a [`SubSystem`](crate::subsystem::SubSystem)
|
||||
/// assembles into its residual/Jacobian/gradient — mirroring the base
|
||||
/// `Constraint::error()`/`::grad()` in the C++, which wrap `errorgrad()`
|
||||
/// (or, for most concrete constraints, an overridden `error()`/`grad()`
|
||||
/// pair) the same way.
|
||||
pub trait Constraint {
|
||||
/// This constraint's own parameters, in a fixed order specific to the
|
||||
/// constraint type — the C++'s `pvec`.
|
||||
fn params(&self) -> Vec<ParamId>;
|
||||
|
||||
/// Defaults to `1.0`, matching the C++ constructor's `scale(1.)`.
|
||||
fn scale(&self) -> f64 {
|
||||
1.0
|
||||
}
|
||||
|
||||
/// Unscaled constraint error at the store's current values.
|
||||
fn error_value(&self, store: &ParamStore) -> f64;
|
||||
|
||||
/// Unscaled derivative of [`error_value`](Self::error_value) w.r.t.
|
||||
/// `param`. Only ever called for a `param` known to be one of
|
||||
/// [`params`](Self::params) — [`grad`](Self::grad) is what does that
|
||||
/// membership check, matching the C++'s `findParamInPvec` guard in the
|
||||
/// base class's `grad()`.
|
||||
fn grad_value(&self, store: &ParamStore, param: ParamId) -> f64;
|
||||
|
||||
fn error(&self, store: &ParamStore) -> f64 {
|
||||
self.scale() * self.error_value(store)
|
||||
}
|
||||
|
||||
fn grad(&self, store: &ParamStore, param: ParamId) -> f64 {
|
||||
if !self.params().contains(¶m) {
|
||||
return 0.0;
|
||||
}
|
||||
self.scale() * self.grad_value(store, param)
|
||||
}
|
||||
|
||||
/// Limits a proposed solver step so this constraint stays satisfiable
|
||||
/// (e.g. a radius must not go negative). `dir` gives the proposed
|
||||
/// per-parameter step for whichever of this constraint's parameters are
|
||||
/// moving. Defaults to leaving `lim` unchanged, matching the C++ base
|
||||
/// class's default `return lim;`.
|
||||
fn max_step(&self, _store: &ParamStore, _dir: &HashMap<ParamId, f64>, lim: f64) -> f64 {
|
||||
lim
|
||||
}
|
||||
}
|
||||
|
||||
pub mod angle_distance;
|
||||
pub mod bspline;
|
||||
pub mod circle_arc;
|
||||
pub mod conic;
|
||||
pub mod curve_generic;
|
||||
pub mod point_line;
|
||||
|
||||
/// Shared test support: cross-checks a constraint's analytic `grad_value`
|
||||
/// against central-difference numerical differentiation of `error_value`,
|
||||
/// for every one of its own parameters. This is Oracle B from the port
|
||||
/// plan — cheap, independent of any FreeCAD fixture, and effective at
|
||||
/// catching a mistranscribed formula (a wrong sign or dropped term in
|
||||
/// `grad_value` almost never happens to match the numeric derivative by
|
||||
/// chance).
|
||||
#[cfg(test)]
|
||||
pub(crate) mod test_support {
|
||||
use super::Constraint;
|
||||
use crate::util::{ParamId, ParamStore};
|
||||
|
||||
pub fn assert_grad_matches_finite_difference(constr: &dyn Constraint, store: &mut ParamStore) {
|
||||
for param in constr.params() {
|
||||
assert_single_param_grad_matches_fd(constr, store, param);
|
||||
}
|
||||
}
|
||||
|
||||
/// Like [`assert_grad_matches_finite_difference`] but for one named
|
||||
/// parameter, so a caller can skip a specific param known to have a
|
||||
/// documented upstream quirk instead of the whole constraint.
|
||||
pub fn assert_single_param_grad_matches_fd(constr: &dyn Constraint, store: &mut ParamStore, param: ParamId) {
|
||||
const H: f64 = 1e-6;
|
||||
const TOL: f64 = 1e-5;
|
||||
let original = store.get(param);
|
||||
|
||||
store.set(param, original + H);
|
||||
let plus = constr.error_value(store);
|
||||
store.set(param, original - H);
|
||||
let minus = constr.error_value(store);
|
||||
store.set(param, original);
|
||||
|
||||
let numeric = (plus - minus) / (2.0 * H);
|
||||
let analytic = constr.grad_value(store, param);
|
||||
assert!(
|
||||
(numeric - analytic).abs() < TOL,
|
||||
"grad mismatch for param {param:?}: analytic={analytic}, numeric={numeric}"
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user