use std::collections::BTreeMap;
use std::sync::Arc;
use ifc_model::{EntityId, Model, Value};
const ROOT_NAME: usize = 2;
const ROOT_DESCRIPTION: usize = 3;
const SET_TEMPLATE_TYPE: usize = 4;
const SET_APPLICABLE_ENTITY: usize = 5;
const SET_HAS_TEMPLATES: usize = 6;
const PROP_TEMPLATE_TYPE: usize = 4;
const PROP_PRIMARY_MEASURE: usize = 5;
const PROP_SECONDARY_MEASURE: usize = 6;
const PROP_PRIMARY_UNIT: usize = 8;
const REL_RELATED_SETS: usize = 4;
const REL_RELATING_TEMPLATE: usize = 5;
#[derive(Debug, Clone, PartialEq)]
pub struct PropertyTemplate {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub template_type: Option<Arc<str>>,
pub primary_measure: Option<Arc<str>>,
pub secondary_measure: Option<Arc<str>>,
pub primary_unit: Option<EntityId>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PropertySetTemplate {
pub id: EntityId,
pub name: Option<Arc<str>>,
pub description: Option<Arc<str>>,
pub template_type: Option<Arc<str>>,
pub applicable_entity: Option<Arc<str>>,
pub properties: Vec<PropertyTemplate>,
}
impl PropertySetTemplate {
pub fn property(&self, name: &str) -> Option<&PropertyTemplate> {
self.properties
.iter()
.find(|p| p.name.as_deref() == Some(name))
}
}
pub fn property_set_template(model: &Model, id: EntityId) -> Option<PropertySetTemplate> {
let entity = model.get(id)?;
if !entity
.type_name
.eq_ignore_ascii_case("IFCPROPERTYSETTEMPLATE")
{
return None;
}
let properties = entity
.attributes
.get(SET_HAS_TEMPLATES)
.and_then(refs)
.unwrap_or_default()
.into_iter()
.filter_map(|t| property_template(model, t))
.collect();
Some(PropertySetTemplate {
id,
name: entity.attributes.get(ROOT_NAME).and_then(text),
description: entity.attributes.get(ROOT_DESCRIPTION).and_then(text),
template_type: entity.attributes.get(SET_TEMPLATE_TYPE).and_then(enum_text),
applicable_entity: entity.attributes.get(SET_APPLICABLE_ENTITY).and_then(text),
properties,
})
}
pub fn property_template(model: &Model, id: EntityId) -> Option<PropertyTemplate> {
let entity = model.get(id)?;
Some(PropertyTemplate {
id,
name: entity.attributes.get(ROOT_NAME).and_then(text),
description: entity.attributes.get(ROOT_DESCRIPTION).and_then(text),
template_type: entity
.attributes
.get(PROP_TEMPLATE_TYPE)
.and_then(enum_text),
primary_measure: entity.attributes.get(PROP_PRIMARY_MEASURE).and_then(text),
secondary_measure: entity.attributes.get(PROP_SECONDARY_MEASURE).and_then(text),
primary_unit: entity.attributes.get(PROP_PRIMARY_UNIT).and_then(one_ref),
})
}
pub fn property_set_templates(model: &Model) -> Vec<PropertySetTemplate> {
let mut ids: Vec<_> = model.ids_of_type("IFCPROPERTYSETTEMPLATE").to_vec();
ids.sort_unstable();
ids.into_iter()
.filter_map(|id| property_set_template(model, id))
.collect()
}
pub fn template_of_set(model: &Model) -> BTreeMap<EntityId, EntityId> {
let mut out = BTreeMap::new();
for &id in model.ids_of_type("IFCRELDEFINESBYTEMPLATE") {
let Some(rel) = model.get(id) else { continue };
let Some(template) = rel.attributes.get(REL_RELATING_TEMPLATE).and_then(one_ref) else {
continue;
};
for set in rel
.attributes
.get(REL_RELATED_SETS)
.and_then(refs)
.unwrap_or_default()
{
out.insert(set, template);
}
}
out
}
fn text(value: &Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
Value::Text(t) => Some(t.clone()),
_ => None,
}
}
fn enum_text(value: &Value) -> Option<Arc<str>> {
match value.unwrap_typed() {
Value::Enum(t) | 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,
}
}