Merge pull request #1812 from hannobraun/builder

Migrate tests away from `FaceBuilder`, towards operations API
This commit is contained in:
Hanno Braun 2023-05-03 11:09:09 +02:00 committed by GitHub
commit fa02019d44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 419 additions and 209 deletions

View File

@ -150,8 +150,9 @@ where
#[cfg(test)]
mod tests {
use crate::{
builder::{CycleBuilder, FaceBuilder},
geometry::curve::Curve,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
};
@ -178,20 +179,23 @@ mod tests {
[ 1., -1.],
];
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
exterior_points,
&mut services,
))
.with_interior(CycleBuilder::polygon(
interior_points,
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(exterior_points, &mut services)
.insert(&mut services)
})
.add_interiors([Cycle::polygon(
interior_points,
&mut services,
)
.insert(&mut services)]);
let expected =
CurveFaceIntersection::from_intervals([[[1.], [2.]], [[4.], [5.]]]);
assert_eq!(CurveFaceIntersection::compute(&curve, &face), expected);
services.only_validate(face);
}
#[test]

View File

@ -62,8 +62,9 @@ mod tests {
use crate::{
algorithms::intersect::CurveFaceIntersection,
builder::{CycleBuilder, FaceBuilder},
geometry::curve::Curve,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
};
@ -85,14 +86,15 @@ mod tests {
services.objects.surfaces.xz_plane(),
]
.map(|surface| {
FaceBuilder::new(surface)
.with_exterior(CycleBuilder::polygon(points, &mut services))
.build(&mut services)
Face::unbound(surface, &mut services).update_exterior(|_| {
Cycle::polygon(points, &mut services).insert(&mut services)
})
});
let intersection = FaceFaceIntersection::compute([&a, &b]);
assert!(intersection.is_none());
services.only_validate([a, b]);
}
#[test]
@ -111,9 +113,9 @@ mod tests {
services.objects.surfaces.xz_plane(),
];
let [a, b] = surfaces.clone().map(|surface| {
FaceBuilder::new(surface)
.with_exterior(CycleBuilder::polygon(points, &mut services))
.build(&mut services)
Face::unbound(surface, &mut services).update_exterior(|_| {
Cycle::polygon(points, &mut services).insert(&mut services)
})
});
let intersection = FaceFaceIntersection::compute([&a, &b]);
@ -131,5 +133,7 @@ mod tests {
intersection_intervals: expected_intervals
})
);
services.only_validate([a, b]);
}
}

View File

@ -138,7 +138,8 @@ mod tests {
use crate::{
algorithms::intersect::{face_point::FacePointIntersection, Intersect},
builder::{CycleBuilder, FaceBuilder},
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
};
@ -146,28 +147,36 @@ mod tests {
fn point_is_outside_face() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [1., 1.], [0., 2.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [1., 1.], [0., 2.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([2., 1.]);
let intersection = (&face, &point).intersect();
assert_eq!(intersection, None);
services.only_validate(face);
}
#[test]
fn ray_hits_vertex_while_passing_outside() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [0., 2.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [0., 2.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -175,18 +184,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
services.only_validate(face);
}
#[test]
fn ray_hits_vertex_at_cycle_seam() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[4., 2.], [0., 4.], [0., 0.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[4., 2.], [0., 4.], [0., 0.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 2.]);
let intersection = (&face, &point).intersect();
@ -194,18 +208,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
services.only_validate(face);
}
#[test]
fn ray_hits_vertex_while_staying_inside() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 0.], [3., 4.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -213,18 +232,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
services.only_validate(face);
}
#[test]
fn ray_hits_parallel_edge_and_leaves_face_at_vertex() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 1.], [0., 2.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -232,18 +256,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
services.only_validate(face);
}
#[test]
fn ray_hits_parallel_edge_and_does_not_leave_face_there() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 1.], [3., 1.], [4., 0.], [4., 5.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 1.]);
let intersection = (&face, &point).intersect();
@ -251,18 +280,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsInsideFace)
);
services.only_validate(face);
}
#[test]
fn point_is_coincident_with_edge() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [2., 0.], [0., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [2., 0.], [0., 1.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
@ -276,18 +310,23 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsOnEdge(edge.clone()))
);
services.only_validate(face);
}
#[test]
fn point_is_coincident_with_vertex() {
let mut services = Services::new();
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [1., 0.], [0., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [1., 0.], [0., 1.]],
&mut services,
)
.insert(&mut services)
});
let point = Point::from([1., 0.]);
let intersection = (&face, &point).intersect();
@ -304,5 +343,7 @@ mod tests {
intersection,
Some(FacePointIntersection::PointIsOnVertex(vertex))
);
services.only_validate(face);
}
}

