ifc_structural/result/
reaction.rs1use ifc_model::EntityId;
4
5use crate::action::CoordinateSystem;
6use crate::error::{StructuralError, StructuralResult};
7use crate::view::Record;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ReactionKind {
12 Point,
14 Curve,
16 Surface,
18}
19
20#[derive(Debug, Clone, Copy)]
25pub struct Reaction<'m, 's> {
26 record: Record<'m, 's>,
27 kind: ReactionKind,
28}
29
30impl<'m, 's> Reaction<'m, 's> {
31 pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
32 let kind = if record
33 .schema
34 .is_a(&record.entity.type_name, "IfcStructuralPointReaction")
35 {
36 ReactionKind::Point
37 } else if record
38 .schema
39 .is_a(&record.entity.type_name, "IfcStructuralCurveReaction")
40 {
41 ReactionKind::Curve
42 } else if record
43 .schema
44 .is_a(&record.entity.type_name, "IfcStructuralSurfaceReaction")
45 {
46 ReactionKind::Surface
47 } else {
48 return Err(StructuralError::WrongType {
49 id: record.id,
50 expected: "concrete IfcStructuralReaction",
51 actual: record.entity.type_name.to_string(),
52 });
53 };
54 Ok(Self { record, kind })
55 }
56
57 #[must_use]
59 pub fn id(&self) -> EntityId {
60 self.record.id
61 }
62
63 #[must_use]
65 pub fn kind(&self) -> ReactionKind {
66 self.kind
67 }
68
69 pub fn name(&self) -> StructuralResult<Option<&'m str>> {
71 self.validate_semantics()?;
72 self.record.optional_text("Name")
73 }
74
75 pub fn applied_load(&self) -> StructuralResult<EntityId> {
77 self.validate_semantics()
78 }
79
80 pub fn coordinate_system(&self) -> StructuralResult<CoordinateSystem> {
82 self.validate_semantics()?;
83 match self
84 .record
85 .required_enum("GlobalOrLocal")?
86 .to_ascii_uppercase()
87 .as_str()
88 {
89 "GLOBAL_COORDS" => Ok(CoordinateSystem::Global),
90 "LOCAL_COORDS" => Ok(CoordinateSystem::Local),
91 _ => Err(StructuralError::InvalidValue {
92 entity: self.record.id,
93 attribute: "GlobalOrLocal",
94 expected: "IfcGlobalOrLocalEnum",
95 }),
96 }
97 }
98
99 pub fn predefined_type(&self) -> StructuralResult<Option<&'m str>> {
101 self.validate_semantics()?;
102 if !self.record.has_attribute("PredefinedType") {
103 return Ok(None);
104 }
105 self.record.required_enum("PredefinedType").map(Some)
106 }
107
108 fn validate_semantics(&self) -> StructuralResult<EntityId> {
109 let applied_load = if self.kind == ReactionKind::Point {
110 self.record.required_ref_select(
111 "AppliedLoad",
112 "point-reaction structural load",
113 &[
114 "IfcStructuralLoadSingleForce",
115 "IfcStructuralLoadSingleDisplacement",
116 ],
117 )?
118 } else {
119 self.record
120 .required_ref("AppliedLoad", "IfcStructuralLoad")?
121 };
122 if self.record.has_attribute("PredefinedType") {
123 let predefined_type = self.record.required_enum("PredefinedType")?;
124 self.record.require_object_type_if(
125 predefined_type.eq_ignore_ascii_case("USERDEFINED"),
126 "USERDEFINED structural reaction requires ObjectType",
127 )?;
128 if self.kind == ReactionKind::Curve
129 && (predefined_type.eq_ignore_ascii_case("SINUS")
130 || predefined_type.eq_ignore_ascii_case("PARABOLA"))
131 {
132 return Err(StructuralError::SemanticViolation {
133 entity: Some(self.record.id),
134 rule: "IfcStructuralCurveReaction.SuitablePredefinedType",
135 });
136 }
137 }
138 Ok(applied_load)
139 }
140}