Skip to main content

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}