Skip to main content

ifc_properties/template/
property.rs

1//! Property templates: `IfcSimplePropertyTemplate` and
2//! `IfcComplexPropertyTemplate`.
3//!
4//! The two concrete subtypes of `IfcPropertyTemplate` share only their
5//! `IfcRoot` attributes, so each record is read by attribute name through
6//! [`Layout`] and an attribute the entity does not declare stays unset.
7//!
8//! `IfcComplexPropertyTemplate.HasPropertyTemplates` nests further
9//! templates. The schema forbids only a direct self-member
10//! (`NoSelfReference`), and one template may be shared by several complex
11//! templates (`PartOfComplexTemplate` is `SET [0:?]`), so nested templates
12//! are read through [`Nesting`] exactly like nested complex properties: the
13//! path from the root cuts a cycle of any length, and a depth bound and a
14//! member budget cap the work, each cut reported as a [`PropertyAnomaly`].
15
16use std::collections::BTreeMap;
17use std::sync::Arc;
18
19use ifc_model::{EntityId, Model, Value};
20
21use super::layout::Layout;
22use crate::error::{PropertyAnomaly, TemplateError};
23use crate::nesting::Nesting;
24
25/// Which concrete `IfcPropertyTemplate` a template is.
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27#[non_exhaustive]
28pub enum PropertyTemplateKind {
29    /// `IfcSimplePropertyTemplate`: one simple property or simple quantity.
30    Simple,
31    /// `IfcComplexPropertyTemplate`: a complex property or complex quantity
32    /// whose members are the nested [`PropertyTemplate::templates`].
33    Complex,
34}
35
36/// A property template: what one property should look like.
37///
38/// Every attribute of both template entities has a field; the ones the
39/// entity does not declare are `None` (or empty). Enumeration constants are
40/// as written in the file, without their dots.
41#[derive(Debug, Clone, PartialEq)]
42#[non_exhaustive]
43pub struct PropertyTemplate {
44    /// The entity.
45    pub id: EntityId,
46    /// `Name`: the property name this template governs.
47    pub name: Option<Arc<str>>,
48    /// `Description`.
49    pub description: Option<Arc<str>>,
50    /// Simple or complex.
51    pub kind: PropertyTemplateKind,
52    /// `TemplateType`: an `IfcSimplePropertyTemplateTypeEnum` constant such
53    /// as `P_SINGLEVALUE` for a simple template, an
54    /// `IfcComplexPropertyTemplateTypeEnum` constant (`P_COMPLEX`,
55    /// `Q_COMPLEX`) for a complex one.
56    ///
57    /// This states which `IfcProperty` or `IfcPhysicalQuantity` subtype an
58    /// instance should use, so it is the link between a template and the
59    /// property families in `pset`.
60    pub template_type: Option<Arc<str>>,
61    /// `PrimaryMeasureType`, e.g. `IfcLengthMeasure` (simple only).
62    pub primary_measure: Option<Arc<str>>,
63    /// `SecondaryMeasureType`, used by bounded and table values (simple
64    /// only).
65    pub secondary_measure: Option<Arc<str>>,
66    /// `Enumerators`: the `IfcPropertyEnumeration` an enumerated value
67    /// selects from (simple only).
68    pub enumerators: Option<EntityId>,
69    /// `PrimaryUnit` (simple only).
70    pub primary_unit: Option<EntityId>,
71    /// `SecondaryUnit`, the defined unit of a table value (simple only).
72    pub secondary_unit: Option<EntityId>,
73    /// `Expression`: a table's correlation or a quantity's formula (simple
74    /// only).
75    pub expression: Option<Arc<str>>,
76    /// `AccessState`, an `IfcStateEnum` constant such as `READONLY` (simple
77    /// only).
78    pub access_state: Option<Arc<str>>,
79    /// `UsageName` (complex only).
80    pub usage_name: Option<Arc<str>>,
81    /// `HasPropertyTemplates`, in file order (complex only; empty when `$`).
82    ///
83    /// Members the traversal refuses (cycle, depth, budget, absent entity,
84    /// non-template) are left out; the checked readers report each one.
85    pub templates: Vec<PropertyTemplate>,
86}
87
88impl PropertyTemplate {
89    /// Look up a nested template of a complex template by name.
90    pub fn template(&self, name: &str) -> Option<&PropertyTemplate> {
91        self.templates
92            .iter()
93            .find(|t| t.name.as_deref() == Some(name))
94    }
95}
96
97/// Read one property template by id.
98///
99/// `None` when the entity is absent or is neither an
100/// `IfcSimplePropertyTemplate` nor an `IfcComplexPropertyTemplate` in the
101/// release the header declares (the IFC4 ADD2 TC1 table when it declares
102/// none it bundles). Until #108 every entity was read with the simple
103/// template's layout, so a complex template reported its `UsageName` as its
104/// `TemplateType` and any other entity read as a template.
105///
106/// Nested templates the traversal refuses are left out without saying so;
107/// use [`property_template_checked`] to have each one reported.
108pub fn property_template(model: &Model, id: EntityId) -> Option<PropertyTemplate> {
109    let mut anomalies = Vec::new();
110    let mut nesting = Nesting::new(&mut anomalies);
111    read_template(model, Layout::permissive(model), id, &mut nesting)
112}
113
114/// Read one property template bound to the release the model declares,
115/// reporting every malformed fact met on the way.
116///
117/// The same value as [`property_template`], with a [`PropertyAnomaly`] for
118/// each nested member left out ([`PropertyAnomaly::ComplexCycle`],
119/// [`PropertyAnomaly::ComplexTooDeep`],
120/// [`PropertyAnomaly::ComplexBudgetExceeded`],
121/// [`PropertyAnomaly::MissingMember`], [`PropertyAnomaly::MemberNotReference`],
122/// [`PropertyAnomaly::DuplicateMember`], [`PropertyAnomaly::NotATemplate`]),
123/// each repeated template name ([`PropertyAnomaly::DuplicatePropertyName`]),
124/// each record of the wrong arity ([`PropertyAnomaly::SlotCountMismatch`])
125/// and each attribute its declared type does not admit
126/// ([`PropertyAnomaly::MalformedAttribute`]).
127///
128/// # Errors
129///
130/// [`TemplateError::Release`] or [`TemplateError::NoTemplates`] when the
131/// model binds to no release that has templates (IFC2X3 has none);
132/// [`TemplateError::MissingEntity`] or [`TemplateError::NotATemplate`] for
133/// the entity itself.
134pub fn property_template_checked(
135    model: &Model,
136    id: EntityId,
137) -> Result<(PropertyTemplate, Vec<PropertyAnomaly>), TemplateError> {
138    let layout = Layout::declared(model)?;
139    let entity = model.get(id).ok_or(TemplateError::MissingEntity { id })?;
140    let mut anomalies = Vec::new();
141    let mut nesting = Nesting::new(&mut anomalies);
142    let template =
143        read_template(model, layout, id, &mut nesting).ok_or(TemplateError::NotATemplate {
144            id,
145            type_name: entity.type_name.to_string(),
146        })?;
147    Ok((template, anomalies))
148}
149
150/// Read the template `id` with `layout`, or `None` when it is not one.
151pub(crate) fn read_template(
152    model: &Model,
153    layout: Layout,
154    id: EntityId,
155    nesting: &mut Nesting<'_>,
156) -> Option<PropertyTemplate> {
157    let entity = model.get(id)?;
158    let kind = if layout.is_a(&entity.type_name, "IFCSIMPLEPROPERTYTEMPLATE") {
159        PropertyTemplateKind::Simple
160    } else if layout.is_a(&entity.type_name, "IFCCOMPLEXPROPERTYTEMPLATE") {
161        PropertyTemplateKind::Complex
162    } else {
163        return None;
164    };
165    let mut found = Vec::new();
166    layout.check_arity(id, entity, &mut found);
167    let out = &mut found;
168    let mut template = PropertyTemplate {
169        id,
170        name: layout.text(id, entity, "Name", out),
171        description: layout.text(id, entity, "Description", out),
172        kind,
173        template_type: layout.enumeration(id, entity, "TemplateType", out),
174        primary_measure: layout.text(id, entity, "PrimaryMeasureType", out),
175        secondary_measure: layout.text(id, entity, "SecondaryMeasureType", out),
176        enumerators: layout.reference(model, id, entity, "Enumerators", out),
177        primary_unit: layout.reference(model, id, entity, "PrimaryUnit", out),
178        secondary_unit: layout.reference(model, id, entity, "SecondaryUnit", out),
179        expression: layout.text(id, entity, "Expression", out),
180        access_state: layout.enumeration(id, entity, "AccessState", out),
181        usage_name: layout.text(id, entity, "UsageName", out),
182        templates: Vec::new(),
183    };
184    for anomaly in found {
185        nesting.report(anomaly);
186    }
187    if kind == PropertyTemplateKind::Complex {
188        let members = layout.get(entity, "HasPropertyTemplates");
189        template.templates = nested_templates(model, layout, id, members, nesting);
190    }
191    Some(template)
192}
193
194/// Resolve the `HasPropertyTemplates` of `container` in file order.
195///
196/// `container` is a complex template, entered on the nesting path, or a
197/// property set template, whose own members cost no budget.
198pub(crate) fn member_templates(
199    model: &Model,
200    layout: Layout,
201    container: EntityId,
202    members: Option<&Value>,
203    nesting: &mut Nesting<'_>,
204) -> Vec<PropertyTemplate> {
205    let mut templates = Vec::new();
206    for member in nesting.members(container, "HasPropertyTemplates", members) {
207        if !nesting.admit(model, container, member) {
208            continue;
209        }
210        match read_template(model, layout, member, nesting) {
211            Some(template) => templates.push(template),
212            None => nesting.report(PropertyAnomaly::NotATemplate {
213                container,
214                member,
215                type_name: model
216                    .get(member)
217                    .map(|entity| entity.type_name.to_string())
218                    .unwrap_or_default(),
219            }),
220        }
221    }
222    duplicate_names(container, &templates, nesting);
223    templates
224}
225
226fn nested_templates(
227    model: &Model,
228    layout: Layout,
229    complex: EntityId,
230    members: Option<&Value>,
231    nesting: &mut Nesting<'_>,
232) -> Vec<PropertyTemplate> {
233    if !nesting.enter(complex) {
234        return Vec::new();
235    }
236    let templates = member_templates(model, layout, complex, members, nesting);
237    nesting.leave();
238    templates
239}
240
241/// `UniquePropertyNames`: report each template whose name an earlier one
242/// in the same container already has.
243fn duplicate_names(container: EntityId, templates: &[PropertyTemplate], nesting: &mut Nesting<'_>) {
244    let mut first: BTreeMap<&str, EntityId> = BTreeMap::new();
245    for template in templates {
246        let Some(name) = template.name.as_deref() else {
247            continue;
248        };
249        match first.get(name) {
250            Some(&kept) => nesting.report(PropertyAnomaly::DuplicatePropertyName {
251                set: container,
252                kept,
253                rejected: template.id,
254            }),
255            None => {
256                first.insert(name, template.id);
257            }
258        }
259    }
260}