Merge pull request #1331 from hannobraun/partial

Integrate `Face` into partial object API
This commit is contained in:
Hanno Braun 2022-11-09 14:54:29 +01:00 committed by GitHub
commit d9bef00c12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 309 additions and 222 deletions

View File

@ -150,7 +150,7 @@ where
#[cfg(test)]
mod tests {
use crate::{
builder::CurveBuilder,
builder::{CurveBuilder, FaceBuilder},
objects::{Curve, Face, Objects},
partial::HasPartial,
};
@ -183,11 +183,11 @@ mod tests {
[ 1., -1.],
];
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points(exterior)
.with_interior_polygon_from_points(interior)
.build();
.build(&objects)?;
let expected =
CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]);

View File

@ -67,7 +67,7 @@ mod tests {
use crate::{
algorithms::intersect::CurveFaceIntersection,
builder::CurveBuilder,
builder::{CurveBuilder, FaceBuilder},
objects::{Curve, Face, Objects},
partial::HasPartial,
};
@ -86,12 +86,12 @@ mod tests {
[1., 2.],
];
let [a, b] = [objects.surfaces.xy_plane(), objects.surfaces.xz_plane()]
.map(|surface| {
Face::builder(&objects)
.try_map_ext(|surface| {
Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points(points)
.build()
});
.build(&objects)
})?;
let intersection = FaceFaceIntersection::compute([&a, &b], &objects)?;
@ -113,12 +113,12 @@ mod tests {
];
let surfaces =
[objects.surfaces.xy_plane(), objects.surfaces.xz_plane()];
let [a, b] = surfaces.clone().map(|surface| {
Face::builder(&objects)
let [a, b] = surfaces.clone().try_map_ext(|surface| {
Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points(points)
.build()
});
.build(&objects)
})?;
let intersection = FaceFaceIntersection::compute([&a, &b], &objects)?;

View File

@ -136,34 +136,38 @@ mod tests {
use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect},
builder::FaceBuilder,
iter::ObjectIters,
objects::{Face, Objects},
partial::HasPartial,
};
#[test]
fn point_is_outside_face() {
fn point_is_outside_face() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 1.], [0., 2.]])
.build();
.build(&objects)?;
let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(intersection, None);
Ok(())
}
#[test]
fn ray_hits_vertex_while_passing_outside() {
fn ray_hits_vertex_while_passing_outside() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [2., 1.], [0., 2.]])
.build();
.build(&objects)?;
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -171,17 +175,19 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
Ok(())
}
#[test]
fn ray_hits_vertex_at_cycle_seam() {
fn ray_hits_vertex_at_cycle_seam() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[4., 2.], [0., 4.], [0., 0.]])
.build();
.build(&objects)?;
let point = Point::from([1., 2.]);
let intersection = (&face, &point).intersect();
@ -189,14 +195,16 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
Ok(())
}
#[test]
fn ray_hits_vertex_while_staying_inside() {
fn ray_hits_vertex_while_staying_inside() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[0., 0.],
@ -204,7 +212,7 @@ mod tests {
[3., 0.],
[3., 4.],
])
.build();
.build(&objects)?;
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -212,14 +220,17 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
Ok(())
}
#[test]
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() -> anyhow::Result<()>
{
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[0., 0.],
@ -227,7 +238,7 @@ mod tests {
[3., 1.],
[0., 2.],
])
.build();
.build(&objects)?;
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -235,14 +246,17 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
Ok(())
}
#[test]
fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
fn ray_hits_parallel_edge_and_does_not_leave_face_there(
) -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[0., 0.],
@ -251,7 +265,7 @@ mod tests {
[4., 0.],
[4., 5.],
])
.build();
.build(&objects)?;
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -259,17 +273,19 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
Ok(())
}
#[test]
fn point_is_coincident_with_edge() {
fn point_is_coincident_with_edge() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [2., 0.], [0., 1.]])
.build();
.build(&objects)?;
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
@ -286,17 +302,19 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
);
Ok(())
}
#[test]
fn point_is_coincident_with_vertex() {
fn point_is_coincident_with_vertex() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]])
.build();
.build(&objects)?;
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
@ -311,5 +329,7 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex.clone()))
);
Ok(())
}
}

View File

