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;
42
43pub use boundary::{
44 connect_path_elements, connect_path_elements_with_owner_history, create_space_boundary,
45 create_space_boundary_with_owner_history, BoundaryDraft, BoundaryLevel,
46};
47pub use owned::{
48 aggregate_with_owner_history, contain_with_owner_history,
49 create_external_spatial_element_with_owner_history, create_project_library_with_owner_history,
50 create_project_with_owner_history, create_spatial_element_with_owner_history,
51};
52pub use owned_relationships::{
53 adhere_to_element_with_owner_history, assign_to_actor_with_owner_history,
54 assign_to_group_by_factor_with_owner_history, assign_to_process_with_owner_history,
55 assign_to_product_with_owner_history, assign_to_resource_with_owner_history,
56 associate_profile_def_with_owner_history, connect_elements_with_owner_history,
57 connect_with_realizing_elements_with_owner_history, control_flow_element_with_owner_history,
58 cover_elements_with_owner_history, cover_spaces_with_owner_history, declare_with_owner_history,
59 define_by_object_with_owner_history, fill_element_with_owner_history,
60 interfere_elements_with_owner_history, position_products_with_owner_history,
61 project_element_with_owner_history, serve_buildings_with_owner_history,
62 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, serve_buildings, void_element,
72};
73
74#[derive(Debug, Clone, Copy, Default)]
80#[non_exhaustive]
81pub struct SpatialDraft<'a> {
82 pub name: Option<&'a str>,
84 pub description: Option<&'a str>,
86 pub long_name: Option<&'a str>,
88 pub composition: Option<&'a str>,
90 pub placement: Option<EntityId>,
92 pub interior_or_exterior: Option<&'a str>,
97}
98
99impl<'a> SpatialDraft<'a> {
100 #[must_use]
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 #[must_use]
108 pub fn name(mut self, value: &'a str) -> Self {
109 self.name = Some(value);
110 self
111 }
112
113 #[must_use]
115 pub fn description(mut self, value: &'a str) -> Self {
116 self.description = Some(value);
117 self
118 }
119
120 #[must_use]
122 pub fn long_name(mut self, value: &'a str) -> Self {
123 self.long_name = Some(value);
124 self
125 }
126
127 #[must_use]
129 pub fn composition(mut self, value: &'a str) -> Self {
130 self.composition = Some(value);
131 self
132 }
133
134 #[must_use]
136 pub fn placement(mut self, value: EntityId) -> Self {
137 self.placement = Some(value);
138 self
139 }
140
141 #[must_use]
143 pub fn interior_or_exterior(mut self, value: &'a str) -> Self {
144 self.interior_or_exterior = Some(value);
145 self
146 }
147}
148
149pub(crate) const INTERIOR_OR_EXTERIOR: &str = "InteriorOrExteriorSpace";
152
153pub fn create_spatial_element(
172 tx: &mut Transaction,
173 kind: SpatialKind,
174 global_id: &str,
175 draft: SpatialDraft<'_>,
176) -> SpatialAuthoringResult<EntityId> {
177 let (type_name, width) = container(kind, global_id)?;
178 if draft.interior_or_exterior.is_some() {
181 return Err(SpatialAuthoringError::AuthoringNotInSchema {
182 entity: type_name,
183 attribute: INTERIOR_OR_EXTERIOR,
184 schema: ifc_schema::SchemaVersion::Ifc4,
185 });
186 }
187 let mut attributes = vec![Value::Null; width];
188 attributes[0] = Value::Text(global_id.into());
189 attributes[2] = optional_text(draft.name);
190 attributes[3] = optional_text(draft.description);
191 attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
192 attributes[7] = optional_text(draft.long_name);
193 attributes[8] = draft
194 .composition
195 .map_or(Value::Null, |t| Value::Enum(t.into()));
196 Ok(tx.create(Entity::new(type_name, attributes)))
197}
198
199fn container(kind: SpatialKind, global_id: &str) -> SpatialAuthoringResult<(&'static str, usize)> {
202 if Guid::parse(global_id).is_none() {
203 return Err(invalid("IFCSPATIALSTRUCTUREELEMENT", "GlobalId", global_id));
204 }
205 Ok(match kind {
209 SpatialKind::Site => ("IFCSITE", 14),
210 SpatialKind::Building => ("IFCBUILDING", 12),
211 SpatialKind::Storey => ("IFCBUILDINGSTOREY", 10),
212 SpatialKind::Space => ("IFCSPACE", 11),
213 other => {
217 return Err(invalid(
218 "IFCSPATIALSTRUCTUREELEMENT",
219 "kind",
220 format!("{other:?}"),
221 ))
222 }
223 })
224}
225
226pub(crate) fn optional_text(value: Option<&str>) -> Value {
227 value.map_or(Value::Null, |t| Value::Text(t.into()))
228}
229
230pub fn create_project(
245 tx: &mut Transaction,
246 global_id: &str,
247 name: Option<&str>,
248 units: Option<EntityId>,
249) -> SpatialAuthoringResult<EntityId> {
250 if Guid::parse(global_id).is_none() {
251 return Err(invalid("IFCPROJECT", "GlobalId", global_id));
252 }
253 let mut attributes = vec![Value::Null; 9];
254 attributes[0] = Value::Text(global_id.into());
255 attributes[2] = optional_text(name);
256 attributes[8] = units.map_or(Value::Null, Value::Ref);
257 Ok(tx.create(Entity::new("IFCPROJECT", attributes)))
258}
259
260fn relate(
265 tx: &mut Transaction,
266 rel: RelSlots,
267 global_id: &str,
268 parent: EntityId,
269 children: &[EntityId],
270) -> SpatialAuthoringResult<EntityId> {
271 check_relate(rel, global_id, parent, children)?;
272 let width = rel.relating.max(rel.related) + 1;
273 let mut attributes = vec![Value::Null; width];
274 attributes[0] = Value::Text(global_id.into());
275 attributes[rel.relating] = Value::Ref(parent);
276 attributes[rel.related] = Value::List(children.iter().copied().map(Value::Ref).collect());
277 Ok(tx.create(Entity::new(rel.type_name, attributes)))
278}
279
280fn relate_one(
287 tx: &mut Transaction,
288 rel: RelSlots,
289 global_id: &str,
290 relating: EntityId,
291 related: EntityId,
292) -> SpatialAuthoringResult<EntityId> {
293 check_relate_one(rel, global_id, relating, related)?;
294 let width = rel.relating.max(rel.related) + 1;
295 let mut attributes = vec![Value::Null; width];
296 attributes[0] = Value::Text(global_id.into());
297 attributes[rel.relating] = Value::Ref(relating);
298 attributes[rel.related] = Value::Ref(related);
299 Ok(tx.create(Entity::new(rel.type_name, attributes)))
300}
301
302fn check_relate(
305 rel: RelSlots,
306 global_id: &str,
307 parent: EntityId,
308 children: &[EntityId],
309) -> SpatialAuthoringResult<()> {
310 if Guid::parse(global_id).is_none() {
311 return Err(invalid(rel.type_name, "GlobalId", global_id));
312 }
313 if children.is_empty() {
314 return Err(invalid(rel.type_name, "RelatedObjects", "empty"));
315 }
316 if children.contains(&parent) {
317 return Err(invalid(
318 rel.type_name,
319 "RelatedObjects",
320 "contains the parent",
321 ));
322 }
323 Ok(())
324}
325
326fn check_relate_one(
328 rel: RelSlots,
329 global_id: &str,
330 relating: EntityId,
331 related: EntityId,
332) -> SpatialAuthoringResult<()> {
333 if Guid::parse(global_id).is_none() {
334 return Err(invalid(rel.type_name, "GlobalId", global_id));
335 }
336 if relating == related {
337 return Err(invalid(
338 rel.type_name,
339 "RelatedElement",
340 "is the relating element",
341 ));
342 }
343 Ok(())
344}
345
346pub fn aggregate(
358 tx: &mut Transaction,
359 global_id: &str,
360 parent: EntityId,
361 children: &[EntityId],
362) -> SpatialAuthoringResult<EntityId> {
363 relate(tx, AGGREGATES, global_id, parent, children)
364}
365
366pub fn contain(
382 tx: &mut Transaction,
383 global_id: &str,
384 structure: EntityId,
385 elements: &[EntityId],
386) -> SpatialAuthoringResult<EntityId> {
387 relate(tx, CONTAINED_IN, global_id, structure, elements)
388}