brep_kernel/feature_pipeline/pmi/annotations/
explode.rs1use super::{boolean_field, id_field, params, reference_field, schema_entry, target_solids, PmiTypeDef};
6use crate::feature_pipeline::pmi::resolve::{a3, solid_bbox};
7use crate::feature_pipeline::pmi::{PmiAnnotation, PmiContext, PmiGeometry, Resolved};
8use crate::feature_pipeline::SelectionProbe;
9use crate::Vec3;
10
11pub const DEF: PmiTypeDef = PmiTypeDef {
12 type_id: "explode",
13 short_name: "EXP",
14 icon: "\u{29C9}",
15 long_name: "\u{29C9} Explode body",
16 label: "Explode body",
17 applicable,
18 schema,
19 resolve,
20};
21
22fn applicable(probe: &SelectionProbe) -> bool {
24 probe.solids + probe.components >= 1
25}
26
27fn schema() -> serde_json::Value {
28 schema_entry(
29 &DEF,
30 params(vec![
31 ("id", id_field()),
32 (
33 "targets",
34 reference_field("Targets", &["SOLID", "COMPONENT"], true, 1, 64, "The solids or components to move"),
35 ),
36 (
37 "transform",
38 serde_json::json!({
39 "type": "transform",
40 "default_value": { "position": [0, 0, 0], "rotationEuler": [0, 0, 0], "scale": [1, 1, 1] },
41 "hint": "The pose delta applied while the view is active"
42 }),
43 ),
44 ("showTraceLine", boolean_field("Trace line", true, "Draw a dashed line from the original to the exploded position")),
45 ]),
46 )
47}
48
49fn triple(value: Option<&serde_json::Value>, default: [f64; 3], env: &crate::feature_pipeline::Env) -> Result<[f64; 3], String> {
50 let Some(items) = value.and_then(serde_json::Value::as_array) else {
51 return Ok(default);
52 };
53 let mut out = default;
54 for (slot, item) in out.iter_mut().zip(items) {
55 if let Some(number) = item.as_f64() {
56 *slot = number;
57 } else if let Some(text) = item.as_str() {
58 *slot = env.eval(text.trim()).map_err(|e| e.to_string())?;
59 }
60 }
61 if out.iter().any(|v| !v.is_finite()) { return Err("explode transform must be finite".into()); }
62 Ok(out)
63}
64
65fn resolve(annotation: &PmiAnnotation, context: &PmiContext<'_>) -> Result<Resolved, String> {
66 let solids = target_solids(&annotation.references("targets"), context)?;
67 let mut low = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
68 let mut high = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
69 for (_, handle) in &solids {
70 let (lo, hi) = solid_bbox(*handle)?;
71 low = Vec3::new(low.x.min(lo.x), low.y.min(lo.y), low.z.min(lo.z));
72 high = Vec3::new(high.x.max(hi.x), high.y.max(hi.y), high.z.max(hi.z));
73 }
74 let center = low.add(high).scale(0.5);
75 let transform = annotation.params.get("transform");
76 let translate = triple(transform.and_then(|t| t.get("position")), [0.0; 3], context.env)?;
77 let rotate_deg = triple(transform.and_then(|t| t.get("rotationEuler")), [0.0; 3], context.env)?;
78 let scale = triple(transform.and_then(|t| t.get("scale")), [1.0; 3], context.env)?;
79 if scale.iter().any(|v| *v <= 0.) { return Err("explode scale must be positive".into()); }
80 let exploded = center.add(Vec3::new(translate[0], translate[1], translate[2]));
81 let names: Vec<String> = solids.into_iter().map(|(name, _)| name).collect();
82 Ok(Resolved {
83 text: format!("Explode {}", names.join(", ")),
84 value: None,
85 unit: "",
86 references: names.clone(),
87 geometry: PmiGeometry::Explode {
88 solids: names,
89 translate,
90 rotate_deg,
91 scale,
92 center: a3(center),
93 trace: annotation
94 .params
95 .get("showTraceLine")
96 .and_then(serde_json::Value::as_bool)
97 .unwrap_or(true),
98 },
99 default_label: a3(exploded),
100 })
101}