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