Skip to main content

ifc_structural/member/
varying.rs

1//! Version-aware varying-member payloads.
2
3use ifc_model::{EntityId, Value};
4
5use crate::error::{StructuralError, StructuralResult};
6use crate::member::{Member, MemberKind};
7
8impl Member<'_, '_> {
9    /// Whether the entity is one of the varying-member marker subtypes.
10    #[must_use]
11    pub fn is_varying(&self) -> bool {
12        matches!(
13            self.kind,
14            MemberKind::CurveVarying | MemberKind::SurfaceVarying
15        )
16    }
17
18    /// IFC2X3 surface-varying thicknesses after the inherited first thickness.
19    ///
20    /// IFC4 and IFC4X3 retain the varying subtype as a marker without these
21    /// attributes, so this returns `None` for those schemas and for curve members.
22    pub fn subsequent_thicknesses(&self) -> StructuralResult<Option<Vec<f64>>> {
23        if self.kind != MemberKind::SurfaceVarying
24            || !self.record.has_attribute("SubsequentThickness")
25        {
26            return Ok(None);
27        }
28        if self.thickness()?.is_none() {
29            return Err(StructuralError::SemanticViolation {
30                entity: Some(self.record.id),
31                rule: "IfcStructuralSurfaceMemberVarying.WR61",
32            });
33        }
34        let values = match self.record.value("SubsequentThickness")?.unwrap_typed() {
35            Value::List(values) => values,
36            _ => {
37                return Err(StructuralError::InvalidValue {
38                    entity: self.record.id,
39                    attribute: "SubsequentThickness",
40                    expected: "LIST [2:?] of positive finite thickness measures",
41                })
42            }
43        };
44        if values.len() < 2 {
45            return Err(StructuralError::InvalidCardinality {
46                entity: self.record.id,
47                attribute: "SubsequentThickness",
48                minimum: 2,
49                maximum: None,
50                actual: values.len(),
51            });
52        }
53        let mut thicknesses = Vec::with_capacity(values.len());
54        for value in values {
55            let number = match value.unwrap_typed() {
56                Value::Integer(value) => *value as f64,
57                Value::Real(value) => *value,
58                _ => {
59                    return Err(StructuralError::InvalidValue {
60                        entity: self.record.id,
61                        attribute: "SubsequentThickness",
62                        expected: "LIST [2:?] of positive finite thickness measures",
63                    })
64                }
65            };
66            if !number.is_finite() || number <= 0.0 {
67                return Err(StructuralError::InvalidValue {
68                    entity: self.record.id,
69                    attribute: "SubsequentThickness",
70                    expected: "LIST [2:?] of positive finite thickness measures",
71                });
72            }
73            thicknesses.push(number);
74        }
75        Ok(Some(thicknesses))
76    }
77
78    /// IFC2X3 shape aspect locating the varying surface-member thicknesses.
79    ///
80    /// Returns `None` when the selected schema does not declare this attribute.
81    pub fn varying_thickness_location(&self) -> StructuralResult<Option<EntityId>> {
82        if self.kind != MemberKind::SurfaceVarying
83            || !self.record.has_attribute("VaryingThicknessLocation")
84        {
85            return Ok(None);
86        }
87        self.record
88            .required_ref("VaryingThicknessLocation", "IfcShapeAspect")
89            .map(Some)
90    }
91}