brep_kernel/feature_pipeline/assembly/constraints.rs
1//! The ten assembly-constraint definitions — ONE MODULE PER CONSTRAINT, the
2//! feature-module pattern: each `constraints/<type>.rs` owns everything about its
3//! type — its [`ConstraintTypeDef`] row (names, element count range, duplicate
4//! family, the `applicable` selection-context predicate), its schema entry, and (for
5//! the mate-mapped types) its `map` function. `fixed` has no `map` — grounding is
6//! handled by [`super::lifecycle`] before body building.
7
8use crate::feature_pipeline::SelectionProbe;
9
10pub(crate) mod angle;
11pub(crate) mod center;
12pub(crate) mod coincident;
13pub(crate) mod concentric;
14pub(crate) mod distance;
15pub(crate) mod fixed;
16pub(crate) mod parallel;
17pub(crate) mod perpendicular;
18pub(crate) mod tangent;
19pub(crate) mod touch_align;
20
21/// One constraint type's static definition.
22pub struct ConstraintTypeDef {
23 /// Canonical `type` string (lowercase snake — the persisted key).
24 pub type_id: &'static str,
25 /// Id-mint prefix (`CONC` → `CONC3`).
26 pub short_name: &'static str,
27 /// The type's ONE icon — a single catalogued glyph character (the app's
28 /// `assets/glyphs` catalog, where Coincident shares `≡` with the sketch
29 /// solver's coincident). Every surface that stands for the type by picture
30 /// alone — the workbench toolbar button, the viewport chip, the tree row's
31 /// glyph column — reads THIS field; nothing derives it from `long_name`.
32 pub icon: &'static str,
33 /// Display label: `"{icon} {label}"` (the context bar draws the leading
34 /// glyph as artwork). Pinned to the other two by a test below.
35 pub long_name: &'static str,
36 /// Plain label for messages ("Duplicate … conflicts with Distance
37 /// constraint DIST4.").
38 pub label: &'static str,
39 /// The accepted `elements` count range, inclusive: fixed takes exactly 1,
40 /// the pairing types exactly 2, center 3 or 4 (two width faces plus a one-
41 /// or two-element tab). The lifecycle marks a count outside the range
42 /// `incomplete`; the schema's `minSelections`/`maxSelections` mirror it.
43 pub min_elements: usize,
44 pub max_elements: usize,
45 /// Member of the duplicate-detection family (overlapping selection pairs
46 /// conflict across these types).
47 pub duplicate_family: bool,
48 /// Selection-context applicability (the feature `context_applicable`
49 /// pattern, [`crate::feature_pipeline::context_offer`]): does the current
50 /// selection make creating this constraint meaningful? Every element must
51 /// be component geometry ([`super::mapping`]'s `resolve_element` rejects
52 /// everything else), and the counted kinds are the ones the app's element
53 /// seeder can actually produce (faces/edges, solids as COMPONENT refs —
54 /// never vertices, whose `@`-refs only the modal picker builds).
55 pub applicable: fn(&SelectionProbe) -> bool,
56}
57
58/// Spec §4 table order — also the panel's `+` dropdown order.
59pub const CONSTRAINT_TYPES: [ConstraintTypeDef; 10] = [
60 fixed::DEF,
61 coincident::DEF,
62 touch_align::DEF,
63 parallel::DEF,
64 distance::DEF,
65 angle::DEF,
66 concentric::DEF,
67 perpendicular::DEF,
68 tangent::DEF,
69 center::DEF,
70];
71
72/// Look up a type definition by its canonical `type` string.
73pub fn constraint_type(type_id: &str) -> Option<&'static ConstraintTypeDef> {
74 CONSTRAINT_TYPES.iter().find(|def| def.type_id == type_id)
75}
76
77/// `number` params are expression-capable against the part's expression scope
78/// (the dialog engine's established convention).
79pub fn constraint_schema_catalogue() -> serde_json::Value {
80 serde_json::Value::Array(vec![
81 fixed::schema(),
82 coincident::schema(),
83 touch_align::schema(),
84 parallel::schema(),
85 distance::schema(),
86 angle::schema(),
87 concentric::schema(),
88 perpendicular::schema(),
89 tangent::schema(),
90 center::schema(),
91 ])
92}
93
94/// A two-element pairing across TWO distinct components (a pair on one rigid
95/// body cannot move anything), counting `seedable` selected elements — the
96/// shared shape behind every pairing type's `applicable`.
97pub(super) fn pair_applicable(probe: &SelectionProbe, seedable: usize) -> bool {
98 probe.all_component && probe.components == 2 && seedable == 2
99}
100
101// --- shared schema-field builders (each module's `schema()` composes these) --
102
103pub(super) fn id_field() -> serde_json::Value {
104 serde_json::json!({
105 "type": "string",
106 "default_value": null,
107 "hint": "Unique identifier for the constraint"
108 })
109}
110
111/// The `elements` reference field: `min..=max` picks from `filter` (the type
112/// def's `min_elements..=max_elements` — a test below pins the two together).
113pub(super) fn elements_field(
114 filter: &[&str],
115 min: usize,
116 max: usize,
117 hint: &str,
118) -> serde_json::Value {
119 serde_json::json!({
120 "type": "reference_selection",
121 "selectionFilter": filter,
122 "multiple": max > 1,
123 "minSelections": min,
124 "maxSelections": max,
125 "default_value": null,
126 "hint": hint
127 })
128}
129
130pub(super) fn schema_entry(
131 def: &ConstraintTypeDef,
132 params: serde_json::Value,
133) -> serde_json::Value {
134 serde_json::json!({
135 "type": def.type_id,
136 "shortName": def.short_name,
137 "longName": def.long_name,
138 "label": def.label,
139 "icon": def.icon,
140 "inputParamsSchema": params,
141 })
142}
143