Skip to main content

ifc_properties/pset/
template_authoring.rs

1//! Authoring for property templates and type-driven definition.
2//!
3//! Split from the parent module, which stages property instances.
4//! These entities sit one level up: a template declares what a set
5//! should contain before any instance exists, and
6//! `IfcRelDefinesByType` carries a type's properties to its
7//! occurrences -- the route `IfcRelDefinesByProperties` refuses.
8//!
9//! The template writers take no model and write `OwnerHistory` as `$`.
10//! That is valid in IFC4 and IFC4X3, the only releases that declare
11//! templates; IFC2X3 declares none of these entities. Their
12//! `*_with_owner_history` variants take the model and refuse an IFC2X3
13//! one with [`EntityNotInSchema`](crate::PropertyError::EntityNotInSchema)
14//! (#202).
15
16use ifc_model::{EntityId, Model, Transaction, Value};
17
18use super::authoring::{optional_text, require_name};
19use super::owned::{stage_ifc4, stage_owned, Rooted};
20use super::predefined::{invalid, require_guid, token};
21use crate::error::PropertyResult;
22
23/// `IfcRelDefinesByType` slots.
24pub mod defines_by_type_slot {
25    /// `GlobalId`. Required.
26    pub const GLOBAL_ID: usize = 0;
27    /// `RelatedObjects`. Required.
28    pub const RELATED_OBJECTS: usize = 4;
29    /// `RelatingType`. Required.
30    pub const RELATING_TYPE: usize = 5;
31}
32
33/// Attach a type object to its occurrences with `IfcRelDefinesByType`.
34///
35/// The counterpart of [`super::authoring::attach_property_set`]:
36/// properties carried by a
37/// type reach its occurrences through this relationship, which is why
38/// `IfcRelDefinesByProperties` refuses a type object outright.
39///
40/// # Release
41///
42/// Bound to the model's declared release like
43/// [`super::authoring::attach_property_set`]: laid out by attribute name,
44/// type checks against that release's inheritance, and an IFC2X3 model
45/// refused with [`AuthoringRequired`](crate::PropertyError::AuthoringRequired) because its
46/// `OwnerHistory` would be `$`. Use
47/// [`attach_type_with_owner_history`](crate::attach_type_with_owner_history)
48/// there.
49///
50/// # Errors
51///
52/// Refuses a malformed GUID, an empty occurrence list (`SET [1:?]`),
53/// a relating type that is not an `IfcTypeObject` subtype, and any
54/// occurrence that is itself a type object; a model that binds no single
55/// known release, and an IFC2X3 model. Nothing is staged on an error.
56pub fn attach_type(
57    tx: &mut Transaction,
58    model: &Model,
59    global_id: &str,
60    objects: &[EntityId],
61    relating_type: EntityId,
62) -> PropertyResult<EntityId> {
63    super::root_authoring::attach_type_record(tx, model, global_id, objects, relating_type, None)
64}
65
66/// `IfcPropertySetTemplate` slots.
67pub mod pset_template_slot {
68    /// `GlobalId`. Required.
69    pub const GLOBAL_ID: usize = 0;
70    /// `Name`.
71    pub const NAME: usize = 2;
72    /// `Description`.
73    pub const DESCRIPTION: usize = 3;
74    /// `TemplateType`.
75    pub const TEMPLATE_TYPE: usize = 4;
76    /// `ApplicableEntity`.
77    pub const APPLICABLE_ENTITY: usize = 5;
78    /// `HasPropertyTemplates`. Required.
79    pub const HAS_PROPERTY_TEMPLATES: usize = 6;
80}
81
82/// `IfcRelDefinesByTemplate` slots.
83pub mod defines_by_template_slot {
84    /// `GlobalId`. Required.
85    pub const GLOBAL_ID: usize = 0;
86    /// `RelatedPropertySets`. Required.
87    pub const RELATED_PROPERTY_SETS: usize = 4;
88    /// `RelatingTemplate`. Required.
89    pub const RELATING_TEMPLATE: usize = 5;
90}
91
92/// Stage an `IfcPropertySetTemplate`.
93///
94/// Declares what a property set should contain before any instance
95/// exists: which properties, of what measure, on which entities.
96///
97/// Takes no model, so it cannot refuse an IFC2X3 model, which declares no
98/// templates. Use [`add_property_set_template_with_owner_history`] to
99/// write against the model's declared release.
100///
101/// # Errors
102///
103/// Refuses a malformed GUID and an empty template list, which the
104/// schema types as `SET [1:?]`.
105pub fn add_property_set_template(
106    tx: &mut Transaction,
107    global_id: &str,
108    name: &str,
109    applicable_entity: Option<&str>,
110    templates: &[EntityId],
111) -> PropertyResult<EntityId> {
112    stage_ifc4(
113        tx,
114        set_template(global_id, name, applicable_entity, templates)?,
115    )
116}
117
118/// [`add_property_set_template`] in the model's declared release, with a
119/// caller-supplied `IfcOwnerHistory` (#202).
120///
121/// # Errors
122///
123/// Those of [`add_property_set_template`];
124/// [`EntityNotInSchema`](crate::PropertyError::EntityNotInSchema) in an
125/// IFC2X3 model, which declares no property templates; and the release and
126/// owner-history refusals of
127/// [`add_door_lining_properties_with_owner_history`](crate::add_door_lining_properties_with_owner_history).
128/// Nothing is staged on an error.
129pub fn add_property_set_template_with_owner_history(
130    tx: &mut Transaction,
131    model: &Model,
132    global_id: &str,
133    name: &str,
134    applicable_entity: Option<&str>,
135    templates: &[EntityId],
136    owner_history: EntityId,
137) -> PropertyResult<EntityId> {
138    let rooted = set_template(global_id, name, applicable_entity, templates)?;
139    stage_owned(tx, model, rooted, owner_history)
140}
141
142fn set_template<'a>(
143    global_id: &'a str,
144    name: &'a str,
145    applicable_entity: Option<&str>,
146    templates: &[EntityId],
147) -> PropertyResult<Rooted<'a>> {
148    const ENTITY: &str = "IFCPROPERTYSETTEMPLATE";
149    require_guid(ENTITY, global_id)?;
150    require_name(ENTITY, name)?;
151    if templates.is_empty() {
152        return Err(invalid(ENTITY, "HasPropertyTemplates", "empty"));
153    }
154    let values = vec![
155        ("ApplicableEntity", optional_text(applicable_entity)),
156        (
157            "HasPropertyTemplates",
158            Value::List(templates.iter().copied().map(Value::Ref).collect()),
159        ),
160    ];
161    Ok(Rooted {
162        entity: ENTITY,
163        global_id,
164        name: Some(name),
165        description: None,
166        values,
167    })
168}
169
170/// Stage an `IfcRelDefinesByTemplate`.
171///
172/// Binds authored property sets to the template they follow, which is
173/// how a checker knows an instance was meant to conform.
174///
175/// Takes no model, so it cannot refuse an IFC2X3 model, which declares no
176/// templates. Use [`attach_template_with_owner_history`] to write against
177/// the model's declared release.
178///
179/// # Errors
180///
181/// Refuses a malformed GUID and an empty property-set list
182/// (`SET [1:?]`).
183pub fn attach_template(
184    tx: &mut Transaction,
185    global_id: &str,
186    property_sets: &[EntityId],
187    template: EntityId,
188) -> PropertyResult<EntityId> {
189    stage_ifc4(tx, defines_by_template(global_id, property_sets, template)?)
190}
191
192/// [`attach_template`] in the model's declared release, with a
193/// caller-supplied `IfcOwnerHistory` (#202).
194///
195/// # Errors
196///
197/// Those of [`attach_template`], and those of
198/// [`add_property_set_template_with_owner_history`]. Nothing is staged on
199/// an error.
200pub fn attach_template_with_owner_history(
201    tx: &mut Transaction,
202    model: &Model,
203    global_id: &str,
204    property_sets: &[EntityId],
205    template: EntityId,
206    owner_history: EntityId,
207) -> PropertyResult<EntityId> {
208    let rooted = defines_by_template(global_id, property_sets, template)?;
209    stage_owned(tx, model, rooted, owner_history)
210}
211
212fn defines_by_template<'a>(
213    global_id: &'a str,
214    property_sets: &[EntityId],
215    template: EntityId,
216) -> PropertyResult<Rooted<'a>> {
217    const ENTITY: &str = "IFCRELDEFINESBYTEMPLATE";
218    require_guid(ENTITY, global_id)?;
219    if property_sets.is_empty() {
220        return Err(invalid(ENTITY, "RelatedPropertySets", "empty"));
221    }
222    let values = vec![
223        (
224            "RelatedPropertySets",
225            Value::List(property_sets.iter().copied().map(Value::Ref).collect()),
226        ),
227        ("RelatingTemplate", Value::Ref(template)),
228    ];
229    Ok(Rooted {
230        entity: ENTITY,
231        global_id,
232        name: None,
233        description: None,
234        values,
235    })
236}
237
238const COMPLEX_TEMPLATE_TYPE: &[&str] = &["P_COMPLEX", "Q_COMPLEX"];
239
240/// Stage an `IfcComplexPropertyTemplate`.
241///
242/// Takes no model, so it cannot refuse an IFC2X3 model, which declares no
243/// templates. Use [`add_complex_property_template_with_owner_history`] to
244/// write against the model's declared release.
245///
246/// # Errors
247///
248/// Refuses a malformed GlobalId, an empty template set (the attribute
249/// is `SET [1:?]` when present), a duplicate template reference, and a
250/// `TemplateType` outside its enumeration.
251///
252/// `NoSelfReference` needs no check: [`Transaction::create`] allocates
253/// the id as it stages the entity, so a caller cannot hold that id in
254/// order to pass it as one of its own children.
255///
256/// `UniquePropertyNames` is stated over the templates' names, which a
257/// staged entity cannot be read back to supply. Callers pass
258/// `(name, id)` pairs, matching `add_property_set`.
259pub fn add_complex_property_template(
260    tx: &mut Transaction,
261    global_id: &str,
262    name: Option<&str>,
263    usage: (Option<&str>, Option<&str>),
264    templates: &[(&str, EntityId)],
265) -> PropertyResult<EntityId> {
266    stage_ifc4(tx, complex_template(global_id, name, usage, templates)?)
267}
268
269/// [`add_complex_property_template`] in the model's declared release,
270/// with a caller-supplied `IfcOwnerHistory` (#202).
271///
272/// # Errors
273///
274/// Those of [`add_complex_property_template`], and those of
275/// [`add_property_set_template_with_owner_history`]. Nothing is staged on
276/// an error.
277pub fn add_complex_property_template_with_owner_history(
278    tx: &mut Transaction,
279    model: &Model,
280    global_id: &str,
281    name: Option<&str>,
282    usage: (Option<&str>, Option<&str>),
283    templates: &[(&str, EntityId)],
284    owner_history: EntityId,
285) -> PropertyResult<EntityId> {
286    let rooted = complex_template(global_id, name, usage, templates)?;
287    stage_owned(tx, model, rooted, owner_history)
288}
289
290fn complex_template<'a>(
291    global_id: &'a str,
292    name: Option<&'a str>,
293    (usage_name, template_type): (Option<&str>, Option<&str>),
294    templates: &[(&str, EntityId)],
295) -> PropertyResult<Rooted<'a>> {
296    const ENTITY: &str = "IFCCOMPLEXPROPERTYTEMPLATE";
297    require_guid(ENTITY, global_id)?;
298    let mut values = vec![("UsageName", optional_text(usage_name))];
299    if let Some(kind) = template_type {
300        values.push((
301            "TemplateType",
302            token(ENTITY, "TemplateType", kind, COMPLEX_TEMPLATE_TYPE)?,
303        ));
304    }
305    if !templates.is_empty() {
306        for (index, (child, _)) in templates.iter().enumerate() {
307            if templates[..index].iter().any(|(seen, _)| seen == child) {
308                return Err(invalid(ENTITY, "HasPropertyTemplates", (*child).to_owned()));
309            }
310        }
311        let children = templates.iter().map(|(_, id)| Value::Ref(*id));
312        values.push(("HasPropertyTemplates", Value::List(children.collect())));
313    }
314    Ok(Rooted {
315        entity: ENTITY,
316        global_id,
317        name,
318        description: None,
319        values,
320    })
321}