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}