1use crate::feature_pipeline::SelectionProbe;
14
15pub(crate) mod angle;
16pub(crate) mod center;
17pub(crate) mod coincident;
18pub(crate) mod concentric;
19pub(crate) mod distance;
20pub(crate) mod fixed;
21pub(crate) mod parallel;
22pub(crate) mod perpendicular;
23pub(crate) mod tangent;
24pub(crate) mod touch_align;
25
26pub struct ConstraintTypeDef {
28 pub type_id: &'static str,
30 pub short_name: &'static str,
32 pub icon: &'static str,
38 pub long_name: &'static str,
41 pub label: &'static str,
44 pub min_elements: usize,
49 pub max_elements: usize,
50 pub duplicate_family: bool,
53 pub applicable: fn(&SelectionProbe) -> bool,
61}
62
63pub const CONSTRAINT_TYPES: [ConstraintTypeDef; 10] = [
65 fixed::DEF,
66 coincident::DEF,
67 touch_align::DEF,
68 parallel::DEF,
69 distance::DEF,
70 angle::DEF,
71 concentric::DEF,
72 perpendicular::DEF,
73 tangent::DEF,
74 center::DEF,
75];
76
77pub fn constraint_type(type_id: &str) -> Option<&'static ConstraintTypeDef> {
79 CONSTRAINT_TYPES.iter().find(|def| def.type_id == type_id)
80}
81
82pub fn constraint_schema_catalogue() -> serde_json::Value {
86 serde_json::Value::Array(vec![
87 fixed::schema(),
88 coincident::schema(),
89 touch_align::schema(),
90 parallel::schema(),
91 distance::schema(),
92 angle::schema(),
93 concentric::schema(),
94 perpendicular::schema(),
95 tangent::schema(),
96 center::schema(),
97 ])
98}
99
100pub(super) fn pair_applicable(probe: &SelectionProbe, seedable: usize) -> bool {
104 probe.all_component && probe.components == 2 && seedable == 2
105}
106
107pub(super) fn id_field() -> serde_json::Value {
110 serde_json::json!({
111 "type": "string",
112 "default_value": null,
113 "hint": "Unique identifier for the constraint"
114 })
115}
116
117pub(super) fn elements_field(
120 filter: &[&str],
121 min: usize,
122 max: usize,
123 hint: &str,
124) -> serde_json::Value {
125 serde_json::json!({
126 "type": "reference_selection",
127 "selectionFilter": filter,
128 "multiple": max > 1,
129 "minSelections": min,
130 "maxSelections": max,
131 "default_value": null,
132 "hint": hint
133 })
134}
135
136pub(super) fn schema_entry(
137 def: &ConstraintTypeDef,
138 params: serde_json::Value,
139) -> serde_json::Value {
140 serde_json::json!({
141 "type": def.type_id,
142 "shortName": def.short_name,
143 "longName": def.long_name,
144 "label": def.label,
145 "icon": def.icon,
146 "inputParamsSchema": params,
147 })
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153
154 #[test]
159 fn every_constraint_type_has_one_icon_that_leads_its_long_name() {
160 for def in CONSTRAINT_TYPES.iter() {
161 assert_eq!(def.icon.chars().count(), 1, "{}: one glyph", def.type_id);
162 assert_eq!(
163 def.long_name,
164 format!("{} {}", def.icon, def.label),
165 "{}: long_name is icon + label",
166 def.type_id
167 );
168 }
169 let schema = &constraint_schema_catalogue()[1];
170 assert_eq!(schema["type"], "coincident");
171 assert_eq!(schema["icon"], "\u{2261}");
172 assert_eq!(schema["label"], "Coincident");
173 }
174
175 #[test]
179 fn every_schema_elements_range_mirrors_its_def() {
180 let catalogue = constraint_schema_catalogue();
181 let schemas = catalogue.as_array().expect("array");
182 assert_eq!(schemas.len(), CONSTRAINT_TYPES.len());
183 for (schema, def) in schemas.iter().zip(CONSTRAINT_TYPES.iter()) {
184 assert_eq!(schema["type"], def.type_id);
185 let elements = &schema["inputParamsSchema"]["elements"];
186 assert_eq!(elements["minSelections"], def.min_elements, "{}", def.type_id);
187 assert_eq!(elements["maxSelections"], def.max_elements, "{}", def.type_id);
188 assert_eq!(elements["multiple"], def.max_elements > 1, "{}", def.type_id);
189 assert!(def.min_elements >= 1 && def.min_elements <= def.max_elements);
190 }
191 let center = schemas.last().expect("center is the tenth type");
192 assert_eq!(center["type"], "center");
193 assert_eq!(center["inputParamsSchema"]["elements"]["minSelections"], 3);
194 assert_eq!(center["inputParamsSchema"]["elements"]["maxSelections"], 4);
195 }
196
197 fn probe(
198 solids: usize,
199 faces: usize,
200 edges: usize,
201 components: usize,
202 all_component: bool,
203 ) -> SelectionProbe {
204 SelectionProbe {
205 solids,
206 faces,
207 edges,
208 components,
209 all_component,
210 ..Default::default()
211 }
212 }
213
214 #[test]
217 fn constraint_applicability_shapes() {
218 let one_component = probe(1, 0, 0, 1, true);
219 let two_faces_two_components = probe(0, 2, 0, 2, true);
220 let face_edge_two_components = probe(0, 1, 1, 2, true);
221 let two_faces_one_component = probe(0, 2, 0, 1, true);
222 let non_component = probe(0, 2, 0, 0, false);
223
224 let applicable = |type_id: &str, p: &SelectionProbe| {
225 (constraint_type(type_id).expect(type_id).applicable)(p)
226 };
227
228 assert!(applicable("fixed", &one_component));
229 assert!(!applicable("fixed", &two_faces_two_components));
230
231 for pair in [
232 "coincident",
233 "touch_align",
234 "parallel",
235 "distance",
236 "angle",
237 "concentric",
238 "perpendicular",
239 ] {
240 assert!(applicable(pair, &two_faces_two_components), "{pair}: 2 faces / 2 comps");
241 assert!(applicable(pair, &face_edge_two_components), "{pair}: face+edge / 2 comps");
242 assert!(!applicable(pair, &two_faces_one_component), "{pair}: same component");
244 assert!(!applicable(pair, &non_component), "{pair}: non-component");
246 }
247
248 assert!(applicable("tangent", &two_faces_two_components));
250 assert!(!applicable("tangent", &face_edge_two_components));
251
252 assert!(applicable("coincident", &probe(2, 0, 0, 2, true)));
254
255 assert!(!applicable("center", &two_faces_two_components));
259 assert!(applicable("center", &probe(0, 3, 0, 2, true)), "2 faces + 1 face");
260 assert!(applicable("center", &probe(0, 4, 0, 2, true)), "2 faces + 2 faces");
261 assert!(applicable("center", &probe(0, 2, 1, 2, true)), "2 faces + 1 edge");
262 assert!(!applicable("center", &probe(0, 1, 2, 2, true)), "one face cannot be a width");
263 assert!(!applicable("center", &probe(0, 5, 0, 2, true)), "five is too many");
264 assert!(!applicable("center", &probe(0, 3, 0, 1, true)), "same component");
265 assert!(!applicable("center", &probe(0, 3, 0, 0, false)), "non-component");
266 }
267}