use crate::StepModel;
use crate::generated::model as m;
use crate::scene::Scene;
#[derive(Clone, Debug, Default)]
pub struct Units {
pub length: Option<Unit>,
pub angle: Option<Unit>,
pub precision: Option<f64>,
}
#[derive(Clone, Debug)]
pub struct Unit {
pub name: String,
pub to_si: f64,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum UnitKind {
Length,
PlaneAngle,
Other,
}
impl Scene<'_> {
#[must_use]
pub fn units(&self) -> Units {
units_of(self.model())
}
}
pub(crate) fn units_of(model: &StepModel) -> Units {
let ctx = find_unit_context(model);
let unit_refs: &[m::UnitRef] = ctx
.and_then(|cu| {
cu.parts.iter().find_map(|p| match p {
m::UnitPart::GlobalUnitAssignedContext { units } => Some(units.as_slice()),
_ => None,
})
})
.or_else(|| {
model
.global_unit_assigned_context_arena
.items
.first()
.map(|g| g.units.as_slice())
})
.unwrap_or(&[]);
let mut out = Units::default();
for ur in unit_refs {
if let Some((kind, unit)) = classify_unit(model, ur) {
match kind {
UnitKind::Length if out.length.is_none() => out.length = Some(unit),
UnitKind::PlaneAngle if out.angle.is_none() => out.angle = Some(unit),
_ => {}
}
}
}
out.precision = ctx
.and_then(|cu| {
cu.parts.iter().find_map(|p| match p {
m::UnitPart::GlobalUncertaintyAssignedContext { uncertainty } => {
Some(uncertainty.as_slice())
}
_ => None,
})
})
.and_then(|u| u.first())
.map(
|m::UncertaintyMeasureWithUnitRef::UncertaintyMeasureWithUnit(id)| {
model
.uncertainty_measure_with_unit_arena
.get(id.0)
.value_component
.value
.as_f64()
},
);
out
}
fn find_unit_context(model: &StepModel) -> Option<&m::ComplexUnit> {
model.complex_unit_arena.items.iter().find(|cu| {
cu.parts
.iter()
.any(|p| matches!(p, m::UnitPart::GlobalUnitAssignedContext { .. }))
})
}
fn classify_unit(model: &StepModel, ur: &m::UnitRef) -> Option<(UnitKind, Unit)> {
match ur {
m::UnitRef::SiUnit(id) => {
let si = model.si_unit_arena.get(id.0);
Some(classify_si(si.prefix, si.name))
}
m::UnitRef::ConversionBasedUnit(id) => {
let cbu = model.conversion_based_unit_arena.get(id.0);
classify_cbu(model, &cbu.name, &cbu.conversion_factor)
}
m::UnitRef::Complex(cuid) => {
let cu = model.complex_unit_arena.get(cuid.0);
cu.parts.iter().find_map(|p| match p {
m::UnitPart::SiUnit { prefix, name } => Some(classify_si(*prefix, *name)),
m::UnitPart::ConversionBasedUnit {
name,
conversion_factor,
} => classify_cbu(model, name, conversion_factor),
_ => None,
})
}
_ => None,
}
}
fn classify_si(prefix: Option<m::SiPrefix>, name: m::SiUnitName) -> (UnitKind, Unit) {
let kind = match name {
m::SiUnitName::Metre => UnitKind::Length,
m::SiUnitName::Radian => UnitKind::PlaneAngle,
_ => UnitKind::Other,
};
let display = format!(
"{}{:?}",
prefix.map(|p| format!("{p:?}")).unwrap_or_default(),
name
)
.to_uppercase();
(
kind,
Unit {
name: display,
to_si: si_prefix_factor(prefix),
},
)
}
fn classify_cbu(
model: &StepModel,
name: &str,
factor: &m::MeasureWithUnitRef,
) -> Option<(UnitKind, Unit)> {
let (value, base) = measure_with_unit(model, factor)?;
let (kind, base_unit) = classify_unit(model, base)?;
Some((
kind,
Unit {
name: name.to_uppercase(),
to_si: value * base_unit.to_si,
},
))
}
fn measure_with_unit<'m>(
model: &'m StepModel,
r: &'m m::MeasureWithUnitRef,
) -> Option<(f64, &'m m::UnitRef)> {
let (value, unit) = match r {
m::MeasureWithUnitRef::MeasureWithUnit(i) => {
let e = model.measure_with_unit_arena.get(i.0);
(&e.value_component, &e.unit_component)
}
m::MeasureWithUnitRef::LengthMeasureWithUnit(i) => {
let e = model.length_measure_with_unit_arena.get(i.0);
(&e.value_component, &e.unit_component)
}
m::MeasureWithUnitRef::PlaneAngleMeasureWithUnit(i) => {
let e = model.plane_angle_measure_with_unit_arena.get(i.0);
(&e.value_component, &e.unit_component)
}
_ => return None,
};
Some((value.value.as_f64(), unit))
}
fn si_prefix_factor(prefix: Option<m::SiPrefix>) -> f64 {
use m::SiPrefix::{
Atto, Centi, Deca, Deci, Exa, Femto, Giga, Hecto, Kilo, Mega, Micro, Milli, Nano, Peta,
Pico, Tera,
};
match prefix {
None => 1.0,
Some(Exa) => 1e18,
Some(Peta) => 1e15,
Some(Tera) => 1e12,
Some(Giga) => 1e9,
Some(Mega) => 1e6,
Some(Kilo) => 1e3,
Some(Hecto) => 1e2,
Some(Deca) => 1e1,
Some(Deci) => 1e-1,
Some(Centi) => 1e-2,
Some(Milli) => 1e-3,
Some(Micro) => 1e-6,
Some(Nano) => 1e-9,
Some(Pico) => 1e-12,
Some(Femto) => 1e-15,
Some(Atto) => 1e-18,
}
}