1use ifc_model::guid::Guid;
36use ifc_model::{Entity, EntityId, Transaction, Value};
37
38use crate::connectivity::relation::slot as connects_slot;
39use crate::port::definition::slot as port_slot;
40use crate::system::group::slot as group_slot;
41use crate::zone::spatial_group::slot as placement_slot;
42
43mod distribution;
44mod error;
45mod owned;
46mod release;
47mod system_kind;
48
49pub use distribution::{
50 create_distribution_element, create_distribution_element_with_owner_history,
51 create_spatial_zone, create_spatial_zone_with_owner_history, create_zone,
52 create_zone_with_owner_history, DistributionElementKind, ElementAttributes,
53};
54pub use error::{SystemAuthoringError, SystemAuthoringResult};
55pub use owned::{
56 assign_to_group_with_owner_history, connect_port_to_element_with_owner_history,
57 connect_ports_with_owner_history, contain_in_spatial_structure_with_owner_history,
58 create_group_with_owner_history, create_port_with_owner_history,
59 create_system_with_owner_history, nest_ports_with_owner_history,
60 reference_in_spatial_structure_with_owner_history,
61};
62pub use system_kind::{
63 create_classified_system, create_classified_system_with_owner_history, ClassifiedSystemDraft,
64 SystemKind,
65};
66
67use error::invalid;
68
69fn guid(entity: &'static str, global_id: &str) -> SystemAuthoringResult<()> {
70 if Guid::parse(global_id).is_none() {
71 return Err(invalid(entity, "GlobalId", global_id));
72 }
73 Ok(())
74}
75
76fn check_related(
78 entity: &'static str,
79 attribute: &'static str,
80 global_id: &str,
81 relating: EntityId,
82 related: &[EntityId],
83 relating_word: &str,
84) -> SystemAuthoringResult<()> {
85 guid(entity, global_id)?;
86 if related.is_empty() {
87 return Err(invalid(entity, attribute, "empty"));
88 }
89 if related.contains(&relating) {
90 return Err(invalid(
91 entity,
92 attribute,
93 format!("contains the {relating_word}"),
94 ));
95 }
96 Ok(())
97}
98
99fn check_ports(
101 global_id: &str,
102 relating: EntityId,
103 related: EntityId,
104) -> SystemAuthoringResult<()> {
105 guid("IFCRELCONNECTSPORTS", global_id)?;
106 if relating == related {
107 return Err(invalid(
108 "IFCRELCONNECTSPORTS",
109 "RelatedPort",
110 "same as RelatingPort",
111 ));
112 }
113 Ok(())
114}
115
116fn refs(ids: &[EntityId]) -> Value {
117 Value::List(ids.iter().copied().map(Value::Ref).collect())
118}
119
120pub fn create_system(
130 tx: &mut Transaction,
131 global_id: &str,
132 name: Option<&str>,
133) -> SystemAuthoringResult<EntityId> {
134 guid("IFCSYSTEM", global_id)?;
135 let mut attributes = vec![Value::Null; 5];
136 attributes[0] = Value::Text(global_id.into());
137 attributes[2] = name.map_or(Value::Null, |t| Value::Text(t.into()));
138 Ok(tx.create(Entity::new("IFCSYSTEM", attributes)))
139}
140
141pub fn create_port(
155 tx: &mut Transaction,
156 global_id: &str,
157 name: Option<&str>,
158 flow_direction: Option<&str>,
159) -> SystemAuthoringResult<EntityId> {
160 guid("IFCDISTRIBUTIONPORT", global_id)?;
161 let mut attributes = vec![Value::Null; 10];
162 attributes[0] = Value::Text(global_id.into());
163 attributes[2] = name.map_or(Value::Null, |t| Value::Text(t.into()));
164 attributes[7] = flow_direction.map_or(Value::Null, |t| Value::Enum(t.into()));
165 Ok(tx.create(Entity::new("IFCDISTRIBUTIONPORT", attributes)))
166}
167
168pub fn assign_to_group(
178 tx: &mut Transaction,
179 global_id: &str,
180 group: EntityId,
181 members: &[EntityId],
182) -> SystemAuthoringResult<EntityId> {
183 check_related(
184 "IFCRELASSIGNSTOGROUP",
185 "RelatedObjects",
186 global_id,
187 group,
188 members,
189 "group",
190 )?;
191 let width = group_slot::ASSIGNS_GROUP.max(group_slot::ASSIGNS_RELATED) + 1;
192 let mut attributes = vec![Value::Null; width];
193 attributes[0] = Value::Text(global_id.into());
194 attributes[group_slot::ASSIGNS_RELATED] = refs(members);
195 attributes[group_slot::ASSIGNS_GROUP] = Value::Ref(group);
196 Ok(tx.create(Entity::new("IFCRELASSIGNSTOGROUP", attributes)))
197}
198
199pub fn nest_ports(
209 tx: &mut Transaction,
210 global_id: &str,
211 parent: EntityId,
212 children: &[EntityId],
213) -> SystemAuthoringResult<EntityId> {
214 check_related(
215 "IFCRELNESTS",
216 "RelatedObjects",
217 global_id,
218 parent,
219 children,
220 "parent",
221 )?;
222 let width = port_slot::NESTS_PARENT.max(port_slot::NESTS_CHILDREN) + 1;
223 let mut attributes = vec![Value::Null; width];
224 attributes[0] = Value::Text(global_id.into());
225 attributes[port_slot::NESTS_PARENT] = Value::Ref(parent);
226 attributes[port_slot::NESTS_CHILDREN] = refs(children);
227 Ok(tx.create(Entity::new("IFCRELNESTS", attributes)))
228}
229
230pub fn connect_port_to_element(
239 tx: &mut Transaction,
240 global_id: &str,
241 port: EntityId,
242 element: EntityId,
243) -> SystemAuthoringResult<EntityId> {
244 guid("IFCRELCONNECTSPORTTOELEMENT", global_id)?;
245 let width = port_slot::PORT_TO_ELEMENT_PORT.max(port_slot::PORT_TO_ELEMENT_ELEMENT) + 1;
246 let mut attributes = vec![Value::Null; width];
247 attributes[0] = Value::Text(global_id.into());
248 attributes[port_slot::PORT_TO_ELEMENT_PORT] = Value::Ref(port);
249 attributes[port_slot::PORT_TO_ELEMENT_ELEMENT] = Value::Ref(element);
250 Ok(tx.create(Entity::new("IFCRELCONNECTSPORTTOELEMENT", attributes)))
251}
252
253pub fn connect_ports(
266 tx: &mut Transaction,
267 global_id: &str,
268 relating: EntityId,
269 related: EntityId,
270 realizing: Option<EntityId>,
271) -> SystemAuthoringResult<EntityId> {
272 check_ports(global_id, relating, related)?;
273 let width = connects_slot::REALIZING + 1;
274 let mut attributes = vec![Value::Null; width];
275 attributes[0] = Value::Text(global_id.into());
276 attributes[connects_slot::RELATING] = Value::Ref(relating);
277 attributes[connects_slot::RELATED] = Value::Ref(related);
278 attributes[connects_slot::REALIZING] = realizing.map_or(Value::Null, Value::Ref);
279 Ok(tx.create(Entity::new("IFCRELCONNECTSPORTS", attributes)))
280}
281
282pub fn contain_in_spatial_structure(
296 tx: &mut Transaction,
297 global_id: &str,
298 structure: EntityId,
299 elements: &[EntityId],
300) -> SystemAuthoringResult<EntityId> {
301 place(
302 tx,
303 "IFCRELCONTAINEDINSPATIALSTRUCTURE",
304 global_id,
305 structure,
306 elements,
307 )
308}
309
310pub fn reference_in_spatial_structure(
322 tx: &mut Transaction,
323 global_id: &str,
324 structure: EntityId,
325 elements: &[EntityId],
326) -> SystemAuthoringResult<EntityId> {
327 place(
328 tx,
329 "IFCRELREFERENCEDINSPATIALSTRUCTURE",
330 global_id,
331 structure,
332 elements,
333 )
334}
335
336fn place(
339 tx: &mut Transaction,
340 entity: &'static str,
341 global_id: &str,
342 structure: EntityId,
343 elements: &[EntityId],
344) -> SystemAuthoringResult<EntityId> {
345 check_related(
346 entity,
347 "RelatedElements",
348 global_id,
349 structure,
350 elements,
351 "structure",
352 )?;
353 let width = placement_slot::RELATING_STRUCTURE.max(placement_slot::RELATED_ELEMENTS) + 1;
354 let mut attributes = vec![Value::Null; width];
355 attributes[0] = Value::Text(global_id.into());
356 attributes[placement_slot::RELATED_ELEMENTS] = refs(elements);
357 attributes[placement_slot::RELATING_STRUCTURE] = Value::Ref(structure);
358 Ok(tx.create(Entity::new(entity, attributes)))
359}
360
361pub fn create_group(
375 tx: &mut Transaction,
376 global_id: &str,
377 name: Option<&str>,
378 description: Option<&str>,
379) -> SystemAuthoringResult<EntityId> {
380 guid("IFCGROUP", global_id)?;
381 let mut attributes = vec![Value::Null; 5];
382 attributes[0] = Value::Text(global_id.into());
383 attributes[2] = name.map_or(Value::Null, |t| Value::Text(t.into()));
384 attributes[3] = description.map_or(Value::Null, |t| Value::Text(t.into()));
385 Ok(tx.create(Entity::new("IFCGROUP", attributes)))
386}