Skip to main content

ifc_lite_processing/types/
mesh.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//! Mesh data types for serialization.
6
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9
10/// A decoded RGBA8 surface texture attached to a mesh (issue #961).
11/// Decoded entirely in Rust (`IfcBlobTexture` PNG / `IfcPixelTexture` raw); the
12/// browser only uploads `rgba` to a GPU texture — no image logic in TS.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct MeshTextureData {
15    /// `width * height * 4` bytes, row-major, top-down, straight alpha.
16    pub rgba: Vec<u8>,
17    pub width: u32,
18    pub height: u32,
19    /// Sampler wrap from `IfcSurfaceTexture.RepeatS/RepeatT`.
20    pub repeat_s: bool,
21    pub repeat_t: bool,
22}
23
24/// Individual mesh data with geometry and metadata.
25#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct MeshData {
27    /// Express ID of the IFC element.
28    pub express_id: u32,
29    /// IFC type name (e.g., "IfcWall").
30    pub ifc_type: String,
31    /// IFC GlobalId (Root attribute #0) when available.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub global_id: Option<String>,
34    /// IFC Name (Root/Object attribute #2) when available.
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub name: Option<String>,
37    /// IFC presentation layer assignment name when available.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub presentation_layer: Option<String>,
40    /// Vertex positions (x, y, z triplets).
41    pub positions: Vec<f32>,
42    /// Vertex normals (x, y, z triplets).
43    pub normals: Vec<f32>,
44    /// Triangle indices.
45    pub indices: Vec<u32>,
46    /// RGBA color [r, g, b, a] in 0-1 range.
47    pub color: [f32; 4],
48    /// Optional material/style name resolved from per-item IFC styling.
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub material_name: Option<String>,
51    /// Optional source geometry item id for submesh outputs.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub geometry_item_id: Option<u32>,
54    /// Optional IFC property set values keyed by IFC property names.
55    /// Primarily attached for IfcSpace/IfcZone so downstream tools can build room attribute UIs.
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub properties: Option<BTreeMap<String, String>>,
58    /// Per-vertex texture coordinates (u, v pairs, 1:1 with `positions`),
59    /// present only for textured meshes (issue #961).
60    #[serde(skip_serializing_if = "Option::is_none")]
61    pub uvs: Option<Vec<f32>>,
62    /// Decoded surface texture, present only for textured meshes (#961).
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub texture: Option<MeshTextureData>,
65}
66
67impl MeshData {
68    /// Create a new MeshData from geometry components.
69    pub fn new(
70        express_id: u32,
71        ifc_type: String,
72        positions: Vec<f32>,
73        normals: Vec<f32>,
74        indices: Vec<u32>,
75        color: [f32; 4],
76    ) -> Self {
77        Self {
78            express_id,
79            ifc_type,
80            global_id: None,
81            name: None,
82            presentation_layer: None,
83            positions,
84            normals,
85            indices,
86            color,
87            material_name: None,
88            geometry_item_id: None,
89            properties: None,
90            uvs: None,
91            texture: None,
92        }
93    }
94
95    /// Attach per-vertex UVs + a decoded surface texture (issue #961).
96    /// `uvs` must be 1:1 with `positions` (2 floats per vertex).
97    pub fn with_texture(mut self, uvs: Vec<f32>, texture: MeshTextureData) -> Self {
98        self.uvs = Some(uvs);
99        self.texture = Some(texture);
100        self
101    }
102
103    /// Set element-level IFC metadata.
104    pub fn with_element_metadata(
105        mut self,
106        global_id: Option<String>,
107        name: Option<String>,
108        presentation_layer: Option<String>,
109    ) -> Self {
110        self.global_id = global_id;
111        self.name = name;
112        self.presentation_layer = presentation_layer;
113        self
114    }
115
116    /// Set material name and source geometry item id metadata.
117    pub fn with_style_metadata(
118        mut self,
119        material_name: Option<String>,
120        geometry_item_id: Option<u32>,
121    ) -> Self {
122        self.material_name = material_name;
123        self.geometry_item_id = geometry_item_id;
124        self
125    }
126
127    /// Attach optional IFC property set values.
128    pub fn with_properties(mut self, properties: Option<BTreeMap<String, String>>) -> Self {
129        self.properties = properties;
130        self
131    }
132
133    /// Get the number of vertices.
134    pub fn vertex_count(&self) -> usize {
135        self.positions.len() / 3
136    }
137
138    /// Get the number of triangles.
139    pub fn triangle_count(&self) -> usize {
140        self.indices.len() / 3
141    }
142
143    /// Check if the mesh is empty.
144    pub fn is_empty(&self) -> bool {
145        self.positions.is_empty() || self.indices.is_empty()
146    }
147}