use std::collections::BTreeSet;
use ifc_model::{Entity, EntityId, Model, Value};
const MAX_DEPTH: usize = 16;
pub fn basis_surfaces(model: &Model, curve: EntityId) -> BTreeSet<EntityId> {
basis_with_depth(model, curve, 0)
}
fn basis_with_depth(model: &Model, curve: EntityId, depth: usize) -> BTreeSet<EntityId> {
let mut out = BTreeSet::new();
if depth > MAX_DEPTH {
return out;
}
let Some(entity) = model.get(curve) else {
return out;
};
let name = entity.type_name.to_ascii_uppercase();
if name == "IFCPCURVE" {
if let Some(surface) = slot_ref(entity, 0) {
out.insert(surface);
}
return out;
}
if crate::select::is_a(&name, "IFCCOMPOSITECURVEONSURFACE") {
return composite_intersection(model, entity, depth);
}
if crate::select::is_a(&name, "IFCSURFACECURVE") {
for pcurve in list_refs(entity, 1) {
out.extend(basis_with_depth(model, pcurve, depth + 1));
}
}
out
}
fn composite_intersection(model: &Model, entity: &Entity, depth: usize) -> BTreeSet<EntityId> {
let mut acc: Option<BTreeSet<EntityId>> = None;
for segment in list_refs(entity, 0) {
let Some(parent) = model.get(segment).and_then(|s| slot_ref(s, 2)) else {
continue;
};
let surfaces = basis_with_depth(model, parent, depth + 1);
acc = Some(match acc {
None => surfaces,
Some(prev) => prev.intersection(&surfaces).copied().collect(),
});
}
acc.unwrap_or_default()
}
fn slot_ref(entity: &Entity, slot: usize) -> Option<EntityId> {
match entity.attribute(slot).map(|v| v.unwrap_typed()) {
Some(Value::Ref(id)) => Some(*id),
_ => None,
}
}
fn list_refs(entity: &Entity, slot: usize) -> Vec<EntityId> {
match entity.attribute(slot).map(|v| v.unwrap_typed()) {
Some(Value::List(items)) => items
.iter()
.filter_map(|v| match v.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
})
.collect(),
_ => Vec::new(),
}
}