Skip to main content

ifc_structural/
load.rs

1//! Core static structural load values.
2
3use ifc_model::EntityId;
4
5use crate::error::{StructuralError, StructuralResult};
6use crate::view::Record;
7
8mod dynamic;
9mod r#static;
10
11pub use dynamic::LoadConfiguration;
12
13/// Which `IfcStructuralLoadStatic` subtype a static load value carries.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum LoadKind {
16    /// `IfcStructuralLoadSingleForce`: force/moment at a point.
17    SingleForce,
18    /// `IfcStructuralLoadLinearForce`: force/moment per unit length along a curve.
19    LinearForce,
20    /// `IfcStructuralLoadPlanarForce`: force per unit area over a surface.
21    PlanarForce,
22    /// `IfcStructuralLoadTemperature`: constant plus through-thickness temperature deltas.
23    Temperature,
24}
25
26/// Borrowed projection of a core `IfcStructuralLoadStatic` subtype (force, planar force, or temperature).
27#[derive(Debug, Clone, Copy)]
28pub struct StaticLoad<'m, 's> {
29    record: Record<'m, 's>,
30    kind: LoadKind,
31}
32
33impl<'m, 's> StaticLoad<'m, 's> {
34    /// Classify `record`'s [`LoadKind`] from its declared IFC type.
35    ///
36    /// Fails with [`StructuralError::WrongType`] if the type is none of
37    /// `IfcStructuralLoadSingleForce`, `IfcStructuralLoadLinearForce`,
38    /// `IfcStructuralLoadPlanarForce`, or `IfcStructuralLoadTemperature`.
39    pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
40        let kind = if record.entity.is_type("IfcStructuralLoadSingleForce") {
41            LoadKind::SingleForce
42        } else if record.entity.is_type("IfcStructuralLoadLinearForce") {
43            LoadKind::LinearForce
44        } else if record.entity.is_type("IfcStructuralLoadPlanarForce") {
45            LoadKind::PlanarForce
46        } else if record.entity.is_type("IfcStructuralLoadTemperature") {
47            LoadKind::Temperature
48        } else {
49            return Err(StructuralError::WrongType {
50                id: record.id,
51                expected: "core static structural load",
52                actual: record.entity.type_name.to_string(),
53            });
54        };
55        Ok(Self { record, kind })
56    }
57
58    #[must_use]
59    /// The `IfcStructuralLoadStatic` entity id.
60    pub fn id(&self) -> EntityId {
61        self.record.id
62    }
63
64    #[must_use]
65    /// Which core static load subtype this value carries.
66    pub fn kind(&self) -> LoadKind {
67        self.kind
68    }
69
70    /// `Name`, inherited from `IfcStructuralLoad`. Legally absent.
71    pub fn name(&self) -> StructuralResult<Option<&'m str>> {
72        self.record.optional_text("Name")
73    }
74
75    /// The load's numeric components in schema-declared order for its [`LoadKind`]:
76    /// six force/moment axes for a single force, six per-length axes for a linear
77    /// force, three per-area axes for a planar force, or three temperature deltas
78    /// (constant, through-Y, through-Z). Each component is legally absent
79    /// individually; `IfcStructuralLoadTemperature` uses the underscored
80    /// `DeltaT_Constant`/`DeltaT_Y`/`DeltaT_Z` names in schemas where the
81    /// unscored `DeltaTConstant` attribute does not exist.
82    pub fn components(&self) -> StructuralResult<Vec<Option<f64>>> {
83        let names: &[&'static str] = match self.kind {
84            LoadKind::SingleForce => &[
85                "ForceX", "ForceY", "ForceZ", "MomentX", "MomentY", "MomentZ",
86            ],
87            LoadKind::LinearForce => &[
88                "LinearForceX",
89                "LinearForceY",
90                "LinearForceZ",
91                "LinearMomentX",
92                "LinearMomentY",
93                "LinearMomentZ",
94            ],
95            LoadKind::PlanarForce => &["PlanarForceX", "PlanarForceY", "PlanarForceZ"],
96            LoadKind::Temperature => {
97                if self.record.has_attribute("DeltaTConstant") {
98                    &["DeltaTConstant", "DeltaTY", "DeltaTZ"]
99                } else {
100                    &["DeltaT_Constant", "DeltaT_Y", "DeltaT_Z"]
101                }
102            }
103        };
104        names
105            .iter()
106            .map(|name| self.record.optional_number(name))
107            .collect()
108    }
109}