use ifc_model::EntityId;
use crate::action::CoordinateSystem;
use crate::error::{StructuralError, StructuralResult};
use crate::view::Record;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ReactionKind {
Point,
Curve,
Surface,
}
#[derive(Debug, Clone, Copy)]
pub struct Reaction<'m, 's> {
record: Record<'m, 's>,
kind: ReactionKind,
}
impl<'m, 's> Reaction<'m, 's> {
pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
let kind = if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralPointReaction")
{
ReactionKind::Point
} else if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralCurveReaction")
{
ReactionKind::Curve
} else if record
.schema
.is_a(&record.entity.type_name, "IfcStructuralSurfaceReaction")
{
ReactionKind::Surface
} else {
return Err(StructuralError::WrongType {
id: record.id,
expected: "concrete IfcStructuralReaction",
actual: record.entity.type_name.to_string(),
});
};
Ok(Self { record, kind })
}
#[must_use]
pub fn id(&self) -> EntityId {
self.record.id
}
#[must_use]
pub fn kind(&self) -> ReactionKind {
self.kind
}
pub fn name(&self) -> StructuralResult<Option<&'m str>> {
self.validate_semantics()?;
self.record.optional_text("Name")
}
pub fn applied_load(&self) -> StructuralResult<EntityId> {
self.validate_semantics()
}
pub fn coordinate_system(&self) -> StructuralResult<CoordinateSystem> {
self.validate_semantics()?;
match self
.record
.required_enum("GlobalOrLocal")?
.to_ascii_uppercase()
.as_str()
{
"GLOBAL_COORDS" => Ok(CoordinateSystem::Global),
"LOCAL_COORDS" => Ok(CoordinateSystem::Local),
_ => Err(StructuralError::InvalidValue {
entity: self.record.id,
attribute: "GlobalOrLocal",
expected: "IfcGlobalOrLocalEnum",
}),
}
}
pub fn predefined_type(&self) -> StructuralResult<Option<&'m str>> {
self.validate_semantics()?;
if !self.record.has_attribute("PredefinedType") {
return Ok(None);
}
self.record.required_enum("PredefinedType").map(Some)
}
fn validate_semantics(&self) -> StructuralResult<EntityId> {
let applied_load = if self.kind == ReactionKind::Point {
self.record.required_ref_select(
"AppliedLoad",
"point-reaction structural load",
&[
"IfcStructuralLoadSingleForce",
"IfcStructuralLoadSingleDisplacement",
],
)?
} else {
self.record
.required_ref("AppliedLoad", "IfcStructuralLoad")?
};
if self.record.has_attribute("PredefinedType") {
let predefined_type = self.record.required_enum("PredefinedType")?;
self.record.require_object_type_if(
predefined_type.eq_ignore_ascii_case("USERDEFINED"),
"USERDEFINED structural reaction requires ObjectType",
)?;
if self.kind == ReactionKind::Curve
&& (predefined_type.eq_ignore_ascii_case("SINUS")
|| predefined_type.eq_ignore_ascii_case("PARABOLA"))
{
return Err(StructuralError::SemanticViolation {
entity: Some(self.record.id),
rule: "IfcStructuralCurveReaction.SuitablePredefinedType",
});
}
}
Ok(applied_load)
}
}