Skip to main content

ifc_spatial/authoring/
external.rs

1//! Staging the external spatial element and the project library.
2//!
3//! # Two records that are not spatial containers
4//!
5//! `IfcExternalSpatialElement` is a product: it carries a placement
6//! and a representation, and describes space *outside* the building
7//! envelope -- the air a facade radiates into, the earth a
8//! foundation sits in. `IfcProjectLibrary` is an `IfcContext`,
9//! sharing `IfcProject`'s tail rather than a container's.
10//!
11//! Neither is an `IfcSpatialStructureElement`, so neither can use
12//! the container path: slot 5 onward means something different in
13//! each of the three shapes and sharing one path would file a
14//! placement as a long name.
15
16use ifc_model::guid::Guid;
17use ifc_model::{Entity, EntityId, Transaction, Value};
18
19use super::{invalid, optional_text, SpatialAuthoringResult};
20
21/// `IfcExternalSpatialElementTypeEnum`.
22///
23/// Closed: a token outside it names a kind of exterior space the
24/// schema does not define.
25const EXTERNAL_KIND: &[&str] = &[
26    "EXTERNAL",
27    "EXTERNAL_EARTH",
28    "EXTERNAL_FIRE",
29    "EXTERNAL_WATER",
30    "USERDEFINED",
31    "NOTDEFINED",
32];
33
34/// Attributes of an `IfcExternalSpatialElement`.
35///
36/// `#[non_exhaustive]`: build it with [`ExternalSpatialDraft::new`] and the
37/// setters.
38#[derive(Debug, Clone, Copy, Default)]
39#[non_exhaustive]
40pub struct ExternalSpatialDraft<'a> {
41    /// `Name`.
42    pub name: Option<&'a str>,
43    /// `Description`.
44    pub description: Option<&'a str>,
45    /// `ObjectType`. Required when `predefined_type` is `USERDEFINED`.
46    pub object_type: Option<&'a str>,
47    /// `ObjectPlacement`.
48    pub placement: Option<EntityId>,
49    /// `Representation`.
50    pub representation: Option<EntityId>,
51    /// `LongName`.
52    pub long_name: Option<&'a str>,
53    /// `PredefinedType`, an `IfcExternalSpatialElementTypeEnum` token.
54    pub predefined_type: Option<&'a str>,
55}
56
57impl<'a> ExternalSpatialDraft<'a> {
58    /// An empty draft: every attribute unset.
59    #[must_use]
60    pub fn new() -> Self {
61        Self::default()
62    }
63
64    /// Set `Name`.
65    #[must_use]
66    pub fn name(mut self, value: &'a str) -> Self {
67        self.name = Some(value);
68        self
69    }
70
71    /// Set `Description`.
72    #[must_use]
73    pub fn description(mut self, value: &'a str) -> Self {
74        self.description = Some(value);
75        self
76    }
77
78    /// Set `ObjectType`; required when `predefined_type` is `USERDEFINED`.
79    #[must_use]
80    pub fn object_type(mut self, value: &'a str) -> Self {
81        self.object_type = Some(value);
82        self
83    }
84
85    /// Set `ObjectPlacement`.
86    #[must_use]
87    pub fn placement(mut self, value: EntityId) -> Self {
88        self.placement = Some(value);
89        self
90    }
91
92    /// Set `Representation`.
93    #[must_use]
94    pub fn representation(mut self, value: EntityId) -> Self {
95        self.representation = Some(value);
96        self
97    }
98
99    /// Set `LongName`.
100    #[must_use]
101    pub fn long_name(mut self, value: &'a str) -> Self {
102        self.long_name = Some(value);
103        self
104    }
105
106    /// Set `PredefinedType`, an `IfcExternalSpatialElementTypeEnum` token.
107    #[must_use]
108    pub fn predefined_type(mut self, value: &'a str) -> Self {
109        self.predefined_type = Some(value);
110        self
111    }
112}
113
114/// Stage an `IfcExternalSpatialElement`.
115///
116/// IFC4 and IFC4X3 only: it writes their layout and leaves
117/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
118/// [`create_external_spatial_element_with_owner_history`](super::create_external_spatial_element_with_owner_history), which binds the model's declared
119/// release.
120///
121/// # Errors
122///
123/// Refuses a malformed GlobalId, a token outside
124/// `IfcExternalSpatialElementTypeEnum`, and `USERDEFINED` without
125/// `ObjectType`.
126pub fn create_external_spatial_element(
127    tx: &mut Transaction,
128    global_id: &str,
129    draft: ExternalSpatialDraft<'_>,
130) -> SpatialAuthoringResult<EntityId> {
131    check_external(global_id, &draft)?;
132    let mut attributes = vec![Value::Null; 9];
133    attributes[0] = Value::Text(global_id.into());
134    attributes[2] = optional_text(draft.name);
135    attributes[3] = optional_text(draft.description);
136    attributes[4] = optional_text(draft.object_type);
137    attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
138    attributes[6] = draft.representation.map_or(Value::Null, Value::Ref);
139    attributes[7] = optional_text(draft.long_name);
140    attributes[8] = draft
141        .predefined_type
142        .map_or(Value::Null, |t| Value::Enum(t.into()));
143    Ok(tx.create(Entity::new(EXTERNAL, attributes)))
144}
145
146pub(super) const EXTERNAL: &str = "IFCEXTERNALSPATIALELEMENT";
147
148/// The checks of [`create_external_spatial_element`].
149pub(super) fn check_external(
150    global_id: &str,
151    draft: &ExternalSpatialDraft<'_>,
152) -> SpatialAuthoringResult<()> {
153    if Guid::parse(global_id).is_none() {
154        return Err(invalid(EXTERNAL, "GlobalId", global_id));
155    }
156    if let Some(token) = draft.predefined_type {
157        if !EXTERNAL_KIND.contains(&token) {
158            return Err(invalid(EXTERNAL, "PredefinedType", token));
159        }
160        if token == "USERDEFINED"
161            && draft
162                .object_type
163                .is_none_or(|value| value.trim().is_empty())
164        {
165            return Err(invalid(EXTERNAL, "ObjectType", "required by USERDEFINED"));
166        }
167    }
168    Ok(())
169}
170
171/// Attributes of an `IfcProjectLibrary`.
172///
173/// `#[non_exhaustive]`: build it with [`ProjectLibraryDraft::new`] and the
174/// setters.
175#[derive(Debug, Clone, Copy, Default)]
176#[non_exhaustive]
177pub struct ProjectLibraryDraft<'a> {
178    /// `Name`.
179    pub name: Option<&'a str>,
180    /// `Description`.
181    pub description: Option<&'a str>,
182    /// `ObjectType`.
183    pub object_type: Option<&'a str>,
184    /// `LongName`.
185    pub long_name: Option<&'a str>,
186    /// `Phase`.
187    pub phase: Option<&'a str>,
188    /// `UnitsInContext`, an `IfcUnitAssignment`.
189    pub units: Option<EntityId>,
190}
191
192impl<'a> ProjectLibraryDraft<'a> {
193    /// An empty draft: every attribute unset.
194    #[must_use]
195    pub fn new() -> Self {
196        Self::default()
197    }
198
199    /// Set `Name`.
200    #[must_use]
201    pub fn name(mut self, value: &'a str) -> Self {
202        self.name = Some(value);
203        self
204    }
205
206    /// Set `Description`.
207    #[must_use]
208    pub fn description(mut self, value: &'a str) -> Self {
209        self.description = Some(value);
210        self
211    }
212
213    /// Set `ObjectType`.
214    #[must_use]
215    pub fn object_type(mut self, value: &'a str) -> Self {
216        self.object_type = Some(value);
217        self
218    }
219
220    /// Set `LongName`.
221    #[must_use]
222    pub fn long_name(mut self, value: &'a str) -> Self {
223        self.long_name = Some(value);
224        self
225    }
226
227    /// Set `Phase`.
228    #[must_use]
229    pub fn phase(mut self, value: &'a str) -> Self {
230        self.phase = Some(value);
231        self
232    }
233
234    /// Set `UnitsInContext`, an `IfcUnitAssignment`.
235    #[must_use]
236    pub fn units(mut self, value: EntityId) -> Self {
237        self.units = Some(value);
238        self
239    }
240}
241
242/// Stage an `IfcProjectLibrary`.
243///
244/// A library holds shared definitions -- types, properties,
245/// materials -- for reuse across projects. It is an `IfcContext`
246/// like `IfcProject`, not a container, so `RepresentationContexts`
247/// and `UnitsInContext` sit where a container keeps its placement.
248///
249/// `RepresentationContexts` is `SET [1:?]`: absent is legal,
250/// present and empty is not.
251///
252/// IFC4 and IFC4X3 only: it writes their layout and leaves
253/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
254/// [`create_project_library_with_owner_history`](super::create_project_library_with_owner_history), which binds the model's declared
255/// release.
256///
257/// # Errors
258///
259/// Refuses a malformed GlobalId and an empty representation-context set.
260pub fn create_project_library(
261    tx: &mut Transaction,
262    global_id: &str,
263    draft: ProjectLibraryDraft<'_>,
264    representation_contexts: &[EntityId],
265) -> SpatialAuthoringResult<EntityId> {
266    const ENTITY: &str = "IFCPROJECTLIBRARY";
267    if Guid::parse(global_id).is_none() {
268        return Err(invalid(ENTITY, "GlobalId", global_id));
269    }
270    let mut attributes = vec![Value::Null; 9];
271    attributes[0] = Value::Text(global_id.into());
272    attributes[2] = optional_text(draft.name);
273    attributes[3] = optional_text(draft.description);
274    attributes[4] = optional_text(draft.object_type);
275    attributes[5] = optional_text(draft.long_name);
276    attributes[6] = optional_text(draft.phase);
277    if !representation_contexts.is_empty() {
278        attributes[7] = Value::List(
279            representation_contexts
280                .iter()
281                .copied()
282                .map(Value::Ref)
283                .collect(),
284        );
285    }
286    attributes[8] = draft.units.map_or(Value::Null, Value::Ref);
287    Ok(tx.create(Entity::new(ENTITY, attributes)))
288}