ifc_spatial/authoring/connections.rs
1//! The pair-valued relationships: two elements, never a set.
2//!
3//! A few relationships connect exactly two elements --
4//! `IfcRelConnectsElements` and its subtypes, `IfcRelInterferesElements`.
5//! A set-shaped writer would accept a one-element or three-element list
6//! for those and produce a record no reader can interpret, so they get
7//! their own constructors taking two `EntityId`s. The refusal that matters
8//! there is self-connection: an element connected to itself is a cycle the
9//! connectivity reader in this crate will follow forever.
10
11use ifc_model::guid::Guid;
12use ifc_model::{Entity, EntityId, Model, Transaction, Value};
13
14use super::owned_relationships::{pair_owned, refs};
15
16use crate::authoring::{invalid, SpatialAuthoringResult};
17use crate::relation::slots::{
18 RelSlots, CONNECTS_ELEMENTS, CONNECTS_WITH_REALIZING, INTERFERES_ELEMENTS,
19};
20
21/// Refuse an empty `RealizingElements`: the subtype exists to name them.
22pub(super) fn check_realizing(realizing: &[EntityId]) -> SpatialAuthoringResult<()> {
23 if realizing.is_empty() {
24 return Err(invalid(
25 CONNECTS_WITH_REALIZING.type_name,
26 "RealizingElements",
27 "empty",
28 ));
29 }
30 Ok(())
31}
32
33/// Refuse a relationship that connects an element to itself.
34///
35/// The connectivity reader walks these as a graph. A self-edge is
36/// not a harmless oddity there: it is a cycle of length one.
37pub(super) fn distinct(
38 rel: RelSlots,
39 global_id: &str,
40 relating: EntityId,
41 related: EntityId,
42) -> SpatialAuthoringResult<()> {
43 if Guid::parse(global_id).is_none() {
44 return Err(invalid(rel.type_name, "GlobalId", global_id));
45 }
46 if relating == related {
47 return Err(invalid(
48 rel.type_name,
49 "RelatedElement",
50 "an element cannot connect to itself",
51 ));
52 }
53 Ok(())
54}
55
56fn pair(
57 tx: &mut Transaction,
58 rel: RelSlots,
59 global_id: &str,
60 relating: EntityId,
61 related: EntityId,
62 width: usize,
63) -> SpatialAuthoringResult<EntityId> {
64 distinct(rel, global_id, relating, related)?;
65
66 let mut attributes = vec![Value::Null; width];
67 attributes[0] = Value::Text(global_id.into());
68 attributes[rel.relating] = Value::Ref(relating);
69 attributes[rel.related] = Value::Ref(related);
70 Ok(tx.create(Entity::new(rel.type_name, attributes)))
71}
72
73/// Stage an `IfcRelConnectsElements`: two elements physically joined.
74///
75/// IFC4 and IFC4X3 only: it writes their layout and leaves
76/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
77/// [`connect_elements_with_owner_history`](super::connect_elements_with_owner_history), which binds the model's declared
78/// release.
79///
80/// # Errors
81///
82/// Refuses a malformed GlobalId and an element connected to itself.
83pub fn connect_elements(
84 tx: &mut Transaction,
85 global_id: &str,
86 relating: EntityId,
87 related: EntityId,
88) -> SpatialAuthoringResult<EntityId> {
89 pair(tx, CONNECTS_ELEMENTS, global_id, relating, related, 7)
90}
91
92/// Stage an `IfcRelConnectsWithRealizingElements`.
93///
94/// The realizing elements are what physically make the connection
95/// -- a weld, a bolt, a bracket.
96///
97/// Bound to the model's declared release (#213): the record is laid out by
98/// attribute name with the release's nine attributes (`ConnectionGeometry`
99/// and `ConnectionType` unset). `OwnerHistory` is left `$`, which IFC4 and
100/// IFC4X3 allow and IFC2X3 does not; in IFC2X3 use
101/// [`connect_with_realizing_elements_with_owner_history`](super::connect_with_realizing_elements_with_owner_history).
102///
103/// # Errors
104///
105/// Refuses a malformed GlobalId, an element connected to itself,
106/// and an empty realizing set: the subtype exists precisely to name
107/// those elements, so omitting them makes it an
108/// `IfcRelConnectsElements` wearing the wrong type name. A header binding
109/// no single verified release (`MultipleSchemas`, `UnsupportedSchema`) and
110/// an IFC2X3 model (`AuthoringRequired`) are refused. Nothing is staged on
111/// an error.
112pub fn connect_with_realizing_elements(
113 tx: &mut Transaction,
114 model: &Model,
115 global_id: &str,
116 relating: EntityId,
117 related: EntityId,
118 realizing: &[EntityId],
119) -> SpatialAuthoringResult<EntityId> {
120 check_realizing(realizing)?;
121 pair_owned(
122 tx,
123 model,
124 CONNECTS_WITH_REALIZING,
125 global_id,
126 relating,
127 related,
128 vec![("RealizingElements", refs(realizing))],
129 None,
130 )
131}
132
133/// Stage an `IfcRelInterferesElements`: a detected clash.
134///
135/// `implied_order` is `ImpliedOrder`, an `IfcLogical`. It says whether the
136/// relating/related order carries meaning (which element gives way).
137/// `None` writes UNKNOWN, which is the honest value when a clash detector
138/// reports an overlap without deciding precedence.
139///
140/// Bound to the model's declared release (#213): the record has the
141/// release's own arity, nine attributes in IFC4 and ten in IFC4X3
142/// (`InterferenceSpace` unset), laid out by name. IFC2X3 declares no
143/// `IfcRelInterferesElements` (`EntityNotInSchema`).
144///
145/// # Errors
146///
147/// Refuses a malformed GlobalId, an element interfering with itself, a
148/// header binding no single verified release (`MultipleSchemas`,
149/// `UnsupportedSchema`) and an IFC2X3 model. Nothing is staged on an
150/// error.
151pub fn interfere_elements(
152 tx: &mut Transaction,
153 model: &Model,
154 global_id: &str,
155 relating: EntityId,
156 related: EntityId,
157 implied_order: Option<bool>,
158) -> SpatialAuthoringResult<EntityId> {
159 pair_owned(
160 tx,
161 model,
162 INTERFERES_ELEMENTS,
163 global_id,
164 relating,
165 related,
166 vec![(
167 "ImpliedOrder",
168 implied_order.map_or(Value::LogicalUnknown, Value::Bool),
169 )],
170 None,
171 )
172}