diff --git a/crates/fj-core/src/operations/holes.rs b/crates/fj-core/src/operations/holes.rs index b9f9ada6e..69bb01407 100644 --- a/crates/fj-core/src/operations/holes.rs +++ b/crates/fj-core/src/operations/holes.rs @@ -26,6 +26,14 @@ pub trait AddHole { path: impl Into>, services: &mut Services, ) -> Self; + + /// Add a through hole between the provided locations + fn add_through_hole( + &self, + locations: [HoleLocation; 2], + radius: impl Into, + services: &mut Services, + ) -> Self; } impl AddHole for Shell { @@ -36,12 +44,12 @@ impl AddHole for Shell { path: impl Into>, services: &mut Services, ) -> Self { - let half_edge = HalfEdge::circle(location.position, radius, services) + let entry = HalfEdge::circle(location.position, radius, services) .insert(services); let hole = Region::empty(services) .update_exterior(|_| { Cycle::empty() - .add_half_edges([half_edge.clone()]) + .add_half_edges([entry.clone()]) .insert(services) }) .sweep_region( @@ -59,11 +67,88 @@ impl AddHole for Shell { region .add_interiors([Cycle::empty() .add_joined_edges( - [( - half_edge.clone(), - half_edge.path(), - half_edge.boundary(), - )], + [(entry.clone(), entry.path(), entry.boundary())], + services, + ) + .insert(services)]) + .insert(services) + }) + .insert(services) + }) + .add_faces(hole) + } + + fn add_through_hole( + &self, + [entry_location, exit_location]: [HoleLocation; 2], + radius: impl Into, + services: &mut Services, + ) -> Self { + let radius = radius.into(); + + let entry = HalfEdge::circle(entry_location.position, radius, services) + .insert(services); + + let path = { + let point = |location: &HoleLocation| { + location + .face + .surface() + .geometry() + .point_from_surface_coords(location.position) + }; + + let entry_point = point(&entry_location); + let exit_point = point(&exit_location); + + exit_point - entry_point + }; + + let swept_region = Region::empty(services) + .update_exterior(|_| { + Cycle::empty() + .add_half_edges([entry.clone()]) + .insert(services) + }) + .sweep_region( + entry_location.face.surface(), + path, + &mut SweepCache::default(), + services, + ); + + let hole = swept_region + .side_faces + .into_iter() + .map(|face| face.insert(services)) + .collect::>(); + + let exit = swept_region + .top_face + .region() + .exterior() + .half_edges() + .only(); + + self.update_face(entry_location.face, |face| { + face.update_region(|region| { + region + .add_interiors([Cycle::empty() + .add_joined_edges( + [(entry.clone(), entry.path(), entry.boundary())], + services, + ) + .insert(services)]) + .insert(services) + }) + .insert(services) + }) + .update_face(exit_location.face, |face| { + face.update_region(|region| { + region + .add_interiors([Cycle::empty() + .add_joined_edges( + [(exit.clone(), exit.path(), exit.boundary())], services, ) .insert(services)]) diff --git a/models/holes/src/lib.rs b/models/holes/src/lib.rs index 2ab641a71..b090a0d7b 100644 --- a/models/holes/src/lib.rs +++ b/models/holes/src/lib.rs @@ -19,21 +19,43 @@ pub fn model( let radius = radius.into(); let size = radius * 4.; - let cuboid = cuboid::model([size, size, size], services); + let cuboid = cuboid::model([size * 2., size, size], services); cuboid .update_shell(cuboid.shells().first(), |shell| { let bottom_face = shell.faces().first(); + let offset = size / 2.; let depth = size / 2.; + let shell = shell.add_blind_hole( + HoleLocation { + face: bottom_face, + position: [-offset, Scalar::ZERO].into(), + }, + radius, + [Scalar::ZERO, Scalar::ZERO, depth], + services, + ); + + let bottom_face = shell.faces().first(); + let top_face = shell + .faces() + .nth(5) + .expect("Expected shell to have top face"); + shell - .add_blind_hole( - HoleLocation { - face: bottom_face, - position: [0., 0.].into(), - }, + .add_through_hole( + [ + HoleLocation { + face: bottom_face, + position: [offset, Scalar::ZERO].into(), + }, + HoleLocation { + face: top_face, + position: [offset, Scalar::ZERO].into(), + }, + ], radius, - [Scalar::ZERO, Scalar::ZERO, depth], services, ) .insert(services)