use std::collections::{BTreeMap, BTreeSet};
use ifc_model::{EntityId, Model, Value};
use ifc_schema::ifc4;
use crate::error::SystemAnomaly;
mod slot {
pub const LONG_NAME: usize = 5;
pub const ASSIGNS_MEMBERS: usize = 4;
pub const ASSIGNS_GROUP: usize = 6;
}
const ZONE_MEMBER_TYPES: [&str; 3] = ["IFCZONE", "IFCSPACE", "IFCSPATIALZONE"];
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Zone {
pub id: EntityId,
pub name: Option<String>,
pub long_name: Option<String>,
pub members: Vec<EntityId>,
}
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(),
}
}
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 zones(model: &Model) -> (Vec<Zone>, Vec<SystemAnomaly>) {
let mut anomalies = Vec::new();
let schema = ifc4();
let mut zone_ids = BTreeSet::new();
for (type_name, _) in model.type_histogram() {
if schema.is_a(type_name, "IFCZONE") {
zone_ids.extend(model.ids_of_type(type_name).iter().copied());
}
}
let mut members: BTreeMap<EntityId, Vec<EntityId>> = 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,
};
if !zone_ids.contains(&group) {
continue;
}
for member in refs(entity.attributes.get(slot::ASSIGNS_MEMBERS)) {
let Some(member_entity) = model.get(member) else {
anomalies.push(SystemAnomaly::Dangling {
relation,
missing: member,
});
continue;
};
let type_name = member_entity.type_name.to_ascii_uppercase();
let permitted = ZONE_MEMBER_TYPES
.iter()
.any(|allowed| schema.is_a(&type_name, allowed));
if permitted {
members.entry(group).or_default().push(member);
} else {
anomalies.push(SystemAnomaly::ZoneMemberNotSpatial {
relation,
zone: group,
member,
type_name: member_entity.type_name.to_string(),
});
}
}
}
let zones = zone_ids
.into_iter()
.map(|id| {
let mut member_ids = members.remove(&id).unwrap_or_default();
member_ids.sort_unstable();
member_ids.dedup();
Zone {
id,
name: text(model, id, 2),
long_name: text(model, id, slot::LONG_NAME),
members: member_ids,
}
})
.collect();
(zones, anomalies)
}