feat: wasm conversion engine — STEP write, OCCT isolation, IFC serialize (pyodide 0.29.4)#29
Merged
Conversation
backend.write_step (STEPCAFControl_Writer via OCCT XCAF) fatally aborted
the pyodide module with `RuntimeError: unreachable`. Two root causes:
1. step_writer.cpp / helpers.cpp were never compiled into the wasm side
module — the BUILD_WASM source list still carried the stale "OCCT not
available in wasm" set. write_shapes_to_step was therefore emitted as
an unresolved `env` import whose function-table slot is a trap stub,
so the first call trapped. GLB / STEP-read / make_box worked only
because they are self-contained in cad_py_wrap.cpp.
2. The OCAF document used "XmlOcaf", whose driver isn't registered under
OCCT_NO_PLUGINS (TKXml* isn't even linked for wasm). Switched to
BinXCAFDrivers::DefineFormat + a "BinXCAF" document (TKBinXCAF is
linked). write_shapes_to_step now also translates Standard_Failure
into a std::runtime_error so nanobind surfaces a Python exception
instead of std::terminate.
Requires the wasm toolchain to move to pyodide 0.29.4 / emscripten 4.0.9
/ Python 3.13 with native WebAssembly exception handling (the old
-fexceptions JS-trampoline model trapped in STEPCAFControl_Writer):
- OCCT + adacpp + nanobind all compile with -fwasm-exceptions
(+ -sSUPPORT_LONGJMP=wasm); OCC_CONVERT_SIGNALS dropped on wasm
(setjmp inside a catch is invalid under wasm-EH → "br_table: label
arity inconsistent" CompileError).
- wheel tag cp313 / pyodide_2025_0_wasm32; Python_SOABI cpython-313.
- xbuildenv path resolved dynamically via `pyodide config get` instead
of a machine-specific content hash (CI-portable).
- native test env + linux preset bumped to python 3.13 to match.
Verified: SAT -> {glb,obj,stl,xml,step} and FEM bake all succeed under
pyodide (node-pyodide harness); native suite 62 passed including
test_cad_write_step / test_basic_write_step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n + IFC BRep serialize)
The viewer's wasm engine loads adacpp AND the upstream ifcopenshell wheel in
one pyodide instance. Both statically link OCCT; with default visibility the
emscripten dynamic linker interposes OCCT symbols across the two copies,
corrupting OCCT's Standard_Type RTTI registry (a Geom_Line is misread and
GeomAdaptor_Curve::BSpline() throws Standard_NoSuchObject — every adacpp OCCT
op fails once ifcopenshell is loaded).
Fixes:
* Build OCCT with -fvisibility=hidden (cmake/wasm_occt.cmake). Hidden OCCT
symbols can't be interposed, so wasm-ld binds adacpp's OCCT references
DIRECTLY (no GOT imports) — OCCT becomes private + entirely intra-module.
This dodges BOTH the RTTI interposition AND emscripten's unreliable
cross-module virtual dispatch (#17907) that also defeats a shared-OCCT
side module. adacpp drops -sEXPORT_ALL=1 (it would re-export the hidden
OCCT and undo the isolation); --no-gc-sections keeps OCCT funcs defined
for intra-module vtable resolution. Adacpp OCCT exports: 7154 -> 0.
* serialize_brep CadBackend verb (BRepTools_ShapeSet text). adapy's IFC
tessellation fallback serialized shapes via ifcopenshell.geom.occ_utils,
which the ifcopenshell wasm wheel doesn't ship; routing through adacpp
emits the same BREP string for ifcopenshell.geom.serialise. Unblocks
sat/step -> ifc. The string crosses the module boundary as plain text,
so the two private OCCT copies still never interpose.
* Register a nanobind exception translator for OCCT Standard_Failure
(Standard_Transient, not std::exception) so OCCT errors surface as a
Python RuntimeError instead of an untranslatable SystemError/abort.
Verified (node-pyodide, all co-loaded with ifcopenshell): sat/step/mesh ->
{glb,obj,stl,step,xml}, sat -> ifc, ifc -> {glb,obj,stl,step,xml,ifc}, FEM
bake — all pass; adacpp standalone unaffected.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
👋 Hi there! I have checked your PR and found no issues. Thanks for your contribution! PR Review:I found no pr-related issues.
Python Review:I found no python-related issues. Python Linting results:
Python Packaging results: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Enables adacpp's full conversion surface under pyodide/WASM, coexisting with the upstream ifcopenshell wheel in one pyodide runtime. Three layers:
1. STEP write under wasm (pyodide 0.29.4 / native wasm-EH)
backend.write_step(STEPCAFControl_Writer) aborted the module (RuntimeError: unreachable). Root causes + fixes:step_writer.cpp/helpers.cppwere missing from theBUILD_WASMsource list →write_shapes_to_stepwas an unresolvedenvimport that trapped. Added them."XmlOcaf"(driver not registered underOCCT_NO_PLUGINS) → switched toBinXCAFDrivers::DefineFormat+"BinXCAF".-fwasm-exceptions, dropOCC_CONVERT_SIGNALS, cp313/pyodide_2025_0tags, dynamic xbuildenv path).2. OCCT isolation — coexist with ifcopenshell (the key fix)
adacpp and the ifcopenshell wasm wheel both statically link OCCT. With default visibility the emscripten linker interposes OCCT symbols across the two copies, corrupting OCCT's
Standard_TypeRTTI registry (GeomAdaptor_Curve::BSplinethrows once ifcopenshell is loaded — breaking every adacpp OCCT op in that session).Fix: build OCCT with
-fvisibility=hidden. Hidden symbols can't be interposed, so wasm-ld binds adacpp's OCCT refs directly (no GOT imports; adacpp OCCT exports 7154 → 0). OCCT becomes private + entirely intra-module, dodging both the RTTI interposition and emscripten's unreliable cross-module virtual dispatch (#17907) that also defeats a shared-OCCT side module. Dropped-sEXPORT_ALL=1(it would re-export the hidden OCCT). This keeps the battle-tested "static OCCT in one module" shape (as OCP.wasm/CadQuery do).3. IFC serialize + exception translation
serialize_brepCadBackend verb (BRepTools_ShapeSettext) so adapy's IFC tessellation fallback works withoutifcopenshell.geom.occ_utils(absent from the wasm wheel) — unblockssat/step → ifc. A BREP string crosses the module boundary, so the two private OCCT copies never interpose.Standard_Failure(which derives fromStandard_Transient, notstd::exception) → clean PythonRuntimeErrorinstead of an untranslatable abort.Verification (node-pyodide, all co-loaded with ifcopenshell in one instance)
sat/step/mesh → {glb,obj,stl,step,xml},sat → ifc,ifc → {glb,obj,stl,step,xml,ifc}, FEM bake — all ✅. adacpp standalone unaffected.CI note
Touches
cmake/wasm_occt.cmake→ merging triggers publish-occt-wasm-base to rebuild the OCCT base image with-fvisibility=hidden. Downstream wheel/ci-wasm-testsmust consume the new base; if they run before the rebuild finishes they'll need a re-run (the fallback builds OCCT from source, which is correct).🤖 Generated with Claude Code