diff --git a/crates/fj-core/src/algorithms/approx/face.rs b/crates/fj-core/src/algorithms/approx/face.rs index 56f45c752..261c0d7eb 100644 --- a/crates/fj-core/src/algorithms/approx/face.rs +++ b/crates/fj-core/src/algorithms/approx/face.rs @@ -85,11 +85,12 @@ impl Approx for &Face { // would need to provide its own approximation, as the edges that bound // it have nothing to do with its curvature. - let exterior = (self.exterior().deref(), self.surface().deref()) - .approx_with_cache(tolerance, cache); + let exterior = + (self.region().exterior().deref(), self.surface().deref()) + .approx_with_cache(tolerance, cache); let mut interiors = BTreeSet::new(); - for cycle in self.interiors() { + for cycle in self.region().interiors() { let cycle = (cycle.deref(), self.surface().deref()) .approx_with_cache(tolerance, cache); interiors.insert(cycle); @@ -98,7 +99,7 @@ impl Approx for &Face { FaceApprox { exterior, interiors, - color: self.color(), + color: self.region().color(), coord_handedness: self.coord_handedness(), } } diff --git a/crates/fj-core/src/algorithms/bounding_volume/face.rs b/crates/fj-core/src/algorithms/bounding_volume/face.rs index 8926680ac..86cba6a08 100644 --- a/crates/fj-core/src/algorithms/bounding_volume/face.rs +++ b/crates/fj-core/src/algorithms/bounding_volume/face.rs @@ -4,7 +4,7 @@ use crate::{geometry::curve::GlobalPath, objects::Face}; impl super::BoundingVolume<3> for Face { fn aabb(&self) -> Option> { - self.exterior().aabb().map(|aabb2| { + self.region().exterior().aabb().map(|aabb2| { let surface = self.surface().geometry(); match surface.u { diff --git a/crates/fj-core/src/algorithms/intersect/curve_face.rs b/crates/fj-core/src/algorithms/intersect/curve_face.rs index 7cb42d0dc..339670b10 100644 --- a/crates/fj-core/src/algorithms/intersect/curve_face.rs +++ b/crates/fj-core/src/algorithms/intersect/curve_face.rs @@ -29,7 +29,10 @@ impl CurveFaceIntersection { /// Compute the intersection pub fn compute(curve: &Curve, face: &Face) -> Self { - let half_edges = face.all_cycles().flat_map(|cycle| cycle.half_edges()); + let half_edges = face + .region() + .all_cycles() + .flat_map(|cycle| cycle.half_edges()); let mut intersections = Vec::new(); @@ -152,7 +155,7 @@ mod tests { use crate::{ geometry::curve::Curve, objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, }; @@ -181,15 +184,19 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon(exterior_points, &mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon(exterior_points, &mut services) + .insert(&mut services) + }) + .add_interiors([Cycle::polygon( + interior_points, + &mut services, + ) + .insert(&mut services)]) .insert(&mut services) - }) - .add_interiors([Cycle::polygon( - interior_points, - &mut services, - ) - .insert(&mut services)]); + }); let expected = CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]); diff --git a/crates/fj-core/src/algorithms/intersect/face_face.rs b/crates/fj-core/src/algorithms/intersect/face_face.rs index dae7f0d6e..11694d9b9 100644 --- a/crates/fj-core/src/algorithms/intersect/face_face.rs +++ b/crates/fj-core/src/algorithms/intersect/face_face.rs @@ -64,7 +64,7 @@ mod tests { algorithms::intersect::CurveFaceIntersection, geometry::curve::Curve, objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, }; @@ -86,8 +86,13 @@ mod tests { services.objects.surfaces.xz_plane(), ] .map(|surface| { - Face::unbound(surface, &mut services).update_exterior(|_| { - Cycle::polygon(points, &mut services).insert(&mut services) + Face::unbound(surface, &mut services).update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon(points, &mut services) + .insert(&mut services) + }) + .insert(&mut services) }) }); @@ -113,8 +118,13 @@ mod tests { services.objects.surfaces.xz_plane(), ]; let [a, b] = surfaces.clone().map(|surface| { - Face::unbound(surface, &mut services).update_exterior(|_| { - Cycle::polygon(points, &mut services).insert(&mut services) + Face::unbound(surface, &mut services).update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon(points, &mut services) + .insert(&mut services) + }) + .insert(&mut services) }) }); diff --git a/crates/fj-core/src/algorithms/intersect/face_point.rs b/crates/fj-core/src/algorithms/intersect/face_point.rs index 474d0ec00..461b6cf65 100644 --- a/crates/fj-core/src/algorithms/intersect/face_point.rs +++ b/crates/fj-core/src/algorithms/intersect/face_point.rs @@ -22,7 +22,7 @@ impl Intersect for (&Face, &Point<2>) { let mut num_hits = 0; - for cycle in face.all_cycles() { + for cycle in face.region().all_cycles() { // We need to properly detect the ray passing the boundary at the // "seam" of the polygon, i.e. the vertex between the last and the // first segment. The logic in the loop properly takes care of that, @@ -139,7 +139,7 @@ mod tests { use crate::{ algorithms::intersect::{face_point::FacePointIntersection, Intersect}, objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, }; @@ -149,12 +149,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [1., 1.], [0., 2.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [1., 1.], [0., 2.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([2., 1.]); @@ -170,12 +174,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [2., 1.], [0., 2.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [2., 1.], [0., 2.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 1.]); @@ -194,12 +202,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[4., 2.], [0., 4.], [0., 0.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[4., 2.], [0., 4.], [0., 0.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 2.]); @@ -218,12 +230,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [2., 1.], [3., 0.], [3., 4.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [2., 1.], [3., 0.], [3., 4.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 1.]); @@ -242,12 +258,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [2., 1.], [3., 1.], [0., 2.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [2., 1.], [3., 1.], [0., 2.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 1.]); @@ -266,12 +286,22 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [ + [0., 0.], + [2., 1.], + [3., 1.], + [4., 0.], + [4., 5.], + ], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 1.]); @@ -290,18 +320,23 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [2., 0.], [0., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [2., 0.], [0., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); let edge = face + .region() .exterior() .half_edges() .find(|edge| edge.start_position() == Point::from([0., 0.])) @@ -320,18 +355,23 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [1., 0.], [0., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [1., 0.], [0., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); let vertex = face + .region() .exterior() .half_edges() .find(|half_edge| { diff --git a/crates/fj-core/src/algorithms/intersect/ray_face.rs b/crates/fj-core/src/algorithms/intersect/ray_face.rs index d4a8b141d..be3a61283 100644 --- a/crates/fj-core/src/algorithms/intersect/ray_face.rs +++ b/crates/fj-core/src/algorithms/intersect/ray_face.rs @@ -153,7 +153,7 @@ mod tests { transform::TransformObject, }, objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, }; @@ -165,12 +165,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.yz_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([-1., 0., 0.], &mut services); @@ -187,12 +191,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.yz_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([1., 0., 0.], &mut services); @@ -212,12 +220,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.yz_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([0., 0., 2.], &mut services); @@ -234,16 +246,21 @@ mod tests { let face = Face::unbound(services.objects.surfaces.yz_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([1., 1., 0.], &mut services); let edge = face + .region() .exterior() .half_edges() .find(|edge| edge.start_position() == Point::from([-1., 1.])) @@ -264,16 +281,21 @@ mod tests { let face = Face::unbound(services.objects.surfaces.yz_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([1., 1., 1.], &mut services); let vertex = face + .region() .exterior() .half_edges() .find(|half_edge| { @@ -297,12 +319,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); assert_eq!( @@ -321,12 +347,16 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - ) - .insert(&mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], + &mut services, + ) + .insert(&mut services) + }) + .insert(&mut services) }); let face = face.translate([0., 0., 1.], &mut services); diff --git a/crates/fj-core/src/algorithms/reverse/face.rs b/crates/fj-core/src/algorithms/reverse/face.rs index a12043af5..089964680 100644 --- a/crates/fj-core/src/algorithms/reverse/face.rs +++ b/crates/fj-core/src/algorithms/reverse/face.rs @@ -1,18 +1,24 @@ use crate::{ - objects::Face, operations::Insert, services::Services, storage::Handle, + objects::{Face, Region}, + operations::Insert, + services::Services, + storage::Handle, }; use super::Reverse; impl Reverse for Handle { fn reverse(self, services: &mut Services) -> Self { - let exterior = self.exterior().clone().reverse(services); + let exterior = self.region().exterior().clone().reverse(services); let interiors = self + .region() .interiors() .map(|cycle| cycle.clone().reverse(services)) .collect::>(); - Face::new(self.surface().clone(), exterior, interiors, self.color()) - .insert(services) + let region = Region::new(exterior, interiors, self.region().color()) + .insert(services); + + Face::new(self.surface().clone(), region).insert(services) } } diff --git a/crates/fj-core/src/algorithms/sweep/edge.rs b/crates/fj-core/src/algorithms/sweep/edge.rs index 1868d1eb8..5f32adb78 100644 --- a/crates/fj-core/src/algorithms/sweep/edge.rs +++ b/crates/fj-core/src/algorithms/sweep/edge.rs @@ -2,7 +2,7 @@ use fj_interop::{ext::ArrayExt, mesh::Color}; use fj_math::{Point, Scalar, Vector}; use crate::{ - objects::{Cycle, Face, HalfEdge, Surface, Vertex}, + objects::{Cycle, Face, HalfEdge, Region, Surface, Vertex}, operations::{BuildHalfEdge, Insert, UpdateCycle, UpdateHalfEdge}, services::Services, storage::Handle, @@ -22,6 +22,9 @@ impl Sweep for (&HalfEdge, &Handle, &Surface, Option) { let (edge, next_vertex, surface, color) = self; let path = path.into(); + let surface = + (edge.curve(), surface).sweep_with_cache(path, cache, services); + // Next, we need to define the boundaries of the face. Let's start with // the global vertices and edges. let (vertices, global_edges) = { @@ -104,12 +107,10 @@ impl Sweep for (&HalfEdge, &Handle, &Surface, Option) { half_edge }); - let face = Face::new( - (edge.curve(), surface).sweep_with_cache(path, cache, services), - exterior.unwrap().insert(services), - Vec::new(), - color, - ); + let region = Region::new(exterior.unwrap().insert(services), [], color) + .insert(services); + + let face = Face::new(surface, region); // And we're done creating the face! All that's left to do is build our // return values. diff --git a/crates/fj-core/src/algorithms/sweep/face.rs b/crates/fj-core/src/algorithms/sweep/face.rs index 5b59cc6d5..627dfc98e 100644 --- a/crates/fj-core/src/algorithms/sweep/face.rs +++ b/crates/fj-core/src/algorithms/sweep/face.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use crate::{ algorithms::{reverse::Reverse, transform::TransformObject}, geometry::curve::GlobalPath, - objects::{Cycle, Face, Shell}, + objects::{Cycle, Face, Region, Shell}, operations::{BuildCycle, Insert, JoinCycle}, services::Services, storage::Handle, @@ -57,7 +57,8 @@ impl Sweep for Handle { let mut exterior = None; let mut interiors = Vec::new(); - for (i, cycle) in bottom_face.all_cycles().cloned().enumerate() { + for (i, cycle) in bottom_face.region().all_cycles().cloned().enumerate() + { let cycle = cycle.reverse(services); let mut top_edges = Vec::new(); @@ -68,7 +69,7 @@ impl Sweep for Handle { half_edge.deref(), next.start_vertex(), self.surface().deref(), - self.color(), + self.region().color(), ) .sweep_with_cache(path, cache, services); @@ -92,8 +93,11 @@ impl Sweep for Handle { }; } - let top_face = - Face::new(top_surface, exterior.unwrap(), interiors, self.color()); + let region = + Region::new(exterior.unwrap(), interiors, self.region().color()) + .insert(services); + + let top_face = Face::new(top_surface, region); let top_face = top_face.insert(services); faces.push(top_face); diff --git a/crates/fj-core/src/algorithms/transform/face.rs b/crates/fj-core/src/algorithms/transform/face.rs index cd3c5cd81..7c99c97dc 100644 --- a/crates/fj-core/src/algorithms/transform/face.rs +++ b/crates/fj-core/src/algorithms/transform/face.rs @@ -1,7 +1,8 @@ use fj_math::Transform; use crate::{ - objects::{Face, FaceSet}, + objects::{Face, FaceSet, Region}, + operations::Insert, services::Services, }; @@ -15,21 +16,24 @@ impl TransformObject for Face { cache: &mut TransformCache, ) -> Self { // Color does not need to be transformed. - let color = self.color(); + let color = self.region().color(); let surface = self .surface() .clone() .transform_with_cache(transform, services, cache); let exterior = self + .region() .exterior() .clone() .transform_with_cache(transform, services, cache); - let interiors = self.interiors().cloned().map(|interior| { + let interiors = self.region().interiors().cloned().map(|interior| { interior.transform_with_cache(transform, services, cache) }); - Self::new(surface, exterior, interiors, color) + let region = Region::new(exterior, interiors, color).insert(services); + + Self::new(surface, region) } } diff --git a/crates/fj-core/src/algorithms/triangulate/mod.rs b/crates/fj-core/src/algorithms/triangulate/mod.rs index b585ac229..6534b9b2e 100644 --- a/crates/fj-core/src/algorithms/triangulate/mod.rs +++ b/crates/fj-core/src/algorithms/triangulate/mod.rs @@ -80,7 +80,7 @@ mod tests { use crate::{ algorithms::approx::{Approx, Tolerance}, objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, }; @@ -97,8 +97,12 @@ mod tests { let face = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon([a, b, c, d], &mut services) + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon([a, b, c, d], &mut services) + .insert(&mut services) + }) .insert(&mut services) }); services.only_validate(&face); @@ -134,13 +138,21 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = Face::unbound(surface.clone(), &mut services) - .update_exterior(|_| { - Cycle::polygon([a, b, c, d], &mut services) + let face = Face::unbound(surface.clone(), &mut services).update_region( + |region| { + region + .update_exterior(|_| { + Cycle::polygon([a, b, c, d], &mut services) + .insert(&mut services) + }) + .add_interiors([Cycle::polygon( + [e, f, g, h], + &mut services, + ) + .insert(&mut services)]) .insert(&mut services) - }) - .add_interiors([Cycle::polygon([e, f, g, h], &mut services) - .insert(&mut services)]); + }, + ); services.only_validate(&face); let triangles = triangulate(face)?; @@ -196,11 +208,16 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = Face::unbound(surface.clone(), &mut services) - .update_exterior(|_| { - Cycle::polygon([a, b, c, d, e], &mut services) + let face = Face::unbound(surface.clone(), &mut services).update_region( + |region| { + region + .update_exterior(|_| { + Cycle::polygon([a, b, c, d, e], &mut services) + .insert(&mut services) + }) .insert(&mut services) - }); + }, + ); services.only_validate(&face); let triangles = triangulate(face)?; diff --git a/crates/fj-core/src/geometry/mod.rs b/crates/fj-core/src/geometry/mod.rs index 6d34554b1..af0cf08f9 100644 --- a/crates/fj-core/src/geometry/mod.rs +++ b/crates/fj-core/src/geometry/mod.rs @@ -1,5 +1,4 @@ //! Types that are tied to objects, but aren't objects themselves pub mod curve; -pub mod region; pub mod surface; diff --git a/crates/fj-core/src/objects/kinds/face.rs b/crates/fj-core/src/objects/kinds/face.rs index 5b62e7f19..45006d283 100644 --- a/crates/fj-core/src/objects/kinds/face.rs +++ b/crates/fj-core/src/objects/kinds/face.rs @@ -1,11 +1,9 @@ use std::collections::{btree_set, BTreeSet}; -use fj_interop::mesh::Color; use fj_math::Winding; use crate::{ - geometry::region::Region, - objects::{Cycle, Surface}, + objects::{Region, Surface}, storage::Handle, }; @@ -36,21 +34,13 @@ use crate::{ #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Face { surface: Handle, - region: Region, + region: Handle, } impl Face { /// Construct an instance of `Face` - pub fn new( - surface: Handle, - exterior: Handle, - interiors: impl IntoIterator>, - color: Option, - ) -> Self { - Self { - surface, - region: Region::new(exterior, interiors, color), - } + pub fn new(surface: Handle, region: Handle) -> Self { + Self { surface, region } } /// Access the surface of the face @@ -58,26 +48,9 @@ impl Face { &self.surface } - /// Access the cycle that bounds the face on the outside - pub fn exterior(&self) -> &Handle { - self.region.exterior() - } - - /// Access the cycles that bound the face on the inside - /// - /// Each of these cycles defines a hole in the face. - pub fn interiors(&self) -> impl Iterator> + '_ { - self.region.interiors() - } - - /// Access all cycles of the face (both exterior and interior) - pub fn all_cycles(&self) -> impl Iterator> + '_ { - self.region.all_cycles() - } - - /// Access the color of the face - pub fn color(&self) -> Option { - self.region.color() + /// Access the region of the face + pub fn region(&self) -> &Handle { + &self.region } /// Determine handed-ness of the face's front-side coordinate system @@ -91,7 +64,7 @@ impl Face { /// back sides. The front side is the side, where the face's exterior cycle /// is wound counter-clockwise. pub fn coord_handedness(&self) -> Handedness { - match self.exterior().winding() { + match self.region.exterior().winding() { Winding::Ccw => Handedness::RightHanded, Winding::Cw => Handedness::LeftHanded, } diff --git a/crates/fj-core/src/objects/kinds/mod.rs b/crates/fj-core/src/objects/kinds/mod.rs index 2261d4905..cf531b35e 100644 --- a/crates/fj-core/src/objects/kinds/mod.rs +++ b/crates/fj-core/src/objects/kinds/mod.rs @@ -1,6 +1,7 @@ pub mod cycle; pub mod edge; pub mod face; +pub mod region; pub mod shell; pub mod sketch; pub mod solid; diff --git a/crates/fj-core/src/geometry/region.rs b/crates/fj-core/src/objects/kinds/region.rs similarity index 72% rename from crates/fj-core/src/geometry/region.rs rename to crates/fj-core/src/objects/kinds/region.rs index e854408eb..1235c20b7 100644 --- a/crates/fj-core/src/geometry/region.rs +++ b/crates/fj-core/src/objects/kinds/region.rs @@ -1,15 +1,9 @@ //! A single, continues 2d region use fj_interop::mesh::Color; -use crate::{ - objects::{Cycle, Face, Surface}, - operations::Insert, - services::Services, - storage::Handle, -}; +use crate::{objects::Cycle, storage::Handle}; -/// A single, continuous 2d region, may contain holes. Once applied to a -/// [`Surface`] becomes a [`Face`] +/// A single, continuous 2d region, may contain holes /// /// Interior cycles must have the opposite winding of the exterior cycle, /// meaning on the front side of the region, they must appear clockwise. This @@ -59,19 +53,4 @@ impl Region { pub fn color(&self) -> Option { self.color } - - /// Convert the 2D region to a 3D face, by applying it to a surface. - pub fn face( - &self, - surface: Handle, - services: &mut Services, - ) -> Handle { - let face: Face = Face::new( - surface, - self.exterior().clone(), - self.interiors().cloned(), - self.color, - ); - face.insert(services) - } } diff --git a/crates/fj-core/src/objects/kinds/sketch.rs b/crates/fj-core/src/objects/kinds/sketch.rs index 3baebb2a0..7cce6806e 100644 --- a/crates/fj-core/src/objects/kinds/sketch.rs +++ b/crates/fj-core/src/objects/kinds/sketch.rs @@ -1,8 +1,8 @@ use std::collections::BTreeSet; use crate::{ - geometry::region::Region, - objects::{FaceSet, Surface}, + objects::{Face, FaceSet, Region, Surface}, + operations::Insert, services::Services, storage::Handle, }; @@ -10,19 +10,19 @@ use crate::{ /// A 2-dimensional shape #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Sketch { - regions: BTreeSet, + regions: BTreeSet>, } impl Sketch { /// Construct an empty instance of `Sketch` - pub fn new(regions: impl IntoIterator) -> Self { + pub fn new(regions: impl IntoIterator>) -> Self { Self { regions: regions.into_iter().collect(), } } /// Access the regions of the sketch - pub fn regions(&self) -> impl Iterator { + pub fn regions(&self) -> impl Iterator> { self.regions.iter() } @@ -34,7 +34,9 @@ impl Sketch { ) -> FaceSet { self.regions .iter() - .map(|r| r.face(surface.clone(), services)) + .map(|region| { + Face::new(surface.clone(), region.clone()).insert(services) + }) .collect() } } diff --git a/crates/fj-core/src/objects/mod.rs b/crates/fj-core/src/objects/mod.rs index eb85cf1dc..0de859e33 100644 --- a/crates/fj-core/src/objects/mod.rs +++ b/crates/fj-core/src/objects/mod.rs @@ -49,6 +49,7 @@ pub use self::{ cycle::{Cycle, HalfEdgesOfCycle}, edge::{GlobalEdge, HalfEdge}, face::{Face, FaceSet, Handedness}, + region::Region, shell::Shell, sketch::Sketch, solid::Solid, diff --git a/crates/fj-core/src/objects/object.rs b/crates/fj-core/src/objects/object.rs index a53840009..d8ca56152 100644 --- a/crates/fj-core/src/objects/object.rs +++ b/crates/fj-core/src/objects/object.rs @@ -1,7 +1,7 @@ use crate::{ objects::{ - Cycle, Face, GlobalEdge, HalfEdge, Objects, Shell, Sketch, Solid, - Surface, Vertex, + Cycle, Face, GlobalEdge, HalfEdge, Objects, Region, Shell, Sketch, + Solid, Surface, Vertex, }, storage::{Handle, HandleWrapper, ObjectId}, validate::{Validate, ValidationError}, @@ -95,6 +95,7 @@ object!( Face, "face", faces; GlobalEdge, "global edge", global_edges; HalfEdge, "half-edge", half_edges; + Region, "region", regions; Shell, "shell", shells; Sketch, "sketch", sketches; Solid, "solid", solids; diff --git a/crates/fj-core/src/objects/set.rs b/crates/fj-core/src/objects/set.rs index 23ae75202..791ff1d0b 100644 --- a/crates/fj-core/src/objects/set.rs +++ b/crates/fj-core/src/objects/set.rs @@ -71,13 +71,15 @@ impl InsertIntoSet for Face { objects.inner.insert(self.surface().clone().into()); self.surface().insert_into_set(objects); - objects.inner.insert(self.exterior().clone().into()); - self.exterior().insert_into_set(objects); + objects + .inner + .insert(self.region().exterior().clone().into()); + self.region().exterior().insert_into_set(objects); - for interior in self.interiors() { + for interior in self.region().interiors() { objects.inner.insert(interior.clone().into()); } - for interior in self.interiors() { + for interior in self.region().interiors() { interior.insert_into_set(objects); } } diff --git a/crates/fj-core/src/objects/stores.rs b/crates/fj-core/src/objects/stores.rs index b6270e48d..13783e398 100644 --- a/crates/fj-core/src/objects/stores.rs +++ b/crates/fj-core/src/objects/stores.rs @@ -6,7 +6,8 @@ use crate::{ }; use super::{ - Cycle, Face, GlobalEdge, HalfEdge, Shell, Sketch, Solid, Surface, Vertex, + Cycle, Face, GlobalEdge, HalfEdge, Region, Shell, Sketch, Solid, Surface, + Vertex, }; /// The available object stores @@ -24,6 +25,9 @@ pub struct Objects { /// Store for [`HalfEdge`]s pub half_edges: Store, + /// Store for [`Region`]s + pub regions: Store, + /// Store for [`Shell`]s pub shells: Store, diff --git a/crates/fj-core/src/operations/build/face.rs b/crates/fj-core/src/operations/build/face.rs index 4146367a2..91b02bfbc 100644 --- a/crates/fj-core/src/operations/build/face.rs +++ b/crates/fj-core/src/operations/build/face.rs @@ -2,7 +2,7 @@ use fj_interop::ext::ArrayExt; use fj_math::Point; use crate::{ - objects::{Cycle, Face, HalfEdge, Surface, Vertex}, + objects::{Cycle, Face, HalfEdge, Region, Surface, Vertex}, operations::{ BuildCycle, BuildHalfEdge, BuildSurface, Insert, IsInserted, IsInsertedNo, @@ -16,7 +16,8 @@ pub trait BuildFace { /// Build a face with an empty exterior, no interiors, and no color fn unbound(surface: Handle, services: &mut Services) -> Face { let exterior = Cycle::empty().insert(services); - Face::new(surface, exterior, [], None) + let region = Region::new(exterior, [], None).insert(services); + Face::new(surface, region) } /// Build a triangle @@ -44,7 +45,9 @@ pub trait BuildFace { (cycle, half_edges, vertices) }; - let face = Face::new(surface, exterior, [], None); + let region = Region::new(exterior, [], None).insert(services); + + let face = Face::new(surface, region); Polygon { face, diff --git a/crates/fj-core/src/operations/build/shell.rs b/crates/fj-core/src/operations/build/shell.rs index 83aeed52e..a40344324 100644 --- a/crates/fj-core/src/operations/build/shell.rs +++ b/crates/fj-core/src/operations/build/shell.rs @@ -3,8 +3,8 @@ use fj_math::Point; use crate::{ objects::{Face, Shell}, operations::{ - BuildFace, Insert, IsInserted, IsInsertedNo, IsInsertedYes, JoinCycle, - Polygon, UpdateFace, + update::region::UpdateRegion, BuildFace, Insert, IsInserted, + IsInsertedNo, IsInsertedYes, JoinCycle, Polygon, UpdateFace, }, services::Services, }; @@ -36,27 +36,66 @@ pub trait BuildShell { let [a, b, c, d] = points.map(Into::into); let abc = Face::triangle([a, b, c], services); - let bad = - Face::triangle([b, a, d], services).update_exterior(|cycle| { - cycle - .join_to(abc.face.exterior(), 0..=0, 0..=0, services) - .insert(services) - }); - let dac = - Face::triangle([d, a, c], services).update_exterior(|cycle| { - cycle - .join_to(abc.face.exterior(), 1..=1, 2..=2, services) - .join_to(bad.face.exterior(), 0..=0, 1..=1, services) - .insert(services) - }); - let cbd = - Face::triangle([c, b, d], services).update_exterior(|cycle| { - cycle - .join_to(abc.face.exterior(), 0..=0, 1..=1, services) - .join_to(bad.face.exterior(), 1..=1, 2..=2, services) - .join_to(dac.face.exterior(), 2..=2, 2..=2, services) - .insert(services) - }); + let bad = Face::triangle([b, a, d], services).update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .join_to( + abc.face.region().exterior(), + 0..=0, + 0..=0, + services, + ) + .insert(services) + }) + .insert(services) + }); + let dac = Face::triangle([d, a, c], services).update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .join_to( + abc.face.region().exterior(), + 1..=1, + 2..=2, + services, + ) + .join_to( + bad.face.region().exterior(), + 0..=0, + 1..=1, + services, + ) + .insert(services) + }) + .insert(services) + }); + let cbd = Face::triangle([c, b, d], services).update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .join_to( + abc.face.region().exterior(), + 0..=0, + 1..=1, + services, + ) + .join_to( + bad.face.region().exterior(), + 1..=1, + 2..=2, + services, + ) + .join_to( + dac.face.region().exterior(), + 2..=2, + 2..=2, + services, + ) + .insert(services) + }) + .insert(services) + }); let triangles = [abc, bad, dac, cbd].map(|triangle| triangle.insert(services)); diff --git a/crates/fj-core/src/operations/insert.rs b/crates/fj-core/src/operations/insert.rs index 66b6a1b42..4cba47b4f 100644 --- a/crates/fj-core/src/operations/insert.rs +++ b/crates/fj-core/src/operations/insert.rs @@ -1,7 +1,7 @@ use crate::{ objects::{ - Cycle, Face, GlobalEdge, HalfEdge, Shell, Sketch, Solid, Surface, - Vertex, + Cycle, Face, GlobalEdge, HalfEdge, Region, Shell, Sketch, Solid, + Surface, Vertex, }, services::Services, storage::Handle, @@ -46,6 +46,7 @@ impl_insert!( Face, faces; GlobalEdge, global_edges; HalfEdge, half_edges; + Region, regions; Shell, shells; Sketch, sketches; Solid, solids; diff --git a/crates/fj-core/src/operations/mod.rs b/crates/fj-core/src/operations/mod.rs index 5f7bbd90f..4f109a4f4 100644 --- a/crates/fj-core/src/operations/mod.rs +++ b/crates/fj-core/src/operations/mod.rs @@ -19,6 +19,7 @@ pub use self::{ join::cycle::JoinCycle, update::{ cycle::UpdateCycle, edge::UpdateHalfEdge, face::UpdateFace, - shell::UpdateShell, sketch::UpdateSketch, solid::UpdateSolid, + region::UpdateRegion, shell::UpdateShell, sketch::UpdateSketch, + solid::UpdateSolid, }, }; diff --git a/crates/fj-core/src/operations/update/face.rs b/crates/fj-core/src/operations/update/face.rs index 6e41a25ff..8bd5beaff 100644 --- a/crates/fj-core/src/operations/update/face.rs +++ b/crates/fj-core/src/operations/update/face.rs @@ -1,64 +1,39 @@ use std::array; use crate::{ - objects::{Cycle, Face}, + objects::{Face, Region}, operations::Polygon, storage::Handle, }; /// Update a [`Face`] pub trait UpdateFace { - /// Update the exterior of the face - fn update_exterior( + /// Replace the region of the face + fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, - ) -> Self; - - /// Add the provides interiors to the face - fn add_interiors( - &self, - interiors: impl IntoIterator>, + f: impl FnOnce(&Handle) -> Handle, ) -> Self; } impl UpdateFace for Face { - fn update_exterior( + fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, + f: impl FnOnce(&Handle) -> Handle, ) -> Self { - let exterior = f(self.exterior()); - - Face::new( - self.surface().clone(), - exterior, - self.interiors().cloned(), - self.color(), - ) - } - - fn add_interiors( - &self, - interiors: impl IntoIterator>, - ) -> Self { - let interiors = self.interiors().cloned().chain(interiors); - - Face::new( - self.surface().clone(), - self.exterior().clone(), - interiors, - self.color(), - ) + let region = f(self.region()); + Face::new(self.surface().clone(), region) } } impl UpdateFace for Polygon { - fn update_exterior( + fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, + f: impl FnOnce(&Handle) -> Handle, ) -> Self { - let face = self.face.update_exterior(f); + let face = self.face.update_region(f); let edges = array::from_fn(|i| { - face.exterior() + face.region() + .exterior() .nth_half_edge(i) .expect("Operation should not have changed length of cycle") .clone() @@ -67,7 +42,8 @@ impl UpdateFace for Polygon { // The duplicated code here is unfortunate, but unless we get a // stable `array::each_ref` and something like `array::unzip`, I'm // not sure how to avoid it. - face.exterior() + face.region() + .exterior() .nth_half_edge(i) .expect("Operation should not have changed length of cycle") .start_vertex() @@ -80,11 +56,4 @@ impl UpdateFace for Polygon { vertices, } } - - fn add_interiors( - &self, - _: impl IntoIterator>, - ) -> Self { - panic!("Adding interiors to `Polygon` is not supported.") - } } diff --git a/crates/fj-core/src/operations/update/mod.rs b/crates/fj-core/src/operations/update/mod.rs index 8928937cb..26b29348a 100644 --- a/crates/fj-core/src/operations/update/mod.rs +++ b/crates/fj-core/src/operations/update/mod.rs @@ -1,6 +1,7 @@ pub mod cycle; pub mod edge; pub mod face; +pub mod region; pub mod shell; pub mod sketch; pub mod solid; diff --git a/crates/fj-core/src/operations/update/region.rs b/crates/fj-core/src/operations/update/region.rs new file mode 100644 index 000000000..c98d53600 --- /dev/null +++ b/crates/fj-core/src/operations/update/region.rs @@ -0,0 +1,37 @@ +use crate::{ + objects::{Cycle, Region}, + storage::Handle, +}; + +/// Update a [`Region`] +pub trait UpdateRegion { + /// Update the exterior of the region + fn update_exterior( + &self, + f: impl FnOnce(&Handle) -> Handle, + ) -> Self; + + /// Add the provides interiors to the region + fn add_interiors( + &self, + interiors: impl IntoIterator>, + ) -> Self; +} + +impl UpdateRegion for Region { + fn update_exterior( + &self, + f: impl FnOnce(&Handle) -> Handle, + ) -> Self { + let exterior = f(self.exterior()); + Region::new(exterior, self.interiors().cloned(), self.color()) + } + + fn add_interiors( + &self, + interiors: impl IntoIterator>, + ) -> Self { + let interiors = self.interiors().cloned().chain(interiors); + Region::new(self.exterior().clone(), interiors, self.color()) + } +} diff --git a/crates/fj-core/src/operations/update/sketch.rs b/crates/fj-core/src/operations/update/sketch.rs index 42f518cb8..8dd771cee 100644 --- a/crates/fj-core/src/operations/update/sketch.rs +++ b/crates/fj-core/src/operations/update/sketch.rs @@ -1,16 +1,16 @@ use fj_math::{Point, Scalar}; use crate::{ - geometry::region::Region, - objects::{Cycle, Sketch}, + objects::{Cycle, Region, Sketch}, operations::{BuildCycle, Insert}, services::Services, + storage::Handle, }; /// Update a [`Sketch`] pub trait UpdateSketch { /// Add a region to the sketch - fn add_region(&self, region: Region) -> Self; + fn add_region(&self, region: Handle) -> Self; /// Add a circle to the sketch fn add_circle( @@ -29,7 +29,7 @@ pub trait UpdateSketch { } impl UpdateSketch for Sketch { - fn add_region(&self, region: Region) -> Self { + fn add_region(&self, region: Handle) -> Self { Sketch::new(self.regions().cloned().chain([region])) } @@ -40,7 +40,8 @@ impl UpdateSketch for Sketch { services: &mut Services, ) -> Self { let exterior = Cycle::circle(center, radius, services).insert(services); - self.add_region(Region::new(exterior, [], None)) + let region = Region::new(exterior, [], None).insert(services); + self.add_region(region) } fn add_polygon(&self, points: Ps, services: &mut Services) -> Self @@ -50,6 +51,7 @@ impl UpdateSketch for Sketch { Ps::IntoIter: Clone + ExactSizeIterator, { let exterior = Cycle::polygon(points, services).insert(services); - self.add_region(Region::new(exterior, [], None)) + let region = Region::new(exterior, [], None).insert(services); + self.add_region(region) } } diff --git a/crates/fj-core/src/validate/face.rs b/crates/fj-core/src/validate/face.rs index 33b31c0f9..cf4f57303 100644 --- a/crates/fj-core/src/validate/face.rs +++ b/crates/fj-core/src/validate/face.rs @@ -38,15 +38,15 @@ pub enum FaceValidationError { impl FaceValidationError { fn check_interior_winding(face: &Face, errors: &mut Vec) { - if face.exterior().half_edges().count() == 0 { + if face.region().exterior().half_edges().count() == 0 { // Can't determine winding, if the cycle has no half-edges. Sounds // like a job for a different validation check. return; } - let exterior_winding = face.exterior().winding(); + let exterior_winding = face.region().exterior().winding(); - for interior in face.interiors() { + for interior in face.region().interiors() { if interior.half_edges().count() == 0 { // Can't determine winding, if the cycle has no half-edges. // Sounds like a job for a different validation check. @@ -73,8 +73,8 @@ mod tests { use crate::{ algorithms::reverse::Reverse, assert_contains_err, - objects::{Cycle, Face}, - operations::{BuildCycle, BuildFace, Insert, UpdateFace}, + objects::{Cycle, Face, Region}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace, UpdateRegion}, services::Services, validate::{FaceValidationError, Validate, ValidationError}, }; @@ -85,31 +85,38 @@ mod tests { let valid = Face::unbound(services.objects.surfaces.xy_plane(), &mut services) - .update_exterior(|_| { - Cycle::polygon( - [[0., 0.], [3., 0.], [0., 3.]], - &mut services, - ) - .insert(&mut services) - }) - .add_interiors([Cycle::polygon( - [[1., 1.], [1., 2.], [2., 1.]], - &mut services, - ) - .insert(&mut services)]); + .update_region(|region| { + region + .update_exterior(|_| { + Cycle::polygon( + [[0., 0.], [3., 0.], [0., 3.]], + &mut services, + ) + .insert(&mut services) + }) + .add_interiors([Cycle::polygon( + [[1., 1.], [1., 2.], [2., 1.]], + &mut services, + ) + .insert(&mut services)]) + .insert(&mut services) + }); let invalid = { let interiors = valid + .region() .interiors() .cloned() .map(|cycle| cycle.reverse(&mut services)) .collect::>(); - Face::new( - valid.surface().clone(), - valid.exterior().clone(), + let region = Region::new( + valid.region().exterior().clone(), interiors, - valid.color(), + valid.region().color(), ) + .insert(&mut services); + + Face::new(valid.surface().clone(), region) }; valid.validate_and_return_first_error()?; diff --git a/crates/fj-core/src/validate/mod.rs b/crates/fj-core/src/validate/mod.rs index d8530b545..9e93a26dc 100644 --- a/crates/fj-core/src/validate/mod.rs +++ b/crates/fj-core/src/validate/mod.rs @@ -3,6 +3,7 @@ mod cycle; mod edge; mod face; +mod region; mod shell; mod sketch; mod solid; @@ -19,8 +20,8 @@ use std::convert::Infallible; use fj_math::Scalar; -/// Assert that some object has a validation error which matches a specifc pattern. -/// This is preferred to matching on [`Validate::validate_and_return_first_error`], since usually we don't care about the order. +/// Assert that some object has a validation error which matches a specific +/// pattern. This is preferred to matching on [`Validate::validate_and_return_first_error`], since usually we don't care about the order. #[macro_export] macro_rules! assert_contains_err { ($o:tt,$p:pat) => { diff --git a/crates/fj-core/src/validate/region.rs b/crates/fj-core/src/validate/region.rs new file mode 100644 index 000000000..3cd0ae87b --- /dev/null +++ b/crates/fj-core/src/validate/region.rs @@ -0,0 +1,12 @@ +use crate::objects::Region; + +use super::{Validate, ValidationConfig, ValidationError}; + +impl Validate for Region { + fn validate_with_config( + &self, + _: &ValidationConfig, + _: &mut Vec, + ) { + } +} diff --git a/crates/fj-core/src/validate/shell.rs b/crates/fj-core/src/validate/shell.rs index f1a81b4e8..d159272b4 100644 --- a/crates/fj-core/src/validate/shell.rs +++ b/crates/fj-core/src/validate/shell.rs @@ -112,7 +112,8 @@ impl ShellValidationError { .faces() .into_iter() .flat_map(|face| { - face.all_cycles() + face.region() + .all_cycles() .flat_map(|cycle| cycle.half_edges().cloned()) .zip(repeat(face.surface().clone())) }) @@ -173,7 +174,7 @@ impl ShellValidationError { let faces = shell.faces(); let mut half_edge_to_faces: HashMap = HashMap::new(); for face in faces { - for cycle in face.all_cycles() { + for cycle in face.region().all_cycles() { for half_edge in cycle.half_edges() { let id = half_edge.global_form().id(); let entry = half_edge_to_faces.entry(id); @@ -196,7 +197,7 @@ mod tests { objects::{GlobalEdge, Shell}, operations::{ BuildShell, Insert, UpdateCycle, UpdateFace, UpdateHalfEdge, - UpdateShell, + UpdateRegion, UpdateShell, }, services::Services, validate::{shell::ShellValidationError, Validate, ValidationError}, @@ -215,13 +216,17 @@ mod tests { valid .abc .face - .update_exterior(|cycle| { - cycle - .update_nth_half_edge(0, |half_edge| { - let global_form = - GlobalEdge::new().insert(&mut services); - half_edge - .replace_global_form(global_form) + .update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .update_nth_half_edge(0, |half_edge| { + let global_form = + GlobalEdge::new().insert(&mut services); + half_edge + .replace_global_form(global_form) + .insert(&mut services) + }) .insert(&mut services) }) .insert(&mut services) diff --git a/crates/fj-core/src/validate/solid.rs b/crates/fj-core/src/validate/solid.rs index 9dff3630f..5c0cdbfe6 100644 --- a/crates/fj-core/src/validate/solid.rs +++ b/crates/fj-core/src/validate/solid.rs @@ -72,7 +72,8 @@ impl SolidValidationError { .shells() .flat_map(|s| s.faces()) .flat_map(|face| { - face.all_cycles() + face.region() + .all_cycles() .flat_map(|cycle| cycle.half_edges().cloned()) .zip(repeat(face.surface().geometry())) })