1use std::collections::{HashMap, HashSet};
24use std::f64::consts::PI;
25
26use super::tagged::Tree;
27
28pub type P3 = [f64; 3];
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
31pub enum Kind {
32 Plane,
33 Cylinder,
34 Cone,
35 Torus,
36 Spline,
38 Fillet,
40 Ruled,
42 Other,
43}
44
45impl Kind {
46 pub fn is_freeform(self) -> bool {
48 matches!(self, Kind::Spline | Kind::Fillet | Kind::Ruled)
49 }
50
51 pub fn rank(self) -> u8 {
54 match self {
55 Kind::Plane => 0,
56 Kind::Cylinder => 1,
57 Kind::Cone => 2,
58 Kind::Torus => 3,
59 Kind::Ruled => 4,
60 Kind::Fillet => 5,
61 Kind::Spline => 6,
62 Kind::Other => 9,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
69pub struct Frame {
70 pub x: P3,
71 pub y: P3,
72 pub z: P3,
73 pub o: P3,
74}
75
76#[derive(Debug, Clone)]
77pub struct Surface {
78 pub id: i64,
79 pub kind: Kind,
80 pub kind_name: String,
81 pub orient: i64,
82 pub frame: Frame,
83 pub params: [f64; 2],
85 pub contours: Vec<(i64, i64)>,
87 pub list_type: Option<i64>,
89 pub twin_of: Option<i64>,
91 pub freeform: Option<super::freeform::Freeform>,
93}
94
95#[derive(Debug, Clone)]
96pub struct Curve {
97 pub id: i64,
98 pub ty: i64,
99 pub faces: [i64; 2],
100 pub next: [i64; 2],
101 pub dir: [i64; 2],
102 pub pts: Vec<[f64; 4]>,
104}
105
106impl Curve {
107 pub fn face(&self, side: usize) -> i64 {
109 if side == 0 {
110 self.faces[0]
111 } else {
112 self.faces[1]
113 }
114 }
115
116 pub fn next_on(&self, side: usize) -> i64 {
118 if side == 0 {
119 self.next[0]
120 } else {
121 self.next[1]
122 }
123 }
124
125 pub fn dir_on(&self, side: usize) -> i64 {
127 if side == 0 {
128 self.dir[0]
129 } else {
130 self.dir[1]
131 }
132 }
133}
134
135#[derive(Debug, Default)]
136pub struct Brep {
137 pub surfaces: Vec<Surface>,
138 pub curves: Vec<Curve>,
139}
140
141fn p3(v: &[f64]) -> Option<P3> {
142 Some([*v.first()?, *v.get(1)?, *v.get(2)?])
143}
144
145#[allow(clippy::indexing_slicing)]
147pub(crate) fn frame_of(rows: &[Vec<f64>]) -> Option<Frame> {
148 let r0 = p3(rows.first()?)?;
149 let r1 = p3(rows.get(1)?)?;
150 let r2 = p3(rows.get(2)?)?;
151 let o = p3(rows.get(3)?)?;
152 let mut m = [r0, r1, r2];
154 for _ in 0..2 {
156 for i in 0..3 {
157 for j in 0..3 {
158 if m[i][j].is_nan() {
159 let others: Vec<f64> = (0..3).filter(|&k| k != j).map(|k| m[i][k]).collect();
160 if others.iter().all(|x| x.is_finite()) {
161 let a = (j + 1) % 3;
162 let b = (j + 2) % 3;
163 let ca = [m[0][a], m[1][a], m[2][a]];
164 let cb = [m[0][b], m[1][b], m[2][b]];
165 if ca.iter().chain(cb.iter()).all(|x| x.is_finite()) {
166 m[i][j] = cross(ca, cb)[i];
167 } else {
168 m[i][j] = (1.0 - others.iter().map(|x| x * x).sum::<f64>())
169 .max(0.0)
170 .sqrt();
171 }
172 }
173 }
174 }
175 }
176 }
177 let x = [m[0][0], m[1][0], m[2][0]];
178 let y = [m[0][1], m[1][1], m[2][1]];
179 let z = [m[0][2], m[1][2], m[2][2]];
180 if x.iter()
181 .chain(y.iter())
182 .chain(z.iter())
183 .chain(o.iter())
184 .any(|v| !v.is_finite())
185 {
186 return None;
187 }
188 Some(Frame { x, y, z, o })
189}
190
191pub fn cross(a: P3, b: P3) -> P3 {
192 [
193 a[1] * b[2] - a[2] * b[1],
194 a[2] * b[0] - a[0] * b[2],
195 a[0] * b[1] - a[1] * b[0],
196 ]
197}
198
199pub fn dot(a: P3, b: P3) -> f64 {
200 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
201}
202
203pub fn sub(a: P3, b: P3) -> P3 {
204 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
205}
206
207pub fn norm(a: P3) -> f64 {
208 dot(a, a).sqrt()
209}
210
211pub fn grow(lo: &mut P3, hi: &mut P3, p: P3) {
213 for ((l, h), x) in lo.iter_mut().zip(hi.iter_mut()).zip(p) {
214 *l = l.min(x);
215 *h = h.max(x);
216 }
217}
218
219pub fn lin(a: P3, k: f64, b: P3) -> P3 {
221 [a[0] + k * b[0], a[1] + k * b[1], a[2] + k * b[2]]
222}
223
224pub fn scale(k: f64, a: P3) -> P3 {
226 [k * a[0], k * a[1], k * a[2]]
227}
228
229fn axpy(out: &mut P3, s: f64, v: P3) {
230 out[0] += s * v[0];
231 out[1] += s * v[1];
232 out[2] += s * v[2];
233}
234
235pub fn extract(tree: &Tree) -> Brep {
237 let mut brep = Brep::default();
238 let root = tree.root();
239 let mut nodes = vec![root];
240 let mut stack = vec![root];
243 let mut seen = HashSet::new();
244 while let Some(n) = stack.pop() {
245 if !seen.insert(n) {
246 continue;
247 }
248 nodes.push(n);
249 match tree.node(n) {
250 super::tagged::Node::Struct(kids) => stack.extend(kids.iter().map(|(_, c)| *c)),
251 super::tagged::Node::List(items) => stack.extend(items.iter().copied()),
252 _ => {}
253 }
254 }
255 for &n in &nodes {
256 if let Some(s) = surface(tree, n) {
257 brep.surfaces.push(s);
258 } else if let Some(c) = curve(tree, n) {
259 brep.curves.push(c);
260 }
261 }
262 brep
263}
264
265fn surface(tree: &Tree, n: usize) -> Option<Surface> {
266 let id = tree.int_at(n, "geom_id")?;
267 let geom_type = tree.int_at(n, "geom_type")?;
268 tree.child(n, "envlp")?;
269 let (kind_name, prim) = tree
270 .children_with_prefix(n, "srf_prim_ptr(")
271 .into_iter()
272 .next()
273 .map(|(name, c)| {
274 (
275 name.trim_start_matches("srf_prim_ptr(")
276 .trim_end_matches(')')
277 .to_string(),
278 c,
279 )
280 })?;
281 let freeform = super::freeform::read(tree, &kind_name, prim);
282 let kind = match (geom_type, kind_name.as_str()) {
283 (34, _) => Kind::Plane,
284 (36, _) => Kind::Cylinder,
285 (37, _) => Kind::Cone,
286 (38, _) => Kind::Torus,
287 (_, "splsrf") if freeform.is_some() => Kind::Spline,
288 (_, "fillet_srf") if freeform.is_some() => Kind::Fillet,
289 (_, "ruled_srf") if freeform.is_some() => Kind::Ruled,
290 _ => Kind::Other,
291 };
292 let frame = tree
293 .child(prim, "local_sys")
294 .and_then(|ls| frame_of(&tree.rows(ls)))
295 .unwrap_or(Frame {
296 x: [1.0, 0.0, 0.0],
297 y: [0.0, 1.0, 0.0],
298 z: [0.0, 0.0, 1.0],
299 o: [0.0; 3],
300 });
301 let params = match kind {
302 Kind::Cylinder => [tree.dbl_at(prim, "radius").unwrap_or(f64::NAN), 0.0],
303 Kind::Cone => [tree.dbl_at(prim, "half_angle").unwrap_or(f64::NAN), 0.0],
304 Kind::Torus => [
305 tree.dbl_at(prim, "radius1").unwrap_or(f64::NAN),
306 tree.dbl_at(prim, "radius2").unwrap_or(f64::NAN),
307 ],
308 _ => [0.0, 0.0],
309 };
310 let mut contours = Vec::new();
311 let mut conts: Vec<usize> = tree.children(n, "frst_cntr_ptr");
312 for c in tree.children(n, "next_cntr_ptr") {
313 match tree.node(c) {
314 super::tagged::Node::List(items) => conts.extend(items.iter().copied()),
315 _ => conts.push(c),
316 }
317 }
318 for c in conts {
319 if let Some(start) = tree.int_at(c, "frst_cntr_crv_hdr_ptr") {
320 contours.push((start, tree.int_at(c, "trv").unwrap_or(0)));
321 }
322 }
323 let list_type = tree
324 .child(n, "topol_ref_data")
325 .and_then(|t| tree.int_at(t, "list_type"));
326 let orient = tree.int_at(n, "orient").unwrap_or(1);
327 Some(Surface {
328 id,
329 kind,
330 kind_name,
331 orient,
332 frame,
333 params,
334 contours,
335 list_type,
336 twin_of: None,
337 freeform,
338 })
339}
340
341fn curve(tree: &Tree, n: usize) -> Option<Curve> {
342 let id = tree.int_at(n, "crv_id")?;
343 let arr = tree.child(n, "crv_pnt_arr")?;
344 let faces = [
345 tree.int_at(n, "crv_hdr_geom_ptr[0]")?,
346 tree.int_at(n, "crv_hdr_geom_ptr[1]")?,
347 ];
348 let next = [
349 tree.int_at(n, "next_crv_hdr_ptr[0]")?,
350 tree.int_at(n, "next_crv_hdr_ptr[1]")?,
351 ];
352 let dir_list = tree
353 .child(n, "crv_pnt_dir")
354 .map(|d| tree.dbls(d))
355 .unwrap_or_default();
356 let dir = [
357 dir_list.first().copied().unwrap_or(1.0) as i64,
358 dir_list.get(1).copied().unwrap_or(1.0) as i64,
359 ];
360 let pts = tree
361 .rows(arr)
362 .into_iter()
363 .filter_map(|r| <[f64; 4]>::try_from(r.as_slice()).ok())
364 .collect();
365 Some(Curve {
366 id,
367 ty: tree.int_at(n, "type").unwrap_or(0),
368 faces,
369 next,
370 dir,
371 pts,
372 })
373}
374
375#[derive(Debug, Clone, Copy, PartialEq)]
377pub struct Conv {
378 pub su: f64,
380 pub off_u: f64,
382 pub sv: f64,
383 pub off_v: f64,
385 pub swap: bool,
387 pub extra: f64,
389}
390
391impl Conv {
392 pub const IDENTITY: Conv = Conv {
393 su: 1.0,
394 off_u: 0.0,
395 sv: 1.0,
396 off_v: 0.0,
397 swap: false,
398 extra: 1.0,
399 };
400
401 pub fn handed(&self) -> f64 {
403 self.su * self.sv * if self.swap { -1.0 } else { 1.0 }
404 }
405}
406
407pub fn candidates(kind: Kind) -> Vec<Conv> {
409 let mut out = Vec::new();
410 match kind {
411 Kind::Plane => {
412 for su in [1.0, -1.0] {
413 for sv in [1.0, -1.0] {
414 out.push(Conv {
415 su,
416 sv,
417 ..Conv::IDENTITY
418 });
419 }
420 }
421 }
422 Kind::Cylinder => {
423 for su in [1.0, -1.0] {
424 for off_u in [0.0, PI] {
425 for sv in [1.0, -1.0] {
426 out.push(Conv {
427 su,
428 off_u,
429 sv,
430 ..Conv::IDENTITY
431 });
432 }
433 }
434 }
435 }
436 Kind::Cone => {
437 for su in [1.0, -1.0] {
438 for off_u in [0.0, PI] {
439 for sv in [1.0, -1.0] {
440 for extra in [0.0, 1.0] {
441 out.push(Conv {
442 su,
443 off_u,
444 sv,
445 extra,
446 ..Conv::IDENTITY
447 });
448 }
449 }
450 }
451 }
452 }
453 Kind::Torus => {
454 for swap in [false, true] {
455 for su in [1.0, -1.0] {
456 for off_u in [0.0, PI] {
457 for sv in [1.0, -1.0] {
458 for off_v in [0.0, 0.5 * PI, PI, 1.5 * PI] {
459 for extra in [1.0, -1.0] {
460 out.push(Conv {
461 su,
462 off_u,
463 sv,
464 off_v,
465 swap,
466 extra,
467 });
468 }
469 }
470 }
471 }
472 }
473 }
474 }
475 Kind::Spline | Kind::Fillet | Kind::Ruled => out.push(Conv::IDENTITY),
476 Kind::Other => {}
477 }
478 out
479}
480
481pub fn eval(s: &Surface, c: &Conv, u: f64, v: f64) -> P3 {
483 let f = &s.frame;
484 let mut p = f.o;
485 match s.kind {
486 Kind::Plane => {
487 axpy(&mut p, c.su * u, f.x);
488 axpy(&mut p, c.sv * v, f.y);
489 }
490 Kind::Cylinder => {
491 let r = s.params[0].abs();
492 let a = c.su * u + c.off_u;
493 axpy(&mut p, r * a.cos(), f.x);
494 axpy(&mut p, r * a.sin(), f.y);
495 axpy(&mut p, c.sv * v, f.z);
496 }
497 Kind::Cone => {
498 let ha = s.params[0];
499 let a = c.su * u + c.off_u;
500 let (rr, h) = if c.extra > 0.5 {
501 (v * ha.sin(), v * ha.cos())
502 } else {
503 (v * ha.tan(), v)
504 };
505 axpy(&mut p, rr * a.cos(), f.x);
506 axpy(&mut p, rr * a.sin(), f.y);
507 axpy(&mut p, c.sv * h, f.z);
508 }
509 Kind::Torus => {
510 let (r1, r2) = (s.params[0], s.params[1].abs() * c.extra);
511 let (a, b) = if c.swap { (v, u) } else { (u, v) };
512 let th = c.su * a + c.off_u;
513 let ph = c.sv * b + c.off_v;
514 let ring = r1 + r2 * ph.cos();
515 axpy(&mut p, ring * th.cos(), f.x);
516 axpy(&mut p, ring * th.sin(), f.y);
517 axpy(&mut p, r2 * ph.sin(), f.z);
518 }
519 Kind::Spline | Kind::Fillet | Kind::Ruled => {
520 return s
521 .freeform
522 .as_ref()
523 .map_or([f64::NAN; 3], |ff| ff.eval(u, v));
524 }
525 Kind::Other => return [f64::NAN; 3],
526 }
527 p
528}
529
530type Adjacency = HashMap<i64, Vec<(usize, usize)>>;
532
533const MAX_SEEDS: usize = 16;
536
537pub fn resolve(brep: &Brep) -> HashMap<i64, Conv> {
545 let by_id: HashMap<i64, &Surface> = brep.surfaces.iter().map(|s| (s.id, s)).collect();
546 let mut adj: Adjacency = HashMap::new();
547 for (ci, c) in brep.curves.iter().enumerate() {
548 if by_id.contains_key(&c.faces[0]) && by_id.contains_key(&c.faces[1]) {
549 adj.entry(c.faces[0]).or_default().push((ci, 0));
550 adj.entry(c.faces[1]).or_default().push((ci, 1));
551 }
552 }
553 let mut fixed: HashMap<i64, Conv> = HashMap::new();
554 for s in &brep.surfaces {
555 if s.kind.is_freeform() {
556 fixed.insert(s.id, Conv::IDENTITY);
557 }
558 if s.kind == Kind::Plane {
559 let f = &s.frame;
560 let ident = |a: P3, b: P3| sub(a, b).iter().all(|d| d.abs() < 1e-9);
561 if ident(f.x, [1.0, 0.0, 0.0])
562 && ident(f.y, [0.0, 1.0, 0.0])
563 && ident(f.z, [0.0, 0.0, 1.0])
564 {
565 fixed.insert(s.id, Conv::IDENTITY);
566 }
567 }
568 }
569 if fixed.is_empty() {
570 fixed = seeded(brep, &by_id, &adj);
573 } else {
574 let tol = tolerance(brep, &by_id, &adj, &fixed);
575 propagate(brep, &by_id, &adj, &mut fixed, tol);
576 }
577 for s in &brep.surfaces {
578 if let Some(c) = candidates(s.kind).first() {
579 fixed.entry(s.id).or_insert(*c);
580 }
581 }
582 fixed
583}
584
585fn tolerance(
588 brep: &Brep,
589 by_id: &HashMap<i64, &Surface>,
590 adj: &Adjacency,
591 fixed: &HashMap<i64, Conv>,
592) -> f64 {
593 let mut lo = [f64::INFINITY; 3];
594 let mut hi = [f64::NEG_INFINITY; 3];
595 for (&id, conv) in fixed {
596 let Some(s) = by_id.get(&id) else { continue };
597 for &(ci, side) in adj
598 .get(&id)
599 .map(|v| v.as_slice())
600 .unwrap_or(&[])
601 .iter()
602 .take(50)
603 {
604 let Some(c) = brep.curves.get(ci) else {
605 continue;
606 };
607 for r in &c.pts {
608 let (u, v) = if side == 0 {
609 (r[0], r[1])
610 } else {
611 (r[2], r[3])
612 };
613 if !(u.is_finite() && v.is_finite()) {
614 continue;
615 }
616 grow(&mut lo, &mut hi, eval(s, conv, u, v));
617 }
618 }
619 }
620 let size = if lo[0].is_finite() {
621 norm(sub(hi, lo)).max(1e-3)
622 } else {
623 1.0
624 };
625 (1e-6 * size).max(1e-6) * 10.0
626}
627
628fn propagate(
632 brep: &Brep,
633 by_id: &HashMap<i64, &Surface>,
634 adj: &Adjacency,
635 fixed: &mut HashMap<i64, Conv>,
636 tol: f64,
637) {
638 for _round in 0..60 {
639 let mut changed = false;
640 for s in &brep.surfaces {
641 if fixed.contains_key(&s.id) {
642 continue;
643 }
644 let cands = candidates(s.kind);
645 if cands.is_empty() {
646 continue;
647 }
648 let mut score = vec![0usize; cands.len()];
649 let mut rows = 0usize;
650 for &(ci, side) in adj.get(&s.id).map(|v| v.as_slice()).unwrap_or(&[]) {
651 let Some(c) = brep.curves.get(ci) else {
652 continue;
653 };
654 let other = c.face(1 - side);
655 let (Some(oconv), Some(os)) = (fixed.get(&other), by_id.get(&other)) else {
656 continue;
657 };
658 for r in &c.pts {
659 let (um, vm, uo, vo) = if side == 0 {
660 (r[0], r[1], r[2], r[3])
661 } else {
662 (r[2], r[3], r[0], r[1])
663 };
664 if ![um, vm, uo, vo].iter().all(|x| x.is_finite()) {
665 continue;
666 }
667 let p = eval(os, oconv, uo, vo);
668 if !p.iter().all(|x| x.is_finite()) {
669 continue;
670 }
671 rows += 1;
672 for (k, cand) in cands.iter().enumerate() {
673 let q = eval(s, cand, um, vm);
674 if norm(sub(q, p)) < tol {
675 if let Some(sc) = score.get_mut(k) {
676 *sc += 1;
677 }
678 }
679 }
680 }
681 }
682 if rows == 0 {
683 continue;
684 }
685 let Some((best, best_score)) = first_max(&score) else {
688 continue;
689 };
690 if best_score > 0 && best_score * 10 >= rows * 6 {
691 if let Some(c) = cands.get(best) {
692 fixed.insert(s.id, *c);
693 changed = true;
694 }
695 }
696 }
697 if !changed {
698 break;
699 }
700 }
701}
702
703fn first_max(scores: &[usize]) -> Option<(usize, usize)> {
705 scores
706 .iter()
707 .enumerate()
708 .fold(None, |acc: Option<(usize, usize)>, (k, &v)| match acc {
709 Some((_, b)) if b >= v => acc,
710 _ => Some((k, v)),
711 })
712}
713
714fn agreement(
716 brep: &Brep,
717 by_id: &HashMap<i64, &Surface>,
718 fixed: &HashMap<i64, Conv>,
719 tol: f64,
720) -> usize {
721 let mut n = 0;
722 for c in &brep.curves {
723 let (Some(a), Some(b)) = (by_id.get(&c.face(0)), by_id.get(&c.face(1))) else {
724 continue;
725 };
726 let (Some(ca), Some(cb)) = (fixed.get(&a.id), fixed.get(&b.id)) else {
727 continue;
728 };
729 for r in &c.pts {
730 let p = eval(a, ca, r[0], r[1]);
731 let q = eval(b, cb, r[2], r[3]);
732 if norm(sub(p, q)) < tol {
733 n += 1;
734 }
735 }
736 }
737 n
738}
739
740fn seeded(brep: &Brep, by_id: &HashMap<i64, &Surface>, adj: &Adjacency) -> HashMap<i64, Conv> {
746 let rank = |k: Kind| match k {
747 Kind::Plane => 0,
748 Kind::Cylinder => 1,
749 Kind::Cone => 2,
750 Kind::Torus => 3,
751 _ => 4,
752 };
753 let mut fixed: HashMap<i64, Conv> = HashMap::new();
754 let mut skip: HashSet<i64> = HashSet::new();
755 for _ in 0..MAX_SEEDS {
756 let Some(seed) = brep
757 .surfaces
758 .iter()
759 .filter(|s| {
760 rank(s.kind) < 4
761 && !fixed.contains_key(&s.id)
762 && !skip.contains(&s.id)
763 && adj.get(&s.id).is_some_and(|v| !v.is_empty())
764 })
765 .min_by_key(|s| (rank(s.kind), s.id))
766 else {
767 break;
768 };
769 let mut tried: Vec<(usize, HashMap<i64, Conv>)> = Vec::new();
770 for c in candidates(seed.kind) {
771 let mut f = fixed.clone();
772 f.insert(seed.id, c);
773 let tol = tolerance(brep, by_id, adj, &f);
774 propagate(brep, by_id, adj, &mut f, tol);
775 tried.push((agreement(brep, by_id, &f, tol), f));
776 }
777 let scores: Vec<usize> = tried.iter().map(|(s, _)| *s).collect();
778 match first_max(&scores) {
779 Some((k, score)) if score > 0 => {
780 if let Some((_, f)) = tried.into_iter().nth(k) {
781 fixed = f;
782 }
783 }
784 _ => {
785 skip.insert(seed.id);
786 }
787 }
788 }
789 fixed
790}
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 fn brep_of_ctc01() -> Brep {
797 let data = std::fs::read(concat!(
798 env!("CARGO_MANIFEST_DIR"),
799 "/tests/fixtures/cad/nist/ctc-01/creo-10/nist_ctc_01_asme1_rd_cr10030.prt.5"
800 ))
801 .unwrap();
802 let toc = super::super::container::toc(&data).unwrap();
803 let sec = super::super::container::section(&data, &toc, "VisibGeom")
804 .unwrap()
805 .unwrap();
806 extract(&super::super::tagged::parse(&sec).unwrap())
807 }
808
809 #[test]
810 fn faces_edges_and_frames_of_ctc_01() {
811 let b = brep_of_ctc01();
812 assert_eq!(b.surfaces.len(), 141);
813 assert_eq!(b.curves.len(), 422);
814 let kinds = |k: Kind| b.surfaces.iter().filter(|s| s.kind == k).count();
815 assert_eq!(
816 (kinds(Kind::Plane), kinds(Kind::Cylinder), kinds(Kind::Cone)),
817 (78, 59, 4)
818 );
819 for s in &b.surfaces {
821 let f = &s.frame;
822 assert!((dot(cross(f.x, f.y), f.z) - 1.0).abs() < 1e-9, "{}", s.id);
823 }
824 let hole = b.surfaces.iter().find(|s| s.id == 210).unwrap();
825 assert_eq!(hole.kind, Kind::Cylinder);
826 assert!((hole.params[0].abs() - 17.5).abs() < 1e-9);
827 assert_eq!(hole.frame.o, [-325.0, -175.0, 0.0]);
828 assert_eq!(
830 b.surfaces
831 .iter()
832 .filter(|s| s.list_type == Some(98))
833 .count(),
834 137
835 );
836 }
837
838 fn edge_check(b: &Brep, fixed: &HashMap<i64, Conv>) -> (usize, usize) {
840 let by_id: HashMap<i64, &Surface> = b.surfaces.iter().map(|s| (s.id, s)).collect();
841 let (mut good, mut bad) = (0, 0);
842 for c in &b.curves {
843 let (Some(a), Some(bb)) = (by_id.get(&c.faces[0]), by_id.get(&c.faces[1])) else {
844 continue;
845 };
846 for r in &c.pts {
847 let p = eval(a, &fixed[&a.id], r[0], r[1]);
848 let q = eval(bb, &fixed[&bb.id], r[2], r[3]);
849 if norm(sub(p, q)) < 1e-3 {
850 good += 1;
851 } else {
852 bad += 1;
853 }
854 }
855 }
856 (good, bad)
857 }
858
859 #[test]
860 fn conventions_make_both_sides_of_every_edge_coincide() {
861 let b = brep_of_ctc01();
862 assert_eq!(edge_check(&b, &resolve(&b)), (1437, 0));
864 }
865
866 #[test]
870 fn a_part_without_a_plane_in_the_identity_frame_is_seeded() {
871 let mut b = brep_of_ctc01();
872 let (sin, cos) = 30f64.to_radians().sin_cos();
873 let turn = |p: P3| [cos * p[0] - sin * p[1], sin * p[0] + cos * p[1], p[2]];
874 for s in &mut b.surfaces {
875 let f = &s.frame;
876 s.frame = Frame {
877 x: turn(f.x),
878 y: turn(f.y),
879 z: turn(f.z),
880 o: turn(f.o),
881 };
882 }
883 let identity = b
884 .surfaces
885 .iter()
886 .filter(|s| {
887 s.kind == Kind::Plane
888 && s.frame.x == [1.0, 0.0, 0.0]
889 && s.frame.y == [0.0, 1.0, 0.0]
890 })
891 .count();
892 assert_eq!(identity, 0);
893 assert_eq!(edge_check(&b, &resolve(&b)), (1437, 0));
894 }
895}