ifc_structural/condition/
mod.rs1use ifc_model::{EntityId, Value};
7use ifc_schema::SchemaVersion;
8
9use crate::error::{StructuralError, StructuralResult};
10use crate::view::Record;
11
12#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct AxisValues<T> {
15 pub x: T,
17 pub y: T,
19 pub z: T,
21}
22
23impl<T: Default> Default for AxisValues<T> {
24 fn default() -> Self {
25 Self {
26 x: T::default(),
27 y: T::default(),
28 z: T::default(),
29 }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35pub enum StiffnessValue {
36 Boolean(bool),
38 Measure(f64),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum BoundaryConditionKind {
45 Edge,
47 Face,
49 Node,
51 NodeWarping,
53}
54
55#[derive(Debug, Clone, Copy)]
57pub struct BoundaryCondition<'m, 's> {
58 record: Record<'m, 's>,
59 kind: BoundaryConditionKind,
60}
61
62impl<'m, 's> BoundaryCondition<'m, 's> {
63 pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
64 let kind = if record
65 .schema
66 .is_a(&record.entity.type_name, "IfcBoundaryNodeConditionWarping")
67 {
68 BoundaryConditionKind::NodeWarping
69 } else if record
70 .schema
71 .is_a(&record.entity.type_name, "IfcBoundaryNodeCondition")
72 {
73 BoundaryConditionKind::Node
74 } else if record
75 .schema
76 .is_a(&record.entity.type_name, "IfcBoundaryEdgeCondition")
77 {
78 BoundaryConditionKind::Edge
79 } else if record
80 .schema
81 .is_a(&record.entity.type_name, "IfcBoundaryFaceCondition")
82 {
83 BoundaryConditionKind::Face
84 } else {
85 return Err(StructuralError::WrongType {
86 id: record.id,
87 expected: "concrete IfcBoundaryCondition",
88 actual: record.entity.type_name.to_string(),
89 });
90 };
91 Ok(Self { record, kind })
92 }
93
94 #[must_use]
96 pub fn id(&self) -> EntityId {
97 self.record.id
98 }
99
100 #[must_use]
102 pub fn kind(&self) -> BoundaryConditionKind {
103 self.kind
104 }
105
106 pub fn name(&self) -> StructuralResult<Option<&'m str>> {
108 self.record.optional_text("Name")
109 }
110
111 pub fn translational_stiffnesses(
113 &self,
114 ) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
115 let (current, legacy) = match self.kind {
116 BoundaryConditionKind::Edge => (
117 [
118 "TranslationalStiffnessByLengthX",
119 "TranslationalStiffnessByLengthY",
120 "TranslationalStiffnessByLengthZ",
121 ],
122 [
123 "LinearStiffnessByLengthX",
124 "LinearStiffnessByLengthY",
125 "LinearStiffnessByLengthZ",
126 ],
127 ),
128 BoundaryConditionKind::Face => (
129 [
130 "TranslationalStiffnessByAreaX",
131 "TranslationalStiffnessByAreaY",
132 "TranslationalStiffnessByAreaZ",
133 ],
134 [
135 "LinearStiffnessByAreaX",
136 "LinearStiffnessByAreaY",
137 "LinearStiffnessByAreaZ",
138 ],
139 ),
140 BoundaryConditionKind::Node | BoundaryConditionKind::NodeWarping => (
141 [
142 "TranslationalStiffnessX",
143 "TranslationalStiffnessY",
144 "TranslationalStiffnessZ",
145 ],
146 ["LinearStiffnessX", "LinearStiffnessY", "LinearStiffnessZ"],
147 ),
148 };
149 self.stiffness_axes(current, legacy)
150 }
151
152 pub fn rotational_stiffnesses(&self) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
157 if self.kind == BoundaryConditionKind::Face {
158 return Ok(AxisValues::default());
159 }
160 let names = if self.kind == BoundaryConditionKind::Edge {
161 [
162 "RotationalStiffnessByLengthX",
163 "RotationalStiffnessByLengthY",
164 "RotationalStiffnessByLengthZ",
165 ]
166 } else {
167 [
168 "RotationalStiffnessX",
169 "RotationalStiffnessY",
170 "RotationalStiffnessZ",
171 ]
172 };
173 self.stiffness_axes(names, names)
174 }
175
176 pub fn warping_stiffness(&self) -> StructuralResult<Option<StiffnessValue>> {
178 if self.kind != BoundaryConditionKind::NodeWarping {
179 return Ok(None);
180 }
181 decode_stiffness(&self.record, "WarpingStiffness")
182 }
183
184 fn stiffness_axes(
185 &self,
186 current: [&'static str; 3],
187 legacy: [&'static str; 3],
188 ) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
189 let value = |index| {
190 let selected = if self.record.has_attribute(current[index]) {
191 current[index]
192 } else {
193 legacy[index]
194 };
195 decode_stiffness(&self.record, selected)
196 };
197 Ok(AxisValues {
198 x: value(0)?,
199 y: value(1)?,
200 z: value(2)?,
201 })
202 }
203}
204
205fn decode_stiffness(
206 record: &Record<'_, '_>,
207 attribute: &'static str,
208) -> StructuralResult<Option<StiffnessValue>> {
209 match record.value(attribute)?.unwrap_typed() {
210 Value::Null | Value::Derived => Ok(None),
211 Value::Bool(value) if record.schema.version() != Some(SchemaVersion::Ifc2x3) => {
212 Ok(Some(StiffnessValue::Boolean(*value)))
213 }
214 Value::Integer(value) => Ok(Some(StiffnessValue::Measure(*value as f64))),
215 Value::Real(value) if value.is_finite() => Ok(Some(StiffnessValue::Measure(*value))),
216 _ => Err(StructuralError::InvalidValue {
217 entity: record.id,
218 attribute,
219 expected: "finite stiffness measure or schema-supported boolean",
220 }),
221 }
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum ConnectionConditionKind {
227 Failure,
229 Slippage,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq)]
235pub struct FailureLimits {
236 pub tension: AxisValues<Option<f64>>,
238 pub compression: AxisValues<Option<f64>>,
240}
241
242#[derive(Debug, Clone, Copy)]
244pub struct ConnectionCondition<'m, 's> {
245 record: Record<'m, 's>,
246 kind: ConnectionConditionKind,
247}
248
249impl<'m, 's> ConnectionCondition<'m, 's> {
250 pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
251 let kind = if record
252 .schema
253 .is_a(&record.entity.type_name, "IfcFailureConnectionCondition")
254 {
255 ConnectionConditionKind::Failure
256 } else if record
257 .schema
258 .is_a(&record.entity.type_name, "IfcSlippageConnectionCondition")
259 {
260 ConnectionConditionKind::Slippage
261 } else {
262 return Err(StructuralError::WrongType {
263 id: record.id,
264 expected: "concrete IfcStructuralConnectionCondition",
265 actual: record.entity.type_name.to_string(),
266 });
267 };
268 Ok(Self { record, kind })
269 }
270
271 #[must_use]
273 pub fn id(&self) -> EntityId {
274 self.record.id
275 }
276
277 #[must_use]
279 pub fn kind(&self) -> ConnectionConditionKind {
280 self.kind
281 }
282
283 pub fn name(&self) -> StructuralResult<Option<&'m str>> {
285 self.record.optional_text("Name")
286 }
287
288 pub fn failure_limits(&self) -> StructuralResult<Option<FailureLimits>> {
290 if self.kind != ConnectionConditionKind::Failure {
291 return Ok(None);
292 }
293 Ok(Some(FailureLimits {
294 tension: number_axes(
295 &self.record,
296 ["TensionFailureX", "TensionFailureY", "TensionFailureZ"],
297 )?,
298 compression: number_axes(
299 &self.record,
300 [
301 "CompressionFailureX",
302 "CompressionFailureY",
303 "CompressionFailureZ",
304 ],
305 )?,
306 }))
307 }
308
309 pub fn slippages(&self) -> StructuralResult<Option<AxisValues<Option<f64>>>> {
311 if self.kind != ConnectionConditionKind::Slippage {
312 return Ok(None);
313 }
314 Ok(Some(number_axes(
315 &self.record,
316 ["SlippageX", "SlippageY", "SlippageZ"],
317 )?))
318 }
319}
320
321fn number_axes(
322 record: &Record<'_, '_>,
323 names: [&'static str; 3],
324) -> StructuralResult<AxisValues<Option<f64>>> {
325 Ok(AxisValues {
326 x: record.optional_number(names[0])?,
327 y: record.optional_number(names[1])?,
328 z: record.optional_number(names[2])?,
329 })
330}