Skip to main content

ifc_spatial/authoring/
relationships.rs

1//! The remaining objectified relationships, and `IfcFacility`.
2//!
3//! Most relationships here point one parent at a set of children, so
4//! they go through [`super::relate`], which already refuses an empty
5//! set and a parent listed among its own children. The pair-valued
6//! ones live in `connections.rs`.
7//!
8//! # `IfcRelDefinesByObject` reverses the usual order
9//!
10//! Slot 4 is `RelatedObjects` and slot 5 is `RelatingObject`, the
11//! opposite of `IfcRelAggregates`. The constant in `relation::slots`
12//! already encodes that, which is exactly why these writers take
13//! named `parent`/`children` arguments and resolve positions through
14//! `RelSlots` rather than indexing literals.
15
16use ifc_model::{EntityId, Model, Transaction, Value};
17
18use super::owned_relationships::relate_owned;
19use crate::authoring::{invalid, SpatialAuthoringResult};
20use crate::relation::slots::{
21    ADHERES_TO_ELEMENT, ASSIGNS_TO_ACTOR, ASSIGNS_TO_GROUP_BY_FACTOR, ASSIGNS_TO_PROCESS,
22    ASSIGNS_TO_PRODUCT, ASSIGNS_TO_RESOURCE, ASSOCIATES_PROFILE_DEF, COVERS_ELEMENTS,
23    COVERS_SPACES, DECLARES, DEFINES_BY_OBJECT, FILLS_ELEMENT, FLOW_CONTROL_ELEMENTS, POSITIONS,
24    PROJECTS_ELEMENT, SERVICES_BUILDINGS, VOIDS_ELEMENT,
25};
26
27/// Stage an `IfcRelCoversBldgElements`: finishes applied to an element.
28///
29/// Kept apart from [`cover_spaces`] because the same covering can do
30/// both, and the two answer different questions: which finishes are on
31/// this wall, versus which finishes bound this space.
32///
33/// IFC4 and IFC4X3 only: it writes their layout and leaves
34/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
35/// [`cover_elements_with_owner_history`](super::cover_elements_with_owner_history), which binds the model's declared
36/// release.
37///
38/// # Errors
39///
40/// Refuses a malformed GlobalId, an empty covering set, and the
41/// element listed among its own coverings.
42pub fn cover_elements(
43    tx: &mut Transaction,
44    global_id: &str,
45    element: EntityId,
46    coverings: &[EntityId],
47) -> SpatialAuthoringResult<EntityId> {
48    super::relate(tx, COVERS_ELEMENTS, global_id, element, coverings)
49}
50
51/// Stage an `IfcRelCoversSpaces`: finishes bounding a space.
52///
53/// IFC4 and IFC4X3 only: it writes their layout and leaves
54/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
55/// [`cover_spaces_with_owner_history`](super::cover_spaces_with_owner_history), which binds the model's declared
56/// release.
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/// IFC4 and IFC4X3 only: it writes their layout and leaves
78/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
79/// [`declare_with_owner_history`](super::declare_with_owner_history), which binds the model's declared
80/// release.
81///
82/// # Errors
83///
84/// Refuses a malformed GlobalId, an empty definition set, and the
85/// context listed among its own declarations.
86pub fn declare(
87    tx: &mut Transaction,
88    global_id: &str,
89    context: EntityId,
90    definitions: &[EntityId],
91) -> SpatialAuthoringResult<EntityId> {
92    super::relate(tx, DECLARES, global_id, context, definitions)
93}
94
95/// Stage an `IfcRelDefinesByObject`: occurrences defined by another object.
96///
97/// IFC4 and IFC4X3 only: it writes their layout and leaves
98/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
99/// [`define_by_object_with_owner_history`](super::define_by_object_with_owner_history), which binds the model's declared
100/// release.
101///
102/// # Errors
103///
104/// Refuses a malformed GlobalId, an empty object set, and the
105/// defining object listed among the objects it defines.
106pub fn define_by_object(
107    tx: &mut Transaction,
108    global_id: &str,
109    defining: EntityId,
110    defined: &[EntityId],
111) -> SpatialAuthoringResult<EntityId> {
112    super::relate(tx, DEFINES_BY_OBJECT, global_id, defining, defined)
113}
114
115/// Stage an `IfcRelServicesBuildings`: which spatial elements a system serves.
116///
117/// IFC4 and IFC4X3 only: it writes their layout and leaves
118/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
119/// [`serve_buildings_with_owner_history`](super::serve_buildings_with_owner_history), which binds the model's declared
120/// release.
121///
122/// # Errors
123///
124/// Refuses a malformed GlobalId, an empty building set, and the
125/// system listed among the buildings it serves.
126pub fn serve_buildings(
127    tx: &mut Transaction,
128    global_id: &str,
129    system: EntityId,
130    buildings: &[EntityId],
131) -> SpatialAuthoringResult<EntityId> {
132    super::relate(tx, SERVICES_BUILDINGS, global_id, system, buildings)
133}
134
135/// Stage an `IfcRelFlowControlElements`: controls bound to a flow element.
136///
137/// IFC4 and IFC4X3 only: it writes their layout and leaves
138/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
139/// [`control_flow_element_with_owner_history`](super::control_flow_element_with_owner_history), which binds the model's declared
140/// release.
141///
142/// # Errors
143///
144/// Refuses a malformed GlobalId, an empty control set, and the flow
145/// element listed among its own controls.
146pub fn control_flow_element(
147    tx: &mut Transaction,
148    global_id: &str,
149    flow_element: EntityId,
150    controls: &[EntityId],
151) -> SpatialAuthoringResult<EntityId> {
152    super::relate(tx, FLOW_CONTROL_ELEMENTS, global_id, flow_element, controls)
153}
154
155/// Stage an `IfcRelAssignsToActor`: objects assigned to an actor.
156///
157/// Bound to the model's declared release (#213): the record is laid out by
158/// attribute name from that release's table, with all eight attributes
159/// (`ActingRole` unset). `OwnerHistory` is left `$`, which IFC4 and IFC4X3
160/// allow and IFC2X3 does not; in IFC2X3 use
161/// [`assign_to_actor_with_owner_history`](super::assign_to_actor_with_owner_history).
162///
163/// # Errors
164///
165/// Refuses a malformed GlobalId, an empty object set, and the actor
166/// listed among the objects assigned to it. A header binding no single
167/// verified release (`MultipleSchemas`, `UnsupportedSchema`) and an IFC2X3
168/// model (`AuthoringRequired`) are refused. Nothing is staged on an error.
169pub fn assign_to_actor(
170    tx: &mut Transaction,
171    model: &Model,
172    global_id: &str,
173    actor: EntityId,
174    objects: &[EntityId],
175) -> SpatialAuthoringResult<EntityId> {
176    relate_owned(
177        tx,
178        model,
179        ASSIGNS_TO_ACTOR,
180        global_id,
181        actor,
182        objects,
183        Vec::new(),
184        None,
185    )
186}
187
188/// Stage an `IfcRelAssignsToProduct`: objects assigned to a product.
189///
190/// IFC4 and IFC4X3 only: it writes their layout and leaves
191/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
192/// [`assign_to_product_with_owner_history`](super::assign_to_product_with_owner_history), which binds the model's declared
193/// release.
194///
195/// # Errors
196///
197/// Refuses a malformed GlobalId, an empty object set, and the product
198/// listed among the objects assigned to it.
199pub fn assign_to_product(
200    tx: &mut Transaction,
201    global_id: &str,
202    product: EntityId,
203    objects: &[EntityId],
204) -> SpatialAuthoringResult<EntityId> {
205    super::relate(tx, ASSIGNS_TO_PRODUCT, global_id, product, objects)
206}
207
208/// Stage an `IfcRelAssignsToProcess`: objects assigned to a process.
209///
210/// Bound to the model's declared release (#213): the record is laid out by
211/// attribute name from that release's table, with all eight attributes
212/// (`QuantityInProcess` unset). `OwnerHistory` is left `$`, which IFC4 and IFC4X3
213/// allow and IFC2X3 does not; in IFC2X3 use
214/// [`assign_to_process_with_owner_history`](super::assign_to_process_with_owner_history).
215///
216/// # Errors
217///
218/// Refuses a malformed GlobalId, an empty object set, and the process
219/// listed among the objects assigned to it. A header binding no single
220/// verified release (`MultipleSchemas`, `UnsupportedSchema`) and an IFC2X3
221/// model (`AuthoringRequired`) are refused. Nothing is staged on an error.
222pub fn assign_to_process(
223    tx: &mut Transaction,
224    model: &Model,
225    global_id: &str,
226    process: EntityId,
227    objects: &[EntityId],
228) -> SpatialAuthoringResult<EntityId> {
229    relate_owned(
230        tx,
231        model,
232        ASSIGNS_TO_PROCESS,
233        global_id,
234        process,
235        objects,
236        Vec::new(),
237        None,
238    )
239}
240
241/// Stage an `IfcRelAssignsToGroupByFactor`.
242///
243/// The `factor` is an `IfcRatioMeasure` at slot 7, scaling each
244/// member's contribution to the group.
245///
246/// IFC4 and IFC4X3 only: it writes their layout and leaves
247/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
248/// [`assign_to_group_by_factor_with_owner_history`](super::assign_to_group_by_factor_with_owner_history), which binds the model's declared
249/// release.
250///
251/// # Errors
252///
253/// Refuses a malformed GlobalId, an empty member set, the group
254/// listed among its own members, and a non-finite factor.
255pub fn assign_to_group_by_factor(
256    tx: &mut Transaction,
257    global_id: &str,
258    group: EntityId,
259    members: &[EntityId],
260    factor: f64,
261) -> SpatialAuthoringResult<EntityId> {
262    check_factor(factor)?;
263    let id = super::relate(tx, ASSIGNS_TO_GROUP_BY_FACTOR, global_id, group, members)?;
264    tx.set_attribute(id, FACTOR_SLOT, Value::Real(factor));
265    Ok(id)
266}
267
268/// Refuse a non-finite `IfcRatioMeasure` factor.
269pub(super) fn check_factor(factor: f64) -> SpatialAuthoringResult<()> {
270    if factor.is_finite() {
271        return Ok(());
272    }
273    Err(invalid(
274        ASSIGNS_TO_GROUP_BY_FACTOR.type_name,
275        "Factor",
276        format!("expected a finite ratio, got {factor}"),
277    ))
278}
279
280/// `Factor` on `IfcRelAssignsToGroupByFactor`, after the six
281/// inherited `IfcRelAssigns` attributes and `RelatingGroup`.
282const FACTOR_SLOT: usize = 7;
283
284/// Stage an `IfcRelVoidsElement`: an opening cut into an element.
285///
286/// IFC4 and IFC4X3 only: it writes their layout and leaves
287/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
288/// [`void_element_with_owner_history`](super::void_element_with_owner_history), which binds the model's declared
289/// release.
290///
291/// # Errors
292///
293/// Refuses a malformed GlobalId and an element voided by itself.
294pub fn void_element(
295    tx: &mut Transaction,
296    global_id: &str,
297    element: EntityId,
298    opening: EntityId,
299) -> SpatialAuthoringResult<EntityId> {
300    super::relate_one(tx, VOIDS_ELEMENT, global_id, element, opening)
301}
302
303/// Stage an `IfcRelFillsElement`: an element filling an opening.
304///
305/// Note the direction. The *opening* is the relating end here, the
306/// inverse of [`void_element`]: a wall is voided by an opening, and
307/// that opening is filled by a door.
308///
309/// IFC4 and IFC4X3 only: it writes their layout and leaves
310/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
311/// [`fill_element_with_owner_history`](super::fill_element_with_owner_history), which binds the model's declared
312/// release.
313///
314/// # Errors
315///
316/// Refuses a malformed GlobalId and an opening filled by itself.
317pub fn fill_element(
318    tx: &mut Transaction,
319    global_id: &str,
320    opening: EntityId,
321    filling: EntityId,
322) -> SpatialAuthoringResult<EntityId> {
323    super::relate_one(tx, FILLS_ELEMENT, global_id, opening, filling)
324}
325
326/// Stage an `IfcRelProjectsElement`: a feature added to an element.
327///
328/// IFC4 and IFC4X3 only: it writes their layout and leaves
329/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
330/// [`project_element_with_owner_history`](super::project_element_with_owner_history), which binds the model's declared
331/// release.
332///
333/// # Errors
334///
335/// Refuses a malformed GlobalId and an element projecting from itself.
336pub fn project_element(
337    tx: &mut Transaction,
338    global_id: &str,
339    element: EntityId,
340    feature: EntityId,
341) -> SpatialAuthoringResult<EntityId> {
342    super::relate_one(tx, PROJECTS_ELEMENT, global_id, element, feature)
343}
344
345/// Stage an `IfcRelAdheresToElement`: surface features bound to an element.
346///
347/// IFC4 and IFC4X3 only: it writes their layout and leaves
348/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
349/// [`adhere_to_element_with_owner_history`](super::adhere_to_element_with_owner_history), which binds the model's declared
350/// release.
351///
352/// # Errors
353///
354/// Refuses a malformed GlobalId, an empty feature set, and an element
355/// listed among its own features.
356pub fn adhere_to_element(
357    tx: &mut Transaction,
358    global_id: &str,
359    element: EntityId,
360    features: &[EntityId],
361) -> SpatialAuthoringResult<EntityId> {
362    super::relate(tx, ADHERES_TO_ELEMENT, global_id, element, features)
363}
364
365/// Stage an `IfcRelPositions`: products placed by a positioning element.
366///
367/// IFC4 and IFC4X3 only: it writes their layout and leaves
368/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
369/// [`position_products_with_owner_history`](super::position_products_with_owner_history), which binds the model's declared
370/// release.
371///
372/// # Errors
373///
374/// Refuses a malformed GlobalId, an empty product set, and the
375/// positioning element listed among its own products, which the
376/// schema's `NoSelfReference` rule forbids.
377pub fn position_products(
378    tx: &mut Transaction,
379    global_id: &str,
380    positioning: EntityId,
381    products: &[EntityId],
382) -> SpatialAuthoringResult<EntityId> {
383    super::relate(tx, POSITIONS, global_id, positioning, products)
384}
385
386/// Stage an `IfcRelAssignsToResource`: objects assigned to a resource.
387///
388/// IFC4 and IFC4X3 only: it writes their layout and leaves
389/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
390/// [`assign_to_resource_with_owner_history`](super::assign_to_resource_with_owner_history), which binds the model's declared
391/// release.
392///
393/// # Errors
394///
395/// Refuses a malformed GlobalId, an empty object set, and the resource
396/// listed among its own objects, which `NoSelfReference` forbids.
397pub fn assign_to_resource(
398    tx: &mut Transaction,
399    global_id: &str,
400    resource: EntityId,
401    objects: &[EntityId],
402) -> SpatialAuthoringResult<EntityId> {
403    super::relate(tx, ASSIGNS_TO_RESOURCE, global_id, resource, objects)
404}
405
406/// Stage an `IfcRelAssociatesProfileDef`: a profile associated with objects.
407///
408/// IFC4 and IFC4X3 only: it writes their layout and leaves
409/// `OwnerHistory` `$`, which IFC2X3 requires. In IFC2X3 use
410/// [`associate_profile_def_with_owner_history`](super::associate_profile_def_with_owner_history), which binds the model's declared
411/// release.
412///
413/// # Errors
414///
415/// Refuses a malformed GlobalId, an empty object set, and the profile
416/// listed among its own objects.
417pub fn associate_profile_def(
418    tx: &mut Transaction,
419    global_id: &str,
420    profile: EntityId,
421    objects: &[EntityId],
422) -> SpatialAuthoringResult<EntityId> {
423    super::relate(tx, ASSOCIATES_PROFILE_DEF, global_id, profile, objects)
424}