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)]
76pub struct SpatialDraft<'a> {
77 pub name: Option<&'a str>,
79 pub description: Option<&'a str>,
81 pub long_name: Option<&'a str>,
83 pub composition: Option<&'a str>,
85 pub placement: Option<EntityId>,
87}
88
89pub fn create_spatial_element(
105 tx: &mut Transaction,
106 kind: SpatialKind,
107 global_id: &str,
108 draft: SpatialDraft<'_>,
109) -> SpatialAuthoringResult<EntityId> {
110 let (type_name, width) = container(kind, global_id)?;
111 let mut attributes = vec![Value::Null; width];
112 attributes[0] = Value::Text(global_id.into());
113 attributes[2] = optional_text(draft.name);
114 attributes[3] = optional_text(draft.description);
115 attributes[5] = draft.placement.map_or(Value::Null, Value::Ref);
116 attributes[7] = optional_text(draft.long_name);
117 attributes[8] = draft
118 .composition
119 .map_or(Value::Null, |t| Value::Enum(t.into()));
120 Ok(tx.create(Entity::new(type_name, attributes)))
121}
122
123fn container(kind: SpatialKind, global_id: &str) -> SpatialAuthoringResult<(&'static str, usize)> {
126 if Guid::parse(global_id).is_none() {
127 return Err(invalid("IFCSPATIALSTRUCTUREELEMENT", "GlobalId", global_id));
128 }
129 Ok(match kind {
133 SpatialKind::Site => ("IFCSITE", 14),
134 SpatialKind::Building => ("IFCBUILDING", 12),
135 SpatialKind::Storey => ("IFCBUILDINGSTOREY", 10),
136 SpatialKind::Space => ("IFCSPACE", 11),
137 other => {
141 return Err(invalid(
142 "IFCSPATIALSTRUCTUREELEMENT",
143 "kind",
144 format!("{other:?}"),
145 ))
146 }
147 })
148}
149
150pub(crate) fn optional_text(value: Option<&str>) -> Value {
151 value.map_or(Value::Null, |t| Value::Text(t.into()))
152}
153
154pub fn create_project(
169 tx: &mut Transaction,
170 global_id: &str,
171 name: Option<&str>,
172 units: Option<EntityId>,
173) -> SpatialAuthoringResult<EntityId> {
174 if Guid::parse(global_id).is_none() {
175 return Err(invalid("IFCPROJECT", "GlobalId", global_id));
176 }
177 let mut attributes = vec![Value::Null; 9];
178 attributes[0] = Value::Text(global_id.into());
179 attributes[2] = optional_text(name);
180 attributes[8] = units.map_or(Value::Null, Value::Ref);
181 Ok(tx.create(Entity::new("IFCPROJECT", attributes)))
182}
183
184fn relate(
189 tx: &mut Transaction,
190 rel: RelSlots,
191 global_id: &str,
192 parent: EntityId,
193 children: &[EntityId],
194) -> SpatialAuthoringResult<EntityId> {
195 check_relate(rel, global_id, parent, children)?;
196 let width = rel.relating.max(rel.related) + 1;
197 let mut attributes = vec![Value::Null; width];
198 attributes[0] = Value::Text(global_id.into());
199 attributes[rel.relating] = Value::Ref(parent);
200 attributes[rel.related] = Value::List(children.iter().copied().map(Value::Ref).collect());
201 Ok(tx.create(Entity::new(rel.type_name, attributes)))
202}
203
204fn relate_one(
211 tx: &mut Transaction,
212 rel: RelSlots,
213 global_id: &str,
214 relating: EntityId,
215 related: EntityId,
216) -> SpatialAuthoringResult<EntityId> {
217 check_relate_one(rel, global_id, relating, related)?;
218 let width = rel.relating.max(rel.related) + 1;
219 let mut attributes = vec![Value::Null; width];
220 attributes[0] = Value::Text(global_id.into());
221 attributes[rel.relating] = Value::Ref(relating);
222 attributes[rel.related] = Value::Ref(related);
223 Ok(tx.create(Entity::new(rel.type_name, attributes)))
224}
225
226fn check_relate(
229 rel: RelSlots,
230 global_id: &str,
231 parent: EntityId,
232 children: &[EntityId],
233) -> SpatialAuthoringResult<()> {
234 if Guid::parse(global_id).is_none() {
235 return Err(invalid(rel.type_name, "GlobalId", global_id));
236 }
237 if children.is_empty() {
238 return Err(invalid(rel.type_name, "RelatedObjects", "empty"));
239 }
240 if children.contains(&parent) {
241 return Err(invalid(
242 rel.type_name,
243 "RelatedObjects",
244 "contains the parent",
245 ));
246 }
247 Ok(())
248}
249
250fn check_relate_one(
252 rel: RelSlots,
253 global_id: &str,
254 relating: EntityId,
255 related: EntityId,
256) -> SpatialAuthoringResult<()> {
257 if Guid::parse(global_id).is_none() {
258 return Err(invalid(rel.type_name, "GlobalId", global_id));
259 }
260 if relating == related {
261 return Err(invalid(
262 rel.type_name,
263 "RelatedElement",
264 "is the relating element",
265 ));
266 }
267 Ok(())
268}
269
270pub fn aggregate(
282 tx: &mut Transaction,
283 global_id: &str,
284 parent: EntityId,
285 children: &[EntityId],
286) -> SpatialAuthoringResult<EntityId> {
287 relate(tx, AGGREGATES, global_id, parent, children)
288}
289
290pub fn contain(
306 tx: &mut Transaction,
307 global_id: &str,
308 structure: EntityId,
309 elements: &[EntityId],
310) -> SpatialAuthoringResult<EntityId> {
311 relate(tx, CONTAINED_IN, global_id, structure, elements)
312}