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