View File

@ -152,7 +152,8 @@ mod tests {
},
transform::TransformObject,
},
builder::{CycleBuilder, FaceBuilder},
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
};
@ -162,15 +163,20 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([-1., 0., 0.], &mut services);
assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
}
#[test]
@ -179,18 +185,23 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([1., 0., 0.], &mut services);
assert_eq!(
(&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsFace)
);
services.only_validate(face);
}
#[test]
@ -199,15 +210,20 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([0., 0., 2.], &mut services);
assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
}
#[test]
@ -216,12 +232,15 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([1., 1., 0.], &mut services);
let edge = face
@ -233,6 +252,8 @@ mod tests {
(&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsEdge(edge.clone()))
);
services.only_validate(face);
}
#[test]
@ -241,12 +262,15 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.yz_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.yz_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([1., 1., 1.], &mut services);
let vertex = face
@ -261,6 +285,8 @@ mod tests {
(&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsVertex(vertex))
);
services.only_validate(face);
}
#[test]
@ -269,17 +295,22 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
assert_eq!(
(&ray, &face).intersect(),
Some(RayFaceIntersection::RayHitsFaceAndAreParallel)
);
services.only_validate(face);
}
#[test]
@ -288,14 +319,19 @@ mod tests {
let ray = HorizontalRayToTheRight::from([0., 0., 0.]);
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[-1., -1.], [1., -1.], [1., 1.], [-1., 1.]],
&mut services,
)
.insert(&mut services)
});
let face = face.translate([0., 0., 1.], &mut services);
assert_eq!((&ray, &face).intersect(), None);
services.only_validate(face);
}
}

View File

