use std::collections::{HashMap, HashSet};
use ifc_model::{EntityId, Model, Value};
mod nests {
pub const RELATING: usize = 4;
pub const RELATED: usize = 5;
}
mod assigns {
pub const RELATED: usize = 4;
pub const RELATING: usize = 6;
}
pub const MAX_NESTING_DEPTH: usize = 64;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum CostRelationError {
NestingCycle {
repeated: EntityId,
path: Vec<EntityId>,
},
DepthExceeded {
at: EntityId,
limit: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum CostAnomaly {
NestedTwice {
item: EntityId,
kept: EntityId,
rejected: EntityId,
relation: EntityId,
},
}
fn kept_parents(model: &Model) -> HashMap<EntityId, EntityId> {
let mut kept = HashMap::new();
for (_, entity) in model.of_type("IFCRELNESTS") {
let Some(Value::Ref(parent)) = entity.attribute(nests::RELATING) else {
continue;
};
if let Some(v) = entity.attribute(nests::RELATED) {
v.for_each_ref(&mut |child| {
kept.entry(child).or_insert(*parent);
});
}
}
kept
}
#[must_use]
pub fn nesting_anomalies(model: &Model) -> Vec<CostAnomaly> {
let kept = kept_parents(model);
let mut out = Vec::new();
for (relation, entity) in model.of_type("IFCRELNESTS") {
let Some(Value::Ref(parent)) = entity.attribute(nests::RELATING) else {
continue;
};
if let Some(v) = entity.attribute(nests::RELATED) {
v.for_each_ref(&mut |item| {
if let Some(&first) = kept.get(&item) {
if first != *parent {
out.push(CostAnomaly::NestedTwice {
item,
kept: first,
rejected: *parent,
relation,
});
}
}
});
}
}
out
}
#[must_use]
pub fn children_of(model: &Model, parent: EntityId) -> Vec<EntityId> {
let kept = kept_parents(model);
let mut out = Vec::new();
for (_, entity) in model.of_type("IFCRELNESTS") {
let relating = match entity.attribute(nests::RELATING) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
if relating != parent {
continue;
}
if let Some(v) = entity.attribute(nests::RELATED) {
v.for_each_ref(&mut |id| {
if kept.get(&id) == Some(&parent) && !out.contains(&id) {
out.push(id);
}
});
}
}
out
}
#[must_use]
pub fn parent_of(model: &Model, child: EntityId) -> Option<EntityId> {
parents_of(model, child).into_iter().next()
}
#[must_use]
pub fn parents_of(model: &Model, child: EntityId) -> Vec<EntityId> {
let mut out = Vec::new();
for (_, entity) in model.of_type("IFCRELNESTS") {
let mut names_child = false;
if let Some(v) = entity.attribute(nests::RELATED) {
v.for_each_ref(&mut |id| {
if id == child {
names_child = true;
}
});
}
if !names_child {
continue;
}
if let Some(Value::Ref(parent)) = entity.attribute(nests::RELATING) {
if !out.contains(parent) {
out.push(*parent);
}
}
}
out
}
pub fn descendants_of(model: &Model, root: EntityId) -> Result<Vec<EntityId>, CostRelationError> {
let mut out = Vec::new();
let mut path = Vec::new();
let mut on_path = HashSet::new();
walk(model, root, &mut out, &mut path, &mut on_path)?;
Ok(out)
}
fn walk(
model: &Model,
node: EntityId,
out: &mut Vec<EntityId>,
path: &mut Vec<EntityId>,
on_path: &mut HashSet<EntityId>,
) -> Result<(), CostRelationError> {
if path.len() >= MAX_NESTING_DEPTH {
return Err(CostRelationError::DepthExceeded {
at: node,
limit: MAX_NESTING_DEPTH,
});
}
path.push(node);
on_path.insert(node);
for child in children_of(model, node) {
if on_path.contains(&child) {
let mut cycle = path.clone();
cycle.push(child);
return Err(CostRelationError::NestingCycle {
repeated: child,
path: cycle,
});
}
out.push(child);
walk(model, child, out, path, on_path)?;
}
path.pop();
on_path.remove(&node);
Ok(())
}
#[must_use]
pub fn controlled_by(model: &Model, control: EntityId) -> Vec<EntityId> {
let mut out = Vec::new();
for (_, entity) in model.of_type("IFCRELASSIGNSTOCONTROL") {
let relating = match entity.attribute(assigns::RELATING) {
Some(Value::Ref(id)) => *id,
_ => continue,
};
if relating != control {
continue;
}
if let Some(v) = entity.attribute(assigns::RELATED) {
v.for_each_ref(&mut |id| {
if !out.contains(&id) {
out.push(id);
}
});
}
}
out
}
#[must_use]
pub fn controls_of(model: &Model, object: EntityId) -> Vec<EntityId> {
let mut out = Vec::new();
for (_, entity) in model.of_type("IFCRELASSIGNSTOCONTROL") {
let mut names_object = false;
if let Some(v) = entity.attribute(assigns::RELATED) {
v.for_each_ref(&mut |id| {
if id == object {
names_object = true;
}
});
}
if !names_object {
continue;
}
if let Some(Value::Ref(control)) = entity.attribute(assigns::RELATING) {
if !out.contains(control) {
out.push(*control);
}
}
}
out
}