@ -152,8 +152,10 @@ mod tests {
},
transform::TransformObject,
},
builder::FaceBuilder,
iter::ObjectIters,
objects::{Face, Objects},
partial::HasPartial,
};
#[test]
@ -163,7 +165,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.yz_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -171,7 +173,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([-1., 0., 0.], &objects)?;
assert_eq!((&ray, &face).intersect(), None);
@ -185,7 +187,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.yz_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -193,7 +195,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([1., 0., 0.], &objects)?;
assert_eq!(
@ -210,7 +212,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.yz_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -218,7 +220,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([0., 0., 2.], &objects)?;
assert_eq!((&ray, &face).intersect(), None);
@ -232,7 +234,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.yz_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -240,7 +242,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([1., 1., 0.], &objects)?;
let edge = face
@ -265,7 +267,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.yz_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -273,7 +275,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([1., 1., 1.], &objects)?;
let vertex = face
@ -290,13 +292,13 @@ mod tests {
}
#[test]
fn ray_is_parallel_to_surface_and_hits() {
fn ray_is_parallel_to_surface_and_hits() -> anyhow::Result<()> {
let objects = Objects::new();
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -304,12 +306,14 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build();
.build(&objects)?;
assert_eq!(
(&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsFaceAndAreParallel)
)
);
Ok(())
}
#[test]
@ -319,7 +323,7 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-1., -1.],
@ -327,7 +331,7 @@ mod tests {
[1., 1.],
[-1., 1.],
])
.build()
.build(&objects)?
.translate([0., 0., 1.], &objects)?;
assert_eq!((&ray, &face).intersect(), None);

View File

@ -1,5 +1,6 @@
use crate::{
objects::{Face, Objects},
partial::HasPartial,
storage::Handle,
validate::ValidationError,
};
@ -14,10 +15,10 @@ impl Reverse for Handle<Face> {
.map(|cycle| cycle.clone().reverse(objects))
.collect::<Result<Vec<_>, _>>()?;
Ok(Face::builder(objects)
Face::partial()
.with_exterior(exterior)
.with_interiors(interiors)
.with_color(self.color())
.build())
.build(objects)
}
}

View File

@ -7,6 +7,7 @@ use crate::{
Curve, Cycle, Face, GlobalEdge, HalfEdge, Objects, SurfaceVertex,
Vertex,
},
partial::HasPartial,
path::SurfacePath,
storage::Handle,
validate::ValidationError,
@ -175,10 +176,10 @@ impl Sweep for (Handle<HalfEdge>, Color) {
objects.cycles.insert(Cycle::new(edges))?
};
Ok(Face::builder(objects)
Face::partial()
.with_exterior(cycle)
.with_color(color)
.build())
.build(objects)
}
}
@ -256,7 +257,7 @@ mod tests {
.cycles
.insert(Cycle::new([bottom, side_up, top, side_down]))?;
Face::builder(&objects).with_exterior(cycle).build()
Face::partial().with_exterior(cycle).build(&objects)?
};
assert_eq!(face, expected_face);

View File

