1use ifc_model::{Entity, EntityId, Transaction, Value};
7
8use super::{finite, invalid};
9use crate::error::AlignmentError;
10use crate::slot;
11
12#[derive(Debug, Clone, Copy)]
17#[non_exhaustive]
18pub struct HorizontalSegmentDraft {
19 pub start_point: EntityId,
21 pub start_direction: f64,
23 pub start_radius: f64,
25 pub end_radius: f64,
27 pub segment_length: f64,
29 pub gravity_center_line_height: Option<f64>,
31 pub predefined_type: &'static str,
33}
34
35impl HorizontalSegmentDraft {
36 #[must_use]
38 pub const fn new(
39 start_point: EntityId,
40 start_direction: f64,
41 start_radius: f64,
42 end_radius: f64,
43 segment_length: f64,
44 predefined_type: &'static str,
45 ) -> Self {
46 Self {
47 start_point,
48 start_direction,
49 start_radius,
50 end_radius,
51 segment_length,
52 gravity_center_line_height: None,
53 predefined_type,
54 }
55 }
56
57 #[must_use]
59 pub const fn gravity_center_line_height(mut self, value: f64) -> Self {
60 self.gravity_center_line_height = Some(value);
61 self
62 }
63}
64
65const HSEG: &str = "IFCALIGNMENTHORIZONTALSEGMENT";
66
67pub fn horizontal_segment(
75 tx: &mut Transaction,
76 draft: &HorizontalSegmentDraft,
77) -> Result<EntityId, AlignmentError> {
78 finite(HSEG, "StartDirection", draft.start_direction)?;
79 finite(HSEG, "StartRadiusOfCurvature", draft.start_radius)?;
80 finite(HSEG, "EndRadiusOfCurvature", draft.end_radius)?;
81 finite(HSEG, "SegmentLength", draft.segment_length)?;
82 if let Some(height) = draft.gravity_center_line_height {
83 finite(HSEG, "GravityCenterLineHeight", height)?;
84 }
85 if draft.segment_length < 0.0 {
86 return Err(invalid(
87 HSEG,
88 "SegmentLength",
89 "a segment cannot be shorter than nothing",
90 ));
91 }
92 if draft.predefined_type == "LINE" && (draft.start_radius != 0.0 || draft.end_radius != 0.0) {
93 return Err(invalid(
94 HSEG,
95 "PredefinedType",
96 "LINE requires zero start and end radii",
97 ));
98 }
99 let mut attrs = vec![Value::Null; slot::horizontal::PREDEFINED_TYPE + 1];
100 attrs[slot::horizontal::START_POINT] = Value::Ref(draft.start_point);
101 attrs[slot::horizontal::START_DIRECTION] = Value::Real(draft.start_direction);
102 attrs[slot::horizontal::START_RADIUS] = Value::Real(draft.start_radius);
103 attrs[slot::horizontal::END_RADIUS] = Value::Real(draft.end_radius);
104 attrs[slot::horizontal::SEGMENT_LENGTH] = Value::Real(draft.segment_length);
105 if let Some(height) = draft.gravity_center_line_height {
106 attrs[slot::horizontal::GRAVITY_CENTER_LINE_HEIGHT] = Value::Real(height);
107 }
108 attrs[slot::horizontal::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
109 Ok(tx.create(Entity::new(HSEG, attrs)))
110}
111
112#[derive(Debug, Clone, Copy)]
114#[non_exhaustive]
115pub struct VerticalSegmentDraft {
116 pub start_dist_along: f64,
118 pub horizontal_length: f64,
120 pub start_height: f64,
122 pub start_gradient: f64,
124 pub end_gradient: f64,
126 pub radius_of_curvature: Option<f64>,
128 pub predefined_type: &'static str,
130}
131
132impl VerticalSegmentDraft {
133 #[must_use]
135 pub const fn new(
136 start_dist_along: f64,
137 horizontal_length: f64,
138 start_height: f64,
139 start_gradient: f64,
140 end_gradient: f64,
141 predefined_type: &'static str,
142 ) -> Self {
143 Self {
144 start_dist_along,
145 horizontal_length,
146 start_height,
147 start_gradient,
148 end_gradient,
149 radius_of_curvature: None,
150 predefined_type,
151 }
152 }
153
154 #[must_use]
156 pub const fn radius_of_curvature(mut self, value: f64) -> Self {
157 self.radius_of_curvature = Some(value);
158 self
159 }
160}
161
162const VSEG: &str = "IFCALIGNMENTVERTICALSEGMENT";
163
164pub fn vertical_segment(
173 tx: &mut Transaction,
174 draft: &VerticalSegmentDraft,
175) -> Result<EntityId, AlignmentError> {
176 finite(VSEG, "StartDistAlong", draft.start_dist_along)?;
177 finite(VSEG, "HorizontalLength", draft.horizontal_length)?;
178 finite(VSEG, "StartHeight", draft.start_height)?;
179 finite(VSEG, "StartGradient", draft.start_gradient)?;
180 finite(VSEG, "EndGradient", draft.end_gradient)?;
181 if let Some(radius) = draft.radius_of_curvature {
182 finite(VSEG, "RadiusOfCurvature", radius)?;
183 }
184 if draft.horizontal_length < 0.0 {
185 return Err(invalid(
186 VSEG,
187 "HorizontalLength",
188 "a segment cannot be shorter than nothing",
189 ));
190 }
191 let needs_radius = matches!(draft.predefined_type, "CIRCULARARC" | "PARABOLICARC");
192 if needs_radius != draft.radius_of_curvature.is_some() {
193 return Err(invalid(
194 VSEG,
195 "RadiusOfCurvature",
196 "radius is required exactly for circular and parabolic vertical segments",
197 ));
198 }
199 let mut attrs = vec![Value::Null; slot::vertical::PREDEFINED_TYPE + 1];
200 attrs[slot::vertical::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
201 attrs[slot::vertical::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
202 attrs[slot::vertical::START_HEIGHT] = Value::Real(draft.start_height);
203 attrs[slot::vertical::START_GRADIENT] = Value::Real(draft.start_gradient);
204 attrs[slot::vertical::END_GRADIENT] = Value::Real(draft.end_gradient);
205 if let Some(radius) = draft.radius_of_curvature {
206 attrs[slot::vertical::RADIUS_OF_CURVATURE] = Value::Real(radius);
207 }
208 attrs[slot::vertical::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
209 Ok(tx.create(Entity::new(VSEG, attrs)))
210}
211
212#[derive(Debug, Clone, Copy)]
214#[non_exhaustive]
215pub struct CantSegmentDraft {
216 pub start_dist_along: f64,
218 pub horizontal_length: f64,
220 pub start_cant_left: f64,
222 pub end_cant_left: Option<f64>,
224 pub start_cant_right: f64,
226 pub end_cant_right: Option<f64>,
228 pub predefined_type: &'static str,
230}
231
232impl CantSegmentDraft {
233 #[must_use]
236 pub const fn new(
237 start_dist_along: f64,
238 horizontal_length: f64,
239 start_cant_left: f64,
240 start_cant_right: f64,
241 predefined_type: &'static str,
242 ) -> Self {
243 Self {
244 start_dist_along,
245 horizontal_length,
246 start_cant_left,
247 end_cant_left: None,
248 start_cant_right,
249 end_cant_right: None,
250 predefined_type,
251 }
252 }
253
254 #[must_use]
256 pub const fn end_cant_left(mut self, value: f64) -> Self {
257 self.end_cant_left = Some(value);
258 self
259 }
260
261 #[must_use]
263 pub const fn end_cant_right(mut self, value: f64) -> Self {
264 self.end_cant_right = Some(value);
265 self
266 }
267}
268
269const CSEG: &str = "IFCALIGNMENTCANTSEGMENT";
270
271pub fn cant_segment(
279 tx: &mut Transaction,
280 draft: &CantSegmentDraft,
281) -> Result<EntityId, AlignmentError> {
282 finite(CSEG, "StartDistAlong", draft.start_dist_along)?;
283 finite(CSEG, "HorizontalLength", draft.horizontal_length)?;
284 finite(CSEG, "StartCantLeft", draft.start_cant_left)?;
285 finite(CSEG, "StartCantRight", draft.start_cant_right)?;
286 if let Some(value) = draft.end_cant_left {
287 finite(CSEG, "EndCantLeft", value)?;
288 }
289 if let Some(value) = draft.end_cant_right {
290 finite(CSEG, "EndCantRight", value)?;
291 }
292 if draft.horizontal_length < 0.0 {
293 return Err(invalid(
294 CSEG,
295 "HorizontalLength",
296 "a segment cannot be shorter than nothing",
297 ));
298 }
299 if draft.end_cant_left.is_some() != draft.end_cant_right.is_some() {
300 return Err(invalid(
301 CSEG,
302 "EndCantLeft",
303 "left and right end cant must both be supplied or both omitted",
304 ));
305 }
306 let mut attrs = vec![Value::Null; slot::cant::PREDEFINED_TYPE + 1];
307 attrs[slot::cant::START_DIST_ALONG] = Value::Real(draft.start_dist_along);
308 attrs[slot::cant::HORIZONTAL_LENGTH] = Value::Real(draft.horizontal_length);
309 attrs[slot::cant::START_CANT_LEFT] = Value::Real(draft.start_cant_left);
310 if let Some(value) = draft.end_cant_left {
311 attrs[slot::cant::END_CANT_LEFT] = Value::Real(value);
312 }
313 attrs[slot::cant::START_CANT_RIGHT] = Value::Real(draft.start_cant_right);
314 if let Some(value) = draft.end_cant_right {
315 attrs[slot::cant::END_CANT_RIGHT] = Value::Real(value);
316 }
317 attrs[slot::cant::PREDEFINED_TYPE] = Value::Enum(draft.predefined_type.into());
318 Ok(tx.create(Entity::new(CSEG, attrs)))
319}