use crate::error::GeometryResult;
use crate::slots::Slots;
use ifc_model::{Entity, EntityId, Model, Value};
pub(crate) mod slot {
pub const OUTER: usize = 0;
pub const VOIDS: usize = 1;
}
#[derive(Debug, Clone, Copy)]
pub struct ManifoldSolidBrep<'m> {
slots: Slots<'m>,
}
impl<'m> ManifoldSolidBrep<'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 type_name(&self) -> &'m str {
self.slots.type_name()
}
pub fn outer(&self) -> GeometryResult<EntityId> {
self.slots.req_ref(slot::OUTER, "Outer")
}
pub fn has_voids(&self) -> bool {
let name = self.type_name();
name.eq_ignore_ascii_case("IFCFACETEDBREPWITHVOIDS")
|| name.eq_ignore_ascii_case("IFCADVANCEDBREPWITHVOIDS")
}
}
#[derive(Debug, Clone, Copy)]
pub struct FacetedBrep<'m> {
slots: Slots<'m>,
}
impl<'m> FacetedBrep<'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 base(&self) -> ManifoldSolidBrep<'m> {
ManifoldSolidBrep { slots: self.slots }
}
}
#[derive(Debug, Clone, Copy)]
pub struct FacetedBrepWithVoids<'m> {
slots: Slots<'m>,
}
impl<'m> FacetedBrepWithVoids<'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 base(&self) -> FacetedBrep<'m> {
FacetedBrep { slots: self.slots }
}
pub fn voids(&self) -> GeometryResult<Vec<EntityId>> {
self.slots.req_ref_list(slot::VOIDS, "Voids")
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdvancedBrep<'m> {
slots: Slots<'m>,
}
impl<'m> AdvancedBrep<'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 base(&self) -> ManifoldSolidBrep<'m> {
ManifoldSolidBrep { slots: self.slots }
}
}
#[derive(Debug, Clone, Copy)]
pub struct AdvancedBrepWithVoids<'m> {
slots: Slots<'m>,
}
impl<'m> AdvancedBrepWithVoids<'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 base(&self) -> AdvancedBrep<'m> {
AdvancedBrep { slots: self.slots }
}
pub fn voids(&self) -> GeometryResult<Vec<EntityId>> {
self.slots.req_ref_list(slot::VOIDS, "Voids")
}
}
pub fn non_advanced_faces(model: &Model, shell: EntityId) -> Vec<EntityId> {
let Some(entity) = model.get(shell) else {
return Vec::new();
};
let Some(Value::List(faces)) = entity.attribute(0).map(|v| v.unwrap_typed()) else {
return Vec::new();
};
faces
.iter()
.filter_map(|v| match v.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
})
.filter(|face| {
model.get(*face).is_some_and(|f| {
!crate::select::is_a(&f.type_name.to_ascii_uppercase(), "IFCADVANCEDFACE")
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solid::testkit::{entity, refs};
#[test]
fn outer_shell_is_slot_zero_for_every_brep_subtype() {
for name in [
"IFCFACETEDBREP",
"IFCADVANCEDBREP",
"IFCFACETEDBREPWITHVOIDS",
"IFCADVANCEDBREPWITHVOIDS",
] {
let e = entity(name, vec![crate::solid::testkit::r(100), refs(&[200])]);
let view = ManifoldSolidBrep::new(EntityId(1), &e);
assert_eq!(view.outer().unwrap(), EntityId(100), "{name}");
}
}
#[test]
fn voids_are_kept_separate_from_the_outer_shell() {
let e = entity(
"IFCFACETEDBREPWITHVOIDS",
vec![crate::solid::testkit::r(100), refs(&[201, 202])],
);
let view = FacetedBrepWithVoids::new(EntityId(1), &e);
let outer = view.base().base().outer().unwrap();
let voids = view.voids().unwrap();
assert_eq!(outer, EntityId(100));
assert_eq!(voids, vec![EntityId(201), EntityId(202)]);
assert!(!voids.contains(&outer), "a void is never the outer shell");
}
#[test]
fn advanced_brep_with_voids_uses_the_same_slot_layout() {
let e = entity(
"IFCADVANCEDBREPWITHVOIDS",
vec![crate::solid::testkit::r(7), refs(&[8, 9])],
);
let view = AdvancedBrepWithVoids::new(EntityId(1), &e);
assert_eq!(view.base().base().outer().unwrap(), EntityId(7));
assert_eq!(view.voids().unwrap(), vec![EntityId(8), EntityId(9)]);
}
#[test]
fn only_the_with_voids_subtypes_report_carrying_voids() {
let plain = entity("IFCFACETEDBREP", vec![crate::solid::testkit::r(1)]);
let voided = entity(
"IFCFACETEDBREPWITHVOIDS",
vec![crate::solid::testkit::r(1), refs(&[2])],
);
assert!(!ManifoldSolidBrep::new(EntityId(1), &plain).has_voids());
assert!(ManifoldSolidBrep::new(EntityId(1), &voided).has_voids());
}
#[test]
fn a_brep_missing_its_outer_shell_reports_the_entity() {
let e = entity("IFCFACETEDBREP", vec![]);
let err = FacetedBrep::new(EntityId(42), &e)
.base()
.outer()
.unwrap_err();
assert_eq!(err.entity(), Some(EntityId(42)));
assert!(err.to_string().contains("Outer"));
}
}