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)]
46pub struct ReactionDraft<'a> {
47 pub root: StructuralRootDraft,
49 pub applied_load: EntityId,
51 pub coordinate_system: CoordinateSystem,
53 pub kind: ReactionDraftKind<'a>,
55}
56
57pub fn stage_reaction(
66 tx: &mut Transaction,
67 model: &Model,
68 schema: &Schema,
69 draft: ReactionDraft<'_>,
70) -> StructuralResult<EntityId> {
71 validate_root(tx, model, schema, &draft.root)?;
72 let (entity_type, predefined_type) = match draft.kind {
73 ReactionDraftKind::Point => ("IfcStructuralPointReaction", None),
74 ReactionDraftKind::Curve { predefined_type } => {
75 ("IfcStructuralCurveReaction", Some(predefined_type))
76 }
77 ReactionDraftKind::Surface { predefined_type } => {
78 ("IfcStructuralSurfaceReaction", Some(predefined_type))
79 }
80 };
81 validate_ref_select(
82 tx,
83 model,
84 schema,
85 draft.applied_load,
86 "structural load",
87 &["IfcStructuralLoad"],
88 )?;
89
90 let mut fields = root_fields(draft.root);
91 fields.push(("AppliedLoad", Value::Ref(draft.applied_load)));
92 fields.push((
93 "GlobalOrLocal",
94 Value::Enum(match draft.coordinate_system {
95 CoordinateSystem::Global => "GLOBAL_COORDS".into(),
96 CoordinateSystem::Local => "LOCAL_COORDS".into(),
97 }),
98 ));
99 if let Some(token) = predefined_type {
100 validate_activity_token(schema, entity_type, token)?;
104 fields.push(("PredefinedType", Value::Enum(token.into())));
105 }
106 Ok(tx.create(build_named(schema, entity_type, fields)?))
107}
108
109#[derive(Debug, Clone)]
116pub struct ResultGroupDraft {
117 pub global_id: String,
119 pub owner_history: Option<EntityId>,
121 pub name: Option<String>,
123 pub description: Option<String>,
125 pub object_type: Option<String>,
127 pub theory_type: String,
129 pub result_for_load_group: Option<EntityId>,
131 pub is_linear: bool,
133}
134
135pub fn stage_result_group(
143 tx: &mut Transaction,
144 model: &Model,
145 schema: &Schema,
146 draft: ResultGroupDraft,
147) -> StructuralResult<EntityId> {
148 const ENTITY: &str = "IfcStructuralResultGroup";
149 if Guid::parse(&draft.global_id).is_none() {
150 return Err(StructuralError::InvalidDraftValue {
151 entity_type: ENTITY,
152 attribute: "GlobalId",
153 expected: "a 22-character IFC GUID",
154 });
155 }
156 validate_optional_ref(tx, model, schema, draft.owner_history, "IfcOwnerHistory")?;
157 validate_optional_ref(
158 tx,
159 model,
160 schema,
161 draft.result_for_load_group,
162 "IfcStructuralLoadGroup",
163 )?;
164 validate_enum_token(schema, ENTITY, "TheoryType", &draft.theory_type)?;
165 if draft.theory_type.eq_ignore_ascii_case("USERDEFINED")
168 && draft
169 .object_type
170 .as_deref()
171 .is_none_or(|text| text.trim().is_empty())
172 {
173 return Err(StructuralError::SemanticViolation {
174 entity: None,
175 rule: "HasObjectType",
176 });
177 }
178 let fields = vec![
179 ("GlobalId", Value::Text(draft.global_id.into())),
180 ("OwnerHistory", optional_ref(draft.owner_history)),
181 ("Name", optional_text(draft.name)),
182 ("Description", optional_text(draft.description)),
183 ("ObjectType", optional_text(draft.object_type)),
184 ("TheoryType", Value::Enum(draft.theory_type.into())),
185 (
186 "ResultForLoadGroup",
187 optional_ref(draft.result_for_load_group),
188 ),
189 ("IsLinear", Value::Bool(draft.is_linear)),
190 ];
191 Ok(tx.create(build_named(schema, ENTITY, fields)?))
192}