@ -84,7 +84,7 @@ mod tests {
use crate::{
algorithms::{reverse::Reverse, transform::TransformObject},
builder::HalfEdgeBuilder,
builder::{FaceBuilder, HalfEdgeBuilder},
objects::{Face, HalfEdge, Objects, Sketch},
partial::HasPartial,
};
@ -107,15 +107,15 @@ mod tests {
.build()
.sweep(UP, &objects)?;
let bottom = Face::builder(&objects)
let bottom = Face::partial()
.with_surface(surface.clone())
.with_exterior_polygon_from_points(TRIANGLE)
.build()
.build(&objects)?
.reverse(&objects)?;
let top = Face::builder(&objects)
let top = Face::partial()
.with_surface(surface.translate(UP, &objects)?)
.with_exterior_polygon_from_points(TRIANGLE)
.build();
.build(&objects)?;
assert!(solid.find_face(&bottom).is_some());
assert!(solid.find_face(&top).is_some());
@ -151,15 +151,15 @@ mod tests {
.build()
.sweep(DOWN, &objects)?;
let bottom = Face::builder(&objects)
let bottom = Face::partial()
.with_surface(surface.clone().translate(DOWN, &objects)?)
.with_exterior_polygon_from_points(TRIANGLE)
.build()
.build(&objects)?
.reverse(&objects)?;
let top = Face::builder(&objects)
let top = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points(TRIANGLE)
.build();
.build(&objects)?;
assert!(solid.find_face(&bottom).is_some());
assert!(solid.find_face(&top).is_some());

View File

@ -2,44 +2,51 @@ use fj_math::Transform;
use crate::{
objects::{Face, FaceSet, Objects},
partial::HasPartial,
storage::Handle,
partial::{HasPartial, PartialFace},
validate::ValidationError,
};
use super::TransformObject;
impl TransformObject for Handle<Face> {
impl TransformObject for PartialFace {
fn transform(
self,
transform: &Transform,
objects: &Objects,
) -> Result<Self, ValidationError> {
let surface = self.surface().clone().transform(transform, objects)?;
let surface = self
.surface()
.map(|surface| surface.transform(transform, objects))
.transpose()?;
let exterior = self
.exterior()
.to_partial()
.into_partial()
.transform(transform, objects)?
.with_surface(Some(surface.clone()))
.build(objects)?;
.with_surface(surface.clone());
let interiors = self
.interiors()
.map(|cycle| -> Result<_, ValidationError> {
cycle
.to_partial()
.into_partial()
.transform(transform, objects)?
.with_surface(Some(surface.clone()))
.with_surface(surface.clone())
.build(objects)
})
.collect::<Result<Vec<_>, _>>()?;
let color = self.color();
Ok(Face::builder(objects)
let mut face = Face::partial()
.with_exterior(exterior)
.with_interiors(interiors)
.with_color(color)
.build())
.with_interiors(interiors);
if let Some(surface) = surface {
face = face.with_surface(surface);
}
if let Some(color) = color {
face = face.with_color(color);
}
Ok(face)
}
}

View File

@ -84,7 +84,9 @@ mod tests {
use crate::{
algorithms::approx::{Approx, Tolerance},
builder::FaceBuilder,
objects::{Face, Objects},
partial::HasPartial,
storage::Handle,
};
@ -100,10 +102,10 @@ mod tests {
let d = [0., 1.];
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([a, b, c, d])
.build();
.build(&objects)?;
let a = Point::from(a).to_xyz();
let b = Point::from(b).to_xyz();
@ -135,11 +137,11 @@ mod tests {
let h = [3., 1.];
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface.clone())
.with_exterior_polygon_from_points([a, b, c, d])
.with_interior_polygon_from_points([e, f, g, h])
.build();
.build(&objects)?;
let triangles = triangulate(face)?;
@ -196,10 +198,10 @@ mod tests {
let e = [0., 0.8];
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface.clone())
.with_exterior_polygon_from_points([a, b, c, d, e])
.build();
.build(&objects)?;
let triangles = triangulate(face)?;

View File

@ -1,115 +1,49 @@
use fj_interop::mesh::Color;
use fj_math::Point;
use crate::{
objects::{Cycle, Face, Objects, Surface},
partial::HasPartial,
storage::Handle,
objects::Cycle,
partial::{HasPartial, PartialFace},
};
use super::CycleBuilder;
/// API for building a [`Face`]
///
/// Also see [`Face::builder`].
pub struct FaceBuilder<'a> {
/// The stores that the created objects are put in
pub objects: &'a Objects,
/// Builder API for [`PartialFace`]
pub trait FaceBuilder {
/// Update the [`PartialFace`] with an exterior polygon
fn with_exterior_polygon_from_points(
self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self;
/// The surface that the [`Face`] is defined in
pub surface: Option<Handle<Surface>>,
/// The exterior cycle that bounds the [`Face`] on the outside
///
/// Must be provided by the caller, directly or using one of the `with_`
/// methods, before [`FaceBuilder::build`] is called.
pub exterior: Option<Handle<Cycle>>,
/// The interior cycles that form holes in the [`Face`]
pub interiors: Vec<Handle<Cycle>>,
/// The color of the [`Face`]
pub color: Option<Color>,
/// Update the [`PartialFace`] with an interior polygon
fn with_interior_polygon_from_points(
self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self;
}
impl<'a> FaceBuilder<'a> {
/// Build the [`Face`] with the provided surface
pub fn with_surface(mut self, surface: Handle<Surface>) -> Self {
self.surface = Some(surface);
self
}
/// Build the [`Face`] with the provided exterior
pub fn with_exterior(mut self, exterior: Handle<Cycle>) -> Self {
self.exterior = Some(exterior);
self
}
/// Build the [`Face`] with an exterior polygon from the provided points
pub fn with_exterior_polygon_from_points(
mut self,
impl FaceBuilder for PartialFace {
fn with_exterior_polygon_from_points(
self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to create polygon");
let surface = self.surface().expect("Need surface to create polygon");
self.exterior = Some(
self.with_exterior(
Cycle::partial()
.with_poly_chain_from_points(surface.clone(), points)
.close_with_line_segment()
.build(self.objects)
.unwrap(),
);
self
.with_poly_chain_from_points(surface, points)
.close_with_line_segment(),
)
}
/// Build the [`Face`] with the provided interior polygons
pub fn with_interiors(
mut self,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self {
self.interiors.extend(interiors);
self
}
/// Build the [`Face`] with an interior polygon from the provided points
pub fn with_interior_polygon_from_points(
mut self,
fn with_interior_polygon_from_points(
self,
points: impl IntoIterator<Item = impl Into<Point<2>>>,
) -> Self {
let surface = self
.surface
.as_ref()
.expect("Need surface to build polygon.");
let surface = self.surface().expect("Need surface to build polygon.");
self.interiors.push(
Cycle::partial()
.with_poly_chain_from_points(surface.clone(), points)
.close_with_line_segment()
.build(self.objects)
.unwrap(),
);
self
}
/// Build the [`Face`] with the provided color
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
/// Construct a polygon from a list of points
pub fn build(self) -> Handle<Face> {
let exterior = self
.exterior
.expect("Can't build `Face` without exterior cycle");
let color = self.color.unwrap_or_default();
self.objects
.faces
.insert(Face::new(exterior, self.interiors, color))
.unwrap()
self.with_interiors([Cycle::partial()
.with_poly_chain_from_points(surface, points)
.close_with_line_segment()])
}
}

View File

@ -3,7 +3,6 @@
// These are the old-style builders that need to be transferred to the partial
// object API. Issue:
// https://github.com/hannobraun/Fornjot/issues/1147
mod face;
mod shell;
mod sketch;
mod solid;
@ -12,6 +11,7 @@ mod solid;
mod curve;
mod cycle;
mod edge;
mod face;
mod vertex;
pub use self::{

View File

@ -5,7 +5,7 @@ use fj_math::Scalar;
use crate::{
algorithms::transform::TransformObject,
builder::HalfEdgeBuilder,
builder::{FaceBuilder, HalfEdgeBuilder},
objects::{
Curve, Cycle, Face, FaceSet, HalfEdge, Objects, Shell, Surface,
SurfaceVertex, Vertex,
@ -54,7 +54,7 @@ impl<'a> ShellBuilder<'a> {
.translate([Z, Z, -h], self.objects)
.unwrap();
Face::builder(self.objects)
Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([
[-h, -h],
@ -62,7 +62,8 @@ impl<'a> ShellBuilder<'a> {
[h, h],
[-h, h],
])
.build()
.build(self.objects)
.unwrap()
};
let (sides, top_edges) = {
@ -193,7 +194,10 @@ impl<'a> ShellBuilder<'a> {
.build(self.objects)
.unwrap();
Face::builder(self.objects).with_exterior(cycle).build()
Face::partial()
.with_exterior(cycle)
.build(self.objects)
.unwrap()
});
(sides, tops)
@ -259,11 +263,12 @@ impl<'a> ShellBuilder<'a> {
);
}
Face::builder(self.objects)
Face::partial()
.with_exterior(
self.objects.cycles.insert(Cycle::new(edges)).unwrap(),
)
.build()
.build(self.objects)
.unwrap()
};
self.faces.extend([bottom]);

View File

@ -2,9 +2,12 @@ use fj_math::Point;
use crate::{
objects::{Face, FaceSet, Objects, Sketch, Surface},
partial::HasPartial,
storage::Handle,
};
use super::FaceBuilder;
/// API for building a [`Sketch`]
///
/// Also see [`Sketch::builder`].
@ -44,10 +47,11 @@ impl<'a> SketchBuilder<'a> {
.surface
.as_ref()
.expect("Can't build `Sketch` without `Surface`");
self.faces.extend([Face::builder(self.objects)
self.faces.extend([Face::partial()
.with_surface(surface.clone())
.with_exterior_polygon_from_points(points)
.build()]);
.build(self.objects)
.unwrap()]);
self
}

View File

@ -360,7 +360,7 @@ impl<T> Iterator for Iter<T> {
#[cfg(test)]
mod tests {
use crate::{
builder::{CurveBuilder, CycleBuilder, HalfEdgeBuilder},
builder::{CurveBuilder, CycleBuilder, FaceBuilder, HalfEdgeBuilder},
objects::{
Curve, Cycle, Face, GlobalCurve, GlobalVertex, HalfEdge, Objects,
Shell, Sketch, Solid, SurfaceVertex, Vertex,
@ -424,10 +424,10 @@ mod tests {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let object = Face::builder(&objects)
let object = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]])
.build();
.build(&objects);
assert_eq!(3, object.curve_iter().count());
assert_eq!(1, object.cycle_iter().count());
@ -528,14 +528,14 @@ mod tests {
}
#[test]
fn sketch() {
fn sketch() -> anyhow::Result<()> {
let objects = Objects::new();
let surface = objects.surfaces.xy_plane();
let face = Face::builder(&objects)
let face = Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points([[0., 0.], [1., 0.], [0., 1.]])
.build();
.build(&objects)?;
let object = Sketch::builder(&objects).with_faces([face]).build();
assert_eq!(3, object.curve_iter().count());
@ -549,6 +549,8 @@ mod tests {
assert_eq!(0, object.solid_iter().count());
assert_eq!(1, object.surface_iter().count());
assert_eq!(6, object.vertex_iter().count());
Ok(())
}
#[test]

View File

@ -3,9 +3,9 @@ use std::collections::{btree_set, BTreeSet};
use fj_interop::mesh::Color;
use fj_math::Winding;
use crate::{builder::FaceBuilder, storage::Handle};
use crate::storage::Handle;
use super::{Cycle, Objects, Surface};
use super::{Cycle, Surface};
/// A face of a shape
///
@ -39,17 +39,6 @@ pub struct Face {
}
impl Face {
/// Build a `Face` using [`FaceBuilder`]
pub fn builder(objects: &Objects) -> FaceBuilder {
FaceBuilder {
objects,
surface: None,
exterior: None,
interiors: Vec::new(),
color: None,
}
}
/// Construct a new instance of `Face`
pub fn new(
exterior: Handle<Cycle>,

View File

@ -45,6 +45,7 @@ pub use self::{
curve::{PartialCurve, PartialGlobalCurve},
cycle::PartialCycle,
edge::{PartialGlobalEdge, PartialHalfEdge},
face::PartialFace,
vertex::{PartialGlobalVertex, PartialSurfaceVertex, PartialVertex},
},
traits::{HasPartial, Partial},

View File

@ -0,0 +1,114 @@
use fj_interop::mesh::Color;
use crate::{
objects::{Cycle, Face, Objects, Surface},
partial::{util::merge_options, MaybePartial},
storage::Handle,
validate::ValidationError,
};
/// A partial [`Face`]
///
/// See [`crate::partial`] for more information.
#[derive(Clone, Debug, Default)]
pub struct PartialFace {
surface: Option<Handle<Surface>>,
exterior: MaybePartial<Cycle>,
interiors: Vec<MaybePartial<Cycle>>,
color: Option<Color>,
}
impl PartialFace {
/// Access th surface that the [`Face`] is defined in
pub fn surface(&self) -> Option<Handle<Surface>> {
self.surface.clone()
}
/// Access the [`Face`]'s exterior cycle
pub fn exterior(&self) -> MaybePartial<Cycle> {
self.exterior.clone()
}
/// Access the [`Face`]'s interior cycles
pub fn interiors(&self) -> impl Iterator<Item = MaybePartial<Cycle>> + '_ {
self.interiors.iter().cloned()
}
/// Access the color of the [`Face`]
pub fn color(&self) -> Option<Color> {
self.color
}
/// Build the [`Face`] with the provided surface
pub fn with_surface(mut self, surface: Handle<Surface>) -> Self {
self.surface = Some(surface);
self
}
/// Build the [`Face`] with the provided exterior
pub fn with_exterior(
mut self,
exterior: impl Into<MaybePartial<Cycle>>,
) -> Self {
self.exterior = exterior.into();
self
}
/// Build the [`Face`] with the provided interior polygons
pub fn with_interiors(
mut self,
interiors: impl IntoIterator<Item = impl Into<MaybePartial<Cycle>>>,
) -> Self {
let interiors = interiors.into_iter().map(Into::into);
self.interiors.extend(interiors);
self
}
/// Build the [`Face`] with the provided color
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
/// Merge this partial object with another
pub fn merge_with(self, other: Self) -> Self {
let mut interiors = self.interiors;
interiors.extend(other.interiors);
Self {
surface: merge_options(self.surface, other.surface),
exterior: self.exterior.merge_with(other.exterior),
interiors,
color: merge_options(self.color, other.color),
}
}
/// Construct a polygon from a list of points
pub fn build(
self,
objects: &Objects,
) -> Result<Handle<Face>, ValidationError> {
let exterior = self.exterior.into_full(objects)?;
let interiors = self
.interiors
.into_iter()
.map(|cycle| cycle.into_full(objects))
.collect::<Result<Vec<_>, _>>()?;
let color = self.color.unwrap_or_default();
Ok(objects
.faces
.insert(Face::new(exterior, interiors, color))?)
}
}
impl From<&Face> for PartialFace {
fn from(face: &Face) -> Self {
Self {
surface: Some(face.surface().clone()),
exterior: face.exterior().clone().into(),
interiors: face.interiors().cloned().map(Into::into).collect(),
color: Some(face.color()),
}
}
}

View File

@ -1,18 +1,19 @@
pub mod curve;
pub mod cycle;
pub mod edge;
pub mod face;
pub mod vertex;
use crate::{
objects::{
Curve, Cycle, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge, Objects,
SurfaceVertex, Vertex,
Curve, Cycle, Face, GlobalCurve, GlobalEdge, GlobalVertex, HalfEdge,
Objects, SurfaceVertex, Vertex,
},
storage::Handle,
};
use super::{
HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle,
HasPartial, MaybePartial, Partial, PartialCurve, PartialCycle, PartialFace,
PartialGlobalCurve, PartialGlobalEdge, PartialGlobalVertex,
PartialHalfEdge, PartialSurfaceVertex, PartialVertex,
};
@ -53,6 +54,7 @@ macro_rules! impl_traits {
impl_traits!(
Curve, PartialCurve;
Cycle, PartialCycle;
Face, PartialFace;
GlobalCurve, PartialGlobalCurve;
GlobalEdge, PartialGlobalEdge;
GlobalVertex, PartialGlobalVertex;

View File

@ -105,7 +105,7 @@ impl FaceValidationError {
mod tests {
use crate::{
algorithms::reverse::Reverse,
builder::CycleBuilder,
builder::{CycleBuilder, FaceBuilder},
objects::{Cycle, Face, Objects},
partial::HasPartial,
validate::Validate,
@ -115,11 +115,11 @@ mod tests {
fn face_surface_mismatch() -> anyhow::Result<()> {
let objects = Objects::new();
let valid = Face::builder(&objects)
let valid = Face::partial()
.with_surface(objects.surfaces.xy_plane())
.with_exterior_polygon_from_points([[0., 0.], [3., 0.], [0., 3.]])
.with_interior_polygon_from_points([[1., 1.], [1., 2.], [2., 1.]])
.build();
.build(&objects)?;
let invalid = {
let interiors = [Cycle::partial()
.with_poly_chain_from_points(
@ -142,11 +142,11 @@ mod tests {
fn face_invalid_interior_winding() -> anyhow::Result<()> {
let objects = Objects::new();
let valid = Face::builder(&objects)
let valid = Face::partial()
.with_surface(objects.surfaces.xy_plane())
.with_exterior_polygon_from_points([[0., 0.], [3., 0.], [0., 3.]])
.with_interior_polygon_from_points([[1., 1.], [1., 2.], [2., 1.]])
.build();
.build(&objects)?;
let invalid = {
let interiors = valid
.interiors()

View File

@ -5,6 +5,7 @@ use fj_kernel::{
algorithms::reverse::Reverse,
iter::ObjectIters,
objects::{Face, Objects, Sketch},
partial::HasPartial,
validate::ValidationError,
};
use fj_math::Aabb;
@ -78,11 +79,11 @@ impl Shape for fj::Difference2d {
);
faces.push(
Face::builder(objects)
Face::partial()
.with_exterior(exterior)
.with_interiors(interiors)
.with_color(Color(self.color()))
.build(),
.build(objects)?,
);
}

View File

@ -2,7 +2,7 @@ use std::ops::Deref;
use fj_interop::{debug::DebugInfo, mesh::Color};
use fj_kernel::{
builder::HalfEdgeBuilder,
builder::{FaceBuilder, HalfEdgeBuilder},
objects::{Cycle, Face, HalfEdge, Objects, Sketch},
partial::HasPartial,
validate::ValidationError,
@ -32,20 +32,20 @@ impl Shape for fj::Sketch {
.build(objects)?;
let cycle = objects.cycles.insert(Cycle::new([half_edge]))?;
Face::builder(objects)
Face::partial()
.with_exterior(cycle)
.with_color(Color(self.color()))
.build()
.build(objects)?
}
fj::Chain::PolyChain(poly_chain) => {
let points =
poly_chain.to_points().into_iter().map(Point::from);
Face::builder(objects)
Face::partial()
.with_surface(surface)
.with_exterior_polygon_from_points(points)
.with_color(Color(self.color()))
.build()
.build(objects)?
}
};