1use std::collections::{BTreeMap, BTreeSet, HashMap};
17
18use geop_core_math::{
19 geop_error::{GeopError, GeopResult},
20 union_find::UnionFind,
21};
22
23use super::{
24 geometry::{Placement, Scope},
25 reader::{Args, Reader, unsupported},
26};
27use crate::part21::{Instance, Value};
28
29#[derive(Clone, Debug)]
32pub struct Item {
33 pub id: u64,
34 pub scope: Scope,
35 pub label: String,
36}
37
38const DEFAULT_UNCERTAINTY: f64 = 1e-6;
40
41const MAX_DEPTH: usize = 64;
44
45const BODY_TYPES: [&str; 3] = [
47 "MANIFOLD_SOLID_BREP",
48 "BREP_WITH_VOIDS",
49 "SHELL_BASED_SURFACE_MODEL",
50];
51
52const REFUSED_BODY_TYPES: [(&str, &str); 1] = [(
54 "FACETED_BREP",
55 "a solid of flat facets bounded by polygons rather than edges",
56)];
57
58fn representation(instance: &Instance) -> Option<&crate::part21::Record> {
61 let records: Vec<&crate::part21::Record> = match instance {
62 Instance::Simple(r) => vec![r],
63 Instance::Complex(rs) => rs.iter().collect(),
64 };
65 records.into_iter().find(|r| {
66 r.name.ends_with("REPRESENTATION")
67 && r.args.len() == 3
68 && matches!(r.args[1], Value::List(_))
69 && matches!(r.args[2], Value::Ref(_))
70 })
71}
72
73impl Reader<'_> {
74 fn context_scope(&self, context: u64) -> GeopResult<Scope> {
77 let instance = self.instance(context)?;
78 let mut scope = Scope {
79 length: 1.0,
80 angle: 1.0,
81 uncertainty: f64::NAN,
82 place: Placement::IDENTITY,
83 };
84 if let Some(record) = instance.record("GLOBAL_UNIT_ASSIGNED_CONTEXT") {
85 let args = Args {
86 id: context,
87 record,
88 };
89 for unit in args.references(0)? {
90 let u = self.instance(unit)?;
91 if u.is("LENGTH_UNIT") {
92 scope.length = self.length_unit(unit)?;
93 } else if u.is("PLANE_ANGLE_UNIT") {
94 scope.angle = self.angle_unit(unit)?;
95 }
96 }
97 }
98 if let Some(record) = instance.record("GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT") {
99 let args = Args {
100 id: context,
101 record,
102 };
103 for uncertainty in args.references(0)? {
104 let u = self.args(uncertainty, "UNCERTAINTY_MEASURE_WITH_UNIT")?;
105 let value = u.real(0)?;
106 let unit = u.reference(1)?;
107 if self.instance(unit)?.is("LENGTH_UNIT") {
108 let mm = value * self.length_unit(unit)?;
109 if mm > 0.0 && (scope.uncertainty.is_nan() || scope.uncertainty > mm) {
110 scope.uncertainty = mm;
111 }
112 }
113 }
114 }
115 if scope.uncertainty.is_nan() || scope.uncertainty <= 0.0 {
116 scope.uncertainty = DEFAULT_UNCERTAINTY;
117 }
118 Ok(scope)
119 }
120
121 fn measure(&self, id: u64) -> GeopResult<(f64, u64)> {
124 let instance = self.instance(id)?;
125 let records: Vec<&crate::part21::Record> = match instance {
126 Instance::Simple(r) => vec![r],
127 Instance::Complex(rs) => rs.iter().collect(),
128 };
129 let record = records
130 .into_iter()
131 .find(|r| r.name.ends_with("MEASURE_WITH_UNIT") && r.args.len() == 2)
132 .ok_or_else(|| {
133 unsupported(
134 id,
135 instance,
136 "a conversion factor that is not a measure with a unit",
137 )
138 })?;
139 let args = Args { id, record };
140 Ok((args.real(0)?, args.reference(1)?))
141 }
142
143 fn length_unit(&self, id: u64) -> GeopResult<f64> {
145 let instance = self.instance(id)?;
146 if let Some(record) = instance.record("SI_UNIT") {
147 let args = Args { id, record };
148 if args.enumeration(1)? != "METRE" {
149 return Err(unsupported(
150 id,
151 instance,
152 "a length unit that is not a metre",
153 ));
154 }
155 return Ok(1000.0 * prefix(id, instance, &args)?);
156 }
157 if let Some(record) = instance.record("CONVERSION_BASED_UNIT") {
158 let (value, unit) = self.measure(Args { id, record }.reference(1)?)?;
159 return Ok(value * self.length_unit(unit)?);
160 }
161 Err(unsupported(
162 id,
163 instance,
164 "a length unit neither SI nor converted from one",
165 ))
166 }
167
168 fn angle_unit(&self, id: u64) -> GeopResult<f64> {
170 let instance = self.instance(id)?;
171 if let Some(record) = instance.record("SI_UNIT") {
172 let args = Args { id, record };
173 if args.enumeration(1)? != "RADIAN" {
174 return Err(unsupported(
175 id,
176 instance,
177 "an angle unit that is not a radian",
178 ));
179 }
180 return prefix(id, instance, &args);
181 }
182 if let Some(record) = instance.record("CONVERSION_BASED_UNIT") {
183 let (value, unit) = self.measure(Args { id, record }.reference(1)?)?;
184 return Ok(value * self.angle_unit(unit)?);
185 }
186 Err(unsupported(
187 id,
188 instance,
189 "an angle unit neither SI nor converted from one",
190 ))
191 }
192
193 pub fn bodies(&self) -> GeopResult<Vec<Item>> {
195 let exchange = self.exchange;
196 let mut reps: BTreeMap<u64, (Vec<u64>, Scope, String)> = BTreeMap::new();
198 for (&id, instance) in &exchange.instances {
199 let Some(record) = representation(instance) else {
200 continue;
201 };
202 let args = Args { id, record };
203 let items = args.references(1)?;
204 let scope = self.context_scope(args.reference(2)?)?;
205 reps.insert(id, (items, scope, args.string(0).unwrap_or("").to_string()));
206 }
207 let rep_ids: Vec<u64> = reps.keys().copied().collect();
208 let index: HashMap<u64, usize> =
209 rep_ids.iter().enumerate().map(|(i, &id)| (id, i)).collect();
210
211 let mut groups = UnionFind::new(rep_ids.len());
214 let mut placed: Vec<(u64, u64, u64, u64)> = Vec::new();
217 for (&id, instance) in &exchange.instances {
218 let Some(record) = instance.record("REPRESENTATION_RELATIONSHIP").or_else(|| {
219 instance
220 .record("SHAPE_REPRESENTATION_RELATIONSHIP")
221 .filter(|r| r.args.len() >= 4)
222 }) else {
223 continue;
224 };
225 let args = Args { id, record };
226 let (Ok(rep_1), Ok(rep_2)) = (args.reference(2), args.reference(3)) else {
227 continue;
228 };
229 let (Some(&a), Some(&b)) = (index.get(&rep_1), index.get(&rep_2)) else {
230 continue;
231 };
232 match instance.record("REPRESENTATION_RELATIONSHIP_WITH_TRANSFORMATION") {
233 None => groups.union(a, b),
234 Some(record) => {
235 let transformation = Args { id, record }.reference(0)?;
236 let t = self.instance(transformation)?;
237 let Some(record) = t.record("ITEM_DEFINED_TRANSFORMATION") else {
238 return Err(unsupported(
239 transformation,
240 t,
241 "a placement of a part not given by two placements",
242 ));
243 };
244 let t_args = Args {
245 id: transformation,
246 record,
247 };
248 placed.push((rep_2, rep_1, t_args.reference(3)?, t_args.reference(2)?));
253 }
254 }
255 }
256 let group_of = |groups: &mut UnionFind, rep: u64| index.get(&rep).map(|&i| groups.find(i));
257
258 let mut shape_of_definition: BTreeMap<u64, Vec<u64>> = BTreeMap::new();
261 let mut definition_name: HashMap<u64, String> = HashMap::new();
262 for id in exchange.all_of("SHAPE_DEFINITION_REPRESENTATION") {
263 let args = self.args(id, "SHAPE_DEFINITION_REPRESENTATION")?;
264 let (Ok(pds), Ok(rep)) = (args.reference(0), args.reference(1)) else {
265 continue;
266 };
267 let Ok(pds) = self.args(pds, "PRODUCT_DEFINITION_SHAPE") else {
268 continue;
269 };
270 let Ok(definition) = pds.reference(2) else {
271 continue;
272 };
273 shape_of_definition.entry(definition).or_default().push(rep);
274 if let Some(name) = self.product_name(definition) {
275 definition_name.insert(definition, name);
276 }
277 }
278 let mut children: BTreeSet<u64> = BTreeSet::new();
279 let mut occurrences: HashMap<u64, (u64, u64)> = HashMap::new();
281 for id in exchange.all_of("NEXT_ASSEMBLY_USAGE_OCCURRENCE") {
282 let args = self.args(id, "NEXT_ASSEMBLY_USAGE_OCCURRENCE")?;
283 let (parent, child) = (args.reference(3)?, args.reference(4)?);
284 children.insert(child);
285 occurrences.insert(id, (parent, child));
286 }
287 for id in exchange.all_of("CONTEXT_DEPENDENT_SHAPE_REPRESENTATION") {
290 let args = self.args(id, "CONTEXT_DEPENDENT_SHAPE_REPRESENTATION")?;
291 let (Ok(relation), Ok(pds)) = (args.reference(0), args.reference(1)) else {
292 continue;
293 };
294 let Ok(pds) = self.args(pds, "PRODUCT_DEFINITION_SHAPE") else {
295 continue;
296 };
297 let Some(&(_, child)) = pds.reference(2).ok().and_then(|o| occurrences.get(&o)) else {
298 continue;
299 };
300 let rel = self.instance(relation)?;
301 let Some(record) = rel.record("REPRESENTATION_RELATIONSHIP") else {
302 continue;
303 };
304 let rel_args = Args {
305 id: relation,
306 record,
307 };
308 let rep_1 = rel_args.reference(2)?;
309 let child_groups: BTreeSet<usize> = shape_of_definition
310 .get(&child)
311 .into_iter()
312 .flatten()
313 .filter_map(|&rep| group_of(&mut groups, rep))
314 .collect();
315 if child_groups.is_empty()
316 || group_of(&mut groups, rep_1).is_some_and(|g| child_groups.contains(&g))
317 {
318 continue;
319 }
320 for p in placed.iter_mut() {
322 if p.1 == rep_1 {
323 *p = (p.1, p.0, p.3, p.2);
324 }
325 }
326 }
327
328 let mut members: BTreeMap<usize, Vec<u64>> = BTreeMap::new();
330 for &rep in &rep_ids {
331 let g = group_of(&mut groups, rep).expect("a representation");
332 members.entry(g).or_default().push(rep);
333 }
334 let mut child_groups: BTreeSet<usize> = BTreeSet::new();
335 let mut edges: BTreeMap<usize, Vec<(u64, u64, u64, u64)>> = BTreeMap::new();
336 for &(parent, child, parent_item, child_item) in &placed {
337 let (Some(pg), Some(cg)) =
338 (group_of(&mut groups, parent), group_of(&mut groups, child))
339 else {
340 continue;
341 };
342 child_groups.insert(cg);
343 edges
344 .entry(pg)
345 .or_default()
346 .push((parent, child, parent_item, child_item));
347 }
348
349 let mut roots: Vec<(usize, String)> = Vec::new();
352 let mut seen = BTreeSet::new();
353 for (&definition, shapes) in &shape_of_definition {
354 if children.contains(&definition) {
355 continue;
356 }
357 for &rep in shapes {
358 if let Some(g) = group_of(&mut groups, rep)
359 && !child_groups.contains(&g)
360 && seen.insert(g)
361 {
362 roots.push((
363 g,
364 definition_name
365 .get(&definition)
366 .cloned()
367 .unwrap_or_default(),
368 ));
369 }
370 }
371 }
372 for &g in members.keys() {
373 if !child_groups.contains(&g) && seen.insert(g) && shape_of_definition.is_empty() {
374 roots.push((g, String::new()));
375 }
376 }
377 roots.sort();
378
379 let mut items = Vec::new();
380 for (root, label) in roots {
381 self.collect(
382 &reps,
383 &members,
384 &edges,
385 &mut groups,
386 &index,
387 root,
388 Placement::IDENTITY,
389 &label,
390 0,
391 &mut items,
392 )?;
393 }
394 Ok(items)
395 }
396
397 fn product_name(&self, definition: u64) -> Option<String> {
400 let formation = self
401 .args(definition, "PRODUCT_DEFINITION")
402 .ok()?
403 .reference(2)
404 .ok()?;
405 let instance = self.instance(formation).ok()?;
406 let record = match instance {
407 Instance::Simple(r) => r,
408 Instance::Complex(rs) => rs.first()?,
409 };
410 let product = Args {
411 id: formation,
412 record,
413 }
414 .reference(2)
415 .ok()?;
416 let product = self.args(product, "PRODUCT").ok()?;
417 let name = product
418 .string(1)
419 .ok()
420 .filter(|s| !s.is_empty())
421 .or_else(|| product.string(0).ok())?;
422 Some(name.to_string())
423 }
424
425 #[allow(clippy::too_many_arguments)]
428 fn collect(
429 &self,
430 reps: &BTreeMap<u64, (Vec<u64>, Scope, String)>,
431 members: &BTreeMap<usize, Vec<u64>>,
432 edges: &BTreeMap<usize, Vec<(u64, u64, u64, u64)>>,
433 groups: &mut UnionFind,
434 index: &HashMap<u64, usize>,
435 group: usize,
436 place: Placement,
437 label: &str,
438 depth: usize,
439 out: &mut Vec<Item>,
440 ) -> GeopResult<()> {
441 if depth > MAX_DEPTH {
442 return Err(GeopError::new(format!(
443 "the file places parts within parts more than {MAX_DEPTH} deep: its placements form a cycle"
444 )));
445 }
446 for &rep in members.get(&group).into_iter().flatten() {
447 let (items, scope, rep_name) = &reps[&rep];
448 let label = if label.is_empty() {
449 rep_name.as_str()
450 } else {
451 label
452 };
453 for &item in items {
454 let instance = self.instance(item)?;
455 if let Some(name) = BODY_TYPES.iter().find(|t| instance.is(t)) {
456 let own = self
457 .args(item, name)
458 .ok()
459 .and_then(|a| a.string(0).ok())
460 .unwrap_or("");
461 out.push(Item {
462 id: item,
463 scope: Scope { place, ..*scope },
464 label: if own.is_empty() {
465 label.to_string()
466 } else {
467 own.to_string()
468 },
469 });
470 } else if let Some((_, why)) =
471 REFUSED_BODY_TYPES.iter().find(|(t, _)| instance.is(t))
472 {
473 return Err(unsupported(item, instance, why));
474 } else if instance.is("MAPPED_ITEM") {
475 let args = self.args(item, "MAPPED_ITEM")?;
476 let map = self.args(args.reference(1)?, "REPRESENTATION_MAP")?;
477 let mapped = map.reference(1)?;
478 let Some((_, child_scope, _)) = reps.get(&mapped) else {
479 continue;
480 };
481 let origin = self.frame(child_scope, map.reference(0)?)?;
482 let target = self.frame(scope, args.reference(2)?)?;
483 let motion = Placement::of(&origin)
484 .inverse()
485 .then(&Placement::of(&target))
486 .then(&place);
487 let Some(&i) = index.get(&mapped) else {
488 continue;
489 };
490 let g = groups.find(i);
491 self.collect(
492 reps,
493 members,
494 edges,
495 groups,
496 index,
497 g,
498 motion,
499 label,
500 depth + 1,
501 out,
502 )?;
503 }
504 }
505 }
506 for &(parent, child, parent_item, child_item) in edges.get(&group).into_iter().flatten() {
507 let parent_scope = reps[&parent].1;
508 let child_scope = reps[&child].1;
509 let target = self.frame(&parent_scope, parent_item)?;
510 let origin = self.frame(&child_scope, child_item)?;
511 let motion = Placement::of(&origin)
512 .inverse()
513 .then(&Placement::of(&target))
514 .then(&place);
515 let g = groups.find(index[&child]);
516 let child_label = reps[&child].2.clone();
517 self.collect(
518 reps,
519 members,
520 edges,
521 groups,
522 index,
523 g,
524 motion,
525 &child_label,
526 depth + 1,
527 out,
528 )?;
529 }
530 Ok(())
531 }
532}
533
534fn prefix(id: u64, instance: &Instance, args: &Args) -> GeopResult<f64> {
536 if args.is_null(0) {
537 return Ok(1.0);
538 }
539 Ok(match args.enumeration(0)? {
540 "EXA" => 1e18,
541 "PETA" => 1e15,
542 "TERA" => 1e12,
543 "GIGA" => 1e9,
544 "MEGA" => 1e6,
545 "KILO" => 1e3,
546 "HECTO" => 1e2,
547 "DECA" => 1e1,
548 "DECI" => 1e-1,
549 "CENTI" => 1e-2,
550 "MILLI" => 1e-3,
551 "MICRO" => 1e-6,
552 "NANO" => 1e-9,
553 "PICO" => 1e-12,
554 "FEMTO" => 1e-15,
555 "ATTO" => 1e-18,
556 other => {
557 return Err(unsupported(
558 id,
559 instance,
560 &format!("the unit prefix {other}"),
561 ));
562 }
563 })
564}