Merge pull request #1310 from hannobraun/partial

Continue cleanup of partial object API
This commit is contained in:
Hanno Braun 2022-11-04 16:07:09 +01:00 committed by GitHub
commit edcd61ffaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 306 additions and 138 deletions

View File

@ -92,8 +92,7 @@ mod tests {
.update_as_u_axis()
.build(&objects)?;
let half_edge = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([[1., -1.], [1., 1.]])
.update_as_line_segment_from_points(surface, [[1., -1.], [1., 1.]])
.build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -117,8 +116,10 @@ mod tests {
.update_as_u_axis()
.build(&objects)?;
let half_edge = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([[-1., -1.], [-1., 1.]])
.update_as_line_segment_from_points(
surface,
[[-1., -1.], [-1., 1.]],
)
.build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -142,8 +143,10 @@ mod tests {
.update_as_u_axis()
.build(&objects)?;
let half_edge = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([[-1., -1.], [1., -1.]])
.update_as_line_segment_from_points(
surface,
[[-1., -1.], [1., -1.]],
)
.build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);
@ -162,8 +165,7 @@ mod tests {
.update_as_u_axis()
.build(&objects)?;
let half_edge = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([[-1., 0.], [1., 0.]])
.update_as_line_segment_from_points(surface, [[-1., 0.], [1., 0.]])
.build(&objects)?;
let intersection = CurveEdgeIntersection::compute(&curve, &half_edge);

View File

@ -199,8 +199,10 @@ mod tests {
let objects = Objects::new();
let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let face =
@ -210,8 +212,10 @@ mod tests {
let surface = objects.surfaces.xz_plane();
let bottom = HalfEdge::partial()
.with_surface(Some(surface.clone()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
surface.clone(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let side_up = HalfEdge::partial()
.with_surface(Some(surface.clone()))

View File

@ -125,8 +125,10 @@ mod tests {
.array_windows_ext()
.map(|&[a, b]| {
let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([a, b])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[a, b],
)
.build(&objects)?;
(half_edge, Color::default()).sweep(UP, &objects)
})
@ -167,8 +169,10 @@ mod tests {
.array_windows_ext()
.map(|&[a, b]| {
let half_edge = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([a, b])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[a, b],
)
.build(&objects)?
.reverse(&objects)?;
(half_edge, Color::default()).sweep(DOWN, &objects)

View File

@ -191,8 +191,7 @@ mod tests {
(vertex, surface.clone()).sweep([0., 0., 1.], &objects)?;
let expected_half_edge = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([[0., 0.], [0., 1.]])
.update_as_line_segment_from_points(surface, [[0., 0.], [0., 1.]])
.build(&objects)?;
assert_eq!(half_edge, expected_half_edge);
Ok(())

View File

@ -12,22 +12,11 @@ impl TransformObject for PartialCycle {
transform: &Transform,
objects: &Objects,
) -> Result<Self, ValidationError> {
let surface = self
.surface()
.map(|surface| surface.transform(transform, objects))
.transpose()?;
let half_edges = self
.half_edges()
.map(|edge| {
Ok(edge
.into_partial()
.transform(transform, objects)?
.with_surface(surface.clone()))
})
.map(|edge| edge.into_partial().transform(transform, objects))
.collect::<Result<Vec<_>, ValidationError>>()?;
Ok(Self::default()
.with_surface(surface)
.with_half_edges(half_edges))
Ok(Self::default().with_half_edges(half_edges))
}
}

View File

@ -15,15 +15,10 @@ impl TransformObject for PartialHalfEdge {
transform: &Transform,
objects: &Objects,
) -> Result<Self, ValidationError> {
let surface = self
.surface()
.map(|surface| surface.transform(transform, objects))
.transpose()?;
let curve: MaybePartial<_> = self
.curve()
.into_partial()
.transform(transform, objects)?
.with_surface(surface.clone())
.into();
let vertices = self.vertices().try_map_ext(
|vertex| -> Result<_, ValidationError> {
@ -41,7 +36,6 @@ impl TransformObject for PartialHalfEdge {
.into();
Ok(Self::default()
.with_surface(surface)
.with_curve(Some(curve))
.with_vertices(Some(vertices))
.with_global_form(global_form))

View File

@ -1,8 +1,9 @@
use fj_math::Point;
use crate::{
objects::{Curve, HalfEdge, SurfaceVertex, Vertex},
objects::{Curve, HalfEdge, Surface, SurfaceVertex, Vertex},
partial::{HasPartial, MaybePartial, PartialCycle},
storage::Handle,
};
use super::{CurveBuilder, HalfEdgeBuilder};
@ -18,6 +19,7 @@ pub trait CycleBuilder {
/// Update the partial cycle with a polygonal chain from the provided points
fn with_poly_chain_from_points(
self,
surface: Handle<Surface>,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self;
@ -49,9 +51,8 @@ impl CycleBuilder for PartialCycle {
let mut half_edges = Vec::new();
for vertex_next in iter {
if let Some(vertex_prev) = previous {
let surface = self
let surface = vertex_prev
.surface()
.clone()
.expect("Need surface to extend cycle with poly-chain");
let position_prev = vertex_prev
@ -61,12 +62,8 @@ impl CycleBuilder for PartialCycle {
.position()
.expect("Need surface position to extend cycle");
let from = vertex_prev.update_partial(|partial| {
partial.with_surface(Some(surface.clone()))
});
let to = vertex_next.update_partial(|partial| {
partial.with_surface(Some(surface.clone()))
});
let from = vertex_prev;
let to = vertex_next;
previous = Some(to.clone());
@ -99,10 +96,13 @@ impl CycleBuilder for PartialCycle {
fn with_poly_chain_from_points(
self,
surface: Handle<Surface>,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self {
self.with_poly_chain(points.into_iter().map(|position| {
SurfaceVertex::partial().with_position(Some(position))
SurfaceVertex::partial()
.with_surface(Some(surface.clone()))
.with_position(Some(position))
}))
}
@ -127,8 +127,7 @@ impl CycleBuilder for PartialCycle {
self.with_half_edges(Some(
HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points(vertices),
.update_as_line_segment_from_points(surface, vertices),
))
}
}

View File

@ -3,7 +3,7 @@ use fj_math::{Point, Scalar};
use crate::{
objects::{
Curve, GlobalVertex, Objects, SurfaceVertex, Vertex,
Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex,
VerticesInNormalizedOrder,
},
partial::{HasPartial, PartialGlobalEdge, PartialHalfEdge},
@ -31,6 +31,7 @@ pub trait HalfEdgeBuilder: Sized {
/// Update partial half-edge as a line segment, from the given points
fn update_as_line_segment_from_points(
self,
surface: Handle<Surface>,
points: [impl Into<Point<2>>; 2],
) -> Self;
@ -44,9 +45,10 @@ impl HalfEdgeBuilder for PartialHalfEdge {
radius: impl Into<Scalar>,
objects: &Objects,
) -> Result<Self, ValidationError> {
let curve = Curve::partial()
let curve = self
.curve()
.into_partial()
.with_global_form(Some(self.extract_global_curve()))
.with_surface(self.surface())
.update_as_circle_from_radius(radius);
let path = curve.path().expect("Expected path that was just created");
@ -55,7 +57,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
[Scalar::ZERO, Scalar::TAU].map(|coord| Point::from([coord]));
let global_vertex = self
.extract_global_vertices()
.global_form()
.vertices()
.map(|[global_form, _]| global_form)
.unwrap_or_else(|| {
GlobalVertex::partial()
@ -65,7 +68,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
let surface_vertex = SurfaceVertex::partial()
.with_position(Some(path.point_from_path_coords(a_curve)))
.with_surface(self.surface())
.with_surface(curve.surface())
.with_global_form(Some(global_vertex))
.build(objects)?;
@ -83,18 +86,20 @@ impl HalfEdgeBuilder for PartialHalfEdge {
fn update_as_line_segment_from_points(
self,
surface: Handle<Surface>,
points: [impl Into<Point<2>>; 2],
) -> Self {
let surface = self.surface();
let vertices = points.map(|point| {
let surface_form = SurfaceVertex::partial()
.with_surface(surface.clone())
.with_surface(Some(surface.clone()))
.with_position(Some(point));
Vertex::partial().with_surface_form(Some(surface_form))
});
self.with_vertices(Some(vertices)).update_as_line_segment()
self.with_surface(Some(surface))
.with_vertices(Some(vertices))
.update_as_line_segment()
}
fn update_as_line_segment(self) -> Self {
@ -103,6 +108,7 @@ impl HalfEdgeBuilder for PartialHalfEdge {
[&from, &to].map(|vertex| vertex.surface_form());
let surface = self
.curve()
.surface()
.or_else(|| from_surface.surface())
.or_else(|| to_surface.surface())
@ -147,7 +153,8 @@ impl HalfEdgeBuilder for PartialHalfEdge {
must_switch_order
};
self.extract_global_vertices()
self.global_form()
.vertices()
.map(
|[a, b]| {
if must_switch_order {

View File

@ -50,10 +50,14 @@ impl<'a> FaceBuilder<'a> {
mut self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to create polygon");
self.exterior = Some(
Cycle::partial()
.with_surface(self.surface.clone())
.with_poly_chain_from_points(points)
.with_poly_chain_from_points(surface.clone(), points)
.close_with_line_segment()
.build(self.objects)
.unwrap(),
@ -75,10 +79,14 @@ impl<'a> FaceBuilder<'a> {
mut self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to build polygon.");
self.interiors.push(
Cycle::partial()
.with_surface(self.surface.clone())
.with_poly_chain_from_points(points)
.with_poly_chain_from_points(surface.clone(), points)
.close_with_line_segment()
.build(self.objects)
.unwrap(),

View File

@ -89,12 +89,11 @@ impl<'a> ShellBuilder<'a> {
.zip(&surfaces)
.map(|(half_edge, surface)| {
HalfEdge::partial()
.with_surface(Some(surface.clone()))
.with_global_form(Some(half_edge.global_form().clone()))
.update_as_line_segment_from_points([
[Z, Z],
[edge_length, Z],
])
.update_as_line_segment_from_points(
surface.clone(),
[[Z, Z], [edge_length, Z]],
)
.build(self.objects)
.unwrap()
})
@ -188,10 +187,8 @@ impl<'a> ShellBuilder<'a> {
.zip(sides_up)
.zip(tops.clone())
.zip(sides_down)
.zip(surfaces)
.map(|((((bottom, side_up), top), side_down), surface)| {
.map(|(((bottom, side_up), top), side_down)| {
let cycle = Cycle::partial()
.with_surface(Some(surface))
.with_half_edges([bottom, side_up, top, side_down])
.build(self.objects)
.unwrap();

View File

@ -399,8 +399,10 @@ mod tests {
let surface = objects.surfaces.xy_plane();
let object = Cycle::partial()
.with_surface(Some(surface))
.with_poly_chain_from_points([[0., 0.], [1., 0.], [0., 1.]])
.with_poly_chain_from_points(
surface,
[[0., 0.], [1., 0.], [0., 1.]],
)
.close_with_line_segment()
.build(&objects);
@ -485,8 +487,10 @@ mod tests {
let objects = Objects::new();
let object = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects);
assert_eq!(1, object.curve_iter().count());

View File

@ -165,12 +165,10 @@ mod tests {
let b = [1., 0.];
let a_to_b = HalfEdge::partial()
.with_surface(Some(surface.clone()))
.update_as_line_segment_from_points([a, b])
.update_as_line_segment_from_points(surface.clone(), [a, b])
.build(&objects)?;
let b_to_a = HalfEdge::partial()
.with_surface(Some(surface))
.update_as_line_segment_from_points([b, a])
.update_as_line_segment_from_points(surface, [b, a])
.build(&objects)?;
assert_eq!(a_to_b.global_form(), b_to_a.global_form());

View File

@ -64,6 +64,20 @@ impl<T: HasPartial> MaybePartial<T> {
}
}
/// Merge this `MaybePartial` with another of the same type
pub fn merge_with(self, other: Self) -> Self {
match (self, other) {
(Self::Full(_), Self::Full(_)) => {
panic!("Can't merge two full objects")
}
(Self::Full(full), Self::Partial(_))
| (Self::Partial(_), Self::Full(full)) => Self::Full(full),
(Self::Partial(a), Self::Partial(b)) => {
Self::Partial(a.merge_with(b))
}
}
}
/// Return or build a full object
///
/// If this already is a full object, it is returned. If this is a partial
@ -160,6 +174,14 @@ impl MaybePartial<GlobalEdge> {
}
impl MaybePartial<HalfEdge> {
/// Access the curve
pub fn curve(&self) -> MaybePartial<Curve> {
match self {
Self::Full(full) => full.curve().clone().into(),
Self::Partial(partial) => partial.curve(),
}
}
/// Access the front vertex
pub fn front(&self) -> MaybePartial<Vertex> {
match self {

View File

@ -37,6 +37,7 @@
mod maybe_partial;
mod objects;
mod traits;
mod util;
pub use self::{
maybe_partial::MaybePartial,

View File

@ -1,6 +1,6 @@
use crate::{
objects::{Curve, GlobalCurve, Objects, Surface},
partial::MaybePartial,
partial::{util::merge_options, MaybePartial},
path::SurfacePath,
storage::Handle,
validate::ValidationError,
@ -59,6 +59,26 @@ impl PartialCurve {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
// This is harder than it should be, as `global_form` uses the redundant
// `Option<MaybePartial<_>>` representation. There's some code relying
// on that though, so we have to live with it for now.
let global_form = match (self.global_form, other.global_form) {
(Some(a), Some(b)) => Some(a.merge_with(b)),
(Some(global_form), None) | (None, Some(global_form)) => {
Some(global_form)
}
(None, None) => None,
};
Self {
path: merge_options(self.path, other.path),
surface: merge_options(self.surface, other.surface),
global_form,
}
}
/// Build a full [`Curve`] from the partial curve
pub fn build(
self,
@ -102,6 +122,11 @@ impl From<&Curve> for PartialCurve {
pub struct PartialGlobalCurve;
impl PartialGlobalCurve {
/// Merge this partial object with another
pub fn merge_with(self, _: Self) -> Self {
Self
}
/// Build a full [`GlobalCurve`] from the partial global curve
pub fn build(
self,

View File

@ -1,6 +1,6 @@
use crate::{
objects::{Cycle, HalfEdge, Objects, Surface},
partial::MaybePartial,
partial::{util::merge_options, MaybePartial},
storage::Handle,
validate::ValidationError,
};
@ -10,46 +10,80 @@ use crate::{
/// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default)]
pub struct PartialCycle {
surface: Option<Handle<Surface>>,
half_edges: Vec<MaybePartial<HalfEdge>>,
}
impl PartialCycle {
/// Access the surface that the [`Cycle`] is defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.surface.clone()
}
/// Access the half-edges that make up the [`Cycle`]
pub fn half_edges(&self) -> impl Iterator<Item = MaybePartial<HalfEdge>> {
self.half_edges.clone().into_iter()
}
/// Update the partial cycle with the given surface
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self {
if let Some(surface) = surface {
self.surface = Some(surface);
}
self
/// Access the surface that the [`Cycle`]'s [`HalfEdge`]s are defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.half_edges
.first()
.and_then(|half_edge| half_edge.curve().surface())
}
/// Update the partial cycle with the given half-edges
/// Add the provided half-edges to the partial cycle
///
/// This will merge all the surfaces of the added half-edges. All added
/// half-edges will end up with the same merged surface.
///
/// # Panics
///
/// Panics, if the surfaces can't be merged.
pub fn with_half_edges(
mut self,
half_edges: impl IntoIterator<Item = impl Into<MaybePartial<HalfEdge>>>,
) -> Self {
self.half_edges
.extend(half_edges.into_iter().map(Into::into));
let half_edges = half_edges.into_iter().map(Into::into);
let mut surface = self.surface();
for half_edge in half_edges {
surface = merge_options(surface, half_edge.curve().surface());
self.half_edges.push(half_edge);
}
self.with_surface(surface)
}
/// Update the partial cycle with the provided surface
///
/// All [`HalfEdge`]s will be updated with this surface.
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self {
if let Some(surface) = surface {
for half_edge in &mut self.half_edges {
*half_edge = half_edge.clone().update_partial(|half_edge| {
half_edge.with_surface(Some(surface.clone()))
});
}
}
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
let a_is_empty = self.half_edges.is_empty();
let b_is_empty = other.half_edges.is_empty();
let half_edges = match (a_is_empty, b_is_empty) {
(true, true) => {
panic!("Can't merge `PartialHalfEdge`, if both have half-edges")
}
(true, false) => self.half_edges,
(false, true) => other.half_edges,
(false, false) => self.half_edges, // doesn't matter which we use
};
Self { half_edges }
}
/// Build a full [`Cycle`] from the partial cycle
pub fn build(
mut self,
objects: &Objects,
) -> Result<Handle<Cycle>, ValidationError> {
let surface = self.surface.expect("Need surface to build `Cycle`");
let surface_for_edges = surface.clone();
let half_edges = {
let last_vertex = self
.half_edges
@ -65,11 +99,7 @@ impl PartialCycle {
.map(|(half_edge, vertex, surface_vertex)|
-> Result<_, ValidationError>
{
let surface_vertex = surface_vertex
.update_partial(|surface_vertex| {
surface_vertex.with_surface(Some(surface.clone()))
})
.into_full(objects)?;
let surface_vertex = surface_vertex.into_full(objects)?;
*half_edge =
half_edge.clone().update_partial(|half_edge| {
@ -98,9 +128,7 @@ impl PartialCycle {
partial.with_surface_form(previous_vertex)
});
half_edge
.with_surface(Some(surface_for_edges.clone()))
.with_back_vertex(Some(back))
half_edge.with_back_vertex(Some(back))
})
.into_full(objects)?;
@ -121,7 +149,6 @@ impl PartialCycle {
impl From<&Cycle> for PartialCycle {
fn from(cycle: &Cycle) -> Self {
Self {
surface: Some(cycle.surface().clone()),
half_edges: cycle.half_edges().cloned().map(Into::into).collect(),
}
}

View File

@ -6,7 +6,7 @@ use crate::{
Curve, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
Surface, Vertex,
},
partial::MaybePartial,
partial::{util::merge_arrays, MaybePartial},
storage::Handle,
validate::ValidationError,
};
@ -16,18 +16,12 @@ use crate::{
/// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default)]
pub struct PartialHalfEdge {
surface: Option<Handle<Surface>>,
curve: MaybePartial<Curve>,
vertices: [MaybePartial<Vertex>; 2],
global_form: MaybePartial<GlobalEdge>,
}
impl PartialHalfEdge {
/// Access the surface that the [`HalfEdge`]'s [`Curve`] is defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.surface.clone()
}
/// Access the curve that the [`HalfEdge`] is defined in
pub fn curve(&self) -> MaybePartial<Curve> {
self.curve.clone()
@ -52,17 +46,24 @@ impl PartialHalfEdge {
.unwrap_or_else(|| self.global_form.curve())
}
/// Access the vertices of the global form, if available
pub fn extract_global_vertices(
&self,
) -> Option<[MaybePartial<GlobalVertex>; 2]> {
self.global_form.vertices()
}
/// Update the partial half-edge with the given surface
pub fn with_surface(mut self, surface: Option<Handle<Surface>>) -> Self {
if let Some(surface) = surface {
self.surface = Some(surface);
self.curve = self.curve.update_partial(|curve| {
curve.with_surface(Some(surface.clone()))
});
self.vertices = self.vertices.map(|vertex| {
vertex.update_partial(|vertex| {
let surface_form = vertex.surface_form().update_partial(
|surface_vertex| {
surface_vertex.with_surface(Some(surface.clone()))
},
);
vertex.with_surface_form(Some(surface_form))
})
});
}
self
}
@ -78,7 +79,7 @@ impl PartialHalfEdge {
self
}
/// Update the partial half-edge with the given from vertex
/// Update the partial half-edge with the given back vertex
pub fn with_back_vertex(
mut self,
vertex: Option<impl Into<MaybePartial<Vertex>>>,
@ -90,7 +91,7 @@ impl PartialHalfEdge {
self
}
/// Update the partial half-edge with the given from vertex
/// Update the partial half-edge with the given front vertex
pub fn with_front_vertex(
mut self,
vertex: Option<impl Into<MaybePartial<Vertex>>>,
@ -125,16 +126,21 @@ impl PartialHalfEdge {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
curve: self.curve.merge_with(other.curve),
vertices: merge_arrays(self.vertices, other.vertices),
global_form: self.global_form.merge_with(other.global_form),
}
}
/// Build a full [`HalfEdge`] from the partial half-edge
pub fn build(
self,
objects: &Objects,
) -> Result<Handle<HalfEdge>, ValidationError> {
let surface = self.surface;
let curve = self
.curve
.update_partial(|curve| curve.with_surface(surface))
.into_full(objects)?;
let curve = self.curve.into_full(objects)?;
let vertices = self.vertices.try_map_ext(|vertex| {
vertex
.update_partial(|vertex| vertex.with_curve(Some(curve.clone())))
@ -160,7 +166,6 @@ impl From<&HalfEdge> for PartialHalfEdge {
half_edge.vertices().clone().map(Into::into);
Self {
surface: Some(half_edge.curve().surface().clone()),
curve: half_edge.curve().clone().into(),
vertices: [back_vertex, front_vertex],
global_form: half_edge.global_form().clone().into(),
@ -210,6 +215,23 @@ impl PartialGlobalEdge {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
// This is harder than it needs to be, because `vertices` uses the
// redundant combination of `Option` and `MaybePartial`. There's some
// code relying on that, however, so we have to live with it for now.
let vertices = match (self.vertices, other.vertices) {
(Some(a), Some(b)) => Some(merge_arrays(a, b)),
(Some(vertices), None) | (None, Some(vertices)) => Some(vertices),
(None, None) => None,
};
Self {
curve: self.curve.merge_with(other.curve),
vertices,
}
}
/// Build a full [`GlobalEdge`] from the partial global edge
pub fn build(
self,

View File

@ -27,6 +27,10 @@ macro_rules! impl_traits {
impl Partial for $partial {
type Full = $full;
fn merge_with(self, other: Self) -> Self {
self.merge_with(other)
}
fn build(self, objects: &Objects)
-> Result<
Handle<Self::Full>,

View File

@ -3,7 +3,7 @@ use fj_math::Point;
use crate::{
builder::GlobalVertexBuilder,
objects::{Curve, GlobalVertex, Objects, Surface, SurfaceVertex, Vertex},
partial::MaybePartial,
partial::{util::merge_options, MaybePartial},
storage::Handle,
validate::ValidationError,
};
@ -67,6 +67,15 @@ impl PartialVertex {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
curve: self.curve.merge_with(other.curve),
surface_form: self.surface_form.merge_with(other.surface_form),
}
}
/// Build a full [`Vertex`] from the partial vertex
///
/// # Panics
@ -170,6 +179,15 @@ impl PartialSurfaceVertex {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
surface: merge_options(self.surface, other.surface),
global_form: self.global_form.merge_with(other.global_form),
}
}
/// Build a full [`SurfaceVertex`] from the partial surface vertex
pub fn build(
self,
@ -232,6 +250,13 @@ impl PartialGlobalVertex {
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
Self {
position: merge_options(self.position, other.position),
}
}
/// Build a full [`GlobalVertex`] from the partial global vertex
pub fn build(
self,

View File

@ -68,6 +68,9 @@ pub trait Partial: Default + for<'a> From<&'a Self::Full> {
/// The type representing the full variant of this object
type Full;
/// Merge another partial object of the same type into this one
fn merge_with(self, other: Self) -> Self;
/// Build a full object from this partial one
///
/// Implementations of this method will typically try to infer any missing

View File

@ -0,0 +1,26 @@
use fj_interop::ext::ArrayExt;
use super::{HasPartial, MaybePartial};
pub fn merge_options<T>(a: Option<T>, b: Option<T>) -> Option<T>
where
T: Eq,
{
if a == b {
return a;
}
// We know that `a != b`, or we wouldn't have made it here.
if a.is_some() && b.is_some() {
panic!("Can't merge `Option`s if both are defined");
}
a.xor(b)
}
pub fn merge_arrays<T: HasPartial>(
a: [MaybePartial<T>; 2],
b: [MaybePartial<T>; 2],
) -> [MaybePartial<T>; 2] {
a.zip_ext(b).map(|(a, b)| a.merge_with(b))
}

View File

@ -207,8 +207,10 @@ mod tests {
let objects = Objects::new();
let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let invalid = {
let mut vertices = valid.vertices().clone();
@ -232,8 +234,10 @@ mod tests {
let objects = Objects::new();
let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let invalid = HalfEdge::new(
valid.vertices().clone(),
@ -255,8 +259,10 @@ mod tests {
let objects = Objects::new();
let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let invalid = HalfEdge::new(
valid.vertices().clone(),
@ -285,8 +291,10 @@ mod tests {
let objects = Objects::new();
let valid = HalfEdge::partial()
.with_surface(Some(objects.surfaces.xy_plane()))
.update_as_line_segment_from_points([[0., 0.], [1., 0.]])
.update_as_line_segment_from_points(
objects.surfaces.xy_plane(),
[[0., 0.], [1., 0.]],
)
.build(&objects)?;
let invalid = HalfEdge::new(
valid.vertices().clone().try_map_ext(|vertex| {