brep_kernel/solvers/sketch_solver/
api.rs1use super::*;
2
3fn ordered_pair_key(a: i64, b: i64) -> String {
8 if a <= b {
9 format!("{a},{b}")
10 } else {
11 format!("{b},{a}")
12 }
13}
14
15fn constraint_signature(
16 constraint_type: &str,
17 points: &[Value],
18 canonical: &HashMap<i64, i64>,
19) -> Option<String> {
20 let canon = |value: &Value| -> Option<i64> {
21 let pid = js_parse_int(Some(value))?;
22 Some(*canonical.get(&pid).unwrap_or(&pid))
23 };
24 if constraint_type == "⇌" && points.len() >= 4 {
25 let p0 = canon(&points[0])?;
26 let p1 = canon(&points[1])?;
27 let p2 = canon(&points[2])?;
28 let p3 = canon(&points[3])?;
29 let a = ordered_pair_key(p0, p1);
30 let b = ordered_pair_key(p2, p3);
31 return Some(if a <= b {
32 format!("⇌:{a}|{b}")
33 } else {
34 format!("⇌:{b}|{a}")
35 });
36 }
37 if constraint_type == "⏛" && points.len() >= 3 {
38 let p0 = canon(&points[0])?;
39 let p1 = canon(&points[1])?;
40 let p2 = canon(&points[2])?;
41 return Some(format!("⏛:{}|{}", ordered_pair_key(p0, p1), p2));
42 }
43 None
44}
45
46fn build_coincident_canonical_map(constraints: &[Value]) -> HashMap<i64, i64> {
47 fn find(parent: &mut HashMap<i64, i64>, x: i64) -> i64 {
48 crate::union_find::find_keyed_root(parent, x)
49 }
50
51 let mut parent: HashMap<i64, i64> = HashMap::default();
52 for constraint in constraints {
53 let Some(obj) = constraint.as_object() else {
54 continue;
55 };
56 if truthy(obj.get("temporary")) {
57 continue;
58 }
59 if obj.get("type").and_then(Value::as_str) != Some("≡") {
60 continue;
61 }
62 let Some(points) = obj.get("points").and_then(Value::as_array) else {
63 continue;
64 };
65 if points.len() < 2 {
66 continue;
67 }
68 let (Some(p0), Some(p1)) = (js_parse_int(points.first()), js_parse_int(points.get(1)))
69 else {
70 continue;
71 };
72 let ra = find(&mut parent, p0);
73 let rb = find(&mut parent, p1);
74 if ra != rb {
75 if ra < rb {
76 parent.insert(rb, ra);
77 } else {
78 parent.insert(ra, rb);
79 }
80 }
81 }
82 let keys: Vec<i64> = parent.keys().copied().collect();
83 let mut out = HashMap::default();
84 for key in keys {
85 let root = find(&mut parent, key);
86 out.insert(key, root);
87 }
88 out
89}
90
91fn remove_constraints_duplicated_by_implied_geometry(sketch: &mut Value) {
92 let Some(obj) = sketch.as_object_mut() else {
93 return;
94 };
95 let Some(geometries) = obj.get("geometries").and_then(Value::as_array).cloned() else {
96 return;
97 };
98 let Some(constraints) = obj.get("constraints").and_then(Value::as_array).cloned() else {
99 return;
100 };
101
102 let canonical = build_coincident_canonical_map(&constraints);
103
104 let mut implied: std::collections::HashSet<String> = std::collections::HashSet::new();
105 for geometry in &geometries {
106 let Some(geo) = geometry.as_object() else {
107 continue;
108 };
109 let Some(points) = geo.get("points").and_then(Value::as_array) else {
110 continue;
111 };
112 let geometry_type = geo.get("type").and_then(Value::as_str).unwrap_or("");
113 if geometry_type == "arc" && points.len() >= 3 {
114 let candidate = vec![
115 points[0].clone(),
116 points[1].clone(),
117 points[0].clone(),
118 points[2].clone(),
119 ];
120 if let Some(key) = constraint_signature("⇌", &candidate, &canonical) {
121 implied.insert(key);
122 }
123 } else if is_spline_geometry_type(geometry_type) && points.len() >= 4 {
124 let seg_count = (points.len() - 1) / 3;
125 let last_anchor_index = seg_count * 3;
126 let mut i = 3usize;
127 while i < last_anchor_index {
128 let prev_handle = &points[i - 1];
129 let anchor = &points[i];
130 let next_handle = &points[i + 1];
131 let nullish = |v: &Value| matches!(v, Value::Null);
132 if !nullish(prev_handle) && !nullish(anchor) && !nullish(next_handle) {
133 let same = |a: &Value, b: &Value| point_key(a) == point_key(b);
134 if !same(prev_handle, anchor)
135 && !same(next_handle, anchor)
136 && !same(prev_handle, next_handle)
137 {
138 let candidate =
139 vec![prev_handle.clone(), next_handle.clone(), anchor.clone()];
140 if let Some(key) = constraint_signature("⏛", &candidate, &canonical) {
141 implied.insert(key);
142 }
143 }
144 }
145 i += 3;
146 }
147 }
148 }
149 if implied.is_empty() {
150 return;
151 }
152
153 let filtered: Vec<Value> = constraints
154 .into_iter()
155 .filter(|constraint| {
156 let Some(c) = constraint.as_object() else {
157 return true;
158 };
159 if truthy(c.get("temporary")) {
160 return true;
161 }
162 let constraint_type = c.get("type").and_then(Value::as_str).unwrap_or("");
163 let Some(points) = c.get("points").and_then(Value::as_array) else {
164 return true;
165 };
166 match constraint_signature(constraint_type, points, &canonical) {
167 Some(key) => !implied.contains(&key),
168 None => true,
169 }
170 })
171 .collect();
172 obj.insert("constraints".into(), Value::Array(filtered));
173}
174
175pub fn solve_sketch(request: &SolveSketchRequest) -> Result<Value, String> {
180 let mut settings = SketchSolverSettings::default();
181 if let Some(tolerance) = request.tolerance {
182 settings.tolerance = tolerance;
183 }
184 if let Some(value) = request.distance_slide_threshold_ratio {
185 if value.is_finite() && value >= 0.0 {
186 settings.distance_slide_threshold_ratio = value;
187 }
188 }
189 if let Some(value) = request.distance_slide_step_ratio {
190 if value.is_finite() && value >= 0.0 {
191 settings.distance_slide_step_ratio = value;
192 }
193 }
194 if let Some(value) = request.distance_slide_min_step {
195 if value.is_finite() && value >= 0.0 {
196 settings.distance_slide_min_step = value;
197 }
198 }
199 if let Some(polish) = request.polish {
200 settings.newton_polish = polish;
201 }
202
203 let mut sketch = request.sketch.clone();
204 if request.remove_implied_duplicates {
205 remove_constraints_duplicated_by_implied_geometry(&mut sketch);
206 }
207
208 let iterations = request.iterations.unwrap_or(1500);
209 let mut engine = Engine::new(&sketch, settings)?;
210 let solved = engine.solve(iterations)?;
211
212 let mut response = Map::new();
213 response.insert("sketch".into(), solved);
214 Ok(Value::Object(response))
215}
216
217pub fn solve_sketch_from_json(request_json: &str) -> Result<String, String> {
218 let request: SolveSketchRequest =
219 serde_json::from_str(request_json).map_err(|error| error.to_string())?;
220 let response = solve_sketch(&request)?;
221 serde_json::to_string(&response).map_err(|error| error.to_string())
222}