diff --git a/crates/fj-core/src/algorithms/sweep/edge.rs b/crates/fj-core/src/algorithms/sweep/edge.rs index 0336d2c2d..5428d29a9 100644 --- a/crates/fj-core/src/algorithms/sweep/edge.rs +++ b/crates/fj-core/src/algorithms/sweep/edge.rs @@ -86,10 +86,10 @@ impl Sweep for (&Edge, &Handle, &Surface, Option) { Some(boundary), services, ) - .replace_start_vertex(start_vertex); + .update_start_vertex(|_| start_vertex); let edge = if let Some(curve) = curve { - edge.replace_curve(curve) + edge.update_curve(|_| curve) } else { edge }; diff --git a/crates/fj-core/src/objects/handles.rs b/crates/fj-core/src/objects/handles.rs index a2ed0c6f1..1114a8f86 100644 --- a/crates/fj-core/src/objects/handles.rs +++ b/crates/fj-core/src/objects/handles.rs @@ -1,4 +1,4 @@ -use std::{collections::BTreeSet, fmt::Debug, slice}; +use std::{collections::BTreeSet, fmt::Debug, slice, vec}; use itertools::Itertools; @@ -98,6 +98,40 @@ impl Handles { pub fn pairs(&self) -> impl Iterator, &Handle)> { self.iter().circular_tuple_windows() } + + /// Create a new instance in which the provided item is updated + /// + /// # Panics + /// + /// Panics, if the provided item is not present. + /// Panics, if the update results in a duplicate item. + #[must_use] + pub fn update( + &self, + handle: &Handle, + update: impl FnOnce(&Handle) -> Handle, + ) -> Self + where + T: Debug + Ord, + { + let mut updated = Some(update(handle)); + + let items = self.iter().map(|h| { + if h.id() == handle.id() { + updated + .take() + .expect("`Handles` should not contain same item twice") + } else { + h.clone() + } + }); + + let handles = items.collect(); + + assert!(updated.is_none(), "Edge not found in cycle"); + + handles + } } impl FromIterator> for Handles @@ -109,6 +143,15 @@ where } } +impl IntoIterator for Handles { + type Item = Handle; + type IntoIter = vec::IntoIter>; + + fn into_iter(self) -> Self::IntoIter { + self.inner.into_iter() + } +} + impl<'r, T> IntoIterator for &'r Handles { // You might wonder why we're returning references to handles here, when // `Handle` already is kind of reference, and easily cloned. diff --git a/crates/fj-core/src/operations/join/cycle.rs b/crates/fj-core/src/operations/join/cycle.rs index c58aa4d60..4ecd16c0a 100644 --- a/crates/fj-core/src/operations/join/cycle.rs +++ b/crates/fj-core/src/operations/join/cycle.rs @@ -84,8 +84,8 @@ impl JoinCycle for Cycle { self.add_edges(edges.into_iter().circular_tuple_windows().map( |((prev, _, _), (edge, curve, boundary))| { Edge::unjoined(curve, boundary, services) - .replace_curve(edge.curve().clone()) - .replace_start_vertex(prev.start_vertex().clone()) + .update_curve(|_| edge.curve().clone()) + .update_start_vertex(|_| prev.start_vertex().clone()) .insert(services) }, )) @@ -111,20 +111,20 @@ impl JoinCycle for Cycle { cycle .update_edge(self.edges().nth_circular(index), |edge| { - edge.replace_curve(edge_other.curve().clone()) - .replace_start_vertex( + edge.update_curve(|_| edge_other.curve().clone()) + .update_start_vertex(|_| { other .edges() .nth_circular(index_other + 1) .start_vertex() - .clone(), - ) + .clone() + }) .insert(services) }) .update_edge(self.edges().nth_circular(index + 1), |edge| { - edge.replace_start_vertex( - edge_other.start_vertex().clone(), - ) + edge.update_start_vertex(|_| { + edge_other.start_vertex().clone() + }) .insert(services) }) }, diff --git a/crates/fj-core/src/operations/update/cycle.rs b/crates/fj-core/src/operations/update/cycle.rs index 03567c942..3a67bf54b 100644 --- a/crates/fj-core/src/operations/update/cycle.rs +++ b/crates/fj-core/src/operations/update/cycle.rs @@ -10,6 +10,12 @@ pub trait UpdateCycle { fn add_edges(&self, edges: impl IntoIterator>) -> Self; /// Update the provided edge + /// + /// # Panics + /// + /// Uses [`Handles::update`] internally, and panics for the same reasons. + /// + /// [`Handles::update`]: crate::objects::Handles::update #[must_use] fn update_edge( &self, @@ -29,18 +35,7 @@ impl UpdateCycle for Cycle { edge: &Handle, update: impl FnOnce(&Handle) -> Handle, ) -> Self { - let mut updated = Some(update(edge)); - - let edges = self.edges().iter().map(|e| { - if e.id() == edge.id() { - updated - .take() - .expect("Cycle should not contain same edge twice") - } else { - e.clone() - } - }); - + let edges = self.edges().update(edge, update); Cycle::new(edges) } } diff --git a/crates/fj-core/src/operations/update/edge.rs b/crates/fj-core/src/operations/update/edge.rs index 2db76fec5..b1b180def 100644 --- a/crates/fj-core/src/operations/update/edge.rs +++ b/crates/fj-core/src/operations/update/edge.rs @@ -8,57 +8,81 @@ use crate::{ /// Update a [`Edge`] pub trait UpdateEdge { - /// Replace the path of the edge + /// Update the path of the edge #[must_use] - fn replace_path(&self, path: SurfacePath) -> Self; + fn update_path( + &self, + update: impl FnOnce(SurfacePath) -> SurfacePath, + ) -> Self; - /// Replace the boundary of the edge + /// Update the boundary of the edge #[must_use] - fn replace_boundary(&self, boundary: CurveBoundary>) -> Self; + fn update_boundary( + &self, + update: impl FnOnce(CurveBoundary>) -> CurveBoundary>, + ) -> Self; - /// Replace the curve of the edge + /// Update the curve of the edge #[must_use] - fn replace_curve(&self, curve: Handle) -> Self; + fn update_curve( + &self, + update: impl FnOnce(&Handle) -> Handle, + ) -> Self; - /// Replace the start vertex of the edge + /// Update the start vertex of the edge #[must_use] - fn replace_start_vertex(&self, start_vertex: Handle) -> Self; + fn update_start_vertex( + &self, + update: impl FnOnce(&Handle) -> Handle, + ) -> Self; } impl UpdateEdge for Edge { - fn replace_path(&self, path: SurfacePath) -> Self { + fn update_path( + &self, + update: impl FnOnce(SurfacePath) -> SurfacePath, + ) -> Self { Edge::new( - path, + update(self.path()), self.boundary(), self.curve().clone(), self.start_vertex().clone(), ) } - fn replace_boundary(&self, boundary: CurveBoundary>) -> Self { + fn update_boundary( + &self, + update: impl FnOnce(CurveBoundary>) -> CurveBoundary>, + ) -> Self { Edge::new( self.path(), - boundary, + update(self.boundary()), self.curve().clone(), self.start_vertex().clone(), ) } - fn replace_curve(&self, curve: Handle) -> Self { + fn update_curve( + &self, + update: impl FnOnce(&Handle) -> Handle, + ) -> Self { Edge::new( self.path(), self.boundary(), - curve, + update(self.curve()), self.start_vertex().clone(), ) } - fn replace_start_vertex(&self, start_vertex: Handle) -> Self { + fn update_start_vertex( + &self, + update: impl FnOnce(&Handle) -> Handle, + ) -> Self { Edge::new( self.path(), self.boundary(), self.curve().clone(), - start_vertex, + update(self.start_vertex()), ) } } diff --git a/crates/fj-core/src/operations/update/face.rs b/crates/fj-core/src/operations/update/face.rs index 4950e0096..b61717da7 100644 --- a/crates/fj-core/src/operations/update/face.rs +++ b/crates/fj-core/src/operations/update/face.rs @@ -6,20 +6,20 @@ use crate::{ /// Update a [`Face`] pub trait UpdateFace { - /// Replace the region of the face + /// Update the region of the face #[must_use] fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self; } impl UpdateFace for Face { fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self { - let region = f(self.region()); + let region = update(self.region()); Face::new(self.surface().clone(), region) } } @@ -27,8 +27,8 @@ impl UpdateFace for Face { impl UpdateFace for Polygon { fn update_region( &self, - f: impl FnOnce(&Handle) -> Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self { - self.replace_face(self.face.update_region(f)) + self.replace_face(self.face.update_region(update)) } } diff --git a/crates/fj-core/src/operations/update/region.rs b/crates/fj-core/src/operations/update/region.rs index f32fd4b23..99813797d 100644 --- a/crates/fj-core/src/operations/update/region.rs +++ b/crates/fj-core/src/operations/update/region.rs @@ -9,10 +9,10 @@ pub trait UpdateRegion { #[must_use] fn update_exterior( &self, - f: impl FnOnce(&Handle) -> Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self; - /// Add the provides interiors to the region + /// Add the provided interiors to the region #[must_use] fn add_interiors( &self, @@ -23,9 +23,9 @@ pub trait UpdateRegion { impl UpdateRegion for Region { fn update_exterior( &self, - f: impl FnOnce(&Handle) -> Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self { - let exterior = f(self.exterior()); + let exterior = update(self.exterior()); Region::new(exterior, self.interiors().iter().cloned(), self.color()) } diff --git a/crates/fj-core/src/operations/update/shell.rs b/crates/fj-core/src/operations/update/shell.rs index eb7d78928..95118ab00 100644 --- a/crates/fj-core/src/operations/update/shell.rs +++ b/crates/fj-core/src/operations/update/shell.rs @@ -9,12 +9,18 @@ pub trait UpdateShell { #[must_use] fn add_faces(&self, faces: impl IntoIterator>) -> Self; - /// Replace a face of the shell + /// Update a face of the shell + /// + /// # Panics + /// + /// Uses [`Handles::update`] internally, and panics for the same reasons. + /// + /// [`Handles::update`]: crate::objects::Handles::update #[must_use] - fn replace_face( + fn update_face( &self, - original: &Handle, - replacement: Handle, + face: &Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self; /// Remove a face from the shell @@ -28,19 +34,12 @@ impl UpdateShell for Shell { Shell::new(faces) } - fn replace_face( + fn update_face( &self, - original: &Handle, - replacement: Handle, + face: &Handle, + update: impl FnOnce(&Handle) -> Handle, ) -> Self { - let faces = self.faces().iter().map(|face| { - if face.id() == original.id() { - replacement.clone() - } else { - face.clone() - } - }); - + let faces = self.faces().update(face, update); Shell::new(faces) } diff --git a/crates/fj-core/src/validate/shell.rs b/crates/fj-core/src/validate/shell.rs index 143b35c92..58be7983d 100644 --- a/crates/fj-core/src/validate/shell.rs +++ b/crates/fj-core/src/validate/shell.rs @@ -391,31 +391,27 @@ mod tests { [[0., 0., 0.], [0., 1., 0.], [1., 0., 0.], [0., 0., 1.]], &mut services, ); - let invalid = valid.shell.replace_face( - &valid.abc.face, - valid - .abc - .face - .update_region(|region| { - region - .update_exterior(|cycle| { - cycle - .update_edge( - cycle.edges().nth_circular(0), - |edge| { - edge.replace_path(edge.path().reverse()) - .replace_boundary( - edge.boundary().reverse(), - ) - .insert(&mut services) - }, - ) - .insert(&mut services) - }) - .insert(&mut services) - }) - .insert(&mut services), - ); + let invalid = valid.shell.update_face(&valid.abc.face, |face| { + face.update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .update_edge( + cycle.edges().nth_circular(0), + |edge| { + edge.update_path(|path| path.reverse()) + .update_boundary(|boundary| { + boundary.reverse() + }) + .insert(&mut services) + }, + ) + .insert(&mut services) + }) + .insert(&mut services) + }) + .insert(&mut services) + }); valid.shell.validate_and_return_first_error()?; assert_contains_err!( @@ -436,31 +432,26 @@ mod tests { [[0., 0., 0.], [0., 1., 0.], [1., 0., 0.], [0., 0., 1.]], &mut services, ); - let invalid = valid.shell.replace_face( - &valid.abc.face, - valid - .abc - .face - .update_region(|region| { - region - .update_exterior(|cycle| { - cycle - .update_edge( - cycle.edges().nth_circular(0), - |edge| { - let curve = - Curve::new().insert(&mut services); - - edge.replace_curve(curve) - .insert(&mut services) - }, - ) - .insert(&mut services) - }) - .insert(&mut services) - }) - .insert(&mut services), - ); + let invalid = valid.shell.update_face(&valid.abc.face, |face| { + face.update_region(|region| { + region + .update_exterior(|cycle| { + cycle + .update_edge( + cycle.edges().nth_circular(0), + |edge| { + edge.update_curve(|_| { + Curve::new().insert(&mut services) + }) + .insert(&mut services) + }, + ) + .insert(&mut services) + }) + .insert(&mut services) + }) + .insert(&mut services) + }); valid.shell.validate_and_return_first_error()?; assert_contains_err!( @@ -499,10 +490,9 @@ mod tests { [[0., 0., 0.], [0., 1., 0.], [1., 0., 0.], [0., 0., 1.]], &mut services, ); - let invalid = valid.shell.replace_face( - &valid.abc.face, - valid.abc.face.reverse(&mut services).insert(&mut services), - ); + let invalid = valid.shell.update_face(&valid.abc.face, |face| { + face.reverse(&mut services).insert(&mut services) + }); valid.shell.validate_and_return_first_error()?; assert_contains_err!(