use ifc_model::Value;
use ifc_schema::{Schema, TypeKind};
use crate::condition::{AxisValues, BoundaryConditionKind, StiffnessValue};
use crate::error::{StructuralError, StructuralResult};
pub(super) fn entity_name(kind: BoundaryConditionKind) -> &'static str {
match kind {
BoundaryConditionKind::Edge => "IFCBOUNDARYEDGECONDITION",
BoundaryConditionKind::Face => "IFCBOUNDARYFACECONDITION",
BoundaryConditionKind::Node => "IFCBOUNDARYNODECONDITION",
BoundaryConditionKind::NodeWarping => "IFCBOUNDARYNODECONDITIONWARPING",
}
}
fn measure_name(kind: BoundaryConditionKind, translational: bool) -> &'static str {
match (kind, translational) {
(BoundaryConditionKind::Edge, true) => "IfcModulusOfLinearSubgradeReactionMeasure",
(BoundaryConditionKind::Edge, false) => "IfcModulusOfRotationalSubgradeReactionMeasure",
(BoundaryConditionKind::Face, _) => "IfcModulusOfSubgradeReactionMeasure",
(_, true) => "IfcLinearStiffnessMeasure",
(_, false) => "IfcRotationalStiffnessMeasure",
}
}
fn names(
kind: BoundaryConditionKind,
translational: bool,
) -> ([&'static str; 3], [&'static str; 3]) {
match (kind, translational) {
(BoundaryConditionKind::Edge, true) => (
[
"TranslationalStiffnessByLengthX",
"TranslationalStiffnessByLengthY",
"TranslationalStiffnessByLengthZ",
],
[
"LinearStiffnessByLengthX",
"LinearStiffnessByLengthY",
"LinearStiffnessByLengthZ",
],
),
(BoundaryConditionKind::Edge, false) => {
let names = [
"RotationalStiffnessByLengthX",
"RotationalStiffnessByLengthY",
"RotationalStiffnessByLengthZ",
];
(names, names)
}
(BoundaryConditionKind::Face, _) => (
[
"TranslationalStiffnessByAreaX",
"TranslationalStiffnessByAreaY",
"TranslationalStiffnessByAreaZ",
],
[
"LinearStiffnessByAreaX",
"LinearStiffnessByAreaY",
"LinearStiffnessByAreaZ",
],
),
(_, true) => (
[
"TranslationalStiffnessX",
"TranslationalStiffnessY",
"TranslationalStiffnessZ",
],
["LinearStiffnessX", "LinearStiffnessY", "LinearStiffnessZ"],
),
(_, false) => {
let names = [
"RotationalStiffnessX",
"RotationalStiffnessY",
"RotationalStiffnessZ",
];
(names, names)
}
}
}
pub(super) fn triple(
schema: &Schema,
kind: BoundaryConditionKind,
translational: bool,
values: AxisValues<Option<StiffnessValue>>,
) -> StructuralResult<[(&'static str, Value); 3]> {
let entity = entity_name(kind);
let (current, legacy) = names(kind, translational);
let declared = schema.attribute_names(entity);
let names = if declared
.iter()
.any(|name| name.eq_ignore_ascii_case(current[0]))
{
current
} else {
legacy
};
let measure = measure_name(kind, translational);
let field = |index: usize, value| {
declared_form(schema, entity, names[index], value, measure).map(|form| (names[index], form))
};
Ok([
field(0, values.x)?,
field(1, values.y)?,
field(2, values.z)?,
])
}
pub(super) fn warping(
schema: &Schema,
value: Option<StiffnessValue>,
) -> StructuralResult<(&'static str, Value)> {
const ATTRIBUTE: &str = "WarpingStiffness";
let entity = entity_name(BoundaryConditionKind::NodeWarping);
let form = declared_form(schema, entity, ATTRIBUTE, value, "IfcWarpingMomentMeasure")?;
Ok((ATTRIBUTE, form))
}
fn declared_form(
schema: &Schema,
entity_type: &'static str,
attribute: &'static str,
value: Option<StiffnessValue>,
measure: &'static str,
) -> StructuralResult<Value> {
let (member, payload) = match value {
None => return Ok(Value::Null),
Some(StiffnessValue::Boolean(flag)) => ("IfcBoolean", Value::Bool(flag)),
Some(StiffnessValue::Measure(number)) => (measure, Value::Real(number)),
};
let declared = schema
.attributes(entity_type)
.into_iter()
.find(|declared| declared.name.eq_ignore_ascii_case(attribute))
.map(|declared| declared.type_name.clone())
.ok_or_else(|| StructuralError::UnsupportedAttribute {
entity_type: entity_type.to_owned(),
attribute: attribute.to_owned(),
})?;
if !schema.accepts_type(&declared, member) {
return Err(StructuralError::InvalidDraftValue {
entity_type,
attribute,
expected:
"a value the release's declared type admits (IFC2X3 has no boolean stiffness)",
});
}
let select = schema
.type_def(&declared)
.is_some_and(|definition| matches!(definition.kind, TypeKind::Select(_)));
Ok(if select {
Value::Typed {
type_name: member.to_ascii_uppercase().into(),
value: Box::new(payload),
}
} else {
payload
})
}