use crate::error::SystemAnomaly;
use crate::flow::FlowDirection;
use crate::release::{self, Release};
use ifc_model::{EntityId, Model, Value};
pub(crate) mod slot {
pub const NESTS_PARENT: usize = 4;
pub const NESTS_CHILDREN: usize = 5;
pub const PORT_TO_ELEMENT_PORT: usize = 4;
pub const PORT_TO_ELEMENT_ELEMENT: usize = 5;
pub const FLOW_DIRECTION: usize = 7;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attachment {
Nests,
ConnectsPortToElement,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Port {
pub id: EntityId,
pub type_name: String,
pub name: Option<String>,
pub flow: FlowDirection,
pub element: Option<EntityId>,
pub attachment: Option<Attachment>,
}
fn port_ids(model: &Model, release: Release) -> Vec<EntityId> {
let mut out = Vec::new();
for (type_name, _) in model.type_histogram() {
if release.is_a(type_name, "IFCPORT") {
out.extend_from_slice(model.ids_of_type(type_name));
}
}
out.sort_unstable();
out
}
fn text(model: &Model, id: EntityId, slot: usize) -> Option<String> {
match model.get(id)?.attributes.get(slot)? {
Value::Text(t) => Some(t.to_string()),
_ => None,
}
}
pub fn ports(model: &Model) -> (Vec<Port>, Vec<SystemAnomaly>) {
let release = release::resolve_or_ifc4(model);
let mut anomalies = Vec::new();
let mut owner: std::collections::BTreeMap<EntityId, (EntityId, Attachment)> =
std::collections::BTreeMap::new();
let mut attach =
|port: EntityId, element: EntityId, how: Attachment, anomalies: &mut Vec<SystemAnomaly>| {
match owner.get(&port) {
Some((kept, _)) if *kept != element => {
anomalies.push(SystemAnomaly::PortAttachedTwice {
port,
kept: *kept,
rejected: element,
});
}
Some(_) => {}
None => {
owner.insert(port, (element, how));
}
}
};
for &relation in model.ids_of_type("IFCRELNESTS") {
let Some(entity) = model.get(relation) else {
continue;
};
let Some(Value::Ref(parent)) = entity.attributes.get(slot::NESTS_PARENT) else {
continue;
};
for child in refs(entity.attributes.get(slot::NESTS_CHILDREN)) {
let Some(child_entity) = model.get(child) else {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: child,
});
continue;
};
if !release.is_a(&child_entity.type_name.to_ascii_uppercase(), "IFCPORT") {
continue;
}
attach(child, *parent, Attachment::Nests, &mut anomalies);
}
}
for &relation in model.ids_of_type("IFCRELCONNECTSPORTTOELEMENT") {
let Some(entity) = model.get(relation) else {
continue;
};
let port = match entity.attributes.get(slot::PORT_TO_ELEMENT_PORT) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
let element = match entity.attributes.get(slot::PORT_TO_ELEMENT_ELEMENT) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
if model.get(port).is_none() {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: port,
});
continue;
}
if model.get(element).is_none() {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: element,
});
continue;
}
attach(
port,
element,
Attachment::ConnectsPortToElement,
&mut anomalies,
);
}
let mut ports = Vec::new();
for id in port_ids(model, release) {
let Some(entity) = model.get(id) else {
continue;
};
let attached = owner.get(&id);
ports.push(Port {
id,
type_name: entity.type_name.to_ascii_uppercase(),
name: text(model, id, 2),
flow: FlowDirection::parse(entity.attributes.get(slot::FLOW_DIRECTION)),
element: attached.map(|(e, _)| *e),
attachment: attached.map(|(_, how)| *how),
});
}
(ports, anomalies)
}
fn refs(value: Option<&Value>) -> Vec<EntityId> {
match value {
Some(Value::List(items)) => items
.iter()
.filter_map(|v| match v {
Value::Ref(id) => Some(*id),
_ => None,
})
.collect(),
Some(Value::Ref(id)) => vec![*id],
_ => Vec::new(),
}
}