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