use super::*; use std::f64::consts::PI; use truck_topology::Shell; #[test] fn test_split_closed_edges() { let vertices = vec![Point2::new(1.0, 0.0)]; let curve = TrimmedCurve::new(UnitCircle::::new(), (0.0, 2.0 * PI)); let edges = vec![CompressedEdge { vertices: (0, 0), curve, }]; let faces = vec![ CompressedFace { surface: (), orientation: true, boundaries: vec![vec![CompressedEdgeIndex { index: 0, orientation: true, }]], }, CompressedFace { surface: (), orientation: false, boundaries: vec![vec![CompressedEdgeIndex { index: 0, orientation: false, }]], }, ]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_edges(&mut shell); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { vertices, edges, faces, } = &shell; assert_eq!(vertices.len(), 2); assert_near!(vertices[0], Point2::new(1.0, 0.0)); assert_near!(vertices[1], Point2::new(-1.0, 0.0)); assert_eq!(edges.len(), 2); assert_eq!(edges[0].vertices, (0, 1)); assert_eq!(edges[1].vertices, (1, 0)); assert_eq!( *faces, vec![ CompressedFace { surface: (), orientation: true, boundaries: vec![vec![ CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: true, } ]], }, CompressedFace { surface: (), orientation: false, boundaries: vec![vec![ CompressedEdgeIndex { index: 1, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: false, } ]], }, ] ); } #[test] fn test_split_closed_face_simple_cylinder_case() { type Surface = RevolutedCurve>; #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Line(Line), Arc(TrimmedCurve, Matrix4>>), #[allow(clippy::enum_variant_names)] PCurve(PCurve, Surface>), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { Self::PCurve(value) } } let vertices = vec![ Point3::new(1.0, 0.0, 0.0), Point3::new(-1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 1.0), ]; let translate = Matrix4::from_translation(Vector3::unit_z()); let edges = vec![ CompressedEdge { vertices: (0, 1), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (0.0, PI), )), }, CompressedEdge { vertices: (1, 0), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (PI, 2.0 * PI), )), }, CompressedEdge { vertices: (0, 2), curve: Curve::Line(Line(vertices[0], vertices[2])), }, CompressedEdge { vertices: (2, 3), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (0.0, PI), )), }, CompressedEdge { vertices: (3, 2), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (PI, 2.0 * PI), )), }, ]; let surface = RevolutedCurve::by_revolution( Line(vertices[2], vertices[0]), Point3::origin(), Vector3::unit_z(), ); let faces = vec![Face { surface, boundaries: vec![vec![ CompressedEdgeIndex { index: 1, orientation: true, }, CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, ]], orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_faces(&mut shell, 0.01, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), 4); assert_eq!(edges.len(), 6); assert_eq!(edges[5].vertices, (3, 1)); let curve0 = &edges[5].curve; let curve1 = Line(Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 0.0)); for i in 0..=10 { let t = i as f64 / 10.0; assert_near!(curve0.subs(t), curve1.subs(t)); } assert_eq!(faces.len(), 2); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 2, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } let i = faces[0].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 2, orientation: true, } }) .unwrap(); faces[0].boundaries[0].rotate_left(i); let i = faces[1].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 3, orientation: false, } }) .unwrap(); faces[1].boundaries[0].rotate_left(i); assert_eq!( *faces, vec![ Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 5, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: true, } ]], surface, orientation: true, }, Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 5, orientation: false, }, ]], surface, orientation: true, }, ] ); } #[test] fn test_split_closed_face_cylinder_with_hole() { type Surface = RevolutedCurve>; #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum ParameterCurve { Line(Line), Arc(TrimmedCurve, Matrix3>>), } #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Line(Line), Arc(TrimmedCurve, Matrix4>>), PCurve(PCurve), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { let (line, surface) = value.decompose(); Self::PCurve(PCurve::new(ParameterCurve::Line(line), surface)) } } let vertices = vec![ Point3::new(1.0, 0.0, 0.0), Point3::new(-1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 0.25), Point3::new(-1.0, 0.0, 0.75), ]; let surface = RevolutedCurve::by_revolution( Line(vertices[2], vertices[0]), Point3::origin(), Vector3::unit_z(), ); let translate = Matrix4::from_translation(Vector3::unit_z()); let transform = Matrix3::from_translation(Vector2::new(0.5, PI)) * Matrix3::from_scale(0.25); let edges = vec![ CompressedEdge { vertices: (0, 1), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (0.0, PI), )), }, CompressedEdge { vertices: (1, 0), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (PI, 2.0 * PI), )), }, CompressedEdge { vertices: (0, 2), curve: Curve::Line(Line(vertices[0], vertices[2])), }, CompressedEdge { vertices: (2, 3), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (0.0, PI), )), }, CompressedEdge { vertices: (3, 2), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (PI, 2.0 * PI), )), }, CompressedEdge { vertices: (4, 5), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(transform), (0.0, PI), )), surface, )), }, CompressedEdge { vertices: (5, 4), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(transform), (PI, 2.0 * PI), )), surface, )), }, ]; let faces = vec![Face { surface, boundaries: vec![ vec![ CompressedEdgeIndex { index: 1, orientation: true, }, CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, ], vec![ CompressedEdgeIndex { index: 6, orientation: false, }, CompressedEdgeIndex { index: 5, orientation: false, }, ], ], orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_faces(&mut shell, 0.01, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), 6); assert_eq!(edges.len(), 9); assert_eq!(edges[7].vertices, (3, 5)); let curve0 = &edges[7].curve; let curve1 = Line(Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 0.75)); for i in 0..=10 { let t = i as f64 / 10.0; assert_near!(curve0.subs(t), curve1.subs(t)); } assert_eq!(edges[8].vertices, (4, 1)); let curve0 = &edges[8].curve; let curve1 = Line(Point3::new(-1.0, 0.0, 0.25), Point3::new(-1.0, 0.0, 0.0)); for i in 0..=10 { let t = i as f64 / 10.0; assert_near!(curve0.subs(t), curve1.subs(t)); } assert_eq!(faces.len(), 2); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 2, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } let i = faces[0].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 2, orientation: true, } }) .unwrap(); faces[0].boundaries[0].rotate_left(i); let i = faces[1].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 3, orientation: false, } }) .unwrap(); faces[1].boundaries[0].rotate_left(i); assert_eq!( *faces, vec![ Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 7, orientation: true, }, CompressedEdgeIndex { index: 5, orientation: false, }, CompressedEdgeIndex { index: 8, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: true, } ]], surface, orientation: true, }, Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 8, orientation: false, }, CompressedEdgeIndex { index: 6, orientation: false, }, CompressedEdgeIndex { index: 7, orientation: false, }, ]], surface, orientation: true, }, ] ); } #[test] fn test_split_closed_face_cylinder_with_rotated_hole() { #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum ParameterCurve { Line(Line), Arc(TrimmedCurve, Matrix3>>), } #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Line(Line), Arc(TrimmedCurve, Matrix4>>), PCurve(PCurve), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { let (line, surface) = value.decompose(); Self::PCurve(PCurve::new(ParameterCurve::Line(line), surface)) } } type Surface = RevolutedCurve>; let surface = RevolutedCurve::by_revolution( Line(Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 0.0, 0.0)), Point3::origin(), Vector3::unit_z(), ); let vertices = vec![ Point3::new(1.0, 0.0, 0.0), Point3::new(-1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 1.0), surface.subs(0.5, PI + 0.25), surface.subs(0.5, PI - 0.25), ]; let translate = Matrix4::from_translation(Vector3::unit_z()); let transform = Matrix3::from_translation(Vector2::new(0.5, PI)) * Matrix3::from_scale(0.25); let edges = vec![ CompressedEdge { vertices: (0, 1), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (0.0, PI), )), }, CompressedEdge { vertices: (1, 0), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (PI, 2.0 * PI), )), }, CompressedEdge { vertices: (0, 2), curve: Curve::Line(Line(vertices[0], vertices[2])), }, CompressedEdge { vertices: (2, 3), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (0.0, PI), )), }, CompressedEdge { vertices: (3, 2), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translate), (PI, 2.0 * PI), )), }, CompressedEdge { vertices: (4, 5), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(transform), (0.5 * PI, 1.5 * PI), )), surface, )), }, CompressedEdge { vertices: (5, 4), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(transform), (1.5 * PI, 2.5 * PI), )), surface, )), }, ]; let faces = vec![Face { surface, boundaries: vec![ vec![ CompressedEdgeIndex { index: 1, orientation: true, }, CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, ], vec![ CompressedEdgeIndex { index: 6, orientation: false, }, CompressedEdgeIndex { index: 5, orientation: false, }, ], ], orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_faces(&mut shell, 0.01, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), 8); assert_eq!(edges.len(), 11); assert_eq!(edges[5].vertices, (4, 7)); assert_eq!(edges[6].vertices, (5, 6)); assert_eq!(edges[7].vertices, (6, 4)); assert_eq!(edges[8].vertices, (7, 5)); assert_eq!(edges[9].vertices, (3, 7)); let curve0 = &edges[9].curve; let curve1 = Line(Point3::new(-1.0, 0.0, 1.0), Point3::new(-1.0, 0.0, 0.75)); for i in 0..=10 { let t = i as f64 / 10.0; assert_near!(curve0.subs(t), curve1.subs(t)); } assert_eq!(edges[10].vertices, (6, 1)); let curve0 = &edges[10].curve; let curve1 = Line(Point3::new(-1.0, 0.0, 0.25), Point3::new(-1.0, 0.0, 0.0)); for i in 0..=10 { let t = i as f64 / 10.0; assert_near!(curve0.subs(t), curve1.subs(t)); } assert_eq!(faces.len(), 2); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 2, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } let i = faces[0].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 2, orientation: true, } }) .unwrap(); faces[0].boundaries[0].rotate_left(i); let i = faces[1].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 3, orientation: false, } }) .unwrap(); faces[1].boundaries[0].rotate_left(i); assert_eq!( *faces, vec![ Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: false, }, CompressedEdgeIndex { index: 9, orientation: true, }, CompressedEdgeIndex { index: 5, orientation: false, }, CompressedEdgeIndex { index: 7, orientation: false, }, CompressedEdgeIndex { index: 10, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: true, } ]], surface, orientation: true, }, Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 10, orientation: false, }, CompressedEdgeIndex { index: 6, orientation: false, }, CompressedEdgeIndex { index: 8, orientation: false, }, CompressedEdgeIndex { index: 9, orientation: false, }, ]], surface, orientation: true, }, ] ); } #[test] fn too_simple_cylinder() { #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Arc(TrimmedCurve, Matrix4>>), PCurve(PCurve, Surface>), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { Curve::PCurve(value) } } type Surface = RevolutedCurve>; let vertices = vec![Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0)]; let translation = Matrix4::from_translation(Vector3::unit_z()); let circle0 = TrimmedCurve::new(Processor::new(UnitCircle::new()), (0.0, 2.0 * PI)); let circle1 = TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed(translation), (0.0, 2.0 * PI), ); let edges = vec![ CompressedEdge { vertices: (0, 0), curve: Curve::Arc(circle0), }, CompressedEdge { vertices: (1, 1), curve: Curve::Arc(circle1), }, ]; let surface = RevolutedCurve::by_revolution( Line(vertices[1], vertices[0]), Point3::origin(), Vector3::unit_z(), ); let faces = vec![CompressedFace { boundaries: vec![ vec![CompressedEdgeIndex { index: 0, orientation: true, }], vec![CompressedEdgeIndex { index: 1, orientation: false, }], ], surface, orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_edges(&mut shell); split_closed_faces(&mut shell, 0.01, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), 4); assert_near!(vertices[2], Point3::new(-1.0, 0.0, 0.0)); assert_near!(vertices[3], Point3::new(-1.0, 0.0, 1.0)); assert_eq!(edges.len(), 6); assert_eq!(edges[0].vertices, (0, 2)); assert_eq!(edges[1].vertices, (1, 3)); assert_eq!(edges[2].vertices, (2, 0)); assert_eq!(edges[3].vertices, (3, 1)); assert_eq!(edges[4].vertices, (0, 1)); assert_eq!(edges[5].vertices, (2, 3)); assert_eq!(faces.len(), 2); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 0, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } let i = faces[0].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 0, orientation: true, } }) .unwrap(); faces[0].boundaries[0].rotate_left(i); let i = faces[1].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 2, orientation: true, } }) .unwrap(); faces[1].boundaries[0].rotate_left(i); assert_eq!( shell.faces, vec![ Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 5, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: false, }, CompressedEdgeIndex { index: 4, orientation: false, }, ]], surface, orientation: true, }, Face { boundaries: vec![vec![ CompressedEdgeIndex { index: 2, orientation: true, }, CompressedEdgeIndex { index: 4, orientation: true, }, CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 5, orientation: false, }, ]], surface, orientation: true, }, ] ); } #[test] fn double_closed_boundary_cylinder() { #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum ParameterCurve { Line(Line), Arc(TrimmedCurve, Matrix3>>), } #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Line(Line), Arc(TrimmedCurve, Matrix4>>), #[allow(clippy::enum_variant_names)] PCurve(PCurve), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { let (line, surface) = value.decompose(); Self::PCurve(PCurve::new(ParameterCurve::Line(line), surface)) } } type Surface = RevolutedCurve>; let vertices = vec![ Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0), Point3::new(1.0, 0.0, 0.25), Point3::new(-1.0, 0.0, 0.25), ]; let surface = RevolutedCurve::by_revolution( Line(vertices[1], vertices[0]), Point3::origin(), Vector3::unit_z(), ); let edges = vec![ CompressedEdge { vertices: (0, 0), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (0.0, 2.0 * PI), )), }, CompressedEdge { vertices: (1, 1), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()) .transformed(Matrix4::from_translation(Vector3::unit_z())), (0.0, 2.0 * PI), )), }, CompressedEdge { vertices: (2, 2), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed( Matrix3::from_translation(Vector2::new(0.5, 0.0)) * Matrix3::from_scale(0.25), ), (0.0, 2.0 * PI), )), surface, )), }, CompressedEdge { vertices: (3, 3), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed( Matrix3::from_translation(Vector2::new(0.5, PI)) * Matrix3::from_scale(0.25), ), (0.0, 2.0 * PI), )), surface, )), }, ]; let faces = vec![CompressedFace { boundaries: vec![ vec![CompressedEdgeIndex { index: 0, orientation: true, }], vec![CompressedEdgeIndex { index: 1, orientation: false, }], vec![CompressedEdgeIndex { index: 2, orientation: false, }], vec![CompressedEdgeIndex { index: 3, orientation: false, }], ], surface, orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_edges(&mut shell); split_closed_faces(&mut shell, 0.05, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), 8); assert_near!(vertices[0], Point3::new(1.0, 0.0, 0.0)); assert_near!(vertices[1], Point3::new(1.0, 0.0, 1.0)); assert_near!(vertices[2], Point3::new(1.0, 0.0, 0.25)); assert_near!(vertices[3], Point3::new(-1.0, 0.0, 0.25)); assert_near!(vertices[4], Point3::new(-1.0, 0.0, 0.0)); assert_near!(vertices[5], Point3::new(-1.0, 0.0, 1.0)); assert_near!(vertices[6], Point3::new(1.0, 0.0, 0.75)); assert_near!(vertices[7], Point3::new(-1.0, 0.0, 0.75)); assert_eq!(edges.len(), 12); assert_eq!(edges[0].vertices, (0, 4)); assert_eq!(edges[1].vertices, (1, 5)); assert_eq!(edges[2].vertices, (2, 6)); assert_eq!(edges[3].vertices, (3, 7)); assert_eq!(edges[4].vertices, (4, 0)); assert_eq!(edges[5].vertices, (5, 1)); assert_eq!(edges[6].vertices, (6, 2)); assert_eq!(edges[7].vertices, (7, 3)); assert_eq!(edges[8].vertices, (0, 2)); assert_eq!(edges[9].vertices, (6, 1)); assert_eq!(edges[10].vertices, (4, 3)); assert_eq!(edges[11].vertices, (7, 5)); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 0, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } let i = faces[0].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 0, orientation: true, } }) .unwrap(); faces[0].boundaries[0].rotate_left(i); let i = faces[1].boundaries[0] .iter() .position(|edge_index| { *edge_index == CompressedEdgeIndex { index: 4, orientation: true, } }) .unwrap(); faces[1].boundaries[0].rotate_left(i); assert_eq!( *faces, vec![ CompressedFace { boundaries: vec![vec![ CompressedEdgeIndex { index: 0, orientation: true, }, CompressedEdgeIndex { index: 10, orientation: true, }, CompressedEdgeIndex { index: 7, orientation: false, }, CompressedEdgeIndex { index: 11, orientation: true, }, CompressedEdgeIndex { index: 1, orientation: false, }, CompressedEdgeIndex { index: 9, orientation: false, }, CompressedEdgeIndex { index: 2, orientation: false, }, CompressedEdgeIndex { index: 8, orientation: false, }, ]], surface, orientation: true, }, CompressedFace { boundaries: vec![vec![ CompressedEdgeIndex { index: 4, orientation: true, }, CompressedEdgeIndex { index: 8, orientation: true, }, CompressedEdgeIndex { index: 6, orientation: false, }, CompressedEdgeIndex { index: 9, orientation: true, }, CompressedEdgeIndex { index: 5, orientation: false, }, CompressedEdgeIndex { index: 11, orientation: false, }, CompressedEdgeIndex { index: 3, orientation: false, }, CompressedEdgeIndex { index: 10, orientation: false, }, ]], surface, orientation: true, }, ] ); } #[test] fn many_closed_boundary_cylinder() { #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum ParameterCurve { Line(Line), Arc(TrimmedCurve, Matrix3>>), } #[derive( Clone, Debug, ParametricCurve, BoundedCurve, ParameterDivision1D, Cut, SearchNearestParameterD1, )] enum Curve { Line(Line), Arc(TrimmedCurve, Matrix4>>), PCurve(PCurve), } impl From, Surface>> for Curve { fn from(value: PCurve, Surface>) -> Self { let (line, surface) = value.decompose(); Self::PCurve(PCurve::new(ParameterCurve::Line(line), surface)) } } type Surface = RevolutedCurve>; const NUM_OF_CIRCLES: usize = 10; let vertices = { let mut vertices = vec![Point3::new(1.0, 0.0, 0.0), Point3::new(1.0, 0.0, 1.0)]; vertices.extend((0..NUM_OF_CIRCLES).map(|i| { let t = 2.0 * PI * i as f64 / NUM_OF_CIRCLES as f64; Point3::new(f64::cos(t), f64::sin(t), 0.4) })); vertices }; let surface = RevolutedCurve::by_revolution( Line(vertices[1], vertices[0]), Point3::origin(), Vector3::unit_z(), ); let edges = { let mut edges = vec![ CompressedEdge { vertices: (0, 0), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()), (0.0, 2.0 * PI), )), }, CompressedEdge { vertices: (1, 1), curve: Curve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()) .transformed(Matrix4::from_translation(Vector3::unit_z())), (0.0, 2.0 * PI), )), }, ]; edges.extend((0..NUM_OF_CIRCLES).map(|i| { let t = 2.0 * PI * i as f64 / NUM_OF_CIRCLES as f64; CompressedEdge { vertices: (2 + i, 2 + i), curve: Curve::PCurve(PCurve::new( ParameterCurve::Arc(TrimmedCurve::new( Processor::new(UnitCircle::new()).transformed( Matrix3::from_translation(Vector2::new(0.5, t)) * Matrix3::from_scale(0.1), ), (0.0, 2.0 * PI), )), surface, )), } })); edges }; let mut boundaries = vec![vec![CompressedEdgeIndex { index: 0, orientation: true, }]]; boundaries.extend((0..=NUM_OF_CIRCLES).map(|i| { vec![CompressedEdgeIndex { index: 1 + i, orientation: false, }] })); let faces = vec![CompressedFace { boundaries, surface, orientation: true, }]; let mut shell = CompressedShell { vertices, edges, faces, }; assert!(Shell::extract(shell.clone()).is_err()); split_closed_edges(&mut shell); split_closed_faces(&mut shell, 0.05, sp); assert!(Shell::extract(shell.clone()).is_ok()); let CompressedShell { ref vertices, ref edges, ref mut faces, } = shell; assert_eq!(vertices.len(), (2 + NUM_OF_CIRCLES) * 2); assert_near!(vertices[0], Point3::new(1.0, 0.0, 0.0)); assert_near!(vertices[1], Point3::new(1.0, 0.0, 1.0)); assert_near!(vertices[NUM_OF_CIRCLES + 2], Point3::new(-1.0, 0.0, 0.0)); assert_near!(vertices[NUM_OF_CIRCLES + 3], Point3::new(-1.0, 0.0, 1.0)); (0..NUM_OF_CIRCLES).for_each(|i| { let t = 2.0 * PI * i as f64 / NUM_OF_CIRCLES as f64; assert_near!(vertices[2 + i], Point3::new(f64::cos(t), f64::sin(t), 0.4)); assert_near!( vertices[4 + i + NUM_OF_CIRCLES], Point3::new(f64::cos(t), f64::sin(t), 0.6) ); }); assert_eq!(edges.len(), 8 + NUM_OF_CIRCLES * 2); (0..NUM_OF_CIRCLES + 2).for_each(|i| { let j = i + 2 + NUM_OF_CIRCLES; assert_eq!(edges[i].vertices, (i, j)); assert_eq!(edges[j].vertices, (j, i)); }); let i = 4 + NUM_OF_CIRCLES * 2; assert_eq!(edges[i].vertices, (0, 2)); assert_eq!(edges[i + 1].vertices, (4 + NUM_OF_CIRCLES, 1)); assert_eq!( edges[i + 2].vertices, (2 + NUM_OF_CIRCLES, 2 + NUM_OF_CIRCLES / 2) ); assert_eq!( edges[i + 3].vertices, (4 + NUM_OF_CIRCLES * 3 / 2, 3 + NUM_OF_CIRCLES) ); let i = faces .iter_mut() .position(|face| { face.boundaries[0].contains(&CompressedEdgeIndex { index: 0, orientation: true, }) }) .unwrap(); if i == 1 { faces.swap(0, 1); } assert_eq!(faces[0].boundaries.len(), NUM_OF_CIRCLES / 2); assert_eq!(faces[1].boundaries.len(), NUM_OF_CIRCLES / 2); (1..NUM_OF_CIRCLES / 2).for_each(|i| { (0..=1).for_each(|fid| { let eid = faces[fid].boundaries[i][0].index; let p = vertices[edges[eid].vertices.0]; assert!(f64::signum(p.y) == f64::signum(f64::powi(-1.0, fid as i32))); }); }); } fn sp(surface: &S, p: Point3, hint: Option<(f64, f64)>) -> Option<(f64, f64)> where S: SearchParameter { surface.search_parameter(p, hint, 10) } #[test] #[cfg(feature = "step-test")] fn step_import() { use truck_stepio::r#in::*; const STEP_DIRECTORY: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../resources/step/"); const STEP_FILES: &[&str] = &[ "occt-cylinder.step", "occt-cone.step", "abc-0006.step", "abc-0008.step", "abc-0035.step", ]; STEP_FILES.iter().for_each(|file_name| { println!("{file_name}"); let path = [STEP_DIRECTORY, file_name].concat(); let step_string = std::fs::read_to_string(path).unwrap(); let table = Table::from_step(&step_string).unwrap(); table.shell.values().cloned().for_each(|step_shell| { let mut cshell = table.to_compressed_shell(&step_shell).unwrap(); cshell.robust_split_closed_edges_and_faces(0.05); truck_topology::Shell::extract(cshell).unwrap(); }); }); }