Skip to main content

ifc_structural/load/
dynamic.rs

1//! `IfcStructuralLoadConfiguration` projections.
2
3use ifc_model::{EntityId, Value};
4
5use crate::error::{StructuralError, StructuralResult};
6use crate::view::Record;
7
8/// Ordered load/result values with optional one- or two-coordinate locations.
9///
10/// This preserves authored configuration data. It does not interpolate,
11/// combine, envelope, or otherwise evaluate the values.
12#[derive(Debug, Clone, Copy)]
13pub struct LoadConfiguration<'m, 's> {
14    record: Record<'m, 's>,
15}
16
17impl<'m, 's> LoadConfiguration<'m, 's> {
18    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
19        Self { record }
20    }
21
22    /// Entity identifier in the shared model graph.
23    #[must_use]
24    pub fn id(&self) -> EntityId {
25        self.record.id
26    }
27
28    /// Optional authored name.
29    pub fn name(&self) -> StructuralResult<Option<&'m str>> {
30        self.record.optional_text("Name")
31    }
32
33    /// Ordered references to `IfcStructuralLoadOrResult` values.
34    pub fn values(&self) -> StructuralResult<Vec<EntityId>> {
35        let values = match self.record.value("Values")?.unwrap_typed() {
36            Value::List(values) if !values.is_empty() => values,
37            Value::List(values) => {
38                return Err(StructuralError::InvalidCardinality {
39                    entity: self.record.id,
40                    attribute: "Values",
41                    minimum: 1,
42                    maximum: None,
43                    actual: values.len(),
44                })
45            }
46            _ => {
47                return Err(StructuralError::InvalidValue {
48                    entity: self.record.id,
49                    attribute: "Values",
50                    expected: "LIST [1:?] of IfcStructuralLoadOrResult references",
51                })
52            }
53        };
54        values
55            .iter()
56            .map(|value| self.load_or_result_ref(value))
57            .collect()
58    }
59
60    /// Optional authored locations, preserving outer and inner list order.
61    ///
62    /// Each location contains one or two finite length coordinates. The outer
63    /// list is unique and, when present, has the same length as `values`.
64    pub fn locations(&self) -> StructuralResult<Option<Vec<Vec<f64>>>> {
65        let raw = match self.record.value("Locations")?.unwrap_typed() {
66            Value::Null | Value::Derived => return Ok(None),
67            Value::List(values) if !values.is_empty() => values,
68            Value::List(values) => {
69                return Err(StructuralError::InvalidCardinality {
70                    entity: self.record.id,
71                    attribute: "Locations",
72                    minimum: 1,
73                    maximum: None,
74                    actual: values.len(),
75                })
76            }
77            _ => {
78                return Err(StructuralError::InvalidValue {
79                    entity: self.record.id,
80                    attribute: "Locations",
81                    expected: "LIST [1:?] of unique LIST [1:2] finite lengths",
82                })
83            }
84        };
85        let value_count = self.values()?.len();
86        if raw.len() != value_count {
87            return Err(StructuralError::SemanticViolation {
88                entity: Some(self.record.id),
89                rule: "IfcStructuralLoadConfiguration.ValidListSize",
90            });
91        }
92        let mut locations = Vec::with_capacity(raw.len());
93        for raw_location in raw {
94            let values = match raw_location.unwrap_typed() {
95                Value::List(values) if (1..=2).contains(&values.len()) => values,
96                Value::List(values) => {
97                    return Err(StructuralError::InvalidCardinality {
98                        entity: self.record.id,
99                        attribute: "Locations",
100                        minimum: 1,
101                        maximum: Some(2),
102                        actual: values.len(),
103                    })
104                }
105                _ => {
106                    return Err(StructuralError::InvalidValue {
107                        entity: self.record.id,
108                        attribute: "Locations",
109                        expected: "LIST [1:2] of finite length measures",
110                    })
111                }
112            };
113            let mut location = Vec::with_capacity(values.len());
114            for value in values {
115                let number = match value.unwrap_typed() {
116                    Value::Integer(value) => *value as f64,
117                    Value::Real(value) => *value,
118                    _ => {
119                        return Err(StructuralError::InvalidValue {
120                            entity: self.record.id,
121                            attribute: "Locations",
122                            expected: "finite length measure",
123                        })
124                    }
125                };
126                if !number.is_finite() {
127                    return Err(StructuralError::InvalidValue {
128                        entity: self.record.id,
129                        attribute: "Locations",
130                        expected: "finite length measure",
131                    });
132                }
133                location.push(number);
134            }
135            if locations.contains(&location) {
136                return Err(StructuralError::SemanticViolation {
137                    entity: Some(self.record.id),
138                    rule: "IfcStructuralLoadConfiguration.UniqueLocations",
139                });
140            }
141            locations.push(location);
142        }
143        Ok(Some(locations))
144    }
145
146    fn load_or_result_ref(&self, value: &Value) -> StructuralResult<EntityId> {
147        let Value::Ref(id) = value.unwrap_typed() else {
148            return Err(StructuralError::InvalidValue {
149                entity: self.record.id,
150                attribute: "Values",
151                expected: "IfcStructuralLoadOrResult reference",
152            });
153        };
154        let target = self
155            .record
156            .model
157            .get(*id)
158            .ok_or(StructuralError::DanglingReference {
159                entity: self.record.id,
160                attribute: "Values",
161                target: *id,
162            })?;
163        if !self
164            .record
165            .schema
166            .is_a(&target.type_name, "IfcStructuralLoadOrResult")
167        {
168            return Err(StructuralError::WrongType {
169                id: *id,
170                expected: "IfcStructuralLoadOrResult",
171                actual: target.type_name.to_string(),
172            });
173        }
174        Ok(*id)
175    }
176}