Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Object Reference Validation #2133 #2144

Merged
merged 20 commits into from
Jan 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
19a1d03
Initial implementation of validating HalfEdges only used by one Cycle…
nathan-folsom Dec 17, 2023
bfde2c7
Test that validation succeeds for valid sketch as well as fails for i…
nathan-folsom Dec 17, 2023
bb97038
Implement validation of Cycle references within Sketch Regions
nathan-folsom Dec 17, 2023
01d976e
Implement Solid reference count validation
nathan-folsom Dec 17, 2023
f4d6c92
fix reference checks in wrong place
nathan-folsom Dec 18, 2023
0e672fe
Unit test shell object reference validations
nathan-folsom Dec 18, 2023
224f892
Fix doc refence
nathan-folsom Dec 18, 2023
cf761c2
Align error messages
nathan-folsom Dec 18, 2023
a8e612a
use hashmap entry api
nathan-folsom Dec 29, 2023
56b387f
create reference counter utility struct
nathan-folsom Dec 29, 2023
5224cc1
write abstraction for validating reference counts
nathan-folsom Dec 29, 2023
5a51f7d
make reference count error be contained within the object's error type
nathan-folsom Dec 29, 2023
81e1c8e
cleanup docs
nathan-folsom Dec 29, 2023
2f2d723
store both referenced and referencing objects for better error messages
nathan-folsom Dec 29, 2023
3bdb468
show referencing and referenced objects in error message
nathan-folsom Dec 30, 2023
2d2506b
rename for clarity
nathan-folsom Dec 30, 2023
0900ef8
cleanup, remove todos
nathan-folsom Dec 30, 2023
8411b17
Make DerefMut impl on Service only available in tests
nathan-folsom Jan 14, 2024
16e18e1
Fix typo in method name
nathan-folsom Jan 14, 2024
cdcf015
Make clippy happy with import
nathan-folsom Jan 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions crates/fj-core/src/services/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ impl<S: State> Deref for Service<S> {
}
}

#[cfg(test)]
impl<S: State> std::ops::DerefMut for Service<S> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.state
}
}

impl<S: State> Default for Service<S>
where
S: Default,
Expand Down
7 changes: 6 additions & 1 deletion crates/fj-core/src/validate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ mod curve;
mod cycle;
mod edge;
mod face;
mod references;
mod region;
mod shell;
mod sketch;
Expand All @@ -86,7 +87,7 @@ mod vertex;
pub use self::{
cycle::CycleValidationError, edge::EdgeValidationError,
face::FaceValidationError, shell::ShellValidationError,
solid::SolidValidationError,
sketch::SketchValidationError, solid::SolidValidationError,
};

use std::{convert::Infallible, fmt};
Expand Down Expand Up @@ -190,6 +191,10 @@ pub enum ValidationError {
/// `Solid` validation error
#[error("`Solid` validation error")]
Solid(#[from] SolidValidationError),

/// `Sketch` validation error
#[error("`Sketch` validation error")]
Sketch(#[from] SketchValidationError),
}

impl From<Infallible> for ValidationError {
Expand Down
107 changes: 107 additions & 0 deletions crates/fj-core/src/validate/references.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
use std::collections::HashMap;
use std::hash::Hash;

use crate::objects::{Cycle, Face, HalfEdge, Region, Shell};
use crate::storage::Handle;

#[derive(Default)]
pub struct ReferenceCounter<T, U>(HashMap<Handle<T>, Vec<Handle<U>>>);

impl<T: Eq + PartialEq + Hash, U> ReferenceCounter<T, U> {
pub fn new() -> Self {
Self(HashMap::new())
}

pub fn add_reference(
&mut self,
referenced: Handle<T>,
reference: Handle<U>,
) {
self.0
.entry(referenced)
.and_modify(|references| references.push(reference.clone()))
.or_insert(vec![reference]);
}

pub fn get_multiples(&self) -> Vec<MultipleReferences<T, U>> {
self.0
.iter()
.filter(|(_, references)| references.len() > 1)
.map(|(referenced, references)| MultipleReferences {
referenced: referenced.clone(),
references: references.to_vec(),
})
.collect()
}
}

/// Find errors and convert to [`crate::validate::ValidationError`]
#[macro_export]
macro_rules! validate_references {
($errors:ident, $error_ty:ty;$($counter:ident, $err:ident;)*) => {
$(
$counter.get_multiples().iter().for_each(|multiple| {
let reference_error = ReferenceCountError::$err { references: multiple.clone() };
$errors.push(Into::<$error_ty>::into(reference_error).into());
});
)*
};
}

/// Validation errors for when an object is referenced by multiple other objects. Each object
/// should only be referenced by a single other object
#[derive(Clone, Debug, thiserror::Error)]
pub enum ReferenceCountError {
/// [`crate::objects::Region`] referenced by more than one [`crate::objects::Face`]
#[error(
"[`Region`] referenced by more than one [`Face`]\n{references:#?}"
)]
Region {
references: MultipleReferences<Region, Face>,
},
/// [`crate::objects::Face`] referenced by more than one [`crate::objects::Shell`]
#[error("[`Face`] referenced by more than one [`Shell`]\n{references:#?}")]
Face {
references: MultipleReferences<Face, Shell>,
},
/// [`crate::objects::HalfEdge`] referenced by more than one [`crate::objects::Cycle`]
#[error(
"[`HalfEdge`] referenced by more than one [`Cycle`]\n{references:#?}"
)]
HalfEdge {
references: MultipleReferences<HalfEdge, Cycle>,
},
/// [`crate::objects::Cycle`] referenced by more than one [`crate::objects::Region`]
#[error(
"[`Cycle`] referenced by more than one [`Region`]\n{references:#?}"
)]
Cycle {
references: MultipleReferences<Cycle, Region>,
},
}

