Skip to main content

ifc_spatial/authoring/
relationships.rs

1//! The remaining objectified relationships, and `IfcFacility`.
2//!
3//! # Set-valued and pair-valued relationships are not the same shape
4//!
5//! Most relationships here point one parent at a set of children, so
6//! they go through [`super::relate`], which already refuses an empty
7//! set and a parent listed among its own children.
8//!
9//! A few connect exactly two elements -- `IfcRelConnectsElements` and
10//! its subtypes, `IfcRelInterferesElements`. A set-shaped writer would
11//! accept a one-element or three-element list for those and produce a
12//! record no reader can interpret, so they get their own constructors
13//! taking two `EntityId`s. The refusal that matters there is
14//! self-connection: an element connected to itself is a cycle the
15//! connectivity reader in this crate will follow forever.
16//!
17//! # `IfcRelDefinesByObject` reverses the usual order
18//!
19//! Slot 4 is `RelatedObjects` and slot 5 is `RelatingObject`, the
20//! opposite of `IfcRelAggregates`. The constant in `relation::slots`
21//! already encodes that, which is exactly why these writers take
22//! named `parent`/`children` arguments and resolve positions through
23//! `RelSlots` rather than indexing literals.
24
25use ifc_model::guid::Guid;
26use ifc_model::{Entity, EntityId, Transaction, Value};
27
28use crate::authoring::{invalid, SpatialAuthoringResult};
29use crate::relation::slots::{
30    RelSlots, ADHERES_TO_ELEMENT, ASSIGNS_TO_ACTOR, ASSIGNS_TO_GROUP_BY_FACTOR, ASSIGNS_TO_PROCESS,
31    ASSIGNS_TO_PRODUCT, ASSIGNS_TO_RESOURCE, ASSOCIATES_PROFILE_DEF, CONNECTS_ELEMENTS,
32    CONNECTS_WITH_REALIZING, COVERS_ELEMENTS, COVERS_SPACES, DECLARES, DEFINES_BY_OBJECT,
33    FILLS_ELEMENT, FLOW_CONTROL_ELEMENTS, INTERFERES_ELEMENTS, POSITIONS, PROJECTS_ELEMENT,
34    SERVICES_BUILDINGS, VOIDS_ELEMENT,
35};
36
37/// Stage an `IfcRelCoversBldgElements`: finishes applied to an element.
38///
39/// Kept apart from [`cover_spaces`] because the same covering can do
40/// both, and the two answer different questions: which finishes are on
41/// this wall, versus which finishes bound this space.
42///
43/// # Errors
44///
45/// Refuses a malformed GlobalId, an empty covering set, and the
46/// element listed among its own coverings.
47pub fn cover_elements(
48    tx: &mut Transaction,
49    global_id: &str,
50    element: EntityId,
51    coverings: &[EntityId],
52) -> SpatialAuthoringResult<EntityId> {
53    super::relate(tx, COVERS_ELEMENTS, global_id, element, coverings)
54}
55
56/// Stage an `IfcRelCoversSpaces`: finishes bounding a space.
57///
58/// # Errors
59///
60/// Refuses a malformed GlobalId, an empty covering set, and the space
61/// listed among its own coverings.
62pub fn cover_spaces(
63    tx: &mut Transaction,
64    global_id: &str,
65    space: EntityId,
66    coverings: &[EntityId],
67) -> SpatialAuthoringResult<EntityId> {
68    super::relate(tx, COVERS_SPACES, global_id, space, coverings)
69}
70
71/// Stage an `IfcRelDeclares`: definitions declared in a context.
72///
73/// The context is an `IfcProject` or `IfcProjectLibrary`. This is how
74/// type objects and property set templates enter a file without being
75/// attached to any occurrence.
76///
77/// # Errors
78///
79/// Refuses a malformed GlobalId, an empty definition set, and the
80/// context listed among its own declarations.
81pub fn declare(
82    tx: &mut Transaction,
83    global_id: &str,
84    context: EntityId,
85    definitions: &[EntityId],
86) -> SpatialAuthoringResult<EntityId> {
87    super::relate(tx, DECLARES, global_id, context, definitions)
88}
89
90/// Stage an `IfcRelDefinesByObject`: occurrences defined by another object.
91///
92/// # Errors
93///
94/// Refuses a malformed GlobalId, an empty object set, and the
95/// defining object listed among the objects it defines.
96pub fn define_by_object(
97    tx: &mut Transaction,
98    global_id: &str,
99    defining: EntityId,
100    defined: &[EntityId],
101) -> SpatialAuthoringResult<EntityId> {
102    super::relate(tx, DEFINES_BY_OBJECT, global_id, defining, defined)
103}
104
105/// Stage an `IfcRelServicesBuildings`: which spatial elements a system serves.
106///
107/// # Errors
108///
109/// Refuses a malformed GlobalId, an empty building set, and the
110/// system listed among the buildings it serves.
111pub fn serve_buildings(
112    tx: &mut Transaction,
113    global_id: &str,
114    system: EntityId,
115    buildings: &[EntityId],
116) -> SpatialAuthoringResult<EntityId> {
117    super::relate(tx, SERVICES_BUILDINGS, global_id, system, buildings)
118}
119
120/// Stage an `IfcRelFlowControlElements`: controls bound to a flow element.
121///
122/// # Errors
123///
124/// Refuses a malformed GlobalId, an empty control set, and the flow
125/// element listed among its own controls.
126pub fn control_flow_element(
127    tx: &mut Transaction,
128    global_id: &str,
129    flow_element: EntityId,
130    controls: &[EntityId],
131) -> SpatialAuthoringResult<EntityId> {
132    super::relate(tx, FLOW_CONTROL_ELEMENTS, global_id, flow_element, controls)
133}
134
135/// Stage an `IfcRelAssignsToActor`: objects assigned to an actor.
136///
137/// # Errors
138///
139/// Refuses a malformed GlobalId, an empty object set, and the actor
140/// listed among the objects assigned to it.
141pub fn assign_to_actor(
142    tx: &mut Transaction,
143    global_id: &str,
144    actor: EntityId,
145    objects: &[EntityId],
146) -> SpatialAuthoringResult<EntityId> {
147    super::relate(tx, ASSIGNS_TO_ACTOR, global_id, actor, objects)
148}
149
150/// Stage an `IfcRelAssignsToProduct`: objects assigned to a product.
151///
152/// # Errors
153///
154/// Refuses a malformed GlobalId, an empty object set, and the product
155/// listed among the objects assigned to it.
156pub fn assign_to_product(
157    tx: &mut Transaction,
158    global_id: &str,
159    product: EntityId,
160    objects: &[EntityId],
161) -> SpatialAuthoringResult<EntityId> {
162    super::relate(tx, ASSIGNS_TO_PRODUCT, global_id, product, objects)
163}
164
165/// Stage an `IfcRelAssignsToProcess`: objects assigned to a process.
166///
167/// # Errors
168///
169/// Refuses a malformed GlobalId, an empty object set, and the process
170/// listed among the objects assigned to it.
171pub fn assign_to_process(
172    tx: &mut Transaction,
173    global_id: &str,
174    process: EntityId,
175    objects: &[EntityId],
176) -> SpatialAuthoringResult<EntityId> {
177    super::relate(tx, ASSIGNS_TO_PROCESS, global_id, process, objects)
178}
179
180/// Stage an `IfcRelAssignsToGroupByFactor`.
181///
182/// The `factor` is an `IfcRatioMeasure` at slot 7, scaling each
183/// member's contribution to the group.
184///
185/// # Errors
186///
187/// Refuses a malformed GlobalId, an empty member set, the group
188/// listed among its own members, and a non-finite factor.
189pub fn assign_to_group_by_factor(
190    tx: &mut Transaction,
191    global_id: &str,
192    group: EntityId,
193    members: &[EntityId],
194    factor: f64,
195) -> SpatialAuthoringResult<EntityId> {
196    if !factor.is_finite() {
197        return Err(invalid(
198            ASSIGNS_TO_GROUP_BY_FACTOR.type_name,
199            "Factor",
200            format!("expected a finite ratio, got {factor}"),
201        ));
202    }
203
204    let id = super::relate(tx, ASSIGNS_TO_GROUP_BY_FACTOR, global_id, group, members)?;
205    tx.set_attribute(id, FACTOR_SLOT, Value::Real(factor));
206    Ok(id)
207}
208
209/// `Factor` on `IfcRelAssignsToGroupByFactor`, after the six
210/// inherited `IfcRelAssigns` attributes and `RelatingGroup`.
211const FACTOR_SLOT: usize = 7;
212
213/// Refuse a relationship that connects an element to itself.
214///
215/// The connectivity reader walks these as a graph. A self-edge is
216/// not a harmless oddity there: it is a cycle of length one.
217fn distinct(
218    rel: RelSlots,
219    global_id: &str,
220    relating: EntityId,
221    related: EntityId,
222) -> SpatialAuthoringResult<()> {
223    if Guid::parse(global_id).is_none() {
224        return Err(invalid(rel.type_name, "GlobalId", global_id));
225    }
226    if relating == related {
227        return Err(invalid(
228            rel.type_name,
229            "RelatedElement",
230            "an element cannot connect to itself",
231        ));
232    }
233    Ok(())
234}
235
236fn pair(
237    tx: &mut Transaction,
238    rel: RelSlots,
239    global_id: &str,
240    relating: EntityId,
241    related: EntityId,
242    width: usize,
243) -> SpatialAuthoringResult<EntityId> {
244    distinct(rel, global_id, relating, related)?;
245
246    let mut attributes = vec![Value::Null; width];
247    attributes[0] = Value::Text(global_id.into());
248    attributes[rel.relating] = Value::Ref(relating);
249    attributes[rel.related] = Value::Ref(related);
250    Ok(tx.create(Entity::new(rel.type_name, attributes)))
251}
252
253/// Stage an `IfcRelConnectsElements`: two elements physically joined.
254///
255/// # Errors
256///
257/// Refuses a malformed GlobalId and an element connected to itself.
258pub fn connect_elements(
259    tx: &mut Transaction,
260    global_id: &str,
261    relating: EntityId,
262    related: EntityId,
263) -> SpatialAuthoringResult<EntityId> {
264    pair(tx, CONNECTS_ELEMENTS, global_id, relating, related, 7)
265}
266
267/// Stage an `IfcRelConnectsWithRealizingElements`.
268///
269/// The realizing elements are what physically make the connection
270/// -- a weld, a bolt, a bracket.
271///
272/// # Errors
273///
274/// Refuses a malformed GlobalId, an element connected to itself,
275/// and an empty realizing set: the subtype exists precisely to name
276/// those elements, so omitting them makes it an
277/// `IfcRelConnectsElements` wearing the wrong type name.
278pub fn connect_with_realizing_elements(
279    tx: &mut Transaction,
280    global_id: &str,
281    relating: EntityId,
282    related: EntityId,
283    realizing: &[EntityId],
284) -> SpatialAuthoringResult<EntityId> {
285    if realizing.is_empty() {
286        return Err(invalid(
287            CONNECTS_WITH_REALIZING.type_name,
288            "RealizingElements",
289            "empty",
290        ));
291    }
292
293    let id = pair(tx, CONNECTS_WITH_REALIZING, global_id, relating, related, 8)?;
294    tx.set_attribute(
295        id,
296        REALIZING_SLOT,
297        Value::List(realizing.iter().copied().map(Value::Ref).collect()),
298    );
299    Ok(id)
300}
301
302/// `RealizingElements` on `IfcRelConnectsWithRealizingElements`.
303const REALIZING_SLOT: usize = 7;
304
305/// Stage an `IfcRelInterferesElements`: a detected clash.
306///
307/// `implied_order` is `ImpliedOrder`, an `IfcLogical` at slot 8. It
308/// says whether the relating/related order carries meaning (which
309/// element gives way). `None` writes UNKNOWN, which is the honest
310/// value when a clash detector reports an overlap without deciding
311/// precedence.
312///
313/// # Errors
314///
315/// Refuses a malformed GlobalId and an element interfering with
316/// itself.
317pub fn interfere_elements(
318    tx: &mut Transaction,
319    global_id: &str,
320    relating: EntityId,
321    related: EntityId,
322    implied_order: Option<bool>,
323) -> SpatialAuthoringResult<EntityId> {
324    let id = pair(tx, INTERFERES_ELEMENTS, global_id, relating, related, 10)?;
325    tx.set_attribute(
326        id,
327        IMPLIED_ORDER_SLOT,
328        implied_order.map_or(Value::LogicalUnknown, Value::Bool),
329    );
330    Ok(id)
331}
332
333/// `ImpliedOrder` on `IfcRelInterferesElements`.
334const IMPLIED_ORDER_SLOT: usize = 8;
335
336/// Stage an `IfcRelVoidsElement`: an opening cut into an element.
337///
338/// # Errors
339///
340/// Refuses a malformed GlobalId and an element voided by itself.
341pub fn void_element(
342    tx: &mut Transaction,
343    global_id: &str,
344    element: EntityId,
345    opening: EntityId,
346) -> SpatialAuthoringResult<EntityId> {
347    super::relate_one(tx, VOIDS_ELEMENT, global_id, element, opening)
348}
349
350/// Stage an `IfcRelFillsElement`: an element filling an opening.
351///
352/// Note the direction. The *opening* is the relating end here, the
353/// inverse of [`void_element`]: a wall is voided by an opening, and
354/// that opening is filled by a door.
355///
356/// # Errors
357///
358/// Refuses a malformed GlobalId and an opening filled by itself.
359pub fn fill_element(
360    tx: &mut Transaction,
361    global_id: &str,
362    opening: EntityId,
363    filling: EntityId,
364) -> SpatialAuthoringResult<EntityId> {
365    super::relate_one(tx, FILLS_ELEMENT, global_id, opening, filling)
366}
367
368/// Stage an `IfcRelProjectsElement`: a feature added to an element.
369///
370/// # Errors
371///
372/// Refuses a malformed GlobalId and an element projecting from itself.
373pub fn project_element(
374    tx: &mut Transaction,
375    global_id: &str,
376    element: EntityId,
377    feature: EntityId,
378) -> SpatialAuthoringResult<EntityId> {
379    super::relate_one(tx, PROJECTS_ELEMENT, global_id, element, feature)
380}
381
382/// Stage an `IfcRelAdheresToElement`: surface features bound to an element.
383///
384/// # Errors
385///
386/// Refuses a malformed GlobalId, an empty feature set, and an element
387/// listed among its own features.
388pub fn adhere_to_element(
389    tx: &mut Transaction,
390    global_id: &str,
391    element: EntityId,
392    features: &[EntityId],
393) -> SpatialAuthoringResult<EntityId> {
394    super::relate(tx, ADHERES_TO_ELEMENT, global_id, element, features)
395}
396
397/// Stage an `IfcRelPositions`: products placed by a positioning element.
398///
399/// # Errors
400///
401/// Refuses a malformed GlobalId, an empty product set, and the
402/// positioning element listed among its own products, which the
403/// schema's `NoSelfReference` rule forbids.
404pub fn position_products(
405    tx: &mut Transaction,
406    global_id: &str,
407    positioning: EntityId,
408    products: &[EntityId],
409) -> SpatialAuthoringResult<EntityId> {
410    super::relate(tx, POSITIONS, global_id, positioning, products)
411}
412
413/// Stage an `IfcRelAssignsToResource`: objects assigned to a resource.
414///
415/// # Errors
416///
417/// Refuses a malformed GlobalId, an empty object set, and the resource
418/// listed among its own objects, which `NoSelfReference` forbids.
419pub fn assign_to_resource(
420    tx: &mut Transaction,
421    global_id: &str,
422    resource: EntityId,
423    objects: &[EntityId],
424) -> SpatialAuthoringResult<EntityId> {
425    super::relate(tx, ASSIGNS_TO_RESOURCE, global_id, resource, objects)
426}
427
428/// Stage an `IfcRelAssociatesProfileDef`: a profile associated with objects.
429///
430/// # Errors
431///
432/// Refuses a malformed GlobalId, an empty object set, and the profile
433/// listed among its own objects.
434pub fn associate_profile_def(
435    tx: &mut Transaction,
436    global_id: &str,
437    profile: EntityId,
438    objects: &[EntityId],
439) -> SpatialAuthoringResult<EntityId> {
440    super::relate(tx, ASSOCIATES_PROFILE_DEF, global_id, profile, objects)
441}