1use ifc_model::guid::Guid;
22use ifc_model::{Entity, EntityId, Transaction, Value};
23mod connections;
24mod error;
25mod external;
26mod owned;
27mod owned_relationships;
28pub(crate) mod release;
29
30pub(crate) use error::invalid;
31pub use error::{SpatialAuthoringError, SpatialAuthoringResult};
32
33pub use external::{
34 create_external_spatial_element, create_project_library, ExternalSpatialDraft,
35 ProjectLibraryDraft,
36};
37
38use crate::relation::slots::{RelSlots, AGGREGATES, CONTAINED_IN};
39
40mod boundary;
41mod relationships;
42mod services;
43
44pub use boundary::{
45 connect_path_elements, connect_path_elements_with_owner_history, create_space_boundary,
46 create_space_boundary_with_owner_history, BoundaryDraft, BoundaryLevel,
47};
48pub use owned::{
49 aggregate_with_owner_history, contain_with_owner_history,
50 create_external_spatial_element_with_owner_history, create_project_library_with_owner_history,
51 create_project_with_owner_history, create_spatial_element_with_owner_history,
52};
53pub use owned_relationships::{
54 adhere_to_element_with_owner_history, assign_to_actor_with_owner_history,
55 assign_to_group_by_factor_with_owner_history, assign_to_process_with_owner_history,
56 assign_to_product_with_owner_history, assign_to_resource_with_owner_history,
57 associate_profile_def_with_owner_history, connect_elements_with_owner_history,
58 connect_with_realizing_elements_with_owner_history, control_flow_element_with_owner_history,
59 cover_elements_with_owner_history, cover_spaces_with_owner_history, declare_with_owner_history,
60 define_by_object_with_owner_history, fill_element_with_owner_history,
61 interfere_elements_with_owner_history, position_products_with_owner_history,
62 project_element_with_owner_history, void_element_with_owner_history,
63};
64
65use crate::tree::SpatialKind;
66pub use connections::{connect_elements, connect_with_realizing_elements, interfere_elements};
67pub use relationships::{
68 adhere_to_element, assign_to_actor, assign_to_group_by_factor, assign_to_process,
69 assign_to_product, assign_to_resource, associate_profile_def, control_flow_element,
70 cover_elements, cover_spaces, declare, define_by_object, fill_element, position_products,
71 project_element, void_element,
72};
73pub use services::{serve_buildings, serve_buildings_with_owner_history};
74
75#[derive(Debug, Clone, Copy, Default)]
81#[non_exhaustive]
82pub struct SpatialDraft<'a> {
83 pub name: Option<&'a str>,
85 pub description: Option<&'a str>,
87 pub long_name: Option<&'a str>,
89 pub composition: Option<&'a str>,
91 pub placement: Option<EntityId>,
93 pub interior_or_exterior: Option<&'a str>,
98}
99
100impl<'a> SpatialDraft<'a> {
101 #[must_use]
103 pub fn new() -> Self {
104 Self::default()
105 }
106
107 #[must_use]
109 pub fn name(mut self, value: &'a str) -> Self {
110 self.name = Some(value);
111 self
112 }
113
114 #[must_use]
116 pub fn description(mut self, value: &'a str) -> Self {
117 self.description = Some(value);
118 self
119 }
120
121 #[must_use]
123 pub fn long_name(mut self, value: &'a str) -> Self {
124 self.long_name = Some(value);
125 self
126 }
127
128 #[must_use]
130 pub fn composition(mut self, value: &'a str) -> Self {
131 self.composition = Some(value);
132 self
133 }
134
135 #[must_use]
137 pub fn placement(mut self, value: EntityId) -> Self {
138 self.placement = Some(value);
139 self
140 }
141
142 #[must_use]
144 pub fn interior_or_exterior(mut self, value: &'a str) -> Self {
145 self.interior_or_exterior = Some(value);
146 self
147 }
148}
149
150pub(crate) const INTERIOR_OR_EXTERIOR: &str = "InteriorOrExteriorSpace";
153
154pub fn create_spatial_element(
173 tx: &mut Transaction,
174 kind: SpatialKind,
175 global_id: &str,
176 draft: SpatialDraft<'_>,
177) -> SpatialAuthoringResult<EntityId> {
178 let (type_name, width) = container(kind, global_id)?;
179 if draft.interior_or_exterior.is_some() {
182 return Err(SpatialAuthoringError::AuthoringNotInSchema {
183 entity: type_name,
184 attribute: INTERIOR_OR_EXTERIOR,
185 schema: ifc_schema::SchemaVersion::Ifc4,
186 });
187 }
188 let mut attributes = vec![Value::Null; width];
189 attributes[0] = Value::Text(global_id.into());
190 attributes[2] = optional_text(draft.name);
191 attributes[3] = optional_text(draft.description);
192 attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
193 attributes[7] = optional_text(draft.long_name);
194 attributes[8] = draft
195 .composition
196 .map_or(Value::Null, |t| Value::Enum(t.into()));
197 Ok(tx.create(Entity::new(type_name, attributes)))
198}
199
200fn container(kind: SpatialKind, global_id: &str) -> SpatialAuthoringResult<(&'static str, usize)> {
203 if Guid::parse(global_id).is_none() {
204 return Err(invalid("IFCSPATIALSTRUCTUREELEMENT", "GlobalId", global_id));
205 }
206 Ok(match kind {
210 SpatialKind::Site => ("IFCSITE", 14),
211 SpatialKind::Building => ("IFCBUILDING", 12),
212 SpatialKind::Storey => ("IFCBUILDINGSTOREY", 10),
213 SpatialKind::Space => ("IFCSPACE", 11),
214 other => {
218 return Err(invalid(
219 "IFCSPATIALSTRUCTUREELEMENT",
220 "kind",
221 format!("{other:?}"),
222 ))
223 }
224 })
225}
226
227pub(crate) fn optional_text(value: Option<&str>) -> Value {
228 value.map_or(Value::Null, |t| Value::Text(t.into()))
229}
230
231pub fn create_project(
246 tx: &mut Transaction,
247 global_id: &str,
248 name: Option<&str>,
249 units: Option<EntityId>,
250) -> SpatialAuthoringResult<EntityId> {
251 if Guid::parse(global_id).is_none() {
252 return Err(invalid("IFCPROJECT", "GlobalId", global_id));
253 }
254 let mut attributes = vec![Value::Null; 9];
255 attributes[0] = Value::Text(global_id.into());
256 attributes[2] = optional_text(name);
257 attributes[8] = units.map_or(Value::Null, Value::Ref);
258 Ok(tx.create(Entity::new("IFCPROJECT", attributes)))
259}
260
261fn relate(
266 tx: &mut Transaction,
267 rel: RelSlots,
268 global_id: &str,
269 parent: EntityId,
270 children: &[EntityId],
271) -> SpatialAuthoringResult<EntityId> {
272 check_relate(rel, global_id, parent, children)?;
273 let width = rel.relating.max(rel.related) + 1;
274 let mut attributes = vec![Value::Null; width];
275 attributes[0] = Value::Text(global_id.into());
276 attributes[rel.relating] = Value::Ref(parent);
277 attributes[rel.related] = Value::List(children.iter().copied().map(Value::Ref).collect());
278 Ok(tx.create(Entity::new(rel.type_name, attributes)))
279}
280
281fn relate_one(
288 tx: &mut Transaction,
289 rel: RelSlots,
290 global_id: &str,
291 relating: EntityId,
292 related: EntityId,
293) -> SpatialAuthoringResult<EntityId> {
294 check_relate_one(rel, global_id, relating, related)?;
295 let width = rel.relating.max(rel.related) + 1;
296 let mut attributes = vec![Value::Null; width];
297 attributes[0] = Value::Text(global_id.into());
298 attributes[rel.relating] = Value::Ref(relating);
299 attributes[rel.related] = Value::Ref(related);
300 Ok(tx.create(Entity::new(rel.type_name, attributes)))
301}
302
303fn check_relate(
306 rel: RelSlots,
307 global_id: &str,
308 parent: EntityId,
309 children: &[EntityId],
310) -> SpatialAuthoringResult<()> {
311 if Guid::parse(global_id).is_none() {
312 return Err(invalid(rel.type_name, "GlobalId", global_id));
313 }
314 if children.is_empty() {
315 return Err(invalid(rel.type_name, "RelatedObjects", "empty"));
316 }
317 if children.contains(&parent) {
318 return Err(invalid(
319 rel.type_name,
320 "RelatedObjects",
321 "contains the parent",
322 ));
323 }
324 Ok(())
325}
326
327fn check_relate_one(
329 rel: RelSlots,
330 global_id: &str,
331 relating: EntityId,
332 related: EntityId,
333) -> SpatialAuthoringResult<()> {
334 if Guid::parse(global_id).is_none() {
335 return Err(invalid(rel.type_name, "GlobalId", global_id));
336 }
337 if relating == related {
338 return Err(invalid(
339 rel.type_name,
340 "RelatedElement",
341 "is the relating element",
342 ));
343 }
344 Ok(())
345}
346
347pub fn aggregate(
359 tx: &mut Transaction,
360 global_id: &str,
361 parent: EntityId,
362 children: &[EntityId],
363) -> SpatialAuthoringResult<EntityId> {
364 relate(tx, AGGREGATES, global_id, parent, children)
365}
366
367pub fn contain(
383 tx: &mut Transaction,
384 global_id: &str,
385 structure: EntityId,
386 elements: &[EntityId],
387) -> SpatialAuthoringResult<EntityId> {
388 relate(tx, CONTAINED_IN, global_id, structure, elements)
389}