Skip to main content

ifc_alignment/authoring/
segment.rs

1//! Alignment parameter segments: horizontal, vertical and cant.
2//!
3//! Each writes one `IfcAlignment*Segment`. `StartTag`/`EndTag` are the
4//! inherited optional labels at slots 0..1 and are left null unless named.
5
6use ifc_model::{Entity, EntityId, Transaction, Value};
7
8use super::{finite, invalid};
9use crate::error::AlignmentError;
10use crate::slot;
11
12/// Authored fields for `IfcAlignmentHorizontalSegment`.
13///
14/// Radii follow the IFC convention the reader already applies: zero means
15/// straight, and a signed value carries the turn direction.
16#[derive(Debug, Clone, Copy)]
17pub struct HorizontalSegmentDraft {
18    /// `StartPoint`, an existing `IfcCartesianPoint`.
19    pub start_point: EntityId,
20    /// `StartDirection`, the bearing in the file's angle unit.
21    pub start_direction: f64,
22    /// `StartRadiusOfCurvature`; zero for a straight.
23    pub start_radius: f64,
24    /// `EndRadiusOfCurvature`; zero for a straight.
25    pub end_radius: f64,
26    /// `SegmentLength`; must be non-negative.
27    pub segment_length: f64,
28    /// `GravityCenterLineHeight`, optional.
29    pub gravity_center_line_height: Option<f64>,
30    /// `PredefinedType`, e.g. `LINE` or `CIRCULARARC`.
31    pub predefined_type: &'static str,
32}
33
34const HSEG: &str = "IFCALIGNMENTHORIZONTALSEGMENT";
35
36/// Stage an `IfcAlignmentHorizontalSegment`.
37///
38/// # Errors
39///
40/// Refuses a non-finite measure, a negative `SegmentLength`, and a `LINE`
41/// carrying a non-zero radius -- the reader rejects that combination when
42/// lowering, so writing it would produce a file this crate cannot read.
43pub fn horizontal_segment(
44    tx: &mut Transaction,
45    draft: &HorizontalSegmentDraft,
46) -> Result<EntityId, AlignmentError> {
47    finite(HSEG, "StartDirection", draft.start_direction)?;
48    finite(HSEG, "StartRadiusOfCurvature", draft.start_radius)?;
49    finite(HSEG, "EndRadiusOfCurvature", draft.end_radius)?;
50    finite(HSEG, "SegmentLength", draft.segment_length)?;
51    if let Some(height) = draft.gravity_center_line_height {
52        finite(HSEG, "GravityCenterLineHeight", height)?;
53    }
54    if draft.segment_length < 0.0 {
55        return Err(invalid(
56            HSEG,
57            "SegmentLength",
58            "a segment cannot be shorter than nothing",
59        ));
60    }
61    if draft.predefined_type == "LINE" && (draft.start_radius != 0.0 || draft.end_radius != 0.0) {
62        return Err(invalid(
63            HSEG,
64            "PredefinedType",
65            "LINE requires zero start and end radii",
66        ));
67    }
68    let mut attrs = vec![Value::Null; slot::horizontal::PREDEFINED_TYPE + 1];
69    attrs[slot::horizontal::START_POINT] = Value::Ref(draft.start_point);
70    attrs[slot::horizontal::START_DIRECTION] = Value::Real(draft.start_direction);
71    attrs[slot::horizontal::START_RADIUS] = Value::Real(draft.start_radius);
72    attrs[slot::horizontal::END_RADIUS] = Value::Real(draft.end_radius);
73    attrs[slot::horizontal::SEGMENT_LENGTH] = Value::Real(draft.segment_length);
74    if let Some(height) = draft.gravity_center_line_height {
75        attrs[slot::horizontal::GRAVITY_CENTER_LINE_HEIGHT] = Value::Real(height);
76    }
77    attrs[slot::horizontal::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
78    Ok(tx.create(Entity::new(HSEG, attrs)))
79}
80
81/// Authored fields for `IfcAlignmentVerticalSegment`.
82#[derive(Debug, Clone, Copy)]
83pub struct VerticalSegmentDraft {
84    /// `StartDistAlong`, measured along the horizontal layout.
85    pub start_dist_along: f64,
86    /// `HorizontalLength`; must be non-negative.
87    pub horizontal_length: f64,
88    /// `StartHeight`.
89    pub start_height: f64,
90    /// `StartGradient`, a ratio, not an angle.
91    pub start_gradient: f64,
92    /// `EndGradient`, a ratio.
93    pub end_gradient: f64,
94    /// `RadiusOfCurvature`; required by arc families.
95    pub radius_of_curvature: Option<f64>,
96    /// `PredefinedType`, e.g. `CONSTANTGRADIENT` or `CIRCULARARC`.
97    pub predefined_type: &'static str,
98}
99
100const VSEG: &str = "IFCALIGNMENTVERTICALSEGMENT";
101
102/// Stage an `IfcAlignmentVerticalSegment`.
103///
104/// # Errors
105///
106/// Refuses a non-finite measure, a negative `HorizontalLength`, and a
107/// radius that disagrees with the family: the reader requires one exactly
108/// for `CIRCULARARC` and `PARABOLICARC`, so both a missing radius there and
109/// a stray radius elsewhere are refused here.
110pub fn vertical_segment(
111    tx: &mut Transaction,
112    draft: &VerticalSegmentDraft,
113) -> Result<EntityId, AlignmentError> {
114    finite(VSEG, "StartDistAlong", draft.start_dist_along)?;
115    finite(VSEG, "HorizontalLength", draft.horizontal_length)?;
116    finite(VSEG, "StartHeight", draft.start_height)?;
117    finite(VSEG, "StartGradient", draft.start_gradient)?;
118    finite(VSEG, "EndGradient", draft.end_gradient)?;
119    if let Some(radius) = draft.radius_of_curvature {
120        finite(VSEG, "RadiusOfCurvature", radius)?;
121    }
122    if draft.horizontal_length < 0.0 {
123        return Err(invalid(
124            VSEG,
125            "HorizontalLength",
126            "a segment cannot be shorter than nothing",
127        ));
128    }
129    let needs_radius = matches!(draft.predefined_type, "CIRCULARARC" | "PARABOLICARC");
130    if needs_radius != draft.radius_of_curvature.is_some() {
131        return Err(invalid(
132            VSEG,
133            "RadiusOfCurvature",
134            "radius is required exactly for circular and parabolic vertical segments",
135        ));
136    }
137    let mut attrs = vec![Value::Null; slot::vertical::PREDEFINED_TYPE + 1];
138    attrs[slot::vertical::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
139    attrs[slot::vertical::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
140    attrs[slot::vertical::START_HEIGHT] = Value::Real(draft.start_height);
141    attrs[slot::vertical::START_GRADIENT] = Value::Real(draft.start_gradient);
142    attrs[slot::vertical::END_GRADIENT] = Value::Real(draft.end_gradient);
143    if let Some(radius) = draft.radius_of_curvature {
144        attrs[slot::vertical::RADIUS_OF_CURVATURE] = Value::Real(radius);
145    }
146    attrs[slot::vertical::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
147    Ok(tx.create(Entity::new(VSEG, attrs)))
148}
149
150/// Authored fields for `IfcAlignmentCantSegment`.
151#[derive(Debug, Clone, Copy)]
152pub struct CantSegmentDraft {
153    /// `StartDistAlong`.
154    pub start_dist_along: f64,
155    /// `HorizontalLength`; must be non-negative.
156    pub horizontal_length: f64,
157    /// `StartCantLeft`.
158    pub start_cant_left: f64,
159    /// `EndCantLeft`; paired with `end_cant_right`.
160    pub end_cant_left: Option<f64>,
161    /// `StartCantRight`.
162    pub start_cant_right: f64,
163    /// `EndCantRight`; paired with `end_cant_left`.
164    pub end_cant_right: Option<f64>,
165    /// `PredefinedType`, e.g. `CONSTANTCANT` or `LINEARTRANSITION`.
166    pub predefined_type: &'static str,
167}
168
169const CSEG: &str = "IFCALIGNMENTCANTSEGMENT";
170
171/// Stage an `IfcAlignmentCantSegment`.
172///
173/// # Errors
174///
175/// Refuses a non-finite measure, a negative `HorizontalLength`, and an
176/// unpaired end cant: the reader requires left and right to be both present
177/// or both absent, because one alone does not describe a rail pair.
178pub fn cant_segment(
179    tx: &mut Transaction,
180    draft: &CantSegmentDraft,
181) -> Result<EntityId, AlignmentError> {
182    finite(CSEG, "StartDistAlong", draft.start_dist_along)?;
183    finite(CSEG, "HorizontalLength", draft.horizontal_length)?;
184    finite(CSEG, "StartCantLeft", draft.start_cant_left)?;
185    finite(CSEG, "StartCantRight", draft.start_cant_right)?;
186    if let Some(value) = draft.end_cant_left {
187        finite(CSEG, "EndCantLeft", value)?;
188    }
189    if let Some(value) = draft.end_cant_right {
190        finite(CSEG, "EndCantRight", value)?;
191    }
192    if draft.horizontal_length < 0.0 {
193        return Err(invalid(
194            CSEG,
195            "HorizontalLength",
196            "a segment cannot be shorter than nothing",
197        ));
198    }
199    if draft.end_cant_left.is_some() != draft.end_cant_right.is_some() {
200        return Err(invalid(
201            CSEG,
202            "EndCantLeft",
203            "left and right end cant must both be supplied or both omitted",
204        ));
205    }
206    let mut attrs = vec![Value::Null; slot::cant::PREDEFINED_TYPE + 1];
207    attrs[slot::cant::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
208    attrs[slot::cant::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
209    attrs[slot::cant::START_CANT_LEFT] = Value::Real(draft.start_cant_left);
210    if let Some(value) = draft.end_cant_left {
211        attrs[slot::cant::END_CANT_LEFT] = Value::Real(value);
212    }
213    attrs[slot::cant::START_CANT_RIGHT] = Value::Real(draft.start_cant_right);
214    if let Some(value) = draft.end_cant_right {
215        attrs[slot::cant::END_CANT_RIGHT] = Value::Real(value);
216    }
217    attrs[slot::cant::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
218    Ok(tx.create(Entity::new(CSEG, attrs)))
219}