use ifc_model::{EntityId, Model};
use crate::error::SchemaResolutionError;
use crate::port::Port;
use crate::release::{self, Release};
#[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) -> Result<Option<Self>, SchemaResolutionError> {
Ok(Self::in_release(release::resolve(model)?, model, element))
}
fn in_release(schema: Release, model: &Model, element: EntityId) -> Option<Self> {
let entity = model.get(element)?;
let name = entity.type_name.to_ascii_uppercase();
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)]
#[non_exhaustive]
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],
) -> Result<Vec<RoleInconsistency>, SchemaResolutionError> {
let release = release::resolve(model)?;
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::in_release(release, 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,
});
}
}
Ok(out)
}