use ifc_model::guid::Guid;
use ifc_model::{EntityId, Model, Transaction, Value};
use ifc_schema::Schema;
use super::action::validate_activity_token;
use super::item::{root_fields, validate_root, StructuralRootDraft};
use super::load_group::validate_enum_token;
use super::{build_named, optional_ref, optional_text, validate_optional_ref, validate_ref_select};
use crate::action::CoordinateSystem;
use crate::error::StructuralError;
use crate::error::StructuralResult;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReactionDraftKind<'a> {
Point,
Curve {
predefined_type: &'a str,
},
Surface {
predefined_type: &'a str,
},
}
#[derive(Debug, Clone)]
pub struct ReactionDraft<'a> {
pub root: StructuralRootDraft,
pub applied_load: EntityId,
pub coordinate_system: CoordinateSystem,
pub kind: ReactionDraftKind<'a>,
}
pub fn stage_reaction(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: ReactionDraft<'_>,
) -> StructuralResult<EntityId> {
validate_root(tx, model, schema, &draft.root)?;
let (entity_type, predefined_type) = match draft.kind {
ReactionDraftKind::Point => ("IfcStructuralPointReaction", None),
ReactionDraftKind::Curve { predefined_type } => {
("IfcStructuralCurveReaction", Some(predefined_type))
}
ReactionDraftKind::Surface { predefined_type } => {
("IfcStructuralSurfaceReaction", Some(predefined_type))
}
};
validate_ref_select(
tx,
model,
schema,
draft.applied_load,
"structural load",
&["IfcStructuralLoad"],
)?;
let mut fields = root_fields(draft.root);
fields.push(("AppliedLoad", Value::Ref(draft.applied_load)));
fields.push((
"GlobalOrLocal",
Value::Enum(match draft.coordinate_system {
CoordinateSystem::Global => "GLOBAL_COORDS".into(),
CoordinateSystem::Local => "LOCAL_COORDS".into(),
}),
));
if let Some(token) = predefined_type {
validate_activity_token(schema, entity_type, token)?;
fields.push(("PredefinedType", Value::Enum(token.into())));
}
Ok(tx.create(build_named(schema, entity_type, fields)?))
}
#[derive(Debug, Clone)]
pub struct ResultGroupDraft {
pub global_id: String,
pub owner_history: Option<EntityId>,
pub name: Option<String>,
pub description: Option<String>,
pub object_type: Option<String>,
pub theory_type: String,
pub result_for_load_group: Option<EntityId>,
pub is_linear: bool,
}
pub fn stage_result_group(
tx: &mut Transaction,
model: &Model,
schema: &Schema,
draft: ResultGroupDraft,
) -> StructuralResult<EntityId> {
const ENTITY: &str = "IfcStructuralResultGroup";
if Guid::parse(&draft.global_id).is_none() {
return Err(StructuralError::InvalidDraftValue {
entity_type: ENTITY,
attribute: "GlobalId",
expected: "a 22-character IFC GUID",
});
}
validate_optional_ref(tx, model, schema, draft.owner_history, "IfcOwnerHistory")?;
validate_optional_ref(
tx,
model,
schema,
draft.result_for_load_group,
"IfcStructuralLoadGroup",
)?;
validate_enum_token(schema, ENTITY, "TheoryType", &draft.theory_type)?;
if draft.theory_type.eq_ignore_ascii_case("USERDEFINED")
&& draft
.object_type
.as_deref()
.is_none_or(|text| text.trim().is_empty())
{
return Err(StructuralError::SemanticViolation {
entity: None,
rule: "HasObjectType",
});
}
let fields = vec![
("GlobalId", Value::Text(draft.global_id.into())),
("OwnerHistory", optional_ref(draft.owner_history)),
("Name", optional_text(draft.name)),
("Description", optional_text(draft.description)),
("ObjectType", optional_text(draft.object_type)),
("TheoryType", Value::Enum(draft.theory_type.into())),
(
"ResultForLoadGroup",
optional_ref(draft.result_for_load_group),
),
("IsLinear", Value::Bool(draft.is_linear)),
];
Ok(tx.create(build_named(schema, ENTITY, fields)?))
}