use std::collections::BTreeMap;
use std::sync::Arc;
use ifc_model::{EntityId, Model, Value};
use super::layout::Layout;
use super::property::{member_templates, PropertyTemplate};
use crate::error::{PropertyAnomaly, TemplateError};
use crate::nesting::Nesting;
#[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 mut anomalies = Vec::new();
read_set_template(model, Layout::permissive(model), id, &mut anomalies)
}
pub fn property_set_template_checked(
model: &Model,
id: EntityId,
) -> Result<(PropertySetTemplate, Vec<PropertyAnomaly>), TemplateError> {
let layout = Layout::declared(model)?;
let entity = model.get(id).ok_or(TemplateError::MissingEntity { id })?;
let mut anomalies = Vec::new();
let template = read_set_template(model, layout, id, &mut anomalies).ok_or(
TemplateError::NotATemplate {
id,
type_name: entity.type_name.to_string(),
},
)?;
Ok((template, anomalies))
}
pub(crate) fn read_set_template(
model: &Model,
layout: Layout,
id: EntityId,
anomalies: &mut Vec<PropertyAnomaly>,
) -> Option<PropertySetTemplate> {
let entity = model.get(id)?;
if !layout.is_a(&entity.type_name, "IFCPROPERTYSETTEMPLATE") {
return None;
}
layout.check_arity(id, entity, anomalies);
let mut template = PropertySetTemplate {
id,
name: layout.text(id, entity, "Name", anomalies),
description: layout.text(id, entity, "Description", anomalies),
template_type: layout.enumeration(id, entity, "TemplateType", anomalies),
applicable_entity: layout.text(id, entity, "ApplicableEntity", anomalies),
properties: Vec::new(),
};
let mut nesting = Nesting::new(anomalies);
let members = layout.get(entity, "HasPropertyTemplates");
template.properties = member_templates(model, layout, id, members, &mut nesting);
Some(template)
}
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, Vec<EntityId>> {
template_links(model, Layout::permissive(model))
.into_iter()
.map(|(set, links)| {
let mut templates: Vec<EntityId> = links.into_iter().map(|(_, t)| t).collect();
templates.sort_unstable();
templates.dedup();
(set, templates)
})
.collect()
}
pub(crate) fn template_links(
model: &Model,
layout: Layout,
) -> BTreeMap<EntityId, Vec<(EntityId, EntityId)>> {
let mut out: BTreeMap<EntityId, Vec<(EntityId, EntityId)>> = BTreeMap::new();
let mut relationships = model.ids_of_type("IFCRELDEFINESBYTEMPLATE").to_vec();
relationships.sort_unstable();
for id in relationships {
let Some(rel) = model.get(id) else { continue };
let Some(template) = layout.get(rel, "RelatingTemplate").and_then(one_ref) else {
continue;
};
let sets = match layout.get(rel, "RelatedPropertySets") {
Some(Value::List(items)) => items.iter().filter_map(one_ref).collect(),
_ => Vec::new(),
};
for set in sets {
out.entry(set).or_default().push((id, template));
}
}
out
}
fn one_ref(value: &Value) -> Option<EntityId> {
match value.unwrap_typed() {
Value::Ref(id) => Some(*id),
_ => None,
}
}