1use ifc_model::value::Value;
4use ifc_model::{EntityId, Model};
5
6use crate::error::{AlignmentError, AlignmentResult};
7use crate::horizontal::AlignmentUnits;
8use crate::slot;
9
10#[non_exhaustive]
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum CantSegmentType {
15 BlossCurve,
17 ConstantCant,
19 CosineCurve,
21 HelmertCurve,
23 LinearTransition,
25 SineCurve,
27 VienneseBend,
35 UserDefined,
37 NotDefined,
39 Other(String),
41}
42
43#[derive(Debug, Clone, PartialEq)]
46pub struct CantSegment {
47 pub entity: EntityId,
49 pub start_dist_along: f64,
52 pub horizontal_length: f64,
54 pub start_cant_left: f64,
56 pub end_cant_left: Option<f64>,
59 pub start_cant_right: f64,
61 pub end_cant_right: Option<f64>,
65 pub predefined_type: CantSegmentType,
67}
68
69impl CantSegment {
70 pub(crate) fn predefined_type_name(&self) -> &str {
72 match &self.predefined_type {
73 CantSegmentType::BlossCurve => "BLOSSCURVE",
74 CantSegmentType::ConstantCant => "CONSTANTCANT",
75 CantSegmentType::CosineCurve => "COSINECURVE",
76 CantSegmentType::HelmertCurve => "HELMERTCURVE",
77 CantSegmentType::LinearTransition => "LINEARTRANSITION",
78 CantSegmentType::SineCurve => "SINECURVE",
79 CantSegmentType::VienneseBend => "VIENNESEBEND",
80 CantSegmentType::UserDefined => "USERDEFINED",
81 CantSegmentType::NotDefined => "NOTDEFINED",
82 CantSegmentType::Other(name) => name,
83 }
84 }
85}
86
87pub fn read_cant_segment(
94 model: &Model,
95 id: EntityId,
96 units: AlignmentUnits,
97) -> AlignmentResult<CantSegment> {
98 validate_units(units)?;
99 let entity = model
100 .get(id)
101 .ok_or(AlignmentError::MissingEntity { entity: id })?;
102 if !entity
103 .type_name
104 .eq_ignore_ascii_case("IFCALIGNMENTCANTSEGMENT")
105 {
106 return Err(AlignmentError::WrongType {
107 entity: id,
108 expected: "IFCALIGNMENTCANTSEGMENT",
109 actual: entity.type_name.to_string(),
110 });
111 }
112 let values = &entity.attributes;
115 let start_dist_along = length(
116 number(values, id, slot::cant::START_DIST_ALONG, "StartDistAlong")?,
117 units,
118 );
119 let horizontal_length = length(
120 number(
121 values,
122 id,
123 slot::cant::HORIZONTAL_LENGTH,
124 "HorizontalLength",
125 )?,
126 units,
127 );
128 let start_cant_left = length(
129 number(values, id, slot::cant::START_CANT_LEFT, "StartCantLeft")?,
130 units,
131 );
132 let end_cant_left = optional_number(values, id, slot::cant::END_CANT_LEFT, "EndCantLeft")?
133 .map(|value| length(value, units));
134 let start_cant_right = length(
135 number(values, id, slot::cant::START_CANT_RIGHT, "StartCantRight")?,
136 units,
137 );
138 let end_cant_right = optional_number(values, id, slot::cant::END_CANT_RIGHT, "EndCantRight")?
139 .map(|value| length(value, units));
140 let predefined_type = parse_type(enum_name(
141 values,
142 id,
143 slot::cant::PREDEFINED_TYPE,
144 "PredefinedType",
145 )?);
146
147 let finite = [
148 start_dist_along,
149 horizontal_length,
150 start_cant_left,
151 start_cant_right,
152 ]
153 .into_iter()
154 .all(f64::is_finite)
155 && end_cant_left.is_none_or(f64::is_finite)
156 && end_cant_right.is_none_or(f64::is_finite);
157 if !finite || horizontal_length < 0.0 {
158 return Err(AlignmentError::InvalidSegment {
159 entity: id,
160 detail: "cant parameters must be finite and horizontal length non-negative",
161 });
162 }
163 let ends_are_paired = end_cant_left.is_some() == end_cant_right.is_some();
164 if !ends_are_paired {
165 return Err(AlignmentError::InvalidSegment {
166 entity: id,
167 detail: "left and right end cant must both be supplied or both omitted",
168 });
169 }
170
171 Ok(CantSegment {
172 entity: id,
173 start_dist_along,
174 horizontal_length,
175 start_cant_left,
176 end_cant_left,
177 start_cant_right,
178 end_cant_right,
179 predefined_type,
180 })
181}
182
183fn parse_type(name: &str) -> CantSegmentType {
184 match name.to_ascii_uppercase().as_str() {
185 "BLOSSCURVE" => CantSegmentType::BlossCurve,
186 "CONSTANTCANT" => CantSegmentType::ConstantCant,
187 "COSINECURVE" => CantSegmentType::CosineCurve,
188 "HELMERTCURVE" => CantSegmentType::HelmertCurve,
189 "LINEARTRANSITION" => CantSegmentType::LinearTransition,
190 "SINECURVE" => CantSegmentType::SineCurve,
191 "VIENNESEBEND" => CantSegmentType::VienneseBend,
192 "USERDEFINED" => CantSegmentType::UserDefined,
193 "NOTDEFINED" => CantSegmentType::NotDefined,
194 _ => CantSegmentType::Other(name.to_string()),
195 }
196}
197
198fn validate_units(units: AlignmentUnits) -> AlignmentResult<()> {
199 if !units.length_to_metres.is_finite() || units.length_to_metres <= 0.0 {
200 return Err(AlignmentError::InvalidUnits {
201 detail: "length factor must be finite and positive",
202 });
203 }
204 Ok(())
205}
206
207fn length(value: f64, units: AlignmentUnits) -> f64 {
208 value * units.length_to_metres
209}
210
211fn number(values: &[Value], id: EntityId, slot: usize, name: &'static str) -> AlignmentResult<f64> {
212 match values.get(slot) {
213 Some(Value::Real(value)) => Ok(*value),
214 Some(Value::Integer(value)) => Ok(*value as f64),
215 _ => Err(AlignmentError::InvalidAttribute {
216 entity: id,
217 index: slot,
218 name,
219 }),
220 }
221}
222
223fn optional_number(
224 values: &[Value],
225 id: EntityId,
226 slot: usize,
227 name: &'static str,
228) -> AlignmentResult<Option<f64>> {
229 match values.get(slot) {
230 Some(Value::Null) => Ok(None),
231 Some(Value::Real(value)) => Ok(Some(*value)),
232 Some(Value::Integer(value)) => Ok(Some(*value as f64)),
233 _ => Err(AlignmentError::InvalidAttribute {
234 entity: id,
235 index: slot,
236 name,
237 }),
238 }
239}
240
241fn enum_name<'a>(
242 values: &'a [Value],
243 id: EntityId,
244 slot: usize,
245 name: &'static str,
246) -> AlignmentResult<&'a str> {
247 match values.get(slot) {
248 Some(Value::Enum(value)) => Ok(value),
249 _ => Err(AlignmentError::InvalidAttribute {
250 entity: id,
251 index: slot,
252 name,
253 }),
254 }
255}