1#![deny(
40 clippy::unwrap_used,
41 clippy::expect_used,
42 clippy::indexing_slicing,
43 clippy::panic,
44 clippy::unreachable
45)]
46
47use std::collections::{HashMap, HashSet};
48use std::io::{self, Read, Seek};
49use std::path::Path;
50
51use crate::invpart::{self, f64_at, u16_at, u32_at};
52use crate::nxasm::{Component, Kind};
53
54pub const PLACEMENTS: &str = "AmRxSegment";
56const OCCURRENCE: u32 = 0x2327_92BC;
58const OVERRIDE_SET: u32 = 0xF17A_4562;
60const OVERRIDE: u32 = 0x2238_DCC5;
62const REPOSE: u32 = 2;
64const NAME_RECORD: [u8; 7] = [1, 5, 1, 0, 0, 0, 5];
66const SUPPRESSION_RECORD: [u8; 7] = [1, 7, 1, 0, 0, 0, 7];
67const SUPPRESSION_AFTER_NAME: usize = 31;
69const MAX_BLOCK: usize = 4096;
71const TAIL: usize = 124;
73const MAX_DOC: u64 = 16 * 1024 * 1024;
75const MAX_SEGMENT: u64 = 256 * 1024 * 1024;
77const TABLE_WITHIN: usize = 64 * 1024;
79const MAX_TEXT: usize = 4096;
81const TO_MM: f64 = 10.0;
83
84#[derive(Clone, Debug, PartialEq)]
86struct FileRef {
87 id: u32,
88 path: String,
89}
90
91#[derive(Clone, Debug, PartialEq)]
93struct Record {
94 file: u32,
95 id: u32,
96 visible: bool,
97 suppressed: bool,
98 start: usize,
100 end: usize,
101}
102
103#[derive(Clone, Debug, PartialEq)]
105pub struct Occurrence {
106 pub id: u32,
108 pub key: u32,
110 pub overrides: Vec<Override>,
112}
113
114#[derive(Clone, Debug, PartialEq)]
116pub struct Override {
117 pub path: Vec<u32>,
120 pub key: u32,
122 pub rotation: [[f64; 3]; 3],
124 pub translation: [f64; 3],
125}
126
127impl Override {
128 pub fn pose(overrides: &[Override], child: &Component) -> Component {
131 let key = child.occurrence.as_ref().map(|o| o.key);
132 match overrides
133 .iter()
134 .find(|o| o.path.is_empty() && Some(o.key) == key)
135 {
136 Some(o) => Component {
137 rotation: o.rotation,
138 translation: o.translation,
139 ..child.clone()
140 },
141 None => child.clone(),
142 }
143 }
144
145 pub fn passed_down(overrides: &[Override], child: &Component) -> Vec<Override> {
148 let Some(occurrence) = child.occurrence.as_ref() else {
149 return Vec::new();
150 };
151 let mut out: Vec<Override> = overrides
152 .iter()
153 .filter(|o| o.path.first() == Some(&occurrence.id))
154 .map(|o| Override {
155 path: o.path.get(1..).unwrap_or_default().to_vec(),
156 ..o.clone()
157 })
158 .collect();
159 out.extend(occurrence.overrides.iter().cloned());
160 out
161 }
162}
163
164struct Placement {
166 matrix: [[f64; 4]; 4],
167 key: u32,
168}
169
170pub fn kind_of(path: &Path) -> io::Result<Kind> {
173 let file = io::BufReader::new(std::fs::File::open(path)?);
174 let Ok(mut ole) = cfb::CompoundFile::open(file) else {
175 return Ok(Kind::Other);
176 };
177 Ok(match components_in(&mut ole)? {
178 Some(c) if !c.is_empty() => Kind::Assembly(c),
179 _ => Kind::Other,
180 })
181}
182
183pub fn components(data: &[u8]) -> Option<Vec<Component>> {
186 let mut ole = cfb::CompoundFile::open(io::Cursor::new(data)).ok()?;
187 components_in(&mut ole).ok().flatten()
188}
189
190fn components_in<F: Read + Seek>(
191 ole: &mut cfb::CompoundFile<F>,
192) -> io::Result<Option<Vec<Component>>> {
193 let Some(segment) = invpart::segment(ole, PLACEMENTS, MAX_SEGMENT)? else {
194 return Ok(None);
195 };
196 let (placements, overrides) = placements(&segment)?;
197 let Ok(doc) = invpart::read_stream(ole, Path::new("/UFRxDoc"), MAX_DOC) else {
198 return Ok(None);
199 };
200 let Some((files, end)) = file_table(&doc) else {
201 return Ok(None);
202 };
203 let known: HashSet<u32> = placements.keys().copied().collect();
204 let occurrences = occurrences(&doc, end, &files, &known);
205 let names: HashMap<u32, &str> = files.iter().map(|f| (f.id, file_name(&f.path))).collect();
206 let mut out = Vec::new();
207 for o in occurrences {
208 let (Some(name), Some(p)) = (names.get(&o.file), placements.get(&o.id)) else {
209 continue;
210 };
211 if !o.visible || o.suppressed || name.is_empty() {
212 continue;
213 }
214 let mut c = component(name, &p.matrix);
215 c.occurrence = Some(Occurrence {
216 id: o.id,
217 key: p.key,
218 overrides: overrides
219 .iter()
220 .filter(|v| v.path.first() == Some(&o.id))
221 .map(|v| Override {
222 path: v.path.get(1..).unwrap_or_default().to_vec(),
223 ..v.clone()
224 })
225 .collect(),
226 });
227 out.push(c);
228 }
229 Ok(Some(out))
230}
231
232fn file_name(path: &str) -> &str {
234 path.rsplit(['\\', '/']).next().unwrap_or(path)
235}
236
237fn component(name: &str, m: &[[f64; 4]; 4]) -> Component {
239 let (rotation, translation) = pose(m);
240 Component {
241 name: name.to_string(),
242 rotation,
243 translation,
244 layer: None,
245 occurrence: None,
246 document: None,
247 }
248}
249
250fn pose(m: &[[f64; 4]; 4]) -> ([[f64; 3]; 3], [f64; 3]) {
252 let mut rotation = [[0.0; 3]; 3];
253 for (i, row) in rotation.iter_mut().enumerate() {
254 for (k, r) in row.iter_mut().enumerate() {
255 *r = m
257 .get(k)
258 .and_then(|mrow| mrow.get(i))
259 .copied()
260 .unwrap_or(0.0);
261 }
262 }
263 let t = |r: usize| m.get(r).and_then(|row| row.get(3)).copied().unwrap_or(0.0) * TO_MM;
264 (rotation, [t(0), t(1), t(2)])
265}
266
267type Placements = (HashMap<u32, Placement>, Vec<Override>);
269
270fn placements(segment: &invpart::Segment) -> io::Result<Placements> {
273 let mut out = HashMap::new();
274 let mut poses: HashMap<usize, (u32, u32, [[f64; 4]; 4])> = HashMap::new();
276 let mut sets: Vec<(Vec<u32>, Vec<u32>)> = Vec::new();
277 for section in segment.sections() {
278 let s = section?;
279 let p = s.payload;
280 if s.class == OVERRIDE {
281 let (Some(key), Some(kind)) = (u32_at(p, 6), u32_at(p, 10)) else {
282 continue;
283 };
284 let at = if u32_at(p, 14) == Some(0x203) { 18 } else { 14 };
285 if let Some((m, _)) = matrix(p, at) {
286 poses.insert(s.index, (key, kind, m));
287 }
288 continue;
289 }
290 if s.class == OVERRIDE_SET {
291 let mut c = invpart::Cursor::new(p, 6);
292 let (Some(path), Some(refs)) = (c.array(segment), c.array(segment)) else {
293 continue;
294 };
295 let words = |a: invpart::Array| -> Vec<u32> {
296 a.bytes
297 .chunks_exact(4)
298 .filter_map(|w| w.try_into().ok().map(u32::from_le_bytes))
299 .collect()
300 };
301 sets.push((words(path), words(refs)));
302 continue;
303 }
304 if s.class != OCCURRENCE {
305 continue;
306 }
307 let mut at = 10;
308 if u32_at(p, at) == Some(0x203) {
309 at += 4;
310 }
311 let Some((m, end)) = matrix(p, at) else {
312 continue;
313 };
314 if p.len().checked_sub(end) != Some(TAIL) {
315 continue;
316 }
317 if let (Some(id), Some(key)) = (u32_at(p, end + 112), u32_at(p, end + 120)) {
318 out.insert(id, Placement { matrix: m, key });
319 }
320 }
321 let mut overrides = Vec::new();
322 for (path, refs) in sets {
323 if path.is_empty() {
324 continue;
325 }
326 for r in refs {
327 let index = usize::try_from(r & 0x7FFF_FFFF)
328 .ok()
329 .and_then(|i| i.checked_sub(1));
330 let Some(&(key, kind, m)) = index.and_then(|i| poses.get(&i)) else {
331 continue;
332 };
333 if kind != REPOSE {
334 continue;
335 }
336 let (rotation, translation) = pose(&m);
337 overrides.push(Override {
338 path: path.clone(),
339 key,
340 rotation,
341 translation,
342 });
343 }
344 }
345 Ok((out, overrides))
346}
347
348fn matrix(p: &[u8], at: usize) -> Option<([[f64; 4]; 4], usize)> {
350 let mask = u32_at(p, at)?;
351 let mut m = [[0.0; 4]; 4];
352 let mut next = at + 4;
353 for k in 0..16 {
354 let one = mask >> k & 1 == 1;
355 let zero = mask >> (16 + k) & 1 == 1;
356 let v = match (one, zero) {
357 (true, true) => -1.0,
358 (true, false) => 1.0,
359 (false, true) => 0.0,
360 (false, false) => {
361 let v = f64_at(p, next)?;
362 next += 8;
363 v
364 }
365 };
366 if !v.is_finite() {
367 return None;
368 }
369 *m.get_mut(k / 4)?.get_mut(k % 4)? = v;
370 }
371 (m.get(3) == Some(&[0.0, 0.0, 0.0, 1.0])).then_some((m, next))
372}
373
374fn text(d: &[u8], at: usize, max: usize) -> Option<(String, usize)> {
376 let n = usize::try_from(u32_at(d, at)?).ok()?;
377 if n > max {
378 return None;
379 }
380 let bytes = d.get(at + 4..at + 4 + 2 * n)?;
381 let units: Vec<u16> = bytes
382 .chunks_exact(2)
383 .filter_map(|c| c.try_into().ok().map(u16::from_le_bytes))
384 .collect();
385 let s = String::from_utf16(&units).ok()?;
386 if s.chars()
387 .any(|c| c.is_control() && !matches!(c, '\t' | '\r' | '\n'))
388 {
389 return None;
390 }
391 Some((s, at + 4 + 2 * n))
392}
393
394fn file_record(d: &[u8], at: usize) -> Option<(FileRef, usize)> {
396 let (path, at) = text(d, at, MAX_TEXT)?;
397 let at = at + 4; let (_, at) = text(d, at, MAX_TEXT)?; let mut at = at + 2;
400 if u32_at(d, at)? >> 16 != 0 {
401 if u16_at(d, at)? != 0 {
402 return None;
403 }
404 at += 2;
405 }
406 let (_, at) = text(d, at, MAX_TEXT)?; let count = usize::try_from(u32_at(d, at)?).ok()?;
408 if count > 10_000 {
409 return None;
410 }
411 let at = at + 4 + 6 * count + 4 + 32;
412 let id = u32_at(d, at)?;
413 let flags = u32_at(d, at + 12)?;
414 let mut end = at + 16;
415 if flags & 0x20 != 0 {
416 let (_, e) = text(d, end, MAX_TEXT)?;
418 end = e + 4;
419 }
420 (end <= d.len()).then_some((FileRef { id, path }, end))
421}
422
423fn file_table(d: &[u8]) -> Option<(Vec<FileRef>, usize)> {
426 let last = d.len().min(TABLE_WITHIN);
427 'offsets: for start in 0x40..last {
428 let n = usize::try_from(u32_at(d, start)?).ok()?;
429 if n == 0 || n > d.len() / 60 {
431 continue;
432 }
433 let Some((_, at)) = text(d, start + 4, 256) else {
434 continue;
435 };
436 let mut at = at + 4;
437 match u32_at(d, at) {
438 Some(len) if (3..2048).contains(&len) => {}
439 _ => continue,
440 }
441 let mut files = Vec::with_capacity(n.min(1024));
442 for _ in 0..n {
443 let Some((f, end)) = file_record(d, at) else {
444 continue 'offsets;
445 };
446 if !f
447 .path
448 .rsplit(['\\', '/'])
449 .next()
450 .is_some_and(|name| name.contains('.'))
451 {
452 continue 'offsets;
453 }
454 files.push(f);
455 at = end;
456 }
457 return Some((files, at));
458 }
459 None
460}
461
462fn occurrences(d: &[u8], from: usize, files: &[FileRef], placed: &HashSet<u32>) -> Vec<Record> {
464 let ids: HashSet<u32> = files.iter().map(|f| f.id).collect();
465 let mut out = Vec::new();
466 let mut seen = HashSet::new();
467 let mut at = from;
468 while at + 28 < d.len() && seen.len() < placed.len() {
469 match occurrence_at(d, at, &ids, placed) {
470 Some(o) if !seen.contains(&o.id) => {
471 seen.insert(o.id);
472 at = o.end;
473 out.push(o);
474 }
475 _ => at += 1,
476 }
477 }
478 let ends: Vec<usize> = out
480 .iter()
481 .skip(1)
482 .map(|o| o.start)
483 .chain(std::iter::once(d.len()))
484 .collect();
485 for (o, &stop) in out.iter_mut().zip(&ends) {
486 o.suppressed = suppressed(d, o.end, stop);
487 }
488 out
489}
490
491fn suppressed(d: &[u8], from: usize, to: usize) -> bool {
495 let Some(block) = d.get(from..to.min(from.saturating_add(MAX_BLOCK))) else {
496 return false;
497 };
498 let Some(at) = block
499 .windows(NAME_RECORD.len())
500 .position(|w| w == NAME_RECORD)
501 else {
502 return false;
503 };
504 let Some(n) = u32_at(block, at + 7).and_then(|n| usize::try_from(n).ok()) else {
505 return false;
506 };
507 let Some(record) = n
508 .checked_mul(2)
509 .and_then(|b| b.checked_add(at + 11 + SUPPRESSION_AFTER_NAME))
510 else {
511 return false;
512 };
513 block.get(record..record + 7) == Some(&SUPPRESSION_RECORD[..])
514 && block.get(record + 7) == Some(&1)
515}
516
517fn occurrence_at(
518 d: &[u8],
519 at: usize,
520 files: &HashSet<u32>,
521 placed: &HashSet<u32>,
522) -> Option<Record> {
523 let v = |k: usize| u32_at(d, at + 4 * k);
524 let (zero, file, id, number, instance) = (v(0)?, v(1)?, v(2)?, v(3)?, v(4)?);
525 if zero != 0
526 || !placed.contains(&id)
527 || (file != 0 && !files.contains(&file))
528 || !(1..=0xFFFF).contains(&number)
529 || !(1..=0xFFFF).contains(&instance)
530 {
531 return None;
532 }
533 let (title, end) = text(d, at + 20, 1024)?;
534 if file == 0 && !title.ends_with(&format!(":{number}")) {
535 return None;
536 }
537 let flags = d.get(end..end + 8)?;
538 if flags.get(..6)? != [0; 6] || flags.get(6)? & 0x80 == 0 {
539 return None;
540 }
541 let visible = flags.get(7)? & 0x40 == 0;
542 Some(Record {
543 file,
544 id,
545 visible,
546 suppressed: false,
547 start: at,
548 end: end + 8,
549 })
550}
551
552#[cfg(test)]
553mod tests {
554 use super::*;
555 use crate::invpart::tests::{array, file};
556
557 fn text16(s: &str) -> Vec<u8> {
558 let units: Vec<u16> = s.encode_utf16().collect();
559 let mut out = (units.len() as u32).to_le_bytes().to_vec();
560 for u in units {
561 out.extend(u.to_le_bytes());
562 }
563 out
564 }
565
566 fn file_rec(id: u32, path: &str) -> Vec<u8> {
567 let mut r = text16(path);
568 r.extend(113u32.to_le_bytes());
569 r.extend(text16("Workspace"));
570 r.extend(0u16.to_le_bytes());
571 r.extend(text16(""));
572 r.extend(0u32.to_le_bytes()); r.extend(0u32.to_le_bytes()); r.extend([0x11; 32]); r.extend(id.to_le_bytes());
576 r.extend(1u32.to_le_bytes());
577 r.extend(0x23u32.to_le_bytes());
578 r.extend(1u32.to_le_bytes());
579 r
580 }
581
582 fn occ_rec(
583 file: u32,
584 id: u32,
585 number: u32,
586 title: &str,
587 visible: bool,
588 suppressed: bool,
589 ) -> Vec<u8> {
590 let mut r = vec![0; 4];
591 for v in [file, id, number, 1] {
592 r.extend(v.to_le_bytes());
593 }
594 r.extend(text16(title));
595 r.extend([0, 0, 0, 0, 0, 0, 0x80, if visible { 0x20 } else { 0x60 }]);
596 r.extend([0xEE; 37]);
598 r.extend(NAME_RECORD);
599 r.extend(text16(title.split(':').next().unwrap_or_default()));
600 r.extend([0; 4]);
601 r.extend([1, 2, 1, 0, 0, 0, 2]);
602 r.extend([0x33; 20]);
603 r.extend(SUPPRESSION_RECORD);
604 r.push(u8::from(suppressed));
605 r.extend([0xEE; 11]);
606 r
607 }
608
609 fn doc(files: &[(u32, &str)], occs: &[(u32, u32, &str, bool)]) -> Vec<u8> {
611 doc_with(files, occs, &[])
612 }
613
614 fn doc_with(
616 files: &[(u32, &str)],
617 occs: &[(u32, u32, &str, bool)],
618 suppressed: &[u32],
619 ) -> Vec<u8> {
620 let mut d = vec![0x55; 0x50];
621 d.extend((files.len() as u32).to_le_bytes());
622 d.extend(text16(""));
623 d.extend(0u32.to_le_bytes());
624 for (id, path) in files {
625 d.extend(file_rec(*id, path));
626 }
627 d.extend([0, 0, 0, 0, 0, 0]);
628 d.extend((occs.len() as u32).to_le_bytes());
629 for (k, (file, id, title, visible)) in occs.iter().enumerate() {
630 d.extend(occ_rec(
631 *file,
632 *id,
633 k as u32 + 1,
634 title,
635 *visible,
636 suppressed.contains(id),
637 ));
638 }
639 d.extend([0; 29]);
640 d
641 }
642
643 fn placement(id: u32, t: [f64; 3], first: bool) -> Vec<u8> {
645 let mut p = vec![0u8; 10];
646 if first {
647 p.extend(0x203u32.to_le_bytes());
648 }
649 let one: u32 = 1 << 1 | 1 << 4 | 1 << 10 | 1 << 15;
652 let zero: u32 = 1 << 0
653 | 1 << 1
654 | 1 << 2
655 | 1 << 5
656 | 1 << 6
657 | 1 << 8
658 | 1 << 9
659 | 1 << 12
660 | 1 << 13
661 | 1 << 14;
662 p.extend((one | zero << 16).to_le_bytes());
663 for v in t {
664 p.extend(v.to_le_bytes());
665 }
666 let mut tail = vec![0u8; TAIL];
667 tail[112..116].copy_from_slice(&id.to_le_bytes());
668 tail[120..124].copy_from_slice(&(id + 100).to_le_bytes());
669 p.extend(tail);
670 p
671 }
672
673 fn shift(t: [f64; 3]) -> Vec<u8> {
675 let one: u32 = 1 | 1 << 5 | 1 << 10 | 1 << 15;
676 let zero: u32 = [1, 2, 4, 6, 8, 9, 12, 13, 14]
677 .iter()
678 .map(|b| 1u32 << b)
679 .sum();
680 let mut p = (one | zero << 16).to_le_bytes().to_vec();
681 for v in t {
682 p.extend(v.to_le_bytes());
683 }
684 p
685 }
686
687 fn override_pose(key: u32, kind: u32, t: [f64; 3], first: bool) -> Vec<u8> {
689 let mut p = vec![0u8; 6];
690 p.extend(key.to_le_bytes());
691 p.extend(kind.to_le_bytes());
692 if first {
693 p.extend(0x203u32.to_le_bytes());
694 }
695 p.extend(shift(t));
696 p.extend([0u8; 120]);
697 p
698 }
699
700 fn override_set(path: &[u32], refs: &[usize]) -> Vec<u8> {
702 let words = |v: Vec<u32>| v.iter().flat_map(|w| w.to_le_bytes()).collect::<Vec<u8>>();
703 let mut p = vec![0u8; 6];
704 p.extend(array(0, path.len(), &words(path.to_vec())));
705 let refs: Vec<u32> = refs.iter().map(|&i| (i as u32 + 1) | 0x8000_0000).collect();
706 p.extend(array(0, refs.len(), &words(refs.clone())));
707 p
708 }
709
710 fn assembly(placements: Vec<(u8, Vec<u8>)>, doc: Vec<u8>) -> Vec<u8> {
711 file(
712 &[(
713 PLACEMENTS,
714 &[OCCURRENCE, 0xFDCD_32FE, OVERRIDE_SET, OVERRIDE],
715 placements,
716 )],
717 &[("/UFRxDoc", doc)],
718 )
719 }
720
721 #[test]
722 fn suppressed_occurrences_are_left_out_and_overrides_go_down() {
723 let bytes = assembly(
726 vec![
727 (0, placement(1, [0.0; 3], true)),
728 (0, placement(2, [1.0, 0.0, 0.0], false)),
729 (0, placement(3, [2.0, 0.0, 0.0], false)),
730 (3, override_pose(777, 2, [0.0, 0.0, 9.0], true)),
731 (3, override_pose(778, 1, [5.0, 5.0, 5.0], false)),
732 (2, override_set(&[1], &[3, 4])),
733 ],
734 doc_with(
735 &[(1, r"C:ASub.iam"), (2, r"C:ABolt.ipt")],
736 &[
737 (1, 1, "Sub:1", true),
738 (2, 2, "Bolt:1", true),
739 (2, 3, "Bolt:2", true),
740 ],
741 &[3],
742 ),
743 );
744 let c = components(&bytes).unwrap();
745 assert_eq!(c.len(), 2, "the suppressed Bolt:2 is left out");
746 let sub = &c[0];
747 let occurrence = sub.occurrence.as_ref().unwrap();
748 assert_eq!((occurrence.id, occurrence.key), (1, 101));
749 assert_eq!(occurrence.overrides.len(), 1, "kind 1 is not applied");
750 assert_eq!(occurrence.overrides[0].path, Vec::<u32>::new());
751 assert_eq!(occurrence.overrides[0].translation, [0.0, 0.0, 90.0]);
752 assert!(c[1].occurrence.as_ref().unwrap().overrides.is_empty());
753
754 let mut child = component(
756 "Pin.ipt",
757 &[
758 [1., 0., 0., 3.],
759 [0., 1., 0., 0.],
760 [0., 0., 1., 0.],
761 [0., 0., 0., 1.],
762 ],
763 );
764 child.occurrence = Some(Occurrence {
765 id: 5,
766 key: 777,
767 overrides: Vec::new(),
768 });
769 let below = Override::passed_down(&[], sub);
770 assert_eq!(Override::pose(&below, &child).translation, [0.0, 0.0, 90.0]);
771 child.occurrence = Some(Occurrence {
773 id: 6,
774 key: 9,
775 overrides: Vec::new(),
776 });
777 assert_eq!(Override::pose(&below, &child).translation, [30.0, 0.0, 0.0]);
778 let deep = vec![Override {
780 path: vec![1, 5],
781 ..occurrence.overrides[0].clone()
782 }];
783 let through = Override::passed_down(&deep, sub);
784 assert_eq!(through[0].path, vec![5]);
785 assert!(Override::passed_down(&deep, &c[1]).is_empty());
786 }
787
788 #[test]
789 fn components_carry_file_names_and_placements_in_millimetres() {
790 let bytes = assembly(
791 vec![
792 (0, placement(1, [1.0, 2.0, 3.0], true)),
793 (1, vec![1, 2, 3]),
794 (0, placement(2, [0.0, 0.0, 0.0], false)),
795 (0, placement(3, [5.0, 0.0, 0.0], false)),
796 ],
797 doc(
798 &[
799 (1, r"C:\Work\Pump\Shaft.ipt"),
800 (2, r"C:\Work\Pump\Sub\Gear Box.iam"),
801 ],
802 &[
803 (1, 1, "Shaft:1", true),
804 (2, 2, "", true),
805 (1, 3, "Shaft:2", false),
806 ],
807 ),
808 );
809 let c = components(&bytes).unwrap();
810 assert_eq!(c.len(), 2, "the hidden Shaft:2 is left out");
811 assert_eq!(c[0].name, "Shaft.ipt");
812 assert_eq!(c[1].name, "Gear Box.iam");
813 assert_eq!(c[0].translation, [10.0, 20.0, 30.0]);
814 let p = c[0].place([1.0, 0.0, 0.0]);
816 assert!(
817 (p[0] - 10.0).abs() < 1e-9 && (p[1] - 21.0).abs() < 1e-9 && (p[2] - 30.0).abs() < 1e-9,
818 "{p:?}"
819 );
820 }
821
822 #[test]
823 fn identity_and_minus_one_elements() {
824 let mut p = 0x7BDE_8421u32.to_le_bytes().to_vec();
825 let (m, end) = matrix(&p, 0).unwrap();
826 assert_eq!(end, 4);
827 assert_eq!(
828 m,
829 [
830 [1., 0., 0., 0.],
831 [0., 1., 0., 0.],
832 [0., 0., 1., 0.],
833 [0., 0., 0., 1.]
834 ]
835 );
836 p = (0x7BDE_8421u32 & !(1 << 15) & !(1 << 31))
838 .to_le_bytes()
839 .to_vec();
840 p.extend(2.0f64.to_le_bytes());
841 assert!(matrix(&p, 0).is_none());
842 let mirror = 0x7BDE_8421u32 | 1 << 16;
844 assert_eq!(matrix(&mirror.to_le_bytes(), 0).unwrap().0[0][0], -1.0);
845 }
846
847 #[test]
848 fn files_that_are_no_inventor_assemblies() {
849 assert!(components(b"ISO-10303-21;").is_none());
850 assert!(components(&[]).is_none());
851 let part = file(&[("PmGraphicsSegment", &[1], vec![])], &[]);
853 assert!(components(&part).is_none());
854 let bytes = assembly(vec![(0, placement(1, [0.0; 3], true))], vec![0; 200]);
856 assert!(components(&bytes).is_none());
857 }
858
859 #[test]
860 fn damaged_assemblies_never_panic() {
861 let bytes = assembly(
862 vec![(0, placement(1, [1.0, 2.0, 3.0], true))],
863 doc(&[(1, r"C:\A\Shaft.ipt")], &[(1, 1, "Shaft:1", true)]),
864 );
865 assert_eq!(components(&bytes).unwrap().len(), 1);
866 let d = doc(&[(1, r"C:\A\Shaft.ipt")], &[(1, 1, "Shaft:1", true)]);
867 for cut in 0..d.len() {
868 if let Some((files, end)) = file_table(&d[..cut]) {
869 let _ = occurrences(&d[..cut], end, &files, &HashSet::from([1]));
870 }
871 }
872 let p = placement(1, [1.0, 2.0, 3.0], true);
873 for cut in 0..p.len() {
874 let _ = matrix(&p[..cut], 14);
875 }
876 }
877}