Skip to main content

ifc_structural/load/
reinforcement.rs

1//! `IfcSurfaceReinforcementArea` projection (IFC4 and IFC4X3).
2//!
3//! The entity is an `IfcStructuralLoadOrResult` beside, not under,
4//! `IfcStructuralLoadStatic`, and IFC2X3 does not declare it: the view
5//! refuses it there with [`StructuralError::UnsupportedSchema`] rather than
6//! reading a same-named record by guesswork.
7
8use ifc_model::{EntityId, Value};
9
10use crate::error::{StructuralError, StructuralResult};
11use crate::view::Record;
12
13/// Borrowed projection of an `IfcSurfaceReinforcementArea`: required
14/// reinforcement areas per unit length of a surface member, as a design
15/// result. It preserves the authored values; it does not design anything.
16#[derive(Debug, Clone, Copy)]
17pub struct SurfaceReinforcementArea<'m, 's> {
18    record: Record<'m, 's>,
19}
20
21impl<'m, 's> SurfaceReinforcementArea<'m, 's> {
22    /// Wrap `record`, enforcing `SurfaceAndOrShearAreaSpecified`: at least
23    /// one of the three areas exists.
24    pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
25        let area = Self { record };
26        let specified = [
27            "SurfaceReinforcement1",
28            "SurfaceReinforcement2",
29            "ShearReinforcement",
30        ]
31        .into_iter()
32        .map(|attribute| {
33            area.record
34                .value(attribute)
35                .map(|value| !matches!(value.unwrap_typed(), Value::Null | Value::Derived))
36        })
37        .collect::<StructuralResult<Vec<_>>>()?;
38        if !specified.contains(&true) {
39            return Err(StructuralError::SemanticViolation {
40                entity: Some(area.record.id),
41                rule: "SurfaceAndOrShearAreaSpecified",
42            });
43        }
44        Ok(area)
45    }
46
47    #[must_use]
48    /// The `IfcSurfaceReinforcementArea` entity id.
49    pub fn id(&self) -> EntityId {
50        self.record.id
51    }
52
53    /// `Name`, inherited from `IfcStructuralLoad`. Legally absent.
54    pub fn name(&self) -> StructuralResult<Option<&'m str>> {
55        self.record.optional_text("Name")
56    }
57
58    /// `SurfaceReinforcement1`: `LIST [2:3] OF IfcLengthMeasure`, the
59    /// reinforcement area in the first direction. Legally absent.
60    ///
61    /// Refused with [`StructuralError::InvalidCardinality`] outside two or
62    /// three entries, and with [`StructuralError::SemanticViolation`]
63    /// (`NonnegativeArea1`) when the first or second entry is negative, the
64    /// entries the published rule constrains.
65    pub fn surface_reinforcement_1(&self) -> StructuralResult<Option<Vec<f64>>> {
66        self.area_list("SurfaceReinforcement1", "NonnegativeArea1")
67    }
68
69    /// `SurfaceReinforcement2`: `LIST [2:3] OF IfcLengthMeasure`, the
70    /// reinforcement area in the second direction. Legally absent.
71    ///
72    /// Refused as [`SurfaceReinforcementArea::surface_reinforcement_1`] is,
73    /// with rule `NonnegativeArea2`.
74    pub fn surface_reinforcement_2(&self) -> StructuralResult<Option<Vec<f64>>> {
75        self.area_list("SurfaceReinforcement2", "NonnegativeArea2")
76    }
77
78    /// `ShearReinforcement`: `IfcRatioMeasure`. Legally absent; refused with
79    /// [`StructuralError::SemanticViolation`] (`NonnegativeArea3`) when
80    /// negative.
81    pub fn shear_reinforcement(&self) -> StructuralResult<Option<f64>> {
82        let value = self.record.optional_number("ShearReinforcement")?;
83        if value.is_some_and(|number| number < 0.0) {
84            return Err(StructuralError::SemanticViolation {
85                entity: Some(self.record.id),
86                rule: "NonnegativeArea3",
87            });
88        }
89        Ok(value)
90    }
91
92    fn area_list(
93        &self,
94        attribute: &'static str,
95        rule: &'static str,
96    ) -> StructuralResult<Option<Vec<f64>>> {
97        let values = match self.record.value(attribute)?.unwrap_typed() {
98            Value::Null | Value::Derived => return Ok(None),
99            Value::List(values) => values,
100            _ => {
101                return Err(StructuralError::InvalidValue {
102                    entity: self.record.id,
103                    attribute,
104                    expected: "LIST [2:3] OF IfcLengthMeasure or null",
105                })
106            }
107        };
108        if !(2..=3).contains(&values.len()) {
109            return Err(StructuralError::InvalidCardinality {
110                entity: self.record.id,
111                attribute,
112                minimum: 2,
113                maximum: Some(3),
114                actual: values.len(),
115            });
116        }
117        let numbers = values
118            .iter()
119            .map(|value| match value.unwrap_typed() {
120                Value::Integer(number) => Ok(*number as f64),
121                Value::Real(number) if number.is_finite() => Ok(*number),
122                _ => Err(StructuralError::InvalidValue {
123                    entity: self.record.id,
124                    attribute,
125                    expected: "LIST [2:3] OF finite IfcLengthMeasure",
126                }),
127            })
128            .collect::<StructuralResult<Vec<f64>>>()?;
129        if numbers[..2].iter().any(|number| *number < 0.0) {
130            return Err(StructuralError::SemanticViolation {
131                entity: Some(self.record.id),
132                rule,
133            });
134        }
135        Ok(Some(numbers))
136    }
137}