Skip to main content

brep_kernel/feature_pipeline/pmi/annotations/
mod.rs

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