@ -79,8 +79,8 @@ mod tests {
use crate::{
algorithms::approx::{Approx, Tolerance},
builder::{CycleBuilder, FaceBuilder},
objects::Face,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
};
@ -95,9 +95,13 @@ mod tests {
let c = [2., 2.];
let d = [0., 1.];
let face = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services))
.build(&mut services);
let face =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon([a, b, c, d], &mut services)
.insert(&mut services)
});
services.only_validate(&face);
let a = Point::from(a).to_xyz();
let b = Point::from(b).to_xyz();
@ -130,10 +134,14 @@ mod tests {
let surface = services.objects.surfaces.xy_plane();
let face = FaceBuilder::new(surface.clone())
.with_exterior(CycleBuilder::polygon([a, b, c, d], &mut services))
.with_interior(CycleBuilder::polygon([e, f, g, h], &mut services))
.build(&mut services);
let face = Face::unbound(surface.clone(), &mut services)
.update_exterior(|_| {
Cycle::polygon([a, b, c, d], &mut services)
.insert(&mut services)
})
.add_interiors([Cycle::polygon([e, f, g, h], &mut services)
.insert(&mut services)]);
services.only_validate(&face);
let triangles = triangulate(face)?;
@ -188,12 +196,12 @@ mod tests {
let surface = services.objects.surfaces.xy_plane();
let face = FaceBuilder::new(surface.clone())
.with_exterior(CycleBuilder::polygon(
[a, b, c, d, e],
&mut services,
))
.build(&mut services);
let face = Face::unbound(surface.clone(), &mut services)
.update_exterior(|_| {
Cycle::polygon([a, b, c, d, e], &mut services)
.insert(&mut services)
});
services.only_validate(&face);
let triangles = triangulate(face)?;

View File

@ -1,58 +0,0 @@
use fj_interop::mesh::Color;
use crate::{
objects::{Face, Surface},
operations::Insert,
services::Services,
storage::Handle,
};
use super::CycleBuilder;
/// Builder API for [`Face`]
pub struct FaceBuilder {
surface: Handle<Surface>,
exterior: CycleBuilder,
interiors: Vec<CycleBuilder>,
color: Option<Color>,
}
impl FaceBuilder {
/// Create an instance of `FaceBuilder`
pub fn new(surface: Handle<Surface>) -> Self {
Self {
surface,
exterior: CycleBuilder::new(),
interiors: Vec::new(),
color: None,
}
}
/// Replace the face's exterior cycle
pub fn with_exterior(mut self, exterior: CycleBuilder) -> Self {
self.exterior = exterior;
self
}
/// Add an interior cycle to the face
pub fn with_interior(mut self, interior: CycleBuilder) -> Self {
self.interiors.push(interior);
self
}
/// Define the color of the face
pub fn with_color(mut self, color: Color) -> Self {
self.color = Some(color);
self
}
/// Build the face
pub fn build(self, services: &mut Services) -> Face {
let exterior = self.exterior.build(services).insert(services);
let interiors = self
.interiors
.into_iter()
.map(|cycle| cycle.build(services).insert(services));
Face::new(self.surface, exterior, interiors, self.color)
}
}

View File

@ -1,6 +1,5 @@
//! API for building objects
mod cycle;
mod face;
pub use self::{cycle::CycleBuilder, face::FaceBuilder};
pub use self::cycle::CycleBuilder;

View File

@ -75,6 +75,7 @@
mod full;
mod object;
mod set;
mod stores;
pub use self::{
@ -89,5 +90,6 @@ pub use self::{
vertex::Vertex,
},
object::{Bare, BehindHandle, Form, Object, WithHandle},
set::ObjectSet,
stores::{Objects, Surfaces},
};

View File

@ -0,0 +1,106 @@
use std::collections::{btree_set, BTreeSet};
use super::{
BehindHandle, Cycle, Face, GlobalEdge, HalfEdge, Object, Surface, Vertex,
};
/// A graph of objects and their relationships
pub struct ObjectSet {
inner: BTreeSet<Object<BehindHandle>>,
}
impl From<&Face> for ObjectSet {
fn from(face: &Face) -> Self {
let mut self_ = Self {
inner: BTreeSet::new(),
};
face.insert_into_set(&mut self_);
self_
}
}
impl From<Face> for ObjectSet {
fn from(face: Face) -> Self {
Self::from(&face)
}
}
impl<Faces> From<Faces> for ObjectSet
where
Faces: IntoIterator<Item = Face>,
{
fn from(faces: Faces) -> Self {
let mut self_ = Self {
inner: BTreeSet::new(),
};
for face in faces {
face.insert_into_set(&mut self_);
}
self_
}
}
impl IntoIterator for ObjectSet {
type Item = Object<BehindHandle>;
type IntoIter = btree_set::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.inner.into_iter()
}
}
trait InsertIntoSet {
fn insert_into_set(&self, objects: &mut ObjectSet);
}
impl InsertIntoSet for Cycle {
fn insert_into_set(&self, objects: &mut ObjectSet) {
for half_edge in self.half_edges() {
objects.inner.insert(half_edge.clone().into());
half_edge.insert_into_set(objects);
}
}
}
impl InsertIntoSet for Face {
fn insert_into_set(&self, objects: &mut ObjectSet) {
objects.inner.insert(self.surface().clone().into());
self.surface().insert_into_set(objects);
objects.inner.insert(self.exterior().clone().into());
self.exterior().insert_into_set(objects);
for interior in self.interiors() {
objects.inner.insert(interior.clone().into());
}
for interior in self.interiors() {
interior.insert_into_set(objects);
}
}
}
impl InsertIntoSet for GlobalEdge {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}
impl InsertIntoSet for HalfEdge {
fn insert_into_set(&self, objects: &mut ObjectSet) {
objects.inner.insert(self.start_vertex().clone().into());
self.start_vertex().insert_into_set(objects);
objects.inner.insert(self.global_form().clone().into());
self.global_form().insert_into_set(objects);
}
}
impl InsertIntoSet for Surface {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}
impl InsertIntoSet for Vertex {
fn insert_into_set(&self, _: &mut ObjectSet) {}
}

View File

@ -8,10 +8,16 @@ use crate::{
storage::Handle,
};
use super::{BuildHalfEdge, BuildSurface};
use super::{BuildCycle, BuildHalfEdge, BuildSurface};
/// Build a [`Face`]
pub trait BuildFace {
/// Build a face with an empty exterior, no interiors, and no color
fn unbound(surface: Handle<Surface>, services: &mut Services) -> Face {
let exterior = Cycle::empty().insert(services);
Face::new(surface, exterior, [], None)
}
/// Build a triangle
fn triangle(
points: [impl Into<Point<3>>; 3],

View File

@ -10,6 +10,12 @@ pub trait UpdateFace {
&self,
f: impl FnOnce(&Handle<Cycle>) -> Handle<Cycle>,
) -> Self;
/// Add the provides interiors to the face
fn add_interiors(
&self,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self;
}
impl UpdateFace for Face {
@ -26,4 +32,18 @@ impl UpdateFace for Face {
self.color(),
)
}
fn add_interiors(
&self,
interiors: impl IntoIterator<Item = Handle<Cycle>>,
) -> Self {
let interiors = self.interiors().cloned().chain(interiors);
Face::new(
self.surface().clone(),
self.exterior().clone(),
interiors,
self.color(),
)
}
}

View File

@ -6,7 +6,7 @@ mod objects;
mod service;
mod validation;
use crate::objects::{Object, Objects, WithHandle};
use crate::objects::{Object, ObjectSet, Objects, WithHandle};
pub use self::{
objects::{InsertObject, Operation},
@ -52,6 +52,15 @@ impl Services {
self.validation.execute(command, &mut Vec::new());
}
}
/// Validate the provided objects and forget all other validation errors
pub fn only_validate(&mut self, objects: impl Into<ObjectSet>) {
let objects = objects.into();
let mut events = Vec::new();
self.validation
.execute(ValidationCommand::OnlyValidate { objects }, &mut events);
}
}
impl Default for Services {

View File

@ -1,7 +1,7 @@
use std::{collections::BTreeMap, thread};
use crate::{
objects::{BehindHandle, Object},
objects::{BehindHandle, Object, ObjectSet},
storage::ObjectId,
validate::ValidationError,
};
@ -39,16 +39,33 @@ impl State for Validation {
type Event = ValidationEvent;
fn decide(&self, command: Self::Command, events: &mut Vec<Self::Event>) {
let ValidationCommand::ValidateObject { object } = command;
let mut errors = Vec::new();
object.validate(&mut errors);
for err in errors {
events.push(ValidationEvent::ValidationFailed {
object: object.clone(),
err,
});
match command {
ValidationCommand::ValidateObject { object } => {
object.validate(&mut errors);
for err in errors {
events.push(ValidationEvent::ValidationFailed {
object: object.clone(),
err,
});
}
}
ValidationCommand::OnlyValidate { objects } => {
events.push(ValidationEvent::ClearErrors);
for object in objects {
object.validate(&mut errors);
for err in errors.drain(..) {
events.push(ValidationEvent::ValidationFailed {
object: object.clone(),
err,
});
}
}
}
}
}
@ -57,6 +74,7 @@ impl State for Validation {
ValidationEvent::ValidationFailed { object, err } => {
self.errors.insert(object.id(), err.clone());
}
ValidationEvent::ClearErrors => self.errors.clear(),
}
}
}
@ -68,6 +86,12 @@ pub enum ValidationCommand {
/// The object to validate
object: Object<BehindHandle>,
},
/// Validate the provided objects, discard all other validation errors
OnlyValidate {
/// The objects to validate
objects: ObjectSet,
},
}
/// The event produced by the validation service
@ -81,4 +105,7 @@ pub enum ValidationEvent {
/// The validation error
err: ValidationError,
},
/// All stored validation errors are being cleared
ClearErrors,
}

