1use std::collections::HashSet;
4
5use brepkit_math::mat::Mat4;
6use brepkit_math::nurbs::curve::NurbsCurve;
7use brepkit_math::nurbs::surface::NurbsSurface;
8use brepkit_math::tolerance::Tolerance;
9use brepkit_math::vec::Vec3;
10use brepkit_topology::Topology;
11use brepkit_topology::edge::{EdgeCurve, EdgeId};
12use brepkit_topology::face::{FaceId, FaceSurface};
13use brepkit_topology::solid::SolidId;
14use brepkit_topology::vertex::VertexId;
15use brepkit_topology::wire::WireId;
16
17#[allow(clippy::too_many_lines)]
29pub fn transform_solid(
30 topo: &mut Topology,
31 solid: SolidId,
32 matrix: &Mat4,
33) -> Result<(), crate::OperationsError> {
34 let tol = Tolerance::new();
35 if tol.approx_eq(matrix.determinant(), 0.0) {
36 return Err(crate::OperationsError::InvalidInput {
37 reason: "transform matrix is degenerate (zero determinant)".into(),
38 });
39 }
40
41 let (vertex_ids, edge_ids, face_ids) = collect_solid_entities(topo, solid)?;
43
44 for vid in vertex_ids {
46 let vertex = topo.vertex_mut(vid)?;
47 let new_point = matrix.mul_point(vertex.point());
48 vertex.set_point(new_point);
49 }
50
51 transform_edges(topo, &edge_ids, matrix)?;
53
54 let normal_matrix = matrix.inverse()?.transpose();
57
58 for fid in face_ids {
59 let face = topo.face(fid)?;
60 match face.surface() {
61 FaceSurface::Plane { normal, .. } => {
62 let n = *normal;
63 let transformed =
67 normal_matrix.mul_point(brepkit_math::vec::Point3::new(n.x(), n.y(), n.z()));
68 let origin = normal_matrix.mul_point(brepkit_math::vec::Point3::new(0.0, 0.0, 0.0));
71 let raw = Vec3::new(
72 transformed.x() - origin.x(),
73 transformed.y() - origin.y(),
74 transformed.z() - origin.z(),
75 );
76 let new_normal = raw.normalize()?;
77
78 let wire = topo.wire(face.outer_wire())?;
81 let first_oe = &wire.edges()[0];
82 let edge = topo.edge(first_oe.edge())?;
83 let ref_vid = if first_oe.is_forward() {
84 edge.start()
85 } else {
86 edge.end()
87 };
88 let ref_point = topo.vertex(ref_vid)?.point();
89 let new_d = new_normal.dot(Vec3::new(ref_point.x(), ref_point.y(), ref_point.z()));
90
91 let face_mut = topo.face_mut(fid)?;
92 face_mut.set_surface(FaceSurface::Plane {
93 normal: new_normal,
94 d: new_d,
95 });
96 }
97 FaceSurface::Nurbs(s) => {
98 let new_control_points: Vec<Vec<_>> = s
99 .control_points()
100 .iter()
101 .map(|row| row.iter().map(|pt| matrix.mul_point(*pt)).collect())
102 .collect();
103 let new_surface = NurbsSurface::new(
104 s.degree_u(),
105 s.degree_v(),
106 s.knots_u().to_vec(),
107 s.knots_v().to_vec(),
108 new_control_points,
109 s.weights().to_vec(),
110 );
111 topo.face_mut(fid)?
112 .set_surface(FaceSurface::Nurbs(new_surface?));
113 }
114 FaceSurface::Cylinder(cyl) => {
115 let new_origin = matrix.mul_point(cyl.origin());
116 let new_axis = transform_direction(matrix, cyl.axis())?;
117 let new_radius = scaled_radius(matrix, cyl.axis(), cyl.radius());
119 let new_cyl = brepkit_math::surfaces::CylindricalSurface::new(
120 new_origin, new_axis, new_radius,
121 )?;
122 topo.face_mut(fid)?
123 .set_surface(FaceSurface::Cylinder(new_cyl));
124 }
125 FaceSurface::Cone(cone) => {
126 if is_uniform_scale(matrix) {
127 let new_apex = matrix.mul_point(cone.apex());
128 let new_axis = transform_direction(matrix, cone.axis())?;
129 let new_cone = brepkit_math::surfaces::ConicalSurface::new(
130 new_apex,
131 new_axis,
132 cone.half_angle(),
133 )?;
134 topo.face_mut(fid)?.set_surface(FaceSurface::Cone(new_cone));
135 } else {
136 let v_range = analytic_face_v_range(topo, fid, |pt| cone.project_point(pt).1)?;
137 let cone_clone = cone.clone();
138 let nurbs = brepkit_heal::construct::convert_surface::cone_to_nurbs(
143 &cone_clone,
144 v_range,
145 )
146 .map_err(|e| crate::OperationsError::InvalidInput {
147 reason: format!("cone_to_nurbs failed: {e}"),
148 })?;
149 let transformed = transform_nurbs_surface(&nurbs, matrix)?;
150 topo.face_mut(fid)?
151 .set_surface(FaceSurface::Nurbs(transformed));
152 }
153 }
154 FaceSurface::Sphere(sph) => {
155 if is_uniform_scale(matrix) {
156 let new_center = matrix.mul_point(sph.center());
157 let m = &matrix.0;
159 let sx = (m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]).sqrt();
160 let new_sph = brepkit_math::surfaces::SphericalSurface::new(
161 new_center,
162 sph.radius() * sx,
163 )?;
164 topo.face_mut(fid)?
165 .set_surface(FaceSurface::Sphere(new_sph));
166 } else {
167 let (v_min, v_max) = sphere_face_v_range(topo, fid, sph)?;
170 let sph_clone = sph.clone();
171 let nurbs = sphere_to_transformed_nurbs(&sph_clone, matrix, v_min, v_max)?;
172 topo.face_mut(fid)?.set_surface(FaceSurface::Nurbs(nurbs));
173 }
174 }
175 FaceSurface::Torus(tor) => {
176 if is_uniform_scale(matrix) {
177 let new_center = matrix.mul_point(tor.center());
178 let m = &matrix.0;
179 let sx = (m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]).sqrt();
180 let new_tor = brepkit_math::surfaces::ToroidalSurface::new(
181 new_center,
182 tor.major_radius() * sx,
183 tor.minor_radius() * sx,
184 )?;
185 topo.face_mut(fid)?.set_surface(FaceSurface::Torus(new_tor));
186 } else {
187 let tor_clone = tor.clone();
188 let nurbs =
192 brepkit_heal::construct::convert_surface::torus_to_nurbs(&tor_clone)
193 .map_err(|e| crate::OperationsError::InvalidInput {
194 reason: format!("torus_to_nurbs failed: {e}"),
195 })?;
196 let transformed = transform_nurbs_surface(&nurbs, matrix)?;
197 topo.face_mut(fid)?
198 .set_surface(FaceSurface::Nurbs(transformed));
199 }
200 }
201 }
202 }
203
204 Ok(())
205}
206
207fn sphere_face_v_range(
213 topo: &Topology,
214 face_id: FaceId,
215 sph: &brepkit_math::surfaces::SphericalSurface,
216) -> Result<(f64, f64), crate::OperationsError> {
217 use std::f64::consts::FRAC_PI_2;
218
219 let face = topo.face(face_id)?;
220 let wire = topo.wire(face.outer_wire())?;
221 let mut v_vals = Vec::new();
222
223 for oe in wire.edges() {
224 let edge = topo.edge(oe.edge())?;
225 let pt = topo.vertex(edge.start())?.point();
226 let (_u, v) = sph.project_point(pt);
227 v_vals.push(v);
228 }
229
230 if v_vals.is_empty() {
231 return Ok((-FRAC_PI_2, FRAC_PI_2));
233 }
234
235 let boundary_v = v_vals.iter().copied().sum::<f64>() / v_vals.len() as f64;
238
239 let center = sph.center();
250 let avg_boundary_z: f64 = {
251 let mut sum = 0.0;
252 for oe in wire.edges() {
253 let edge = topo.edge(oe.edge())?;
254 let pt = topo.vertex(edge.start())?.point();
255 sum += pt.z() - center.z();
256 }
257 sum / wire.edges().len() as f64
258 };
259
260 if boundary_v.abs() < 0.1 {
266 let mut has_pole_north = false;
270 let mut has_pole_south = false;
271 for oe in wire.edges() {
272 let edge = topo.edge(oe.edge())?;
273 if edge.start() == edge.end() {
274 let pt = topo.vertex(edge.start())?.point();
275 let dz = pt.z() - center.z();
276 if dz > 0.0 {
277 has_pole_north = true;
278 } else {
279 has_pole_south = true;
280 }
281 }
282 }
283 if has_pole_north {
284 return Ok((boundary_v, FRAC_PI_2));
285 }
286 if has_pole_south {
287 return Ok((-FRAC_PI_2, boundary_v));
288 }
289 if avg_boundary_z >= 0.0 {
296 return Ok((boundary_v, FRAC_PI_2));
297 }
298 return Ok((-FRAC_PI_2, boundary_v));
299 }
300
301 if boundary_v > 0.0 {
302 Ok((boundary_v, FRAC_PI_2))
303 } else {
304 Ok((-FRAC_PI_2, boundary_v))
305 }
306}
307
308fn scaled_radius(matrix: &Mat4, axis: Vec3, radius: f64) -> f64 {
313 let perp = if axis.x().abs() < 0.9 {
315 Vec3::new(1.0, 0.0, 0.0)
316 .cross(axis)
317 .normalize()
318 .unwrap_or(Vec3::new(1.0, 0.0, 0.0))
319 } else {
320 Vec3::new(0.0, 1.0, 0.0)
321 .cross(axis)
322 .normalize()
323 .unwrap_or(Vec3::new(0.0, 1.0, 0.0))
324 };
325 let origin = brepkit_math::vec::Point3::new(0.0, 0.0, 0.0);
327 let end =
328 brepkit_math::vec::Point3::new(perp.x() * radius, perp.y() * radius, perp.z() * radius);
329 let t_origin = matrix.mul_point(origin);
330 let t_end = matrix.mul_point(end);
331 let diff = t_end - t_origin;
332 diff.length()
333}
334
335#[allow(clippy::too_many_lines)]
339fn transform_face_surface(
340 topo: &mut Topology,
341 fid: FaceId,
342 matrix: &Mat4,
343 normal_matrix: &Mat4,
344) -> Result<(), crate::OperationsError> {
345 let face = topo.face(fid)?;
346 match face.surface() {
347 FaceSurface::Plane { normal, .. } => {
348 let n = *normal;
349 let transformed =
350 normal_matrix.mul_point(brepkit_math::vec::Point3::new(n.x(), n.y(), n.z()));
351 let origin = normal_matrix.mul_point(brepkit_math::vec::Point3::new(0.0, 0.0, 0.0));
352 let raw = Vec3::new(
353 transformed.x() - origin.x(),
354 transformed.y() - origin.y(),
355 transformed.z() - origin.z(),
356 );
357 let new_normal = raw.normalize()?;
358 let wire = topo.wire(face.outer_wire())?;
359 let first_oe =
360 wire.edges()
361 .first()
362 .ok_or_else(|| crate::OperationsError::InvalidInput {
363 reason: "face has empty outer wire".into(),
364 })?;
365 let edge = topo.edge(first_oe.edge())?;
366 let ref_vid = if first_oe.is_forward() {
367 edge.start()
368 } else {
369 edge.end()
370 };
371 let ref_point = topo.vertex(ref_vid)?.point();
372 let new_d = new_normal.dot(Vec3::new(ref_point.x(), ref_point.y(), ref_point.z()));
373 topo.face_mut(fid)?.set_surface(FaceSurface::Plane {
374 normal: new_normal,
375 d: new_d,
376 });
377 }
378 FaceSurface::Nurbs(s) => {
379 let new_control_points: Vec<Vec<_>> = s
380 .control_points()
381 .iter()
382 .map(|row| row.iter().map(|pt| matrix.mul_point(*pt)).collect())
383 .collect();
384 let new_surface = NurbsSurface::new(
385 s.degree_u(),
386 s.degree_v(),
387 s.knots_u().to_vec(),
388 s.knots_v().to_vec(),
389 new_control_points,
390 s.weights().to_vec(),
391 );
392 topo.face_mut(fid)?
393 .set_surface(FaceSurface::Nurbs(new_surface?));
394 }
395 FaceSurface::Cylinder(cyl) => {
396 let new_origin = matrix.mul_point(cyl.origin());
397 let new_axis = transform_direction(matrix, cyl.axis())?;
398 let new_radius = scaled_radius(matrix, cyl.axis(), cyl.radius());
399 let new_cyl =
400 brepkit_math::surfaces::CylindricalSurface::new(new_origin, new_axis, new_radius)?;
401 topo.face_mut(fid)?
402 .set_surface(FaceSurface::Cylinder(new_cyl));
403 }
404 FaceSurface::Cone(cone) => {
405 if is_uniform_scale(matrix) {
406 let new_apex = matrix.mul_point(cone.apex());
407 let new_axis = transform_direction(matrix, cone.axis())?;
408 let new_cone = brepkit_math::surfaces::ConicalSurface::new(
409 new_apex,
410 new_axis,
411 cone.half_angle(),
412 )?;
413 topo.face_mut(fid)?.set_surface(FaceSurface::Cone(new_cone));
414 } else {
415 let v_range = analytic_face_v_range(topo, fid, |pt| cone.project_point(pt).1)?;
416 let cone_clone = cone.clone();
417 let nurbs =
418 brepkit_heal::construct::convert_surface::cone_to_nurbs(&cone_clone, v_range)
419 .map_err(|e| crate::OperationsError::InvalidInput {
420 reason: format!("cone_to_nurbs failed: {e}"),
421 })?;
422 let transformed = transform_nurbs_surface(&nurbs, matrix)?;
423 topo.face_mut(fid)?
424 .set_surface(FaceSurface::Nurbs(transformed));
425 }
426 }
427 FaceSurface::Sphere(sph) => {
428 if is_uniform_scale(matrix) {
429 let new_center = matrix.mul_point(sph.center());
430 let m = &matrix.0;
431 let sx = (m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]).sqrt();
432 let new_sph =
433 brepkit_math::surfaces::SphericalSurface::new(new_center, sph.radius() * sx)?;
434 topo.face_mut(fid)?
435 .set_surface(FaceSurface::Sphere(new_sph));
436 } else {
437 let (v_min, v_max) = sphere_face_v_range(topo, fid, sph)?;
438 let sph_clone = sph.clone();
439 let nurbs = sphere_to_transformed_nurbs(&sph_clone, matrix, v_min, v_max)?;
440 topo.face_mut(fid)?.set_surface(FaceSurface::Nurbs(nurbs));
441 }
442 }
443 FaceSurface::Torus(tor) => {
444 if is_uniform_scale(matrix) {
445 let new_center = matrix.mul_point(tor.center());
446 let m = &matrix.0;
447 let sx = (m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]).sqrt();
448 let new_tor = brepkit_math::surfaces::ToroidalSurface::new(
449 new_center,
450 tor.major_radius() * sx,
451 tor.minor_radius() * sx,
452 )?;
453 topo.face_mut(fid)?.set_surface(FaceSurface::Torus(new_tor));
454 } else {
455 let tor_clone = tor.clone();
456 let nurbs = brepkit_heal::construct::convert_surface::torus_to_nurbs(&tor_clone)
457 .map_err(|e| crate::OperationsError::InvalidInput {
458 reason: format!("torus_to_nurbs failed: {e}"),
459 })?;
460 let transformed = transform_nurbs_surface(&nurbs, matrix)?;
461 topo.face_mut(fid)?
462 .set_surface(FaceSurface::Nurbs(transformed));
463 }
464 }
465 }
466 Ok(())
467}
468
469fn analytic_face_v_range(
473 topo: &Topology,
474 face_id: FaceId,
475 project_v: impl Fn(brepkit_math::vec::Point3) -> f64,
476) -> Result<(f64, f64), crate::OperationsError> {
477 let face = topo.face(face_id)?;
478 let wire = topo.wire(face.outer_wire())?;
479 let mut v_min = f64::INFINITY;
480 let mut v_max = f64::NEG_INFINITY;
481 for oe in wire.edges() {
482 let edge = topo.edge(oe.edge())?;
483 let pt = topo.vertex(edge.start())?.point();
484 let v = project_v(pt);
485 v_min = v_min.min(v);
486 v_max = v_max.max(v);
487 }
488 if v_min >= v_max {
489 v_min = 0.0;
490 v_max = 1.0;
491 }
492 Ok((v_min, v_max))
493}
494
495fn transform_nurbs_surface(
497 surface: &NurbsSurface,
498 matrix: &Mat4,
499) -> Result<NurbsSurface, crate::OperationsError> {
500 let new_cps: Vec<Vec<_>> = surface
501 .control_points()
502 .iter()
503 .map(|row| row.iter().map(|pt| matrix.mul_point(*pt)).collect())
504 .collect();
505 Ok(NurbsSurface::new(
506 surface.degree_u(),
507 surface.degree_v(),
508 surface.knots_u().to_vec(),
509 surface.knots_v().to_vec(),
510 new_cps,
511 surface.weights().to_vec(),
512 )?)
513}
514
515fn is_uniform_scale(matrix: &Mat4) -> bool {
516 let m = &matrix.0;
517 let sx = (m[0][0] * m[0][0] + m[1][0] * m[1][0] + m[2][0] * m[2][0]).sqrt();
519 let sy = (m[0][1] * m[0][1] + m[1][1] * m[1][1] + m[2][1] * m[2][1]).sqrt();
520 let sz = (m[0][2] * m[0][2] + m[1][2] * m[1][2] + m[2][2] * m[2][2]).sqrt();
521 let avg = (sx + sy + sz) / 3.0;
522 let rel = 0.01; (sx - avg).abs() < avg * rel && (sy - avg).abs() < avg * rel && (sz - avg).abs() < avg * rel
524}
525
526#[allow(clippy::cast_precision_loss)]
530fn sphere_to_transformed_nurbs(
531 sph: &brepkit_math::surfaces::SphericalSurface,
532 matrix: &Mat4,
533 v_min: f64,
534 v_max: f64,
535) -> Result<NurbsSurface, crate::OperationsError> {
536 use std::f64::consts::TAU;
537
538 let n_u = 33; let n_v = 17; let mut rows: Vec<Vec<brepkit_math::vec::Point3>> = Vec::with_capacity(n_v);
542 for iv in 0..n_v {
543 let v = v_min + (v_max - v_min) * (iv as f64) / ((n_v - 1) as f64);
544 let mut row = Vec::with_capacity(n_u);
545 for iu in 0..n_u {
546 let u = TAU * (iu as f64) / ((n_u - 1) as f64);
547 let pt = sph.evaluate(u, v);
548 row.push(matrix.mul_point(pt));
549 }
550 rows.push(row);
551 }
552
553 let nurbs = brepkit_math::nurbs::surface_fitting::interpolate_surface(&rows, 3, 3)?;
554 Ok(nurbs)
555}
556
557fn transform_direction(matrix: &Mat4, dir: Vec3) -> Result<Vec3, crate::OperationsError> {
560 let origin = matrix.mul_point(brepkit_math::vec::Point3::new(0.0, 0.0, 0.0));
561 let tip = matrix.mul_point(brepkit_math::vec::Point3::new(dir.x(), dir.y(), dir.z()));
562 let raw = Vec3::new(
563 tip.x() - origin.x(),
564 tip.y() - origin.y(),
565 tip.z() - origin.z(),
566 );
567 Ok(raw.normalize()?)
568}
569
570#[allow(clippy::too_many_lines)]
574fn transform_edges(
575 topo: &mut Topology,
576 edge_ids: &HashSet<EdgeId>,
577 matrix: &Mat4,
578) -> Result<(), crate::OperationsError> {
579 let origin = matrix.mul_point(brepkit_math::vec::Point3::new(0.0, 0.0, 0.0));
580 let transform_dir = |d: Vec3| -> Vec3 {
581 matrix.mul_point(brepkit_math::vec::Point3::new(d.x(), d.y(), d.z())) - origin
582 };
583 for &eid in edge_ids {
584 let edge = topo.edge(eid)?;
585 let new_curve = match edge.curve() {
586 EdgeCurve::Line => None,
587 EdgeCurve::NurbsCurve(c) => {
588 let new_control_points: Vec<_> = c
589 .control_points()
590 .iter()
591 .map(|pt| matrix.mul_point(*pt))
592 .collect();
593 Some(EdgeCurve::NurbsCurve(NurbsCurve::new(
594 c.degree(),
595 c.knots().to_vec(),
596 new_control_points,
597 c.weights().to_vec(),
598 )?))
599 }
600 EdgeCurve::Circle(c) => {
601 let new_center = matrix.mul_point(c.center());
602 let new_u = transform_dir(c.u_axis());
603 let new_v = transform_dir(c.v_axis());
604 let su = new_u.length();
605 let sv = new_v.length();
606 let new_normal = new_u.cross(new_v).normalize()?;
607 if (su - sv).abs() < 1e-12 * su.max(sv).max(1.0) {
608 Some(EdgeCurve::Circle(
609 brepkit_math::curves::Circle3D::with_axes(
610 new_center,
611 new_normal,
612 c.radius() * su,
613 new_u.normalize()?,
614 new_v.normalize()?,
615 )?,
616 ))
617 } else {
618 let (semi_major, semi_minor, u_dir, v_dir) = if su >= sv {
619 (
620 c.radius() * su,
621 c.radius() * sv,
622 new_u.normalize()?,
623 new_v.normalize()?,
624 )
625 } else {
626 (
627 c.radius() * sv,
628 c.radius() * su,
629 new_v.normalize()?,
630 new_u.normalize()?,
631 )
632 };
633 Some(EdgeCurve::Ellipse(
634 brepkit_math::curves::Ellipse3D::with_axes(
635 new_center, new_normal, semi_major, semi_minor, u_dir, v_dir,
636 )?,
637 ))
638 }
639 }
640 EdgeCurve::Ellipse(e) => {
641 let new_center = matrix.mul_point(e.center());
642 let new_u = transform_dir(e.u_axis());
643 let new_v = transform_dir(e.v_axis());
644 let new_normal = new_u.cross(new_v).normalize()?;
645 Some(EdgeCurve::Ellipse(
646 brepkit_math::curves::Ellipse3D::with_axes(
647 new_center,
648 new_normal,
649 e.semi_major() * new_u.length(),
650 e.semi_minor() * new_v.length(),
651 new_u.normalize()?,
652 new_v.normalize()?,
653 )?,
654 ))
655 }
656 };
657 if let Some(curve) = new_curve {
658 topo.edge_mut(eid)?.set_curve(curve);
659 }
660 }
661 Ok(())
662}
663
664pub fn transform_wire(
671 topo: &mut Topology,
672 wire_id: WireId,
673 matrix: &Mat4,
674) -> Result<(), crate::OperationsError> {
675 let tol = Tolerance::new();
676 if tol.approx_eq(matrix.determinant(), 0.0) {
677 return Err(crate::OperationsError::InvalidInput {
678 reason: "transform matrix is degenerate (zero determinant)".into(),
679 });
680 }
681
682 let (vertex_ids, edge_ids) = collect_wire_entities(topo, wire_id)?;
683
684 for vid in vertex_ids {
686 let vertex = topo.vertex_mut(vid)?;
687 let new_point = matrix.mul_point(vertex.point());
688 vertex.set_point(new_point);
689 }
690
691 transform_edges(topo, &edge_ids, matrix)?;
693
694 Ok(())
695}
696
697#[allow(clippy::too_many_lines)]
707pub fn transform_face(
708 topo: &mut Topology,
709 face_id: FaceId,
710 matrix: &Mat4,
711) -> Result<(), crate::OperationsError> {
712 let tol = Tolerance::new();
713 if tol.approx_eq(matrix.determinant(), 0.0) {
714 return Err(crate::OperationsError::InvalidInput {
715 reason: "transform matrix is degenerate (zero determinant)".into(),
716 });
717 }
718
719 let (vertex_ids, edge_ids) = collect_face_entities(topo, face_id)?;
721
722 for vid in vertex_ids {
724 let vertex = topo.vertex_mut(vid)?;
725 let new_point = matrix.mul_point(vertex.point());
726 vertex.set_point(new_point);
727 }
728
729 transform_edges(topo, &edge_ids, matrix)?;
731
732 let normal_matrix = matrix.inverse()?.transpose();
734 transform_face_surface(topo, face_id, matrix, &normal_matrix)?;
735
736 Ok(())
737}
738
739fn collect_face_entities(
741 topo: &Topology,
742 face_id: FaceId,
743) -> Result<(HashSet<VertexId>, HashSet<EdgeId>), crate::OperationsError> {
744 let mut vertex_ids = HashSet::new();
745 let mut edge_ids = HashSet::new();
746 let face = topo.face(face_id)?;
747 let wire_ids: Vec<_> = std::iter::once(face.outer_wire())
748 .chain(face.inner_wires().iter().copied())
749 .collect();
750
751 for wid in wire_ids {
752 let wire = topo.wire(wid)?;
753 for oe in wire.edges() {
754 let eid = oe.edge();
755 edge_ids.insert(eid);
756 let edge = topo.edge(eid)?;
757 vertex_ids.insert(edge.start());
758 vertex_ids.insert(edge.end());
759 }
760 }
761
762 Ok((vertex_ids, edge_ids))
763}
764
765fn collect_wire_entities(
767 topo: &Topology,
768 wire_id: WireId,
769) -> Result<(HashSet<VertexId>, HashSet<EdgeId>), crate::OperationsError> {
770 let mut vertex_ids = HashSet::new();
771 let mut edge_ids = HashSet::new();
772 let wire = topo.wire(wire_id)?;
773 for oe in wire.edges() {
774 let eid = oe.edge();
775 edge_ids.insert(eid);
776 let edge = topo.edge(eid)?;
777 vertex_ids.insert(edge.start());
778 vertex_ids.insert(edge.end());
779 }
780 Ok((vertex_ids, edge_ids))
781}
782
783#[allow(clippy::type_complexity)]
786fn collect_solid_entities(
787 topo: &Topology,
788 solid: SolidId,
789) -> Result<(HashSet<VertexId>, HashSet<EdgeId>, HashSet<FaceId>), crate::OperationsError> {
790 let mut vertex_ids = HashSet::new();
791 let mut edge_ids = HashSet::new();
792 let mut face_ids = HashSet::new();
793 let solid_data = topo.solid(solid)?;
794 let shell_ids: Vec<_> = std::iter::once(solid_data.outer_shell())
795 .chain(solid_data.inner_shells().iter().copied())
796 .collect();
797
798 for shell_id in shell_ids {
799 let shell = topo.shell(shell_id)?;
800 let fids: Vec<_> = shell.faces().to_vec();
801
802 for face_id in fids {
803 face_ids.insert(face_id);
804 let face = topo.face(face_id)?;
805 let wire_ids: Vec<_> = std::iter::once(face.outer_wire())
806 .chain(face.inner_wires().iter().copied())
807 .collect();
808
809 for wire_id in wire_ids {
810 let wire = topo.wire(wire_id)?;
811 for oe in wire.edges() {
812 let eid = oe.edge();
813 edge_ids.insert(eid);
814 let edge = topo.edge(eid)?;
815 vertex_ids.insert(edge.start());
816 vertex_ids.insert(edge.end());
817 }
818 }
819 }
820 }
821
822 Ok((vertex_ids, edge_ids, face_ids))
823}
824
825#[cfg(test)]
826mod tests;