Skip to main content

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}