diff --git a/fj-kernel/src/algorithms/approximation.rs b/fj-kernel/src/algorithms/approximation.rs index bb5e5b42e..047bb2fb6 100644 --- a/fj-kernel/src/algorithms/approximation.rs +++ b/fj-kernel/src/algorithms/approximation.rs @@ -120,7 +120,7 @@ mod tests { use crate::{ geometry::Surface, shape::Shape, - topology::{Cycle, Edge, Face, Vertex}, + topology::{Cycle, Face, Vertex}, }; use super::Approximation; @@ -162,23 +162,7 @@ mod tests { let c = Point::from([3., 5., 8.]); let d = Point::from([5., 8., 13.]); - let v1 = Vertex::build(&mut shape).from_point(a)?; - let v2 = Vertex::build(&mut shape).from_point(b)?; - let v3 = Vertex::build(&mut shape).from_point(c)?; - let v4 = Vertex::build(&mut shape).from_point(d)?; - - let ab = Edge::build(&mut shape) - .line_segment_from_vertices([v1.clone(), v2.clone()])?; - let bc = Edge::build(&mut shape) - .line_segment_from_vertices([v2, v3.clone()])?; - let cd = Edge::build(&mut shape) - .line_segment_from_vertices([v3, v4.clone()])?; - let da = - Edge::build(&mut shape).line_segment_from_vertices([v4, v1])?; - - let abcd = shape.insert(Cycle { - edges: vec![ab, bc, cd, da], - })?; + let abcd = Cycle::build(&mut shape).polygon([a, b, c, d])?; let surface = shape.insert(Surface::x_y_plane())?; let face = Face::Face { diff --git a/fj-kernel/src/algorithms/sweep.rs b/fj-kernel/src/algorithms/sweep.rs index d99be2976..68bfd9223 100644 --- a/fj-kernel/src/algorithms/sweep.rs +++ b/fj-kernel/src/algorithms/sweep.rs @@ -326,7 +326,7 @@ mod tests { use crate::{ geometry::{Surface, SweptCurve}, shape::{Handle, Shape}, - topology::{Cycle, Edge, Face, Vertex}, + topology::{Cycle, Edge, Face}, }; use super::sweep_shape; @@ -377,32 +377,24 @@ mod tests { } impl Triangle { - fn new([a, b, c]: [impl Into>; 3]) -> anyhow::Result { + fn new(points: [impl Into>; 3]) -> anyhow::Result { let mut shape = Shape::new(); - let a = shape.insert(a.into())?; - let b = shape.insert(b.into())?; - let c = shape.insert(c.into())?; + let [a, b, c] = points.map(|point| point.into()); - let a = shape.insert(Vertex { point: a })?; - let b = shape.insert(Vertex { point: b })?; - let c = shape.insert(Vertex { point: c })?; - - let ab = Edge::build(&mut shape) - .line_segment_from_vertices([a.clone(), b.clone()])?; - let bc = Edge::build(&mut shape) - .line_segment_from_vertices([b.clone(), c.clone()])?; - let ca = Edge::build(&mut shape) - .line_segment_from_vertices([c.clone(), a.clone()])?; + let ab = + Edge::build(&mut shape).line_segment_from_points([a, b])?; + let bc = + Edge::build(&mut shape).line_segment_from_points([b, c])?; + let ca = + Edge::build(&mut shape).line_segment_from_points([c, a])?; let cycles = shape.insert(Cycle { edges: vec![ab, bc, ca], })?; let surface = shape.insert(Surface::SweptCurve( - SweptCurve::plane_from_points( - [a, b, c].map(|vertex| vertex.get().point()), - ), + SweptCurve::plane_from_points([a, b, c]), ))?; let abc = Face::Face { surface, diff --git a/fj-kernel/src/shape/topology.rs b/fj-kernel/src/shape/topology.rs index ec63bdca5..f18e852b5 100644 --- a/fj-kernel/src/shape/topology.rs +++ b/fj-kernel/src/shape/topology.rs @@ -191,15 +191,13 @@ mod tests { } fn add_edge(&mut self) -> anyhow::Result> { - let vertices = [(); 2].map(|()| { + let points = [(); 2].map(|()| { let point = self.next_point; self.next_point.x += Scalar::ONE; - - let point = self.insert(point).unwrap(); - self.insert(Vertex { point }).unwrap() + point }); let edge = Edge::build(&mut self.inner) - .line_segment_from_vertices(vertices)?; + .line_segment_from_points(points)?; Ok(edge) } diff --git a/fj-kernel/src/topology/builder.rs b/fj-kernel/src/topology/builder.rs index 7b55db4e0..55ea4e906 100644 --- a/fj-kernel/src/topology/builder.rs +++ b/fj-kernel/src/topology/builder.rs @@ -5,7 +5,7 @@ use crate::{ shape::{Handle, Shape, ValidationResult}, }; -use super::{Edge, Vertex}; +use super::{Cycle, Edge, Vertex}; /// API for building a [`Vertex`] pub struct VertexBuilder<'r> { @@ -19,12 +19,15 @@ impl<'r> VertexBuilder<'r> { } /// Build a [`Vertex`] from a point + /// + /// If an identical point or vertex are already part of the shape, those + /// objects are re-used. pub fn from_point( self, point: impl Into>, ) -> ValidationResult { - let point = self.shape.insert(point.into())?; - let vertex = self.shape.insert(Vertex { point })?; + let point = self.shape.get_handle_or_insert(point.into())?; + let vertex = self.shape.get_handle_or_insert(Vertex { point })?; Ok(vertex) } @@ -55,6 +58,25 @@ impl<'r> EdgeBuilder<'r> { Ok(edge) } + /// Build a line segment from two points + pub fn line_segment_from_points( + self, + vertices: [impl Into>; 2], + ) -> ValidationResult { + // Can be cleaned up with `try_map`, once that is stable: + // https://doc.rust-lang.org/std/primitive.array.html#method.try_map + let vertices = + vertices.map(|point| Vertex::build(self.shape).from_point(point)); + let vertices = match vertices { + [Ok(a), Ok(b)] => Ok([a, b]), + [Err(err), _] | [_, Err(err)] => Err(err), + }?; + + let edge = self.line_segment_from_vertices(vertices)?; + + Ok(edge) + } + /// Build a line segment from two vertices pub fn line_segment_from_vertices( self, @@ -71,3 +93,42 @@ impl<'r> EdgeBuilder<'r> { Ok(edge) } } + +/// API for building a [`Cycle`] +pub struct CycleBuilder<'r> { + shape: &'r mut Shape, +} + +impl<'r> CycleBuilder<'r> { + /// Construct a new instance of `CycleBuilder` + pub fn new(shape: &'r mut Shape) -> Self { + Self { shape } + } + + /// Build a polygon from a list of points + pub fn polygon( + self, + points: impl IntoIterator>>, + ) -> ValidationResult { + // A polygon is closed, so we need to add the first point at the end + // again, for the next step. + let mut points: Vec<_> = points.into_iter().map(Into::into).collect(); + if let Some(point) = points.first().cloned() { + points.push(point); + } + + let mut edges = Vec::new(); + for ab in points.windows(2) { + // Can't panic, as we passed `2` to `windows`. + // + // Can be cleaned up, once `array_windows` is stable. + let points = [ab[0], ab[1]]; + + let edge = + Edge::build(self.shape).line_segment_from_points(points)?; + edges.push(edge); + } + + self.shape.insert(Cycle { edges }) + } +} diff --git a/fj-kernel/src/topology/edges.rs b/fj-kernel/src/topology/edges.rs index 7871481ba..cf5aad58c 100644 --- a/fj-kernel/src/topology/edges.rs +++ b/fj-kernel/src/topology/edges.rs @@ -5,7 +5,7 @@ use crate::{ shape::{Handle, Shape}, }; -use super::{vertices::Vertex, EdgeBuilder}; +use super::{builder::CycleBuilder, vertices::Vertex, EdgeBuilder}; /// A cycle of connected edges /// @@ -29,6 +29,11 @@ pub struct Cycle { } impl Cycle { + /// Build a cycle using the [`CycleBuilder`] API + pub fn build(shape: &mut Shape) -> CycleBuilder { + CycleBuilder::new(shape) + } + /// Access the edges that this cycle refers to /// /// This is a convenience method that saves the caller from dealing with the