brep_kernel/feature_pipeline/pmi/annotations/
datum.rs1use super::{id_field, params, reference_field, schema_entry, string_field, PmiTypeDef};
6use crate::feature_pipeline::pmi::resolve::{a3, direction_of, resolve_reference};
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: "datum",
13 short_name: "DTM",
14 icon: "\u{25B3}",
15 long_name: "\u{25B3} Datum",
16 label: "Datum",
17 applicable,
18 schema,
19 resolve,
20};
21
22fn applicable(probe: &SelectionProbe) -> bool {
24 probe.faces + probe.edges == 1
25 && probe.vertices == 0
26 && probe.planes == 0
27 && probe.solids == 0
28 && probe.sketches == 0
29}
30
31fn schema() -> serde_json::Value {
32 schema_entry(
33 &DEF,
34 params(vec![
35 ("id", id_field()),
36 ("target", reference_field("Target", &["FACE", "EDGE"], false, 1, 1, "The datum feature")),
37 ("letter", string_field("Letter", "", "The datum identifier (A–Z, skipping I, O, Q); assigned automatically when left empty at creation")),
38 ]),
39 )
40}
41
42pub fn valid_letter(letter: &str) -> bool {
44 !letter.is_empty()
45 && letter.len() <= 2
46 && letter
47 .chars()
48 .all(|c| c.is_ascii_uppercase() && !matches!(c, 'I' | 'O' | 'Q'))
49}
50
51fn resolve(annotation: &PmiAnnotation, context: &PmiContext<'_>) -> Result<Resolved, String> {
52 let targets = annotation.references("target");
53 let Some(target) = targets.first() else {
54 return Err("select the datum feature (a face or an edge)".into());
55 };
56 let letter = annotation.text("letter").to_ascii_uppercase();
57 if !valid_letter(&letter) {
58 return Err(format!("'{letter}' is not a datum letter (A–Z, skipping I, O and Q)"));
59 }
60 if let Some(owner) = context.datums.get(&letter) {
61 if owner != annotation.id() {
62 return Err(format!("datum letter {letter} is already used by {owner}"));
63 }
64 }
65 let geometry = resolve_reference(context.scene, target)?;
66 let anchor = geometry.representative_point();
67 let normal = direction_of(&geometry).unwrap_or(Vec3::new(0.0, 0.0, 1.0));
68 let default_label = anchor.add(normal.scale(4.0)).add(Vec3::new(2.0, 2.0, 0.0));
69 Ok(Resolved {
70 text: letter.clone(),
71 value: None,
72 unit: "",
73 references: targets,
74 geometry: PmiGeometry::Datum {
75 anchor: a3(anchor),
76 normal: a3(normal),
77 letter,
78 },
79 default_label: a3(default_label),
80 })
81}