From 422f46883e491eece680e718b522646e3698d34c Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:04:26 +0200 Subject: [PATCH 1/7] Add `Shell` --- crates/fj-kernel/src/objects/mod.rs | 2 ++ crates/fj-kernel/src/objects/shell.rs | 51 +++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 crates/fj-kernel/src/objects/shell.rs diff --git a/crates/fj-kernel/src/objects/mod.rs b/crates/fj-kernel/src/objects/mod.rs index 61ac43142..e915a087b 100644 --- a/crates/fj-kernel/src/objects/mod.rs +++ b/crates/fj-kernel/src/objects/mod.rs @@ -8,6 +8,7 @@ mod curve; mod cycle; mod edge; mod face; +mod shell; mod sketch; mod solid; mod surface; @@ -18,6 +19,7 @@ pub use self::{ cycle::Cycle, edge::{Edge, VerticesOfEdge}, face::Face, + shell::Shell, sketch::Sketch, solid::Solid, surface::{Surface, SweptCurve}, diff --git a/crates/fj-kernel/src/objects/shell.rs b/crates/fj-kernel/src/objects/shell.rs new file mode 100644 index 000000000..371063a63 --- /dev/null +++ b/crates/fj-kernel/src/objects/shell.rs @@ -0,0 +1,51 @@ +use std::collections::BTreeSet; + +use super::Face; + +/// A 3-dimensional closed shell +/// +/// # Implementation Note +/// +/// The faces that make up a shell should be closed ("watertight"). This is not +/// currently validated. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Shell { + faces: BTreeSet, +} + +impl Shell { + /// Construct an empty instance of `Shell` + pub fn new() -> Self { + Self { + faces: BTreeSet::new(), + } + } + + /// Add faces to the shell + /// + /// Consumes the shell and returns the updated instance. + pub fn with_faces( + mut self, + faces: impl IntoIterator>, + ) -> Self { + let faces = faces.into_iter().map(Into::into); + self.faces.extend(faces); + self + } + + /// Access the shell's faces + pub fn faces(&self) -> impl Iterator { + self.faces.iter() + } + + /// Convert the shell into a list of faces + pub fn into_faces(self) -> impl Iterator { + self.faces.into_iter() + } +} + +impl Default for Shell { + fn default() -> Self { + Self::new() + } +} From 7347e233851d621b0c30e2504d3df8aad0a34ac8 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:07:03 +0200 Subject: [PATCH 2/7] Integrate `Shell` into `algorithms::transform` --- crates/fj-kernel/src/algorithms/transform.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/crates/fj-kernel/src/algorithms/transform.rs b/crates/fj-kernel/src/algorithms/transform.rs index 37c43a2b0..77d7dcb2b 100644 --- a/crates/fj-kernel/src/algorithms/transform.rs +++ b/crates/fj-kernel/src/algorithms/transform.rs @@ -1,7 +1,7 @@ use fj_math::{Transform, Vector}; use crate::objects::{ - Curve, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Sketch, Solid, + Curve, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Shell, Sketch, Solid, Surface, Vertex, }; @@ -103,6 +103,13 @@ impl TransformObject for GlobalVertex { } } +impl TransformObject for Shell { + fn transform(self, transform: &Transform) -> Self { + let faces = self.into_faces().map(|face| face.transform(transform)); + Self::new().with_faces(faces) + } +} + impl TransformObject for Sketch { fn transform(self, transform: &Transform) -> Self { let faces = self.into_faces().map(|face| face.transform(transform)); From d4e7e8c1daeb3e27f2e529d09f81380eb83789bb Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:09:34 +0200 Subject: [PATCH 3/7] Add `ObjectIters::shell_iter` --- crates/fj-kernel/src/iter.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 53c5575c4..abc6c94bd 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -3,7 +3,7 @@ use std::collections::VecDeque; use crate::objects::{ - Curve, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Sketch, Solid, + Curve, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Shell, Sketch, Solid, Surface, Vertex, }; @@ -81,6 +81,17 @@ pub trait ObjectIters<'r> { iter } + /// Iterate over all shells + fn shell_iter(&'r self) -> Iter<&'r Shell> { + let mut iter = Iter::empty(); + + for object in self.referenced_objects() { + iter = iter.with(object.shell_iter()); + } + + iter + } + /// Iterate over all sketches fn sketch_iter(&'r self) -> Iter<&'r Sketch> { let mut iter = Iter::empty(); @@ -338,6 +349,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(3, object.global_curve_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); @@ -356,6 +368,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(1, object.global_curve_iter().count()); assert_eq!(2, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); @@ -376,6 +389,7 @@ mod tests { assert_eq!(1, object.face_iter().count()); assert_eq!(3, object.global_curve_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); @@ -391,6 +405,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(1, object.global_curve_iter().count()); assert_eq!(0, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); @@ -406,6 +421,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(0, object.global_curve_iter().count()); assert_eq!(1, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); @@ -427,6 +443,7 @@ mod tests { assert_eq!(1, object.face_iter().count()); assert_eq!(3, object.global_curve_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(1, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); @@ -442,6 +459,7 @@ mod tests { assert_eq!(6, object.face_iter().count()); assert_eq!(18, object.global_curve_iter().count()); assert_eq!(8, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(1, object.solid_iter().count()); assert_eq!(6, object.surface_iter().count()); @@ -457,6 +475,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(0, object.global_curve_iter().count()); assert_eq!(0, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); @@ -473,6 +492,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(0, object.global_curve_iter().count()); assert_eq!(1, object.global_vertex_iter().count()); + assert_eq!(0, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); From a3f237a89ae516ef437d2d93b3e7121a80661f81 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:13:22 +0200 Subject: [PATCH 4/7] Add builder API for `Shell` For now, this is a straight copy of `SolidBuilder`. `Solid` will be updated to build on top of `Shell` soon, and then this redundancy can be consolidated. --- crates/fj-kernel/src/builder/mod.rs | 2 ++ crates/fj-kernel/src/builder/shell.rs | 38 +++++++++++++++++++++++++++ crates/fj-kernel/src/objects/shell.rs | 7 +++++ 3 files changed, 47 insertions(+) create mode 100644 crates/fj-kernel/src/builder/shell.rs diff --git a/crates/fj-kernel/src/builder/mod.rs b/crates/fj-kernel/src/builder/mod.rs index b056530a7..d96d27bd5 100644 --- a/crates/fj-kernel/src/builder/mod.rs +++ b/crates/fj-kernel/src/builder/mod.rs @@ -4,6 +4,7 @@ mod curve; mod cycle; mod edge; mod face; +mod shell; mod solid; pub use self::{ @@ -11,5 +12,6 @@ pub use self::{ cycle::CycleBuilder, edge::EdgeBuilder, face::{FaceBuilder, FacePolygon}, + shell::ShellBuilder, solid::SolidBuilder, }; diff --git a/crates/fj-kernel/src/builder/shell.rs b/crates/fj-kernel/src/builder/shell.rs new file mode 100644 index 000000000..a33235cfa --- /dev/null +++ b/crates/fj-kernel/src/builder/shell.rs @@ -0,0 +1,38 @@ +use fj_math::Scalar; + +use crate::{ + algorithms::TransformObject, + objects::{Face, Shell, Surface}, +}; + +/// API for building a [`Shell`] +pub struct ShellBuilder; + +impl ShellBuilder { + /// Create a cube from the length of its edges + pub fn cube_from_edge_length( + &self, + edge_length: impl Into, + ) -> Shell { + // Let's define a short-hand for half the edge length. We're going to + // need it a lot. + let h = edge_length.into() / 2.; + + let points = [[-h, -h], [h, -h], [h, h], [-h, h]]; + + const Z: Scalar = Scalar::ZERO; + let planes = [ + Surface::xy_plane().translate([Z, Z, -h]), // bottom + Surface::xy_plane().translate([Z, Z, h]), // top + Surface::xz_plane().translate([Z, -h, Z]), // front + Surface::xz_plane().translate([Z, h, Z]), // back + Surface::yz_plane().translate([-h, Z, Z]), // left + Surface::yz_plane().translate([h, Z, Z]), // right + ]; + + let faces = + planes.map(|plane| Face::build(plane).polygon_from_points(points)); + + Shell::new().with_faces(faces) + } +} diff --git a/crates/fj-kernel/src/objects/shell.rs b/crates/fj-kernel/src/objects/shell.rs index 371063a63..0416999e8 100644 --- a/crates/fj-kernel/src/objects/shell.rs +++ b/crates/fj-kernel/src/objects/shell.rs @@ -1,5 +1,7 @@ use std::collections::BTreeSet; +use crate::builder::ShellBuilder; + use super::Face; /// A 3-dimensional closed shell @@ -14,6 +16,11 @@ pub struct Shell { } impl Shell { + /// Build a shell using [`ShellBuilder`] + pub fn build() -> ShellBuilder { + ShellBuilder + } + /// Construct an empty instance of `Shell` pub fn new() -> Self { Self { From 53f1a240e55ff3e00fa3eecce20d071cadac7adf Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:15:03 +0200 Subject: [PATCH 5/7] Integrate shell into object iter infrastructure --- crates/fj-kernel/src/iter.rs | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index abc6c94bd..c422ef7f4 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -219,6 +219,22 @@ impl<'r> ObjectIters<'r> for GlobalVertex { } } +impl<'r> ObjectIters<'r> for Shell { + fn referenced_objects(&'r self) -> Vec<&'r dyn ObjectIters> { + let mut objects = Vec::new(); + + for face in self.faces() { + objects.push(face as &dyn ObjectIters); + } + + objects + } + + fn shell_iter(&'r self) -> Iter<&'r Shell> { + Iter::from_object(self) + } +} + impl<'r> ObjectIters<'r> for Sketch { fn referenced_objects(&'r self) -> Vec<&'r dyn ObjectIters> { let mut objects = Vec::new(); @@ -330,8 +346,8 @@ impl Iterator for Iter { #[cfg(test)] mod tests { use crate::objects::{ - Cycle, Edge, Face, GlobalCurve, GlobalVertex, Sketch, Solid, Surface, - Vertex, + Cycle, Edge, Face, GlobalCurve, GlobalVertex, Shell, Sketch, Solid, + Surface, Vertex, }; use super::ObjectIters as _; @@ -428,6 +444,22 @@ mod tests { assert_eq!(0, object.vertex_iter().count()); } + #[test] + fn shell() { + let object = Shell::build().cube_from_edge_length(1.); + + assert_eq!(6, object.cycle_iter().count()); + assert_eq!(20, object.edge_iter().count()); + assert_eq!(6, object.face_iter().count()); + assert_eq!(18, object.global_curve_iter().count()); + assert_eq!(8, object.global_vertex_iter().count()); + assert_eq!(1, object.shell_iter().count()); + assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); + assert_eq!(6, object.surface_iter().count()); + assert_eq!(16, object.vertex_iter().count()); + } + #[test] fn sketch() { let surface = Surface::xy_plane(); From 02a4e1e40c9a866cbf63bc0fce94cc9e6d7dec8a Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:20:00 +0200 Subject: [PATCH 6/7] Use `Shell` in `Solid` --- crates/fj-kernel/src/algorithms/sweep.rs | 7 ++--- crates/fj-kernel/src/algorithms/transform.rs | 4 +-- crates/fj-kernel/src/builder/solid.rs | 27 +++---------------- crates/fj-kernel/src/iter.rs | 4 +-- crates/fj-kernel/src/objects/solid.rs | 28 ++++++++++---------- crates/fj-operations/src/lib.rs | 3 ++- 6 files changed, 27 insertions(+), 46 deletions(-) diff --git a/crates/fj-kernel/src/algorithms/sweep.rs b/crates/fj-kernel/src/algorithms/sweep.rs index a49db73a4..c3df6720f 100644 --- a/crates/fj-kernel/src/algorithms/sweep.rs +++ b/crates/fj-kernel/src/algorithms/sweep.rs @@ -4,8 +4,8 @@ use fj_math::{Point, Scalar, Transform, Triangle, Vector}; use crate::{ iter::ObjectIters, objects::{ - Curve, CurveKind, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Sketch, - Solid, Surface, Vertex, VerticesOfEdge, + Curve, CurveKind, Cycle, Edge, Face, GlobalCurve, GlobalVertex, Shell, + Sketch, Solid, Surface, Vertex, VerticesOfEdge, }, }; @@ -62,7 +62,8 @@ pub fn sweep( } } - Solid::new().with_faces(target) + let shell = Shell::new().with_faces(target); + Solid::new().with_shells([shell]) } fn create_bottom_faces( diff --git a/crates/fj-kernel/src/algorithms/transform.rs b/crates/fj-kernel/src/algorithms/transform.rs index 77d7dcb2b..f26598877 100644 --- a/crates/fj-kernel/src/algorithms/transform.rs +++ b/crates/fj-kernel/src/algorithms/transform.rs @@ -119,8 +119,8 @@ impl TransformObject for Sketch { impl TransformObject for Solid { fn transform(self, transform: &Transform) -> Self { - let faces = self.into_faces().map(|face| face.transform(transform)); - Self::new().with_faces(faces) + let faces = self.into_shells().map(|shell| shell.transform(transform)); + Self::new().with_shells(faces) } } diff --git a/crates/fj-kernel/src/builder/solid.rs b/crates/fj-kernel/src/builder/solid.rs index 53dbb45ba..c109c105a 100644 --- a/crates/fj-kernel/src/builder/solid.rs +++ b/crates/fj-kernel/src/builder/solid.rs @@ -1,9 +1,6 @@ use fj_math::Scalar; -use crate::{ - algorithms::TransformObject, - objects::{Face, Solid, Surface}, -}; +use crate::objects::{Shell, Solid}; /// API for building a [`Solid`] pub struct SolidBuilder; @@ -14,25 +11,7 @@ impl SolidBuilder { &self, edge_length: impl Into, ) -> Solid { - // Let's define a short-hand for half the edge length. We're going to - // need it a lot. - let h = edge_length.into() / 2.; - - let points = [[-h, -h], [h, -h], [h, h], [-h, h]]; - - const Z: Scalar = Scalar::ZERO; - let planes = [ - Surface::xy_plane().translate([Z, Z, -h]), // bottom - Surface::xy_plane().translate([Z, Z, h]), // top - Surface::xz_plane().translate([Z, -h, Z]), // front - Surface::xz_plane().translate([Z, h, Z]), // back - Surface::yz_plane().translate([-h, Z, Z]), // left - Surface::yz_plane().translate([h, Z, Z]), // right - ]; - - let faces = - planes.map(|plane| Face::build(plane).polygon_from_points(points)); - - Solid::new().with_faces(faces) + let shell = Shell::build().cube_from_edge_length(edge_length); + Solid::new().with_shells([shell]) } } diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index c422ef7f4..2adcc8d2d 100644 --- a/crates/fj-kernel/src/iter.rs +++ b/crates/fj-kernel/src/iter.rs @@ -255,7 +255,7 @@ impl<'r> ObjectIters<'r> for Solid { fn referenced_objects(&'r self) -> Vec<&'r dyn ObjectIters> { let mut objects = Vec::new(); - for face in self.faces() { + for face in self.shells() { objects.push(face as &dyn ObjectIters); } @@ -491,7 +491,7 @@ mod tests { assert_eq!(6, object.face_iter().count()); assert_eq!(18, object.global_curve_iter().count()); assert_eq!(8, object.global_vertex_iter().count()); - assert_eq!(0, object.shell_iter().count()); + assert_eq!(1, object.shell_iter().count()); assert_eq!(0, object.sketch_iter().count()); assert_eq!(1, object.solid_iter().count()); assert_eq!(6, object.surface_iter().count()); diff --git a/crates/fj-kernel/src/objects/solid.rs b/crates/fj-kernel/src/objects/solid.rs index ce54ecd25..7c89be7ea 100644 --- a/crates/fj-kernel/src/objects/solid.rs +++ b/crates/fj-kernel/src/objects/solid.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use crate::builder::SolidBuilder; -use super::Face; +use super::Shell; /// A 3-dimensional shape /// @@ -18,7 +18,7 @@ use super::Face; /// `Shell`s, and validate that those `Shell`s don't intersect. #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Solid { - faces: BTreeSet, + shells: BTreeSet, } impl Solid { @@ -30,30 +30,30 @@ impl Solid { /// Construct an empty instance of `Solid` pub fn new() -> Self { Self { - faces: BTreeSet::new(), + shells: BTreeSet::new(), } } - /// Add faces to the solid + /// Add shells to the solid /// /// Consumes the solid and returns the updated instance. - pub fn with_faces( + pub fn with_shells( mut self, - faces: impl IntoIterator>, + shells: impl IntoIterator>, ) -> Self { - let faces = faces.into_iter().map(Into::into); - self.faces.extend(faces); + let shells = shells.into_iter().map(Into::into); + self.shells.extend(shells); self } - /// Access the solid's faces - pub fn faces(&self) -> impl Iterator { - self.faces.iter() + /// Access the solid's shells + pub fn shells(&self) -> impl Iterator { + self.shells.iter() } - /// Convert the solid into a list of faces - pub fn into_faces(self) -> impl Iterator { - self.faces.into_iter() + /// Convert the solid into a list of shells + pub fn into_shells(self) -> impl Iterator { + self.shells.into_iter() } } diff --git a/crates/fj-operations/src/lib.rs b/crates/fj-operations/src/lib.rs index 9105f7baf..da6d659fc 100644 --- a/crates/fj-operations/src/lib.rs +++ b/crates/fj-operations/src/lib.rs @@ -77,7 +77,8 @@ impl Shape for fj::Shape { shape .compute_brep(config, tolerance, debug_info)? .into_inner() - .into_faces() + .into_shells() + .flat_map(|shell| shell.into_faces()) .collect(), config, ), From 85419c22f01e0f3d1a289ee3c27df0662885d962 Mon Sep 17 00:00:00 2001 From: Hanno Braun Date: Tue, 23 Aug 2022 12:20:09 +0200 Subject: [PATCH 7/7] Update doc comment --- crates/fj-kernel/src/objects/solid.rs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/crates/fj-kernel/src/objects/solid.rs b/crates/fj-kernel/src/objects/solid.rs index 7c89be7ea..18558937b 100644 --- a/crates/fj-kernel/src/objects/solid.rs +++ b/crates/fj-kernel/src/objects/solid.rs @@ -8,14 +8,8 @@ use super::Shell; /// /// # Implementation Note /// -/// The faces that make up the solid must form a closed shape. This is not -/// currently validated. -/// -/// In fact, solids could be made up of several closed shells. One outer shell, -/// and multiple inner ones (cavities within the solid). There should probably -/// a separate `Shell` object that is a collection of faces, and validates that -/// those faces form a closed shape. `Solid` should be a collection of such -/// `Shell`s, and validate that those `Shell`s don't intersect. +/// The shells that form the boundaries of the solid must not intersect. This is +/// not currently validated. #[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] pub struct Solid { shells: BTreeSet,