sgenoud/replicad

how to properly load the full wasm package from opencascade and/or see better errors

Closed this issue · 8 comments

I'm having trouble loading the full OC wasm. I've changed my worker like:

// import opencascadeWasm from 'replicad-opencascadejs/src/replicad_single.wasm?url';
import opencascadeWasm from 'opencascade.js/dist/opencascade.full.wasm?url';

and now I get the error:

comlink.js?v=2496509d:42 Uncaught (in promise) RuntimeError: Aborted(LinkError: WebAssembly.instantiate(): Import #68 module="a" function="oa": function import requires a callable). Build with -sASSERTIONS for more info.
    at abort (replicad-opencascade…s?v=2496509d:501:15)
    at replicad-opencascade…s?v=2496509d:578:11

Really, my end goal isn't necessarily to have the full wasm library, but just to get better errors than:

image

Which I thought the error message is so basic due to the note on the opencascadejs site, but I don't see the sDISABLE_EXCEPTION_CATCHING flag in the build config. Am I just doing something wrong for getting the errors?

DISABLING EXCEPTIONS GREATLY REDUCES FILE SIZE
For many applications, exception support is not required in OpenCascade.js and can be disabled. For a full build, this reduces the file size by ~45% and greatly improves performance (even if exceptions are never thrown). Pass -sDISABLE_EXCEPTION_CATCHING=1 in the emccFlags of your custom build to disable exceptions. Check out this custom build definition for an example.

I believe you gotta sent to url to the wasm file to your web worker from outside your web worker. (Make the url to the wasm file in your non-webworker then sent the result as a string to your webworker to use inside your webworker on startup).

E.g.
Non-webworker:

import opencascadeWasm from "opencascade.js/dist/opencascade.full.wasm?url";
import { generateUUID } from "three/src/math/MathUtils";
import { Request, ShapeId, ShapeMeshWithUVCoords } from "./Request";
import Worker from "./geometric_kernel_worker?worker";
import { Result, err, flatMapResult, mapResult, ok } from "../Result";
import { ReplicadMeshedEdges, ReplicadMeshedFaces } from "replicad-threejs-helper";
import { Transform3D } from "../../math/Transform3D";
import { Mesh3dState } from "../../general_designer/components/Mesh3dComponent";
import { Vec3 } from "../../math/Vec3";
import { Line3D } from "../../math/Line3D";
import { CircularArcState } from "../../general_designer/components/CircularArcComponent";
import { Plane3D } from "../../math/Plane3D";
import { Quaternion } from "../../math/Quaternion";

let callbacks = new Map<string, (result: any) => void>();

const worker = new Worker();
worker.addEventListener("message", (e) => {
    let response = e.data;
    let callback = callbacks.get(response.requestId);
    callbacks.delete(response.requestId);
    if (callback != undefined) {
        callback(response.result);
    }
});

function workerRemoteCall(request: Request): Promise<any> {
    return new Promise((resolve) => {
        callbacks.set(request.requestId, (result) => {
            resolve(result);
        });
        worker.postMessage(request);
    })
}

await workerRemoteCall({
    type: "setupOpenCascade",
    requestId: generateUUID(),
    params: {
        opencascadeWasm,
    }
});

Web worker:

import opencascade, * as OC from "opencascade.js/dist/opencascade.full";
import * as Pako from "pako";
import * as Replicad from "replicad";
import { getOC, setOC } from "replicad";
import { Request, ShapeId } from "./Request";
import { Result, err, ok } from "../Result";
import { generateUUID } from "three/src/math/MathUtils";
import { Quaternion } from "../../math/Quaternion";
import { Vec3 } from "../../math/Vec3";
import { deleteDocument, document_getShapesViaLabels, loadDocumentFromBase64String, newDocument, saveDocumentToBase64String } from "./worker/xcaf_document";
import { base64ToUInt8Array, ocErrorMessage, uint8ArrayToBase64 } from "./worker/util";
import { shapeFaces } from "./worker/shape_explorer";
import { Transform3D } from "../../math/Transform3D";
import { differenceSamePlaneFaces, doesIntersectSamePlaneFaces, flipFaceNormal, intersectLineWithFace, intersectSamePlaneFaces, intersectWithInfinitePrismFromFace, makeBox, makeFacesForLines, makeSolidFromFaces, shapeToMeshWithUVCoords, shapePoints, shapeSolids } from "./worker/worker_callbacks";

let opencascadeWasm: string | undefined = undefined;
// This is the logic to load the web assembly code into replicad
let loaded = false;
const init = async () => {
    if (loaded) return Promise.resolve(true);

    const OC = await (opencascade as any)({
        locateFile: () => opencascadeWasm,
    });

    loaded = true;
    setOC(OC);

    return true;
};

let shapesHeap = new Map<string, Replicad.AnyShape>();

self.addEventListener("message", async (e) => {
    let message = JSON.parse(JSON.stringify(e.data)) as Request;
    switch (message.type) {
        case "setupOpenCascade": {
            opencascadeWasm = message.params.opencascadeWasm;
            await init();
            self.postMessage({
                requestId: message.requestId,
                result: ok({}),
            });
            break;
        }

Please ignore extra imports, I'm doing copy paste on mobile phone and editing text is a bit hard.

Be warned the full version of opencascade is a 50Mb wasm file. It will be slow in production. You'll still need to make a custom build.

To convert that number you get into an error message:

export function ocErrorMessage(e: any): string {
    if (typeof e === "number") {
        let oc = getOC();
        const exceptionData = oc.OCJS.getStandard_FailureData(e);
        return exceptionData.GetMessageString();
    }
    return "" + e;
}

The function getStandard_FailureData is provided by the full opencascade.js as extra C++. You can use a try/catch block to get that error number.

See this file here how they add that extra C++ function down the bottom:

https://github.com/donalffons/opencascade.js/blob/master/builds/opencascade.full.yml

You really just need to add the same bit to the bottom of the custom build for replicad. Then you can get the error message from the pointer (number) to the error.

thx @clinuxrulz I'll take a look and see if I can get it working as you suggest!

no problem... if possible reach out for a custom build. I.E. edit replicad's yml file for their version and add the extra C++ code to the bottom to extract the error message from the pointer to the error (the number thrown in JS).

Sadly I could not custom build opencascade.js through the docker container on my computer. 16 GB of ram was not enough. Probably needed 32 GB. However I have had luck compiling opencascade official version to wasm using emscripten without the docker container, by writing my web-worker in C++ instead of TS and only embind-ing a single function for message passing into/out-of the webworker.

I really thought 16GB ram was plenty for coding tasks, but I guess that is not the case these days.

Good news! I have added a new custom build within opencascade-replicad that supports full exceptions. You can use it in that same way that you use the single build, as I do in the studio now

And the studio has been modified as well to allow for these errors. We will see if they are actually useful!

Capture d’écran 2024-07-06 à 17 08 28

@sgenoud amazing, thank you!