Skip to main content

bimifc_parser/ifcx/
types.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//! IFC5 (IFCX) type definitions
6//!
7//! Based on buildingSMART IFC5-development schema.
8//! Uses serde for JSON deserialization with minimal allocations.
9
10use serde::Deserialize;
11use std::collections::HashMap;
12
13/// Root IFCX file structure
14#[derive(Debug, Deserialize)]
15pub struct IfcxFile {
16    pub header: IfcxHeader,
17    #[serde(default)]
18    pub imports: Vec<ImportNode>,
19    #[serde(default)]
20    pub schemas: HashMap<String, serde_json::Value>,
21    pub data: Vec<IfcxNode>,
22}
23
24/// IFCX file header
25#[derive(Debug, Deserialize)]
26#[serde(rename_all = "camelCase")]
27pub struct IfcxHeader {
28    pub id: String,
29    pub ifcx_version: String,
30    pub data_version: String,
31    #[serde(default)]
32    pub author: String,
33    #[serde(default)]
34    pub timestamp: String,
35}
36
37/// Import reference to external schema
38#[derive(Debug, Deserialize)]
39pub struct ImportNode {
40    pub uri: String,
41    #[serde(default)]
42    pub integrity: Option<String>,
43}
44
45/// Raw IFCX node from JSON
46#[derive(Debug, Deserialize)]
47pub struct IfcxNode {
48    pub path: String,
49    #[serde(default)]
50    pub children: HashMap<String, Option<String>>,
51    #[serde(default)]
52    pub inherits: HashMap<String, Option<String>>,
53    #[serde(default)]
54    pub attributes: HashMap<String, serde_json::Value>,
55}
56
57/// Composed node after flattening ECS structure
58#[derive(Debug, Clone)]
59pub struct ComposedNode {
60    pub path: String,
61    pub attributes: rustc_hash::FxHashMap<String, serde_json::Value>,
62    pub children: Vec<String>,  // Child paths
63    pub parent: Option<String>, // Parent path
64}
65
66/// Well-known attribute namespace constants
67pub mod attr {
68    /// IFC classification (bsi::ifc::class)
69    pub const CLASS: &str = "bsi::ifc::class";
70    /// USD mesh geometry
71    pub const MESH: &str = "usd::usdgeom::mesh";
72    /// USD transform prefix
73    pub const TRANSFORM: &str = "usd::xformop::transform";
74    /// USD visibility
75    pub const VISIBILITY: &str = "usd::usdgeom::visibility";
76    /// Diffuse color
77    pub const DIFFUSE_COLOR: &str = "bsi::ifc::presentation::diffuseColor";
78    /// Opacity
79    pub const OPACITY: &str = "bsi::ifc::presentation::opacity";
80    /// Material
81    pub const MATERIAL: &str = "bsi::ifc::material";
82    /// Property prefix
83    pub const PROP_PREFIX: &str = "bsi::ifc::prop::";
84}
85
86/// USD mesh data structure
87#[derive(Debug, Clone, Default)]
88pub struct UsdMesh {
89    pub points: Vec<[f64; 3]>,
90    pub face_vertex_indices: Vec<u32>,
91    pub face_vertex_counts: Option<Vec<u32>>,
92    pub normals: Option<Vec<[f64; 3]>>,
93}
94
95impl UsdMesh {
96    /// Parse from serde_json::Value
97    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
98        let obj = value.as_object()?;
99
100        // Parse points: [[x,y,z], ...]
101        let points_arr = obj.get("points")?.as_array()?;
102        let mut points = Vec::with_capacity(points_arr.len());
103        for p in points_arr {
104            let arr = p.as_array()?;
105            if arr.len() >= 3 {
106                points.push([arr[0].as_f64()?, arr[1].as_f64()?, arr[2].as_f64()?]);
107            }
108        }
109
110        // Parse faceVertexIndices
111        let indices_arr = obj.get("faceVertexIndices")?.as_array()?;
112        let mut face_vertex_indices = Vec::with_capacity(indices_arr.len());
113        for i in indices_arr {
114            face_vertex_indices.push(i.as_u64()? as u32);
115        }
116
117        // Parse optional faceVertexCounts
118        let face_vertex_counts = obj.get("faceVertexCounts").and_then(|v| {
119            let arr = v.as_array()?;
120            let mut counts = Vec::with_capacity(arr.len());
121            for c in arr {
122                counts.push(c.as_u64()? as u32);
123            }
124            Some(counts)
125        });
126
127        // Parse optional normals
128        let normals = obj.get("normals").and_then(|v| {
129            let arr = v.as_array()?;
130            let mut norms = Vec::with_capacity(arr.len());
131            for n in arr {
132                let narr = n.as_array()?;
133                if narr.len() >= 3 {
134                    norms.push([narr[0].as_f64()?, narr[1].as_f64()?, narr[2].as_f64()?]);
135                }
136            }
137            Some(norms)
138        });
139
140        Some(Self {
141            points,
142            face_vertex_indices,
143            face_vertex_counts,
144            normals,
145        })
146    }
147
148    /// Check if mesh is already triangulated
149    pub fn is_triangulated(&self) -> bool {
150        match &self.face_vertex_counts {
151            None => true, // Default is triangles
152            Some(counts) => counts.iter().all(|&c| c == 3),
153        }
154    }
155
156    /// Triangulate mesh (fan triangulation for polygons)
157    pub fn triangulate(&self) -> Vec<u32> {
158        if self.is_triangulated() {
159            return self.face_vertex_indices.clone();
160        }
161
162        let counts = self.face_vertex_counts.as_ref().unwrap();
163        let mut result = Vec::new();
164        let mut idx = 0usize;
165
166        for &count in counts {
167            let count = count as usize;
168            if count == 3 {
169                // Already a triangle
170                result.push(self.face_vertex_indices[idx]);
171                result.push(self.face_vertex_indices[idx + 1]);
172                result.push(self.face_vertex_indices[idx + 2]);
173            } else if count > 3 {
174                // Fan triangulation: first vertex + consecutive pairs
175                let v0 = self.face_vertex_indices[idx];
176                for i in 1..(count - 1) {
177                    result.push(v0);
178                    result.push(self.face_vertex_indices[idx + i]);
179                    result.push(self.face_vertex_indices[idx + i + 1]);
180                }
181            }
182            idx += count;
183        }
184
185        result
186    }
187}
188
189/// IFC class reference
190#[derive(Debug, Clone)]
191pub struct IfcClass {
192    pub code: String,
193    pub uri: Option<String>,
194}
195
196impl IfcClass {
197    /// Parse from serde_json::Value
198    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
199        let obj = value.as_object()?;
200        let code = obj.get("code")?.as_str()?.to_string();
201        let uri = obj.get("uri").and_then(|v| v.as_str()).map(String::from);
202        Some(Self { code, uri })
203    }
204}
205
206/// 4x4 transformation matrix (row-major)
207#[derive(Debug, Clone, Copy)]
208pub struct Transform4x4 {
209    pub matrix: [[f64; 4]; 4],
210}
211
212impl Default for Transform4x4 {
213    fn default() -> Self {
214        Self::identity()
215    }
216}
217
218impl Transform4x4 {
219    pub fn identity() -> Self {
220        Self {
221            matrix: [
222                [1.0, 0.0, 0.0, 0.0],
223                [0.0, 1.0, 0.0, 0.0],
224                [0.0, 0.0, 1.0, 0.0],
225                [0.0, 0.0, 0.0, 1.0],
226            ],
227        }
228    }
229
230    /// Parse from serde_json::Value (array of arrays)
231    pub fn from_value(value: &serde_json::Value) -> Option<Self> {
232        // Can be under "transform" key or directly be array
233        let arr = if let Some(obj) = value.as_object() {
234            obj.get("transform")?.as_array()?
235        } else {
236            value.as_array()?
237        };
238
239        if arr.len() != 4 {
240            return None;
241        }
242
243        let mut matrix = [[0.0; 4]; 4];
244        for (i, row) in arr.iter().enumerate() {
245            let row_arr = row.as_array()?;
246            if row_arr.len() != 4 {
247                return None;
248            }
249            for (j, val) in row_arr.iter().enumerate() {
250                matrix[i][j] = val.as_f64()?;
251            }
252        }
253
254        Some(Self { matrix })
255    }
256
257    /// Transform a point
258    pub fn transform_point(&self, point: [f64; 3]) -> [f64; 3] {
259        let m = &self.matrix;
260        let w = m[3][0] * point[0] + m[3][1] * point[1] + m[3][2] * point[2] + m[3][3];
261        [
262            (m[0][0] * point[0] + m[0][1] * point[1] + m[0][2] * point[2] + m[0][3]) / w,
263            (m[1][0] * point[0] + m[1][1] * point[1] + m[1][2] * point[2] + m[1][3]) / w,
264            (m[2][0] * point[0] + m[2][1] * point[1] + m[2][2] * point[2] + m[2][3]) / w,
265        ]
266    }
267
268    /// Multiply two matrices
269    pub fn multiply(&self, other: &Self) -> Self {
270        let mut result = [[0.0; 4]; 4];
271        #[allow(clippy::needless_range_loop)]
272        for i in 0..4 {
273            for j in 0..4 {
274                for k in 0..4 {
275                    result[i][j] += self.matrix[i][k] * other.matrix[k][j];
276                }
277            }
278        }
279        Self { matrix: result }
280    }
281}