Skip to main content

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}