diff --git a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs index 9ede7f995..9e87ec130 100644 --- a/crates/fj-kernel/src/algorithms/intersect/curve_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/curve_face.rs @@ -150,7 +150,7 @@ where #[cfg(test)] mod tests { use crate::{ - builder::CurveBuilder, + builder::{CurveBuilder, FaceBuilder}, objects::{Curve, Face, Objects}, partial::HasPartial, }; @@ -183,11 +183,11 @@ mod tests { [ 1., -1.], ]; - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points(exterior) .with_interior_polygon_from_points(interior) - .build(); + .build(&objects)?; let expected = CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]); diff --git a/crates/fj-kernel/src/algorithms/intersect/face_face.rs b/crates/fj-kernel/src/algorithms/intersect/face_face.rs index ac9e2776c..66b41ac30 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_face.rs @@ -67,7 +67,7 @@ mod tests { use crate::{ algorithms::intersect::CurveFaceIntersection, - builder::CurveBuilder, + builder::{CurveBuilder, FaceBuilder}, objects::{Curve, Face, Objects}, partial::HasPartial, }; @@ -86,12 +86,12 @@ mod tests { [1., 2.], ]; let [a, b] = [objects.surfaces.xy_plane(), objects.surfaces.xz_plane()] - .map(|surface| { - Face::builder(&objects) + .try_map_ext(|surface| { + Face::partial() .with_surface(surface) .with_exterior_polygon_from_points(points) - .build() - }); + .build(&objects) + })?; let intersection = FaceFaceIntersection::compute([&a, &b], &objects)?; @@ -113,12 +113,12 @@ mod tests { ]; let surfaces = [objects.surfaces.xy_plane(), objects.surfaces.xz_plane()]; - let [a, b] = surfaces.clone().map(|surface| { - Face::builder(&objects) + let [a, b] = surfaces.clone().try_map_ext(|surface| { + Face::partial() .with_surface(surface) .with_exterior_polygon_from_points(points) - .build() - }); + .build(&objects) + })?; let intersection = FaceFaceIntersection::compute([&a, &b], &objects)?; diff --git a/crates/fj-kernel/src/algorithms/intersect/face_point.rs b/crates/fj-kernel/src/algorithms/intersect/face_point.rs index 639dde796..6fa3d17b9 100644 --- a/crates/fj-kernel/src/algorithms/intersect/face_point.rs +++ b/crates/fj-kernel/src/algorithms/intersect/face_point.rs @@ -136,34 +136,38 @@ mod tests { use crate::{ algorithms::intersect::{face_point::FacePointIntersection, Intersect}, + builder::FaceBuilder, iter::ObjectIters, objects::{Face, Objects}, + partial::HasPartial, }; #[test] - fn point_is_outside_face() { + fn point_is_outside_face() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [1., 1.], [0., 2.]]) - .build(); + .build(&objects)?; let point = Point::from([2., 1.]); let intersection = (&face, &point).intersect(); assert_eq!(intersection, None); + + Ok(()) } #[test] - fn ray_hits_vertex_while_passing_outside() { + fn ray_hits_vertex_while_passing_outside() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [2., 1.], [0., 2.]]) - .build(); + .build(&objects)?; let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -171,17 +175,19 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + Ok(()) } #[test] - fn ray_hits_vertex_at_cycle_seam() { + fn ray_hits_vertex_at_cycle_seam() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[4., 2.], [0., 4.], [0., 0.]]) - .build(); + .build(&objects)?; let point = Point::from([1., 2.]); let intersection = (&face, &point).intersect(); @@ -189,14 +195,16 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + Ok(()) } #[test] - fn ray_hits_vertex_while_staying_inside() { + fn ray_hits_vertex_while_staying_inside() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [0., 0.], @@ -204,7 +212,7 @@ mod tests { [3., 0.], [3., 4.], ]) - .build(); + .build(&objects)?; let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -212,14 +220,17 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + Ok(()) } #[test] - fn ray_hits_parallel_edge_and_leaves_face_at_vertex() { + fn ray_hits_parallel_edge_and_leaves_face_at_vertex() -> anyhow::Result<()> + { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [0., 0.], @@ -227,7 +238,7 @@ mod tests { [3., 1.], [0., 2.], ]) - .build(); + .build(&objects)?; let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -235,14 +246,17 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + Ok(()) } #[test] - fn ray_hits_parallel_edge_and_does_not_leave_face_there() { + fn ray_hits_parallel_edge_and_does_not_leave_face_there( + ) -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [0., 0.], @@ -251,7 +265,7 @@ mod tests { [4., 0.], [4., 5.], ]) - .build(); + .build(&objects)?; let point = Point::from([1., 1.]); let intersection = (&face, &point).intersect(); @@ -259,17 +273,19 @@ mod tests { intersection, Some(FacePointIntersection::PointIsInsideFace) ); + + Ok(()) } #[test] - fn point_is_coincident_with_edge() { + fn point_is_coincident_with_edge() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [2., 0.], [0., 1.]]) - .build(); + .build(&objects)?; let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); @@ -286,17 +302,19 @@ mod tests { intersection, Some(FacePointIntersection::PointIsOnEdge(edge.clone())) ); + + Ok(()) } #[test] - fn point_is_coincident_with_vertex() { + fn point_is_coincident_with_vertex() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]]) - .build(); + .build(&objects)?; let point = Point::from([1., 0.]); let intersection = (&face, &point).intersect(); @@ -311,5 +329,7 @@ mod tests { intersection, Some(FacePointIntersection::PointIsOnVertex(vertex.clone())) ); + + Ok(()) } } diff --git a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs index ada935ad9..38201c372 100644 --- a/crates/fj-kernel/src/algorithms/intersect/ray_face.rs +++ b/crates/fj-kernel/src/algorithms/intersect/ray_face.rs @@ -152,8 +152,10 @@ mod tests { }, transform::TransformObject, }, + builder::FaceBuilder, iter::ObjectIters, objects::{Face, Objects}, + partial::HasPartial, }; #[test] @@ -163,7 +165,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.yz_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -171,7 +173,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([-1., 0., 0.], &objects)?; assert_eq!((&ray, &face).intersect(), None); @@ -185,7 +187,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.yz_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -193,7 +195,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([1., 0., 0.], &objects)?; assert_eq!( @@ -210,7 +212,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.yz_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -218,7 +220,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([0., 0., 2.], &objects)?; assert_eq!((&ray, &face).intersect(), None); @@ -232,7 +234,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.yz_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -240,7 +242,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([1., 1., 0.], &objects)?; let edge = face @@ -265,7 +267,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.yz_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -273,7 +275,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([1., 1., 1.], &objects)?; let vertex = face @@ -290,13 +292,13 @@ mod tests { } #[test] - fn ray_is_parallel_to_surface_and_hits() { + fn ray_is_parallel_to_surface_and_hits() -> anyhow::Result<()> { let objects = Objects::new(); let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -304,12 +306,14 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build(); + .build(&objects)?; assert_eq!( (&ray, &face).intersect(), Some(RayFaceIntersection::RayHitsFaceAndAreParallel) - ) + ); + + Ok(()) } #[test] @@ -319,7 +323,7 @@ mod tests { let ray = HorizontalRayToTheRight::from([0., 0., 0.]); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-1., -1.], @@ -327,7 +331,7 @@ mod tests { [1., 1.], [-1., 1.], ]) - .build() + .build(&objects)? .translate([0., 0., 1.], &objects)?; assert_eq!((&ray, &face).intersect(), None); diff --git a/crates/fj-kernel/src/algorithms/reverse/face.rs b/crates/fj-kernel/src/algorithms/reverse/face.rs index e96edeeaa..9f7a20607 100644 --- a/crates/fj-kernel/src/algorithms/reverse/face.rs +++ b/crates/fj-kernel/src/algorithms/reverse/face.rs @@ -1,5 +1,6 @@ use crate::{ objects::{Face, Objects}, + partial::HasPartial, storage::Handle, validate::ValidationError, }; @@ -14,10 +15,10 @@ impl Reverse for Handle { .map(|cycle| cycle.clone().reverse(objects)) .collect::, _>>()?; - Ok(Face::builder(objects) + Face::partial() .with_exterior(exterior) .with_interiors(interiors) .with_color(self.color()) - .build()) + .build(objects) } } diff --git a/crates/fj-kernel/src/algorithms/sweep/edge.rs b/crates/fj-kernel/src/algorithms/sweep/edge.rs index a4c09f01e..b4659507a 100644 --- a/crates/fj-kernel/src/algorithms/sweep/edge.rs +++ b/crates/fj-kernel/src/algorithms/sweep/edge.rs @@ -7,6 +7,7 @@ use crate::{ Curve, Cycle, Face, GlobalEdge, HalfEdge, Objects, SurfaceVertex, Vertex, }, + partial::HasPartial, path::SurfacePath, storage::Handle, validate::ValidationError, @@ -175,10 +176,10 @@ impl Sweep for (Handle, Color) { objects.cycles.insert(Cycle::new(edges))? }; - Ok(Face::builder(objects) + Face::partial() .with_exterior(cycle) .with_color(color) - .build()) + .build(objects) } } @@ -256,7 +257,7 @@ mod tests { .cycles .insert(Cycle::new([bottom, side_up, top, side_down]))?; - Face::builder(&objects).with_exterior(cycle).build() + Face::partial().with_exterior(cycle).build(&objects)? }; assert_eq!(face, expected_face); diff --git a/crates/fj-kernel/src/algorithms/sweep/face.rs b/crates/fj-kernel/src/algorithms/sweep/face.rs index eb44fc87c..3919832f7 100644 --- a/crates/fj-kernel/src/algorithms/sweep/face.rs +++ b/crates/fj-kernel/src/algorithms/sweep/face.rs @@ -84,7 +84,7 @@ mod tests { use crate::{ algorithms::{reverse::Reverse, transform::TransformObject}, - builder::HalfEdgeBuilder, + builder::{FaceBuilder, HalfEdgeBuilder}, objects::{Face, HalfEdge, Objects, Sketch}, partial::HasPartial, }; @@ -107,15 +107,15 @@ mod tests { .build() .sweep(UP, &objects)?; - let bottom = Face::builder(&objects) + let bottom = Face::partial() .with_surface(surface.clone()) .with_exterior_polygon_from_points(TRIANGLE) - .build() + .build(&objects)? .reverse(&objects)?; - let top = Face::builder(&objects) + let top = Face::partial() .with_surface(surface.translate(UP, &objects)?) .with_exterior_polygon_from_points(TRIANGLE) - .build(); + .build(&objects)?; assert!(solid.find_face(&bottom).is_some()); assert!(solid.find_face(&top).is_some()); @@ -151,15 +151,15 @@ mod tests { .build() .sweep(DOWN, &objects)?; - let bottom = Face::builder(&objects) + let bottom = Face::partial() .with_surface(surface.clone().translate(DOWN, &objects)?) .with_exterior_polygon_from_points(TRIANGLE) - .build() + .build(&objects)? .reverse(&objects)?; - let top = Face::builder(&objects) + let top = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points(TRIANGLE) - .build(); + .build(&objects)?; assert!(solid.find_face(&bottom).is_some()); assert!(solid.find_face(&top).is_some()); diff --git a/crates/fj-kernel/src/algorithms/transform/face.rs b/crates/fj-kernel/src/algorithms/transform/face.rs index 249f32700..833eaebaa 100644 --- a/crates/fj-kernel/src/algorithms/transform/face.rs +++ b/crates/fj-kernel/src/algorithms/transform/face.rs @@ -2,44 +2,51 @@ use fj_math::Transform; use crate::{ objects::{Face, FaceSet, Objects}, - partial::HasPartial, - storage::Handle, + partial::{HasPartial, PartialFace}, validate::ValidationError, }; use super::TransformObject; -impl TransformObject for Handle { +impl TransformObject for PartialFace { fn transform( self, transform: &Transform, objects: &Objects, ) -> Result { - let surface = self.surface().clone().transform(transform, objects)?; + let surface = self + .surface() + .map(|surface| surface.transform(transform, objects)) + .transpose()?; let exterior = self .exterior() - .to_partial() + .into_partial() .transform(transform, objects)? - .with_surface(Some(surface.clone())) - .build(objects)?; + .with_surface(surface.clone()); let interiors = self .interiors() .map(|cycle| -> Result<_, ValidationError> { cycle - .to_partial() + .into_partial() .transform(transform, objects)? - .with_surface(Some(surface.clone())) + .with_surface(surface.clone()) .build(objects) }) .collect::, _>>()?; let color = self.color(); - Ok(Face::builder(objects) + let mut face = Face::partial() .with_exterior(exterior) - .with_interiors(interiors) - .with_color(color) - .build()) + .with_interiors(interiors); + if let Some(surface) = surface { + face = face.with_surface(surface); + } + if let Some(color) = color { + face = face.with_color(color); + } + + Ok(face) } } diff --git a/crates/fj-kernel/src/algorithms/triangulate/mod.rs b/crates/fj-kernel/src/algorithms/triangulate/mod.rs index 73b68039e..6f9579c70 100644 --- a/crates/fj-kernel/src/algorithms/triangulate/mod.rs +++ b/crates/fj-kernel/src/algorithms/triangulate/mod.rs @@ -84,7 +84,9 @@ mod tests { use crate::{ algorithms::approx::{Approx, Tolerance}, + builder::FaceBuilder, objects::{Face, Objects}, + partial::HasPartial, storage::Handle, }; @@ -100,10 +102,10 @@ mod tests { let d = [0., 1.]; let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([a, b, c, d]) - .build(); + .build(&objects)?; let a = Point::from(a).to_xyz(); let b = Point::from(b).to_xyz(); @@ -135,11 +137,11 @@ mod tests { let h = [3., 1.]; let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface.clone()) .with_exterior_polygon_from_points([a, b, c, d]) .with_interior_polygon_from_points([e, f, g, h]) - .build(); + .build(&objects)?; let triangles = triangulate(face)?; @@ -196,10 +198,10 @@ mod tests { let e = [0., 0.8]; let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface.clone()) .with_exterior_polygon_from_points([a, b, c, d, e]) - .build(); + .build(&objects)?; let triangles = triangulate(face)?; diff --git a/crates/fj-kernel/src/builder/face.rs b/crates/fj-kernel/src/builder/face.rs index 0f217d409..41f6dc5b1 100644 --- a/crates/fj-kernel/src/builder/face.rs +++ b/crates/fj-kernel/src/builder/face.rs @@ -1,115 +1,49 @@ -use fj_interop::mesh::Color; use fj_math::Point; use crate::{ - objects::{Cycle, Face, Objects, Surface}, - partial::HasPartial, - storage::Handle, + objects::Cycle, + partial::{HasPartial, PartialFace}, }; use super::CycleBuilder; -/// API for building a [`Face`] -/// -/// Also see [`Face::builder`]. -pub struct FaceBuilder<'a> { - /// The stores that the created objects are put in - pub objects: &'a Objects, +/// Builder API for [`PartialFace`] +pub trait FaceBuilder { + /// Update the [`PartialFace`] with an exterior polygon + fn with_exterior_polygon_from_points( + self, + points: impl IntoIterator>>, + ) -> Self; - /// The surface that the [`Face`] is defined in - pub surface: Option>, - - /// The exterior cycle that bounds the [`Face`] on the outside - /// - /// Must be provided by the caller, directly or using one of the `with_` - /// methods, before [`FaceBuilder::build`] is called. - pub exterior: Option>, - - /// The interior cycles that form holes in the [`Face`] - pub interiors: Vec>, - - /// The color of the [`Face`] - pub color: Option, + /// Update the [`PartialFace`] with an interior polygon + fn with_interior_polygon_from_points( + self, + points: impl IntoIterator>>, + ) -> Self; } -impl<'a> FaceBuilder<'a> { - /// Build the [`Face`] with the provided surface - pub fn with_surface(mut self, surface: Handle) -> Self { - self.surface = Some(surface); - self - } - - /// Build the [`Face`] with the provided exterior - pub fn with_exterior(mut self, exterior: Handle) -> Self { - self.exterior = Some(exterior); - self - } - - /// Build the [`Face`] with an exterior polygon from the provided points - pub fn with_exterior_polygon_from_points( - mut self, +impl FaceBuilder for PartialFace { + fn with_exterior_polygon_from_points( + self, points: impl IntoIterator>>, ) -> Self { - let surface = self - .surface - .as_ref() - .expect("Need surface to create polygon"); + let surface = self.surface().expect("Need surface to create polygon"); - self.exterior = Some( + self.with_exterior( Cycle::partial() - .with_poly_chain_from_points(surface.clone(), points) - .close_with_line_segment() - .build(self.objects) - .unwrap(), - ); - self + .with_poly_chain_from_points(surface, points) + .close_with_line_segment(), + ) } - /// Build the [`Face`] with the provided interior polygons - pub fn with_interiors( - mut self, - interiors: impl IntoIterator>, - ) -> Self { - self.interiors.extend(interiors); - self - } - - /// Build the [`Face`] with an interior polygon from the provided points - pub fn with_interior_polygon_from_points( - mut self, + fn with_interior_polygon_from_points( + self, points: impl IntoIterator>>, ) -> Self { - let surface = self - .surface - .as_ref() - .expect("Need surface to build polygon."); + let surface = self.surface().expect("Need surface to build polygon."); - self.interiors.push( - Cycle::partial() - .with_poly_chain_from_points(surface.clone(), points) - .close_with_line_segment() - .build(self.objects) - .unwrap(), - ); - self - } - - /// Build the [`Face`] with the provided color - pub fn with_color(mut self, color: Color) -> Self { - self.color = Some(color); - self - } - - /// Construct a polygon from a list of points - pub fn build(self) -> Handle { - let exterior = self - .exterior - .expect("Can't build `Face` without exterior cycle"); - let color = self.color.unwrap_or_default(); - - self.objects - .faces - .insert(Face::new(exterior, self.interiors, color)) - .unwrap() + self.with_interiors([Cycle::partial() + .with_poly_chain_from_points(surface, points) + .close_with_line_segment()]) } } diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index 9dafd5326..1d96e73d7 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -3,7 +3,6 @@ // These are the old-style builders that need to be transferred to the partial // object API. Issue: // https://github.com/hannobraun/Fornjot/issues/1147 -mod face; mod shell; mod sketch; mod solid; @@ -12,6 +11,7 @@ mod solid; mod curve; mod cycle; mod edge; +mod face; mod vertex; pub use self::{ diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs index 9c6d1629b..867519507 100644 --- a/crates/fj-kernel/src/builder/shell.rs +++ b/crates/fj-kernel/src/builder/shell.rs @@ -5,7 +5,7 @@ use fj_math::Scalar; use crate::{ algorithms::transform::TransformObject, - builder::HalfEdgeBuilder, + builder::{FaceBuilder, HalfEdgeBuilder}, objects::{ Curve, Cycle, Face, FaceSet, HalfEdge, Objects, Shell, Surface, SurfaceVertex, Vertex, @@ -54,7 +54,7 @@ impl<'a> ShellBuilder<'a> { .translate([Z, Z, -h], self.objects) .unwrap(); - Face::builder(self.objects) + Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([ [-h, -h], @@ -62,7 +62,8 @@ impl<'a> ShellBuilder<'a> { [h, h], [-h, h], ]) - .build() + .build(self.objects) + .unwrap() }; let (sides, top_edges) = { @@ -193,7 +194,10 @@ impl<'a> ShellBuilder<'a> { .build(self.objects) .unwrap(); - Face::builder(self.objects).with_exterior(cycle).build() + Face::partial() + .with_exterior(cycle) + .build(self.objects) + .unwrap() }); (sides, tops) @@ -259,11 +263,12 @@ impl<'a> ShellBuilder<'a> { ); } - Face::builder(self.objects) + Face::partial() .with_exterior( self.objects.cycles.insert(Cycle::new(edges)).unwrap(), ) - .build() + .build(self.objects) + .unwrap() }; self.faces.extend([bottom]); diff --git a/crates/fj-kernel/src/builder/sketch.rs b/crates/fj-kernel/src/builder/sketch.rs index e0aef7c48..aad2b9620 100644 --- a/crates/fj-kernel/src/builder/sketch.rs +++ b/crates/fj-kernel/src/builder/sketch.rs @@ -2,9 +2,12 @@ use fj_math::Point; use crate::{ objects::{Face, FaceSet, Objects, Sketch, Surface}, + partial::HasPartial, storage::Handle, }; +use super::FaceBuilder; + /// API for building a [`Sketch`] /// /// Also see [`Sketch::builder`]. @@ -44,10 +47,11 @@ impl<'a> SketchBuilder<'a> { .surface .as_ref() .expect("Can't build `Sketch` without `Surface`"); - self.faces.extend([Face::builder(self.objects) + self.faces.extend([Face::partial() .with_surface(surface.clone()) .with_exterior_polygon_from_points(points) - .build()]); + .build(self.objects) + .unwrap()]); self } diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index c27d3fbaf..6708c96b1 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -360,7 +360,7 @@ impl Iterator for Iter { #[cfg(test)] mod tests { use crate::{ - builder::{CurveBuilder, CycleBuilder, HalfEdgeBuilder}, + builder::{CurveBuilder, CycleBuilder, FaceBuilder, HalfEdgeBuilder}, objects::{ Curve, Cycle, Face, GlobalCurve, GlobalVertex, HalfEdge, Objects, Shell, Sketch, Solid, SurfaceVertex, Vertex, @@ -424,10 +424,10 @@ mod tests { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let object = Face::builder(&objects) + let object = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]]) - .build(); + .build(&objects); assert_eq!(3, object.curve_iter().count()); assert_eq!(1, object.cycle_iter().count()); @@ -528,14 +528,14 @@ mod tests { } #[test] - fn sketch() { + fn sketch() -> anyhow::Result<()> { let objects = Objects::new(); let surface = objects.surfaces.xy_plane(); - let face = Face::builder(&objects) + let face = Face::partial() .with_surface(surface) .with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]]) - .build(); + .build(&objects)?; let object = Sketch::builder(&objects).with_faces([face]).build(); assert_eq!(3, object.curve_iter().count()); @@ -549,6 +549,8 @@ mod tests { assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); assert_eq!(6, object.vertex_iter().count()); + + Ok(()) } #[test] diff --git a/crates/fj-kernel/src/objects/face.rs b/crates/fj-kernel/src/objects/face.rs index 649c3d571..7fba05e73 100644 --- a/crates/fj-kernel/src/objects/face.rs +++ b/crates/fj-kernel/src/objects/face.rs @@ -3,9 +3,9 @@ use std::collections::{btree_set, BTreeSet}; use fj_interop::mesh::Color; use fj_math::Winding; -use crate::{builder::FaceBuilder, storage::Handle}; +use crate::storage::Handle; -use super::{Cycle, Objects, Surface}; +use super::{Cycle, Surface}; /// A face of a shape /// @@ -39,17 +39,6 @@ pub struct Face { } impl Face { - /// Build a `Face` using [`FaceBuilder`] - pub fn builder(objects: &Objects) -> FaceBuilder { - FaceBuilder { - objects, - surface: None, - exterior: None, - interiors: Vec::new(), - color: None, - } - } - /// Construct a new instance of `Face` pub fn new( exterior: Handle, diff --git a/crates/fj-kernel/src/partial/mod.rs b/crates/fj-kernel/src/partial/mod.rs index 172c05cef..f7e845d07 100644 --- a/crates/fj-kernel/src/partial/mod.rs +++ b/crates/fj-kernel/src/partial/mod.rs @@ -45,6 +45,7 @@ pub use self::{ curve::{PartialCurve, PartialGlobalCurve}, cycle::PartialCycle, edge::{PartialGlobalEdge, PartialHalfEdge}, + face::PartialFace, vertex::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex}, }, traits::{HasPartial, Partial}, diff --git a/crates/fj-kernel/src/partial/objects/face.rs b/crates/fj-kernel/src/partial/objects/face.rs new file mode 100644 index 000000000..c2dbf2a33 --- /dev/null +++ b/crates/fj-kernel/src/partial/objects/face.rs @@ -0,0 +1,114 @@ +use fj_interop::mesh::Color; + +use crate::{ + objects::{Cycle, Face, Objects, Surface}, + partial::{util::merge_options, MaybePartial}, + storage::Handle, + validate::ValidationError, +}; + +/// A partial [`Face`] +/// +/// See [`crate::partial`] for more information. +#[derive(Clone, Debug, Default)] +pub struct PartialFace { + surface: Option>, + exterior: MaybePartial, + interiors: Vec>, + color: Option, +} + +impl PartialFace { + /// Access th surface that the [`Face`] is defined in + pub fn surface(&self) -> Option> { + self.surface.clone() + } + + /// Access the [`Face`]'s exterior cycle + pub fn exterior(&self) -> MaybePartial { + self.exterior.clone() + } + + /// Access the [`Face`]'s interior cycles + pub fn interiors(&self) -> impl Iterator> + '_ { + self.interiors.iter().cloned() + } + + /// Access the color of the [`Face`] + pub fn color(&self) -> Option { + self.color + } + + /// Build the [`Face`] with the provided surface + pub fn with_surface(mut self, surface: Handle) -> Self { + self.surface = Some(surface); + self + } + + /// Build the [`Face`] with the provided exterior + pub fn with_exterior( + mut self, + exterior: impl Into>, + ) -> Self { + self.exterior = exterior.into(); + self + } + + /// Build the [`Face`] with the provided interior polygons + pub fn with_interiors( + mut self, + interiors: impl IntoIterator>>, + ) -> Self { + let interiors = interiors.into_iter().map(Into::into); + self.interiors.extend(interiors); + self + } + + /// Build the [`Face`] with the provided color + pub fn with_color(mut self, color: Color) -> Self { + self.color = Some(color); + self + } + + /// Merge this partial object with another + pub fn merge_with(self, other: Self) -> Self { + let mut interiors = self.interiors; + interiors.extend(other.interiors); + + Self { + surface: merge_options(self.surface, other.surface), + exterior: self.exterior.merge_with(other.exterior), + interiors, + color: merge_options(self.color, other.color), + } + } + + /// Construct a polygon from a list of points + pub fn build( + self, + objects: &Objects, + ) -> Result, ValidationError> { + let exterior = self.exterior.into_full(objects)?; + let interiors = self + .interiors + .into_iter() + .map(|cycle| cycle.into_full(objects)) + .collect::, _>>()?; + let color = self.color.unwrap_or_default(); + + Ok(objects + .faces + .insert(Face::new(exterior, interiors, color))?) + } +} + +impl From<&Face> for PartialFace { + fn from(face: &Face) -> Self { + Self { + surface: Some(face.surface().clone()), + exterior: face.exterior().clone().into(), + interiors: face.interiors().cloned().map(Into::into).collect(), + color: Some(face.color()), + } + } +} diff --git a/crates/fj-kernel/src/partial/objects/mod.rs b/crates/fj-kernel/src/partial/objects/mod.rs index 751063c23..5ab9c57b7 100644 --- a/crates/fj-kernel/src/partial/objects/mod.rs +++ b/crates/fj-kernel/src/partial/objects/mod.rs @@ -1,18 +1,19 @@ pub mod curve; pub mod cycle; pub mod edge; +pub mod face; pub mod vertex; use crate::{ objects::{ - Curve, Cycle, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects, - SurfaceVertex, Vertex, + Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, + Objects, SurfaceVertex, Vertex, }, storage::Handle, }; use super::{ - HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, + HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, PartialFace, PartialGlobalCurve, PartialGlobalEdge, PartialGlobalVertex, PartialHalfEdge, PartialSurfaceVertex, PartialVertex, }; @@ -53,6 +54,7 @@ macro_rules! impl_traits { impl_traits!( Curve, PartialCurve; Cycle, PartialCycle; + Face, PartialFace; GlobalCurve, PartialGlobalCurve; GlobalEdge, PartialGlobalEdge; GlobalVertex, PartialGlobalVertex; diff --git a/crates/fj-kernel/src/validate/face.rs b/crates/fj-kernel/src/validate/face.rs index 166afb827..098b5ea28 100644 --- a/crates/fj-kernel/src/validate/face.rs +++ b/crates/fj-kernel/src/validate/face.rs @@ -105,7 +105,7 @@ impl FaceValidationError { mod tests { use crate::{ algorithms::reverse::Reverse, - builder::CycleBuilder, + builder::{CycleBuilder, FaceBuilder}, objects::{Cycle, Face, Objects}, partial::HasPartial, validate::Validate, @@ -115,11 +115,11 @@ mod tests { fn face_surface_mismatch() -> anyhow::Result<()> { let objects = Objects::new(); - let valid = Face::builder(&objects) + let valid = Face::partial() .with_surface(objects.surfaces.xy_plane()) .with_exterior_polygon_from_points([[0., 0.], [3., 0.], [0., 3.]]) .with_interior_polygon_from_points([[1., 1.], [1., 2.], [2., 1.]]) - .build(); + .build(&objects)?; let invalid = { let interiors = [Cycle::partial() .with_poly_chain_from_points( @@ -142,11 +142,11 @@ mod tests { fn face_invalid_interior_winding() -> anyhow::Result<()> { let objects = Objects::new(); - let valid = Face::builder(&objects) + let valid = Face::partial() .with_surface(objects.surfaces.xy_plane()) .with_exterior_polygon_from_points([[0., 0.], [3., 0.], [0., 3.]]) .with_interior_polygon_from_points([[1., 1.], [1., 2.], [2., 1.]]) - .build(); + .build(&objects)?; let invalid = { let interiors = valid .interiors() diff --git a/crates/fj-operations/src/difference_2d.rs b/crates/fj-operations/src/difference_2d.rs index c5c5e9e30..341ac2b30 100644 --- a/crates/fj-operations/src/difference_2d.rs +++ b/crates/fj-operations/src/difference_2d.rs @@ -5,6 +5,7 @@ use fj_kernel::{ algorithms::reverse::Reverse, iter::ObjectIters, objects::{Face, Objects, Sketch}, + partial::HasPartial, validate::ValidationError, }; use fj_math::Aabb; @@ -78,11 +79,11 @@ impl Shape for fj::Difference2d { ); faces.push( - Face::builder(objects) + Face::partial() .with_exterior(exterior) .with_interiors(interiors) .with_color(Color(self.color())) - .build(), + .build(objects)?, ); } diff --git a/crates/fj-operations/src/sketch.rs b/crates/fj-operations/src/sketch.rs index 3b0573d76..83ab1911c 100644 --- a/crates/fj-operations/src/sketch.rs +++ b/crates/fj-operations/src/sketch.rs @@ -2,7 +2,7 @@ use std::ops::Deref; use fj_interop::{debug::DebugInfo, mesh::Color}; use fj_kernel::{ - builder::HalfEdgeBuilder, + builder::{FaceBuilder, HalfEdgeBuilder}, objects::{Cycle, Face, HalfEdge, Objects, Sketch}, partial::HasPartial, validate::ValidationError, @@ -32,20 +32,20 @@ impl Shape for fj::Sketch { .build(objects)?; let cycle = objects.cycles.insert(Cycle::new([half_edge]))?; - Face::builder(objects) + Face::partial() .with_exterior(cycle) .with_color(Color(self.color())) - .build() + .build(objects)? } fj::Chain::PolyChain(poly_chain) => { let points = poly_chain.to_points().into_iter().map(Point::from); - Face::builder(objects) + Face::partial() .with_surface(surface) .with_exterior_polygon_from_points(points) .with_color(Color(self.color())) - .build() + .build(objects)? } };