use ifc_model::{EntityId, Model};
use ifc_schema::ifc4;
use crate::port::Port;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ElementRole {
Segment,
Fitting,
Terminal,
Controller,
MovingDevice,
StorageDevice,
TreatmentDevice,
EnergyConversionDevice,
Other,
}
impl ElementRole {
pub fn of(model: &Model, element: EntityId) -> Option<Self> {
let entity = model.get(element)?;
let name = entity.type_name.to_ascii_uppercase();
let schema = ifc4();
for (ancestor, role) in [
("IFCFLOWSEGMENT", Self::Segment),
("IFCFLOWFITTING", Self::Fitting),
("IFCFLOWTERMINAL", Self::Terminal),
("IFCFLOWCONTROLLER", Self::Controller),
("IFCFLOWMOVINGDEVICE", Self::MovingDevice),
("IFCFLOWSTORAGEDEVICE", Self::StorageDevice),
("IFCFLOWTREATMENTDEVICE", Self::TreatmentDevice),
("IFCENERGYCONVERSIONDEVICE", Self::EnergyConversionDevice),
] {
if schema.is_a(&name, ancestor) {
return Some(role);
}
}
if schema.is_a(&name, "IFCDISTRIBUTIONELEMENT") {
return Some(Self::Other);
}
None
}
pub fn is_endpoint(self) -> bool {
matches!(self, Self::Terminal)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum RoleInconsistency {
NoPath {
element: EntityId,
role: ElementRole,
has_inlet: bool,
has_outlet: bool,
},
UndirectedPorts {
element: EntityId,
count: usize,
},
}
pub fn role_inconsistencies(model: &Model, ports: &[Port]) -> Vec<RoleInconsistency> {
let mut by_element: std::collections::BTreeMap<EntityId, Vec<&Port>> =
std::collections::BTreeMap::new();
for port in ports {
if let Some(element) = port.element {
by_element.entry(element).or_default().push(port);
}
}
let mut out = Vec::new();
for (element, owned) in by_element {
let Some(role) = ElementRole::of(model, element) else {
continue;
};
let undirected = owned.iter().filter(|p| !p.flow.is_stated()).count();
if undirected > 0 {
out.push(RoleInconsistency::UndirectedPorts {
element,
count: undirected,
});
}
if role.is_endpoint() || owned.len() < 2 {
continue;
}
let has_inlet = owned.iter().any(|p| p.flow.accepts());
let has_outlet = owned.iter().any(|p| p.flow.emits());
let any_stated = owned.iter().any(|p| p.flow.is_stated());
if any_stated && !(has_inlet && has_outlet) {
out.push(RoleInconsistency::NoPath {
element,
role,
has_inlet,
has_outlet,
});
}
}
out
}