View File

@ -73,8 +73,8 @@ mod tests {
use crate::{
algorithms::reverse::Reverse,
assert_contains_err,
builder::{CycleBuilder, FaceBuilder},
objects::Face,
objects::{Cycle, Face},
operations::{BuildCycle, BuildFace, Insert, UpdateFace},
services::Services,
validate::{FaceValidationError, Validate, ValidationError},
};
@ -83,16 +83,20 @@ mod tests {
fn face_invalid_interior_winding() -> anyhow::Result<()> {
let mut services = Services::new();
let valid = FaceBuilder::new(services.objects.surfaces.xy_plane())
.with_exterior(CycleBuilder::polygon(
[[0., 0.], [3., 0.], [0., 3.]],
&mut services,
))
.with_interior(CycleBuilder::polygon(
[[1., 1.], [1., 2.], [2., 1.]],
&mut services,
))
.build(&mut services);
let valid =
Face::unbound(services.objects.surfaces.xy_plane(), &mut services)
.update_exterior(|_| {
Cycle::polygon(
[[0., 0.], [3., 0.], [0., 3.]],
&mut services,
)
.insert(&mut services)
})
.add_interiors([Cycle::polygon(
[[1., 1.], [1., 2.], [2., 1.]],
&mut services,
)
.insert(&mut services)]);
let invalid = {
let interiors = valid
.interiors()
@ -116,6 +120,8 @@ mod tests {
)
);
services.only_validate(valid);
Ok(())
}
}