brep_render/sketch/solve.rs
1//! Solve wiring — call the kernel's 2D constraint solver in-process.
2//!
3//! `brep-render` links the kernel crate directly (a plain Rust path dependency,
4//! for BOTH native and the wasm canvas build), so the solver is reached by a
5//! direct `brep_kernel::solve_sketch(&SolveSketchRequest)` call on every
6//! target — there is NO wasm-bindgen JSON hop (that boundary only existed for the
7//! previous app calling INTO the wasm module from the browser; here the solver is
8//! compiled into the same binary/module). The solver is pure numeric Rust with no `std::time`,
9//! so it is wasm-clean.
10//!
11//! The solver returns `{sketch: {points, geometries, constraints, diagnostics}}`.
12//! We split that into the solved [`SketchDoc`] (the three editable arrays) and the
13//! read-only [`SketchDiagnostics`] the overlay coloring + DOF readout consume.
14
15use super::doc::{SketchDiagnostics, SketchDoc};
16use brep_kernel::{solve_sketch, SolveSketchRequest};
17
18/// User-tunable 2D solver knobs (the "Solver Settings" section), all optional
19/// — `None` keeps the kernel's default. Held on the [`super::SketchSession`] and
20/// read by every `resolve`, so a change re-solves with the new settings without
21/// threading params through each edit path.
22#[derive(Clone, Debug, PartialEq)]
23pub struct SketchSolverSettings {
24 /// Convergence tolerance (kernel default when `None`).
25 pub tolerance: Option<f64>,
26 /// Max solver iterations (defaults to 1000 when `None`).
27 pub iterations: Option<u32>,
28 /// Distance-slide threshold ratio (kernel default when `None`).
29 pub distance_slide_threshold_ratio: Option<f64>,
30 /// Distance-slide step ratio (kernel default when `None`).
31 pub distance_slide_step_ratio: Option<f64>,
32 /// Distance-slide minimum step (kernel default when `None`).
33 pub distance_slide_min_step: Option<f64>,
34}
35
36impl Default for SketchSolverSettings {
37 fn default() -> Self {
38 // All `None`/default → byte-identical to the historical hard-coded request
39 // (iterations 1000, everything else the kernel default), so an untouched
40 // solver behaves exactly as before this knob existed.
41 Self {
42 tolerance: None,
43 iterations: Some(1000),
44 distance_slide_threshold_ratio: None,
45 distance_slide_step_ratio: None,
46 distance_slide_min_step: None,
47 }
48 }
49}
50
51/// Solve `doc` and return the solved coordinates plus the constraint diagnostics.
52///
53/// `remove_implied_duplicates` is left off (the display path never edits
54/// constraints, so we keep every authored constraint) and the Newton polish is
55/// left on (`polish: None` → default true) so the displayed coordinates match
56/// what a committed feature-pipeline solve would produce.
57pub fn solve(doc: &SketchDoc) -> Result<(SketchDoc, SketchDiagnostics), String> {
58 solve_with(doc, &SketchSolverSettings::default())
59}
60
61/// Like [`solve`], but with user-tunable [`SketchSolverSettings`] (the Solver
62/// Settings panel). Default settings reproduce [`solve`] byte-for-byte.
63pub fn solve_with(
64 doc: &SketchDoc,
65 settings: &SketchSolverSettings,
66) -> Result<(SketchDoc, SketchDiagnostics), String> {
67 let sketch = serde_json::to_value(doc).map_err(|e| format!("sketch serialize: {e}"))?;
68 let request = SolveSketchRequest {
69 sketch,
70 iterations: settings.iterations,
71 remove_implied_duplicates: false,
72 tolerance: settings.tolerance,
73 distance_slide_threshold_ratio: settings.distance_slide_threshold_ratio,
74 distance_slide_step_ratio: settings.distance_slide_step_ratio,
75 distance_slide_min_step: settings.distance_slide_min_step,
76 polish: None,
77 };
78
79 let response = solve_sketch(&request)?;
80 let solved = response
81 .get("sketch")
82 .cloned()
83 .ok_or_else(|| "solve_sketch: response missing `sketch`".to_string())?;
84
85 let diagnostics = match solved.get("diagnostics").cloned() {
86 Some(value) => {
87 serde_json::from_value(value).map_err(|e| format!("diagnostics parse: {e}"))?
88 }
89 None => SketchDiagnostics::default(),
90 };
91 let solved_doc: SketchDoc =
92 serde_json::from_value(solved).map_err(|e| format!("solved sketch parse: {e}"))?;
93
94 Ok((solved_doc, diagnostics))
95}