1use super::*;
3use kernel::pmi::{PluginAnnotationOutput, PmiAnnotation, PmiContext, PmiGeometry};
4
5impl Runtime {
6 pub fn execute_annotation(
7 &self,
8 pins: &[PluginPin],
9 annotation: &PmiAnnotation,
10 context: &PmiContext<'_>,
11 ) -> Result<PluginAnnotationOutput, String> {
12 let package = self.package(pins, &annotation.kind)?;
13 let pin = package.pin()?;
14 let registration = self
15 .registry
16 .annotations
17 .iter()
18 .find(|r| r.id == annotation.kind && r.package == pin)
19 .ok_or("annotation registration unavailable")?;
20 let mut params = annotation.params.clone();
21 if let Some(fields) = params.as_object_mut() {
24 for (key, schema) in registration.input_params_schema.as_object().unwrap() {
25 if schema["type"] == "number" {
26 if let Some(source) = fields.get(key).and_then(Value::as_str) {
27 let value = context.env.eval(source)?;
28 if !value.is_finite() {
29 return Err("annotation parameter must be finite".into());
30 }
31 fields.insert(key.clone(), json!(value));
32 }
33 }
34 }
35 }
36 validate_params(®istration.input_params_schema, &mut params, None)?;
37 let mut names = BTreeSet::new();
38 fn collect(value: &Value, names: &mut BTreeSet<String>) -> Result<(), String> {
39 match value {
40 Value::String(name) => {
41 if !name.trim().is_empty() {
42 names.insert(name.clone());
43 }
44 }
45 Value::Object(object) => {
46 let name = object["name"].as_str().ok_or("reference missing name")?;
47 if !name.trim().is_empty() {
48 names.insert(name.to_owned());
49 }
50 }
51 Value::Array(values) => {
52 for value in values {
53 collect(value, names)?;
54 }
55 }
56 _ => return Err("invalid annotation reference".into()),
57 }
58 Ok(())
59 }
60 for (key, schema) in registration.input_params_schema.as_object().unwrap() {
61 if schema["type"] == "reference_selection" {
62 collect(¶ms[key], &mut names)?;
63 }
64 }
65 if names.len() > 256 {
66 return Err("annotation dependency limit exceeded".into());
67 }
68 let mut dependencies = serde_json::Map::new();
69 for name in &names {
70 if name.is_empty() || name.len() > 1024 {
71 return Err("invalid annotation dependency name".into());
72 }
73 let point = kernel::pmi::resolve::resolve_reference(context.scene, name)?
74 .representative_point();
75 let point = [point.x, point.y, point.z];
76 if !point.iter().all(|v| v.is_finite()) {
77 return Err("nonfinite annotation dependency".into());
78 }
79 dependencies.insert(name.clone(), json!({"point":point}));
80 }
81 let input = json!({"params":params,"persistentData":annotation.persistent_data,"annotationId":annotation.id(),"units":"mm","dependencies":dependencies});
82 if input.to_string().len() > 1024 * 1024 {
83 return Err("annotation input exceeds 1 MiB".into());
84 }
85 let (registry, value, _) = invocation::invoke(
86 package,
87 Some(("annotation", &annotation.kind)),
88 Some(&input),
89 None,
90 Some(&self.registry.for_document(&[pin])?),
91 )?;
92 self.verify(package, ®istry)?;
93 let mut output = validate_output(value)?;
94 if output.references.iter().any(|r| !names.contains(r)) {
95 return Err("annotation output contains undeclared reference".into());
96 }
97 output.references = names.into_iter().collect();
99 Ok(output)
100 }
101}
102
103pub(super) fn validate_output(value: Value) -> Result<PluginAnnotationOutput, String> {
104 let object = value
105 .as_object()
106 .ok_or("annotation result must be an object")?;
107 if object.keys().any(|k| {
108 ![
109 "text",
110 "geometry",
111 "defaultLabel",
112 "references",
113 "value",
114 "unit",
115 "persistentData",
116 ]
117 .contains(&k.as_str())
118 }) {
119 return Err("unknown annotation result property".into());
120 }
121 let geometry = object
122 .get("geometry")
123 .and_then(Value::as_object)
124 .ok_or("annotation geometry must be an object")?;
125 let allowed: &[&str] = match geometry.get("kind").and_then(Value::as_str) {
126 Some("note") => &["kind", "position"],
127 Some("leader") => &["kind", "targets", "dot"],
128 Some("linear") => &["kind", "a", "b", "component"],
129 _ => return Err("unsupported annotation geometry; expected note, leader or linear".into()),
130 };
131 if geometry.keys().any(|k| !allowed.contains(&k.as_str())) {
132 return Err("unknown annotation geometry property".into());
133 }
134 let output: PluginAnnotationOutput =
135 serde_json::from_value(value).map_err(|e| format!("invalid annotation result: {e}"))?;
136 output.validate()?;
137 let finite = |p: &[f64; 3]| p.iter().all(|v| v.is_finite() && v.abs() <= 1e12);
138 if output.text.is_empty()
139 || output.text.len() > 16384
140 || !matches!(output.unit.as_str(), "" | "mm" | "deg")
141 || !finite(&output.default_label)
142 || output.value.is_some_and(|v| !v.is_finite())
143 || output.references.len() > 256
144 || output
145 .references
146 .iter()
147 .any(|s| s.is_empty() || s.len() > 1024)
148 || output.persistent_data.to_string().len() > 1024 * 1024
149 {
150 return Err("invalid annotation text, value, references, persistent data or label".into());
151 }
152 match &output.geometry {
153 PmiGeometry::Note { position } if finite(position) => {}
154 PmiGeometry::Leader { targets, .. }
155 if !targets.is_empty() && targets.len() <= 256 && targets.iter().all(finite) => {}
156 PmiGeometry::Linear { a, b, component }
157 if finite(a)
158 && finite(b)
159 && a != b
160 && component.is_none_or(|c| match c {
161 'X' => a[1] == b[1] && a[2] == b[2],
162 'Y' => a[0] == b[0] && a[2] == b[2],
163 'Z' => a[0] == b[0] && a[1] == b[1],
164 _ => false,
165 }) => {}
166 _ => return Err("invalid annotation geometry".into()),
167 }
168 Ok(output)
169}
170
171pub(super) fn primitive(op: &str, params: &Value) -> Result<Value, String> {
172 let mut output =
173 json!({"text":params["text"], "defaultLabel":params["defaultLabel"], "persistentData":{}});
174 output["geometry"] = match op {
175 "annotation.note" => {
176 output["defaultLabel"] = params["position"].clone();
177 json!({"kind":"note", "position":params["position"]})
178 }
179 "annotation.leader" => {
180 json!({"kind":"leader", "targets":params["targets"], "dot":params.get("dot").cloned().unwrap_or(json!(false))})
181 }
182 "annotation.linear" => {
183 json!({"kind":"linear", "a":params["a"], "b":params["b"], "component":params.get("component").cloned().unwrap_or(Value::Null)})
184 }
185 _ => return Err("unknown annotation primitive".into()),
186 };
187 if op == "annotation.linear" {
190 if let Some(axis) = match params["component"].as_str() {
191 Some("X") => Some(0),
192 Some("Y") => Some(1),
193 Some("Z") => Some(2),
194 _ => None,
195 } {
196 let a: [f64; 3] = serde_json::from_value(params["a"].clone())
197 .map_err(|e| format!("invalid linear endpoint: {e}"))?;
198 let original_b: [f64; 3] = serde_json::from_value(params["b"].clone())
199 .map_err(|e| format!("invalid linear endpoint: {e}"))?;
200 if !a
201 .iter()
202 .chain(original_b.iter())
203 .all(|v| v.is_finite() && v.abs() <= 1e12)
204 {
205 return Err("invalid linear endpoint".into());
206 }
207 let mut b = a;
208 b[axis] = original_b[axis];
209 output["geometry"]["b"] = json!(b);
210 }
211 }
212 let mut validated = validate_output(output)?;
213 if let PmiGeometry::Linear { a, b, component } = &validated.geometry {
214 let delta = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
215 validated.value = Some(match component {
216 Some('X') => delta[0].abs(),
217 Some('Y') => delta[1].abs(),
218 Some('Z') => delta[2].abs(),
219 _ => delta.iter().map(|v| v * v).sum::<f64>().sqrt(),
220 });
221 validated.unit = "mm".into();
222 }
223 serde_json::to_value(validated).map_err(|e| e.to_string())
224}