use std::sync::Arc;
use ifc_geometry::{product_world_transform, units, GeometryError, Transform};
use ifc_model::{Entity, EntityId, Model, Value};
use ifc_properties::{exact_unit, ExactPredefinedSet, ExactSource, ExactUnitError};
use ifc_schema::{Schema, TypeKind};
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ReadError {
Malformed {
entity: EntityId,
attribute: &'static str,
},
Unit(ExactUnitError),
Placement(GeometryError),
}
fn attribute<'m>(
schema: &'static Schema,
entity: &'m Entity,
name: &str,
) -> Option<(&'m Value, &'static str)> {
let (slot, declaration) = schema
.attributes(&entity.type_name)
.into_iter()
.enumerate()
.find(|(_, attribute)| attribute.name.eq_ignore_ascii_case(name))?;
Some((&entity.attributes[slot], declaration.type_name.as_str()))
}
pub(crate) fn enumeration(
schema: &'static Schema,
id: EntityId,
entity: &Entity,
name: &'static str,
) -> Result<Option<Arc<str>>, ReadError> {
let malformed = ReadError::Malformed {
entity: id,
attribute: name,
};
let Some((value, declared)) = attribute(schema, entity, name) else {
return Ok(None);
};
let members = match schema.type_def(declared).map(|definition| &definition.kind) {
Some(TypeKind::Enumeration(members)) => members,
_ => return Err(malformed),
};
match value {
Value::Null => Ok(None),
Value::Enum(member) if members.iter().any(|m| m.eq_ignore_ascii_case(member)) => {
Ok(Some(member.to_ascii_uppercase().into()))
}
_ => Err(malformed),
}
}
pub(crate) fn type_object(
model: &Model,
schema: &'static Schema,
product: EntityId,
) -> Result<Option<EntityId>, ReadError> {
for id in model.ids_of_type("IFCRELDEFINESBYTYPE") {
let relation = model.get(*id).expect("type index is current");
let related = attribute(schema, relation, "RelatedObjects");
let relating = attribute(schema, relation, "RelatingType");
let (Some((Value::List(related), _)), Some((Value::Ref(type_id), _))) = (related, relating)
else {
return Err(ReadError::Malformed {
entity: *id,
attribute: "RelatingType",
});
};
if related.contains(&Value::Ref(product)) {
return Ok(Some(*type_id));
}
}
Ok(None)
}
pub(crate) fn positive_length(
model: &Model,
schema: &'static Schema,
id: EntityId,
entity: &Entity,
name: &'static str,
) -> Result<Option<f64>, ReadError> {
match attribute(schema, entity, name) {
Some((Value::Null, _)) => Ok(None),
Some((Value::Real(length), _)) if length.is_finite() && *length > 0.0 => {
Ok(Some(length * length_scale(model)?))
}
_ => Err(ReadError::Malformed {
entity: id,
attribute: name,
}),
}
}
fn length_scale(model: &Model) -> Result<f64, ReadError> {
exact_unit(model, "IFCPOSITIVELENGTHMEASURE", None)
.map(|unit| unit.scale)
.map_err(ReadError::Unit)
}
pub(crate) fn world_transform(model: &Model, product: EntityId) -> Result<Transform, ReadError> {
let mut scale = units::resolve(model);
scale.length_to_metres = length_scale(model)?;
product_world_transform(model, &scale, product).map_err(ReadError::Placement)
}
pub(crate) fn governing(
sets: &[ExactPredefinedSet],
) -> Option<(ExactSource, Vec<&ExactPredefinedSet>)> {
let source = sets
.iter()
.find(|set| set.source == ExactSource::Occurrence)
.or_else(|| sets.first())
.map(|set| set.source)?;
let governing = sets.iter().filter(|set| set.source == source).collect();
Some((source, governing))
}