1use crate::StepModel;
10use crate::generated::model as m;
11use crate::scene::Scene;
12
13#[derive(Clone, Debug, Default)]
16pub struct Units {
17 pub length: Option<Unit>,
18 pub angle: Option<Unit>,
19 pub precision: Option<f64>,
21}
22
23#[derive(Clone, Debug)]
26pub struct Unit {
27 pub name: String,
28 pub to_si: f64,
29}
30
31#[derive(Clone, Copy, PartialEq, Eq)]
32enum UnitKind {
33 Length,
34 PlaneAngle,
35 Other,
36}
37
38impl Scene<'_> {
39 #[must_use]
45 pub fn units(&self) -> Units {
46 units_of(self.model())
47 }
48}
49
50pub(crate) fn units_of(model: &StepModel) -> Units {
53 let ctx = find_unit_context(model);
54
55 let unit_refs: &[m::UnitRef] = ctx
56 .and_then(|cu| {
57 cu.parts.iter().find_map(|p| match p {
58 m::UnitPart::GlobalUnitAssignedContext { units } => Some(units.as_slice()),
59 _ => None,
60 })
61 })
62 .or_else(|| {
63 model
64 .global_unit_assigned_context_arena
65 .items
66 .first()
67 .map(|g| g.units.as_slice())
68 })
69 .unwrap_or(&[]);
70
71 let mut out = Units::default();
72 for ur in unit_refs {
73 if let Some((kind, unit)) = classify_unit(model, ur) {
74 match kind {
75 UnitKind::Length if out.length.is_none() => out.length = Some(unit),
76 UnitKind::PlaneAngle if out.angle.is_none() => out.angle = Some(unit),
77 _ => {}
78 }
79 }
80 }
81 out.precision = ctx
82 .and_then(|cu| {
83 cu.parts.iter().find_map(|p| match p {
84 m::UnitPart::GlobalUncertaintyAssignedContext { uncertainty } => {
85 Some(uncertainty.as_slice())
86 }
87 _ => None,
88 })
89 })
90 .and_then(|u| u.first())
91 .map(
92 |m::UncertaintyMeasureWithUnitRef::UncertaintyMeasureWithUnit(id)| {
93 model
94 .uncertainty_measure_with_unit_arena
95 .get(id.0)
96 .value_component
97 .value
98 .as_f64()
99 },
100 );
101 out
102}
103
104fn find_unit_context(model: &StepModel) -> Option<&m::ComplexUnit> {
107 model.complex_unit_arena.items.iter().find(|cu| {
108 cu.parts
109 .iter()
110 .any(|p| matches!(p, m::UnitPart::GlobalUnitAssignedContext { .. }))
111 })
112}
113
114fn classify_unit(model: &StepModel, ur: &m::UnitRef) -> Option<(UnitKind, Unit)> {
117 match ur {
118 m::UnitRef::SiUnit(id) => {
119 let si = model.si_unit_arena.get(id.0);
120 Some(classify_si(si.prefix, si.name))
121 }
122 m::UnitRef::ConversionBasedUnit(id) => {
123 let cbu = model.conversion_based_unit_arena.get(id.0);
124 classify_cbu(model, &cbu.name, &cbu.conversion_factor)
125 }
126 m::UnitRef::Complex(cuid) => {
127 let cu = model.complex_unit_arena.get(cuid.0);
128 cu.parts.iter().find_map(|p| match p {
129 m::UnitPart::SiUnit { prefix, name } => Some(classify_si(*prefix, *name)),
130 m::UnitPart::ConversionBasedUnit {
131 name,
132 conversion_factor,
133 } => classify_cbu(model, name, conversion_factor),
134 _ => None,
135 })
136 }
137 _ => None,
138 }
139}
140
141fn classify_si(prefix: Option<m::SiPrefix>, name: m::SiUnitName) -> (UnitKind, Unit) {
144 let kind = match name {
145 m::SiUnitName::Metre => UnitKind::Length,
146 m::SiUnitName::Radian => UnitKind::PlaneAngle,
147 _ => UnitKind::Other,
148 };
149 let display = format!(
150 "{}{:?}",
151 prefix.map(|p| format!("{p:?}")).unwrap_or_default(),
152 name
153 )
154 .to_uppercase();
155 (
156 kind,
157 Unit {
158 name: display,
159 to_si: si_prefix_factor(prefix),
160 },
161 )
162}
163
164fn classify_cbu(
167 model: &StepModel,
168 name: &str,
169 factor: &m::MeasureWithUnitRef,
170) -> Option<(UnitKind, Unit)> {
171 let (value, base) = measure_with_unit(model, factor)?;
172 let (kind, base_unit) = classify_unit(model, base)?;
173 Some((
174 kind,
175 Unit {
176 name: name.to_uppercase(),
177 to_si: value * base_unit.to_si,
178 },
179 ))
180}
181
182fn measure_with_unit<'m>(
185 model: &'m StepModel,
186 r: &'m m::MeasureWithUnitRef,
187) -> Option<(f64, &'m m::UnitRef)> {
188 let (value, unit) = match r {
189 m::MeasureWithUnitRef::MeasureWithUnit(i) => {
190 let e = model.measure_with_unit_arena.get(i.0);
191 (&e.value_component, &e.unit_component)
192 }
193 m::MeasureWithUnitRef::LengthMeasureWithUnit(i) => {
194 let e = model.length_measure_with_unit_arena.get(i.0);
195 (&e.value_component, &e.unit_component)
196 }
197 m::MeasureWithUnitRef::PlaneAngleMeasureWithUnit(i) => {
198 let e = model.plane_angle_measure_with_unit_arena.get(i.0);
199 (&e.value_component, &e.unit_component)
200 }
201 _ => return None,
202 };
203 Some((value.value.as_f64(), unit))
204}
205
206fn si_prefix_factor(prefix: Option<m::SiPrefix>) -> f64 {
208 use m::SiPrefix::{
209 Atto, Centi, Deca, Deci, Exa, Femto, Giga, Hecto, Kilo, Mega, Micro, Milli, Nano, Peta,
210 Pico, Tera,
211 };
212 match prefix {
213 None => 1.0,
214 Some(Exa) => 1e18,
215 Some(Peta) => 1e15,
216 Some(Tera) => 1e12,
217 Some(Giga) => 1e9,
218 Some(Mega) => 1e6,
219 Some(Kilo) => 1e3,
220 Some(Hecto) => 1e2,
221 Some(Deca) => 1e1,
222 Some(Deci) => 1e-1,
223 Some(Centi) => 1e-2,
224 Some(Milli) => 1e-3,
225 Some(Micro) => 1e-6,
226 Some(Nano) => 1e-9,
227 Some(Pico) => 1e-12,
228 Some(Femto) => 1e-15,
229 Some(Atto) => 1e-18,
230 }
231}