use ifc_model::{EntityId, Model, Value};
use super::link::refs_in_slot;
use super::slots::SPACE_BOUNDARY_TYPES;
mod slot {
pub const CONNECTION_GEOMETRY: usize = 6;
pub const PHYSICAL_OR_VIRTUAL: usize = 7;
pub const INTERNAL_OR_EXTERNAL: usize = 8;
pub const PARENT_BOUNDARY: usize = 9;
pub const CORRESPONDING_BOUNDARY: usize = 10;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryPhysicality {
Physical,
Virtual,
NotDefined,
Unrecognized,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryExposure {
Internal,
External,
ExternalVariant,
NotDefined,
Unrecognized,
}
const CONNECTION_GEOMETRY_TYPES: [&str; 6] = [
"IFCCONNECTIONCURVEGEOMETRY",
"IFCCONNECTIONPOINTECCENTRICITY",
"IFCCONNECTIONPOINTGEOMETRY",
"IFCCONNECTIONPORTGEOMETRY",
"IFCCONNECTIONSURFACEGEOMETRY",
"IFCCONNECTIONVOLUMEGEOMETRY",
];
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum ConnectionGeometryAnomaly {
Dangling {
boundary: EntityId,
target: EntityId,
},
WrongKind {
boundary: EntityId,
target: EntityId,
type_name: String,
},
NotAReference {
boundary: EntityId,
},
MissingBoundary {
boundary: EntityId,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SpaceBoundary {
pub id: EntityId,
pub type_name: String,
pub space: Option<EntityId>,
pub element: Option<EntityId>,
pub physicality: BoundaryPhysicality,
pub exposure: BoundaryExposure,
pub parent: Option<EntityId>,
pub corresponding: Option<EntityId>,
}
impl SpaceBoundary {
#[must_use]
pub fn physical_matches_element(&self, model: &Model) -> Option<bool> {
let element = self.element?;
let entity = model.get(element)?;
let name = entity.type_name.to_ascii_uppercase();
let is_virtual = name == "IFCVIRTUALELEMENT";
let is_opening = name == "IFCOPENINGELEMENT";
Some(match self.physicality {
BoundaryPhysicality::Physical => !is_virtual,
BoundaryPhysicality::Virtual => is_virtual || is_opening,
BoundaryPhysicality::NotDefined => true,
BoundaryPhysicality::Unrecognized => false,
})
}
pub fn connection_geometry(
&self,
model: &Model,
) -> Result<Option<EntityId>, ConnectionGeometryAnomaly> {
let boundary = self.id;
let entity = model
.get(boundary)
.ok_or(ConnectionGeometryAnomaly::MissingBoundary { boundary })?;
let target = match entity.attribute(slot::CONNECTION_GEOMETRY) {
None | Some(Value::Null) => return Ok(None),
Some(Value::Ref(target)) => *target,
Some(_) => return Err(ConnectionGeometryAnomaly::NotAReference { boundary }),
};
let resolved = model
.get(target)
.ok_or(ConnectionGeometryAnomaly::Dangling { boundary, target })?;
let type_name = resolved.type_name.to_ascii_uppercase();
if CONNECTION_GEOMETRY_TYPES.contains(&type_name.as_str()) {
Ok(Some(target))
} else {
Err(ConnectionGeometryAnomaly::WrongKind {
boundary,
target,
type_name,
})
}
}
}
fn physicality(model: &Model, id: EntityId, slot: usize) -> BoundaryPhysicality {
match enum_text(model, id, slot).as_deref() {
None => BoundaryPhysicality::NotDefined,
Some("PHYSICAL") => BoundaryPhysicality::Physical,
Some("VIRTUAL") => BoundaryPhysicality::Virtual,
Some("NOTDEFINED") => BoundaryPhysicality::NotDefined,
Some(_) => BoundaryPhysicality::Unrecognized,
}
}
fn exposure(model: &Model, id: EntityId, slot: usize) -> BoundaryExposure {
match enum_text(model, id, slot).as_deref() {
None => BoundaryExposure::NotDefined,
Some("INTERNAL") => BoundaryExposure::Internal,
Some("EXTERNAL") => BoundaryExposure::External,
Some("EXTERNAL_EARTH" | "EXTERNAL_WATER" | "EXTERNAL_FIRE") => {
BoundaryExposure::ExternalVariant
}
Some("NOTDEFINED") => BoundaryExposure::NotDefined,
Some(_) => BoundaryExposure::Unrecognized,
}
}
fn enum_text(model: &Model, id: EntityId, slot: usize) -> Option<String> {
let entity = model.get(id)?;
match entity.attribute(slot)? {
Value::Enum(text) => Some(text.to_ascii_uppercase()),
_ => None,
}
}
#[must_use]
pub fn all(model: &Model) -> Vec<SpaceBoundary> {
let mut out = Vec::new();
for slots in SPACE_BOUNDARY_TYPES {
for id in model.ids_of_type(slots.type_name) {
let Some(entity) = model.get(*id) else {
continue;
};
out.push(SpaceBoundary {
id: *id,
type_name: entity.type_name.to_ascii_uppercase(),
space: refs_in_slot(model, *id, slots.relating).into_iter().next(),
element: refs_in_slot(model, *id, slots.related).into_iter().next(),
physicality: physicality(model, *id, slot::PHYSICAL_OR_VIRTUAL),
exposure: exposure(model, *id, slot::INTERNAL_OR_EXTERNAL),
parent: refs_in_slot(model, *id, slot::PARENT_BOUNDARY)
.into_iter()
.next(),
corresponding: refs_in_slot(model, *id, slot::CORRESPONDING_BOUNDARY)
.into_iter()
.next(),
});
}
}
out
}
#[must_use]
pub fn of_space(model: &Model, space: EntityId) -> Vec<SpaceBoundary> {
all(model)
.into_iter()
.filter(|boundary| boundary.space == Some(space))
.collect()
}
#[must_use]
pub fn of_element(model: &Model, element: EntityId) -> Vec<SpaceBoundary> {
all(model)
.into_iter()
.filter(|boundary| boundary.element == Some(element))
.collect()
}