Skip to main content

brep_kernel/feature_pipeline/pmi/annotations/
mod.rs

1//! The PMI annotation type table — nine types, each a module exposing its
2//! [`PmiTypeDef`] (`DEF`), its `schema()` and its resolver. The table is the
3//! third applicability family beside the feature catalogue and the assembly
4//! constraint types: an annotation predicate accepts plain AND component
5//! geometry.
6
7pub mod angle;
8pub mod datum;
9pub mod explode;
10pub mod fcf;
11pub mod hole_callout;
12pub mod leader;
13pub mod linear;
14pub mod note;
15pub mod radial;
16
17use super::{PmiAnnotation, PmiContext, Resolved};
18use crate::feature_pipeline::SelectionProbe;
19use serde_json::{Map, Value};
20
21/// One annotation type.
22pub struct PmiTypeDef {
23    /// Canonical `type` string (the persisted key).
24    pub type_id: &'static str,
25    /// Id-mint prefix (`DIM` → `DIM3`).
26    pub short_name: &'static str,
27    /// The type's ONE icon — a catalogued glyph character (the app's
28    /// `assets/glyphs` set). Every surface that stands for the type by picture
29    /// (tree row, viewport chip, context offer) reads this.
30    pub icon: &'static str,
31    /// `"{icon} {label}"`.
32    pub long_name: &'static str,
33    /// Plain label for messages and the tree.
34    pub label: &'static str,
35    /// Does the current selection make creating this annotation meaningful?
36    pub applicable: fn(&SelectionProbe) -> bool,
37    /// The annotation's schema (`{type, shortName, longName, label, icon,
38    /// inputParamsSchema}`), the shape the dialog engine consumes.
39    pub schema: fn() -> Value,
40    /// Resolve the annotation against the scene.
41    pub resolve: fn(&PmiAnnotation, &PmiContext<'_>) -> Result<Resolved, String>,
42}
43
44/// Panel / `+` dropdown order.
45pub const PMI_TYPES: [PmiTypeDef; 9] = [
46    linear::DEF,
47    radial::DEF,
48    angle::DEF,
49    leader::DEF,
50    note::DEF,
51    hole_callout::DEF,
52    datum::DEF,
53    fcf::DEF,
54    explode::DEF,
55];
56
57/// Look up a type by its canonical `type` string.
58pub fn pmi_type(type_id: &str) -> Option<&'static PmiTypeDef> {
59    PMI_TYPES.iter().find(|def| def.type_id == type_id)
60}
61
62/// The nine annotation schemas, table order.
63pub fn pmi_schema_catalogue() -> Value {
64    Value::Array(PMI_TYPES.iter().map(|def| (def.schema)()).collect())
65}
66
67// --- shared schema-field builders --------------------------------------------
68
69pub(super) fn id_field() -> Value {
70    serde_json::json!({
71        "type": "string",
72        "default_value": null,
73        "hint": "Unique identifier for the annotation"
74    })
75}
76
77pub(super) fn reference_field(
78    label: &str,
79    filter: &[&str],
80    multiple: bool,
81    min: usize,
82    max: usize,
83    hint: &str,
84) -> Value {
85    serde_json::json!({
86        "type": "reference_selection",
87        "label": label,
88        "selectionFilter": filter,
89        "multiple": multiple,
90        "minSelections": min,
91        "maxSelections": max,
92        "default_value": null,
93        "hint": hint
94    })
95}
96
97pub(super) fn number_field(label: &str, default: f64, step: f64, hint: &str) -> Value {
98    serde_json::json!({
99        "type": "number",
100        "label": label,
101        "default_value": default,
102        "step": step,
103        "hint": hint
104    })
105}
106
107pub(super) fn boolean_field(label: &str, default: bool, hint: &str) -> Value {
108    serde_json::json!({
109        "type": "boolean",
110        "label": label,
111        "default_value": default,
112        "hint": hint
113    })
114}
115
116pub(super) fn string_field(label: &str, default: &str, hint: &str) -> Value {
117    serde_json::json!({
118        "type": "string",
119        "label": label,
120        "default_value": default,
121        "hint": hint
122    })
123}
124
125pub(super) fn options_field(label: &str, options: &[&str], default: &str, hint: &str) -> Value {
126    serde_json::json!({
127        "type": "options",
128        "label": label,
129        "options": options,
130        "default_value": default,
131        "hint": hint
132    })
133}
134
135/// The dimension tolerance block + decimals + reference flag, appended to a
136/// dimension schema's params in this order.
137pub(super) fn dimension_fields(params: &mut Map<String, Value>, decimals_default: u64) {
138    params.insert(
139        "decimals".into(),
140        serde_json::json!({
141            "type": "number",
142            "label": "Decimals",
143            "default_value": decimals_default,
144            "step": 1,
145            "hint": "Decimal places shown (0–8)"
146        }),
147    );
148    params.insert(
149        "isReference".into(),
150        boolean_field("Reference", false, "A reference dimension: shown in parentheses, no tolerance"),
151    );
152    params.insert(
153        "tolMode".into(),
154        options_field(
155            "Tolerance",
156            &["none", "symmetric", "deviation", "limits"],
157            "none",
158            "none · ± symmetric · +upper/−lower deviation · upper/lower limit values",
159        ),
160    );
161    params.insert(
162        "tolUpper".into(),
163        number_field("Upper (+)", 0.0, 0.01, "The upper deviation (the ± value for symmetric)"),
164    );
165    params.insert(
166        "tolLower".into(),
167        number_field("Lower (−)", 0.0, 0.01, "The lower deviation (deviation / limits modes)"),
168    );
169}
170
171/// The annotation-plane field every drawn type carries (explode draws
172/// nothing): a planar face or reference plane the annotation lies in;
173/// empty aligns it to the view camera.
174pub(super) fn plane_field() -> Value {
175    reference_field(
176        "Annotation plane",
177        &["FACE", "PLANE"],
178        false,
179        0,
180        1,
181        "A planar face or reference plane the annotation lies in — leave empty to align it to the view camera",
182    )
183}
184
185pub(super) fn schema_entry(def: &PmiTypeDef, mut params: Map<String, Value>) -> Value {
186    if def.type_id != "explode" {
187        params.insert("plane".into(), plane_field());
188    }
189    serde_json::json!({
190        "type": def.type_id,
191        "shortName": def.short_name,
192        "longName": def.long_name,
193        "label": def.label,
194        "icon": def.icon,
195        "inputParamsSchema": Value::Object(params),
196    })
197}
198
199/// Build a params object in insertion order.
200pub(super) fn params(entries: Vec<(&str, Value)>) -> Map<String, Value> {
201    let mut map = Map::new();
202    for (key, value) in entries {
203        map.insert(key.to_string(), value);
204    }
205    map
206}
207
208/// The decimals param clamped to `0..=8`.
209pub(super) fn decimals_of(annotation: &PmiAnnotation, context: &PmiContext<'_>, default: f64) -> usize {
210    annotation
211        .number("decimals", context.env, default)
212        .unwrap_or(default)
213        .round()
214        .clamp(0.0, 8.0) as usize
215}