Skip to main content

ifc_lite_processing/types/
response.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//! Shared response types for the IFC processing API.
6
7use super::mesh::MeshData;
8use crate::georeferencing::Georeferencing;
9use crate::mesh_frame::MeshCoordinateSpace;
10use crate::symbolic::SymbolicData;
11use serde::{Deserialize, Serialize};
12
13/// Full parse response with all meshes.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct ParseResponse {
16    /// Cache key for this result (SHA256 of file content).
17    pub cache_key: String,
18    /// All meshes extracted from the IFC file.
19    pub meshes: Vec<MeshData>,
20    /// Declares the coordinate space used by serialized mesh vertices (see
21    /// [`MeshCoordinateSpace`] for the three tiers and their wire spelling).
22    /// `None` only on responses written before the tag existed.
23    #[serde(skip_serializing_if = "Option::is_none")]
24    pub mesh_coordinate_space: Option<MeshCoordinateSpace>,
25    /// IfcSite ObjectPlacement as a column-major 4x4 matrix (16 f64 values, in meters).
26    /// Used by clients to relocate geometry between global and site-local coordinate systems.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub site_transform: Option<Vec<f64>>,
29    /// IfcBuilding ObjectPlacement as a column-major 4x4 matrix (16 f64 values, in meters).
30    /// Used by clients to relocate geometry between global and building-local coordinate systems.
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub building_transform: Option<Vec<f64>>,
33    /// Model metadata.
34    pub metadata: ModelMetadata,
35    /// Processing statistics.
36    pub stats: ProcessingStats,
37    /// 2D symbol data extracted from `IfcAnnotation` and `IfcGrid`
38    /// entities. Always emitted (potentially empty); see issue #843 for
39    /// the parity rationale with the browser-side parser.
40    #[serde(default, skip_serializing_if = "SymbolicData::is_empty")]
41    pub symbolic_data: SymbolicData,
42}
43
44/// Model metadata extracted from the IFC file.
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
46pub struct ModelMetadata {
47    /// IFC schema version (e.g., "IFC2X3", "IFC4", "IFC4X3").
48    pub schema_version: String,
49    /// Total number of entities in the file.
50    pub entity_count: usize,
51    /// Number of geometry-bearing entities.
52    pub geometry_entity_count: usize,
53    /// Coordinate system information.
54    pub coordinate_info: CoordinateInfo,
55    /// Length unit scale to convert model length values to metres (e.g. `0.001`
56    /// for millimetres). `None` when not yet computed; consumers treat it as
57    /// `1.0`. Brings the server to parity with the browser parser's
58    /// `extractLengthUnitScale` (issue #900).
59    #[serde(default, skip_serializing_if = "Option::is_none")]
60    pub length_unit_scale: Option<f64>,
61    /// Georeferencing (`IfcMapConversion` + `IfcProjectedCRS`). `None` when the
62    /// model carries no map-conversion data. Mirrors the browser parser's
63    /// `extractGeoreferencing` (issue #900).
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub georeferencing: Option<Georeferencing>,
66}
67
68/// Coordinate system information.
69#[derive(Debug, Clone, Default, Serialize, Deserialize)]
70pub struct CoordinateInfo {
71    /// Origin shift applied to coordinates (for RTC rendering).
72    pub origin_shift: [f64; 3],
73    /// Whether the model is geo-referenced.
74    pub is_geo_referenced: bool,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct QuickMetadataEntitySummary {
79    pub express_id: u32,
80    pub type_name: String,
81    pub name: String,
82    #[serde(skip_serializing_if = "Option::is_none")]
83    pub global_id: Option<String>,
84    pub kind: String,
85    pub has_children: bool,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub element_count: Option<usize>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub elevation: Option<f64>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
93pub struct QuickMetadataSpatialNode {
94    #[serde(flatten)]
95    pub summary: QuickMetadataEntitySummary,
96    pub children: Vec<QuickMetadataSpatialNode>,
97    pub elements: Vec<QuickMetadataEntitySummary>,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub struct QuickMetadataBootstrap {
102    pub schema_version: String,
103    pub entity_count: usize,
104    #[serde(skip_serializing_if = "Option::is_none")]
105    pub spatial_tree: Option<QuickMetadataSpatialNode>,
106    /// Child edges the tree walk skipped, in the order it met them (#4662).
107    #[serde(default, skip_serializing_if = "Vec::is_empty")]
108    pub pruned_aggregate_edges: Vec<QuickMetadataPrunedEdge>,
109}
110
111/// A spatial child edge left out of [`QuickMetadataBootstrap::spatial_tree`].
112/// Each node is placed where the depth-first walk from the root first reaches
113/// it; every later `IfcRelAggregates` edge to it is recorded here, as is every
114/// child edge the walk would follow out of a node at the depth limit. A spatial
115/// element an `IfcRelContainedInSpatialStructure` promotes to a child is not an
116/// aggregate edge: skipping it because the node is already placed, or because
117/// the node is settled (see [`QuickMetadataPrunedEdgeKind::DepthLimit`]), is not
118/// recorded (#4689).
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120pub struct QuickMetadataPrunedEdge {
121    /// Express id of the node whose child list names the edge.
122    pub parent_express_id: u32,
123    /// Express id of the child the edge names.
124    pub child_express_id: u32,
125    pub kind: QuickMetadataPrunedEdgeKind,
126}
127
128/// Why a [`QuickMetadataPrunedEdge`] was left out.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131#[non_exhaustive]
132pub enum QuickMetadataPrunedEdgeKind {
133    /// The parent that placed the child names it again.
134    SiblingRepeat,
135    /// A different parent names a child already placed elsewhere.
136    SecondParent,
137    /// A node names itself or one of its own ancestors.
138    BackEdge,
139    /// The parent sits at the tree's depth limit, so its subtree is cut there
140    /// (#4689). The child still appears if the walk reaches it by another
141    /// edge it follows. A containment of a settled node is not followed, even
142    /// when the limit cuts the path that settles it: settled means aggregated
143    /// and reached from the root through aggregates and containments of nodes
144    /// no aggregate names.
145    DepthLimit,
146}
147
148/// Processing statistics.
149#[derive(Debug, Clone, Default, Serialize, Deserialize)]
150pub struct ProcessingStats {
151    /// Total number of meshes generated.
152    pub total_meshes: usize,
153    /// Total number of vertices.
154    pub total_vertices: usize,
155    /// Total number of triangles.
156    pub total_triangles: usize,
157    /// Time spent parsing entities (ms).
158    pub parse_time_ms: u64,
159    /// Time spent scanning entities and building initial job lists (ms).
160    pub entity_scan_time_ms: u64,
161    /// Time spent resolving lookups, styles, and optional metadata (ms).
162    pub lookup_time_ms: u64,
163    /// Time spent in geometry preprocessing before the extraction loop begins (ms).
164    pub preprocess_time_ms: u64,
165    /// Time spent processing geometry (ms).
166    pub geometry_time_ms: u64,
167    /// Total processing time (ms).
168    pub total_time_ms: u64,
169    /// Whether result was from cache.
170    pub from_cache: bool,
171    /// Total CSG boolean failures recorded during geometry extraction
172    /// (mirrors the browser console diagnostics — see `BoolFailureReason`).
173    #[serde(default)]
174    pub total_csg_failures: u64,
175    /// Number of distinct products with at least one CSG failure.
176    #[serde(default)]
177    pub products_with_failures: u64,
178    /// Triangles removed by the f32-collapse degenerate-triangle backstop
179    /// across the whole pass (see `element::build_mesh_data`). Non-zero means
180    /// the backstop engaged for this model.
181    #[serde(default)]
182    pub degenerate_triangles_dropped: u64,
183    /// CartesianPoints served from the per-worker point cache while meshing
184    /// faceted breps (`EntityDecoder::get_polyloop_coords_cached`). Non-zero
185    /// means the cross-element cache hoist memoized a shared point list rather
186    /// than re-parsing it per part.
187    #[serde(default)]
188    pub point_cache_hits: u64,
189    /// CartesianPoints parsed for the first time (point-cache misses) across the
190    /// faceted-brep pass. `hits / (hits + misses)` is the memoization rate.
191    #[serde(default)]
192    pub point_cache_misses: u64,
193    /// Wall time (ms) attributed to faceted-brep part meshing. Populated only in
194    /// `observability` native builds (`Instant` traps on wasm32); 0 otherwise.
195    #[serde(default)]
196    pub faceted_brep_time_ms: u64,
197    /// Full CSG / opening diagnostics (opening classification, per-reason failure
198    /// breakdown, per-host detail, silent rectangular no-ops, rect_fast engagement)
199    /// aggregated across the native geometry pass — the same `GeometryDiagnostics`
200    /// contract the WASM batch path surfaces, so a consumer sees one shape on both.
201    /// `None` when nothing diagnostic-worthy happened (no openings, no failures).
202    #[serde(default, skip_serializing_if = "Option::is_none")]
203    pub geometry_diagnostics: Option<ifc_lite_geometry::GeometryDiagnostics>,
204}