use crate::error::GeometryResult;
use crate::slots::Slots;
use ifc_model::{Entity, EntityId};
use super::{int_list, TriangulatedFaceSet};
pub const TYPE: &str = "IFCTRIANGULATEDIRREGULARNETWORK";
pub(crate) mod slot {
pub const FLAGS: usize = 5;
}
pub const EXCLUDED_TRIANGLE: &str = "a Flags value is -1 (hole) or -2 (void); the triangle is \
excluded from the surface, and the neutral mesh has no face-exclusion or fall-back channel";
pub const UNDOCUMENTED_FLAG: &str =
"a Flags value is outside the documented codes -2 to 7, so its meaning is unknown";
#[derive(Debug, Clone, Copy)]
pub struct TriangulatedIrregularNetwork<'m> {
slots: Slots<'m>,
}
impl<'m> TriangulatedIrregularNetwork<'m> {
pub fn new(id: EntityId, entity: &'m Entity) -> Self {
Self {
slots: Slots::new(id, entity),
}
}
pub fn id(&self) -> EntityId {
self.slots.id()
}
pub fn face_set(&self) -> TriangulatedFaceSet<'m> {
TriangulatedFaceSet::new(self.slots.id(), self.slots.entity())
}
pub fn flags(&self) -> GeometryResult<Vec<i64>> {
let value = self.slots.req(slot::FLAGS, "Flags")?;
int_list(value.unwrap_typed())
.ok_or_else(|| self.slots.degenerate("Flags is not a list of integers"))
}
pub fn admit(&self, triangles: usize) -> GeometryResult<()> {
let flags = self.flags()?;
if flags.len() != triangles {
return Err(self.slots.degenerate(format!(
"Flags has {} values but CoordIndex declares {triangles} triangles",
flags.len()
)));
}
if flags.iter().any(|flag| !(-2..=7).contains(flag)) {
return Err(self.slots.unsupported(UNDOCUMENTED_FLAG));
}
if flags.iter().any(|flag| *flag < 0) {
return Err(self.slots.unsupported(EXCLUDED_TRIANGLE));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solid::testkit::{entity, int_grid as grid, ints, r};
use crate::GeometryError;
use ifc_model::Value;
fn network(flags: &[i64]) -> Entity {
entity(
TYPE,
vec![
r(50),
Value::Null,
Value::Bool(false),
grid(&[&[1, 2, 3], &[1, 3, 4]]),
Value::Null,
ints(flags),
],
)
}
#[test]
fn inherited_slots_read_through_the_face_set_view() {
let e = network(&[0, 0]);
let view = TriangulatedIrregularNetwork::new(EntityId(1), &e);
assert_eq!(view.face_set().coordinates().unwrap(), EntityId(50));
assert_eq!(view.face_set().closed(), Some(false));
assert_eq!(view.flags().unwrap(), vec![0, 0]);
}
#[test]
fn breakline_codes_are_admitted() {
for flags in [[0, 0], [1, 7], [3, 4]] {
let e = network(&flags);
TriangulatedIrregularNetwork::new(EntityId(1), &e)
.admit(2)
.unwrap_or_else(|error| panic!("{flags:?} must be admitted: {error}"));
}
}
#[test]
fn voids_holes_and_unknown_codes_are_typed_refusals() {
for (flags, detail) in [
([0, -1], EXCLUDED_TRIANGLE),
([-2, 0], EXCLUDED_TRIANGLE),
([8, 0], UNDOCUMENTED_FLAG),
([0, -3], UNDOCUMENTED_FLAG),
] {
let e = network(&flags);
let error = TriangulatedIrregularNetwork::new(EntityId(4), &e)
.admit(2)
.expect_err("must refuse");
assert!(
matches!(&error, GeometryError::Unsupported { detail: d, .. } if *d == detail),
"{flags:?}: {error}"
);
}
}
#[test]
fn a_flag_count_that_disagrees_with_the_triangles_is_degenerate() {
let e = network(&[0]);
let error = TriangulatedIrregularNetwork::new(EntityId(4), &e)
.admit(2)
.expect_err("one flag for two triangles");
assert!(matches!(error, GeometryError::Degenerate { .. }), "{error}");
}
}