brep_kernel/feature_pipeline/pmi/annotations/
mod.rs1pub mod balloon;
8pub mod angle;
9pub mod centerline;
10pub mod datum;
11pub mod explode;
12pub mod fcf;
13pub mod hide;
14pub mod hole_callout;
15pub mod leader;
16pub mod linear;
17pub mod note;
18pub mod radial;
19
20use super::{PmiAnnotation, PmiContext, Resolved};
21use crate::feature_pipeline::SelectionProbe;
22use serde_json::{Map, Value};
23
24pub struct PmiTypeDef {
26 pub type_id: &'static str,
28 pub short_name: &'static str,
30 pub icon: &'static str,
34 pub long_name: &'static str,
36 pub label: &'static str,
38 pub applicable: fn(&SelectionProbe) -> bool,
40 pub schema: fn() -> Value,
43 pub resolve: fn(&PmiAnnotation, &PmiContext<'_>) -> Result<Resolved, String>,
45}
46
47pub const PMI_TYPES: [PmiTypeDef; 12] = [
49 linear::DEF,
50 radial::DEF,
51 angle::DEF,
52 leader::DEF,
53 balloon::DEF,
54 note::DEF,
55 hole_callout::DEF,
56 centerline::DEF,
57 datum::DEF,
58 fcf::DEF,
59 explode::DEF,
60 hide::DEF,
61];
62
63pub fn pmi_type(type_id: &str) -> Option<&'static PmiTypeDef> {
65 PMI_TYPES.iter().find(|def| def.type_id == type_id)
66}
67
68pub fn pmi_schema_catalogue() -> Value {
70 Value::Array(PMI_TYPES.iter().map(|def| (def.schema)()).collect())
71}
72
73pub(super) fn target_solids(targets: &[String], context: &PmiContext<'_>) -> Result<Vec<(String, u32)>, String> {
78 if targets.is_empty() {
79 return Err("select at least one solid or component".into());
80 }
81 let mut solids: Vec<(String, u32)> = Vec::new();
82 for target in targets {
83 if let Some(handle) = context.scene.resolve_solid(target) {
84 solids.push((target.clone(), handle));
85 } else if crate::is_component_reference(crate::split_component_namespace(target).1) {
86 let members = context.scene.occurrence_solids(target);
87 if members.is_empty() {
88 return Err(format!("component '{target}' has no solids"));
89 }
90 solids.extend(members);
91 } else {
92 return Err(format!("'{target}' is not a solid or component"));
93 }
94 }
95 let mut seen = std::collections::HashSet::new();
96 solids.retain(|(name, _)| seen.insert(name.clone()));
97 Ok(solids)
98}
99
100pub(super) fn id_field() -> Value {
103 serde_json::json!({
104 "type": "string",
105 "default_value": null,
106 "hint": "Unique identifier for the annotation"
107 })
108}
109
110pub(super) fn reference_field(
111 label: &str,
112 filter: &[&str],
113 multiple: bool,
114 min: usize,
115 max: usize,
116 hint: &str,
117) -> Value {
118 serde_json::json!({
119 "type": "reference_selection",
120 "label": label,
121 "selectionFilter": filter,
122 "multiple": multiple,
123 "minSelections": min,
124 "maxSelections": max,
125 "default_value": null,
126 "hint": hint
127 })
128}
129
130pub(super) fn number_field(label: &str, default: f64, step: f64, hint: &str) -> Value {
131 serde_json::json!({
132 "type": "number",
133 "label": label,
134 "default_value": default,
135 "step": step,
136 "hint": hint
137 })
138}
139
140pub(super) fn boolean_field(label: &str, default: bool, hint: &str) -> Value {
141 serde_json::json!({
142 "type": "boolean",
143 "label": label,
144 "default_value": default,
145 "hint": hint
146 })
147}
148
149pub(super) fn string_field(label: &str, default: &str, hint: &str) -> Value {
150 serde_json::json!({
151 "type": "string",
152 "label": label,
153 "default_value": default,
154 "hint": hint
155 })
156}
157
158pub(super) fn options_field(label: &str, options: &[&str], default: &str, hint: &str) -> Value {
159 serde_json::json!({
160 "type": "options",
161 "label": label,
162 "options": options,
163 "default_value": default,
164 "hint": hint
165 })
166}
167
168pub(super) fn dimension_fields(params: &mut Map<String, Value>, decimals_default: u64) {
171 params.insert(
172 "decimals".into(),
173 serde_json::json!({
174 "type": "number",
175 "label": "Decimals",
176 "default_value": decimals_default,
177 "step": 1,
178 "hint": "Decimal places shown (0–8)"
179 }),
180 );
181 params.insert(
182 "isReference".into(),
183 boolean_field("Reference", false, "A reference dimension: shown in parentheses, no tolerance"),
184 );
185 params.insert(
186 "tolMode".into(),
187 options_field(
188 "Tolerance",
189 &["none", "symmetric", "deviation", "limits"],
190 "none",
191 "none · ± symmetric · +upper/−lower deviation · upper/lower limit values",
192 ),
193 );
194 params.insert(
195 "tolUpper".into(),
196 number_field("Upper (+)", 0.0, 0.01, "The upper deviation (the ± value for symmetric)"),
197 );
198 params.insert(
199 "tolLower".into(),
200 number_field("Lower (−)", 0.0, 0.01, "The lower deviation (deviation / limits modes)"),
201 );
202}
203
204pub(super) fn plane_field() -> Value {
208 reference_field(
209 "Annotation plane",
210 &["FACE", "PLANE"],
211 false,
212 0,
213 1,
214 "A planar face or reference plane the annotation lies in — leave empty to align it to the view camera",
215 )
216}
217
218pub(super) fn schema_entry(def: &PmiTypeDef, mut params: Map<String, Value>) -> Value {
219 if def.type_id != "explode" {
220 params.insert("plane".into(), plane_field());
221 }
222 serde_json::json!({
223 "type": def.type_id,
224 "shortName": def.short_name,
225 "longName": def.long_name,
226 "label": def.label,
227 "icon": def.icon,
228 "inputParamsSchema": Value::Object(params),
229 })
230}
231
232pub(super) fn params(entries: Vec<(&str, Value)>) -> Map<String, Value> {
234 let mut map = Map::new();
235 for (key, value) in entries {
236 map.insert(key.to_string(), value);
237 }
238 map
239}
240
241pub(super) fn decimals_of(annotation: &PmiAnnotation, context: &PmiContext<'_>, default: f64) -> usize {
243 annotation
244 .number("decimals", context.env, default)
245 .unwrap_or(default)
246 .round()
247 .clamp(0.0, 8.0) as usize
248}