Skip to content

Commit fbaf52c

Browse files
zaloclaude
andcommitted
Upgrade opencascade.js from v0.1.x (WebIDL) to v2 (Embind/OCCT 8.0)
Migrate all OpenCascade API calls to v2 Embind conventions: - Numbered constructor/method suffixes (e.g. gp_Pnt_3, BRepPrimAPI_MakeBox_2) - Scoped enums (TopAbs_ShapeEnum.TopAbs_FACE instead of TopAbs_FACE) - Static method calls (TopoDS.Face_1, BRep_Tool.Triangulation) - Handle upcasting via Handle_Geom_Curve_2 for BRepBuilderAPI_MakeEdge - Message_ProgressRange_1 parameter for Build/Transfer calls - Per-element Poly_Triangulation accessors (Node/UVNode/Triangle) - Direct Emscripten module factory initialization (no thenable hack) Custom WASM build (18MB vs 35MB) with only CascadeStudio's ~80 needed classes, built from zalo/opencascade.js#cascadestudio-v2. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent df2f8b6 commit fbaf52c

11 files changed

Lines changed: 4732 additions & 264 deletions

js/CADWorker/CascadeStudioFileUtils.js

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -69,18 +69,18 @@ class CascadeStudioFileIO {
6969

7070
var reader = null; let tempFilename = fileName.toLowerCase();
7171
if (tempFilename.endsWith(".step") || tempFilename.endsWith(".stp")) {
72-
reader = new oc.STEPControl_Reader();
72+
reader = new oc.STEPControl_Reader_1();
7373
} else if (tempFilename.endsWith(".iges") || tempFilename.endsWith(".igs")) {
74-
reader = new oc.IGESControl_Reader();
74+
reader = new oc.IGESControl_Reader_1();
7575
} else { console.error("opencascade.js can't parse this extension! (yet)"); }
7676

7777
let readResult = reader.ReadFile(fileName);
78-
if (readResult === 1) {
78+
if (readResult === oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
7979
console.log(fileName + " loaded successfully! Converting to OCC now...");
80-
reader.TransferRoots();
80+
reader.TransferRoots(new oc.Message_ProgressRange_1());
8181
let stepShape = reader.OneShape();
8282

83-
self.externalShapes[fileName] = new oc.TopoDS_Shape(stepShape);
83+
self.externalShapes[fileName] = stepShape;
8484
self.externalShapes[fileName].hash = self.stringToHash(fileName);
8585
console.log("Shape Import complete! Use sceneShapes.push(externalShapes['" + fileName + "']); to add it to the scene!");
8686

@@ -104,10 +104,10 @@ class CascadeStudioFileIO {
104104
if (reader.Read(readShape, fileName)) {
105105
console.log(fileName + " loaded successfully! Converting to OCC now...");
106106

107-
let solidSTL = new oc.BRepBuilderAPI_MakeSolid();
108-
solidSTL.Add(new oc.TopoDS_Shape(readShape));
107+
let solidSTL = new oc.BRepBuilderAPI_MakeSolid_1();
108+
solidSTL.Add(oc.TopoDS.Shell_1(readShape));
109109

110-
self.externalShapes[fileName] = new oc.TopoDS_Shape(solidSTL.Solid());
110+
self.externalShapes[fileName] = solidSTL.Solid();
111111
self.externalShapes[fileName].hash = self.stringToHash(fileName);
112112
console.log("Shape Import complete! Use sceneShapes.push(externalShapes['" + fileName + "']); to see it!");
113113

@@ -123,11 +123,11 @@ class CascadeStudioFileIO {
123123
/** Returns `currentShape` `.STEP` file content. */
124124
saveShapeSTEP(filename = "CascadeStudioPart.step") {
125125
let oc = self.oc;
126-
let writer = new oc.STEPControl_Writer();
127-
let transferResult = writer.Transfer(self.currentShape, 0);
128-
if (transferResult === 1) {
126+
let writer = new oc.STEPControl_Writer_1();
127+
let transferResult = writer.Transfer(self.currentShape, oc.STEPControl_StepModelType.STEPControl_AsIs, true, new oc.Message_ProgressRange_1());
128+
if (transferResult === oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
129129
let writeResult = writer.Write(filename);
130-
if (writeResult === 1) {
130+
if (writeResult === oc.IFSelect_ReturnStatus.IFSelect_RetDone) {
131131
let stepFileText = oc.FS.readFile("/" + filename, { encoding: "utf8" });
132132
oc.FS.unlink("/" + filename);
133133
return stepFileText;

js/CADWorker/CascadeStudioMainWorker.js

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,11 @@ class CascadeStudioWorker {
6262

6363
/** Asynchronously load all dependencies and initialize OpenCascade WASM. */
6464
async init() {
65-
let opencascade, opentype, potpack;
65+
let initOpenCascade, opentype, potpack;
6666

6767
try {
68-
const ocMod = await import('../../node_modules/opencascade.js/dist/opencascade.wasm.module.js');
69-
opencascade = ocMod.default;
68+
const ocMod = await import('../../node_modules/opencascade.js/dist/cascadestudio.js');
69+
initOpenCascade = ocMod.default;
7070
} catch(e) {
7171
postMessage({ type: "log", payload: "ERROR loading opencascade: " + e.message });
7272
throw e;
@@ -98,26 +98,15 @@ class CascadeStudioWorker {
9898
// Preload fonts available via Text3D
9999
this._loadFonts(opentype);
100100

101-
// Load the full OpenCascade WebAssembly Module
102-
// NOTE: Emscripten's Module object has a .then() method that returns itself,
103-
// creating an infinite thenable resolution loop with `await`. We use a proper
104-
// Promise with onRuntimeInitialized instead.
101+
// Load the OpenCascade WebAssembly Module (v2 Embind)
105102
try {
106-
const openCascade = await new Promise((resolve) => {
107-
new opencascade({
108-
locateFile(path) {
109-
if (path.endsWith('.wasm')) {
110-
return "../../node_modules/opencascade.js/dist/opencascade.wasm.wasm";
111-
}
112-
return path;
113-
},
114-
onRuntimeInitialized() {
115-
// Delete the Emscripten thenable to prevent infinite resolution
116-
// loop when this Module object is passed to Promise.resolve()
117-
delete this.then;
118-
resolve(this);
103+
const openCascade = await new initOpenCascade({
104+
locateFile(path) {
105+
if (path.endsWith('.wasm')) {
106+
return '../../node_modules/opencascade.js/dist/cascadestudio.wasm';
119107
}
120-
});
108+
return path;
109+
}
121110
});
122111

123112
// Register the "OpenCascade" WebAssembly Module under the shorthand "oc"
@@ -184,6 +173,7 @@ class CascadeStudioWorker {
184173
// Initialize currentShape as an empty Compound Solid
185174
self.currentShape = new oc.TopoDS_Compound();
186175
let sceneBuilder = new oc.BRep_Builder();
176+
// Note: BRep_Builder and TopoDS_Compound have no overloaded constructors in v2
187177
sceneBuilder.MakeCompound(self.currentShape);
188178
let fullShapeEdgeHashes = {}; let fullShapeFaceHashes = {};
189179
postMessage({ "type": "Progress", "payload": { "opNumber": self.opNumber++, "opType": "Combining Shapes" } });

js/CADWorker/CascadeStudioShapeToMesh.js

Lines changed: 67 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class CascadeStudioMesher {
1010
}
1111

1212
static lengthOfCurve(geomAdaptor, UMin, UMax, segments = 5) {
13-
let point1 = new Vector3(), point2 = new Vector3(), arcLength = 0, gpPnt = new self.oc.gp_Pnt();
13+
let point1 = new Vector3(), point2 = new Vector3(), arcLength = 0, gpPnt = new self.oc.gp_Pnt_1();
1414
for (let s = UMin; s <= UMax; s += (UMax - UMin) / segments) {
1515
geomAdaptor.D0(s, gpPnt);
1616
point1.set(gpPnt.X(), gpPnt.Y(), gpPnt.Z());
@@ -27,19 +27,23 @@ class CascadeStudioMesher {
2727
/** Iterate over all the faces in this shape, calling `callback` on each one. */
2828
static forEachFace(shape, callback) {
2929
let face_index = 0;
30-
let anExplorer = new self.oc.TopExp_Explorer(shape, self.oc.TopAbs_FACE);
31-
for (anExplorer.Init(shape, self.oc.TopAbs_FACE); anExplorer.More(); anExplorer.Next()) {
32-
callback(face_index++, self.oc.TopoDS.prototype.Face(anExplorer.Current()));
30+
let anExplorer = new self.oc.TopExp_Explorer_2(shape,
31+
self.oc.TopAbs_ShapeEnum.TopAbs_FACE, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
32+
for (anExplorer.Init(shape, self.oc.TopAbs_ShapeEnum.TopAbs_FACE,
33+
self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); anExplorer.More(); anExplorer.Next()) {
34+
callback(face_index++, self.oc.TopoDS.Face_1(anExplorer.Current()));
3335
}
3436
}
3537

3638
/** Iterate over all the UNIQUE indices and edges in this shape, calling `callback` on each one. */
3739
static forEachEdge(shape, callback) {
3840
let edgeHashes = {};
3941
let edgeIndex = 0;
40-
let anExplorer = new self.oc.TopExp_Explorer(shape, self.oc.TopAbs_EDGE);
41-
for (anExplorer.Init(shape, self.oc.TopAbs_EDGE); anExplorer.More(); anExplorer.Next()) {
42-
let edge = self.oc.TopoDS.prototype.Edge(anExplorer.Current());
42+
let anExplorer = new self.oc.TopExp_Explorer_2(shape,
43+
self.oc.TopAbs_ShapeEnum.TopAbs_EDGE, self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE);
44+
for (anExplorer.Init(shape, self.oc.TopAbs_ShapeEnum.TopAbs_EDGE,
45+
self.oc.TopAbs_ShapeEnum.TopAbs_SHAPE); anExplorer.More(); anExplorer.Next()) {
46+
let edge = self.oc.TopoDS.Edge_1(anExplorer.Current());
4347
let edgeHash = edge.HashCode(100000000);
4448
if (!edgeHashes.hasOwnProperty(edgeHash)) {
4549
edgeHashes[edgeHash] = edgeIndex;
@@ -53,19 +57,18 @@ class CascadeStudioMesher {
5357
let facelist = [], edgeList = [];
5458
try {
5559
let oc = self.oc;
56-
shape = new oc.TopoDS_Shape(shape);
5760

5861
// Set up the Incremental Mesh builder, with a precision
59-
new oc.BRepMesh_IncrementalMesh(shape, maxDeviation, false, maxDeviation * 5);
62+
new oc.BRepMesh_IncrementalMesh_2(shape, maxDeviation, false, maxDeviation * 5, false);
6063

6164
// Construct the edge hashes to assign proper indices to the edges
6265
let fullShapeEdgeHashes2 = {};
6366

6467
// Iterate through the faces and triangulate each one
6568
let triangulations = []; let uv_boxes = []; let curFace = 0;
6669
CascadeStudioMesher.forEachFace(shape, (faceIndex, myFace) => {
67-
let aLocation = new oc.TopLoc_Location();
68-
let myT = oc.BRep_Tool.prototype.Triangulation(myFace, aLocation);
70+
let aLocation = new oc.TopLoc_Location_1();
71+
let myT = oc.BRep_Tool.Triangulation(myFace, aLocation, 0 /* Poly_MeshPurpose_NONE */);
6972
if (myT.IsNull()) { console.error("Encountered Null Face!"); self.argCache = {}; return; }
7073

7174
let this_face = {
@@ -77,27 +80,27 @@ class CascadeStudioMesher {
7780
face_index: fullShapeFaceHashes[myFace.HashCode(100000000)]
7881
};
7982

80-
let pc = new oc.Poly_Connect(myT);
81-
let Nodes = myT.get().Nodes();
83+
let pc = new oc.Poly_Connect_2(myT);
84+
let nbNodes = myT.get().NbNodes();
8285

8386
// Write vertex buffer
84-
this_face.vertex_coord = new Array(Nodes.Length() * 3);
85-
for (let i = 0; i < Nodes.Length(); i++) {
86-
let p = Nodes.Value(i + 1).Transformed(aLocation.Transformation());
87-
this_face.vertex_coord[(i * 3) + 0] = p.X();
88-
this_face.vertex_coord[(i * 3) + 1] = p.Y();
89-
this_face.vertex_coord[(i * 3) + 2] = p.Z();
87+
this_face.vertex_coord = new Array(nbNodes * 3);
88+
for (let i = 1; i <= nbNodes; i++) {
89+
let p = myT.get().Node(i).Transformed(aLocation.Transformation());
90+
this_face.vertex_coord[((i - 1) * 3) + 0] = p.X();
91+
this_face.vertex_coord[((i - 1) * 3) + 1] = p.Y();
92+
this_face.vertex_coord[((i - 1) * 3) + 2] = p.Z();
9093
}
9194

9295
// Write UV buffer
93-
let orient = myFace.Orientation();
96+
let orient = myFace.Orientation_1();
9497
if (myT.get().HasUVNodes()) {
9598
let UMin = 0, UMax = 0, VMin = 0, VMax = 0;
9699

97-
let UVNodes = myT.get().UVNodes(), UVNodesLength = UVNodes.Length();
100+
let UVNodesLength = nbNodes;
98101
this_face.uv_coord = new Array(UVNodesLength * 2);
99102
for (let i = 0; i < UVNodesLength; i++) {
100-
let p = UVNodes.Value(i + 1);
103+
let p = myT.get().UVNode(i + 1);
101104
let x = p.X(), y = p.Y();
102105
this_face.uv_coord[(i * 2) + 0] = x;
103106
this_face.uv_coord[(i * 2) + 1] = y;
@@ -108,11 +111,11 @@ class CascadeStudioMesher {
108111
}
109112

110113
// Compute the Arclengths of the Isoparametric Curves of the face
111-
let surface = oc.BRep_Tool.prototype.Surface(myFace).get();
114+
let surface = oc.BRep_Tool.Surface_2(myFace).get();
112115
let UIso_Handle = surface.UIso(UMin + ((UMax - UMin) * 0.5));
113116
let VIso_Handle = surface.VIso(VMin + ((VMax - VMin) * 0.5));
114-
let UAdaptor = new oc.GeomAdaptor_Curve(VIso_Handle);
115-
let VAdaptor = new oc.GeomAdaptor_Curve(UIso_Handle);
117+
let UAdaptor = new oc.GeomAdaptor_Curve_2(VIso_Handle);
118+
let VAdaptor = new oc.GeomAdaptor_Curve_2(UIso_Handle);
116119
uv_boxes.push({
117120
w: CascadeStudioMesher.lengthOfCurve(UAdaptor, UMin, UMax),
118121
h: CascadeStudioMesher.lengthOfCurve(VAdaptor, VMin, VMax),
@@ -126,35 +129,34 @@ class CascadeStudioMesher {
126129

127130
x = ((x - UMin) / (UMax - UMin));
128131
y = ((y - VMin) / (VMax - VMin));
129-
if (orient !== oc.TopAbs_FORWARD) { x = 1.0 - x; }
132+
if (orient !== oc.TopAbs_Orientation.TopAbs_FORWARD) { x = 1.0 - x; }
130133

131134
this_face.uv_coord[(i * 2) + 0] = x;
132135
this_face.uv_coord[(i * 2) + 1] = y;
133136
}
134137
}
135138

136139
// Write normal buffer
137-
let myNormal = new oc.TColgp_Array1OfDir(Nodes.Lower(), Nodes.Upper());
138-
let SST = new oc.StdPrs_ToolTriangulatedShape();
139-
SST.Normal(myFace, pc, myNormal);
140-
this_face.normal_coord = new Array(myNormal.Length() * 3);
141-
for (let i = 0; i < myNormal.Length(); i++) {
140+
let myNormal = new oc.TColgp_Array1OfDir_2(1, nbNodes);
141+
oc.StdPrs_ToolTriangulatedShape.Normal(myFace, pc, myNormal);
142+
this_face.normal_coord = new Array(nbNodes * 3);
143+
for (let i = 0; i < nbNodes; i++) {
142144
let d = myNormal.Value(i + 1).Transformed(aLocation.Transformation());
143145
this_face.normal_coord[(i * 3) + 0] = d.X();
144146
this_face.normal_coord[(i * 3) + 1] = d.Y();
145147
this_face.normal_coord[(i * 3) + 2] = d.Z();
146148
}
147149

148150
// Write triangle buffer
149-
let triangles = myT.get().Triangles();
150-
this_face.tri_indexes = new Array(triangles.Length() * 3);
151+
let nbTriangles = myT.get().NbTriangles();
152+
this_face.tri_indexes = new Array(nbTriangles * 3);
151153
let validFaceTriCount = 0;
152-
for (let nt = 1; nt <= myT.get().NbTriangles(); nt++) {
153-
let t = triangles.Value(nt);
154+
for (let nt = 1; nt <= nbTriangles; nt++) {
155+
let t = myT.get().Triangle(nt);
154156
let n1 = t.Value(1);
155157
let n2 = t.Value(2);
156158
let n3 = t.Value(3);
157-
if (orient !== oc.TopAbs_FORWARD) {
159+
if (orient !== oc.TopAbs_Orientation.TopAbs_FORWARD) {
158160
let tmp = n1;
159161
n1 = n2;
160162
n2 = tmp;
@@ -176,15 +178,32 @@ class CascadeStudioMesher {
176178
edge_index: -1
177179
};
178180

179-
let myP = oc.BRep_Tool.prototype.PolygonOnTriangulation(myEdge, myT, aLocation);
180-
let edgeNodes = myP.get().Nodes();
181-
182-
this_edge.vertex_coord = new Array(edgeNodes.Length() * 3);
183-
for (let j = 0; j < edgeNodes.Length(); j++) {
184-
let vertexIndex = edgeNodes.Value(j + 1);
185-
this_edge.vertex_coord[(j * 3) + 0] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 0];
186-
this_edge.vertex_coord[(j * 3) + 1] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 1];
187-
this_edge.vertex_coord[(j * 3) + 2] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 2];
181+
try {
182+
let myP = oc.BRep_Tool.PolygonOnTriangulation_1(myEdge, myT, aLocation);
183+
if (!myP.IsNull()) {
184+
let edgeNodes = myP.get().Nodes();
185+
186+
this_edge.vertex_coord = new Array(edgeNodes.Length() * 3);
187+
for (let j = 0; j < edgeNodes.Length(); j++) {
188+
let vertexIndex = edgeNodes.Value(j + 1);
189+
this_edge.vertex_coord[(j * 3) + 0] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 0];
190+
this_edge.vertex_coord[(j * 3) + 1] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 1];
191+
this_edge.vertex_coord[(j * 3) + 2] = this_face.vertex_coord[((vertexIndex - 1) * 3) + 2];
192+
}
193+
} else {
194+
throw new Error("Null polygon on triangulation");
195+
}
196+
} catch (e) {
197+
// Fallback: discretize edge directly using BRepAdaptor_Curve
198+
let adaptorCurve = new oc.BRepAdaptor_Curve_2(myEdge);
199+
let tangDef = new oc.GCPnts_TangentialDeflection_2(adaptorCurve, maxDeviation, 0.1, 2, 1.0e-9, 1.0e-7);
200+
this_edge.vertex_coord = new Array(tangDef.NbPoints() * 3);
201+
for (let j = 0; j < tangDef.NbPoints(); j++) {
202+
let vertex = tangDef.Value(j + 1).Transformed(aLocation.Transformation());
203+
this_edge.vertex_coord[(j * 3) + 0] = vertex.X();
204+
this_edge.vertex_coord[(j * 3) + 1] = vertex.Y();
205+
this_edge.vertex_coord[(j * 3) + 2] = vertex.Z();
206+
}
188207
}
189208

190209
this_edge.edge_index = fullShapeEdgeHashes[edgeHash];
@@ -227,9 +246,9 @@ class CascadeStudioMesher {
227246
edge_index: -1
228247
};
229248

230-
let aLocation = new oc.TopLoc_Location();
231-
let adaptorCurve = new oc.BRepAdaptor_Curve(myEdge);
232-
let tangDef = new oc.GCPnts_TangentialDeflection(adaptorCurve, maxDeviation, 0.1);
249+
let aLocation = new oc.TopLoc_Location_1();
250+
let adaptorCurve = new oc.BRepAdaptor_Curve_2(myEdge);
251+
let tangDef = new oc.GCPnts_TangentialDeflection_2(adaptorCurve, maxDeviation, 0.1, 2, 1.0e-9, 1.0e-7);
233252

234253
this_edge.vertex_coord = new Array(tangDef.NbPoints() * 3);
235254
for (let j = 0; j < tangDef.NbPoints(); j++) {

0 commit comments

Comments
 (0)