Skip to main content

brepkit_wasm/bindings/
io.rs

1//! File I/O (import/export) bindings.
2
3#![cfg(feature = "io")]
4#![allow(clippy::missing_errors_doc)]
5
6use wasm_bindgen::prelude::*;
7
8use crate::error::{WasmError, validate_positive};
9use crate::handles::solid_id_to_u32;
10use crate::helpers::TOL;
11use crate::kernel::BrepKernel;
12
13#[wasm_bindgen]
14impl BrepKernel {
15    // ── Export ─────────────────────────────────────────────────────
16
17    /// Export a solid to 3MF format (ZIP archive as bytes).
18    ///
19    /// Returns a `Uint8Array` in JavaScript containing the `.3mf` file.
20    ///
21    /// # Errors
22    ///
23    /// Returns an error if the solid handle is invalid or export fails.
24    #[wasm_bindgen(js_name = "export3mf")]
25    pub fn export_3mf(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
26        validate_positive(deflection, "deflection")?;
27        let solid_id = self.resolve_solid(solid)?;
28        let bytes = brepkit_io::threemf::write_threemf(&self.topo, &[solid_id], deflection)?;
29        Ok(bytes)
30    }
31
32    /// Export a solid to binary STL format.
33    ///
34    /// Returns a `Uint8Array` containing the `.stl` file.
35    ///
36    /// # Errors
37    ///
38    /// Returns an error if the solid handle is invalid or export fails.
39    #[wasm_bindgen(js_name = "exportStl")]
40    pub fn export_stl(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
41        validate_positive(deflection, "deflection")?;
42        let solid_id = self.resolve_solid(solid)?;
43        let bytes = brepkit_io::stl::writer::write_stl(
44            &self.topo,
45            &[solid_id],
46            deflection,
47            brepkit_io::stl::writer::StlFormat::Binary,
48        )?;
49        Ok(bytes)
50    }
51
52    /// Export a solid to STL ASCII format.
53    ///
54    /// Returns the ASCII STL as UTF-8 bytes.
55    ///
56    /// # Errors
57    ///
58    /// Returns an error if the solid handle is invalid or export fails.
59    #[wasm_bindgen(js_name = "exportStlAscii")]
60    pub fn export_stl_ascii(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
61        validate_positive(deflection, "deflection")?;
62        let solid_id = self.resolve_solid(solid)?;
63        let bytes = brepkit_io::stl::writer::write_stl(
64            &self.topo,
65            &[solid_id],
66            deflection,
67            brepkit_io::stl::writer::StlFormat::Ascii,
68        )?;
69        Ok(bytes)
70    }
71
72    /// Export a solid to OBJ format (UTF-8 string as bytes).
73    ///
74    /// # Errors
75    ///
76    /// Returns an error if the solid handle is invalid or tessellation fails.
77    #[wasm_bindgen(js_name = "exportObj")]
78    pub fn export_obj(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
79        validate_positive(deflection, "deflection")?;
80        let solid_id = self.resolve_solid(solid)?;
81        let obj_str = brepkit_io::obj::write_obj(&self.topo, &[solid_id], deflection)?;
82        Ok(obj_str.into_bytes())
83    }
84
85    /// Export a solid to glTF binary (.glb) format.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if the solid handle is invalid or tessellation fails.
90    #[wasm_bindgen(js_name = "exportGlb")]
91    pub fn export_glb(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
92        validate_positive(deflection, "deflection")?;
93        let solid_id = self.resolve_solid(solid)?;
94        let glb = brepkit_io::gltf::write_glb(&self.topo, &[solid_id], deflection)?;
95        Ok(glb)
96    }
97
98    /// Export a solid to PLY format (binary little-endian).
99    ///
100    /// # Errors
101    ///
102    /// Returns an error if the solid handle is invalid or tessellation fails.
103    #[wasm_bindgen(js_name = "exportPly")]
104    pub fn export_ply(&self, solid: u32, deflection: f64) -> Result<Vec<u8>, JsError> {
105        validate_positive(deflection, "deflection")?;
106        let solid_id = self.resolve_solid(solid)?;
107        let ply = brepkit_io::ply::write_ply(
108            &self.topo,
109            &[solid_id],
110            deflection,
111            brepkit_io::ply::writer::PlyFormat::BinaryLittleEndian,
112        )?;
113        Ok(ply)
114    }
115
116    // ── Import ──────────────────────────────────────────────────────
117
118    /// Import an OBJ file and return a solid handle.
119    ///
120    /// # Errors
121    ///
122    /// Returns an error if the file is malformed or mesh import fails.
123    #[wasm_bindgen(js_name = "importObj")]
124    pub fn import_obj(&mut self, data: &[u8]) -> Result<u32, JsError> {
125        let text = std::str::from_utf8(data).map_err(|e| WasmError::InvalidInput {
126            reason: format!("OBJ must be valid UTF-8: {e}"),
127        })?;
128        let mesh = brepkit_io::obj::read_obj(text)?;
129        let solid_id = brepkit_io::stl::import::import_mesh(self.topo_mut(), &mesh, 1e-7)?;
130        #[allow(clippy::cast_possible_truncation)]
131        Ok(solid_id.index() as u32)
132    }
133
134    /// Import a GLB (glTF binary) file and return a solid handle.
135    ///
136    /// # Errors
137    ///
138    /// Returns an error if the file is malformed or mesh import fails.
139    #[wasm_bindgen(js_name = "importGlb")]
140    pub fn import_glb(&mut self, data: &[u8]) -> Result<u32, JsError> {
141        let mesh = brepkit_io::gltf::read_glb(data)?;
142        let solid_id = brepkit_io::stl::import::import_mesh(self.topo_mut(), &mesh, 1e-7)?;
143        #[allow(clippy::cast_possible_truncation)]
144        Ok(solid_id.index() as u32)
145    }
146
147    /// Import an STL file (binary or ASCII) and return a solid handle.
148    ///
149    /// The mesh triangles are converted to planar B-Rep faces with
150    /// vertex merging.
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if the STL data is malformed or empty.
155    #[wasm_bindgen(js_name = "importStl")]
156    pub fn import_stl(&mut self, data: &[u8]) -> Result<u32, JsError> {
157        let mesh = brepkit_io::stl::reader::read_stl(data)?;
158        let solid_id = brepkit_io::stl::import::import_mesh(self.topo_mut(), &mesh, TOL)?;
159        Ok(solid_id_to_u32(solid_id))
160    }
161
162    /// Import a 3MF file and return solid handles.
163    ///
164    /// Returns handles for each object found in the 3MF archive.
165    ///
166    /// # Errors
167    ///
168    /// Returns an error if the 3MF data is malformed.
169    #[wasm_bindgen(js_name = "import3mf")]
170    pub fn import_3mf(&mut self, data: &[u8]) -> Result<Vec<u32>, JsError> {
171        let meshes = brepkit_io::threemf::reader::read_threemf(data)?;
172        let mut handles = Vec::new();
173        for mesh in &meshes {
174            let solid_id = brepkit_io::stl::import::import_mesh(self.topo_mut(), mesh, TOL)?;
175            handles.push(solid_id_to_u32(solid_id));
176        }
177        Ok(handles)
178    }
179
180    /// Import a triangle mesh from flat vertex/index arrays.
181    ///
182    /// `positions` is a flat `[x0,y0,z0, x1,y1,z1, ...]` array.
183    /// `indices` is a flat `[i0,i1,i2, i3,i4,i5, ...]` array of triangle
184    /// vertex indices. Returns a solid handle.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if the arrays are malformed or mesh import fails.
189    #[wasm_bindgen(js_name = "importIndexedMesh")]
190    pub fn import_indexed_mesh(
191        &mut self,
192        positions: &[f64],
193        indices: &[u32],
194    ) -> Result<u32, JsError> {
195        use brepkit_math::vec::Point3;
196
197        if !positions.len().is_multiple_of(3) {
198            return Err(WasmError::InvalidInput {
199                reason: format!(
200                    "positions length {} is not a multiple of 3",
201                    positions.len()
202                ),
203            }
204            .into());
205        }
206        if !indices.len().is_multiple_of(3) {
207            return Err(WasmError::InvalidInput {
208                reason: format!("indices length {} is not a multiple of 3", indices.len()),
209            }
210            .into());
211        }
212
213        let verts: Vec<Point3> = positions
214            .chunks_exact(3)
215            .map(|c| Point3::new(c[0], c[1], c[2]))
216            .collect();
217        let normals = Vec::new();
218
219        let mesh = brepkit_operations::tessellate::TriangleMesh {
220            positions: verts,
221            normals,
222            indices: indices.to_vec(),
223        };
224
225        let solid_id = brepkit_io::stl::import::import_mesh(self.topo_mut(), &mesh, TOL)?;
226        Ok(solid_id_to_u32(solid_id))
227    }
228
229    /// Export a solid to STEP AP203 format.
230    ///
231    /// Returns the STEP file as a UTF-8 encoded byte vector.
232    ///
233    /// # Errors
234    ///
235    /// Returns an error if the solid handle is invalid or export fails.
236    #[wasm_bindgen(js_name = "exportStep")]
237    pub fn export_step(&self, solid: u32) -> Result<Vec<u8>, JsError> {
238        let solid_id = self.resolve_solid(solid)?;
239        let step_str = brepkit_io::step::writer::write_step(&self.topo, &[solid_id])?;
240        Ok(step_str.into_bytes())
241    }
242
243    /// Import a STEP file and return solid handles.
244    ///
245    /// Returns handles for each solid found in the STEP file.
246    ///
247    /// # Errors
248    ///
249    /// Returns an error if the STEP data is malformed.
250    #[wasm_bindgen(js_name = "importStep")]
251    pub fn import_step(&mut self, data: &[u8]) -> Result<Vec<u32>, JsError> {
252        let text = std::str::from_utf8(data)
253            .map_err(|e| JsError::new(&format!("STEP data is not valid UTF-8: {e}")))?;
254        let solid_ids = brepkit_io::step::reader::read_step(text, self.topo_mut())?;
255        Ok(solid_ids.iter().map(|id| solid_id_to_u32(*id)).collect())
256    }
257
258    // ── IGES Import/Export ────────────────────────────────────────
259
260    /// Export a solid to IGES format.
261    ///
262    /// Returns the IGES file as a UTF-8 encoded byte vector.
263    ///
264    /// # Errors
265    ///
266    /// Returns an error if the solid handle is invalid or export fails.
267    #[wasm_bindgen(js_name = "exportIges")]
268    pub fn export_iges(&self, solid: u32) -> Result<Vec<u8>, JsError> {
269        let solid_id = self.resolve_solid(solid)?;
270        let iges_str = brepkit_io::iges::writer::write_iges(&self.topo, &[solid_id])?;
271        Ok(iges_str.into_bytes())
272    }
273
274    /// Import an IGES file and return solid handles.
275    ///
276    /// # Errors
277    ///
278    /// Returns an error if the IGES data is malformed.
279    #[wasm_bindgen(js_name = "importIges")]
280    pub fn import_iges(&mut self, data: &[u8]) -> Result<Vec<u32>, JsError> {
281        let text = std::str::from_utf8(data)
282            .map_err(|e| JsError::new(&format!("IGES data is not valid UTF-8: {e}")))?;
283        let solid_ids = brepkit_io::iges::reader::read_iges(text, self.topo_mut())?;
284        Ok(solid_ids.iter().map(|id| solid_id_to_u32(*id)).collect())
285    }
286
287    // ── Arena debug serialization ─────────────────────────────────
288
289    /// Serialize a solid's complete in-memory topology sub-arena to bytes.
290    ///
291    /// Captures every vertex, edge, wire, face, shell reachable from the
292    /// solid with byte-exact f64 values (no geometry re-derivation or
293    /// tolerance normalization). Unlike STEP/IGES export, this preserves the
294    /// kernel's exact in-memory state — intended for capturing live operands
295    /// and replaying them in a native Rust harness to reproduce
296    /// sub-ULP-sensitive boolean behavior.
297    ///
298    /// Returns a `Uint8Array` consumable by `brepkit_io::arena_io::deserialize_solid`.
299    ///
300    /// # Errors
301    ///
302    /// Returns an error if the solid handle is invalid or serialization fails.
303    #[wasm_bindgen(js_name = "serializeSolid")]
304    pub fn serialize_solid(&self, solid: u32) -> Result<Vec<u8>, JsError> {
305        let solid_id = self.resolve_solid(solid)?;
306        let bytes = brepkit_io::arena_io::serialize_solid(&self.topo, solid_id)?;
307        Ok(bytes)
308    }
309
310    /// Reconstruct a solid from a buffer produced by [`Self::serialize_solid`].
311    ///
312    /// # Errors
313    ///
314    /// Returns an error if the buffer is malformed or reconstruction fails.
315    #[wasm_bindgen(js_name = "deserializeSolid")]
316    pub fn deserialize_solid(&mut self, data: &[u8]) -> Result<u32, JsError> {
317        let solid_id = brepkit_io::arena_io::deserialize_solid(data, self.topo_mut())?;
318        Ok(solid_id_to_u32(solid_id))
319    }
320}