use std::collections::BTreeMap;
use std::sync::Arc;
use ifc_model::{EntityId, Model, Value};
use ifc_schema::ifc4;
use crate::error::PropertyAnomaly;
use crate::pset::scalar::{property, Property};
const REL_RELATED_OBJECTS: usize = 4;
const REL_RELATING_DEFINITION: usize = 5;
const TYPE_HAS_PROPERTY_SETS: usize = 5;
const SET_HAS_PROPERTIES: usize = 4;
const ROOT_NAME: usize = 2;
const ROOT_DESCRIPTION: usize = 3;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Attachment {
Occurrence,
Type,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertySet {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub properties: Vec<Property>,
}
impl PropertySet {
pub fn property(&self, name: &str) -> Option<&Property> {
self.properties
.iter()
.find(|p| p.name.as_deref() == Some(name))
}
}
pub fn property_set(model: &Model, id: EntityId) -> Option<PropertySet> {
let entity = model.get(id)?;
if !entity.type_name.eq_ignore_ascii_case("IFCPROPERTYSET") {
return None;
}
let properties = entity
.attributes
.get(SET_HAS_PROPERTIES)
.and_then(refs)
.unwrap_or_default()
.into_iter()
.filter_map(|p| property(model, p))
.collect();
Some(PropertySet {
id,
name: entity.attributes.get(ROOT_NAME).and_then(text),
description: entity.attributes.get(ROOT_DESCRIPTION).and_then(text),
properties,
})
}
pub type AttachedSets = BTreeMap<EntityId, Vec<(Attachment, PropertySet)>>;
pub fn property_sets_by_object(model: &Model) -> (AttachedSets, Vec<PropertyAnomaly>) {
let mut out: AttachedSets = BTreeMap::new();
let mut anomalies = Vec::new();
let schema = ifc4();
for &id in model.ids_of_type("IFCRELDEFINESBYPROPERTIES") {
let Some(rel) = model.get(id) else { continue };
let Some(definition) = rel
.attributes
.get(REL_RELATING_DEFINITION)
.and_then(one_ref)
else {
continue;
};
let Some(set) = property_set(model, definition) else {
if model.get(definition).is_none() {
anomalies.push(PropertyAnomaly::MissingDefinition {
relationship: id,
definition,
});
}
continue;
};
for object in rel
.attributes
.get(REL_RELATED_OBJECTS)
.and_then(refs)
.unwrap_or_default()
{
let Some(target) = model.get(object) else {
anomalies.push(PropertyAnomaly::MissingObject {
relationship: id,
object,
});
continue;
};
if schema.is_a(&target.type_name.to_ascii_uppercase(), "IFCTYPEOBJECT") {
anomalies.push(PropertyAnomaly::TypeAttachedByRelationship {
relationship: id,
type_object: object,
});
}
out.entry(object)
.or_default()
.push((Attachment::Occurrence, set.clone()));
}
}
for (type_name, _) in model.type_histogram() {
let upper = type_name.to_ascii_uppercase();
if !schema.is_a(&upper, "IFCTYPEOBJECT") {
continue;
}
for &id in model.ids_of_type(&upper) {
let Some(entity) = model.get(id) else {
continue;
};
for set_id in entity
.attributes
.get(TYPE_HAS_PROPERTY_SETS)
.and_then(refs)
.unwrap_or_default()
{
if let Some(set) = property_set(model, set_id) {
out.entry(id).or_default().push((Attachment::Type, set));
}
}
}
}
for sets in out.values_mut() {
sets.sort_by_key(|(_, set)| set.id);
}
(out, anomalies)
}
fn text(value: &Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
Value::Text(t) => Some(t.clone()),
_ => None,
}
}
fn one_ref(value: &Value) -> Option<EntityId> {
match value.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}
}
fn refs(value: &Value) -> Option<Vec<EntityId>> {
match value {
Value::List(items) => Some(items.iter().filter_map(one_ref).collect()),
_ => None,
}
}