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}