use axiolid_model::{Station, StationOffsets};
use ifc_model::{EntityId, Value};
use super::POINT;
use crate::error::GeometryResult;
use crate::lower::session::LoweringSession;
mod slot {
pub const DISTANCE_ALONG: usize = 0;
pub const OFFSET_LATERAL: usize = 1;
pub const OFFSET_VERTICAL: usize = 2;
pub const OFFSET_LONGITUDINAL: usize = 3;
pub const BASIS_CURVE: usize = 4;
}
pub(crate) const PARAMETER: &str =
"DistanceAlong is an IfcParameterValue; IFC4.3 ADD2 gives most basis curves no \
parameterisation a distance can be read from, and a neutral station is a distance";
#[derive(Debug, Clone, Copy, PartialEq)]
pub(crate) struct DistanceExpression {
pub entity: EntityId,
pub basis: EntityId,
pub distance: f64,
pub lateral: Option<f64>,
pub vertical: Option<f64>,
pub longitudinal: Option<f64>,
}
impl DistanceExpression {
pub(crate) fn station(&self) -> Station {
Station::new(
self.distance,
StationOffsets::new(
self.lateral.unwrap_or(0.0),
self.vertical.unwrap_or(0.0),
self.longitudinal.unwrap_or(0.0),
),
)
}
pub(crate) fn states_an_offset(&self) -> bool {
self.lateral.is_some() || self.vertical.is_some() || self.longitudinal.is_some()
}
}
pub(crate) fn distance_expression(
session: &LoweringSession<'_>,
owner: EntityId,
owner_type: &str,
point: EntityId,
) -> GeometryResult<DistanceExpression> {
let kind = session.type_name(point)?;
if kind != POINT {
return Err(session.degenerate(
owner,
owner_type,
format!(
"a linear position must be an IfcPointByDistanceExpression (WR1), found {kind}"
),
));
}
let slots = session.slots(point)?;
let units = session.units();
let distance = match slots.req(slot::DISTANCE_ALONG, "DistanceAlong")? {
Value::Typed { type_name, value } if type_name.eq_ignore_ascii_case("IFCLENGTHMEASURE") => {
value.as_f64().map(|raw| units.length(raw))
}
Value::Typed { type_name, .. } if type_name.eq_ignore_ascii_case("IFCPARAMETERVALUE") => {
return Err(session.unsupported(owner, owner_type, PARAMETER))
}
_ => None,
};
let Some(distance) = distance.filter(|d| d.is_finite()) else {
return Err(session.degenerate(
owner,
owner_type,
"DistanceAlong must be a finite, typed IfcLengthMeasure (IfcCurveMeasureSelect)",
));
};
let offset = |index: usize, name: &str| -> GeometryResult<Option<f64>> {
match slots.opt(index) {
None => Ok(None),
Some(_) => match slots.opt_f64(index).map(|raw| units.length(raw)) {
Some(value) if value.is_finite() => Ok(Some(value)),
_ => Err(session.degenerate(
owner,
owner_type,
format!("{name} must be a finite IfcLengthMeasure"),
)),
},
}
};
Ok(DistanceExpression {
entity: point,
basis: slots.req_ref(slot::BASIS_CURVE, "BasisCurve")?,
distance,
lateral: offset(slot::OFFSET_LATERAL, "OffsetLateral")?,
vertical: offset(slot::OFFSET_VERTICAL, "OffsetVertical")?,
longitudinal: offset(slot::OFFSET_LONGITUDINAL, "OffsetLongitudinal")?,
})
}