diff --git a/crates/fj-kernel/src/algorithms/sweep.rs b/crates/fj-kernel/src/algorithms/sweep.rs index 0f5c4b108..af832b464 100644 --- a/crates/fj-kernel/src/algorithms/sweep.rs +++ b/crates/fj-kernel/src/algorithms/sweep.rs @@ -4,7 +4,7 @@ use crate::{ iter::ObjectIters, local::Local, objects::{ - Curve, Cycle, Edge, Face, GlobalVertex, Sketch, Surface, Vertex, + Curve, Cycle, Edge, Face, GlobalVertex, Sketch, Solid, Surface, Vertex, VerticesOfEdge, }, }; @@ -17,7 +17,7 @@ pub fn sweep( path: impl Into>, tolerance: Tolerance, color: [u8; 4], -) -> Vec { +) -> Solid { let path = path.into(); let is_sweep_along_negative_direction = @@ -62,7 +62,7 @@ pub fn sweep( } } - target + Solid::from_faces(target) } fn create_bottom_faces( diff --git a/crates/fj-kernel/src/algorithms/transform.rs b/crates/fj-kernel/src/algorithms/transform.rs index bbcf117a9..13ea5da38 100644 --- a/crates/fj-kernel/src/algorithms/transform.rs +++ b/crates/fj-kernel/src/algorithms/transform.rs @@ -4,7 +4,7 @@ use crate::{ local::Local, objects::{ Curve, Cycle, CyclesInFace, Edge, Face, FaceBRep, GlobalVertex, Sketch, - Surface, Vertex, + Solid, Surface, Vertex, }, }; @@ -116,6 +116,14 @@ impl TransformObject for Sketch { } } +impl TransformObject for Solid { + fn transform(self, transform: &Transform) -> Self { + let mut faces = self.into_faces(); + transform_faces(&mut faces, transform); + Self::from_faces(faces) + } +} + impl TransformObject for Surface { fn transform(self, transform: &Transform) -> Self { match self { diff --git a/crates/fj-kernel/src/iter.rs b/crates/fj-kernel/src/iter.rs index 1b9c73e19..44695fc99 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, GlobalVertex, Sketch, Surface, Vertex, + Curve, Cycle, Edge, Face, GlobalVertex, Sketch, Solid, Surface, Vertex, }; /// Access iterators over all objects of a shape, or part of it @@ -29,6 +29,9 @@ pub trait ObjectIters { /// Iterate over all sketches fn sketch_iter(&self) -> Iter; + /// Iterate over all solids + fn solid_iter(&self) -> Iter; + /// Iterate over all surfaces fn surface_iter(&self) -> Iter; @@ -61,6 +64,10 @@ impl ObjectIters for Curve<3> { Iter::empty() } + fn solid_iter(&self) -> Iter { + Iter::empty() + } + fn surface_iter(&self) -> Iter { Iter::empty() } @@ -125,6 +132,16 @@ impl ObjectIters for Cycle { iter } + fn solid_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.edges() { + iter = iter.with(edge.solid_iter()); + } + + iter + } + fn surface_iter(&self) -> Iter { let mut iter = Iter::empty(); @@ -201,6 +218,16 @@ impl ObjectIters for Edge { iter } + fn solid_iter(&self) -> Iter { + let mut iter = Iter::empty().with(self.curve().solid_iter()); + + for vertex in self.vertices().into_iter().flatten() { + iter = iter.with(vertex.solid_iter()); + } + + iter + } + fn surface_iter(&self) -> Iter { let mut iter = Iter::empty().with(self.curve().surface_iter()); @@ -298,6 +325,20 @@ impl ObjectIters for Face { Iter::empty() } + fn solid_iter(&self) -> Iter { + if let Face::Face(face) = self { + let mut iter = Iter::empty().with(face.surface().solid_iter()); + + for cycle in face.all_cycles() { + iter = iter.with(cycle.solid_iter()); + } + + return iter; + } + + Iter::empty() + } + fn surface_iter(&self) -> Iter { if let Face::Face(face) = self { let mut iter = Iter::empty().with(face.surface().surface_iter()); @@ -352,6 +393,10 @@ impl ObjectIters for GlobalVertex { Iter::empty() } + fn solid_iter(&self) -> Iter { + Iter::empty() + } + fn surface_iter(&self) -> Iter { Iter::empty() } @@ -416,6 +461,102 @@ impl ObjectIters for Sketch { Iter::from_object(self.clone()) } + fn solid_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.solid_iter()); + } + + iter + } + + fn surface_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.surface_iter()); + } + + iter + } + + fn vertex_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.vertex_iter()); + } + + iter + } +} + +impl ObjectIters for Solid { + fn curve_iter(&self) -> Iter> { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.curve_iter()); + } + + iter + } + + fn cycle_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.cycle_iter()); + } + + iter + } + + fn edge_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.edge_iter()); + } + + iter + } + + fn face_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.face_iter()); + } + + iter + } + + fn global_vertex_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.global_vertex_iter()); + } + + iter + } + + fn sketch_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for edge in self.faces() { + iter = iter.with(edge.sketch_iter()); + } + + iter + } + + fn solid_iter(&self) -> Iter { + Iter::from_object(self.clone()) + } + fn surface_iter(&self) -> Iter { let mut iter = Iter::empty(); @@ -462,6 +603,10 @@ impl ObjectIters for Surface { Iter::empty() } + fn solid_iter(&self) -> Iter { + Iter::empty() + } + fn surface_iter(&self) -> Iter { Iter::from_object(*self) } @@ -496,6 +641,10 @@ impl ObjectIters for Vertex { self.global().sketch_iter() } + fn solid_iter(&self) -> Iter { + self.global().solid_iter() + } + fn surface_iter(&self) -> Iter { self.global().surface_iter() } @@ -575,6 +724,16 @@ where iter } + fn solid_iter(&self) -> Iter { + let mut iter = Iter::empty(); + + for object in self.into_iter() { + iter = iter.with(object.solid_iter()); + } + + iter + } + fn surface_iter(&self) -> Iter { let mut iter = Iter::empty(); @@ -637,7 +796,7 @@ impl Iterator for Iter { #[cfg(test)] mod tests { use crate::objects::{ - Curve, Cycle, Edge, Face, GlobalVertex, Sketch, Surface, Vertex, + Curve, Cycle, Edge, Face, GlobalVertex, Sketch, Solid, Surface, Vertex, }; use super::ObjectIters as _; @@ -652,6 +811,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(0, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); assert_eq!(0, object.vertex_iter().count()); } @@ -669,6 +829,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); assert_eq!(6, object.vertex_iter().count()); } @@ -686,6 +847,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(2, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); assert_eq!(2, object.vertex_iter().count()); } @@ -702,6 +864,7 @@ mod tests { assert_eq!(1, object.face_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); assert_eq!(6, object.vertex_iter().count()); } @@ -716,6 +879,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(1, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); assert_eq!(0, object.vertex_iter().count()); } @@ -733,10 +897,26 @@ mod tests { assert_eq!(1, object.face_iter().count()); assert_eq!(3, object.global_vertex_iter().count()); assert_eq!(1, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); assert_eq!(6, object.vertex_iter().count()); } + #[test] + fn solid() { + let object = Solid::cube_from_edge_length(1.); + + assert_eq!(18, object.curve_iter().count()); + assert_eq!(6, object.cycle_iter().count()); + assert_eq!(20, object.edge_iter().count()); + assert_eq!(6, object.face_iter().count()); + assert_eq!(8, object.global_vertex_iter().count()); + assert_eq!(0, object.sketch_iter().count()); + assert_eq!(1, object.solid_iter().count()); + assert_eq!(6, object.surface_iter().count()); + assert_eq!(16, object.vertex_iter().count()); + } + #[test] fn surface() { let object = Surface::xy_plane(); @@ -747,6 +927,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(0, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(1, object.surface_iter().count()); assert_eq!(0, object.vertex_iter().count()); } @@ -762,6 +943,7 @@ mod tests { assert_eq!(0, object.face_iter().count()); assert_eq!(1, object.global_vertex_iter().count()); assert_eq!(0, object.sketch_iter().count()); + assert_eq!(0, object.solid_iter().count()); assert_eq!(0, object.surface_iter().count()); assert_eq!(1, object.vertex_iter().count()); } diff --git a/crates/fj-kernel/src/objects/mod.rs b/crates/fj-kernel/src/objects/mod.rs index 3f981f04f..72021c07b 100644 --- a/crates/fj-kernel/src/objects/mod.rs +++ b/crates/fj-kernel/src/objects/mod.rs @@ -10,6 +10,7 @@ mod edge; mod face; mod global_vertex; mod sketch; +mod solid; mod surface; mod vertex; @@ -20,6 +21,7 @@ pub use self::{ face::{CyclesInFace, Face, FaceBRep}, global_vertex::GlobalVertex, sketch::Sketch, + solid::Solid, surface::{Surface, SweptCurve}, vertex::Vertex, }; diff --git a/crates/fj-kernel/src/objects/sketch.rs b/crates/fj-kernel/src/objects/sketch.rs index 5fd1ac522..1cf200407 100644 --- a/crates/fj-kernel/src/objects/sketch.rs +++ b/crates/fj-kernel/src/objects/sketch.rs @@ -12,7 +12,7 @@ pub struct Sketch { } impl Sketch { - /// Construct a sketch from a number of faces + /// Construct a sketch from faces pub fn from_faces(faces: impl IntoIterator) -> Self { let faces = faces.into_iter().collect(); Self { faces } diff --git a/crates/fj-kernel/src/objects/solid.rs b/crates/fj-kernel/src/objects/solid.rs new file mode 100644 index 000000000..a2ce1f0e5 --- /dev/null +++ b/crates/fj-kernel/src/objects/solid.rs @@ -0,0 +1,59 @@ +use fj_math::Scalar; + +use crate::algorithms::TransformObject; + +use super::{Face, Surface}; + +/// A 3-dimensional shape +/// +/// # Implementation Note +/// +/// The faces that make up the solid must form a closed shape. This is not +/// currently validated. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] +pub struct Solid { + faces: Vec, +} + +impl Solid { + /// Construct a solid from faces + pub fn from_faces(faces: impl IntoIterator) -> Self { + let faces = faces.into_iter().collect(); + Self { faces } + } + + /// Create a cube from the length of its edges + pub fn cube_from_edge_length(edge_length: impl Into) -> Self { + // 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::builder(plane).with_exterior_polygon(points).build() + }); + + Solid::from_faces(faces) + } + + /// Access the solid's faces + pub fn faces(&self) -> impl Iterator { + self.faces.iter() + } + + /// Convert the solid into a list of faces + pub fn into_faces(self) -> Vec { + self.faces + } +} diff --git a/crates/fj-operations/src/sweep.rs b/crates/fj-operations/src/sweep.rs index c1b81f797..0cf4a150c 100644 --- a/crates/fj-operations/src/sweep.rs +++ b/crates/fj-operations/src/sweep.rs @@ -27,7 +27,7 @@ impl Shape for fj::Sweep { color, ); - validate(solid, config) + validate(solid.into_faces(), config) } fn bounding_volume(&self) -> Aabb<3> {