1use std::collections::HashSet;
4
5use ifc_model::{Entity, EntityId, Model, Value};
6use ifc_schema::{ifc2x3, ifc4, ifc4x3, Schema, SchemaVersion, TypeKind};
7
8use crate::error::{StructuralError, StructuralResult};
9use crate::{
10 AnalysisModel, BoundaryCondition, ConnectionCondition, LoadConfiguration, LoadGroup, Member,
11 Reaction, ResultGroup, StaticLoad, StructuralAction, StructuralConnection,
12};
13
14#[derive(Debug, Clone, Copy)]
16pub struct StructuralView<'m, 's> {
17 pub(crate) model: &'m Model,
18 pub(crate) schema: &'s Schema,
19}
20
21impl<'m, 's> StructuralView<'m, 's> {
22 #[must_use]
23 pub fn new(model: &'m Model, schema: &'s Schema) -> Self {
25 Self { model, schema }
26 }
27
28 #[must_use]
29 pub fn schema(&self) -> &'s Schema {
31 self.schema
32 }
33
34 pub fn analysis_model(&self, id: EntityId) -> StructuralResult<AnalysisModel<'m, 's>> {
36 Ok(AnalysisModel::from_record(
37 self.record(id, "IfcStructuralAnalysisModel")?,
38 ))
39 }
40
41 pub fn load_group(&self, id: EntityId) -> StructuralResult<LoadGroup<'m, 's>> {
43 Ok(LoadGroup::from_record(
44 self.record(id, "IfcStructuralLoadGroup")?,
45 ))
46 }
47
48 pub fn result_group(&self, id: EntityId) -> StructuralResult<ResultGroup<'m, 's>> {
50 Ok(ResultGroup::from_record(
51 self.record(id, "IfcStructuralResultGroup")?,
52 ))
53 }
54
55 pub fn member(&self, id: EntityId) -> StructuralResult<Member<'m, 's>> {
57 Member::from_record(self.record(id, "IfcStructuralMember")?)
58 }
59
60 pub fn connection(&self, id: EntityId) -> StructuralResult<StructuralConnection<'m, 's>> {
62 StructuralConnection::from_record(self.record(id, "IfcStructuralConnection")?)
63 }
64
65 pub fn boundary_condition(&self, id: EntityId) -> StructuralResult<BoundaryCondition<'m, 's>> {
67 BoundaryCondition::from_record(self.record(id, "IfcBoundaryCondition")?)
68 }
69
70 pub fn connection_condition(
72 &self,
73 id: EntityId,
74 ) -> StructuralResult<ConnectionCondition<'m, 's>> {
75 ConnectionCondition::from_record(self.record(id, "IfcStructuralConnectionCondition")?)
76 }
77
78 pub fn action(&self, id: EntityId) -> StructuralResult<StructuralAction<'m, 's>> {
80 StructuralAction::from_record(self.record(id, "IfcStructuralAction")?)
81 }
82
83 pub fn load(&self, id: EntityId) -> StructuralResult<StaticLoad<'m, 's>> {
85 StaticLoad::from_record(self.record(id, "IfcStructuralLoad")?)
86 }
87
88 pub fn load_configuration(&self, id: EntityId) -> StructuralResult<LoadConfiguration<'m, 's>> {
90 Ok(LoadConfiguration::from_record(
91 self.record(id, "IfcStructuralLoadConfiguration")?,
92 ))
93 }
94
95 pub fn reaction(&self, id: EntityId) -> StructuralResult<Reaction<'m, 's>> {
97 Reaction::from_record(self.record(id, "IfcStructuralReaction")?)
98 }
99
100 pub fn static_load(&self, id: EntityId) -> StructuralResult<StaticLoad<'m, 's>> {
102 self.load(id)
103 }
104
105 pub(crate) fn record(
107 &self,
108 id: EntityId,
109 expected: &'static str,
110 ) -> StructuralResult<Record<'m, 's>> {
111 Record::new(self.model, self.schema, id, expected)
112 }
113
114 pub(crate) fn ids_of_ancestor(&self, ancestor: &str) -> Vec<EntityId> {
116 let matching_types: HashSet<_> = self
117 .model
118 .type_histogram()
119 .into_iter()
120 .filter_map(|(type_name, _)| self.schema.is_a(type_name, ancestor).then_some(type_name))
121 .collect();
122 self.model
123 .iter()
124 .filter_map(|(id, entity)| matching_types.contains(&*entity.type_name).then_some(id))
125 .collect()
126 }
127}
128
129impl<'m> StructuralView<'m, 'static> {
130 pub fn for_model(model: &'m Model) -> StructuralResult<Self> {
135 let token = match model.header().schema.as_slice() {
136 [] => return Err(StructuralError::MissingSchema),
137 [token] => token,
138 tokens => {
139 return Err(StructuralError::AmbiguousSchema {
140 tokens: tokens.to_vec(),
141 })
142 }
143 };
144 let version = SchemaVersion::from_header_token(token).ok_or_else(|| {
145 StructuralError::UnsupportedSchema {
146 token: token.clone(),
147 }
148 })?;
149 let schema = match version {
150 SchemaVersion::Ifc2x3 => ifc2x3(),
151 SchemaVersion::Ifc4 => ifc4(),
152 SchemaVersion::Ifc4x3 => ifc4x3(),
153 };
154 Ok(Self::new(model, schema))
155 }
156}
157
158#[derive(Debug, Clone, Copy)]
159pub(crate) struct Record<'m, 's> {
160 pub(crate) model: &'m Model,
161 pub(crate) schema: &'s Schema,
162 pub(crate) id: EntityId,
163 pub(crate) entity: &'m Entity,
164}
165
166impl<'m, 's> Record<'m, 's> {
167 pub(crate) fn new(
168 model: &'m Model,
169 schema: &'s Schema,
170 id: EntityId,
171 expected: &'static str,
172 ) -> StructuralResult<Self> {
173 let entity = model
174 .get(id)
175 .ok_or(StructuralError::EntityNotFound { id })?;
176 if !schema.is_a(&entity.type_name, expected) {
177 return Err(StructuralError::WrongType {
178 id,
179 expected,
180 actual: entity.type_name.to_string(),
181 });
182 }
183 Ok(Self {
184 model,
185 schema,
186 id,
187 entity,
188 })
189 }
190
191 pub(crate) fn has_attribute(&self, attribute: &str) -> bool {
192 self.schema
193 .attribute_names(&self.entity.type_name)
194 .iter()
195 .any(|name| name.eq_ignore_ascii_case(attribute))
196 }
197
198 fn slot(&self, attribute: &'static str) -> StructuralResult<usize> {
199 self.schema
200 .attribute_names(&self.entity.type_name)
201 .iter()
202 .position(|name| name.eq_ignore_ascii_case(attribute))
203 .ok_or(StructuralError::MissingAttribute {
204 entity: self.id,
205 attribute,
206 })
207 }
208
209 pub(crate) fn value(&self, attribute: &'static str) -> StructuralResult<&'m Value> {
210 let slot = self.slot(attribute)?;
211 self.entity
212 .attributes
213 .get(slot)
214 .ok_or(StructuralError::MissingAttribute {
215 entity: self.id,
216 attribute,
217 })
218 }
219
220 pub(crate) fn optional_text(
221 &self,
222 attribute: &'static str,
223 ) -> StructuralResult<Option<&'m str>> {
224 match self.value(attribute)?.unwrap_typed() {
225 Value::Null | Value::Derived => Ok(None),
226 Value::Text(value) => Ok(Some(value)),
227 _ => Err(self.invalid(attribute, "text or null")),
228 }
229 }
230
231 pub(crate) fn require_object_type_if(
232 &self,
233 condition: bool,
234 rule: &'static str,
235 ) -> StructuralResult<()> {
236 if condition
237 && self
238 .optional_text("ObjectType")?
239 .is_none_or(|value| value.trim().is_empty())
240 {
241 return Err(StructuralError::SemanticViolation {
242 entity: Some(self.id),
243 rule,
244 });
245 }
246 Ok(())
247 }
248
249 pub(crate) fn required_enum(&self, attribute: &'static str) -> StructuralResult<&'m str> {
250 match self.value(attribute)?.unwrap_typed() {
251 Value::Enum(value) if self.declares_enum_member(attribute, value) => Ok(value),
252 _ => Err(self.invalid(attribute, "enumeration")),
253 }
254 }
255
256 pub(crate) fn optional_enum(
257 &self,
258 attribute: &'static str,
259 ) -> StructuralResult<Option<&'m str>> {
260 match self.value(attribute)?.unwrap_typed() {
261 Value::Null | Value::Derived => Ok(None),
262 Value::Enum(value) if self.declares_enum_member(attribute, value) => Ok(Some(value)),
263 _ => Err(self.invalid(attribute, "enumeration or null")),
264 }
265 }
266
267 fn declares_enum_member(&self, attribute: &str, value: &str) -> bool {
268 let attributes = self.schema.attributes(&self.entity.type_name);
269 let Some(declaration) = attributes
270 .iter()
271 .find(|declaration| declaration.name.eq_ignore_ascii_case(attribute))
272 else {
273 return false;
274 };
275 let mut type_name = declaration.type_name.as_str();
276 for _ in 0..16 {
277 let Some(type_def) = self.schema.type_def(type_name) else {
278 return false;
279 };
280 match &type_def.kind {
281 TypeKind::Enumeration(members) => {
282 return members
283 .iter()
284 .any(|member| member.eq_ignore_ascii_case(value));
285 }
286 TypeKind::Defined(alias) => type_name = alias,
287 TypeKind::Select(_) => return false,
288 }
289 }
290 false
291 }
292
293 pub(crate) fn optional_bool(&self, attribute: &'static str) -> StructuralResult<Option<bool>> {
294 match self.value(attribute)?.unwrap_typed() {
295 Value::Null | Value::Derived => Ok(None),
296 Value::Bool(value) => Ok(Some(*value)),
297 _ => Err(self.invalid(attribute, "boolean or null")),
298 }
299 }
300
301 pub(crate) fn required_bool(&self, attribute: &'static str) -> StructuralResult<bool> {
302 self.optional_bool(attribute)?
303 .ok_or_else(|| self.invalid(attribute, "boolean"))
304 }
305
306 pub(crate) fn optional_number(&self, attribute: &'static str) -> StructuralResult<Option<f64>> {
307 let value = match self.value(attribute)?.unwrap_typed() {
308 Value::Null | Value::Derived => Ok(None),
309 Value::Integer(value) => Ok(Some(*value as f64)),
310 Value::Real(value) => Ok(Some(*value)),
311 _ => Err(self.invalid(attribute, "number or null")),
312 }?;
313 if value.is_some_and(|number| !number.is_finite()) {
314 return Err(self.invalid(attribute, "finite number or null"));
315 }
316 Ok(value)
317 }
318
319 pub(crate) fn optional_ref(
320 &self,
321 attribute: &'static str,
322 expected: &'static str,
323 ) -> StructuralResult<Option<EntityId>> {
324 let target = match self.value(attribute)?.unwrap_typed() {
325 Value::Null | Value::Derived => return Ok(None),
326 Value::Ref(target) => *target,
327 _ => return Err(self.invalid(attribute, "entity reference or null")),
328 };
329 self.check_reference(attribute, target, &[expected], expected)?;
330 Ok(Some(target))
331 }
332
333 pub(crate) fn required_ref(
334 &self,
335 attribute: &'static str,
336 expected: &'static str,
337 ) -> StructuralResult<EntityId> {
338 self.optional_ref(attribute, expected)?
339 .ok_or_else(|| self.invalid(attribute, "entity reference"))
340 }
341
342 pub(crate) fn required_ref_select(
343 &self,
344 attribute: &'static str,
345 expected: &'static str,
346 members: &[&str],
347 ) -> StructuralResult<EntityId> {
348 let target = match self.value(attribute)?.unwrap_typed() {
349 Value::Ref(target) => *target,
350 _ => return Err(self.invalid(attribute, "entity reference")),
351 };
352 self.check_reference(attribute, target, members, expected)?;
353 Ok(target)
354 }
355
356 pub(crate) fn optional_set_refs(
357 &self,
358 attribute: &'static str,
359 expected: &'static str,
360 minimum_when_present: usize,
361 ) -> StructuralResult<Vec<EntityId>> {
362 let values = match self.value(attribute)?.unwrap_typed() {
363 Value::Null | Value::Derived => return Ok(Vec::new()),
364 Value::List(values) => values,
365 _ => return Err(self.invalid(attribute, "aggregate of entity references or null")),
366 };
367 if values.len() < minimum_when_present {
368 return Err(StructuralError::InvalidCardinality {
369 entity: self.id,
370 attribute,
371 minimum: minimum_when_present,
372 maximum: None,
373 actual: values.len(),
374 });
375 }
376 let mut targets = Vec::with_capacity(values.len());
377 let mut unique = HashSet::with_capacity(values.len());
378 for value in values {
379 let Value::Ref(target) = value.unwrap_typed() else {
380 return Err(self.invalid(attribute, "aggregate of entity references"));
381 };
382 if !unique.insert(*target) {
383 return Err(self.invalid(attribute, "SET of unique entity references"));
384 }
385 self.check_reference(attribute, *target, &[expected], expected)?;
386 targets.push(*target);
387 }
388 Ok(targets)
389 }
390
391 pub(crate) fn required_set_refs_select(
392 &self,
393 attribute: &'static str,
394 expected: &'static str,
395 members: &[&str],
396 minimum: usize,
397 ) -> StructuralResult<Vec<EntityId>> {
398 let values = match self.value(attribute)?.unwrap_typed() {
399 Value::List(values) => values,
400 _ => return Err(self.invalid(attribute, "aggregate of entity references")),
401 };
402 if values.len() < minimum {
403 return Err(StructuralError::InvalidCardinality {
404 entity: self.id,
405 attribute,
406 minimum,
407 maximum: None,
408 actual: values.len(),
409 });
410 }
411 let mut targets = Vec::with_capacity(values.len());
412 let mut unique = HashSet::with_capacity(values.len());
413 for value in values {
414 let Value::Ref(target) = value.unwrap_typed() else {
415 return Err(self.invalid(attribute, "aggregate of entity references"));
416 };
417 if !unique.insert(*target) {
418 return Err(self.invalid(attribute, "SET of unique entity references"));
419 }
420 self.check_reference(attribute, *target, members, expected)?;
421 targets.push(*target);
422 }
423 Ok(targets)
424 }
425
426 fn check_reference(
427 &self,
428 attribute: &'static str,
429 target: EntityId,
430 members: &[&str],
431 expected: &'static str,
432 ) -> StructuralResult<()> {
433 let target_entity = self
434 .model
435 .get(target)
436 .ok_or(StructuralError::DanglingReference {
437 entity: self.id,
438 attribute,
439 target,
440 })?;
441 if !members
442 .iter()
443 .any(|member| self.schema.is_a(&target_entity.type_name, member))
444 {
445 return Err(StructuralError::WrongReferenceType {
446 entity: self.id,
447 attribute,
448 target,
449 expected,
450 actual: target_entity.type_name.to_string(),
451 });
452 }
453 Ok(())
454 }
455
456 fn invalid(&self, attribute: &'static str, expected: &'static str) -> StructuralError {
457 StructuralError::InvalidValue {
458 entity: self.id,
459 attribute,
460 expected,
461 }
462 }
463}