ifc_lite_wasm/api/extract_profiles.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//! WASM API: extract_profiles — exposes raw profile polygons for 2D projection.
6
7use super::IfcAPI;
8use wasm_bindgen::prelude::*;
9
10// ═══════════════════════════════════════════════════════════════════════════
11// JS-FRIENDLY TYPES
12// ═══════════════════════════════════════════════════════════════════════════
13
14/// A single profile entry – raw 2D polygon + world transform.
15///
16/// Profile points are in **local 2D profile space** (metres).
17/// Apply `transform` to `[x, y, 0, 1]` to get WebGL Y-up world coordinates.
18#[wasm_bindgen]
19pub struct ProfileEntryJs {
20 express_id: u32,
21 ifc_type: String,
22 outer_points: Vec<f32>,
23 hole_counts: Vec<u32>,
24 hole_points: Vec<f32>,
25 transform: [f32; 16],
26 extrusion_dir: [f32; 3],
27 extrusion_depth: f32,
28 model_index: u32,
29}
30
31#[wasm_bindgen]
32impl ProfileEntryJs {
33 /// Express ID of the building element.
34 #[wasm_bindgen(getter, js_name = expressId)]
35 pub fn express_id(&self) -> u32 {
36 self.express_id
37 }
38
39 /// IFC type name (e.g., `"IfcWall"`).
40 #[wasm_bindgen(getter, js_name = ifcType)]
41 pub fn ifc_type(&self) -> String {
42 self.ifc_type.clone()
43 }
44
45 /// Outer boundary: flat `[x0, y0, x1, y1, …]` in local profile space (metres).
46 #[wasm_bindgen(getter, js_name = outerPoints)]
47 pub fn outer_points(&self) -> js_sys::Float32Array {
48 js_sys::Float32Array::from(&self.outer_points[..])
49 }
50
51 /// Number of points per hole.
52 #[wasm_bindgen(getter, js_name = holeCounts)]
53 pub fn hole_counts(&self) -> js_sys::Uint32Array {
54 js_sys::Uint32Array::from(&self.hole_counts[..])
55 }
56
57 /// All hole points concatenated: `[x0, y0, x1, y1, …]` (metres).
58 #[wasm_bindgen(getter, js_name = holePoints)]
59 pub fn hole_points(&self) -> js_sys::Float32Array {
60 js_sys::Float32Array::from(&self.hole_points[..])
61 }
62
63 /// 4 × 4 column-major transform in WebGL Y-up world space.
64 /// `M * [x, y, 0, 1]ᵀ` gives the world position.
65 #[wasm_bindgen(getter)]
66 pub fn transform(&self) -> js_sys::Float32Array {
67 js_sys::Float32Array::from(&self.transform[..])
68 }
69
70 /// Extrusion direction `[dx, dy, dz]` in WebGL Y-up world space (unit vector).
71 #[wasm_bindgen(getter, js_name = extrusionDir)]
72 pub fn extrusion_dir(&self) -> js_sys::Float32Array {
73 js_sys::Float32Array::from(&self.extrusion_dir[..])
74 }
75
76 /// Extrusion depth (metres).
77 #[wasm_bindgen(getter, js_name = extrusionDepth)]
78 pub fn extrusion_depth(&self) -> f32 {
79 self.extrusion_depth
80 }
81
82 /// Model index for multi-model federation.
83 #[wasm_bindgen(getter, js_name = modelIndex)]
84 pub fn model_index(&self) -> u32 {
85 self.model_index
86 }
87}
88
89impl From<ifc_lite_geometry::ExtractedProfile> for ProfileEntryJs {
90 fn from(p: ifc_lite_geometry::ExtractedProfile) -> Self {
91 Self {
92 express_id: p.express_id,
93 ifc_type: p.ifc_type,
94 outer_points: p.outer_points,
95 hole_counts: p.hole_counts,
96 hole_points: p.hole_points,
97 transform: p.transform,
98 extrusion_dir: p.extrusion_dir,
99 extrusion_depth: p.extrusion_depth,
100 model_index: p.model_index,
101 }
102 }
103}
104
105// ═══════════════════════════════════════════════════════════════════════════
106// COLLECTION
107// ═══════════════════════════════════════════════════════════════════════════
108
109/// A collection of extracted profiles.
110#[wasm_bindgen]
111pub struct ProfileCollection {
112 entries: Vec<ProfileEntryJs>,
113}
114
115#[wasm_bindgen]
116impl ProfileCollection {
117 /// Number of profiles.
118 #[wasm_bindgen(getter)]
119 pub fn length(&self) -> usize {
120 self.entries.len()
121 }
122
123 /// Get profile at `index`. Returns `undefined` for out-of-bounds index.
124 pub fn get(&self, index: usize) -> Option<ProfileEntryJs> {
125 self.entries.get(index).map(|e| ProfileEntryJs {
126 express_id: e.express_id,
127 ifc_type: e.ifc_type.clone(),
128 outer_points: e.outer_points.clone(),
129 hole_counts: e.hole_counts.clone(),
130 hole_points: e.hole_points.clone(),
131 transform: e.transform,
132 extrusion_dir: e.extrusion_dir,
133 extrusion_depth: e.extrusion_depth,
134 model_index: e.model_index,
135 })
136 }
137}
138
139// ═══════════════════════════════════════════════════════════════════════════
140// IfcAPI METHOD
141// ═══════════════════════════════════════════════════════════════════════════
142
143#[wasm_bindgen]
144impl IfcAPI {
145 /// Extract raw profile polygons from all building elements with `IfcExtrudedAreaSolid`
146 /// representations.
147 ///
148 /// Returns a [`ProfileCollection`] whose entries each carry:
149 /// - A 2D polygon (outer + holes) in local profile space (metres)
150 /// - A 4 × 4 column-major transform in WebGL Y-up world space
151 /// - Extrusion direction (world space) and depth (metres)
152 ///
153 /// Use [`ProfileProjector`] (TypeScript) to convert these into `DrawingLine[]`
154 /// for clean projection without tessellation artifacts.
155 ///
156 /// ```javascript
157 /// const api = new IfcAPI();
158 /// const profiles = api.extractProfiles(ifcContent, 0);
159 /// console.log('Profiles:', profiles.length);
160 /// for (let i = 0; i < profiles.length; i++) {
161 /// const p = profiles.get(i);
162 /// console.log(p.ifcType, 'depth:', p.extrusionDepth);
163 /// }
164 /// ```
165 #[wasm_bindgen(js_name = extractProfiles)]
166 pub fn extract_profiles(&self, content: String, model_index: u32) -> ProfileCollection {
167 let raw = ifc_lite_geometry::extract_profiles(&content, model_index);
168 ProfileCollection {
169 entries: raw.into_iter().map(ProfileEntryJs::from).collect(),
170 }
171 }
172}