ifc_lite_processing/analytic_export/
definitions.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//! Reusable authored source geometry and explicit product occurrences.
6
7use std::collections::BTreeMap;
8use ifc_lite_geometry::analytic::{AnalyticCurveSegment, AnalyticStatus, AnalyticSweptDisk};
9use serde::Serialize;
10use sha2::{Digest, Sha256};
11use nalgebra::Matrix4;
12
13/// A context is a top-level representation for a direct solid, or the ordered
14/// `IfcRepresentationMap` IDs for a mapped solid. MappingTarget IDs are excluded
15/// so occurrences of one map share their source definition.
16#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
17#[serde(tag = "kind", rename_all = "snake_case")]
18pub enum AnalyticSourceContext {
19    Direct { representation_id: u32 },
20    Mapped { representation_map_path: Vec<u32> },
21}
22
23/// Stable within and across reads of the same IFC bytes.
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
25pub struct AnalyticSourceKey {
26    pub model_sha256: String,
27    pub schema: Option<String>,
28    /// Sixteen hex digits preserve f64 identity through JSON/JavaScript.
29    pub length_unit_scale_bits: String,
30    pub context: AnalyticSourceContext,
31    pub solid_id: u32,
32}
33
34/// Compatible name for source keys returned by swept-disk APIs.
35pub type SweptDiskSourceKey = AnalyticSourceKey;
36/// Compatible name for source contexts returned by swept-disk APIs.
37pub type SweptDiskSourceContext = AnalyticSourceContext;
38
39/// Original `IfcSweptDiskSolid` geometry in IFC file length units.
40#[derive(Debug, Clone, Serialize)]
41pub struct SweptDiskDefinition {
42    pub key: SweptDiskSourceKey,
43    pub directrix_id: u32,
44    #[serde(rename = "Radius")]
45    pub radius: f64,
46    #[serde(rename = "InnerRadius")]
47    pub inner_radius: Option<f64>,
48    #[serde(rename = "Directrix")]
49    pub directrix: Vec<AnalyticCurveSegment>,
50    pub status: AnalyticStatus,
51}
52
53/// One use of a source solid. Matrix is column-major f64 and maps raw IFC
54/// source coordinates directly to absolute IFC Z-up world metres. It includes
55/// model length units, object placement and every enclosing mapping transform.
56#[derive(Debug, Clone, Serialize)]
57pub struct SweptDiskInstance {
58    pub ordinal: usize,
59    pub source: SweptDiskSourceKey,
60    pub product_id: u32,
61    pub solid_id: u32,
62    pub mapping_path: Vec<u32>,
63    pub source_modified: bool,
64    pub world_from_source: Option<[f64; 16]>,
65    pub status: AnalyticStatus,
66}
67
68/// Bounded source/instance view of authored swept disks.
69#[derive(Debug, Clone, Serialize)]
70pub struct SweptDiskDefinitions {
71    pub up_axis: &'static str,
72    pub source_units: &'static str,
73    pub world_units: &'static str,
74    pub coordinate_space: &'static str,
75    pub model_sha256: String,
76    pub schema: Option<String>,
77    pub length_unit_scale: f64,
78    pub sources: Vec<SweptDiskDefinition>,
79    pub instances: BTreeMap<u32, Vec<SweptDiskInstance>>,
80    pub diagnostics: Vec<String>,
81}
82
83impl SweptDiskDefinitions {
84    pub(super) fn new(content: &[u8], unit_scale: f64) -> Self {
85        let model_sha256 = model_sha256(content);
86        let schema = ifc_lite_core::declared_schema_bounded(content);
87        Self { up_axis: "Z", source_units: "ifc_file_length_units",
88            world_units: "m", coordinate_space: "absolute_ifc_world",
89            model_sha256, schema, length_unit_scale: unit_scale,
90            sources: Vec::new(), instances: BTreeMap::new(), diagnostics: Vec::new() }
91    }
92
93    pub(super) fn key(&self, context: AnalyticSourceContext, solid_id: u32) -> AnalyticSourceKey {
94        source_key(&self.model_sha256, self.schema.as_deref(), self.length_unit_scale,
95            context, solid_id)
96    }
97}
98
99impl SweptDiskDefinition {
100    pub(super) fn from_disk(key: SweptDiskSourceKey, disk: &AnalyticSweptDisk) -> Self {
101        Self { key, directrix_id: disk.directrix_id, radius: disk.radius,
102            inner_radius: disk.inner_radius, directrix: disk.segments.clone(),
103            status: disk.status.clone() }
104    }
105}
106
107pub(super) fn model_sha256(content: &[u8]) -> String {
108    format!("{:x}", Sha256::digest(content))
109}
110
111pub(super) fn source_key(
112    model_sha256: &str, schema: Option<&str>, length_unit_scale: f64,
113    context: AnalyticSourceContext, solid_id: u32,
114) -> AnalyticSourceKey {
115    AnalyticSourceKey { model_sha256: model_sha256.to_owned(),
116        schema: schema.map(str::to_owned),
117        length_unit_scale_bits: format!("{:016x}", length_unit_scale.to_bits()),
118        context, solid_id }
119}
120
121pub(super) fn source_matrix(transform: &Matrix4<f64>, unit_scale: f64) -> Option<[f64; 16]> {
122    if !unit_scale.is_finite() || unit_scale <= 0.0 || transform.iter().any(|v| !v.is_finite()) {
123        return None;
124    }
125    let mut matrix = *transform;
126    for column in 0..3 {
127        for row in 0..3 { matrix[(row, column)] *= unit_scale; }
128    }
129    matrix.iter().all(|v| v.is_finite()).then(|| {
130        let mut values = [0.0; 16];
131        values.copy_from_slice(matrix.as_slice());
132        values
133    })
134}