use ifc_model::{EntityId, Model, Value};
use ifc_schema::ifc4;
use crate::error::SystemAnomaly;
pub(crate) mod slot {
pub const ASSIGNS_RELATED: usize = 4;
pub const ASSIGNS_GROUP: usize = 6;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct System {
pub id: EntityId,
pub type_name: String,
pub name: Option<String>,
pub members: Vec<EntityId>,
}
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,
}
}
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(),
}
}
pub fn systems(model: &Model) -> (Vec<System>, Vec<SystemAnomaly>) {
let mut anomalies = Vec::new();
let mut members: std::collections::BTreeMap<EntityId, Vec<EntityId>> =
std::collections::BTreeMap::new();
for &relation in model.ids_of_type("IFCRELASSIGNSTOGROUP") {
let Some(entity) = model.get(relation) else {
continue;
};
let group = match entity.attributes.get(slot::ASSIGNS_GROUP) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
let Some(group_entity) = model.get(group) else {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: group,
});
continue;
};
if !ifc4().is_a(&group_entity.type_name.to_ascii_uppercase(), "IFCSYSTEM") {
anomalies.push(SystemAnomaly::NotASystem {
relation,
group,
type_name: group_entity.type_name.to_ascii_uppercase(),
});
continue;
}
for member in refs(entity.attributes.get(slot::ASSIGNS_RELATED)) {
if model.get(member).is_none() {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: member,
});
continue;
}
members.entry(group).or_default().push(member);
}
}
let mut systems = Vec::new();
for id in system_ids(model) {
let Some(entity) = model.get(id) else {
continue;
};
systems.push(System {
id,
type_name: entity.type_name.to_ascii_uppercase(),
name: text(model, id, 2),
members: members.remove(&id).unwrap_or_default(),
});
}
(systems, anomalies)
}
fn system_ids(model: &Model) -> Vec<EntityId> {
let schema = ifc4();
let mut out = Vec::new();
for (type_name, _) in model.type_histogram() {
if schema.is_a(type_name, "IFCSYSTEM") {
out.extend_from_slice(model.ids_of_type(type_name));
}
}
out.sort_unstable();
out
}