Skip to main content

ifc_properties/template/
property_set.rs

1//! `IfcPropertySetTemplate` and the sets it governs.
2//!
3//! ```text
4//! IfcPropertySetTemplate   TemplateType ApplicableEntity HasPropertyTemplates
5//! IfcRelDefinesByTemplate  RelatedPropertySets RelatingTemplate
6//! ```
7//!
8//! Every attribute is read by name from the bound release's table
9//! ([`Layout`]); both releases that define templates (IFC4 ADD2 TC1, IFC4X3
10//! ADD2) place them after the four `IfcRoot` attributes.
11//!
12//! A template DESCRIBES what a property set should contain; it does not carry
13//! values. Reading it as a property set yields nothing useful, which is why
14//! it lives here rather than in `pset`.
15
16use std::collections::BTreeMap;
17use std::sync::Arc;
18
19use ifc_model::{EntityId, Model, Value};
20
21use super::layout::Layout;
22use super::property::{member_templates, PropertyTemplate};
23use crate::error::{PropertyAnomaly, TemplateError};
24use crate::nesting::Nesting;
25
26/// An `IfcPropertySetTemplate` with its property templates.
27#[derive(Debug, Clone, PartialEq)]
28pub struct PropertySetTemplate {
29    /// The entity.
30    pub id: EntityId,
31    /// `Name`, required by `ExistsName`.
32    pub name: Option<Arc<str>>,
33    /// `Description`.
34    pub description: Option<Arc<str>>,
35    /// `TemplateType`, e.g. `PSET_TYPEDRIVENOVERRIDE`.
36    pub template_type: Option<Arc<str>>,
37    /// `ApplicableEntity`: which IFC entity the set applies to.
38    ///
39    /// A free-text identifier such as `IfcWall`, not a validated reference.
40    pub applicable_entity: Option<Arc<str>>,
41    /// Property templates, in file order, simple and complex, complex ones
42    /// with their nested templates.
43    pub properties: Vec<PropertyTemplate>,
44}
45
46impl PropertySetTemplate {
47    /// Look up a property template by name.
48    pub fn property(&self, name: &str) -> Option<&PropertyTemplate> {
49        self.properties
50            .iter()
51            .find(|p| p.name.as_deref() == Some(name))
52    }
53}
54
55/// Read one `IfcPropertySetTemplate` by id.
56///
57/// `None` when the entity is absent, is not a set template, or the release
58/// the header declares defines none (IFC2X3). Members the traversal refuses
59/// are left out without saying so; use [`property_set_template_checked`] to
60/// have each one reported.
61pub fn property_set_template(model: &Model, id: EntityId) -> Option<PropertySetTemplate> {
62    let mut anomalies = Vec::new();
63    read_set_template(model, Layout::permissive(model), id, &mut anomalies)
64}
65
66/// Read one `IfcPropertySetTemplate` bound to the release the model
67/// declares, reporting every malformed fact met on the way.
68///
69/// The anomalies are those described on
70/// [`property_template_checked`](crate::property_template_checked), for the
71/// set template and every template below it. Only nested members cost the
72/// traversal budget, as for property sets.
73///
74/// # Errors
75///
76/// [`TemplateError::Release`] or [`TemplateError::NoTemplates`] when the
77/// model binds to no release that has templates;
78/// [`TemplateError::MissingEntity`] or [`TemplateError::NotATemplate`] for
79/// the entity itself.
80pub fn property_set_template_checked(
81    model: &Model,
82    id: EntityId,
83) -> Result<(PropertySetTemplate, Vec<PropertyAnomaly>), TemplateError> {
84    let layout = Layout::declared(model)?;
85    let entity = model.get(id).ok_or(TemplateError::MissingEntity { id })?;
86    let mut anomalies = Vec::new();
87    let template = read_set_template(model, layout, id, &mut anomalies).ok_or(
88        TemplateError::NotATemplate {
89            id,
90            type_name: entity.type_name.to_string(),
91        },
92    )?;
93    Ok((template, anomalies))
94}
95
96pub(crate) fn read_set_template(
97    model: &Model,
98    layout: Layout,
99    id: EntityId,
100    anomalies: &mut Vec<PropertyAnomaly>,
101) -> Option<PropertySetTemplate> {
102    let entity = model.get(id)?;
103    if !layout.is_a(&entity.type_name, "IFCPROPERTYSETTEMPLATE") {
104        return None;
105    }
106    layout.check_arity(id, entity, anomalies);
107    let mut template = PropertySetTemplate {
108        id,
109        name: layout.text(id, entity, "Name", anomalies),
110        description: layout.text(id, entity, "Description", anomalies),
111        template_type: layout.enumeration(id, entity, "TemplateType", anomalies),
112        applicable_entity: layout.text(id, entity, "ApplicableEntity", anomalies),
113        properties: Vec::new(),
114    };
115    let mut nesting = Nesting::new(anomalies);
116    let members = layout.get(entity, "HasPropertyTemplates");
117    template.properties = member_templates(model, layout, id, members, &mut nesting);
118    Some(template)
119}
120
121/// Every template in the file, ascending by id.
122pub fn property_set_templates(model: &Model) -> Vec<PropertySetTemplate> {
123    let mut ids: Vec<_> = model.ids_of_type("IFCPROPERTYSETTEMPLATE").to_vec();
124    ids.sort_unstable();
125    ids.into_iter()
126        .filter_map(|id| property_set_template(model, id))
127        .collect()
128}
129
130/// Which templates define each property set, via `IfcRelDefinesByTemplate`.
131///
132/// A set with no entry is untemplated, which is normal: templates describe
133/// custom property sets and standard Psets rely on the published catalogue
134/// instead.
135///
136/// Each set maps to EVERY template defining it, ascending by id and without
137/// repeats. `IfcPropertySetDefinition.IsDefinedBy` is `SET [0:?] OF
138/// IfcRelDefinesByTemplate`, so several templates are legal. Before 0.4 this
139/// returned one template per set, and silently dropped the rest (#60).
140pub fn template_of_set(model: &Model) -> BTreeMap<EntityId, Vec<EntityId>> {
141    template_links(model, Layout::permissive(model))
142        .into_iter()
143        .map(|(set, links)| {
144            let mut templates: Vec<EntityId> = links.into_iter().map(|(_, t)| t).collect();
145            templates.sort_unstable();
146            templates.dedup();
147            (set, templates)
148        })
149        .collect()
150}
151
152/// Every `(relationship, template)` pair naming each set, in relationship
153/// id order.
154pub(crate) fn template_links(
155    model: &Model,
156    layout: Layout,
157) -> BTreeMap<EntityId, Vec<(EntityId, EntityId)>> {
158    let mut out: BTreeMap<EntityId, Vec<(EntityId, EntityId)>> = BTreeMap::new();
159    let mut relationships = model.ids_of_type("IFCRELDEFINESBYTEMPLATE").to_vec();
160    relationships.sort_unstable();
161    for id in relationships {
162        let Some(rel) = model.get(id) else { continue };
163        let Some(template) = layout.get(rel, "RelatingTemplate").and_then(one_ref) else {
164            continue;
165        };
166        let sets = match layout.get(rel, "RelatedPropertySets") {
167            Some(Value::List(items)) => items.iter().filter_map(one_ref).collect(),
168            _ => Vec::new(),
169        };
170        for set in sets {
171            out.entry(set).or_default().push((id, template));
172        }
173    }
174    out
175}
176
177fn one_ref(value: &Value) -> Option<EntityId> {
178    match value.unwrap_typed() {
179        Value::Ref(id) => Some(*id),
180        _ => None,
181    }
182}