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        // The WASM binary uses wasm-opt --experimental-new-eh to convert
153        // Emscripten's legacy exceptions to the new (exnref) encoding.
154        config.wasm_exceptions(true);
155
156        let engine = Engine::new(&config)?;
157        let wasm_bytes = decompress_wasm()?;
158        let module = Module::new(&engine, &wasm_bytes)?;
159        let mut store = Store::new(&engine, ());
160        let linker = Linker::new(&engine);
161        let instance = linker.instantiate(&mut store, &module)?;
162
163        let memory = instance
164            .get_memory(&mut store, "memory")
165            .ok_or_else(|| OcctError::Memory("no memory export".to_owned()))?;
166
167        // Call occt_init
168        let init: TypedFunc<(), i32> = instance.get_typed_func(&mut store, "occt_init")?;
169        let result = init.call(&mut store, ())?;
170        if result != 0 {
171            return Err(OcctError::Memory("occt_init failed".to_owned()));
172        }
173
174        // Resolve all accessor functions
175        macro_rules! get_fn {
176            ($name:expr => $ret:ty) => {
177                instance.get_typed_func::<(), $ret>(&mut store, $name)?
178            };
179            ($name:expr, $param:ty => $ret:ty) => {
180                instance.get_typed_func::<$param, $ret>(&mut store, $name)?
181            };
182        }
183
184        let generated = GeneratedFuncs::resolve(&instance, &mut store)?;
185
186        Ok(Self {
187            memory,
188            instance,
189
190            fn_has_error: get_fn!("occt_has_error" => i32),
191            fn_get_error: get_fn!("occt_get_error" => i32),
192            fn_get_error_len: get_fn!("occt_get_error_len" => u32),
193            fn_alloc: get_fn!("occt_alloc", u32 => u32),
194            fn_free: get_fn!("occt_free", u32 => ()),
195
196            fn_get_string_result: get_fn!("occt_get_string_result" => i32),
197            fn_get_string_result_len: get_fn!("occt_get_string_result_len" => u32),
198            fn_get_vec_u32_result: get_fn!("occt_get_vec_u32_result" => i32),
199            fn_get_vec_u32_result_len: get_fn!("occt_get_vec_u32_result_len" => u32),
200            fn_get_vec_f64_result: get_fn!("occt_get_vec_f64_result" => i32),
201            fn_get_vec_f64_result_len: get_fn!("occt_get_vec_f64_result_len" => u32),
202            fn_get_vec_i32_result: get_fn!("occt_get_vec_i32_result" => i32),
203            fn_get_vec_i32_result_len: get_fn!("occt_get_vec_i32_result_len" => u32),
204
205            fn_get_bbox_xmin: get_fn!("occt_get_bbox_xmin" => f64),
206            fn_get_bbox_ymin: get_fn!("occt_get_bbox_ymin" => f64),
207            fn_get_bbox_zmin: get_fn!("occt_get_bbox_zmin" => f64),
208            fn_get_bbox_xmax: get_fn!("occt_get_bbox_xmax" => f64),
209            fn_get_bbox_ymax: get_fn!("occt_get_bbox_ymax" => f64),
210            fn_get_bbox_zmax: get_fn!("occt_get_bbox_zmax" => f64),
211
212            fn_get_mesh_positions: get_fn!("occt_get_mesh_positions" => i32),
213            fn_get_mesh_positions_len: get_fn!("occt_get_mesh_positions_len" => i32),
214            fn_get_mesh_normals: get_fn!("occt_get_mesh_normals" => i32),
215            fn_get_mesh_normals_len: get_fn!("occt_get_mesh_normals_len" => i32),
216            fn_get_mesh_indices: get_fn!("occt_get_mesh_indices" => i32),
217            fn_get_mesh_indices_len: get_fn!("occt_get_mesh_indices_len" => i32),
218            fn_get_mesh_face_groups: get_fn!("occt_get_mesh_face_groups" => i32),
219            fn_get_mesh_face_groups_len: get_fn!("occt_get_mesh_face_groups_len" => i32),
220
221            fn_get_mesh_batch_positions: get_fn!("occt_get_mesh_batch_positions" => i32),
222            fn_get_mesh_batch_positions_len: get_fn!("occt_get_mesh_batch_positions_len" => i32),
223            fn_get_mesh_batch_normals: get_fn!("occt_get_mesh_batch_normals" => i32),
224            fn_get_mesh_batch_normals_len: get_fn!("occt_get_mesh_batch_normals_len" => i32),
225            fn_get_mesh_batch_indices: get_fn!("occt_get_mesh_batch_indices" => i32),
226            fn_get_mesh_batch_indices_len: get_fn!("occt_get_mesh_batch_indices_len" => i32),
227            fn_get_mesh_batch_shape_offsets: get_fn!("occt_get_mesh_batch_shape_offsets" => i32),
228            fn_get_mesh_batch_shape_count: get_fn!("occt_get_mesh_batch_shape_count" => i32),
229
230            fn_get_edge_points: get_fn!("occt_get_edge_points" => i32),
231            fn_get_edge_points_len: get_fn!("occt_get_edge_points_len" => i32),
232            fn_get_edge_groups: get_fn!("occt_get_edge_groups" => i32),
233            fn_get_edge_groups_len: get_fn!("occt_get_edge_groups_len" => i32),
234
235            fn_get_nurbs_degree: get_fn!("occt_get_nurbs_degree" => i32),
236            fn_get_nurbs_rational: get_fn!("occt_get_nurbs_rational" => i32),
237            fn_get_nurbs_periodic: get_fn!("occt_get_nurbs_periodic" => i32),
238            fn_get_nurbs_knots: get_fn!("occt_get_nurbs_knots" => i32),
239            fn_get_nurbs_knots_len: get_fn!("occt_get_nurbs_knots_len" => u32),
240            fn_get_nurbs_multiplicities: get_fn!("occt_get_nurbs_multiplicities" => i32),
241            fn_get_nurbs_multiplicities_len: get_fn!("occt_get_nurbs_multiplicities_len" => u32),
242            fn_get_nurbs_poles: get_fn!("occt_get_nurbs_poles" => i32),
243            fn_get_nurbs_poles_len: get_fn!("occt_get_nurbs_poles_len" => u32),
244            fn_get_nurbs_weights: get_fn!("occt_get_nurbs_weights" => i32),
245            fn_get_nurbs_weights_len: get_fn!("occt_get_nurbs_weights_len" => u32),
246
247            fn_get_evo_result_id: get_fn!("occt_get_evo_result_id" => u32),
248            fn_get_evo_modified: get_fn!("occt_get_evo_modified" => i32),
249            fn_get_evo_modified_len: get_fn!("occt_get_evo_modified_len" => u32),
250            fn_get_evo_generated: get_fn!("occt_get_evo_generated" => i32),
251            fn_get_evo_generated_len: get_fn!("occt_get_evo_generated_len" => u32),
252            fn_get_evo_deleted: get_fn!("occt_get_evo_deleted" => i32),
253            fn_get_evo_deleted_len: get_fn!("occt_get_evo_deleted_len" => u32),
254
255            fn_get_proj_visible_outline: get_fn!("occt_get_proj_visible_outline" => u32),
256            fn_get_proj_visible_smooth: get_fn!("occt_get_proj_visible_smooth" => u32),
257            fn_get_proj_visible_sharp: get_fn!("occt_get_proj_visible_sharp" => u32),
258            fn_get_proj_hidden_outline: get_fn!("occt_get_proj_hidden_outline" => u32),
259            fn_get_proj_hidden_smooth: get_fn!("occt_get_proj_hidden_smooth" => u32),
260            fn_get_proj_hidden_sharp: get_fn!("occt_get_proj_hidden_sharp" => u32),
261
262            fn_get_label_info_label_id: get_fn!("occt_get_label_info_label_id" => i32),
263            fn_get_label_info_name: get_fn!("occt_get_label_info_name" => i32),
264            fn_get_label_info_name_len: get_fn!("occt_get_label_info_name_len" => u32),
265            fn_get_label_info_has_color: get_fn!("occt_get_label_info_has_color" => i32),
266            fn_get_label_info_r: get_fn!("occt_get_label_info_r" => f64),
267            fn_get_label_info_g: get_fn!("occt_get_label_info_g" => f64),
268            fn_get_label_info_b: get_fn!("occt_get_label_info_b" => f64),
269            fn_get_label_info_is_assembly: get_fn!("occt_get_label_info_is_assembly" => i32),
270            fn_get_label_info_is_component: get_fn!("occt_get_label_info_is_component" => i32),
271            fn_get_label_info_shape_id: get_fn!("occt_get_label_info_shape_id" => u32),
272
273            generated,
274            store,
275        })
276    }
277
278    // === Memory helpers ===
279
280    /// Write bytes into WASM linear memory via `occt_alloc`.
281    pub(crate) fn write_bytes(&mut self, data: &[u8]) -> OcctResult<u32> {
282        let ptr = self.fn_alloc.call(&mut self.store, data.len() as u32)?;
283        if ptr == 0 {
284            core::hint::cold_path();
285            return Err(OcctError::Memory("allocation failed".to_owned()));
286        }
287        let mem = self.memory.data_mut(&mut self.store);
288        let start = ptr as usize;
289        let end = start + data.len();
290        if end > mem.len() {
291            core::hint::cold_path();
292            return Err(OcctError::Memory("write out of bounds".to_owned()));
293        }
294        mem[start..end].copy_from_slice(data);
295        Ok(ptr)
296    }
297
298    /// Free previously allocated WASM memory.
299    pub(crate) fn free_bytes(&mut self, ptr: u32) -> OcctResult<()> {
300        self.fn_free.call(&mut self.store, ptr)?;
301        Ok(())
302    }
303
304    /// Read a byte slice from WASM memory.
305    fn read_bytes(&self, ptr: u32, len: u32) -> OcctResult<Vec<u8>> {
306        let mem = self.memory.data(&self.store);
307        let start = ptr as usize;
308        let end = start + len as usize;
309        if end > mem.len() {
310            core::hint::cold_path();
311            return Err(OcctError::Memory("read out of bounds".to_owned()));
312        }
313        Ok(mem[start..end].to_vec())
314    }
315
316    /// Read a typed slice from WASM memory as `f32` values.
317    fn read_f32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<f32>> {
318        let bytes = self.read_bytes(ptr, count * 4)?;
319        Ok(bytes
320            .chunks_exact(4)
321            .map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
322            .collect())
323    }
324
325    /// Read a typed slice from WASM memory as `u32` values.
326    fn read_u32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<u32>> {
327        let bytes = self.read_bytes(ptr, count * 4)?;
328        Ok(bytes
329            .chunks_exact(4)
330            .map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
331            .collect())
332    }
333
334    /// Read a typed slice from WASM memory as `i32` values.
335    fn read_i32_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<i32>> {
336        let bytes = self.read_bytes(ptr, count * 4)?;
337        Ok(bytes
338            .chunks_exact(4)
339            .map(|c| i32::from_le_bytes([c[0], c[1], c[2], c[3]]))
340            .collect())
341    }
342
343    /// Read a typed slice from WASM memory as `f64` values.
344    fn read_f64_slice(&self, ptr: u32, count: u32) -> OcctResult<Vec<f64>> {
345        let bytes = self.read_bytes(ptr, count * 8)?;
346        Ok(bytes
347            .chunks_exact(8)
348            .map(|c| f64::from_le_bytes([c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]]))
349            .collect())
350    }
351
352    // === Error handling ===
353
354    /// Check if the WASM module has a pending error and return it.
355    pub(crate) fn check_error(&mut self, operation: &str) -> OcctResult<()> {
356        let has_error = self.fn_has_error.call(&mut self.store, ())?;
357        if has_error != 0 {
358            // Errors are the exception, not the norm — this branch runs after
359            // every facade call. Hint to LLVM that it's cold so the happy path
360            // stays tight in the instruction cache. (stabilized in Rust 1.95)
361            core::hint::cold_path();
362            return Err(self.read_last_error(operation));
363        }
364        Ok(())
365    }
366
367    /// Read the last error message from the WASM module.
368    #[cold]
369    pub(crate) fn read_last_error(&mut self, operation: &str) -> OcctError {
370        let ptr = self.fn_get_error.call(&mut self.store, ()).unwrap_or(0);
371        let len = self.fn_get_error_len.call(&mut self.store, ()).unwrap_or(0);
372        let message = if ptr != 0 && len > 0 {
373            self.read_bytes(ptr as u32, len)
374                .ok()
375                .and_then(|b| String::from_utf8(b).ok())
376                .unwrap_or_else(|| "unknown error".to_owned())
377        } else {
378            "unknown error".to_owned()
379        };
380        OcctError::Operation {
381            operation: operation.to_owned(),
382            message,
383        }
384    }
385
386    // === Result buffer readers ===
387
388    /// Read a string result from the WASM string buffer.
389    pub(crate) fn read_string_result(&mut self) -> OcctResult<String> {
390        let ptr = self.fn_get_string_result.call(&mut self.store, ())?;
391        let len = self.fn_get_string_result_len.call(&mut self.store, ())?;
392        let bytes = self.read_bytes(ptr as u32, len)?;
393        String::from_utf8(bytes).map_err(|e| OcctError::Memory(e.to_string()))
394    }
395
396    /// Read a `Vec<u32>` result.
397    pub(crate) fn read_vec_u32_result(&mut self) -> OcctResult<Vec<u32>> {
398        let ptr = self.fn_get_vec_u32_result.call(&mut self.store, ())?;
399        let len = self.fn_get_vec_u32_result_len.call(&mut self.store, ())?;
400        self.read_u32_slice(ptr as u32, len)
401    }
402
403    /// Read a `Vec<f64>` result.
404    pub(crate) fn read_vec_f64_result(&mut self) -> OcctResult<Vec<f64>> {
405        let ptr = self.fn_get_vec_f64_result.call(&mut self.store, ())?;
406        let len = self.fn_get_vec_f64_result_len.call(&mut self.store, ())?;
407        self.read_f64_slice(ptr as u32, len)
408    }
409
410    /// Read a `Vec<i32>` result.
411    pub(crate) fn read_vec_i32_result(&mut self) -> OcctResult<Vec<i32>> {
412        let ptr = self.fn_get_vec_i32_result.call(&mut self.store, ())?;
413        let len = self.fn_get_vec_i32_result_len.call(&mut self.store, ())?;
414        self.read_i32_slice(ptr as u32, len)
415    }
416
417    /// Read a bounding box result.
418    pub(crate) fn read_bbox_result(&mut self) -> OcctResult<BoundingBox> {
419        Ok(BoundingBox {
420            min: Vec3 {
421                x: self.fn_get_bbox_xmin.call(&mut self.store, ())?,
422                y: self.fn_get_bbox_ymin.call(&mut self.store, ())?,
423                z: self.fn_get_bbox_zmin.call(&mut self.store, ())?,
424            },
425            max: Vec3 {
426                x: self.fn_get_bbox_xmax.call(&mut self.store, ())?,
427                y: self.fn_get_bbox_ymax.call(&mut self.store, ())?,
428                z: self.fn_get_bbox_zmax.call(&mut self.store, ())?,
429            },
430        })
431    }
432
433    /// Read a mesh result.
434    pub(crate) fn read_mesh_result(&mut self) -> OcctResult<Mesh> {
435        let pos_ptr = self.fn_get_mesh_positions.call(&mut self.store, ())?;
436        let pos_len = self.fn_get_mesh_positions_len.call(&mut self.store, ())?;
437        let norm_ptr = self.fn_get_mesh_normals.call(&mut self.store, ())?;
438        let norm_len = self.fn_get_mesh_normals_len.call(&mut self.store, ())?;
439        let idx_ptr = self.fn_get_mesh_indices.call(&mut self.store, ())?;
440        let idx_len = self.fn_get_mesh_indices_len.call(&mut self.store, ())?;
441        let fg_ptr = self.fn_get_mesh_face_groups.call(&mut self.store, ())?;
442        let fg_len = self.fn_get_mesh_face_groups_len.call(&mut self.store, ())?;
443
444        Ok(Mesh {
445            positions: self.read_f32_slice(pos_ptr as u32, pos_len as u32)?,
446            normals: self.read_f32_slice(norm_ptr as u32, norm_len as u32)?,
447            indices: self.read_u32_slice(idx_ptr as u32, idx_len as u32)?,
448            face_groups: self.read_i32_slice(fg_ptr as u32, fg_len as u32)?,
449        })
450    }
451
452    /// Read a mesh batch result.
453    pub(crate) fn read_mesh_batch_result(&mut self) -> OcctResult<MeshBatch> {
454        let pos_ptr = self.fn_get_mesh_batch_positions.call(&mut self.store, ())?;
455        let pos_len = self
456            .fn_get_mesh_batch_positions_len
457            .call(&mut self.store, ())?;
458        let norm_ptr = self.fn_get_mesh_batch_normals.call(&mut self.store, ())?;
459        let norm_len = self
460            .fn_get_mesh_batch_normals_len
461            .call(&mut self.store, ())?;
462        let idx_ptr = self.fn_get_mesh_batch_indices.call(&mut self.store, ())?;
463        let idx_len = self
464            .fn_get_mesh_batch_indices_len
465            .call(&mut self.store, ())?;
466        let off_ptr = self
467            .fn_get_mesh_batch_shape_offsets
468            .call(&mut self.store, ())?;
469        let shape_count = self
470            .fn_get_mesh_batch_shape_count
471            .call(&mut self.store, ())?;
472
473        Ok(MeshBatch {
474            positions: self.read_f32_slice(pos_ptr as u32, pos_len as u32)?,
475            normals: self.read_f32_slice(norm_ptr as u32, norm_len as u32)?,
476            indices: self.read_u32_slice(idx_ptr as u32, idx_len as u32)?,
477            shape_offsets: self.read_i32_slice(off_ptr as u32, (shape_count * 4) as u32)?,
478        })
479    }
480
481    /// Read an edge data result.
482    pub(crate) fn read_edge_result(&mut self) -> OcctResult<EdgeData> {
483        let pts_ptr = self.fn_get_edge_points.call(&mut self.store, ())?;
484        let pts_len = self.fn_get_edge_points_len.call(&mut self.store, ())?;
485        let grp_ptr = self.fn_get_edge_groups.call(&mut self.store, ())?;
486        let grp_len = self.fn_get_edge_groups_len.call(&mut self.store, ())?;
487
488        Ok(EdgeData {
489            points: self.read_f32_slice(pts_ptr as u32, pts_len as u32)?,
490            edge_groups: self.read_i32_slice(grp_ptr as u32, grp_len as u32)?,
491        })
492    }
493
494    /// Read a NURBS curve data result.
495    pub(crate) fn read_nurbs_result(&mut self) -> OcctResult<NurbsCurveData> {
496        let degree = self.fn_get_nurbs_degree.call(&mut self.store, ())?;
497        let rational = self.fn_get_nurbs_rational.call(&mut self.store, ())? != 0;
498        let periodic = self.fn_get_nurbs_periodic.call(&mut self.store, ())? != 0;
499
500        let knots_ptr = self.fn_get_nurbs_knots.call(&mut self.store, ())?;
501        let knots_len = self.fn_get_nurbs_knots_len.call(&mut self.store, ())?;
502        let mult_ptr = self.fn_get_nurbs_multiplicities.call(&mut self.store, ())?;
503        let mult_len = self
504            .fn_get_nurbs_multiplicities_len
505            .call(&mut self.store, ())?;
506        let poles_ptr = self.fn_get_nurbs_poles.call(&mut self.store, ())?;
507        let poles_len = self.fn_get_nurbs_poles_len.call(&mut self.store, ())?;
508        let weights_ptr = self.fn_get_nurbs_weights.call(&mut self.store, ())?;
509        let weights_len = self.fn_get_nurbs_weights_len.call(&mut self.store, ())?;
510
511        Ok(NurbsCurveData {
512            degree,
513            rational,
514            periodic,
515            knots: self.read_f64_slice(knots_ptr as u32, knots_len)?,
516            multiplicities: self.read_i32_slice(mult_ptr as u32, mult_len)?,
517            poles: self.read_f64_slice(poles_ptr as u32, poles_len)?,
518            weights: self.read_f64_slice(weights_ptr as u32, weights_len)?,
519        })
520    }
521
522    /// Read an evolution data result.
523    pub(crate) fn read_evolution_result(&mut self) -> OcctResult<EvolutionData> {
524        let result_id = self.fn_get_evo_result_id.call(&mut self.store, ())?;
525        let mod_ptr = self.fn_get_evo_modified.call(&mut self.store, ())?;
526        let mod_len = self.fn_get_evo_modified_len.call(&mut self.store, ())?;
527        let gen_ptr = self.fn_get_evo_generated.call(&mut self.store, ())?;
528        let gen_len = self.fn_get_evo_generated_len.call(&mut self.store, ())?;
529        let del_ptr = self.fn_get_evo_deleted.call(&mut self.store, ())?;
530        let del_len = self.fn_get_evo_deleted_len.call(&mut self.store, ())?;
531
532        Ok(EvolutionData {
533            result_id,
534            modified: self.read_i32_slice(mod_ptr as u32, mod_len)?,
535            generated: self.read_i32_slice(gen_ptr as u32, gen_len)?,
536            deleted: self.read_i32_slice(del_ptr as u32, del_len)?,
537        })
538    }
539
540    /// Read a projection data result.
541    pub(crate) fn read_projection_result(&mut self) -> OcctResult<ProjectionData> {
542        Ok(ProjectionData {
543            visible_outline: ShapeHandle(
544                self.fn_get_proj_visible_outline.call(&mut self.store, ())?,
545            ),
546            visible_smooth: ShapeHandle(self.fn_get_proj_visible_smooth.call(&mut self.store, ())?),
547            visible_sharp: ShapeHandle(self.fn_get_proj_visible_sharp.call(&mut self.store, ())?),
548            hidden_outline: ShapeHandle(self.fn_get_proj_hidden_outline.call(&mut self.store, ())?),
549            hidden_smooth: ShapeHandle(self.fn_get_proj_hidden_smooth.call(&mut self.store, ())?),
550            hidden_sharp: ShapeHandle(self.fn_get_proj_hidden_sharp.call(&mut self.store, ())?),
551        })
552    }
553
554    /// Read a label info result.
555    pub(crate) fn read_label_info_result(&mut self) -> OcctResult<LabelInfo> {
556        let label_id = self.fn_get_label_info_label_id.call(&mut self.store, ())?;
557        let name_ptr = self.fn_get_label_info_name.call(&mut self.store, ())?;
558        let name_len = self.fn_get_label_info_name_len.call(&mut self.store, ())?;
559        let name_bytes = self.read_bytes(name_ptr as u32, name_len)?;
560        let name = String::from_utf8(name_bytes).map_err(|e| OcctError::Memory(e.to_string()))?;
561
562        Ok(LabelInfo {
563            label_id,
564            name,
565            has_color: self.fn_get_label_info_has_color.call(&mut self.store, ())? != 0,
566            r: self.fn_get_label_info_r.call(&mut self.store, ())?,
567            g: self.fn_get_label_info_g.call(&mut self.store, ())?,
568            b: self.fn_get_label_info_b.call(&mut self.store, ())?,
569            is_assembly: self
570                .fn_get_label_info_is_assembly
571                .call(&mut self.store, ())?
572                != 0,
573            is_component: self
574                .fn_get_label_info_is_component
575                .call(&mut self.store, ())?
576                != 0,
577            shape_id: self.fn_get_label_info_shape_id.call(&mut self.store, ())?,
578        })
579    }
580}
581
582impl Drop for OcctKernel {
583    fn drop(&mut self) {
584        // Best-effort cleanup
585        if let Ok(destroy) = self
586            .instance
587            .get_typed_func::<(), ()>(&mut self.store, "occt_destroy")
588        {
589            let _ = destroy.call(&mut self.store, ());
590        }
591    }
592}
593
594/// Decompress the embedded brotli-compressed WASM binary.
595fn decompress_wasm() -> OcctResult<Vec<u8>> {
596    let mut output = Vec::new();
597    let mut input: &[u8] = WASM_BINARY;
598    brotli::BrotliDecompress(&mut input, &mut output)
599        .map_err(|e| OcctError::Memory(format!("brotli decompression failed: {e}")))?;
600    Ok(output)
601}