use std::collections::{BTreeMap, BTreeSet};
use ifc_model::{EntityId, Model, Value};
use crate::error::SystemAnomaly;
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(crate) mod slot {
pub const RELATED_ELEMENTS: usize = 4;
pub const RELATING_STRUCTURE: usize = 5;
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct SpatialPlacement {
pub contained_in: Option<EntityId>,
pub referenced_in: Vec<EntityId>,
}
impl SpatialPlacement {
pub fn all_structures(&self) -> Vec<EntityId> {
let mut out: Vec<_> = self.contained_in.into_iter().collect();
out.extend(self.referenced_in.iter().copied());
out.dedup();
out
}
}
pub fn spatial_placements(
model: &Model,
) -> (BTreeMap<EntityId, SpatialPlacement>, Vec<SystemAnomaly>) {
let mut out: BTreeMap<EntityId, SpatialPlacement> = BTreeMap::new();
let mut anomalies = Vec::new();
for &relation in model.ids_of_type("IFCRELCONTAINEDINSPATIALSTRUCTURE") {
let Some(entity) = model.get(relation) else {
continue;
};
let structure = match entity.attributes.get(slot::RELATING_STRUCTURE) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
for element in refs(entity.attributes.get(slot::RELATED_ELEMENTS)) {
let slot_entry = out.entry(element).or_default();
match slot_entry.contained_in {
Some(existing) if existing != structure => {
anomalies.push(SystemAnomaly::ContainedTwice {
element,
first: existing,
second: structure,
});
}
Some(_) => {}
None => slot_entry.contained_in = Some(structure),
}
}
}
let mut references: BTreeMap<EntityId, BTreeSet<EntityId>> = BTreeMap::new();
for &relation in model.ids_of_type("IFCRELREFERENCEDINSPATIALSTRUCTURE") {
let Some(entity) = model.get(relation) else {
continue;
};
let structure = match entity.attributes.get(slot::RELATING_STRUCTURE) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
for element in refs(entity.attributes.get(slot::RELATED_ELEMENTS)) {
references.entry(element).or_default().insert(structure);
}
}
for (element, structures) in references {
out.entry(element).or_default().referenced_in = structures.into_iter().collect();
}
(out, anomalies)
}