Skip to main content

ifc_alignment/authoring/
layout.rs

1//! Alignment roots and their layout children.
2//!
3//! `IfcAlignment`, `IfcAlignmentHorizontal`, `IfcAlignmentVertical` and
4//! `IfcAlignmentCant` are all `IfcProduct` subtypes, so they carry the same
5//! seven inherited attributes before adding their own.
6//!
7//! Nesting is deliberately not written here. `IfcRelNests` is a generic
8//! relationship owned by `ifc-author`, and duplicating its construction in
9//! this crate would give the same relationship two writers.
10
11use ifc_model::{Entity, EntityId, Transaction, Value};
12
13use super::{finite, guid, invalid};
14use crate::error::AlignmentError;
15use crate::slot;
16
17/// Fill the seven inherited `IfcProduct` slots.
18fn product_attrs(
19    type_name: &'static str,
20    arity: usize,
21    global_id: &str,
22    name: Option<&str>,
23) -> Result<Vec<Value>, AlignmentError> {
24    let mut attrs = vec![Value::Null; arity];
25    attrs[slot::product::GLOBAL_ID] = guid(type_name, global_id)?;
26    if let Some(name) = name {
27        attrs[slot::product::NAME] = Value::Text(name.into());
28    }
29    Ok(attrs)
30}
31
32/// Stage an `IfcAlignment`.
33///
34/// # Errors
35///
36/// Refuses a `GlobalId` that is not 22 characters.
37pub fn alignment(
38    tx: &mut Transaction,
39    global_id: &str,
40    name: Option<&str>,
41    predefined_type: Option<&'static str>,
42) -> Result<EntityId, AlignmentError> {
43    let mut attrs = product_attrs("IFCALIGNMENT", slot::alignment::ARITY, global_id, name)?;
44    if let Some(token) = predefined_type {
45        attrs[slot::alignment::PREDEFINED_TYPE] = Value::Enum(token.into());
46    }
47    Ok(tx.create(Entity::new("IFCALIGNMENT", attrs)))
48}
49
50/// Stage an `IfcAlignmentHorizontal`.
51///
52/// # Errors
53///
54/// Refuses a `GlobalId` that is not 22 characters.
55pub fn horizontal_layout(
56    tx: &mut Transaction,
57    global_id: &str,
58    name: Option<&str>,
59) -> Result<EntityId, AlignmentError> {
60    let attrs = product_attrs(
61        "IFCALIGNMENTHORIZONTAL",
62        slot::product::ARITY,
63        global_id,
64        name,
65    )?;
66    Ok(tx.create(Entity::new("IFCALIGNMENTHORIZONTAL", attrs)))
67}
68
69/// Stage an `IfcAlignmentVertical`.
70///
71/// # Errors
72///
73/// Refuses a `GlobalId` that is not 22 characters.
74pub fn vertical_layout(
75    tx: &mut Transaction,
76    global_id: &str,
77    name: Option<&str>,
78) -> Result<EntityId, AlignmentError> {
79    let attrs = product_attrs(
80        "IFCALIGNMENTVERTICAL",
81        slot::product::ARITY,
82        global_id,
83        name,
84    )?;
85    Ok(tx.create(Entity::new("IFCALIGNMENTVERTICAL", attrs)))
86}
87
88/// Stage an `IfcAlignmentCant`.
89///
90/// `rail_head_distance` is the gauge the cant values are measured against.
91/// It is mandatory on this entity, and a non-positive value would make the
92/// cant angle meaningless, so it is checked rather than written blindly.
93///
94/// # Errors
95///
96/// Refuses a `GlobalId` that is not 22 characters, and a rail head distance
97/// that is not finite and positive.
98pub fn cant_layout(
99    tx: &mut Transaction,
100    global_id: &str,
101    name: Option<&str>,
102    rail_head_distance: f64,
103) -> Result<EntityId, AlignmentError> {
104    finite("IFCALIGNMENTCANT", "RailHeadDistance", rail_head_distance)?;
105    if rail_head_distance <= 0.0 {
106        return Err(invalid(
107            "IFCALIGNMENTCANT",
108            "RailHeadDistance",
109            "the distance between rail heads must be positive",
110        ));
111    }
112    let mut attrs = product_attrs(
113        "IFCALIGNMENTCANT",
114        slot::cant_layout::ARITY,
115        global_id,
116        name,
117    )?;
118    attrs[slot::cant_layout::RAIL_HEAD_DISTANCE] = Value::Real(rail_head_distance);
119    Ok(tx.create(Entity::new("IFCALIGNMENTCANT", attrs)))
120}
121
122/// Stage an `IfcAlignmentSegment` wrapping one parameter segment.
123///
124/// A layout does not nest parameter segments directly: it nests these
125/// wrappers, each pointing at its `DesignParameters`. `AlignmentView`'s
126/// traversal resolves that reference, so a layout built without wrappers
127/// reads back as a malformed segment chain.
128///
129/// # Errors
130///
131/// Refuses a `GlobalId` that is not 22 characters.
132pub fn alignment_segment(
133    tx: &mut Transaction,
134    global_id: &str,
135    design_parameters: EntityId,
136) -> Result<EntityId, AlignmentError> {
137    let mut attrs = product_attrs("IFCALIGNMENTSEGMENT", slot::segment::ARITY, global_id, None)?;
138    attrs[slot::segment::DESIGN_PARAMETERS] = Value::Ref(design_parameters);
139    Ok(tx.create(Entity::new("IFCALIGNMENTSEGMENT", attrs)))
140}