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}