ifc_structural/authoring/
reaction.rs1use ifc_model::guid::Guid;
14use ifc_model::{EntityId, Model, Transaction, Value};
15use ifc_schema::Schema;
16
17use super::action::validate_activity_token;
18use super::item::{root_fields, validate_root, StructuralRootDraft};
19use super::load_group::validate_enum_token;
20use super::{build_named, optional_ref, optional_text, validate_optional_ref, validate_ref_select};
21use crate::action::CoordinateSystem;
22use crate::error::StructuralError;
23use crate::error::StructuralResult;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ReactionDraftKind<'a> {
28 Point,
30 Curve {
33 predefined_type: &'a str,
35 },
36 Surface {
39 predefined_type: &'a str,
41 },
42}
43
44#[derive(Debug, Clone)]
46#[non_exhaustive]
47pub struct ReactionDraft<'a> {
48 pub root: StructuralRootDraft,
50 pub applied_load: EntityId,
52 pub coordinate_system: CoordinateSystem,
54 pub kind: ReactionDraftKind<'a>,
56}
57
58impl<'a> ReactionDraft<'a> {
59 #[must_use]
61 pub fn new(
62 root: StructuralRootDraft,
63 applied_load: EntityId,
64 coordinate_system: CoordinateSystem,
65 kind: ReactionDraftKind<'a>,
66 ) -> Self {
67 Self {
68 root,
69 applied_load,
70 coordinate_system,
71 kind,
72 }
73 }
74}
75
76pub fn stage_reaction(
85 tx: &mut Transaction,
86 model: &Model,
87 schema: &Schema,
88 draft: ReactionDraft<'_>,
89) -> StructuralResult<EntityId> {
90 validate_root(tx, model, schema, &draft.root)?;
91 let (entity_type, predefined_type) = match draft.kind {
92 ReactionDraftKind::Point => ("IfcStructuralPointReaction", None),
93 ReactionDraftKind::Curve { predefined_type } => {
94 ("IfcStructuralCurveReaction", Some(predefined_type))
95 }
96 ReactionDraftKind::Surface { predefined_type } => {
97 ("IfcStructuralSurfaceReaction", Some(predefined_type))
98 }
99 };
100 validate_ref_select(
101 tx,
102 model,
103 schema,
104 draft.applied_load,
105 "structural load",
106 &["IfcStructuralLoad"],
107 )?;
108
109 let mut fields = root_fields(draft.root);
110 fields.push(("AppliedLoad", Value::Ref(draft.applied_load)));
111 fields.push((
112 "GlobalOrLocal",
113 Value::Enum(match draft.coordinate_system {
114 CoordinateSystem::Global => "GLOBAL_COORDS".into(),
115 CoordinateSystem::Local => "LOCAL_COORDS".into(),
116 }),
117 ));
118 if let Some(token) = predefined_type {
119 validate_activity_token(schema, entity_type, token)?;
123 fields.push(("PredefinedType", Value::Enum(token.into())));
124 }
125 Ok(tx.create(build_named(schema, entity_type, fields)?))
126}
127
128#[derive(Debug, Clone)]
135#[non_exhaustive]
136pub struct ResultGroupDraft {
137 pub global_id: String,
139 pub owner_history: Option<EntityId>,
141 pub name: Option<String>,
143 pub description: Option<String>,
145 pub object_type: Option<String>,
147 pub theory_type: String,
149 pub result_for_load_group: Option<EntityId>,
151 pub is_linear: bool,
153}
154
155impl ResultGroupDraft {
156 #[must_use]
158 pub fn new(
159 global_id: impl Into<String>,
160 theory_type: impl Into<String>,
161 is_linear: bool,
162 ) -> Self {
163 Self {
164 global_id: global_id.into(),
165 owner_history: None,
166 name: None,
167 description: None,
168 object_type: None,
169 theory_type: theory_type.into(),
170 result_for_load_group: None,
171 is_linear,
172 }
173 }
174
175 #[must_use]
177 pub fn owner_history(mut self, value: EntityId) -> Self {
178 self.owner_history = Some(value);
179 self
180 }
181
182 #[must_use]
184 pub fn name(mut self, value: impl Into<String>) -> Self {
185 self.name = Some(value.into());
186 self
187 }
188
189 #[must_use]
191 pub fn description(mut self, value: impl Into<String>) -> Self {
192 self.description = Some(value.into());
193 self
194 }
195
196 #[must_use]
199 pub fn object_type(mut self, value: impl Into<String>) -> Self {
200 self.object_type = Some(value.into());
201 self
202 }
203
204 #[must_use]
207 pub fn result_for_load_group(mut self, value: EntityId) -> Self {
208 self.result_for_load_group = Some(value);
209 self
210 }
211}
212
213pub fn stage_result_group(
221 tx: &mut Transaction,
222 model: &Model,
223 schema: &Schema,
224 draft: ResultGroupDraft,
225) -> StructuralResult<EntityId> {
226 const ENTITY: &str = "IfcStructuralResultGroup";
227 if Guid::parse(&draft.global_id).is_none() {
228 return Err(StructuralError::InvalidDraftValue {
229 entity_type: ENTITY,
230 attribute: "GlobalId",
231 expected: "a 22-character IFC GUID",
232 });
233 }
234 validate_optional_ref(tx, model, schema, draft.owner_history, "IfcOwnerHistory")?;
235 validate_optional_ref(
236 tx,
237 model,
238 schema,
239 draft.result_for_load_group,
240 "IfcStructuralLoadGroup",
241 )?;
242 validate_enum_token(schema, ENTITY, "TheoryType", &draft.theory_type)?;
243 if draft.theory_type.eq_ignore_ascii_case("USERDEFINED")
246 && draft
247 .object_type
248 .as_deref()
249 .is_none_or(|text| text.trim().is_empty())
250 {
251 return Err(StructuralError::SemanticViolation {
252 entity: None,
253 rule: "HasObjectType",
254 });
255 }
256 let fields = vec![
257 ("GlobalId", Value::Text(draft.global_id.into())),
258 ("OwnerHistory", optional_ref(draft.owner_history)),
259 ("Name", optional_text(draft.name)),
260 ("Description", optional_text(draft.description)),
261 ("ObjectType", optional_text(draft.object_type)),
262 ("TheoryType", Value::Enum(draft.theory_type.into())),
263 (
264 "ResultForLoadGroup",
265 optional_ref(draft.result_for_load_group),
266 ),
267 ("IsLinear", Value::Bool(draft.is_linear)),
268 ];
269 Ok(tx.create(build_named(schema, ENTITY, fields)?))
270}