Skip to main content

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