ifc_lite_core/decoder/
precision.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Declared geometric context precision for context-free analytic extraction.
6
7use super::EntityDecoder;
8use crate::{Error, Result};
9use crate::parser::{keyword_eq, EntityScanner};
10
11impl EntityDecoder<'_> {
12    /// Smallest and largest declared `IfcGeometricRepresentationContext.Precision`
13    /// in raw IFC file-length units. A source profile can be reused from multiple
14    /// representations: the largest declaration gives conservative boundary
15    /// clearance, while the smallest bounds when adjacent points may be treated
16    /// as the same join. Returns `None` when nothing declares Precision; IFC
17    /// defines no default.
18    ///
19    /// This is a lazy, cached full-file scan and does not decode non-context
20    /// entities. A malformed explicit Precision is an error, not zero.
21    pub fn geometric_context_precision_range(&mut self) -> Result<Option<(f64, f64)>> {
22        if self.geometric_precision_cache.is_none() {
23            self.geometric_precision_cache = Some(self.scan_geometric_context_precision());
24        }
25        self.geometric_precision_cache.as_ref().unwrap().clone()
26            .map_err(|message| Error::parse(0, message))
27    }
28
29    fn scan_geometric_context_precision(&self) -> std::result::Result<Option<(f64, f64)>, String> {
30        let mut scanner = EntityScanner::new(self.content);
31        let mut range: Option<(f64, f64)> = None;
32        while let Some((id, type_name, start, end)) = scanner.next_entity() {
33            if !keyword_eq(type_name, "IFCGEOMETRICREPRESENTATIONCONTEXT")
34                && !keyword_eq(type_name, "IFCGEOMETRICREPRESENTATIONSUBCONTEXT") {
35                continue;
36            }
37            let entity = self.decode_at_uncached(start, end)
38                .map_err(|error| format!("geometric context #{id}: {error}"))?;
39            let attribute = entity.get(3)
40                .ok_or_else(|| format!("geometric context #{id} has no Precision attribute"))?;
41            if attribute.is_null() { continue; }
42            let precision = attribute.as_float()
43                .filter(|value| value.is_finite() && *value > 0.0)
44                .ok_or_else(|| format!("geometric context #{id} has invalid Precision"))?;
45            range = Some(range.map_or((precision, precision), |(min, max)| {
46                (min.min(precision), max.max(precision))
47            }));
48        }
49        Ok(range)
50    }
51}
52
53#[cfg(test)]
54#[path = "precision_tests.rs"]
55mod tests;