Skip to main content

occt_wasm/
kernel.rs

1//! WASM host implementation for the OCCT kernel.
2//!
3//! Manages the wasmtime `Engine`, `Store`, and `Instance`, and provides
4//! helper methods for memory transfer across the WASM boundary.
5
6use wasmtime::{Engine, Instance, Linker, Memory, Module, Store, TypedFunc};
7
8use crate::error::{OcctError, OcctResult};
9use crate::kernel_generated::GeneratedFuncs;
10use crate::types::{
11    BoundingBox, EdgeData, EvolutionData, LabelInfo, Mesh, MeshBatch, NurbsCurveData,
12    ProjectionData, ShapeHandle, Vec3,
13};
14
15/// Brotli-compressed WASM binary, embedded at compile time.
16///
17/// The uncompressed binary is ~21 MB; brotli brings it to ~4 MB.
18/// This is decompressed once during `OcctKernel::new()`.
19static WASM_BINARY: &[u8] = include_bytes!("occt-wasm.wasm.br");
20
21/// The OCCT CAD kernel, backed by a sandboxed WASM module.
22///
23/// Create an instance with [`OcctKernel::new()`], then call methods to
24/// create and manipulate shapes. All shapes live in an arena inside the
25/// WASM module and are referenced by [`ShapeHandle`].
26///
27/// # Example
28///
29/// ```no_run
30/// use occt_wasm::{OcctKernel, ShapeHandle};
31///
32/// let mut kernel = OcctKernel::new().unwrap();
33/// let box_shape = kernel.make_box(10.0, 20.0, 30.0).unwrap();
34/// let volume = kernel.get_volume(box_shape).unwrap();
35/// assert!((volume - 6000.0).abs() < 1.0);
36/// ```
37pub struct OcctKernel {
38    pub(crate) store: Store<()>,
39    pub(crate) instance: Instance,
40    pub(crate) memory: Memory,
41
42    // Lifecycle + error functions
43    pub(crate) fn_has_error: TypedFunc<(), i32>,
44    pub(crate) fn_get_error: TypedFunc<(), i32>,
45    pub(crate) fn_get_error_len: TypedFunc<(), u32>,
46
47    // Memory management
48    pub(crate) fn_alloc: TypedFunc<u32, u32>,
49    pub(crate) fn_free: TypedFunc<u32, ()>,
50
51    // Result buffer accessors
52    pub(crate) fn_get_string_result: TypedFunc<(), i32>,
53    pub(crate) fn_get_string_result_len: TypedFunc<(), u32>,
54    pub(crate) fn_get_vec_u32_result: TypedFunc<(), i32>,
55    pub(crate) fn_get_vec_u32_result_len: TypedFunc<(), u32>,
56    pub(crate) fn_get_vec_f64_result: TypedFunc<(), i32>,
57    pub(crate) fn_get_vec_f64_result_len: TypedFunc<(), u32>,
58    pub(crate) fn_get_vec_i32_result: TypedFunc<(), i32>,
59    pub(crate) fn_get_vec_i32_result_len: TypedFunc<(), u32>,
60
61    // BBox accessors
62    pub(crate) fn_get_bbox_xmin: TypedFunc<(), f64>,
63    pub(crate) fn_get_bbox_ymin: TypedFunc<(), f64>,
64    pub(crate) fn_get_bbox_zmin: TypedFunc<(), f64>,
65    pub(crate) fn_get_bbox_xmax: TypedFunc<(), f64>,
66    pub(crate) fn_get_bbox_ymax: TypedFunc<(), f64>,
67    pub(crate) fn_get_bbox_zmax: TypedFunc<(), f64>,
68
69    // Mesh accessors
70    pub(crate) fn_get_mesh_positions: TypedFunc<(), i32>,
71    pub(crate) fn_get_mesh_positions_len: TypedFunc<(), i32>,
72    pub(crate) fn_get_mesh_normals: TypedFunc<(), i32>,
73    pub(crate) fn_get_mesh_normals_len: TypedFunc<(), i32>,
74    pub(crate) fn_get_mesh_indices: TypedFunc<(), i32>,
75    pub(crate) fn_get_mesh_indices_len: TypedFunc<(), i32>,
76    pub(crate) fn_get_mesh_face_groups: TypedFunc<(), i32>,
77    pub(crate) fn_get_mesh_face_groups_len: TypedFunc<(), i32>,
78
79    // MeshBatch accessors
80    pub(crate) fn_get_mesh_batch_positions: TypedFunc<(), i32>,
81    pub(crate) fn_get_mesh_batch_positions_len: TypedFunc<(), i32>,
82    pub(crate) fn_get_mesh_batch_normals: TypedFunc<(), i32>,
83    pub(crate) fn_get_mesh_batch_normals_len: TypedFunc<(), i32>,
84    pub(crate) fn_get_mesh_batch_indices: TypedFunc<(), i32>,
85    pub(crate) fn_get_mesh_batch_indices_len: TypedFunc<(), i32>,
86    pub(crate) fn_get_mesh_batch_shape_offsets: TypedFunc<(), i32>,
87    pub(crate) fn_get_mesh_batch_shape_count: TypedFunc<(), i32>,
88
89    // Edge accessors
90    pub(crate) fn_get_edge_points: TypedFunc<(), i32>,
91    pub(crate) fn_get_edge_points_len: TypedFunc<(), i32>,
92    pub(crate) fn_get_edge_groups: TypedFunc<(), i32>,
93    pub(crate) fn_get_edge_groups_len: TypedFunc<(), i32>,
94
95    // NURBS accessors
96    pub(crate) fn_get_nurbs_degree: TypedFunc<(), i32>,
97    pub(crate) fn_get_nurbs_rational: TypedFunc<(), i32>,
98    pub(crate) fn_get_nurbs_periodic: TypedFunc<(), i32>,
99    pub(crate) fn_get_nurbs_knots: TypedFunc<(), i32>,
100    pub(crate) fn_get_nurbs_knots_len: TypedFunc<(), u32>,
101    pub(crate) fn_get_nurbs_multiplicities: TypedFunc<(), i32>,
102    pub(crate) fn_get_nurbs_multiplicities_len: TypedFunc<(), u32>,
103    pub(crate) fn_get_nurbs_poles: TypedFunc<(), i32>,
104    pub(crate) fn_get_nurbs_poles_len: TypedFunc<(), u32>,
105    pub(crate) fn_get_nurbs_weights: TypedFunc<(), i32>,
106    pub(crate) fn_get_nurbs_weights_len: TypedFunc<(), u32>,
107
108    // Evolution accessors
109    pub(crate) fn_get_evo_result_id: TypedFunc<(), u32>,
110    pub(crate) fn_get_evo_modified: TypedFunc<(), i32>,
111    pub(crate) fn_get_evo_modified_len: TypedFunc<(), u32>,
112    pub(crate) fn_get_evo_generated: TypedFunc<(), i32>,
113    pub(crate) fn_get_evo_generated_len: TypedFunc<(), u32>,
114    pub(crate) fn_get_evo_deleted: TypedFunc<(), i32>,
115    pub(crate) fn_get_evo_deleted_len: TypedFunc<(), u32>,
116
117    // Projection accessors
118    pub(crate) fn_get_proj_visible_outline: TypedFunc<(), u32>,
119    pub(crate) fn_get_proj_visible_smooth: TypedFunc<(), u32>,
120    pub(crate) fn_get_proj_visible_sharp: TypedFunc<(), u32>,
121    pub(crate) fn_get_proj_hidden_outline: TypedFunc<(), u32>,
122    pub(crate) fn_get_proj_hidden_smooth: TypedFunc<(), u32>,
123    pub(crate) fn_get_proj_hidden_sharp: TypedFunc<(), u32>,
124
125    // Label info accessors
126    pub(crate) fn_get_label_info_label_id: TypedFunc<(), i32>,
127    pub(crate) fn_get_label_info_name: TypedFunc<(), i32>,
128    pub(crate) fn_get_label_info_name_len: TypedFunc<(), u32>,
129    pub(crate) fn_get_label_info_has_color: TypedFunc<(), i32>,
130    pub(crate) fn_get_label_info_r: TypedFunc<(), f64>,
131    pub(crate) fn_get_label_info_g: TypedFunc<(), f64>,
132    pub(crate) fn_get_label_info_b: TypedFunc<(), f64>,
133    pub(crate) fn_get_label_info_is_assembly: TypedFunc<(), i32>,
134    pub(crate) fn_get_label_info_is_component: TypedFunc<(), i32>,
135    pub(crate) fn_get_label_info_shape_id: TypedFunc<(), u32>,
136
137    // Generated method handles
138    pub(crate) generated: GeneratedFuncs,
139}
140
141impl OcctKernel {
142    /// Create a new OCCT kernel instance.
143    ///
144    /// Decompresses the embedded WASM binary, compiles it with `wasmtime`,
145    /// and initializes the OCCT runtime. This takes ~100-500ms depending
146    /// on the platform.
147    #[allow(clippy::too_many_lines)]
148    pub fn new() -> OcctResult<Self> {
149        let mut config = wasmtime::Config::new();
150        config.wasm_simd(true);
151        config.wasm_tail_call(true);
152        config.wasm_relaxed_simd(true);
153        // The WASM binary uses wasm-opt --experimental-new-eh to convert
154        // Emscripten's legacy exceptions to the new (exnref) encoding.
155        config.wasm_exceptions(true);
156
157        let engine = Engine::new(&config)?;
158        let wasm_bytes = decompress_wasm()?;
159        let module = Module::new(&engine, &wasm_bytes)?;
160        let mut store = Store::new(&engine, ());
161        let linker = Linker::new(&engine);
162        let instance = linker.instantiate(&mut store, &module)?;
163
164        let memory = instance
165            .get_memory(&mut store, "memory")
166            .ok_or_else(|| OcctError::Memory("no memory export".to_owned()))?;
167
168        // Call occt_init
169        let init: TypedFunc<(), i32> = instance.get_typed_func(&mut store, "occt_init")?;
170        let result = init.call(&mut store, ())?;
171        if result != 0 {
172            return Err(OcctError::Memory("occt_init failed".to_owned()));
173        }
174
175        // Resolve all accessor functions
176        macro_rules! get_fn {
177            ($name:expr => $ret:ty) => {
178                instance.get_typed_func::<(), $ret>(&mut store, $name)?
179            };
180            ($name:expr, $param:ty => $ret:ty) => {
181                instance.get_typed_func::<$param, $ret>(&mut store, $name)?
182            };
183        }
184
185        let generated = GeneratedFuncs::resolve(&instance, &mut store)?;
186
187        Ok(Self {
188            memory,
189            instance,
190
191            fn_has_error: get_fn!("occt_has_error" => i32),
192            fn_get_error: get_fn!("occt_get_error" => i32),
193            fn_get_error_len: get_fn!("occt_get_error_len" => u32),
194            fn_alloc: get_fn!("occt_alloc", u32 => u32),
195            fn_free: get_fn!("occt_free", u32 => ()),
196
197            fn_get_string_result: get_fn!("occt_get_string_result" => i32),
198            fn_get_string_result_len: get_fn!("occt_get_string_result_len" => u32),
199            fn_get_vec_u32_result: get_fn!("occt_get_vec_u32_result" => i32),
200            fn_get_vec_u32_result_len: get_fn!("occt_get_vec_u32_result_len" => u32),
201            fn_get_vec_f64_result: get_fn!("occt_get_vec_f64_result" => i32),
202            fn_get_vec_f64_result_len: get_fn!("occt_get_vec_f64_result_len" => u32),
203            fn_get_vec_i32_result: get_fn!("occt_get_vec_i32_result" => i32),
204            fn_get_vec_i32_result_len: get_fn!("occt_get_vec_i32_result_len" => u32),
205
206            fn_get_bbox_xmin: get_fn!("occt_get_bbox_xmin" => f64),
207            fn_get_bbox_ymin: get_fn!("occt_get_bbox_ymin" => f64),
208            fn_get_bbox_zmin: get_fn!("occt_get_bbox_zmin" => f64),
209            fn_get_bbox_xmax: get_fn!("occt_get_bbox_xmax" => f64),
210            fn_get_bbox_ymax: get_fn!("occt_get_bbox_ymax" => f64),
211            fn_get_bbox_zmax: get_fn!("occt_get_bbox_zmax" => f64),
212
213            fn_get_mesh_positions: get_fn!("occt_get_mesh_positions" => i32),
214            fn_get_mesh_positions_len: get_fn!("occt_get_mesh_positions_len" => i32),
215            fn_get_mesh_normals: get_fn!("occt_get_mesh_normals" => i32),
216            fn_get_mesh_normals_len: get_fn!("occt_get_mesh_normals_len" => i32),
217            fn_get_mesh_indices: get_fn!("occt_get_mesh_indices" => i32),
218            fn_get_mesh_indices_len: get_fn!("occt_get_mesh_indices_len" => i32),
219            fn_get_mesh_face_groups: get_fn!("occt_get_mesh_face_groups" => i32),
220            fn_get_mesh_face_groups_len: get_fn!("occt_get_mesh_face_groups_len" => i32),
221
222            fn_get_mesh_batch_positions: get_fn!("occt_get_mesh_batch_positions" => i32),
223            fn_get_mesh_batch_positions_len: get_fn!("occt_get_mesh_batch_positions_len" => i32),
224            fn_get_mesh_batch_normals: get_fn!("occt_get_mesh_batch_normals" => i32),
225            fn_get_mesh_batch_normals_len: get_fn!("occt_get_mesh_batch_normals_len" => i32),
226            fn_get_mesh_batch_indices: get_fn!("occt_get_mesh_batch_indices" => i32),
227            fn_get_mesh_batch_indices_len: get_fn!("occt_get_mesh_batch_indices_len" => i32),
228            fn_get_mesh_batch_shape_offsets: get_fn!("occt_get_mesh_batch_shape_offsets" => i32),
229            fn_get_mesh_batch_shape_count: get_fn!("occt_get_mesh_batch_shape_count" => i32),
230
231            fn_get_edge_points: get_fn!("occt_get_edge_points" => i32),
232            fn_get_edge_points_len: get_fn!("occt_get_edge_points_len" => i32),
233            fn_get_edge_groups: get_fn!("occt_get_edge_groups" => i32),
234            fn_get_edge_groups_len: get_fn!("occt_get_edge_groups_len" => i32),
235
236            fn_get_nurbs_degree: get_fn!("occt_get_nurbs_degree" => i32),
237            fn_get_nurbs_rational: get_fn!("occt_get_nurbs_rational" => i32),
238            fn_get_nurbs_periodic: get_fn!("occt_get_nurbs_periodic" => i32),
239            fn_get_nurbs_knots: get_fn!("occt_get_nurbs_knots" => i32),
240            fn_get_nurbs_knots_len: get_fn!("occt_get_nurbs_knots_len" => u32),
241            fn_get_nurbs_multiplicities: get_fn!("occt_get_nurbs_multiplicities" => i32),
242            fn_get_nurbs_multiplicities_len: get_fn!("occt_get_nurbs_multiplicities_len" => u32),
243            fn_get_nurbs_poles: get_fn!("occt_get_nurbs_poles" => i32),
244            fn_get_nurbs_poles_len: get_fn!("occt_get_nurbs_poles_len" => u32),
245            fn_get_nurbs_weights: get_fn!("occt_get_nurbs_weights" => i32),
246            fn_get_nurbs_weights_len: get_fn!("occt_get_nurbs_weights_len" => u32),
247
248            fn_get_evo_result_id: get_fn!("occt_get_evo_result_id" => u32),
249            fn_get_evo_modified: get_fn!("occt_get_evo_modified" => i32),
250            fn_get_evo_modified_len: get_fn!("occt_get_evo_modified_len" => u32),
251            fn_get_evo_generated: get_fn!("occt_get_evo_generated" => i32),
252            fn_get_evo_generated_len: get_fn!("occt_get_evo_generated_len" => u32),
253            fn_get_evo_deleted: get_fn!("occt_get_evo_deleted" => i32),
254            fn_get_evo_deleted_len: get_fn!("occt_get_evo_deleted_len" => u32),
255
256            fn_get_proj_visible_outline: get_fn!("occt_get_proj_visible_outline" => u32),
257            fn_get_proj_visible_smooth: get_fn!("occt_get_proj_visible_smooth" => u32),
258            fn_get_proj_visible_sharp: get_fn!("occt_get_proj_visible_sharp" => u32),
259            fn_get_proj_hidden_outline: get_fn!("occt_get_proj_hidden_outline" => u32),
260            fn_get_proj_hidden_smooth: get_fn!("occt_get_proj_hidden_smooth" => u32),
261            fn_get_proj_hidden_sharp: get_fn!("occt_get_proj_hidden_sharp" => u32),
262
263            fn_get_label_info_label_id: get_fn!("occt_get_label_info_label_id" => i32),
264            fn_get_label_info_name: get_fn!("occt_get_label_info_name" => i32),
265            fn_get_label_info_name_len: get_fn!("occt_get_label_info_name_len" => u32),
266            fn_get_label_info_has_color: get_fn!("occt_get_label_info_has_color" => i32),
267            fn_get_label_info_r: get_fn!("occt_get_label_info_r" => f64),
268            fn_get_label_info_g: get_fn!("occt_get_label_info_g" => f64),
269            fn_get_label_info_b: get_fn!("occt_get_label_info_b" => f64),
270            fn_get_label_info_is_assembly: get_fn!("occt_get_label_info_is_assembly" => i32),
271            fn_get_label_info_is_component: get_fn!("occt_get_label_info_is_component" => i32),
272            fn_get_label_info_shape_id: get_fn!("occt_get_label_info_shape_id" => u32),
273
274            generated,
275            store,
276        })
277    }
278
279    // === Memory helpers ===
280
281    /// Write bytes into WASM linear memory via `occt_alloc`.
282    pub(crate) fn write_bytes(&mut self, data: &[u8]) -> OcctResult<u32> {
283        let ptr = self.fn_alloc.call(&mut self.store, data.len() as u32)?;
284        if ptr == 0 {
285            core::hint::cold_path();
286            return Err(OcctError::Memory("allocation failed".to_owned()));
287        }
288        let mem = self.memory.data_mut(&mut self.store);
289        let start = ptr as usize;
290        let end = start + data.len();
291        if end > mem.len() {
292            core::hint::cold_path();
293            return Err(OcctError::Memory("write out of bounds".to_owned()));
294        }
295        mem[start..end].copy_from_slice(data);
296        Ok(ptr)
297    }
298
299    /// Free previously allocated WASM memory.
300    pub(crate) fn free_bytes(&mut self, ptr: u32) -> OcctResult<()> {
301        self.fn_free.call(&mut self.store, ptr)?;
302        Ok(())
303    }
304
305    /// Read a byte slice from WASM memory.
306    fn read_bytes(&self, ptr: u32, len: u32) -> OcctResult<Vec<u8>> {
307        let mem = self.memory.data(&self.store);
308        let start = ptr as usize;
309        let end = start + len as usize;
310        if end > mem.len() {
311            core::hint::cold_path();
312            return Err(OcctError::Memory("read out of bounds".to_owned()));
313        }
314        Ok(mem[start..end].to_vec())
315    }
316
317    /// Read a typed slice from WASM memory as `f32` values.
318    fn read_f32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<f32>> {
319        let bytes = self.read_bytes(ptr, count * 4)?;
320        Ok(bytes
321            .chunks_exact(4)
322            .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
323            .collect())
324    }
325
326    /// Read a typed slice from WASM memory as `u32` values.
327    fn read_u32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<u32>> {
328        let bytes = self.read_bytes(ptr, count * 4)?;
329        Ok(bytes
330            .chunks_exact(4)
331            .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
332            .collect())
333    }
334
335    /// Read a typed slice from WASM memory as `i32` values.
336    fn read_i32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<i32>> {
337        let bytes = self.read_bytes(ptr, count * 4)?;
338        Ok(bytes
339            .chunks_exact(4)
340            .map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]))
341            .collect())
342    }
343
344    /// Read a typed slice from WASM memory as `f64` values.
345    fn read_f64_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<f64>> {
346        let bytes = self.read_bytes(ptr, count * 8)?;
347        Ok(bytes
348            .chunks_exact(8)
349            .map(|c| f64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]))
350            .collect())
351    }
352
353    // === Error handling ===
354
355    /// Check if the WASM module has a pending error and return it.
356    pub(crate) fn check_error(&mut self, operation: &str) -> OcctResult<()> {
357        let has_error = self.fn_has_error.call(&mut self.store, ())?;
358        if has_error != 0 {
359            // Errors are the exception, not the norm — this branch runs after
360            // every facade call. Hint to LLVM that it's cold so the happy path
361            // stays tight in the instruction cache. (stabilized in Rust 1.95)
362            core::hint::cold_path();
363            return Err(self.read_last_error(operation));
364        }
365        Ok(())
366    }
367
368    /// Read the last error message from the WASM module.
369    #[cold]
370    pub(crate) fn read_last_error(&mut self, operation: &str) -> OcctError {
371        let ptr = self.fn_get_error.call(&mut self.store, ()).unwrap_or(0);
372        let len = self.fn_get_error_len.call(&mut self.store, ()).unwrap_or(0);
373        let message = if ptr != 0 && len > 0 {
374            self.read_bytes(ptr as u32, len)
375                .ok()
376                .and_then(|b| String::from_utf8(b).ok())
377                .unwrap_or_else(|| "unknown error".to_owned())
378        } else {
379            "unknown error".to_owned()
380        };
381        OcctError::Operation {
382            operation: operation.to_owned(),
383            message,
384        }
385    }
386
387    // === Result buffer readers ===
388
389    /// Read a string result from the WASM string buffer.
390    pub(crate) fn read_string_result(&mut self) -> OcctResult<String> {
391        let ptr = self.fn_get_string_result.call(&mut self.store, ())?;
392        let len = self.fn_get_string_result_len.call(&mut self.store, ())?;
393        let bytes = self.read_bytes(ptr as u32, len)?;
394        String::from_utf8(bytes).map_err(|e| OcctError::Memory(e.to_string()))
395    }
396
397    /// Read a `Vec<u32>` result.
398    pub(crate) fn read_vec_u32_result(&mut self) -> OcctResult<Vec<u32>> {
399        let ptr = self.fn_get_vec_u32_result.call(&mut self.store, ())?;
400        let len = self.fn_get_vec_u32_result_len.call(&mut self.store, ())?;
401        self.read_u32_slice(ptr as u32, len)
402    }
403
404    /// Read a `Vec<f64>` result.
405    pub(crate) fn read_vec_f64_result(&mut self) -> OcctResult<Vec<f64>> {
406        let ptr = self.fn_get_vec_f64_result.call(&mut self.store, ())?;
407        let len = self.fn_get_vec_f64_result_len.call(&mut self.store, ())?;
408        self.read_f64_slice(ptr as u32, len)
409    }
410
411    /// Read a `Vec<i32>` result.
412    pub(crate) fn read_vec_i32_result(&mut self) -> OcctResult<Vec<i32>> {
413        let ptr = self.fn_get_vec_i32_result.call(&mut self.store, ())?;
414        let len = self.fn_get_vec_i32_result_len.call(&mut self.store, ())?;
415        self.read_i32_slice(ptr as u32, len)
416    }
417
418    /// Read a bounding box result.
419    pub(crate) fn read_bbox_result(&mut self) -> OcctResult<BoundingBox> {
420        Ok(BoundingBox {
421            min: Vec3 {
422                x: self.fn_get_bbox_xmin.call(&mut self.store, ())?,
423                y: self.fn_get_bbox_ymin.call(&mut self.store, ())?,
424                z: self.fn_get_bbox_zmin.call(&mut self.store, ())?,
425            },
426            max: Vec3 {
427                x: self.fn_get_bbox_xmax.call(&mut self.store, ())?,
428                y: self.fn_get_bbox_ymax.call(&mut self.store, ())?,
429                z: self.fn_get_bbox_zmax.call(&mut self.store, ())?,
430            },
431        })
432    }
433
434    /// Read a mesh result.
435    pub(crate) fn read_mesh_result(&mut self) -> OcctResult<Mesh> {
436        let pos_ptr = self.fn_get_mesh_positions.call(&mut self.store, ())?;
437        let pos_len = self.fn_get_mesh_positions_len.call(&mut self.store, ())?;
438        let norm_ptr = self.fn_get_mesh_normals.call(&mut self.store, ())?;
439        let norm_len = self.fn_get_mesh_normals_len.call(&mut self.store, ())?;
440        let idx_ptr = self.fn_get_mesh_indices.call(&mut self.store, ())?;
441        let idx_len = self.fn_get_mesh_indices_len.call(&mut self.store, ())?;
442        let fg_ptr = self.fn_get_mesh_face_groups.call(&mut self.store, ())?;
443        let fg_len = self.fn_get_mesh_face_groups_len.call(&mut self.store, ())?;
444
445        Ok(Mesh {
446            positions: self.read_f32_slice(pos_ptr as u32, pos_len as u32)?,
447            normals: self.read_f32_slice(norm_ptr as u32, norm_len as u32)?,
448            indices: self.read_u32_slice(idx_ptr as u32, idx_len as u32)?,
449            face_groups: self.read_i32_slice(fg_ptr as u32, fg_len as u32)?,
450        })
451    }
452
453    /// Read a mesh batch result.
454    pub(crate) fn read_mesh_batch_result(&mut self) -> OcctResult<MeshBatch> {
455        let pos_ptr = self.fn_get_mesh_batch_positions.call(&mut self.store, ())?;
456        let pos_len = self
457            .fn_get_mesh_batch_positions_len
458            .call(&mut self.store, ())?;
459        let norm_ptr = self.fn_get_mesh_batch_normals.call(&mut self.store, ())?;
460        let norm_len = self
461            .fn_get_mesh_batch_normals_len
462            .call(&mut self.store, ())?;
463        let idx_ptr = self.fn_get_mesh_batch_indices.call(&mut self.store, ())?;
464        let idx_len = self
465            .fn_get_mesh_batch_indices_len
466            .call(&mut self.store, ())?;
467        let off_ptr = self
468            .fn_get_mesh_batch_shape_offsets
469            .call(&mut self.store, ())?;
470        let shape_count = self
471            .fn_get_mesh_batch_shape_count
472            .call(&mut self.store, ())?;
473
474        Ok(MeshBatch {
475            positions: self.read_f32_slice(pos_ptr as u32, pos_len as u32)?,
476            normals: self.read_f32_slice(norm_ptr as u32, norm_len as u32)?,
477            indices: self.read_u32_slice(idx_ptr as u32, idx_len as u32)?,
478            shape_offsets: self.read_i32_slice(off_ptr as u32, (shape_count * 4) as u32)?,
479        })
480    }
481
482    /// Read an edge data result.
483    pub(crate) fn read_edge_result(&mut self) -> OcctResult<EdgeData> {
484        let pts_ptr = self.fn_get_edge_points.call(&mut self.store, ())?;
485        let pts_len = self.fn_get_edge_points_len.call(&mut self.store, ())?;
486        let grp_ptr = self.fn_get_edge_groups.call(&mut self.store, ())?;
487        let grp_len = self.fn_get_edge_groups_len.call(&mut self.store, ())?;
488
489        Ok(EdgeData {
490            points: self.read_f32_slice(pts_ptr as u32, pts_len as u32)?,
491            edge_groups: self.read_i32_slice(grp_ptr as u32, grp_len as u32)?,
492        })
493    }
494
495    /// Read a NURBS curve data result.
496    pub(crate) fn read_nurbs_result(&mut self) -> OcctResult<NurbsCurveData> {
497        let degree = self.fn_get_nurbs_degree.call(&mut self.store, ())?;
498        let rational = self.fn_get_nurbs_rational.call(&mut self.store, ())? != 0;
499        let periodic = self.fn_get_nurbs_periodic.call(&mut self.store, ())? != 0;
500
501        let knots_ptr = self.fn_get_nurbs_knots.call(&mut self.store, ())?;
502        let knots_len = self.fn_get_nurbs_knots_len.call(&mut self.store, ())?;
503        let mult_ptr = self.fn_get_nurbs_multiplicities.call(&mut self.store, ())?;
504        let mult_len = self
505            .fn_get_nurbs_multiplicities_len
506            .call(&mut self.store, ())?;
507        let poles_ptr = self.fn_get_nurbs_poles.call(&mut self.store, ())?;
508        let poles_len = self.fn_get_nurbs_poles_len.call(&mut self.store, ())?;
509        let weights_ptr = self.fn_get_nurbs_weights.call(&mut self.store, ())?;
510        let weights_len = self.fn_get_nurbs_weights_len.call(&mut self.store, ())?;
511
512        Ok(NurbsCurveData {
513            degree,
514            rational,
515            periodic,
516            knots: self.read_f64_slice(knots_ptr as u32, knots_len)?,
517            multiplicities: self.read_i32_slice(mult_ptr as u32, mult_len)?,
518            poles: self.read_f64_slice(poles_ptr as u32, poles_len)?,
519            weights: self.read_f64_slice(weights_ptr as u32, weights_len)?,
520        })
521    }
522
523    /// Read an evolution data result.
524    pub(crate) fn read_evolution_result(&mut self) -> OcctResult<EvolutionData> {
525        let result_id = self.fn_get_evo_result_id.call(&mut self.store, ())?;
526        let mod_ptr = self.fn_get_evo_modified.call(&mut self.store, ())?;
527        let mod_len = self.fn_get_evo_modified_len.call(&mut self.store, ())?;
528        let gen_ptr = self.fn_get_evo_generated.call(&mut self.store, ())?;
529        let gen_len = self.fn_get_evo_generated_len.call(&mut self.store, ())?;
530        let del_ptr = self.fn_get_evo_deleted.call(&mut self.store, ())?;
531        let del_len = self.fn_get_evo_deleted_len.call(&mut self.store, ())?;
532
533        Ok(EvolutionData {
534            result_id,
535            modified: self.read_i32_slice(mod_ptr as u32, mod_len)?,
536            generated: self.read_i32_slice(gen_ptr as u32, gen_len)?,
537            deleted: self.read_i32_slice(del_ptr as u32, del_len)?,
538        })
539    }
540
541    /// Read a projection data result.
542    pub(crate) fn read_projection_result(&mut self) -> OcctResult<ProjectionData> {
543        Ok(ProjectionData {
544            visible_outline: ShapeHandle(
545                self.fn_get_proj_visible_outline.call(&mut self.store, ())?,
546            ),
547            visible_smooth: ShapeHandle(self.fn_get_proj_visible_smooth.call(&mut self.store, ())?),
548            visible_sharp: ShapeHandle(self.fn_get_proj_visible_sharp.call(&mut self.store, ())?),
549            hidden_outline: ShapeHandle(self.fn_get_proj_hidden_outline.call(&mut self.store, ())?),
550            hidden_smooth: ShapeHandle(self.fn_get_proj_hidden_smooth.call(&mut self.store, ())?),
551            hidden_sharp: ShapeHandle(self.fn_get_proj_hidden_sharp.call(&mut self.store, ())?),
552        })
553    }
554
555    /// Read a label info result.
556    pub(crate) fn read_label_info_result(&mut self) -> OcctResult<LabelInfo> {
557        let label_id = self.fn_get_label_info_label_id.call(&mut self.store, ())?;
558        let name_ptr = self.fn_get_label_info_name.call(&mut self.store, ())?;
559        let name_len = self.fn_get_label_info_name_len.call(&mut self.store, ())?;
560        let name_bytes = self.read_bytes(name_ptr as u32, name_len)?;
561        let name = String::from_utf8(name_bytes).map_err(|e| OcctError::Memory(e.to_string()))?;
562
563        Ok(LabelInfo {
564            label_id,
565            name,
566            has_color: self.fn_get_label_info_has_color.call(&mut self.store, ())? != 0,
567            r: self.fn_get_label_info_r.call(&mut self.store, ())?,
568            g: self.fn_get_label_info_g.call(&mut self.store, ())?,
569            b: self.fn_get_label_info_b.call(&mut self.store, ())?,
570            is_assembly: self
571                .fn_get_label_info_is_assembly
572                .call(&mut self.store, ())?
573                != 0,
574            is_component: self
575                .fn_get_label_info_is_component
576                .call(&mut self.store, ())?
577                != 0,
578            shape_id: self.fn_get_label_info_shape_id.call(&mut self.store, ())?,
579        })
580    }
581}
582
583impl Drop for OcctKernel {
584    fn drop(&mut self) {
585        // Best-effort cleanup
586        if let Ok(destroy) = self
587            .instance
588            .get_typed_func::<(), ()>(&mut self.store, "occt_destroy")
589        {
590            let _ = destroy.call(&mut self.store, ());
591        }
592    }
593}
594
595/// Decompress the embedded brotli-compressed WASM binary.
596fn decompress_wasm() -> OcctResult<Vec<u8>> {
597    let mut output = Vec::new();
598    let mut input: &[u8] = WASM_BINARY;
599    brotli::BrotliDecompress(&mut input, &mut output)
600        .map_err(|e| OcctError::Memory(format!("brotli decompression failed: {e}")))?;
601    Ok(output)
602}