diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs index 856f76b29..7cb42d0dc 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs @@ -150,8 +150,9 @@ where #[cfg(test)] mod tests { use crate::{ - builder::{CycleBuilder, FaceBuilder}, geometry::curve::Curve, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, }; @@ -178,20 +179,23 @@ mod tests { [ 1., -1.], ]; - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - exterior_points, - &mut services, - )) - .with_interior(CycleBuilder::polygon( - interior_points, - &mut services, - )) - .build(&mut services); + let face = + Face::unbound(services.objects.surfaces.xy_plane(), &mut services) + .update_exterior(|_| { + Cycle::polygon(exterior_points, &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.]]]); assert_eq!(CurveFaceIntersection::compute(&curve, &face), expected); + + services.only_validate(face); } #[test] diff --git a/crates/fj-kernel/src/algorithms/intersect/face_face.rs b/crates/fj-kernel/src/algorithms/intersect/face_face.rs index 931af8fc8..dae7f0d6e 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_face.rs @@ -62,8 +62,9 @@ mod tests { use crate::{ algorithms::intersect::CurveFaceIntersection, - builder::{CycleBuilder, FaceBuilder}, geometry::curve::Curve, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, }; @@ -85,14 +86,15 @@ mod tests { services.objects.surfaces.xz_plane(), ] .map(|surface| { - FaceBuilder::new(surface) - .with_exterior(CycleBuilder::polygon(points, &mut services)) - .build(&mut services) + Face::unbound(surface, &mut services).update_exterior(|_| { + Cycle::polygon(points, &mut services).insert(&mut services) + }) }); let intersection = FaceFaceIntersection::compute([&a, &b]); - assert!(intersection.is_none()); + + services.only_validate([a, b]); } #[test] @@ -111,9 +113,9 @@ mod tests { services.objects.surfaces.xz_plane(), ]; let [a, b] = surfaces.clone().map(|surface| { - FaceBuilder::new(surface) - .with_exterior(CycleBuilder::polygon(points, &mut services)) - .build(&mut services) + Face::unbound(surface, &mut services).update_exterior(|_| { + Cycle::polygon(points, &mut services).insert(&mut services) + }) }); let intersection = FaceFaceIntersection::compute([&a, &b]); @@ -131,5 +133,7 @@ mod tests { intersection_intervals: expected_intervals }) ); + + services.only_validate([a, b]); } } diff --git a/crates/fj-kernel/src/algorithms/intersect/face_point.rs b/crates/fj-kernel/src/algorithms/intersect/face_point.rs index 109543316..474d0ec00 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_point.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_point.rs @@ -138,7 +138,8 @@ mod tests { use crate::{ algorithms::intersect::{face_point::FacePointIntersection, Intersect}, - builder::{CycleBuilder, FaceBuilder}, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, }; @@ -146,28 +147,36 @@ mod tests { fn point_is_outside_face() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [1., 1.], [0., 2.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([2., 1.]); let intersection = (&face, &point).intersect(); assert_eq!(intersection, None); + + services.only_validate(face); } #[test] fn ray_hits_vertex_while_passing_outside() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [2., 1.], [0., 2.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -175,18 +184,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + services.only_validate(face); } #[test] fn ray_hits_vertex_at_cycle_seam() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[4., 2.], [0., 4.], [0., 0.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 2.]); let intersection = (&face, &point).intersect(); @@ -194,18 +208,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + services.only_validate(face); } #[test] fn ray_hits_vertex_while_staying_inside() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [2., 1.], [3., 0.], [3., 4.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -213,18 +232,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + services.only_validate(face); } #[test] fn ray_hits_parallel_edge_and_leaves_face_at_vertex() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [2., 1.], [3., 1.], [0., 2.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -232,18 +256,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + services.only_validate(face); } #[test] fn ray_hits_parallel_edge_and_does_not_leave_face_there() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -251,18 +280,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + services.only_validate(face); } #[test] fn point_is_coincident_with_edge() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [2., 0.], [0., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); @@ -276,18 +310,23 @@ mod tests { intersection, Some(FacePointIntersection::PointIsOnEdge(edge.clone())) ); + + services.only_validate(face); } #[test] fn point_is_coincident_with_vertex() { let mut services = Services::new(); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [1., 0.], [0., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); @@ -304,5 +343,7 @@ mod tests { intersection, Some(FacePointIntersection::PointIsOnVertex(vertex)) ); + + services.only_validate(face); } } diff --git a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs index 97afca4f5..d4a8b141d 100644 --- a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs @@ -152,7 +152,8 @@ mod tests { }, transform::TransformObject, }, - builder::{CycleBuilder, FaceBuilder}, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, }; @@ -162,15 +163,20 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([-1., 0., 0.], &mut services); assert_eq!((&ray, &face).intersect(), None); + + services.only_validate(face); } #[test] @@ -179,18 +185,23 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([1., 0., 0.], &mut services); assert_eq!( (&ray, &face).intersect(), Some(RayFaceIntersection::RayHitsFace) ); + + services.only_validate(face); } #[test] @@ -199,15 +210,20 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([0., 0., 2.], &mut services); assert_eq!((&ray, &face).intersect(), None); + + services.only_validate(face); } #[test] @@ -216,12 +232,15 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([1., 1., 0.], &mut services); let edge = face @@ -233,6 +252,8 @@ mod tests { (&ray, &face).intersect(), Some(RayFaceIntersection::RayHitsEdge(edge.clone())) ); + + services.only_validate(face); } #[test] @@ -241,12 +262,15 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.yz_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([1., 1., 1.], &mut services); let vertex = face @@ -261,6 +285,8 @@ mod tests { (&ray, &face).intersect(), Some(RayFaceIntersection::RayHitsVertex(vertex)) ); + + services.only_validate(face); } #[test] @@ -269,17 +295,22 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); assert_eq!( (&ray, &face).intersect(), Some(RayFaceIntersection::RayHitsFaceAndAreParallel) ); + + services.only_validate(face); } #[test] @@ -288,14 +319,19 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]], - &mut services, - )) - .build(&mut services); + 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) + }); let face = face.translate([0., 0., 1.], &mut services); assert_eq!((&ray, &face).intersect(), None); + + services.only_validate(face); } } diff --git a/crates/fj-kernel/src/algorithms/triangulate/mod.rs b/crates/fj-kernel/src/algorithms/triangulate/mod.rs index 7e8f5b731..b585ac229 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/mod.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/mod.rs @@ -79,8 +79,8 @@ mod tests { use crate::{ algorithms::approx::{Approx, Tolerance}, - builder::{CycleBuilder, FaceBuilder}, - objects::Face, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, }; @@ -95,9 +95,13 @@ mod tests { let c = [2., 2.]; let d = [0., 1.]; - let face = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services)) - .build(&mut services); + let face = + Face::unbound(services.objects.surfaces.xy_plane(), &mut services) + .update_exterior(|_| { + Cycle::polygon([a, b, c, d], &mut services) + .insert(&mut services) + }); + services.only_validate(&face); let a = Point::from(a).to_xyz(); let b = Point::from(b).to_xyz(); @@ -130,10 +134,14 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = FaceBuilder::new(surface.clone()) - .with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services)) - .with_interior(CycleBuilder::polygon([e, f, g, h], &mut services)) - .build(&mut services); + let face = Face::unbound(surface.clone(), &mut services) + .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)]); + services.only_validate(&face); let triangles = triangulate(face)?; @@ -188,12 +196,12 @@ mod tests { let surface = services.objects.surfaces.xy_plane(); - let face = FaceBuilder::new(surface.clone()) - .with_exterior(CycleBuilder::polygon( - [a, b, c, d, e], - &mut services, - )) - .build(&mut services); + let face = Face::unbound(surface.clone(), &mut services) + .update_exterior(|_| { + Cycle::polygon([a, b, c, d, e], &mut services) + .insert(&mut services) + }); + services.only_validate(&face); let triangles = triangulate(face)?; diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs deleted file mode 100644 index 7dd955e9c..000000000 --- a/crates/fj-kernel/src/builder/face.rs +++ /dev/null @@ -1,58 +0,0 @@ -use fj_interop::mesh::Color; - -use crate::{ - objects::{Face, Surface}, - operations::Insert, - services::Services, - storage::Handle, -}; - -use super::CycleBuilder; - -/// Builder API for [`Face`] -pub struct FaceBuilder { - surface: Handle, - exterior: CycleBuilder, - interiors: Vec, - color: Option, -} -impl FaceBuilder { - /// Create an instance of `FaceBuilder` - pub fn new(surface: Handle) -> Self { - Self { - surface, - exterior: CycleBuilder::new(), - interiors: Vec::new(), - color: None, - } - } - - /// Replace the face's exterior cycle - pub fn with_exterior(mut self, exterior: CycleBuilder) -> Self { - self.exterior = exterior; - self - } - - /// Add an interior cycle to the face - pub fn with_interior(mut self, interior: CycleBuilder) -> Self { - self.interiors.push(interior); - self - } - - /// Define the color of the face - pub fn with_color(mut self, color: Color) -> Self { - self.color = Some(color); - self - } - - /// Build the face - pub fn build(self, services: &mut Services) -> Face { - let exterior = self.exterior.build(services).insert(services); - let interiors = self - .interiors - .into_iter() - .map(|cycle| cycle.build(services).insert(services)); - - Face::new(self.surface, exterior, interiors, self.color) - } -} diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 067bb759d..453523ff1 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -1,6 +1,5 @@ //! API for building objects mod cycle; -mod face; -pub use self::{cycle::CycleBuilder, face::FaceBuilder}; +pub use self::cycle::CycleBuilder; diff --git a/crates/fj-kernel/src/objects/mod.rs b/crates/fj-kernel/src/objects/mod.rs index 6c19fa57f..3964e9f5e 100644 --- a/crates/fj-kernel/src/objects/mod.rs +++ b/crates/fj-kernel/src/objects/mod.rs @@ -75,6 +75,7 @@ mod full; mod object; +mod set; mod stores; pub use self::{ @@ -89,5 +90,6 @@ pub use self::{ vertex::Vertex, }, object::{Bare, BehindHandle, Form, Object, WithHandle}, + set::ObjectSet, stores::{Objects, Surfaces}, }; diff --git a/crates/fj-kernel/src/objects/set.rs b/crates/fj-kernel/src/objects/set.rs new file mode 100644 index 000000000..23ae75202 --- /dev/null +++ b/crates/fj-kernel/src/objects/set.rs @@ -0,0 +1,106 @@ +use std::collections::{btree_set, BTreeSet}; + +use super::{ + BehindHandle, Cycle, Face, GlobalEdge, HalfEdge, Object, Surface, Vertex, +}; + +/// A graph of objects and their relationships +pub struct ObjectSet { + inner: BTreeSet>, +} + +impl From<&Face> for ObjectSet { + fn from(face: &Face) -> Self { + let mut self_ = Self { + inner: BTreeSet::new(), + }; + + face.insert_into_set(&mut self_); + + self_ + } +} + +impl From for ObjectSet { + fn from(face: Face) -> Self { + Self::from(&face) + } +} + +impl From for ObjectSet +where + Faces: IntoIterator, +{ + fn from(faces: Faces) -> Self { + let mut self_ = Self { + inner: BTreeSet::new(), + }; + + for face in faces { + face.insert_into_set(&mut self_); + } + + self_ + } +} + +impl IntoIterator for ObjectSet { + type Item = Object; + type IntoIter = btree_set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + +trait InsertIntoSet { + fn insert_into_set(&self, objects: &mut ObjectSet); +} + +impl InsertIntoSet for Cycle { + fn insert_into_set(&self, objects: &mut ObjectSet) { + for half_edge in self.half_edges() { + objects.inner.insert(half_edge.clone().into()); + half_edge.insert_into_set(objects); + } + } +} + +impl InsertIntoSet for Face { + fn insert_into_set(&self, objects: &mut ObjectSet) { + 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); + + for interior in self.interiors() { + objects.inner.insert(interior.clone().into()); + } + for interior in self.interiors() { + interior.insert_into_set(objects); + } + } +} + +impl InsertIntoSet for GlobalEdge { + fn insert_into_set(&self, _: &mut ObjectSet) {} +} + +impl InsertIntoSet for HalfEdge { + fn insert_into_set(&self, objects: &mut ObjectSet) { + objects.inner.insert(self.start_vertex().clone().into()); + self.start_vertex().insert_into_set(objects); + + objects.inner.insert(self.global_form().clone().into()); + self.global_form().insert_into_set(objects); + } +} + +impl InsertIntoSet for Surface { + fn insert_into_set(&self, _: &mut ObjectSet) {} +} + +impl InsertIntoSet for Vertex { + fn insert_into_set(&self, _: &mut ObjectSet) {} +} diff --git a/crates/fj-kernel/src/operations/build/face.rs b/crates/fj-kernel/src/operations/build/face.rs index 047b1232a..57a5de45a 100644 --- a/crates/fj-kernel/src/operations/build/face.rs +++ b/crates/fj-kernel/src/operations/build/face.rs @@ -8,10 +8,16 @@ use crate::{ storage::Handle, }; -use super::{BuildHalfEdge, BuildSurface}; +use super::{BuildCycle, BuildHalfEdge, BuildSurface}; /// Build a [`Face`] 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) + } + /// Build a triangle fn triangle( points: [impl Into>; 3], diff --git a/crates/fj-kernel/src/operations/update/face.rs b/crates/fj-kernel/src/operations/update/face.rs index 8c29847a1..2de785677 100644 --- a/crates/fj-kernel/src/operations/update/face.rs +++ b/crates/fj-kernel/src/operations/update/face.rs @@ -10,6 +10,12 @@ pub trait UpdateFace { &self, f: impl FnOnce(&Handle) -> Handle, ) -> Self; + + /// Add the provides interiors to the face + fn add_interiors( + &self, + interiors: impl IntoIterator>, + ) -> Self; } impl UpdateFace for Face { @@ -26,4 +32,18 @@ impl UpdateFace for Face { 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(), + ) + } } diff --git a/crates/fj-kernel/src/services/mod.rs b/crates/fj-kernel/src/services/mod.rs index 79482136d..71c3310d2 100644 --- a/crates/fj-kernel/src/services/mod.rs +++ b/crates/fj-kernel/src/services/mod.rs @@ -6,7 +6,7 @@ mod objects; mod service; mod validation; -use crate::objects::{Object, Objects, WithHandle}; +use crate::objects::{Object, ObjectSet, Objects, WithHandle}; pub use self::{ objects::{InsertObject, Operation}, @@ -52,6 +52,15 @@ impl Services { self.validation.execute(command, &mut Vec::new()); } } + + /// Validate the provided objects and forget all other validation errors + pub fn only_validate(&mut self, objects: impl Into) { + let objects = objects.into(); + + let mut events = Vec::new(); + self.validation + .execute(ValidationCommand::OnlyValidate { objects }, &mut events); + } } impl Default for Services { diff --git a/crates/fj-kernel/src/services/validation.rs b/crates/fj-kernel/src/services/validation.rs index 326ff8738..afa3b59e9 100644 --- a/crates/fj-kernel/src/services/validation.rs +++ b/crates/fj-kernel/src/services/validation.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, thread}; use crate::{ - objects::{BehindHandle, Object}, + objects::{BehindHandle, Object, ObjectSet}, storage::ObjectId, validate::ValidationError, }; @@ -39,16 +39,33 @@ impl State for Validation { type Event = ValidationEvent; fn decide(&self, command: Self::Command, events: &mut Vec) { - let ValidationCommand::ValidateObject { object } = command; - let mut errors = Vec::new(); - object.validate(&mut errors); - for err in errors { - events.push(ValidationEvent::ValidationFailed { - object: object.clone(), - err, - }); + match command { + ValidationCommand::ValidateObject { object } => { + object.validate(&mut errors); + + for err in errors { + events.push(ValidationEvent::ValidationFailed { + object: object.clone(), + err, + }); + } + } + ValidationCommand::OnlyValidate { objects } => { + events.push(ValidationEvent::ClearErrors); + + for object in objects { + object.validate(&mut errors); + + for err in errors.drain(..) { + events.push(ValidationEvent::ValidationFailed { + object: object.clone(), + err, + }); + } + } + } } } @@ -57,6 +74,7 @@ impl State for Validation { ValidationEvent::ValidationFailed { object, err } => { self.errors.insert(object.id(), err.clone()); } + ValidationEvent::ClearErrors => self.errors.clear(), } } } @@ -68,6 +86,12 @@ pub enum ValidationCommand { /// The object to validate object: Object, }, + + /// Validate the provided objects, discard all other validation errors + OnlyValidate { + /// The objects to validate + objects: ObjectSet, + }, } /// The event produced by the validation service @@ -81,4 +105,7 @@ pub enum ValidationEvent { /// The validation error err: ValidationError, }, + + /// All stored validation errors are being cleared + ClearErrors, } diff --git a/crates/fj-kernel/src/validate/face.rs b/crates/fj-kernel/src/validate/face.rs index 7b45fd64a..33b31c0f9 100644 --- a/crates/fj-kernel/src/validate/face.rs +++ b/crates/fj-kernel/src/validate/face.rs @@ -73,8 +73,8 @@ mod tests { use crate::{ algorithms::reverse::Reverse, assert_contains_err, - builder::{CycleBuilder, FaceBuilder}, - objects::Face, + objects::{Cycle, Face}, + operations::{BuildCycle, BuildFace, Insert, UpdateFace}, services::Services, validate::{FaceValidationError, Validate, ValidationError}, }; @@ -83,16 +83,20 @@ mod tests { fn face_invalid_interior_winding() -> anyhow::Result<()> { let mut services = Services::new(); - let valid = FaceBuilder::new(services.objects.surfaces.xy_plane()) - .with_exterior(CycleBuilder::polygon( - [[0., 0.], [3., 0.], [0., 3.]], - &mut services, - )) - .with_interior(CycleBuilder::polygon( - [[1., 1.], [1., 2.], [2., 1.]], - &mut services, - )) - .build(&mut services); + 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)]); let invalid = { let interiors = valid .interiors() @@ -116,6 +120,8 @@ mod tests { ) ); + services.only_validate(valid); + Ok(()) } }