use ifc_model::{EntityId, Model, Value};
use crate::resource::direction::Direction;
use crate::resource::point::CartesianPoint;
const MAX_DEPTH: usize = 32;
pub fn dim_of(model: &Model, id: EntityId) -> Option<usize> {
dim_with_depth(model, id, 0)
}
fn dim_with_depth(model: &Model, id: EntityId, depth: usize) -> Option<usize> {
if depth > MAX_DEPTH {
return None;
}
let entity = model.get(id)?;
let name = entity.type_name.to_ascii_uppercase();
let is_a = |super_type: &str| crate::select::is_a(&name, super_type);
if name == "IFCCARTESIANPOINT" {
return CartesianPoint::new(id, entity)
.coordinates()
.ok()
.map(|c| c.len());
}
if name == "IFCDIRECTION" {
return Direction::new(id, entity).ratios().ok().map(|r| r.len());
}
if is_a("IFCSURFACE")
|| is_a("IFCSOLIDMODEL")
|| is_a("IFCHALFSPACESOLID")
|| is_a("IFCCSGPRIMITIVE3D")
|| is_a("IFCTESSELLATEDFACESET")
|| is_a("IFCSECTIONEDSPINE")
|| name == "IFCBOUNDINGBOX"
|| name == "IFCFACEBASEDSURFACEMODEL"
|| name == "IFCSHELLBASEDSURFACEMODEL"
{
return Some(3);
}
if is_a("IFCPLACEMENT") {
return slot_ref(entity, 0).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if is_a("IFCCARTESIANTRANSFORMATIONOPERATOR") {
return slot_ref(entity, 2).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if name == "IFCGEOMETRICSET" || name == "IFCGEOMETRICCURVESET" {
return first_of_list(entity, 0).and_then(|e| dim_with_depth(model, e, depth + 1));
}
if is_a("IFCBOOLEANRESULT") {
return slot_ref(entity, 1).and_then(|e| dim_with_depth(model, e, depth + 1));
}
if name == "IFCCOMPOSITECURVESEGMENT" {
return slot_ref(entity, 2).and_then(|c| dim_with_depth(model, c, depth + 1));
}
if is_a("IFCCURVE") {
return curve_dim(model, id, entity, &name, depth);
}
None
}
fn curve_dim(
model: &Model,
id: EntityId,
entity: &ifc_model::Entity,
name: &str,
depth: usize,
) -> Option<usize> {
let is_a = |super_type: &str| crate::select::is_a(name, super_type);
if name == "IFCOFFSETCURVE2D" {
return Some(2);
}
if name == "IFCOFFSETCURVE3D" || name == "IFCPCURVE" {
return Some(3);
}
if name == "IFCLINE" {
return slot_ref(entity, 0).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if is_a("IFCCONIC") {
return slot_ref(entity, 0).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if name == "IFCPOLYLINE" {
return first_of_list(entity, 0).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if name == "IFCTRIMMEDCURVE" {
return slot_ref(entity, 0).and_then(|c| dim_with_depth(model, c, depth + 1));
}
if is_a("IFCCOMPOSITECURVE") {
return first_of_list(entity, 0).and_then(|s| dim_with_depth(model, s, depth + 1));
}
if is_a("IFCBSPLINECURVE") {
return first_of_list(entity, 1).and_then(|p| dim_with_depth(model, p, depth + 1));
}
if name == "IFCINDEXEDPOLYCURVE" {
return slot_ref(entity, 0).and_then(|p| point_list_dim(model, p));
}
let _ = id;
None
}
fn point_list_dim(model: &Model, id: EntityId) -> Option<usize> {
let entity = model.get(id)?;
match entity.type_name.to_ascii_uppercase().as_str() {
"IFCCARTESIANPOINTLIST2D" => Some(2),
"IFCCARTESIANPOINTLIST3D" => Some(3),
_ => None,
}
}
fn slot_ref(entity: &ifc_model::Entity, slot: usize) -> Option<EntityId> {
match entity.attribute(slot)?.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}
}
fn first_of_list(entity: &ifc_model::Entity, slot: usize) -> Option<EntityId> {
match entity.attribute(slot)?.unwrap_typed() {
Value::List(items) => items.iter().find_map(|v| match v.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}),
_ => None,
}
}
pub fn list_refs(entity: &ifc_model::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(),
}
}
pub fn first_dim_disagreement(model: &Model, ids: &[EntityId]) -> Option<(usize, usize, EntityId)> {
let mut expected: Option<usize> = None;
for id in ids {
let Some(dim) = dim_of(model, *id) else {
continue;
};
match expected {
None => expected = Some(dim),
Some(first) if dim != first => return Some((first, dim, *id)),
Some(_) => {}
}
}
None
}
#[cfg(test)]
mod probe {
use super::*;
use ifc_model::{Entity, Value};
#[test]
fn a_pcurve_resolves_to_three_dimensions() {
let mut m = Model::new();
m.insert(
EntityId(1),
Entity::new(
"IFCCARTESIANPOINT",
vec![Value::List(vec![Value::Real(0.0), Value::Real(0.0)])],
),
);
m.insert(
EntityId(3),
Entity::new(
"IFCPOLYLINE",
vec![Value::List(vec![Value::Ref(EntityId(1))])],
),
);
m.insert(
EntityId(4),
Entity::new("IFCPCURVE", vec![Value::Null, Value::Ref(EntityId(3))]),
);
assert_eq!(dim_of(&m, EntityId(3)), Some(2), "polyline is 2D");
assert_eq!(
dim_of(&m, EntityId(4)),
Some(3),
"IfcCurveDim: p-curve is 3D"
);
}
}