Skip to main content

ifc_structural/model/
analysis.rs

1//! `IfcStructuralAnalysisModel` projection.
2
3use ifc_model::EntityId;
4
5use crate::error::{StructuralError, StructuralResult};
6use crate::view::Record;
7
8/// Value of `IfcAnalysisModelTypeEnum` naming an analysis model's dimensionality and load plane.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum AnalysisModelType {
12    /// `IN_PLANE_LOADING_2D`: planar model loaded within its own plane.
13    InPlaneLoading2d,
14    /// `OUT_PLANE_LOADING_2D`: planar model loaded out of its own plane.
15    OutPlaneLoading2d,
16    /// `LOADING_3D`: full three-dimensional model.
17    Loading3d,
18    /// `USERDEFINED`: a custom type named by `ObjectType`.
19    UserDefined,
20    /// `NOTDEFINED`: no dimensionality/load-plane classification given.
21    NotDefined,
22}
23
24impl AnalysisModelType {
25    pub(crate) fn parse(value: &str) -> Option<Self> {
26        match value.to_ascii_uppercase().as_str() {
27            "IN_PLANE_LOADING_2D" => Some(Self::InPlaneLoading2d),
28            "OUT_PLANE_LOADING_2D" => Some(Self::OutPlaneLoading2d),
29            "LOADING_3D" => Some(Self::Loading3d),
30            "USERDEFINED" => Some(Self::UserDefined),
31            "NOTDEFINED" => Some(Self::NotDefined),
32            _ => None,
33        }
34    }
35
36    pub(crate) fn token(self) -> &'static str {
37        match self {
38            Self::InPlaneLoading2d => "IN_PLANE_LOADING_2D",
39            Self::OutPlaneLoading2d => "OUT_PLANE_LOADING_2D",
40            Self::Loading3d => "LOADING_3D",
41            Self::UserDefined => "USERDEFINED",
42            Self::NotDefined => "NOTDEFINED",
43        }
44    }
45}
46
47/// Borrowed projection of an `IfcStructuralAnalysisModel`.
48#[derive(Debug, Clone, Copy)]
49pub struct AnalysisModel<'m, 's> {
50    record: Record<'m, 's>,
51}
52
53impl<'m, 's> AnalysisModel<'m, 's> {
54    pub(crate) fn from_record(record: Record<'m, 's>) -> Self {
55        Self { record }
56    }
57
58    #[must_use]
59    /// The `IfcStructuralAnalysisModel` entity id.
60    pub fn id(&self) -> EntityId {
61        self.record.id
62    }
63
64    /// `Name`, inherited from `IfcRoot`. Legally absent.
65    pub fn name(&self) -> StructuralResult<Option<&'m str>> {
66        self.record.optional_text("Name")
67    }
68
69    /// `ObjectType`. Mandatory when `PredefinedType` is `USERDEFINED`, otherwise legally absent.
70    pub fn object_type(&self) -> StructuralResult<Option<&'m str>> {
71        self.record.optional_text("ObjectType")
72    }
73
74    /// `PredefinedType`, always mandatory.
75    ///
76    /// Fails with [`StructuralError::InvalidValue`] if the token is not a
77    /// member of `IfcAnalysisModelTypeEnum`, and with
78    /// [`StructuralError::SemanticViolation`] if the value is `USERDEFINED`
79    /// but `ObjectType` is unset or blank.
80    pub fn predefined_type(&self) -> StructuralResult<AnalysisModelType> {
81        let value = self.record.required_enum("PredefinedType")?;
82        let parsed = AnalysisModelType::parse(value).ok_or(StructuralError::InvalidValue {
83            entity: self.record.id,
84            attribute: "PredefinedType",
85            expected: "IfcAnalysisModelTypeEnum",
86        })?;
87        if parsed == AnalysisModelType::UserDefined
88            && self
89                .object_type()?
90                .is_none_or(|value| value.trim().is_empty())
91        {
92            return Err(StructuralError::SemanticViolation {
93                entity: Some(self.record.id),
94                rule: "USERDEFINED analysis model requires ObjectType",
95            });
96        }
97        Ok(parsed)
98    }
99
100    /// `OrientationOf2DPlane`, the local axis placement for a 2D model. Legally absent.
101    pub fn orientation_of_2d_plane(&self) -> StructuralResult<Option<EntityId>> {
102        self.record
103            .optional_ref("OrientationOf2DPlane", "IfcAxis2Placement3D")
104    }
105
106    /// `LoadedBy`, the `IfcStructuralLoadGroup`s applying loads to this model. Legally empty.
107    pub fn loaded_by(&self) -> StructuralResult<Vec<EntityId>> {
108        self.record
109            .optional_set_refs("LoadedBy", "IfcStructuralLoadGroup", 1)
110    }
111
112    /// `HasResults`, the `IfcStructuralResultGroup`s holding this model's analysis results. Legally empty.
113    pub fn result_groups(&self) -> StructuralResult<Vec<EntityId>> {
114        self.record
115            .optional_set_refs("HasResults", "IfcStructuralResultGroup", 1)
116    }
117
118    /// `SharedPlacement`, a placement shared by items in this model. `None` when the
119    /// attribute does not exist in the selected schema, or when it exists but is unset.
120    pub fn shared_placement(&self) -> StructuralResult<Option<EntityId>> {
121        if !self.record.has_attribute("SharedPlacement") {
122            return Ok(None);
123        }
124        self.record
125            .optional_ref("SharedPlacement", "IfcObjectPlacement")
126    }
127}