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, Transaction, Value};
13
14use crate::authoring::{invalid, SpatialAuthoringResult};
15use crate::relation::slots::{
16    RelSlots, CONNECTS_ELEMENTS, CONNECTS_WITH_REALIZING, INTERFERES_ELEMENTS,
17};
18
19/// Refuse an empty `RealizingElements`: the subtype exists to name them.
20pub(super) fn check_realizing(realizing: &[EntityId]) -> SpatialAuthoringResult<()> {
21    if realizing.is_empty() {
22        return Err(invalid(
23            CONNECTS_WITH_REALIZING.type_name,
24            "RealizingElements",
25            "empty",
26        ));
27    }
28    Ok(())
29}
30
31/// Refuse a relationship that connects an element to itself.
32///
33/// The connectivity reader walks these as a graph. A self-edge is
34/// not a harmless oddity there: it is a cycle of length one.
35pub(super) fn distinct(
36    rel: RelSlots,
37    global_id: &str,
38    relating: EntityId,
39    related: EntityId,
40) -> SpatialAuthoringResult<()> {
41    if Guid::parse(global_id).is_none() {
42        return Err(invalid(rel.type_name, "GlobalId", global_id));
43    }
44    if relating == related {
45        return Err(invalid(
46            rel.type_name,
47            "RelatedElement",
48            "an element cannot connect to itself",
49        ));
50    }
51    Ok(())
52}
53
54fn pair(
55    tx: &mut Transaction,
56    rel: RelSlots,
57    global_id: &str,
58    relating: EntityId,
59    related: EntityId,
60    width: usize,
61) -> SpatialAuthoringResult<EntityId> {
62    distinct(rel, global_id, relating, related)?;
63
64    let mut attributes = vec![Value::Null; width];
65    attributes[0] = Value::Text(global_id.into());
66    attributes[rel.relating] = Value::Ref(relating);
67    attributes[rel.related] = Value::Ref(related);
68    Ok(tx.create(Entity::new(rel.type_name, attributes)))
69}
70
71/// Stage an `IfcRelConnectsElements`: two elements physically joined.
72///
73/// IFC4 and IFC4X3 only: it writes their layout and leaves
74/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
75/// [`connect_elements_with_owner_history`](super::connect_elements_with_owner_history), which binds the model's declared
76/// release.
77///
78/// # Errors
79///
80/// Refuses a malformed GlobalId and an element connected to itself.
81pub fn connect_elements(
82    tx: &mut Transaction,
83    global_id: &str,
84    relating: EntityId,
85    related: EntityId,
86) -> SpatialAuthoringResult<EntityId> {
87    pair(tx, CONNECTS_ELEMENTS, global_id, relating, related, 7)
88}
89
90/// Stage an `IfcRelConnectsWithRealizingElements`.
91///
92/// The realizing elements are what physically make the connection
93/// -- a weld, a bolt, a bracket.
94///
95/// IFC4 and IFC4X3 only: it writes their layout and leaves
96/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
97/// [`connect_with_realizing_elements_with_owner_history`](super::connect_with_realizing_elements_with_owner_history), which binds the model's declared
98/// release.
99///
100/// # Errors
101///
102/// Refuses a malformed GlobalId, an element connected to itself,
103/// and an empty realizing set: the subtype exists precisely to name
104/// those elements, so omitting them makes it an
105/// `IfcRelConnectsElements` wearing the wrong type name.
106pub fn connect_with_realizing_elements(
107    tx: &mut Transaction,
108    global_id: &str,
109    relating: EntityId,
110    related: EntityId,
111    realizing: &[EntityId],
112) -> SpatialAuthoringResult<EntityId> {
113    check_realizing(realizing)?;
114    let id = pair(tx, CONNECTS_WITH_REALIZING, global_id, relating, related, 8)?;
115    tx.set_attribute(
116        id,
117        REALIZING_SLOT,
118        Value::List(realizing.iter().copied().map(Value::Ref).collect()),
119    );
120    Ok(id)
121}
122
123/// `RealizingElements` on `IfcRelConnectsWithRealizingElements`.
124const REALIZING_SLOT: usize = 7;
125
126/// Stage an `IfcRelInterferesElements`: a detected clash.
127///
128/// `implied_order` is `ImpliedOrder`, an `IfcLogical` at slot 8. It
129/// says whether the relating/related order carries meaning (which
130/// element gives way). `None` writes UNKNOWN, which is the honest
131/// value when a clash detector reports an overlap without deciding
132/// precedence.
133///
134/// IFC4 and IFC4X3 only: it writes their layout and leaves
135/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
136/// [`interfere_elements_with_owner_history`](super::interfere_elements_with_owner_history), which binds the model's declared
137/// release.
138///
139/// # Errors
140///
141/// Refuses a malformed GlobalId and an element interfering with
142/// itself.
143pub fn interfere_elements(
144    tx: &mut Transaction,
145    global_id: &str,
146    relating: EntityId,
147    related: EntityId,
148    implied_order: Option<bool>,
149) -> SpatialAuthoringResult<EntityId> {
150    let id = pair(tx, INTERFERES_ELEMENTS, global_id, relating, related, 10)?;
151    tx.set_attribute(
152        id,
153        IMPLIED_ORDER_SLOT,
154        implied_order.map_or(Value::LogicalUnknown, Value::Bool),
155    );
156    Ok(id)
157}
158
159/// `ImpliedOrder` on `IfcRelInterferesElements`.
160const IMPLIED_ORDER_SLOT: usize = 8;