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#[derive(Debug, Clone, Copy, Default)]
36pub struct ExternalSpatialDraft<'a> {
37    /// `Name`.
38    pub name: Option<&'a str>,
39    /// `Description`.
40    pub description: Option<&'a str>,
41    /// `ObjectType`. Required when `predefined_type` is `USERDEFINED`.
42    pub object_type: Option<&'a str>,
43    /// `ObjectPlacement`.
44    pub placement: Option<EntityId>,
45    /// `Representation`.
46    pub representation: Option<EntityId>,
47    /// `LongName`.
48    pub long_name: Option<&'a str>,
49    /// `PredefinedType`, an `IfcExternalSpatialElementTypeEnum` token.
50    pub predefined_type: Option<&'a str>,
51}
52
53/// Stage an `IfcExternalSpatialElement`.
54///
55/// # Errors
56///
57/// Refuses a malformed GlobalId, a token outside
58/// `IfcExternalSpatialElementTypeEnum`, and `USERDEFINED` without
59/// `ObjectType`.
60pub fn create_external_spatial_element(
61    tx: &mut Transaction,
62    global_id: &str,
63    draft: ExternalSpatialDraft<'_>,
64) -> SpatialAuthoringResult<EntityId> {
65    const ENTITY: &str = "IFCEXTERNALSPATIALELEMENT";
66    if Guid::parse(global_id).is_none() {
67        return Err(invalid(ENTITY, "GlobalId", global_id));
68    }
69    if let Some(token) = draft.predefined_type {
70        if !EXTERNAL_KIND.contains(&token) {
71            return Err(invalid(ENTITY, "PredefinedType", token));
72        }
73        if token == "USERDEFINED"
74            && draft
75                .object_type
76                .is_none_or(|value| value.trim().is_empty())
77        {
78            return Err(invalid(ENTITY, "ObjectType", "required by USERDEFINED"));
79        }
80    }
81    let mut attributes = vec![Value::Null; 9];
82    attributes[0] = Value::Text(global_id.into());
83    attributes[2] = optional_text(draft.name);
84    attributes[3] = optional_text(draft.description);
85    attributes[4] = optional_text(draft.object_type);
86    attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
87    attributes[6] = draft.representation.map_or(Value::Null, Value::Ref);
88    attributes[7] = optional_text(draft.long_name);
89    attributes[8] = draft
90        .predefined_type
91        .map_or(Value::Null, |t| Value::Enum(t.into()));
92    Ok(tx.create(Entity::new(ENTITY, attributes)))
93}
94
95/// Attributes of an `IfcProjectLibrary`.
96#[derive(Debug, Clone, Copy, Default)]
97pub struct ProjectLibraryDraft<'a> {
98    /// `Name`.
99    pub name: Option<&'a str>,
100    /// `Description`.
101    pub description: Option<&'a str>,
102    /// `ObjectType`.
103    pub object_type: Option<&'a str>,
104    /// `LongName`.
105    pub long_name: Option<&'a str>,
106    /// `Phase`.
107    pub phase: Option<&'a str>,
108    /// `UnitsInContext`, an `IfcUnitAssignment`.
109    pub units: Option<EntityId>,
110}
111
112/// Stage an `IfcProjectLibrary`.
113///
114/// A library holds shared definitions -- types, properties,
115/// materials -- for reuse across projects. It is an `IfcContext`
116/// like `IfcProject`, not a container, so `RepresentationContexts`
117/// and `UnitsInContext` sit where a container keeps its placement.
118///
119/// `RepresentationContexts` is `SET [1:?]`: absent is legal,
120/// present and empty is not.
121///
122/// # Errors
123///
124/// Refuses a malformed GlobalId and an empty representation-context set.
125pub fn create_project_library(
126    tx: &mut Transaction,
127    global_id: &str,
128    draft: ProjectLibraryDraft<'_>,
129    representation_contexts: &[EntityId],
130) -> SpatialAuthoringResult<EntityId> {
131    const ENTITY: &str = "IFCPROJECTLIBRARY";
132    if Guid::parse(global_id).is_none() {
133        return Err(invalid(ENTITY, "GlobalId", global_id));
134    }
135    let mut attributes = vec![Value::Null; 9];
136    attributes[0] = Value::Text(global_id.into());
137    attributes[2] = optional_text(draft.name);
138    attributes[3] = optional_text(draft.description);
139    attributes[4] = optional_text(draft.object_type);
140    attributes[5] = optional_text(draft.long_name);
141    attributes[6] = optional_text(draft.phase);
142    if !representation_contexts.is_empty() {
143        attributes[7] = Value::List(
144            representation_contexts
145                .iter()
146                .copied()
147                .map(Value::Ref)
148                .collect(),
149        );
150    }
151    attributes[8] = draft.units.map_or(Value::Null, Value::Ref);
152    Ok(tx.create(Entity::new(ENTITY, attributes)))
153}