ifc_lite_processing/symbolic/primitives.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
5use serde::{Deserialize, Serialize};
6
7// ────────────────────────────────────────────────────────────────────────────
8// Pure-Rust serializable primitive types. The wasm-bindgen wrappers in
9// `rust/wasm-bindings/src/zero_copy.rs` are thin views over these.
10// ────────────────────────────────────────────────────────────────────────────
11
12/// A single 2D polyline for symbolic representations.
13#[derive(Debug, Clone, Serialize, Deserialize)]
14pub struct SymbolicPolyline {
15 /// Express ID of the IFC entity that authored the curve.
16 pub express_id: u32,
17 /// Owning element's IFC type name.
18 pub ifc_type: String,
19 /// Flat 2D points `[x0, y0, x1, y1, …]` in metres.
20 pub points: Vec<f32>,
21 /// True if the curve is a closed loop.
22 pub closed: bool,
23 /// World-Y elevation captured from the placement chain or the
24 /// polyline's own 3D `IfcCartesianPoint` Z component.
25 pub world_y: f32,
26 /// Representation identifier (`Plan`, `Annotation`, `FootPrint`, `Axis`).
27 pub representation: String,
28}
29
30/// A single 2D circle / arc for symbolic representations.
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SymbolicCircle {
33 pub express_id: u32,
34 pub ifc_type: String,
35 pub center_x: f32,
36 pub center_y: f32,
37 pub radius: f32,
38 /// World-Y elevation (see [`SymbolicPolyline::world_y`]).
39 pub world_y: f32,
40 /// Start angle in radians (0 for full circle).
41 pub start_angle: f32,
42 /// End angle in radians (`TAU` for full circle).
43 pub end_angle: f32,
44 pub representation: String,
45}
46
47impl SymbolicCircle {
48 /// Full-circle constructor.
49 pub fn full(
50 express_id: u32,
51 ifc_type: String,
52 center_x: f32,
53 center_y: f32,
54 radius: f32,
55 world_y: f32,
56 representation: String,
57 ) -> Self {
58 Self {
59 express_id,
60 ifc_type,
61 center_x,
62 center_y,
63 radius,
64 world_y,
65 start_angle: 0.0,
66 end_angle: std::f32::consts::TAU,
67 representation,
68 }
69 }
70}
71
72/// A 2D text annotation (`IfcTextLiteral` / `IfcTextLiteralWithExtent`).
73#[derive(Debug, Clone, Serialize, Deserialize)]
74pub struct SymbolicText {
75 pub express_id: u32,
76 pub ifc_type: String,
77 /// Anchor point on the text baseline (model units).
78 pub x: f32,
79 pub y: f32,
80 /// Baseline orientation as a `(cos, sin)` pair. Defaults to `(1, 0)`.
81 pub dir_x: f32,
82 pub dir_y: f32,
83 /// Font height in model units (already unit-scaled).
84 pub height: f32,
85 /// UTF-8 text content (verbatim from the IFC literal — JS-side
86 /// decodes any `\X2\…\X0\` escape sequences).
87 pub content: String,
88 /// IFC `BoxAlignment` (`top-left`, `center`, `bottom-right`, …). Empty
89 /// string when absent.
90 pub alignment: String,
91 /// World-Y elevation (see [`SymbolicPolyline::world_y`]).
92 pub world_y: f32,
93 /// sRGB straight-alpha colour `[r, g, b, a]`. Defaults to dark-grey
94 /// when no IfcStyledItem chain resolves a colour.
95 pub color: [f32; 4],
96 /// Per-instance target screen-pixel cap height. `0.0` = renderer
97 /// global default (~14 px for body text).
98 pub target_px: f32,
99 pub representation: String,
100}
101
102/// A 2D filled region (`IfcAnnotationFillArea`).
103///
104/// Outer ring + optional inner rings (holes) packed into a single `points`
105/// buffer. `holes_offsets[i]` is the vertex index where hole `i` begins —
106/// outer ring spans `[0, holes_offsets[0])` (or all points when no holes).
107#[derive(Debug, Clone, Serialize, Deserialize)]
108pub struct SymbolicFillArea {
109 pub express_id: u32,
110 pub ifc_type: String,
111 /// All ring vertices: outer ring first, then each hole back-to-back.
112 /// Format: `[x0, y0, x1, y1, …]`.
113 pub points: Vec<f32>,
114 /// Inclusive prefix of where each hole begins (in vertex indices).
115 pub holes_offsets: Vec<u32>,
116 /// Fill colour sRGB, 0..1. Defaults to opaque black.
117 pub fill_color: [f32; 4],
118 /// Whether this fill carries a hatching style.
119 pub has_hatching: bool,
120 pub hatch_spacing: f32,
121 pub hatch_angle: f32,
122 /// Secondary cross-hatch angle. NaN if absent.
123 pub hatch_angle_secondary: f32,
124 pub hatch_line_width: f32,
125 pub world_y: f32,
126 pub representation: String,
127}
128
129/// A single `IfcGridAxis` tag + axis curve (server-friendly endpoint-pair
130/// representation; the wasm pipeline emits the same data via
131/// [`SymbolicPolyline`] axis lines and [`SymbolicText`] bubbles, both of
132/// which are also populated below).
133#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct SymbolicGridAxis {
135 pub express_id: u32,
136 pub grid_express_id: u32,
137 pub tag: String,
138 /// Endpoint pair `[x0, y0, x1, y1]` in metres (plan view).
139 pub endpoints: [f32; 4],
140 pub world_y: f32,
141}
142
143/// Server-friendly summary of the IFC's 2D symbol data.
144#[derive(Debug, Clone, Default, Serialize, Deserialize)]
145pub struct SymbolicData {
146 /// Axis endpoints for every `IfcGridAxis` (compact summary shape).
147 pub grid_axes: Vec<SymbolicGridAxis>,
148 /// All polylines (`IfcPolyline`, `IfcIndexedPolyCurve`, `IfcEllipse`
149 /// tessellations, `IfcTrimmedCurve` arcs, grid axis lines).
150 pub polylines: Vec<SymbolicPolyline>,
151 /// All circles (`IfcCircle` full disks).
152 pub circles: Vec<SymbolicCircle>,
153 /// All text annotations (`IfcTextLiteral`, grid bubble outlines + tags).
154 pub texts: Vec<SymbolicText>,
155 /// All filled regions (`IfcAnnotationFillArea`).
156 pub fills: Vec<SymbolicFillArea>,
157}
158
159impl SymbolicData {
160 /// Returns true if no symbolic primitives were extracted — the server
161 /// can omit the field from its response instead of emitting an empty
162 /// object.
163 pub fn is_empty(&self) -> bool {
164 self.grid_axes.is_empty()
165 && self.polylines.is_empty()
166 && self.circles.is_empty()
167 && self.texts.is_empty()
168 && self.fills.is_empty()
169 }
170}