use ifc_model::{EntityId, Value};
use ifc_schema::SchemaVersion;
use crate::error::{StructuralError, StructuralResult};
use crate::view::Record;
mod rotation;
mod translation;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct AxisValues<T> {
pub x: T,
pub y: T,
pub z: T,
}
impl<T: Default> Default for AxisValues<T> {
fn default() -> Self {
Self {
x: T::default(),
y: T::default(),
z: T::default(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum StiffnessValue {
Boolean(bool),
Measure(f64),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BoundaryConditionKind {
Edge,
Face,
Node,
NodeWarping,
}
#[derive(Debug, Clone, Copy)]
pub struct BoundaryCondition<'m, 's> {
record: Record<'m, 's>,
kind: BoundaryConditionKind,
}
impl<'m, 's> BoundaryCondition<'m, 's> {
pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
let kind = if record
.schema
.is_a(&record.entity.type_name, "IfcBoundaryNodeConditionWarping")
{
BoundaryConditionKind::NodeWarping
} else if record
.schema
.is_a(&record.entity.type_name, "IfcBoundaryNodeCondition")
{
BoundaryConditionKind::Node
} else if record
.schema
.is_a(&record.entity.type_name, "IfcBoundaryEdgeCondition")
{
BoundaryConditionKind::Edge
} else if record
.schema
.is_a(&record.entity.type_name, "IfcBoundaryFaceCondition")
{
BoundaryConditionKind::Face
} else {
return Err(StructuralError::WrongType {
id: record.id,
expected: "concrete IfcBoundaryCondition",
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) -> BoundaryConditionKind {
self.kind
}
pub fn name(&self) -> StructuralResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn translational_stiffnesses(
&self,
) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
let (current, legacy) = match self.kind {
BoundaryConditionKind::Edge => (
[
"TranslationalStiffnessByLengthX",
"TranslationalStiffnessByLengthY",
"TranslationalStiffnessByLengthZ",
],
[
"LinearStiffnessByLengthX",
"LinearStiffnessByLengthY",
"LinearStiffnessByLengthZ",
],
),
BoundaryConditionKind::Face => (
[
"TranslationalStiffnessByAreaX",
"TranslationalStiffnessByAreaY",
"TranslationalStiffnessByAreaZ",
],
[
"LinearStiffnessByAreaX",
"LinearStiffnessByAreaY",
"LinearStiffnessByAreaZ",
],
),
BoundaryConditionKind::Node | BoundaryConditionKind::NodeWarping => (
[
"TranslationalStiffnessX",
"TranslationalStiffnessY",
"TranslationalStiffnessZ",
],
["LinearStiffnessX", "LinearStiffnessY", "LinearStiffnessZ"],
),
};
self.stiffness_axes(current, legacy)
}
pub fn rotational_stiffnesses(&self) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
if self.kind == BoundaryConditionKind::Face {
return Ok(AxisValues::default());
}
let names = if self.kind == BoundaryConditionKind::Edge {
[
"RotationalStiffnessByLengthX",
"RotationalStiffnessByLengthY",
"RotationalStiffnessByLengthZ",
]
} else {
[
"RotationalStiffnessX",
"RotationalStiffnessY",
"RotationalStiffnessZ",
]
};
self.stiffness_axes(names, names)
}
pub fn warping_stiffness(&self) -> StructuralResult<Option<StiffnessValue>> {
if self.kind != BoundaryConditionKind::NodeWarping {
return Ok(None);
}
decode_stiffness(&self.record, "WarpingStiffness")
}
fn stiffness_axes(
&self,
current: [&'static str; 3],
legacy: [&'static str; 3],
) -> StructuralResult<AxisValues<Option<StiffnessValue>>> {
let value = |index| {
let selected = if self.record.has_attribute(current[index]) {
current[index]
} else {
legacy[index]
};
decode_stiffness(&self.record, selected)
};
Ok(AxisValues {
x: value(0)?,
y: value(1)?,
z: value(2)?,
})
}
}
fn decode_stiffness(
record: &Record<'_, '_>,
attribute: &'static str,
) -> StructuralResult<Option<StiffnessValue>> {
match record.value(attribute)?.unwrap_typed() {
Value::Null | Value::Derived => Ok(None),
Value::Bool(value) if record.schema.version() != Some(SchemaVersion::Ifc2x3) => {
Ok(Some(StiffnessValue::Boolean(*value)))
}
Value::Integer(value) => Ok(Some(StiffnessValue::Measure(*value as f64))),
Value::Real(value) if value.is_finite() => Ok(Some(StiffnessValue::Measure(*value))),
_ => Err(StructuralError::InvalidValue {
entity: record.id,
attribute,
expected: "finite stiffness measure or schema-supported boolean",
}),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionConditionKind {
Failure,
Slippage,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FailureLimits {
pub tension: AxisValues<Option<f64>>,
pub compression: AxisValues<Option<f64>>,
}
#[derive(Debug, Clone, Copy)]
pub struct ConnectionCondition<'m, 's> {
record: Record<'m, 's>,
kind: ConnectionConditionKind,
}
impl<'m, 's> ConnectionCondition<'m, 's> {
pub(crate) fn from_record(record: Record<'m, 's>) -> StructuralResult<Self> {
let kind = if record
.schema
.is_a(&record.entity.type_name, "IfcFailureConnectionCondition")
{
ConnectionConditionKind::Failure
} else if record
.schema
.is_a(&record.entity.type_name, "IfcSlippageConnectionCondition")
{
ConnectionConditionKind::Slippage
} else {
return Err(StructuralError::WrongType {
id: record.id,
expected: "concrete IfcStructuralConnectionCondition",
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) -> ConnectionConditionKind {
self.kind
}
pub fn name(&self) -> StructuralResult<Option<&'m str>> {
self.record.optional_text("Name")
}
pub fn failure_limits(&self) -> StructuralResult<Option<FailureLimits>> {
if self.kind != ConnectionConditionKind::Failure {
return Ok(None);
}
Ok(Some(FailureLimits {
tension: number_axes(
&self.record,
["TensionFailureX", "TensionFailureY", "TensionFailureZ"],
)?,
compression: number_axes(
&self.record,
[
"CompressionFailureX",
"CompressionFailureY",
"CompressionFailureZ",
],
)?,
}))
}
pub fn slippages(&self) -> StructuralResult<Option<AxisValues<Option<f64>>>> {
if self.kind != ConnectionConditionKind::Slippage {
return Ok(None);
}
Ok(Some(number_axes(
&self.record,
["SlippageX", "SlippageY", "SlippageZ"],
)?))
}
}
fn number_axes(
record: &Record<'_, '_>,
names: [&'static str; 3],
) -> StructuralResult<AxisValues<Option<f64>>> {
Ok(AxisValues {
x: record.optional_number(names[0])?,
y: record.optional_number(names[1])?,
z: record.optional_number(names[2])?,
})
}