pub struct MultipleReferences<T, U> {
referenced: Handle<T>,
references: Vec<Handle<U>>,
}

use std::fmt::Debug;

impl<T: Debug, U: Debug> Debug for MultipleReferences<T, U> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?} referenced by {:?}",
self.referenced, self.references
)
}
}

impl<T, U> Clone for MultipleReferences<T, U> {
fn clone(&self) -> Self {
Self {
referenced: self.referenced.clone(),
references: self.references.to_vec(),
}
}
}
136 changes: 132 additions & 4 deletions crates/fj-core/src/validate/sketch.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,140 @@
use crate::objects::Sketch;
use crate::{objects::Sketch, validate_references};

use super::{Validate, ValidationConfig, ValidationError};
use super::{
references::{ReferenceCountError, ReferenceCounter},
Validate, ValidationConfig, ValidationError,
};

impl Validate for Sketch {
fn validate_with_config(
&self,
_: &ValidationConfig,
_: &mut Vec<ValidationError>,
config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
SketchValidationError::check_object_references(self, config, errors);
}
}

/// [`Sketch`] validation failed
#[derive(Clone, Debug, thiserror::Error)]
pub enum SketchValidationError {
/// Object within sketch referenced by more than one other object
#[error("Object within sketch referenced by more than one other Object")]
MultipleReferences(#[from] ReferenceCountError),
}

impl SketchValidationError {
fn check_object_references(
sketch: &Sketch,
_config: &ValidationConfig,
errors: &mut Vec<ValidationError>,
) {
let mut referenced_edges = ReferenceCounter::new();
let mut referenced_cycles = ReferenceCounter::new();

sketch.regions().iter().for_each(|r| {
r.all_cycles().for_each(|c| {
referenced_cycles.add_reference(c.clone(), r.clone());
c.half_edges().into_iter().for_each(|e| {
referenced_edges.add_reference(e.clone(), c.clone());
})
})
});

validate_references!(
errors, SketchValidationError;
referenced_edges, HalfEdge;
referenced_cycles, Cycle;
);
}
}

#[cfg(test)]
mod tests {
use crate::{
assert_contains_err,
objects::{Cycle, HalfEdge, Region, Sketch, Vertex},
operations::{build::BuildHalfEdge, insert::Insert},
services::Services,
validate::{
references::ReferenceCountError, SketchValidationError, Validate,
ValidationError,
},
};

#[test]
fn should_find_cycle_multiple_references() -> anyhow::Result<()> {
let mut services = Services::new();

let shared_cycle = Cycle::new(vec![]).insert(&mut services);

let invalid_sketch = Sketch::new(vec![
Region::new(
Cycle::new(vec![]).insert(&mut services),
vec![shared_cycle.clone()],
None,
)
.insert(&mut services),
Region::new(shared_cycle, vec![], None).insert(&mut services),
]);
assert_contains_err!(
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::Cycle { references: _ }
))
);

let valid_sketch = Sketch::new(vec![Region::new(
Cycle::new(vec![]).insert(&mut services),
vec![],
None,
)
.insert(&mut services)]);
valid_sketch.validate_and_return_first_error()?;

Ok(())
}

#[test]
fn should_find_half_edge_multiple_references() -> anyhow::Result<()> {
let mut services = Services::new();

let half_edge =
HalfEdge::line_segment([[0., 0.], [1., 0.]], None, &mut services)
.insert(&mut services);
let sibling_edge = HalfEdge::from_sibling(
&half_edge,
Vertex::new().insert(&mut services),
)
.insert(&mut services);

let exterior =
Cycle::new(vec![half_edge.clone(), sibling_edge.clone()])
.insert(&mut services);

let interior =
Cycle::new(vec![half_edge.clone(), sibling_edge.clone()])
.insert(&mut services);

let invalid_sketch = Sketch::new(vec![Region::new(
exterior.clone(),
vec![interior],
None,
)
.insert(&mut services)]);
assert_contains_err!(
invalid_sketch,
ValidationError::Sketch(SketchValidationError::MultipleReferences(
ReferenceCountError::HalfEdge { references: _ }
))
);

let valid_sketch =
Sketch::new(vec![
Region::new(exterior, vec![], None).insert(&mut services)
]);
valid_sketch.validate_and_return_first_error()?;

Ok(())
}
}
Loading