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}