Skip to main content

ifc_structural/action/
varying.rs

1//! IFC2X3 varying linear and planar actions.
2//!
3//! IFC2X3 declares `IfcStructuralLinearActionVarying` and
4//! `IfcStructuralPlanarActionVarying`: the inherited `AppliedLoad` is the
5//! first load, `SubsequentAppliedLoads` the rest, located along the
6//! activity by `VaryingAppliedLoadLocation`. IFC4 and IFC4X3 declare
7//! neither entity, so every accessor here returns `None` for them.
8
9use ifc_model::{EntityId, Value};
10
11use crate::action::StructuralAction;
12use crate::error::{StructuralError, StructuralResult};
13use crate::load::StaticLoad;
14use crate::view::Record;
15
16const LINEAR_VARYING: &str = "IfcStructuralLinearActionVarying";
17const PLANAR_VARYING: &str = "IfcStructuralPlanarActionVarying";
18
19impl<'m, 's> StructuralAction<'m, 's> {
20    /// Whether the entity is an IFC2X3 `IfcStructuralLinearActionVarying`
21    /// or `IfcStructuralPlanarActionVarying`.
22    ///
23    /// Always `false` under IFC4 and IFC4X3, which declare neither entity.
24    #[must_use]
25    pub fn is_varying(&self) -> bool {
26        self.subsequent_minimum().is_some()
27    }
28
29    /// `VaryingAppliedLoadLocation`, the `IfcShapeAspect` locating the
30    /// varying loads along the activity.
31    ///
32    /// `None` for a non-varying action and under IFC4/IFC4X3. Fails with
33    /// [`StructuralError::InvalidValue`] when the mandatory reference is
34    /// unset, and with the reference errors when it dangles or names
35    /// another type.
36    pub fn varying_applied_load_location(&self) -> StructuralResult<Option<EntityId>> {
37        if !self.is_varying() {
38            return Ok(None);
39        }
40        self.validate_semantics()?;
41        self.record
42            .required_ref("VaryingAppliedLoadLocation", "IfcShapeAspect")
43            .map(Some)
44    }
45
46    /// `SubsequentAppliedLoads`, the loads after the inherited `AppliedLoad`,
47    /// in list order and projected as [`StaticLoad`].
48    ///
49    /// `None` for a non-varying action and under IFC4/IFC4X3. The schema
50    /// minimum is enforced: `LIST [1:?]` on the linear form, `LIST [2:?]`
51    /// on the planar form, otherwise [`StructuralError::InvalidCardinality`].
52    /// A list member that dangles fails with
53    /// [`StructuralError::DanglingReference`]; one that is not an
54    /// `IfcStructuralLoad` fails with [`StructuralError::WrongReferenceType`].
55    /// The derived `VaryingAppliedLoads` is `AppliedLoad` followed by this
56    /// list.
57    pub fn subsequent_applied_loads(&self) -> StructuralResult<Option<Vec<StaticLoad<'m, 's>>>> {
58        let Some(minimum) = self.subsequent_minimum() else {
59            return Ok(None);
60        };
61        self.validate_semantics()?;
62        const ATTRIBUTE: &str = "SubsequentAppliedLoads";
63        const EXPECTED: &str = "LIST of IfcStructuralLoad references";
64        let values = match self.record.value(ATTRIBUTE)?.unwrap_typed() {
65            Value::List(values) => values,
66            _ => {
67                return Err(StructuralError::InvalidValue {
68                    entity: self.record.id,
69                    attribute: ATTRIBUTE,
70                    expected: EXPECTED,
71                })
72            }
73        };
74        if values.len() < minimum {
75            return Err(StructuralError::InvalidCardinality {
76                entity: self.record.id,
77                attribute: ATTRIBUTE,
78                minimum,
79                maximum: None,
80                actual: values.len(),
81            });
82        }
83        values
84            .iter()
85            .map(|value| {
86                let Value::Ref(target) = value.unwrap_typed() else {
87                    return Err(StructuralError::InvalidValue {
88                        entity: self.record.id,
89                        attribute: ATTRIBUTE,
90                        expected: EXPECTED,
91                    });
92                };
93                self.subsequent_load(ATTRIBUTE, *target)
94            })
95            .collect::<StructuralResult<Vec<_>>>()
96            .map(Some)
97    }
98
99    fn subsequent_load(
100        &self,
101        attribute: &'static str,
102        target: EntityId,
103    ) -> StructuralResult<StaticLoad<'m, 's>> {
104        let record = Record::new(
105            self.record.model,
106            self.record.schema,
107            target,
108            "IfcStructuralLoad",
109        )
110        .map_err(|error| match error {
111            StructuralError::EntityNotFound { .. } => StructuralError::DanglingReference {
112                entity: self.record.id,
113                attribute,
114                target,
115            },
116            StructuralError::WrongType { actual, .. } => StructuralError::WrongReferenceType {
117                entity: self.record.id,
118                attribute,
119                target,
120                expected: "IfcStructuralLoad",
121                actual,
122            },
123            other => other,
124        })?;
125        StaticLoad::from_record(record)
126    }
127
128    /// The `SubsequentAppliedLoads` list minimum, or `None` when this is not
129    /// a varying action in the view's schema.
130    fn subsequent_minimum(&self) -> Option<usize> {
131        let schema = self.record.schema;
132        let type_name = &self.record.entity.type_name;
133        if schema.entity(LINEAR_VARYING).is_some() && schema.is_a(type_name, LINEAR_VARYING) {
134            Some(1)
135        } else if schema.entity(PLANAR_VARYING).is_some() && schema.is_a(type_name, PLANAR_VARYING)
136        {
137            Some(2)
138        } else {
139            None
140        }
141    }
142}