Skip to main content

brep_kernel/feature_pipeline/assembly/
constraints.rs

1//! The ten assembly-constraint definitions (build-spec §4) — ONE MODULE PER
2//! CONSTRAINT, the feature-module pattern: each `constraints/<type>.rs` owns
3//! everything about its type — its [`ConstraintTypeDef`] row (names, element
4//! count range, duplicate family, the `applicable` selection-context predicate), its
5//! schema entry, and (for the mate-mapped types) its `map` function. This root
6//! only AGGREGATES them in the spec §4 table order (`feature_pipeline/schema.rs`
7//! pattern) and serves the catalogue, which joins the kernel schema export
8//! under its own namespace (`feature_schemas_json` → `assemblyConstraints`) so
9//! the app dialog engine renders constraint dialogs exactly like feature
10//! dialogs. `fixed` has no `map` — grounding is handled by [`super::lifecycle`]
11//! before body building.
12
13use 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
26/// One constraint type's static definition.
27pub struct ConstraintTypeDef {
28    /// Canonical `type` string (lowercase snake — the persisted key).
29    pub type_id: &'static str,
30    /// Id-mint prefix (`CONC` → `CONC3`).
31    pub short_name: &'static str,
32    /// The type's ONE icon — a single catalogued glyph character (the app's
33    /// `assets/glyphs` catalog, where Coincident shares `≡` with the sketch
34    /// solver's coincident). Every surface that stands for the type by picture
35    /// alone — the workbench toolbar button, the viewport chip, the tree row's
36    /// glyph column — reads THIS field; nothing derives it from `long_name`.
37    pub icon: &'static str,
38    /// Display label: `"{icon} {label}"` (the context bar draws the leading
39    /// glyph as artwork). Pinned to the other two by a test below.
40    pub long_name: &'static str,
41    /// Plain label for messages ("Duplicate … conflicts with Distance
42    /// constraint DIST4.").
43    pub label: &'static str,
44    /// The accepted `elements` count range, inclusive: fixed takes exactly 1,
45    /// the pairing types exactly 2, center 3 or 4 (two width faces plus a one-
46    /// or two-element tab). The lifecycle marks a count outside the range
47    /// `incomplete`; the schema's `minSelections`/`maxSelections` mirror it.
48    pub min_elements: usize,
49    pub max_elements: usize,
50    /// Member of the duplicate-detection family (overlapping selection pairs
51    /// conflict across these types — requirements §4.3).
52    pub duplicate_family: bool,
53    /// Selection-context applicability (the feature `context_applicable`
54    /// pattern, [`crate::feature_pipeline::context_offer`]): does the current
55    /// selection make creating this constraint meaningful? Every element must
56    /// be component geometry ([`super::mapping`]'s `resolve_element` rejects
57    /// everything else), and the counted kinds are the ones the app's element
58    /// seeder can actually produce (faces/edges, solids as COMPONENT refs —
59    /// never vertices, whose `@`-refs only the modal picker builds).
60    pub applicable: fn(&SelectionProbe) -> bool,
61}
62
63/// Spec §4 table order — also the panel's `+` dropdown order.
64pub 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
77/// Look up a type definition by its canonical `type` string.
78pub fn constraint_type(type_id: &str) -> Option<&'static ConstraintTypeDef> {
79    CONSTRAINT_TYPES.iter().find(|def| def.type_id == type_id)
80}
81
82/// The ten constraint schemas, spec §4 order. `number` params are
83/// expression-capable against the part's expression scope (the dialog engine's
84/// established convention).
85pub 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
100/// A two-element pairing across TWO distinct components (a pair on one rigid
101/// body cannot move anything), counting `seedable` selected elements — the
102/// shared shape behind every pairing type's `applicable`.
103pub(super) fn pair_applicable(probe: &SelectionProbe, seedable: usize) -> bool {
104    probe.all_component && probe.components == 2 && seedable == 2
105}
106
107// --- shared schema-field builders (each module's `schema()` composes these) --
108
109pub(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
117/// The `elements` reference field: `min..=max` picks from `filter` (the type
118/// def's `min_elements..=max_elements` — a test below pins the two together).
119pub(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// BREP private tests: 82430eaff3489bbf