diff --git a/.playwright-mcp/console-2026-05-12T03-39-43-633Z.log b/.playwright-mcp/console-2026-05-12T03-39-43-633Z.log
new file mode 100644
index 00000000..8f5c817d
--- /dev/null
+++ b/.playwright-mcp/console-2026-05-12T03-39-43-633Z.log
@@ -0,0 +1,6 @@
+[ 34278ms] [WARNING] The keyword 'slider-vertical' specified to an 'appearance' property is not standardized. It will be removed in the future. Use instead. @ http://127.0.0.1:5174/@vite/client:1069
+[ 34279ms] [WARNING] The keyword 'slider-vertical' specified to an 'appearance' property is not standardized. It will be removed in the future. Use instead. @ http://127.0.0.1:5174/@vite/client:1069
+[ 34389ms] [VERBOSE] [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) %o @ http://127.0.0.1:5174/index.html:0
+[ 39834ms] [WARNING] The keyword 'slider-vertical' specified to an 'appearance' property is not standardized. It will be removed in the future. Use instead. @ http://127.0.0.1:5174/@vite/client:1069
+[ 39835ms] [WARNING] The keyword 'slider-vertical' specified to an 'appearance' property is not standardized. It will be removed in the future. Use instead. @ http://127.0.0.1:5174/@vite/client:1069
+[ 39956ms] [VERBOSE] [DOM] Password field is not contained in a form: (More info: https://goo.gl/9p2vKq) %o @ http://127.0.0.1:5174/index.html:0
diff --git a/.playwright-mcp/console-2026-05-13T00-28-59-352Z.log b/.playwright-mcp/console-2026-05-13T00-28-59-352Z.log
new file mode 100644
index 00000000..e4267254
--- /dev/null
+++ b/.playwright-mcp/console-2026-05-13T00-28-59-352Z.log
@@ -0,0 +1,3 @@
+[ 6326155ms] [ERROR] Failed to load resource: the server responded with a status of 404 (Not Found) @ http://127.0.0.1:5174/src/BREP/faceThicken.js?t=1778638464606:0
+[ 7584527ms] The requested module '/src/BREP/OpenCascadeKernel.js?t=1778639722592' does not provide an export named 'getOccVisualizationQuality'
+[ 7587738ms] The requested module '/src/BREP/OpenCascadeKernel.js?t=1778639722592' does not provide an export named 'getOccVisualizationQuality'
diff --git a/.playwright-mcp/page-2026-05-12T03-39-46-662Z.yml b/.playwright-mcp/page-2026-05-12T03-39-46-662Z.yml
new file mode 100644
index 00000000..1926cbf1
--- /dev/null
+++ b/.playwright-mcp/page-2026-05-12T03-39-46-662Z.yml
@@ -0,0 +1 @@
+- generic "CAD sidebar" [ref=e2]
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-05-13T00-29-01-002Z.yml b/.playwright-mcp/page-2026-05-13T00-29-01-002Z.yml
new file mode 100644
index 00000000..1926cbf1
--- /dev/null
+++ b/.playwright-mcp/page-2026-05-13T00-29-01-002Z.yml
@@ -0,0 +1 @@
+- generic "CAD sidebar" [ref=e2]
\ No newline at end of file
diff --git a/README.md b/README.md
index 7a1bed4e..e63353d0 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,6 @@ Solid operations:
- [Fillet](docs/features/fillet.md)
- [Chamfer](docs/features/chamfer.md)
- [Hole](docs/features/hole.md)
-- [Push Face](docs/features/push-face.md)
- [Thicken](docs/features/thicken.md)
- [Offset Shell](docs/features/offset-shell.md)
- [Remesh](docs/features/remesh.md)
diff --git a/brep-kernel-class-stubs.js b/brep-kernel-class-stubs.js
index 63818733..144f0801 100644
--- a/brep-kernel-class-stubs.js
+++ b/brep-kernel-class-stubs.js
@@ -719,27 +719,17 @@ class Tube extends Solid {
}
generate() {
- // Generate the tube, preferring a fast native path and falling back to the slower native rebuild if needed.
+ // Generate the tube with the OpenCascade pipe builder.
return notImplemented();
}
- generateFast() {
- // Generate the tube using the fast native tube builder path.
- return notImplemented();
- }
-
- generateSlow() {
- // Generate the tube using the slower native path intended for harder or self-intersecting cases.
- return notImplemented();
- }
-
- buildNativeSnapshot(overrides = {}) {
- // Build the native authoring-state snapshot for the current tube parameters.
+ buildNativeSnapshot() {
+ // Build the OpenCascade authoring-state snapshot for the current tube parameters.
return notImplemented();
}
- generateNative(overrides = {}) {
- // Rebuild this tube directly from a native snapshot generated from its current parameters.
+ generateNative() {
+ // Rebuild this tube directly from its current OpenCascade pipe state.
return notImplemented();
}
}
diff --git a/docs/features/chamfer.md b/docs/features/chamfer.md
index c7e3f5eb..2dbd3a73 100644
--- a/docs/features/chamfer.md
+++ b/docs/features/chamfer.md
@@ -4,16 +4,12 @@ Status: Implemented

-Chamfer builds a bevel on the selected edges of a single solid by cutting (INSET) or unioning (OUTSET) helper solids generated by `BREP.ChamferSolid`.
+Chamfer builds a bevel on the selected edges of a single solid using the BREP kernel.
## Inputs
- `edges` – select edges directly or pick faces to gather all of their boundary edges.
- `distance` – the single offset distance used for both faces meeting at each edge.
-- `inflate` – optional expansion of the helper body to avoid sliver leftovers (small values like `0.0005` are typical).
-- `direction` – `INSET` subtracts material, `OUTSET` adds material. `AUTO` is reserved for internal use.
-- `debug` – keeps the intermediate helper solids visible for inspection.
## Behaviour
- All selected edges must belong to the same solid. When multiple faces are selected the feature expands them into unique edges.
-- The target solid is cloned, each helper body is booleaned in sequence, and the original solid is flagged for removal when the chamfer succeeds.
-- When `debug` is enabled, helper solids are left in the scene; otherwise only the finished solid is returned.
+- The target solid is replaced by the chamfered result when the operation succeeds.
diff --git a/docs/features/fillet.md b/docs/features/fillet.md
index e7e220de..05f4aa78 100644
--- a/docs/features/fillet.md
+++ b/docs/features/fillet.md
@@ -4,23 +4,12 @@ Status: Implemented

-Fillet replaces selected edges on a single solid with a constant-radius blend generated by `BREP.filletSolid`.
+Fillet replaces selected edges on a single solid with a constant-radius BREP blend.
## Inputs
- `edges` – pick edges directly or select faces to expand into their boundary edges.
- `radius` – constant radius applied to every edge.
-- `resolution` – number of segments around the fillet tube; increase for smoother large radii.
-- `inflate` – offsets tangency curves and end caps to avoid coplanar leftovers; closed loops skip the wedge inset to avoid self‑intersection.
-- `direction` – `AUTO` (default) classifies each selected edge as inside/outside and applies the corresponding boolean (`INSET` => subtract, `OUTSET` => union). You can still force `INSET` or `OUTSET`.
-- `patchFilletEndCaps` – enabled by default; moves eligible three-face tip points and patches selected end-cap triangles.
-- `smoothGeneratedEdges` – optional endpoint-constrained curve fitting on newly generated fillet boundary edges.
-- `cleanupTinyFaceIslandsArea` – area threshold for relabeling tiny disconnected face islands (`<= 0` disables).
-- `debug` – keeps helper bodies visible and logs extra diagnostics.
-- `showTangentOverlays` – adds pre-inflate tangency polylines to the helper tube for debugging.
## Behaviour
- All selected edges must belong to the same solid; face picks expand to boundary edges before the builder runs.
-- Helper tube/wedge bodies are generated per edge, grouped by resolved side, then applied in one unified pass: subtract all `INSET` tools and union all `OUTSET` tools.
-- In `AUTO`, edge side is decided per edge (signed-dihedral first, inside/outside probing as fallback) so mixed inside/outside selections can run in one feature.
-- Closed-loop paths suppress the wedge inset used for INSET cuts so the cutter does not self-intersect; tangency offsets still keep the fillet slightly inflated to avoid coplanar seams.
-- On success the original solid is replaced by the blended result. Enabling `debug` leaves helper solids (and optional tangent overlays) in the scene for inspection.
+- On success the original solid is replaced by the blended result.
diff --git a/docs/features/index.md b/docs/features/index.md
index 272863e8..ae88b9de 100644
--- a/docs/features/index.md
+++ b/docs/features/index.md
@@ -33,7 +33,6 @@ Reference docs for the built-in modeling features.
- [Primitive Pyramid](primitive-pyramid.md)
- [Primitive Sphere](primitive-sphere.md)
- [Primitive Torus](primitive-torus.md)
-- [Push Face](push-face.md)
- [Remesh](remesh.md)
- [Revolve](revolve.md)
- [Sheet Metal Contour Flange](sheet-metal-contour-flange.md)
diff --git a/docs/features/push-face.md b/docs/features/push-face.md
deleted file mode 100644
index f061d9fd..00000000
--- a/docs/features/push-face.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# Push Face
-
-Status: Implemented
-
-Push Face moves one or more selected faces on a solid by calling `solid.pushFace()` with the specified signed distance.
-
-## Inputs
-- `faces` – one or more face selections on the same solid.
-- `distance` – signed push distance along each selected face normal.
-- `id` – optional identifier for the history entry.
-
-## Behaviour
-- Resolves all selected faces to a single source solid.
-- Clones the source solid, pushes each selected face on the clone, and replaces the source solid in the scene.
-- Preserves the source solid name so downstream history references continue to resolve against the updated body.
diff --git a/docs/features/tube.md b/docs/features/tube.md
index e0bb1c35..682aec2f 100644
--- a/docs/features/tube.md
+++ b/docs/features/tube.md
@@ -10,13 +10,10 @@ Builds a tubular solid along one or more connected path edges, with optional hol
- `path` – one or more `EDGE` references that form the tube centerline.
- `radius` – outer radius (must be positive).
- `innerRadius` – optional hollow radius (`0` creates a solid tube).
-- `resolution` – segments around the circumference.
-- `mode` – `Light (fast)` or `Heavy (slow)` generation strategy.
-- `debug` – emits extra path/build diagnostics.
- `boolean` – optional union/subtract/intersect against selected solids.
## Behaviour
- Resolves and chains selected edge polylines into one or more connected paths.
-- Generates tube bodies using the selected build mode, trimming and capping open-path ends as needed.
-- Produces watertight outer/inner walls for hollow tubes when `innerRadius > 0`.
+- Generates tube bodies with OpenCascade `BRepOffsetAPI_MakePipe` from a swept circular profile face.
+- Produces hollow tubes by sweeping a single annular profile when `innerRadius > 0`.
- Applies the optional boolean operation through the shared helper and returns only resulting solids.
diff --git a/docs/workbenches/modeling.md b/docs/workbenches/modeling.md
index f5824fcf..88eaefe6 100644
--- a/docs/workbenches/modeling.md
+++ b/docs/workbenches/modeling.md
@@ -6,7 +6,7 @@ The Modeling workbench is the default workbench for new parts. It keeps the `+`
- Reference geometry: [Datium](../features/datium.md), [Plane](../features/plane.md)
- Primitives: [Cube](../features/primitive-cube.md), [Cylinder](../features/primitive-cylinder.md), [Cone](../features/primitive-cone.md), [Sphere](../features/primitive-sphere.md), [Torus](../features/primitive-torus.md), [Pyramid](../features/primitive-pyramid.md)
- Sketch-driven creation: [Sketch](../features/sketch.md), [Extrude](../features/extrude.md), [Revolve](../features/revolve.md), [Sweep](../features/sweep.md), [Tube](../features/tube.md)
-- Editing and booleans: [Boolean](../features/boolean.md), [Fillet](../features/fillet.md), [Chamfer](../features/chamfer.md), [Hole](../features/hole.md), [Push Face](../features/push-face.md)
+- Editing and booleans: [Boolean](../features/boolean.md), [Fillet](../features/fillet.md), [Chamfer](../features/chamfer.md), [Hole](../features/hole.md)
- Repetition and transforms: [Pattern Linear](../features/pattern-linear.md), [Pattern Radial](../features/pattern-radial.md), [Pattern (Legacy)](../features/pattern.md), [Transform](../features/transform.md), [Mirror](../features/mirror.md)
- Utility geometry: [Text to Face](../features/text-to-face.md), [Helix](../features/helix.md), [Spline](../features/spline.md)
diff --git a/spline-sketch-extrude-after-orientation.png b/spline-sketch-extrude-after-orientation.png
new file mode 100644
index 00000000..8db1f7bd
Binary files /dev/null and b/spline-sketch-extrude-after-orientation.png differ
diff --git a/src/BREP/BREP.js b/src/BREP/BREP.js
index e18d4f42..ce3b87e8 100644
--- a/src/BREP/BREP.js
+++ b/src/BREP/BREP.js
@@ -6,11 +6,7 @@ import { Revolve } from "./Revolve.js";
import { Tube } from "./Tube.js";
import { ChamferSolid } from "./chamfer.js";
import { ExtrudeSolid } from "./Extrude.js";
-import { computeFilletCenterlineForEdge } from "./CppSolidCore.js";
-import { filletSolid, attachFilletCenterlineAuxEdge } from "./fillets/fillet.js";
import { applyBooleanOperation } from "./applyBooleanOperation.js";
-import { MeshToBrep } from "./meshToBrep.js";
-import { MeshRepairer } from "./MeshRepairer.js";
import { AssemblyComponent } from "./AssemblyComponent.js";
import { buildHelixPolyline } from "./helix.js";
import * as THREE from 'three';
@@ -32,12 +28,7 @@ export const BREP = {
Revolve,
ExtrudeSolid,
ChamferSolid,
- filletSolid,
- computeFilletCenterline: computeFilletCenterlineForEdge,
- attachFilletCenterlineAuxEdge,
applyBooleanOperation,
- MeshToBrep,
- MeshRepairer,
AssemblyComponent,
buildHelixPolyline,
diff --git a/src/BREP/BetterSolid.js b/src/BREP/BetterSolid.js
index e96c86cb..76b780cc 100644
--- a/src/BREP/BetterSolid.js
+++ b/src/BREP/BetterSolid.js
@@ -1,61 +1,7 @@
-
/**
- * Solid: Authoring wrapper around manifold-3d Mesh/Manifold
- *
- * Requirements
- * - Environment: ES modules. For disk export (`writeSTL`) a Node.js runtime is required.
- * - Dependency: `setupManifold.js` must provide a ready-to-use `manifold` module
- * exposing `{ Manifold, Mesh }` from `manifold-3d`.
- * - Geometry: Input triangles must describe a closed, watertight, 2‑manifold.
- * The class includes helpers to fix triangle adjacency winding and will flip
- * the entire mesh if overall orientation is inward, but it cannot repair
- * topological holes or self-intersections.
- * - Vertex uniqueness: Vertices are uniqued by exact coordinate match
- * (string key of `x,y,z`). If you author with floating tolerances, you must
- * supply identical numeric values for shared vertices or change `_key()` to
- * implement a tolerance strategy.
- *
- * Theory of Operation
- * - Authoring model:
- * - Add triangles via `addTriangle(faceName, v1, v2, v3)`.
- * - Each triangle stores three vertex indices in `_triVerts` and a per‑triangle
- * face label ID in `_triIDs`. Face labels are mapped to globally unique
- * IDs from `Manifold.reserveIDs()` so provenance persists through CSG.
- * - Vertices are stored in `_vertProperties` in MeshGL layout `[x,y,z,...]`.
- *
- * - Manifold build (`_manifoldize`):
- * - Before building, `fixTriangleWindingsByAdjacency()` enforces opposite
- * orientation across shared edges; then a signed‑volume check flips all
- * triangles if the mesh is inward‑facing.
- * - A `Mesh` is constructed with `{ numProp, vertProperties, triVerts, faceID }`,
- * where `faceID` is the per‑triangle label array. `mesh.merge()` is called
- * to fill merge vectors when needed, then `new Manifold(mesh)` is created
- * and cached until the authoring arrays change.
- *
- * - Provenance & queries:
- * - After any boolean operation, Manifold propagates `faceID` so each output
- * triangle keeps the original face label. `getFace(name)` and `getFaces()`
- * read `mesh.faceID` to enumerate triangles by label (no planar grouping or
- * merging), which supports faces comprised of many non‑coplanar triangles
- * (e.g., cylinder side walls).
- *
- * - Boolean CSG:
- * - `union`, `subtract`, `intersect` call Manifold’s CSG APIs on the cached
- * Manifold objects and then rebuild a new Solid from the result. Face ID →
- * name maps from both inputs are merged so all original labels remain
- * available in the output.
- *
- * - Export:
- * - `toSTL()` returns an ASCII STL string from the current Manifold mesh.
- * - `writeSTL(path)` writes the STL to disk using a dynamic `fs` import so
- * the module stays browser‑safe.
- * - `toSTEP()` returns a triangulated STEP (faceted BREP) string.
- * - `writeSTEP(path)` writes the STEP file to disk in Node.js environments.
- *
- * Performance Notes
- * - Manifoldization is cached and only recomputed when authoring arrays change
- * (`_dirty`). Face queries iterate triangles and filter by `faceID`, which is
- * linear in triangle count.
+ * Solid: OpenCASCADE-backed CAD solid wrapper.
+ * Legacy triangle buffers remain only as compatibility data for metadata,
+ * visualization snapshots, and older serialized documents.
*/
import * as SolidMethods from "./SolidMethods/index.js";
import {
@@ -66,13 +12,12 @@ export { Edge, Face, Vertex } from "./SolidShared.js";
/**
* Solid
* - Add triangles with a face name.
- * - Data is stored in Manifold's MeshGL layout (vertProperties, triVerts, faceID).
- * - Face names are mapped to globally-unique Manifold IDs so they propagate through boolean ops.
- * - Query triangles for a given face name after any CSG by reading runs back from MeshGL.
+ * - OCCT state is authoritative for modeling operations.
+ * - Face names are mapped to stable IDs for visualization and metadata.
*/
export class Solid extends THREE.Group {
// Always reconstruct booleans as this base Solid (not subclasses) to avoid
- // re-running primitive generate() when rebuilding from Manifold.
+ // re-running primitive generate() when rebuilding from kernel results.
static BaseSolid = Solid;
/**
* Construct an empty Solid with authoring buffers, face/edge metadata, and aux-edge storage initialized.
@@ -83,7 +28,7 @@ export class Solid extends THREE.Group {
}
/**
- * Bake a Matrix4 into authored vertices (and aux edges/metadata), marking the manifold dirty.
+ * Bake a Matrix4 into authored vertices or OCCT state.
* @param {THREE.Matrix4|{elements:number[]}} matrix Matrix or matrix-like object
* @returns {Solid}
*/
@@ -119,7 +64,7 @@ export class Solid extends THREE.Group {
}
/**
- * Internal: map a face name to a globally unique Manifold ID (creates if missing).
+ * Internal: map a face name to a stable face ID (creates if missing).
* @param {string} faceName
* @returns {number}
*/
@@ -307,7 +252,7 @@ export class Solid extends THREE.Group {
}
/**
- * Flip all triangle windings to invert normals and rebuild the manifold.
+ * Removed legacy mesh operation.
* @returns {Solid}
*/
invertNormals(..._args) {
@@ -322,14 +267,6 @@ export class Solid extends THREE.Group {
return SolidMethods.fixTriangleWindingsByAdjacency.apply(this, arguments);
}
- /**
- * Check whether the authored mesh is a coherently oriented manifold.
- * @returns {boolean}
- */
- _isCoherentlyOrientedManifold(..._args) {
- return SolidMethods._isCoherentlyOrientedManifold.apply(this, arguments);
- }
-
/**
* Set vertex weld epsilon (<=0 disables) and optionally weld existing vertices.
* @param {number} [epsilon=0]
@@ -357,23 +294,14 @@ export class Solid extends THREE.Group {
}
/**
- * Build (or reuse) the cached Manifold from authored arrays, fixing winding/orientation first.
- * @returns {import('./SolidShared.js').Manifold}
- */
- _manifoldize(..._args) {
- return SolidMethods._manifoldize.apply(this, arguments);
- }
-
- /**
- * Get a fresh MeshGL snapshot (`vertProperties`, `triVerts`, `faceID`) from the manifold.
- * @returns {import('./SolidShared.js').ManifoldMesh}
+ * Get a visualization mesh snapshot from the OCCT shape.
*/
getMesh(..._args) {
return SolidMethods.getMesh.apply(this, arguments);
}
/**
- * Dispose the cached Manifold to free wasm memory and mark the solid dirty.
+ * Dispose cached resources and mark the solid dirty.
* @returns {Solid}
*/
free(..._args) {
@@ -406,7 +334,7 @@ export class Solid extends THREE.Group {
}
/**
- * Generate an ASCII STL string for the current manifold mesh.
+ * Generate an ASCII STL string from the current visualization mesh.
* @param {string} [name='solid']
* @param {number} [precision=6]
* @returns {string}
@@ -460,8 +388,8 @@ export class Solid extends THREE.Group {
* Build per-face meshes and boundary edges as children for visualization.
* @param {object} [options]
* @param {boolean} [options.showEdges=true] include boundary edge polylines
- * @param {boolean} [options.forceAuthoring=false] force authoring arrays instead of manifold mesh
- * @param {boolean} [options.authoringOnly=false] skip manifold path entirely (fallback visualization)
+ * @param {boolean} [options.forceAuthoring=false] force legacy authoring arrays
+ * @param {boolean} [options.authoringOnly=false] use only legacy authoring arrays
* @returns {void}
*/
visualize(..._args) {
@@ -494,25 +422,6 @@ export class Solid extends THREE.Group {
return SolidMethods._combineFaceMetadata.apply(this, arguments);
}
- /**
- * Static helper: expand face IDs from a MeshGL into a JS array (or zeros when absent).
- * @param {import('./SolidShared.js').ManifoldMesh} mesh
- * @returns {number[]}
- */
- static _expandTriIDsFromMesh(..._args) {
- return SolidMethods._expandTriIDsFromMeshStatic.apply(this, arguments);
- }
-
- /**
- * Static helper: build a Solid from an existing Manifold and ID -> face-name map.
- * @param {import('./SolidShared.js').Manifold} manifoldObj
- * @param {Map} idToFaceName
- * @returns {Solid}
- */
- static _fromManifold(..._args) {
- return SolidMethods._fromManifoldStatic.apply(this, arguments);
- }
-
/**
* Boolean union with another solid; merges face labels, metadata, and aux edges.
* @param {Solid} other
@@ -541,7 +450,7 @@ export class Solid extends THREE.Group {
}
/**
- * Boolean difference alias using Manifold.difference (same as subtract).
+ * Boolean difference alias.
* @param {Solid} other
* @returns {Solid}
*/
@@ -550,7 +459,7 @@ export class Solid extends THREE.Group {
}
/**
- * Simplify the manifold (optionally with tolerance and in-place update).
+ * Simplify the solid representation where supported.
* @param {number} [tolerance]
* @param {boolean} [updateInPlace] when true, mutate this solid instead of returning a clone
* @returns {Solid}
@@ -560,7 +469,7 @@ export class Solid extends THREE.Group {
}
/**
- * Rebuild with Manifold tolerance applied, returning a new Solid.
+ * Return a clone with tolerance metadata applied where supported.
* @param {number} tolerance
* @returns {Solid}
*/
@@ -569,7 +478,7 @@ export class Solid extends THREE.Group {
}
/**
- * Compute volume from the current manifold mesh.
+ * Compute volume from the current solid.
* @returns {number}
*/
volume(..._args) {
@@ -577,7 +486,7 @@ export class Solid extends THREE.Group {
}
/**
- * Compute total surface area from the current manifold mesh.
+ * Compute total surface area from the current solid.
* @returns {number}
*/
surfaceArea(..._args) {
@@ -585,7 +494,7 @@ export class Solid extends THREE.Group {
}
/**
- * Count triangles in the current manifold mesh.
+ * Count triangles in the current visualization mesh.
* @returns {number}
*/
getTriangleCount(..._args) {
@@ -610,7 +519,7 @@ export class Solid extends THREE.Group {
}
/**
- * Rebuild authoring arrays from the manifold’s exterior surface, dropping internal faces.
+ * Removed legacy mesh cleanup operation.
* @returns {number} triangles removed
*/
removeInternalTriangles(..._args) {
@@ -618,7 +527,7 @@ export class Solid extends THREE.Group {
}
/**
- * Remove internal triangles via centroid ray tests without needing manifoldization.
+ * Removed legacy mesh cleanup operation.
* @returns {number} triangles removed
*/
removeInternalTrianglesByRaycast(..._args) {
@@ -762,15 +671,14 @@ const __solidProfilingLogTiming = (prefix, methodName, phase, durationMs) => {
};
// --- Method-level time profiling for Solid -----------------------------------
-// Wrap all prototype methods (except constructor and _manifoldize, which is
-// already instrumented) to log execution time when debugMode is true, and
+// Wrap prototype methods to log execution time when debugMode is true, and
// always flag calls that exceed __solidSlowMethodThresholdMs.
(() => {
try {
if (Solid.__profiled) return;
Solid.__profiled = true;
const nowMs = () => (typeof performance !== 'undefined' && performance?.now ? performance.now() : Date.now());
- const skip = new Set(['constructor', '_manifoldize']);
+ const skip = new Set(['constructor']);
const proto = Solid.prototype;
for (const name of Object.getOwnPropertyNames(proto)) {
if (skip.has(name)) continue;
diff --git a/src/BREP/CppSolidCore.js b/src/BREP/CppSolidCore.js
index dd65b531..867dfaab 100644
--- a/src/BREP/CppSolidCore.js
+++ b/src/BREP/CppSolidCore.js
@@ -1,5 +1,5 @@
-import { Manifold } from "./SolidShared.js";
import { manifold } from "./setupManifold.js";
+import { reserveFaceID } from "./faceIdAllocator.js";
const parseMetadataJson = (raw) => {
if (!raw) return {};
@@ -471,7 +471,7 @@ const remapSnapshotFaceIDsToReservedRange = (snapshot) => {
const ensureReservedID = (rawId, fallbackName = "") => {
const key = Number(rawId);
if (idRemap.has(key)) return idRemap.get(key);
- const reservedID = Manifold.reserveIDs(1);
+ const reservedID = reserveFaceID();
idRemap.set(key, reservedID);
const faceName = String(sourceIDToFaceName.get(rawId) ?? fallbackName ?? "").trim() || `FACE_${reservedID}`;
remappedIDToFaceName.set(reservedID, faceName);
diff --git a/src/BREP/Edge.js b/src/BREP/Edge.js
index a6a288fb..9522023e 100644
--- a/src/BREP/Edge.js
+++ b/src/BREP/Edge.js
@@ -131,18 +131,6 @@ export class Edge extends Line2 {
}
solid._dirty = true;
solid._faceIndex = null;
- try {
- if (solid._manifold && typeof solid._manifold.delete === "function") {
- solid._manifold.delete();
- }
- } catch { /* ignore stale-manifold cleanup errors */ }
- solid._manifold = null;
-
- try {
- if (typeof solid._manifoldize === "function") solid._manifoldize();
- } catch (error) {
- console.warn(`[Edge.collapseToPoint] Manifold rebuild failed for edge "${this.name || "UNKNOWN"}":`, error?.message || error);
- }
try {
if (typeof solid.visualize === "function") solid.visualize();
} catch (error) {
diff --git a/src/BREP/OpenCascadeKernel.js b/src/BREP/OpenCascadeKernel.js
new file mode 100644
index 00000000..40e243a6
--- /dev/null
+++ b/src/BREP/OpenCascadeKernel.js
@@ -0,0 +1,3541 @@
+import { OpenCascade as oc } from "./setupOpenCascade.js";
+import {
+ DEFAULT_OCC_TRIANGULATION_ANGLE,
+ DEFAULT_OCC_TRIANGULATION_DEFLECTION,
+ DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES,
+ MAX_OCC_VISUALIZATION_CURVE_SAMPLES,
+} from "./occTriangulationSettings.js";
+
+const DEFAULT_DEFLECTION = DEFAULT_OCC_TRIANGULATION_DEFLECTION;
+const DEFAULT_ANGLE = DEFAULT_OCC_TRIANGULATION_ANGLE;
+const OCC_BOOLEAN_UNIFY_EDGES = true;
+const OCC_BOOLEAN_UNIFY_FACES = true;
+const OCC_BOOLEAN_UNIFY_ANGULAR_TOLERANCE = 1e-7;
+
+const cloneMap = (value) => new Map(value instanceof Map ? value.entries() : []);
+
+export function createOccState({ shape, faceNames = [], faceMetadata = new Map(), edgeMetadata = new Map(), ...rest } = {}) {
+ return {
+ ...rest,
+ shape,
+ faceNames: Array.from(faceNames || []),
+ faceMetadata: cloneMap(faceMetadata),
+ edgeMetadata: cloneMap(edgeMetadata),
+ faceNameByIndex: Array.isArray(rest.faceNameByIndex) ? Array.from(rest.faceNameByIndex) : null,
+ meshCache: null,
+ };
+}
+
+export function hasOccShape(solid) {
+ return !!solid?._occ?.shape;
+}
+
+export function setOccState(solid, state) {
+ solid._kernel = "opencascade";
+ solid._occ = createOccState(state);
+ solid._dirty = false;
+ solid._faceIndex = null;
+ solid._vertProperties = [];
+ solid._triVerts = [];
+ solid._triIDs = [];
+ solid._vertKeyToIndex = new Map();
+ solid._faceNameToID = new Map();
+ solid._idToFaceName = new Map();
+ solid._faceMetadata = cloneMap(solid._occ.faceMetadata);
+ solid._edgeMetadata = cloneMap(solid._occ.edgeMetadata);
+ for (const name of solid._occ.faceNames) solid._getOrCreateID(name);
+ return solid;
+}
+
+export function makeBox({ x = 1, y = 1, z = 1, name = "Cube" } = {}) {
+ const shape = new oc.BRepPrimAPI_MakeBox_1(x, y, z).Shape();
+ const faceNames = [`${name}_NX`, `${name}_PX`, `${name}_NY`, `${name}_PY`, `${name}_NZ`, `${name}_PZ`];
+ const state = createOccState({ shape, faceNames, primitive: { kind: "box", x, y, z, name } });
+ bindPrimitiveFaceNames(state);
+ return state;
+}
+
+function yAxisPlacement() {
+ return new oc.gp_Ax2_2(
+ new oc.gp_Pnt_3(0, 0, 0),
+ new oc.gp_Dir_4(0, 1, 0),
+ new oc.gp_Dir_4(1, 0, 0),
+ );
+}
+
+export function makeCylinder({ radius = 1, height = 1, name = "Cylinder" } = {}) {
+ const shape = new oc.BRepPrimAPI_MakeCylinder_4(yAxisPlacement(), radius, height, Math.PI * 2).Shape();
+ const faceNames = [`${name}_S`, `${name}_B`, `${name}_T`];
+ const faceMetadata = new Map([
+ [`${name}_S`, { type: "cylindrical", radius, height }],
+ ]);
+ const state = createOccState({ shape, faceNames, faceMetadata, primitive: { kind: "cylinder", radius, height, name } });
+ bindPrimitiveFaceNames(state);
+ return state;
+}
+
+export function makeCone({ r1 = 0.5, r2 = 1, h = 1, name = "Cone" } = {}) {
+ const shape = new oc.BRepPrimAPI_MakeCone_4(yAxisPlacement(), r2, r1, h, Math.PI * 2).Shape();
+ const faceNames = [`${name}_S`, `${name}_B`, `${name}_T`];
+ const faceMetadata = new Map([
+ [`${name}_S`, { type: "conical", radiusBottom: r2, radiusTop: r1, height: h }],
+ ]);
+ const state = createOccState({ shape, faceNames, faceMetadata, primitive: { kind: "cone", r1, r2, h, name } });
+ bindPrimitiveFaceNames(state);
+ return state;
+}
+
+function makePolygonWire(points) {
+ const polygon = new oc.BRepBuilderAPI_MakePolygon_1();
+ const pts = Array.isArray(points) ? points : [];
+ const usable = pts.length >= 2 && pointDistanceSq(pts[0], pts[pts.length - 1]) <= 1e-18
+ ? pts.slice(0, -1)
+ : pts;
+ for (const point of usable) polygon.Add_1(new oc.gp_Pnt_3(point[0], point[1], point[2]));
+ polygon.Close();
+ return polygon.Wire();
+}
+
+function addVecToPoint(point, vec) {
+ return [
+ Number(point?.[0] || 0) + Number(vec?.[0] || 0),
+ Number(point?.[1] || 0) + Number(vec?.[1] || 0),
+ Number(point?.[2] || 0) + Number(vec?.[2] || 0),
+ ];
+}
+
+function translateEdgeInputs(edgeInputs, vec) {
+ return (Array.isArray(edgeInputs) ? edgeInputs : []).map((edge) => ({
+ ...edge,
+ polyline: (Array.isArray(edge?.polyline) ? edge.polyline : []).map((point) => addVecToPoint(point, vec)),
+ bezierPoles: Array.isArray(edge?.bezierPoles) ? edge.bezierPoles.map((point) => addVecToPoint(point, vec)) : edge?.bezierPoles,
+ circleCenter: Array.isArray(edge?.circleCenter) ? addVecToPoint(edge.circleCenter, vec) : edge?.circleCenter,
+ arcCenter: Array.isArray(edge?.arcCenter) ? addVecToPoint(edge.arcCenter, vec) : edge?.arcCenter,
+ }));
+}
+
+function pointInLoop2D(point, loop, normal) {
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
+ if (!Array.isArray(point) || !Array.isArray(pts) || pts.length < 3) return false;
+ const n = normalizeVector(normal);
+ let ux = Math.abs(n[0]) < 0.8 ? [1, 0, 0] : [0, 1, 0];
+ const nd = dot(ux, n);
+ ux = normalizeVector([ux[0] - n[0] * nd, ux[1] - n[1] * nd, ux[2] - n[2] * nd]);
+ const uy = [
+ n[1] * ux[2] - n[2] * ux[1],
+ n[2] * ux[0] - n[0] * ux[2],
+ n[0] * ux[1] - n[1] * ux[0],
+ ];
+ const p2 = [dot(point, ux), dot(point, uy)];
+ let inside = false;
+ for (let i = 0, j = pts.length - 1; i < pts.length; j = i, i += 1) {
+ const pi = pts[i];
+ const pj = pts[j];
+ if (!Array.isArray(pi) || !Array.isArray(pj)) continue;
+ const xi = dot(pi, ux), yi = dot(pi, uy);
+ const xj = dot(pj, ux), yj = dot(pj, uy);
+ const crosses = ((yi > p2[1]) !== (yj > p2[1]))
+ && (p2[0] < ((xj - xi) * (p2[1] - yi)) / ((yj - yi) || 1e-30) + xi);
+ if (crosses) inside = !inside;
+ }
+ return inside;
+}
+
+function circleEdgeMatchesLoop(edgeInput, loop, normal) {
+ if (String(edgeInput?.curveType || edgeInput?.sketchGeomType || "").toLowerCase() !== "circle") return false;
+ const center = Array.isArray(edgeInput?.circleCenter) ? edgeInput.circleCenter : null;
+ const radius = Number(edgeInput?.circleRadius);
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
+ if (!center || !Number.isFinite(radius) || radius <= 0 || !Array.isArray(pts) || pts.length < 3) return false;
+ if (!pointInLoop2D(center, pts, normal)) return false;
+ let sum = 0;
+ let count = 0;
+ let maxErr = 0;
+ for (const p of pts) {
+ if (!Array.isArray(p)) continue;
+ const d = Math.hypot(p[0] - center[0], p[1] - center[1], p[2] - center[2]);
+ const err = Math.abs(d - radius);
+ sum += err;
+ maxErr = Math.max(maxErr, err);
+ count += 1;
+ }
+ const tol = Math.max(1e-5, radius * 1e-3);
+ return count > 0 && (sum / count) <= tol && maxErr <= tol * 4;
+}
+
+function findCircleEdgeForLoop(loop, circleEdges, normal, used = new Set()) {
+ for (const edge of circleEdges || []) {
+ if (used.has(edge)) continue;
+ if (circleEdgeMatchesLoop(edge, loop, normal)) return edge;
+ }
+ return null;
+}
+
+function signedLoopAreaOnPlane(points, normal) {
+ const pts = Array.isArray(points) ? points.filter((point) => Array.isArray(point) && point.length >= 3) : [];
+ if (pts.length < 3) return 0;
+ const n = normalizeVector(normal);
+ let ux = Math.abs(n[0]) < 0.8 ? [1, 0, 0] : [0, 1, 0];
+ const nd = dot(ux, n);
+ ux = normalizeVector([ux[0] - n[0] * nd, ux[1] - n[1] * nd, ux[2] - n[2] * nd]);
+ const uy = [
+ n[1] * ux[2] - n[2] * ux[1],
+ n[2] * ux[0] - n[0] * ux[2],
+ n[0] * ux[1] - n[1] * ux[0],
+ ];
+ let area = 0;
+ for (let i = 0; i < pts.length; i += 1) {
+ const a = pts[i];
+ const b = pts[(i + 1) % pts.length];
+ area += dot(a, ux) * dot(b, uy) - dot(b, ux) * dot(a, uy);
+ }
+ return area * 0.5;
+}
+
+function sketchChainPolyline(chain) {
+ const out = [];
+ for (const item of chain || []) {
+ const raw = Array.isArray(item?.edgeInput?.polyline) ? item.edgeInput.polyline : [];
+ const pts = item?.reverse ? raw.slice().reverse() : raw;
+ for (const point of pts) {
+ if (!Array.isArray(point) || point.length < 3) continue;
+ const last = out[out.length - 1];
+ if (last && pointDistanceSq(last, point) <= 1e-16) continue;
+ out.push(point);
+ }
+ }
+ return out;
+}
+
+function reverseSketchChainInPlace(chain) {
+ chain.reverse();
+ for (const item of chain) item.reverse = !item.reverse;
+}
+
+function translatedLoops(loops, vec) {
+ return (loops || []).map((loop) => ({
+ ...loop,
+ pts: (Array.isArray(loop?.pts) ? loop.pts : loop || []).map((point) => addVecToPoint(point, vec)),
+ }));
+}
+
+function fallbackCircleXAxis(normal) {
+ const n = normalizeVector(normal);
+ const basis = Math.abs(n[0]) < 0.8 ? [1, 0, 0] : [0, 1, 0];
+ return normalizeVector([
+ (n[1] * basis[2]) - (n[2] * basis[1]),
+ (n[2] * basis[0]) - (n[0] * basis[2]),
+ (n[0] * basis[1]) - (n[1] * basis[0]),
+ ]);
+}
+
+function makeCircleWireFromEdgeInput(edgeInput, normal) {
+ const center = Array.isArray(edgeInput?.circleCenter) ? edgeInput.circleCenter : null;
+ const radius = Number(edgeInput?.circleRadius);
+ const kind = String(edgeInput?.curveType || edgeInput?.sketchGeomType || "").toLowerCase();
+ if (kind !== "circle") return null;
+ if (!center || !Number.isFinite(radius) || radius <= 0) return null;
+ const n = normalizeVector(normal);
+ if (Math.hypot(n[0], n[1], n[2]) <= 1e-12) return null;
+ const first = Array.isArray(edgeInput?.polyline?.[0]) ? edgeInput.polyline[0] : null;
+ let xDir = first ? sub(first, center) : [0, 0, 0];
+ const normalDot = dot(xDir, n);
+ xDir = [
+ xDir[0] - n[0] * normalDot,
+ xDir[1] - n[1] * normalDot,
+ xDir[2] - n[2] * normalDot,
+ ];
+ if (Math.hypot(xDir[0], xDir[1], xDir[2]) <= 1e-12) xDir = fallbackCircleXAxis(n);
+ xDir = normalizeVector(xDir);
+ const axis = new oc.gp_Ax2_2(
+ new oc.gp_Pnt_3(center[0], center[1], center[2]),
+ new oc.gp_Dir_4(n[0], n[1], n[2]),
+ new oc.gp_Dir_4(xDir[0], xDir[1], xDir[2]),
+ );
+ const edge = new oc.BRepBuilderAPI_MakeEdge_8(new oc.gp_Circ_2(axis, radius)).Edge();
+ return new oc.BRepBuilderAPI_MakeWire_2(edge).Wire();
+}
+
+function makePlaneFromLoop(loop, normal) {
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
+ const origin = (Array.isArray(pts) ? pts : []).find((point) => Array.isArray(point) && point.length >= 3);
+ if (!origin) return null;
+ const n = normalizeVector(normal);
+ if (Math.hypot(n[0], n[1], n[2]) <= 1e-12) return null;
+ try {
+ return new oc.gp_Pln_3(
+ new oc.gp_Pnt_3(origin[0], origin[1], origin[2]),
+ new oc.gp_Dir_4(n[0], n[1], n[2]),
+ );
+ } catch {
+ return null;
+ }
+}
+
+function makeArcEdgeFromEdgeInput(edgeInput) {
+ const pts = Array.isArray(edgeInput?.polyline) ? edgeInput.polyline.filter((point) => Array.isArray(point) && point.length >= 3) : [];
+ if (pts.length < 3) return null;
+ const first = pts[0];
+ const middle = pts[(pts.length / 2) | 0];
+ const last = pts[pts.length - 1];
+ if (pointDistanceSq(first, last) <= 1e-18) return null;
+ try {
+ const arc = new oc.GC_MakeArcOfCircle_4(
+ new oc.gp_Pnt_3(first[0], first[1], first[2]),
+ new oc.gp_Pnt_3(middle[0], middle[1], middle[2]),
+ new oc.gp_Pnt_3(last[0], last[1], last[2]),
+ ).Value();
+ const curve = new oc.Handle_Geom_Curve_2(arc.get());
+ const edge = new oc.BRepBuilderAPI_MakeEdge_24(curve).Edge();
+ return edge && !edge.IsNull?.() ? edge : null;
+ } catch {
+ return null;
+ }
+}
+
+function makeEdgeFromSketchInput(edgeInput) {
+ const kind = String(edgeInput?.curveType || edgeInput?.sketchGeomType || "").toLowerCase();
+ const pts = Array.isArray(edgeInput?.polyline) ? edgeInput.polyline.filter((point) => Array.isArray(point) && point.length >= 3) : [];
+ if (kind === "line") return pts.length >= 2 ? makeLineEdge(pts[0], pts[pts.length - 1]) : null;
+ if (kind === "arc") return makeArcEdgeFromEdgeInput(edgeInput);
+ if (kind === "bezier") return makeBezierEdge(edgeInput?.bezierPoles || pts);
+ return pts.length >= 2 ? makeLineEdge(pts[0], pts[pts.length - 1]) : null;
+}
+
+function reverseOccEdge(edge) {
+ if (!edge) return edge;
+ try {
+ return oc.TopoDS.Edge_1(edge.Reversed());
+ } catch {
+ return edge;
+ }
+}
+
+function makeWireFromSketchLoop(loop, edgeInputs, normal) {
+ const segmentIds = Array.isArray(loop?.segmentIds) ? loop.segmentIds : [];
+ const circleEdges = (Array.isArray(edgeInputs) ? edgeInputs : [])
+ .filter((edge) => String(edge?.curveType || edge?.sketchGeomType || "").toLowerCase() === "circle" && Number(edge?.circleRadius) > 0);
+ const circleEdge = findCircleEdgeForLoop(loop, circleEdges, normal);
+ if (circleEdge) return makeCircleWireFromEdgeInput(circleEdge, normal);
+ if (!segmentIds.length) return null;
+ const selectedInputs = segmentIds.map((segmentId) => (
+ (edgeInputs || []).find((edge) => String(edge?.sketchGeometryId) === String(segmentId))
+ )).filter(Boolean);
+ if (selectedInputs.length !== segmentIds.length) return null;
+ const endpointTolSq = 1e-8;
+ const endpoint = (edgeInput, useEnd = false) => {
+ const pts = Array.isArray(edgeInput?.polyline) ? edgeInput.polyline : [];
+ return useEnd ? pts[pts.length - 1] : pts[0];
+ };
+ const samePoint = (a, b) => Array.isArray(a) && Array.isArray(b) && pointDistanceSq(a, b) <= endpointTolSq;
+ const remaining = selectedInputs.slice(1);
+ const chain = [{ edgeInput: selectedInputs[0], reverse: false }];
+ let chainStart = endpoint(selectedInputs[0], false);
+ let chainEnd = endpoint(selectedInputs[0], true);
+ while (remaining.length) {
+ let consumed = -1;
+ let entry = null;
+ let prepend = false;
+ for (let i = 0; i < remaining.length; i += 1) {
+ const candidate = remaining[i];
+ const start = endpoint(candidate, false);
+ const end = endpoint(candidate, true);
+ if (samePoint(chainEnd, start)) {
+ consumed = i; entry = { edgeInput: candidate, reverse: false }; chainEnd = end; break;
+ }
+ if (samePoint(chainEnd, end)) {
+ consumed = i; entry = { edgeInput: candidate, reverse: true }; chainEnd = start; break;
+ }
+ if (samePoint(chainStart, end)) {
+ consumed = i; entry = { edgeInput: candidate, reverse: false }; chainStart = start; prepend = true; break;
+ }
+ if (samePoint(chainStart, start)) {
+ consumed = i; entry = { edgeInput: candidate, reverse: true }; chainStart = end; prepend = true; break;
+ }
+ }
+ if (consumed < 0 || !entry) return null;
+ remaining.splice(consumed, 1);
+ if (prepend) chain.unshift(entry);
+ else chain.push(entry);
+ }
+ if (!samePoint(chainStart, chainEnd)) return null;
+ const targetArea = signedLoopAreaOnPlane(loop?.pts, normal);
+ const chainArea = signedLoopAreaOnPlane(sketchChainPolyline(chain), normal);
+ if (Math.abs(targetArea) > 1e-12 && Math.abs(chainArea) > 1e-12 && Math.sign(targetArea) !== Math.sign(chainArea)) {
+ reverseSketchChainInPlace(chain);
+ }
+ const simpleStartIndex = chain.findIndex((item) => {
+ const kind = String(item?.edgeInput?.curveType || item?.edgeInput?.sketchGeomType || "").toLowerCase();
+ return kind === "line" || kind === "arc";
+ });
+ if (simpleStartIndex > 0) {
+ chain.push(...chain.splice(0, simpleStartIndex));
+ }
+
+ const wire = new oc.BRepBuilderAPI_MakeWire_1();
+ let added = 0;
+ for (const item of chain) {
+ let edge = makeEdgeFromSketchInput(item.edgeInput);
+ if (!edge) return null;
+ if (item.reverse) edge = reverseOccEdge(edge);
+ try {
+ wire.Add_1(edge);
+ } catch {
+ return null;
+ }
+ added += 1;
+ }
+ if (!added) return null;
+ let result = null;
+ try {
+ result = wire.Wire();
+ } catch {
+ return null;
+ }
+ return result && !result.IsNull?.() ? result : null;
+}
+
+function makeFaceFromOuterAndHoleWires(outerWire, holeWires, plane = null) {
+ try {
+ const maker = plane
+ ? new oc.BRepBuilderAPI_MakeFace_16(plane, outerWire, true)
+ : new oc.BRepBuilderAPI_MakeFace_15(outerWire, true);
+ for (const wire of holeWires || []) maker.Add(wire);
+ if (typeof maker.IsDone === "function" && !maker.IsDone()) return null;
+ return oc.TopoDS.Face_1(maker.Shape());
+ } catch {
+ return null;
+ }
+}
+
+function makeAnalyticFaceFromBoundaryLoops(loops, edgeInputs, normal) {
+ const normalizedLoops = (loops || [])
+ .map((loop) => ({
+ pts: Array.isArray(loop?.pts) ? loop.pts : loop,
+ isHole: !!loop?.isHole,
+ segmentIds: Array.isArray(loop?.segmentIds) ? loop.segmentIds.slice() : [],
+ }))
+ .filter((loop) => Array.isArray(loop.pts) && loop.pts.length >= 3);
+ const outerLoops = normalizedLoops.filter((loop) => !loop.isHole);
+ const holeLoops = normalizedLoops.filter((loop) => loop.isHole);
+ if (outerLoops.length !== 1) return null;
+ const circleEdges = (Array.isArray(edgeInputs) ? edgeInputs : [])
+ .filter((edge) => String(edge?.curveType || edge?.sketchGeomType || "").toLowerCase() === "circle" && Number(edge?.circleRadius) > 0);
+ const hasSketchLoopSegments = normalizedLoops.some((loop) => Array.isArray(loop.segmentIds) && loop.segmentIds.length);
+ if (!circleEdges.length && !hasSketchLoopSegments) return null;
+ const used = new Set();
+ const outerCircle = findCircleEdgeForLoop(outerLoops[0], circleEdges.filter((edge) => !edge?.isHole), normal, used)
+ || (holeLoops.length ? null : findCircleEdgeForLoop(outerLoops[0], circleEdges, normal, used));
+ const outerWire = outerCircle
+ ? makeCircleWireFromEdgeInput(outerCircle, normal)
+ : (outerLoops[0].segmentIds.length ? makeWireFromSketchLoop(outerLoops[0], edgeInputs, normal) : makePolygonWire(outerLoops[0].pts));
+ if (!outerWire) return null;
+ if (outerCircle) used.add(outerCircle);
+ const holeWires = [];
+ for (const loop of holeLoops) {
+ const holeCircle = findCircleEdgeForLoop(loop, circleEdges.filter((edge) => edge?.isHole || edge !== outerCircle), normal, used);
+ const wire = holeCircle
+ ? makeCircleWireFromEdgeInput(holeCircle, normal)
+ : (loop.segmentIds.length ? makeWireFromSketchLoop(loop, edgeInputs, normal) : makePolygonWire(loop.pts));
+ if (!wire) return null;
+ if (holeCircle) used.add(holeCircle);
+ holeWires.push(holeCircle ? reverseWire(wire) : wire);
+ }
+ return makeFaceFromOuterAndHoleWires(outerWire, holeWires, makePlaneFromLoop(outerLoops[0], normal));
+}
+
+function makeFaceFromBoundaryLoops(loops, edgeInputs = [], normal = [0, 0, 1]) {
+ let analytic = null;
+ try {
+ analytic = makeAnalyticFaceFromBoundaryLoops(loops, edgeInputs, normal);
+ } catch {
+ analytic = null;
+ }
+ if (analytic) return analytic;
+ const normalized = (loops || [])
+ .map((loop) => ({
+ pts: Array.isArray(loop?.pts) ? loop.pts : loop,
+ isHole: !!loop?.isHole,
+ segmentIds: Array.isArray(loop?.segmentIds) ? loop.segmentIds.slice() : [],
+ }))
+ .filter((loop) => Array.isArray(loop.pts) && loop.pts.length >= 3);
+ const requiresAnalyticSketch = normalized.some((loop) => Array.isArray(loop?.segmentIds) && loop.segmentIds.length);
+ if (requiresAnalyticSketch) {
+ throw new Error("OpenCASCADE sketch face construction failed for authored curve geometry.");
+ }
+ const outer = normalized.find((loop) => !loop.isHole) || normalized[0];
+ if (!outer) throw new Error("OpenCASCADE extrusion requires at least one closed boundary loop.");
+ const plane = makePlaneFromLoop(outer, normal);
+ const face = makeFaceFromOuterAndHoleWires(
+ makePolygonWire(outer.pts),
+ normalized.filter((loop) => loop !== outer && loop.isHole).map((loop) => makePolygonWire(loop.pts)),
+ plane,
+ );
+ if (!face) {
+ throw new Error("OpenCASCADE could not build a face from extrusion boundary loops.");
+ }
+ return face;
+}
+
+function parseJsonObject(value) {
+ if (!value) return {};
+ if (typeof value === "object") return { ...value };
+ try {
+ const parsed = JSON.parse(String(value));
+ return parsed && typeof parsed === "object" ? parsed : {};
+ } catch {
+ return {};
+ }
+}
+
+export function makePyramid({ bL = 1, s = 4, h = 1, name = "Pyramid" } = {}) {
+ const sides = Math.max(3, Math.floor(Number(s) || 4));
+ const baseLength = Number(bL) || 1;
+ const height = Number(h) || 1;
+ const radius = baseLength / (2 * Math.sin(Math.PI / sides));
+ const ring = [];
+ for (let i = 0; i < sides; i += 1) {
+ const angle = (i / sides) * Math.PI * 2;
+ ring.push([radius * Math.cos(angle), 0, radius * Math.sin(angle)]);
+ }
+
+ const loft = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
+ loft.AddWire(makePolygonWire(ring));
+ loft.AddVertex(new oc.BRepBuilderAPI_MakeVertex(new oc.gp_Pnt_3(0, height, 0)).Vertex());
+ loft.Build();
+ const faceNames = [`${name}_Base`, ...Array.from({ length: sides }, (_, index) => `${name}_S[${index}]`)];
+ const state = createOccState({ shape: loft.Shape(), faceNames, primitive: { kind: "pyramid", bL: baseLength, s: sides, h: height, name } });
+ bindPrimitiveFaceNames(state);
+ return state;
+}
+
+function dot(a, b) {
+ return (Number(a?.[0] || 0) * Number(b?.[0] || 0))
+ + (Number(a?.[1] || 0) * Number(b?.[1] || 0))
+ + (Number(a?.[2] || 0) * Number(b?.[2] || 0));
+}
+
+function sub(a, b) {
+ return [
+ Number(a?.[0] || 0) - Number(b?.[0] || 0),
+ Number(a?.[1] || 0) - Number(b?.[1] || 0),
+ Number(a?.[2] || 0) - Number(b?.[2] || 0),
+ ];
+}
+
+function closestSegmentDistanceSq(point, a, b) {
+ const ab = sub(b, a);
+ const ap = sub(point, a);
+ const len2 = dot(ab, ab);
+ const t = len2 > 1e-20 ? Math.max(0, Math.min(1, dot(ap, ab) / len2)) : 0;
+ const q = [a[0] + ab[0] * t, a[1] + ab[1] * t, a[2] + ab[2] * t];
+ const d = sub(point, q);
+ return dot(d, d);
+}
+
+function closestSegmentPoint(point, a, b) {
+ const ab = sub(b, a);
+ const ap = sub(point, a);
+ const len2 = dot(ab, ab);
+ const t = len2 > 1e-20 ? Math.max(0, Math.min(1, dot(ap, ab) / len2)) : 0;
+ return [a[0] + ab[0] * t, a[1] + ab[1] * t, a[2] + ab[2] * t];
+}
+
+function closestPathPoint(point, points, closed = false) {
+ let best = null;
+ const count = Array.isArray(points) ? points.length : 0;
+ for (let i = 0; i < count - 1; i += 1) {
+ const q = closestSegmentPoint(point, points[i], points[i + 1]);
+ const dist2 = dot(sub(point, q), sub(point, q));
+ if (!best || dist2 < best.dist2) best = { point: q, dist2 };
+ }
+ if (closed && count > 2) {
+ const q = closestSegmentPoint(point, points[count - 1], points[0]);
+ const dist2 = dot(sub(point, q), sub(point, q));
+ if (!best || dist2 < best.dist2) best = { point: q, dist2 };
+ }
+ return best;
+}
+
+function averageDistanceToPath(pointsToClassify, pathPoints, closed = false) {
+ const samples = (Array.isArray(pointsToClassify) ? pointsToClassify : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3);
+ if (!samples.length) return Infinity;
+ let sum = 0;
+ let count = 0;
+ for (const point of samples) {
+ const closest = closestPathPoint(point, pathPoints, closed);
+ if (!closest || !Number.isFinite(closest.dist2)) continue;
+ sum += Math.sqrt(Math.max(0, closest.dist2));
+ count += 1;
+ }
+ return count ? sum / count : Infinity;
+}
+
+function loopSegmentsWithNames(boundaryLoops, edgeInputs, defaultName, normal = [0, 0, 1]) {
+ const segments = [];
+ const named = Array.isArray(edgeInputs) ? edgeInputs : [];
+ const namedSegmentIds = new Set(
+ named
+ .map((edge) => edge?.sketchGeometryId)
+ .filter((id) => id !== undefined && id !== null)
+ .map((id) => String(id)),
+ );
+ const circleLoopCovered = new Set();
+ for (const edge of named) {
+ const pts = Array.isArray(edge?.polyline) ? edge.polyline : [];
+ if (edge?.curveType === "circle" && pts.length >= 2) {
+ for (let loopIndex = 0; loopIndex < (boundaryLoops || []).length; loopIndex += 1) {
+ if (circleEdgeMatchesLoop(edge, boundaryLoops[loopIndex], normal)) circleLoopCovered.add(loopIndex);
+ }
+ segments.push({
+ a: pts[0],
+ b: pts[Math.max(0, pts.length - 2)],
+ name: edge.name || defaultName,
+ metadata: parseJsonObject(edge.metadataJson),
+ curveType: "circle",
+ center: Array.isArray(edge.circleCenter) ? edge.circleCenter.slice() : null,
+ radius: Number(edge.circleRadius),
+ });
+ continue;
+ }
+ for (let i = 0; i + 1 < pts.length; i += 1) {
+ segments.push({
+ a: pts[i],
+ b: pts[i + 1],
+ name: edge.name || defaultName,
+ metadata: parseJsonObject(edge.metadataJson),
+ });
+ }
+ }
+ let segmentIndex = 0;
+ for (let loopIndex = 0; loopIndex < (boundaryLoops || []).length; loopIndex += 1) {
+ if (circleLoopCovered.has(loopIndex)) continue;
+ const loop = boundaryLoops[loopIndex];
+ const segmentIds = Array.isArray(loop?.segmentIds) ? loop.segmentIds : [];
+ if (segmentIds.length && segmentIds.every((id) => namedSegmentIds.has(String(id)))) continue;
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
+ if (!Array.isArray(pts) || pts.length < 2) continue;
+ for (let i = 0; i < pts.length; i += 1) {
+ segments.push({
+ a: pts[i],
+ b: pts[(i + 1) % pts.length],
+ name: `${defaultName}_${segmentIndex++}`,
+ metadata: { faceType: "SIDEWALL" },
+ });
+ }
+ }
+ return segments;
+}
+
+function classifyPrismFace(stats, params) {
+ const dir = params.direction;
+ const len = Math.hypot(dir[0], dir[1], dir[2]);
+ if (len <= 1e-12) return params.startName;
+ const unit = [dir[0] / len, dir[1] / len, dir[2] / len];
+ const t = dot(sub(stats.centroid, params.startOrigin), unit);
+ const capTol = Math.max(1e-6, len * 1e-5);
+ if (Math.abs(t) <= capTol) return params.startName;
+ if (Math.abs(t - len) <= capTol) return params.endName;
+
+ const midOffset = [dir[0] * 0.5, dir[1] * 0.5, dir[2] * 0.5];
+ let best = null;
+ for (const segment of params.segments || []) {
+ let dist;
+ if (segment?.curveType === "circle" && Array.isArray(segment.center) && Number(segment.radius) > 0) {
+ const center = addVecToPoint(segment.center, midOffset);
+ const rel = sub(stats.centroid, center);
+ const axial = dot(rel, unit);
+ const radial = [
+ rel[0] - unit[0] * axial,
+ rel[1] - unit[1] * axial,
+ rel[2] - unit[2] * axial,
+ ];
+ const radialError = Math.hypot(radial[0], radial[1], radial[2]) - Number(segment.radius);
+ dist = radialError * radialError;
+ } else {
+ const a = addVecToPoint(segment.a, midOffset);
+ const b = addVecToPoint(segment.b, midOffset);
+ dist = closestSegmentDistanceSq(stats.centroid, a, b);
+ }
+ if (!best || dist < best.dist) best = { dist, segment };
+ }
+ return best?.segment?.name || params.defaultSideName;
+}
+
+function bindPrismFaceNames(state, params) {
+ state.faceNameByIndex = [];
+ new oc.BRepMesh_IncrementalMesh_2(state.shape, DEFAULT_DEFLECTION, false, DEFAULT_ANGLE, false);
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ state.faceNameByIndex[faceIndex] = classifyPrismFace(statsFromOccFace(face), params);
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+function bindSingleFacePrismFaceNames(state, params) {
+ state.faceNameByIndex = [];
+ new oc.BRepMesh_IncrementalMesh_2(state.shape, DEFAULT_DEFLECTION, false, DEFAULT_ANGLE, false);
+ const dir = params.direction || [0, 0, 1];
+ const len = Math.hypot(dir[0], dir[1], dir[2]);
+ const unit = len > 1e-12 ? [dir[0] / len, dir[1] / len, dir[2] / len] : [0, 0, 1];
+ const sourceStats = params.sourceFace ? statsFromOccFace(params.sourceFace) : null;
+ const startOrigin = sourceStats?.centroid || [0, 0, 0];
+ const capTol = Math.max(1e-6, len * 1e-5);
+ let sideIndex = 0;
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const stats = statsFromOccFace(face);
+ const t = dot(sub(stats.centroid, startOrigin), unit);
+ if (Math.abs(t) <= capTol) {
+ state.faceNameByIndex[faceIndex] = params.startName;
+ } else if (Math.abs(t - len) <= capTol) {
+ state.faceNameByIndex[faceIndex] = params.endName;
+ } else {
+ state.faceNameByIndex[faceIndex] = `${params.sidePrefix}_E${sideIndex++}_SW`;
+ }
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+function bindLoftFaceNames(state, params) {
+ state.faceNameByIndex = [];
+ new oc.BRepMesh_IncrementalMesh_2(state.shape, DEFAULT_DEFLECTION, false, DEFAULT_ANGLE, false);
+ const sections = Array.isArray(params.sections) ? params.sections : [];
+ const first = sections[0] || [];
+ const last = sections[sections.length - 1] || [];
+ const sideNames = Array.isArray(params.sideNames) ? params.sideNames : [];
+ const startCentroid = makeFaceStats(first).centroid;
+ const endCentroid = makeFaceStats(last).centroid;
+ const modelSize = Math.max(1, Math.hypot(...sub(startCentroid, endCentroid)));
+ const axis = sub(endCentroid, startCentroid);
+ const axisLenSq = Math.max(1e-20, dot(axis, axis));
+ const capTol = Math.max(1e-5, modelSize * 1e-4);
+ const startNormal = finiteNormalOrFallback(params.startNormal, first);
+ const endNormal = finiteNormalOrFallback(params.endNormal, last);
+
+ const rails = [];
+ const sectionCount = sections.length;
+ const edgeCount = sideNames.length;
+ for (let edgeIndex = 0; edgeIndex < edgeCount; edgeIndex += 1) {
+ const rail = [];
+ for (let sectionIndex = 0; sectionIndex < sectionCount; sectionIndex += 1) {
+ const pts = sections[sectionIndex] || [];
+ const n = pts.length;
+ if (!n) continue;
+ const a = pts[edgeIndex % n];
+ const b = pts[(edgeIndex + 1) % n];
+ if (!a || !b) continue;
+ rail.push([
+ (Number(a[0] || 0) + Number(b[0] || 0)) * 0.5,
+ (Number(a[1] || 0) + Number(b[1] || 0)) * 0.5,
+ (Number(a[2] || 0) + Number(b[2] || 0)) * 0.5,
+ ]);
+ }
+ rails.push(rail);
+ }
+
+ let created = 0;
+ let faceIndex = 0;
+ const sideFaces = [];
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const stats = statsFromOccFace(face);
+ const ds = Math.hypot(...sub(stats.centroid, startCentroid));
+ const de = Math.hypot(...sub(stats.centroid, endCentroid));
+ const t = dot(sub(stats.centroid, startCentroid), axis) / axisLenSq;
+ const startPlaneDistance = Math.abs(dot(sub(stats.centroid, first[0] || startCentroid), startNormal));
+ const endPlaneDistance = Math.abs(dot(sub(stats.centroid, last[0] || endCentroid), endNormal));
+ if (ds <= capTol || startPlaneDistance <= capTol || t <= capTol / modelSize) {
+ state.faceNameByIndex[faceIndex] = params.startName;
+ continue;
+ }
+ if (de <= capTol || endPlaneDistance <= capTol || t >= 1 - (capTol / modelSize)) {
+ state.faceNameByIndex[faceIndex] = params.endName;
+ continue;
+ }
+
+ const candidates = [];
+ for (let i = 0; i < rails.length; i += 1) {
+ const rail = rails[i];
+ if (!Array.isArray(rail) || rail.length < 2) continue;
+ let dist = Infinity;
+ for (let j = 0; j + 1 < rail.length; j += 1) {
+ dist = Math.min(dist, closestSegmentDistanceSq(stats.centroid, rail[j], rail[j + 1]));
+ }
+ candidates.push({ dist, name: sideNames[i] });
+ }
+ candidates.sort((a, b) => a.dist - b.dist);
+ sideFaces.push({ faceIndex, candidates });
+ }
+ const assignedFaces = new Set();
+ for (const sideName of sideNames) {
+ let best = null;
+ for (const entry of sideFaces) {
+ if (assignedFaces.has(entry.faceIndex)) continue;
+ const candidate = entry.candidates.find((item) => item.name === sideName);
+ if (!candidate) continue;
+ if (!best || candidate.dist < best.dist) best = { faceIndex: entry.faceIndex, dist: candidate.dist };
+ }
+ if (best) {
+ state.faceNameByIndex[best.faceIndex] = sideName;
+ assignedFaces.add(best.faceIndex);
+ }
+ }
+ for (const entry of sideFaces) {
+ if (assignedFaces.has(entry.faceIndex)) continue;
+ state.faceNameByIndex[entry.faceIndex] = entry.candidates[0]?.name || `${params.featureID || "LOFT"}_FACE_${created++}`;
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+function makeLoftSectionWire(section, wireInput = null) {
+ const loops = Array.isArray(wireInput?.loops) && wireInput.loops.length ? wireInput.loops : [{ pts: section, isHole: false }];
+ const edgeInputs = Array.isArray(wireInput?.edgeInputs) ? wireInput.edgeInputs : [];
+ const normal = Array.isArray(wireInput?.normal) ? wireInput.normal : [0, 0, 1];
+ const normalizedLoops = loops
+ .map((loop) => ({
+ pts: Array.isArray(loop?.pts) ? loop.pts : loop,
+ isHole: !!loop?.isHole,
+ }))
+ .filter((loop) => Array.isArray(loop.pts) && loop.pts.length >= 3);
+ const outerLoops = normalizedLoops.filter((loop) => !loop.isHole);
+ const holeLoops = normalizedLoops.filter((loop) => loop.isHole);
+ if (outerLoops.length === 1 && holeLoops.length === 0) {
+ const circleEdges = edgeInputs.filter((edge) => (
+ String(edge?.curveType || edge?.sketchGeomType || "").toLowerCase() === "circle"
+ && Number(edge?.circleRadius) > 0
+ ));
+ if (circleEdges.length === 1) {
+ const circleWire = makeCircleWireFromEdgeInput(circleEdges[0], normal);
+ if (circleWire) return circleWire;
+ }
+ }
+ const outer = outerLoops[0] || normalizedLoops[0];
+ if (!outer) throw new Error("OpenCASCADE loft section requires a closed loop.");
+ return makePolygonWire(outer.pts);
+}
+
+export function makeLoft({
+ sections = [],
+ sectionWireInputs = [],
+ sideNames = [],
+ startName = "LOFT_START",
+ endName = "LOFT_END",
+ featureID = "LOFT",
+} = {}) {
+ const normalizedSections = (Array.isArray(sections) ? sections : [])
+ .map((pts) => (Array.isArray(pts) ? pts : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]))
+ .map((pts) => (pts.length >= 2 && pointDistanceSq(pts[0], pts[pts.length - 1]) <= 1e-18) ? pts.slice(0, -1) : pts)
+ .filter((pts) => pts.length >= 3);
+ if (normalizedSections.length < 2) {
+ throw new Error("OpenCASCADE loft requires at least two closed profile sections.");
+ }
+
+ const op = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
+ for (let i = 0; i < normalizedSections.length; i += 1) {
+ op.AddWire(makeLoftSectionWire(normalizedSections[i], sectionWireInputs[i] || null));
+ }
+ op.Build();
+ if (typeof op.IsDone === "function" && !op.IsDone()) {
+ throw new Error("OpenCASCADE loft failed.");
+ }
+
+ const normalizedSideNames = Array.isArray(sideNames) && sideNames.length
+ ? sideNames.map((name, index) => String(name || `${featureID}_SIDE_${index}`))
+ : Array.from({ length: normalizedSections[0].length }, (_, index) => `${featureID}_SIDE_${index}`);
+ const faceMetadata = new Map([
+ [startName, { faceType: "STARTCAP" }],
+ [endName, { faceType: "ENDCAP" }],
+ ]);
+ for (const name of normalizedSideNames) faceMetadata.set(name, { faceType: "SIDEWALL" });
+ const state = createOccState({
+ shape: op.Shape(),
+ faceNames: uniqueNames([startName, endName, ...normalizedSideNames]),
+ faceMetadata,
+ feature: { kind: "loft", featureID },
+ });
+ bindLoftFaceNames(state, {
+ sections: normalizedSections,
+ sideNames: normalizedSideNames,
+ startName,
+ endName,
+ featureID,
+ startNormal: sectionWireInputs?.[0]?.normal,
+ endNormal: sectionWireInputs?.[normalizedSections.length - 1]?.normal,
+ });
+ return state;
+}
+
+function makeHelixWireOnSurface({ radius, radiusEnd = radius, length, turns, phase = 0, leftHanded = false } = {}) {
+ const r = Math.max(1e-9, Number(radius) || 0);
+ const rEnd = Math.max(1e-9, Number(radiusEnd) || r);
+ const h = Math.max(1e-9, Number(length) || 0);
+ const nTurns = Math.max(1e-9, Math.abs(Number(turns) || 0));
+ const u0 = Number(phase) || 0;
+ const u1 = u0 + (leftHanded ? -1 : 1) * Math.PI * 2 * nTurns;
+ const ax3 = new oc.gp_Ax3_3(
+ new oc.gp_Pnt_3(0, 0, 0),
+ new oc.gp_Dir_4(0, 0, 1),
+ new oc.gp_Dir_4(1, 0, 0),
+ );
+ const taper = (rEnd - r) / h;
+ const semiAngle = Math.atan(taper);
+ const v1 = Math.abs(taper) > 1e-12 ? h / Math.cos(semiAngle) : h;
+ const surfaceObject = Math.abs(taper) > 1e-12
+ ? new oc.Geom_ConicalSurface_1(ax3, semiAngle, r)
+ : new oc.Geom_CylindricalSurface_1(ax3, r);
+ const surface = new oc.Handle_Geom_Surface_2(surfaceObject);
+ const dir = new oc.gp_Dir2d_4(u1 - u0, v1);
+ const line = new oc.Geom2d_Line_1(new oc.gp_Ax2d_2(new oc.gp_Pnt2d_3(u0, 0), dir));
+ const curve = new oc.Handle_Geom2d_Curve_2(line);
+ const edge = new oc.BRepBuilderAPI_MakeEdge_31(curve, surface, 0, Math.hypot(u1 - u0, v1)).Edge();
+ oc.BRepLib.BuildCurve3d(edge, 1e-7, oc.GeomAbs_Shape.GeomAbs_C1, 14, 1000);
+ oc.BRepLib.BuildCurves3d_2(edge);
+ return new oc.BRepBuilderAPI_MakeWire_2(edge).Wire();
+}
+
+export function makeHelicalPipe({
+ radius = 1,
+ radiusEnd = radius,
+ length = 1,
+ turns = 1,
+ profilePoints = [],
+ phase = 0,
+ leftHanded = false,
+ sideNames = [],
+ startName = "PIPE_START",
+ endName = "PIPE_END",
+ featureID = "PIPE",
+} = {}) {
+ const normalizedProfile = (Array.isArray(profilePoints) ? profilePoints : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]);
+ if (normalizedProfile.length < 3) {
+ throw new Error("OpenCASCADE helical pipe requires a closed profile with at least three points.");
+ }
+
+ const spine = makeHelixWireOnSurface({ radius, radiusEnd, length, turns, phase, leftHanded });
+ const profileWire = makePolygonWire(normalizedProfile);
+ const profileFace = new oc.BRepBuilderAPI_MakeFace_15(profileWire, true).Face();
+ const pipe = new oc.BRepOffsetAPI_MakePipe_2(
+ spine,
+ profileFace,
+ oc.GeomFill_Trihedron.GeomFill_IsFrenet,
+ false,
+ );
+ pipe.Build();
+ const normalizedSideNames = Array.isArray(sideNames) && sideNames.length
+ ? sideNames.map((name, index) => String(name || `${featureID}_SIDE_${index}`))
+ : Array.from({ length: normalizedProfile.length }, (_, index) => `${featureID}_SIDE_${index}`);
+ const faceMetadata = new Map([
+ [startName, { faceType: "STARTCAP" }],
+ [endName, { faceType: "ENDCAP" }],
+ ]);
+ for (const name of normalizedSideNames) faceMetadata.set(name, { faceType: "SIDEWALL" });
+ return createOccState({
+ shape: pipe.Shape(),
+ faceNames: uniqueNames([startName, endName, ...normalizedSideNames]),
+ faceMetadata,
+ meshOptions: { deflection: 0.02, angle: 0.18 },
+ feature: { kind: "helicalPipe", featureID, radius, radiusEnd, length, turns, phase, leftHanded },
+ });
+}
+
+export function makeExtrusion({
+ boundaryLoops = [],
+ faceName = "Face",
+ name = "Extrude",
+ direction = [0, 0, 1],
+ normal = null,
+ distanceBack = 0,
+ edgeInputs = [],
+ omitBaseCap = false,
+} = {}) {
+ const dir = [Number(direction[0]) || 0, Number(direction[1]) || 0, Number(direction[2]) || 0];
+ const normalVec = Array.isArray(normal) ? [Number(normal[0]) || 0, Number(normal[1]) || 0, Number(normal[2]) || 0] : null;
+ const back = Number(distanceBack) || 0;
+ const len = Math.hypot(dir[0], dir[1], dir[2]);
+ const normalLen = Math.hypot(normalVec?.[0] || 0, normalVec?.[1] || 0, normalVec?.[2] || 0);
+ const backUnit = len > 1e-12
+ ? [dir[0] / len, dir[1] / len, dir[2] / len]
+ : (normalLen > 1e-12 ? [normalVec[0] / normalLen, normalVec[1] / normalLen, normalVec[2] / normalLen] : [0, 0, 1]);
+ let backVec = [0, 0, 0];
+ if (back > 0) backVec = [-backUnit[0] * back, -backUnit[1] * back, -backUnit[2] * back];
+ const totalDir = [dir[0] - backVec[0], dir[1] - backVec[1], dir[2] - backVec[2]];
+ const loops = translatedLoops(boundaryLoops, backVec);
+ const translatedEdges = translateEdgeInputs(edgeInputs, backVec);
+ const face = makeFaceFromBoundaryLoops(loops, translatedEdges, normalLen > 1e-12 ? normalVec : backUnit);
+ const prism = new oc.BRepPrimAPI_MakePrism_1(face, new oc.gp_Vec_4(totalDir[0], totalDir[1], totalDir[2]), false, true);
+ if (typeof prism.IsDone === "function" && !prism.IsDone()) {
+ throw new Error("OpenCASCADE prism extrusion failed.");
+ }
+
+ const featureTag = name ? `${name}:` : "";
+ const startName = `${featureTag}${faceName}_START`;
+ const endName = `${featureTag}${faceName}_END`;
+ const defaultSideName = `${featureTag}${faceName}_SW`;
+ const segments = loopSegmentsWithNames(loops, translatedEdges, defaultSideName, normalLen > 1e-12 ? normalVec : backUnit);
+ const profileNormal = normalLen > 1e-12 ? normalVec : backUnit;
+ const faceMetadata = new Map([
+ [startName, {
+ faceType: "STARTCAP",
+ boundaryLoopsWorld: loops,
+ sketchEdgeInputsWorld: translatedEdges,
+ profileNormal,
+ }],
+ [endName, {
+ faceType: "ENDCAP",
+ boundaryLoopsWorld: translatedLoops(loops, totalDir),
+ sketchEdgeInputsWorld: translateEdgeInputs(translatedEdges, totalDir),
+ profileNormal,
+ }],
+ ]);
+ for (const segment of segments) {
+ if (!segment.name) continue;
+ faceMetadata.set(segment.name, Object.keys(segment.metadata || {}).length ? segment.metadata : { faceType: "SIDEWALL" });
+ }
+ if (omitBaseCap) faceMetadata.delete(startName);
+
+ const state = createOccState({
+ shape: prism.Shape(),
+ faceNames: uniqueNames([startName, endName, ...segments.map((segment) => segment.name)]),
+ faceMetadata,
+ primitive: null,
+ feature: { kind: "extrude", name, faceName },
+ });
+ bindPrismFaceNames(state, {
+ startName,
+ endName,
+ defaultSideName,
+ direction: totalDir,
+ startOrigin: loops?.[0]?.pts?.[0] || [0, 0, 0],
+ segments,
+ });
+ if (omitBaseCap) {
+ state.faceNames = state.faceNames.filter((entry) => entry !== startName);
+ state.faceNameByIndex = state.faceNameByIndex.map((entry) => entry === startName ? defaultSideName : entry);
+ }
+ return state;
+}
+
+export function makeFacePrismFromOccSolid(solid, faceName, {
+ distance,
+ sourceFaceName = faceName,
+ featureID = "THICKEN",
+} = {}) {
+ if (!hasOccShape(solid)) return null;
+ const selectedFaceName = String(faceName || "").trim();
+ if (!selectedFaceName) return null;
+ const source = String(sourceFaceName || selectedFaceName).trim() || selectedFaceName;
+ const d = Number(distance);
+ if (!Number.isFinite(d) || Math.abs(d) <= 1e-12) {
+ throw new Error(`OpenCASCADE face thicken distance must be non-zero, got ${distance}`);
+ }
+ const face = findOccFaceByName(solid, selectedFaceName);
+ if (!face) return null;
+ const surfaceType = occFaceSurfaceTypeValue(face);
+ const planeSurfaceType = Number(
+ oc.GeomAbs_SurfaceType.GeomAbs_Plane?.value
+ ?? oc.GeomAbs_SurfaceType.GeomAbs_Plane,
+ );
+ if (!sameOccSurfaceType(surfaceType, planeSurfaceType)) {
+ throw new Error("OpenCASCADE face thicken currently requires a planar BREP face.");
+ }
+ const normal = surfaceNormalFromOccFace(face) || occFaceNormal(solid, selectedFaceName)?.normal || [0, 0, 1];
+ const direction = [normal[0] * d, normal[1] * d, normal[2] * d];
+ const prism = new oc.BRepPrimAPI_MakePrism_1(face, new oc.gp_Vec_4(direction[0], direction[1], direction[2]), false, true);
+ if (typeof prism.IsDone === "function" && !prism.IsDone()) {
+ throw new Error("OpenCASCADE face prism thicken failed.");
+ }
+ const startName = `${source}_START`;
+ const endName = `${source}_END`;
+ const faceMetadata = new Map([
+ [startName, { faceType: "STARTCAP", sourceFaceName: source, profileNormal: normal }],
+ [endName, { faceType: "ENDCAP", sourceFaceName: source, profileNormal: normal }],
+ ]);
+ const state = createOccState({
+ shape: prism.Shape(),
+ faceNames: [startName, endName],
+ faceMetadata,
+ feature: { kind: "faceThicken", featureID, sourceFaceName: source, distance: d },
+ });
+ bindSingleFacePrismFaceNames(state, {
+ sourceFace: face,
+ startName,
+ endName,
+ sidePrefix: source,
+ direction,
+ });
+ for (const name of state.faceNames || []) {
+ if (name !== startName && name !== endName && !faceMetadata.has(name)) {
+ faceMetadata.set(name, { faceType: "SIDEWALL", sourceFaceName: source });
+ }
+ }
+ state.faceMetadata = faceMetadata;
+ return state;
+}
+
+function rotatePointAroundAxis(point, origin, axis, angle) {
+ const len = Math.hypot(axis[0], axis[1], axis[2]) || 1;
+ const ux = axis[0] / len, uy = axis[1] / len, uz = axis[2] / len;
+ const x = point[0] - origin[0], y = point[1] - origin[1], z = point[2] - origin[2];
+ const c = Math.cos(angle), s = Math.sin(angle);
+ const d = ux * x + uy * y + uz * z;
+ return [
+ origin[0] + x * c + (uy * z - uz * y) * s + ux * d * (1 - c),
+ origin[1] + y * c + (uz * x - ux * z) * s + uy * d * (1 - c),
+ origin[2] + z * c + (ux * y - uy * x) * s + uz * d * (1 - c),
+ ];
+}
+
+function loopsCentroid(loops) {
+ const sum = [0, 0, 0];
+ let count = 0;
+ for (const loop of loops || []) {
+ const pts = Array.isArray(loop?.pts) ? loop.pts : loop;
+ if (!Array.isArray(pts)) continue;
+ for (const p of pts) {
+ sum[0] += Number(p?.[0] || 0);
+ sum[1] += Number(p?.[1] || 0);
+ sum[2] += Number(p?.[2] || 0);
+ count += 1;
+ }
+ }
+ return count ? [sum[0] / count, sum[1] / count, sum[2] / count] : [0, 0, 0];
+}
+
+function classifyRevolveSideFace(stats, params) {
+ const midAngle = Number(params.angleRad || 0) * 0.5;
+ const profilePoint = rotatePointAroundAxis(stats.centroid, params.axisOrigin, params.axisDirection, -midAngle);
+ let best = null;
+ for (const segment of params.segments || []) {
+ let dist;
+ if (segment?.curveType === "circle" && Array.isArray(segment.center) && Number(segment.radius) > 0) {
+ const radiusError = Math.hypot(...sub(profilePoint, segment.center)) - Number(segment.radius);
+ dist = radiusError * radiusError;
+ } else {
+ dist = closestSegmentDistanceSq(profilePoint, segment.a, segment.b);
+ }
+ if (!best || dist < best.dist) best = { dist, segment };
+ }
+ return best?.segment?.name || params.defaultSideName;
+}
+
+function edgeInputSegments(edgeInput) {
+ const pts = Array.isArray(edgeInput?.polyline) ? edgeInput.polyline : [];
+ const segments = [];
+ for (let i = 0; i + 1 < pts.length; i += 1) {
+ if (pointDistanceSq(pts[i], pts[i + 1]) > 1e-16) segments.push([pts[i], pts[i + 1]]);
+ }
+ return segments;
+}
+
+function matchEdgeInputToOccEdge(edgeInputs, occEdge) {
+ let best = null;
+ for (const edgeInput of edgeInputs || []) {
+ const name = String(edgeInput?.name || "").trim();
+ if (!name) continue;
+ const segments = edgeInputSegments(edgeInput);
+ if (!segments.length) continue;
+ let score = curvePolylineMatchScore(segments, occEdge);
+ const endpoints = occEdgeEndpoints(occEdge);
+ const pts = Array.isArray(edgeInput?.polyline) ? edgeInput.polyline : [];
+ const inputClosed = pts.length > 2 && pointDistanceSq(pts[0], pts[pts.length - 1]) <= 1e-16;
+ if (endpoints && !inputClosed) {
+ const endpointScore = Math.min(
+ pointDistanceSq(pts[0], endpoints[0]) + pointDistanceSq(pts[pts.length - 1], endpoints[1]),
+ pointDistanceSq(pts[0], endpoints[1]) + pointDistanceSq(pts[pts.length - 1], endpoints[0]),
+ );
+ score = Math.min(score, endpointScore);
+ }
+ if (!best || score < best.score) best = { name, score };
+ }
+ return best?.name || null;
+}
+
+function collectNamedSourceEdges(sourceFace, edgeInputs) {
+ if (!sourceFace || !Array.isArray(edgeInputs) || !edgeInputs.length) return [];
+ const out = [];
+ const used = new Set();
+ try {
+ const explorer = new oc.TopExp_Explorer_2(
+ sourceFace,
+ oc.TopAbs_ShapeEnum.TopAbs_EDGE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next()) {
+ const edge = oc.TopoDS.Edge_1(explorer.Current());
+ const name = matchEdgeInputToOccEdge(edgeInputs, edge);
+ if (!name) continue;
+ const key = `${name}:${out.length}`;
+ if (used.has(key)) continue;
+ used.add(key);
+ out.push({ edge, name });
+ }
+ } catch {
+ return [];
+ }
+ return out;
+}
+
+function generatedFaceNameFromSourceEdges(resultFace, namedSourceEdges, operation) {
+ if (!operation || typeof operation.Generated !== "function") return null;
+ for (const source of namedSourceEdges || []) {
+ try {
+ if (listContainsSameShape(operation.Generated(source.edge), resultFace)) return source.name;
+ } catch {
+ // Fall back to geometric naming below.
+ }
+ }
+ return null;
+}
+
+function bindRevolveFaceNames(state, params) {
+ state.faceNameByIndex = [];
+ new oc.BRepMesh_IncrementalMesh_2(state.shape, DEFAULT_DEFLECTION, false, DEFAULT_ANGLE, false);
+ const full = Math.abs(params.angleDegrees) >= 360 - 1e-6;
+ const namedSourceEdges = collectNamedSourceEdges(params.sourceFace, params.edgeInputs);
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const generatedName = generatedFaceNameFromSourceEdges(face, namedSourceEdges, params.operation);
+ if (generatedName) {
+ state.faceNameByIndex[faceIndex] = generatedName;
+ continue;
+ }
+ const stats = statsFromOccFace(face);
+ if (!full) {
+ const startPlaneDistance = Math.abs(dot(sub(stats.centroid, params.startOrigin), params.startNormal));
+ const endPlaneDistance = Math.abs(dot(sub(stats.centroid, params.endOrigin), params.endNormal));
+ const ds = Math.hypot(...sub(stats.centroid, params.startCentroid));
+ const de = Math.hypot(...sub(stats.centroid, params.endCentroid));
+ const tol = Math.max(1e-5, params.modelSize * 1e-4);
+ if (startPlaneDistance <= tol || ds <= tol || ds < de * 0.1) {
+ state.faceNameByIndex[faceIndex] = params.startName;
+ continue;
+ }
+ if (endPlaneDistance <= tol || de <= tol || de < ds * 0.1) {
+ state.faceNameByIndex[faceIndex] = params.endName;
+ continue;
+ }
+ }
+ state.faceNameByIndex[faceIndex] = classifyRevolveSideFace(stats, params);
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+export function makeRevolution({
+ sourceFace = null,
+ boundaryLoops = [],
+ faceName = "Face",
+ axisOrigin = [0, 0, 0],
+ axisDirection = [0, 1, 0],
+ angleDegrees = 360,
+ edgeInputs = [],
+ normal = [0, 0, 1],
+} = {}) {
+ const face = sourceFace || makeFaceFromBoundaryLoops(boundaryLoops, edgeInputs, normal);
+ const axisLen = Math.hypot(axisDirection[0], axisDirection[1], axisDirection[2]) || 1;
+ const ax = new oc.gp_Ax1_2(
+ new oc.gp_Pnt_3(axisOrigin[0], axisOrigin[1], axisOrigin[2]),
+ new oc.gp_Dir_4(axisDirection[0] / axisLen, axisDirection[1] / axisLen, axisDirection[2] / axisLen),
+ );
+ const angleRad = -(Number(angleDegrees) || 360) * Math.PI / 180;
+ const revol = new oc.BRepPrimAPI_MakeRevol_1(face, ax, angleRad, false);
+ if (typeof revol.IsDone === "function" && !revol.IsDone()) {
+ throw new Error("OpenCASCADE revolve failed.");
+ }
+
+ const full = Math.abs(Number(angleDegrees) || 360) >= 360 - 1e-6;
+ const startName = `${faceName}_START`;
+ const endName = `${faceName}_END`;
+ const defaultSideName = `${faceName}_RV`;
+ const segments = loopSegmentsWithNames(boundaryLoops, edgeInputs, defaultSideName, normal);
+ const faceMetadata = new Map();
+ if (!full) {
+ const endLoops = translatedLoops(boundaryLoops, [0, 0, 0]).map((loop) => ({
+ ...loop,
+ pts: loop.pts.map((point) => rotatePointAroundAxis(point, axisOrigin, axisDirection, angleRad)),
+ }));
+ const endEdges = (Array.isArray(edgeInputs) ? edgeInputs : []).map((edge) => ({
+ ...edge,
+ polyline: (Array.isArray(edge?.polyline) ? edge.polyline : []).map((point) => rotatePointAroundAxis(point, axisOrigin, axisDirection, angleRad)),
+ bezierPoles: Array.isArray(edge?.bezierPoles) ? edge.bezierPoles.map((point) => rotatePointAroundAxis(point, axisOrigin, axisDirection, angleRad)) : edge?.bezierPoles,
+ circleCenter: Array.isArray(edge?.circleCenter) ? rotatePointAroundAxis(edge.circleCenter, axisOrigin, axisDirection, angleRad) : edge?.circleCenter,
+ arcCenter: Array.isArray(edge?.arcCenter) ? rotatePointAroundAxis(edge.arcCenter, axisOrigin, axisDirection, angleRad) : edge?.arcCenter,
+ }));
+ const startCentroidForNormal = loopsCentroid(boundaryLoops);
+ const endCentroidForNormal = rotatePointAroundAxis(startCentroidForNormal, axisOrigin, axisDirection, angleRad);
+ const normalTip = addVecToPoint(startCentroidForNormal, normal);
+ const endNormalTip = rotatePointAroundAxis(normalTip, axisOrigin, axisDirection, angleRad);
+ faceMetadata.set(startName, {
+ faceType: "STARTCAP",
+ boundaryLoopsWorld: boundaryLoops,
+ sketchEdgeInputsWorld: edgeInputs,
+ profileNormal: normal,
+ });
+ faceMetadata.set(endName, {
+ faceType: "ENDCAP",
+ boundaryLoopsWorld: endLoops,
+ sketchEdgeInputsWorld: endEdges,
+ profileNormal: sub(endNormalTip, endCentroidForNormal),
+ });
+ }
+ for (const segment of segments) {
+ if (!segment.name) continue;
+ faceMetadata.set(segment.name, Object.keys(segment.metadata || {}).length ? segment.metadata : { faceType: "SIDEWALL" });
+ }
+ const startCentroid = loopsCentroid(boundaryLoops);
+ const endCentroid = rotatePointAroundAxis(startCentroid, axisOrigin, axisDirection, angleRad);
+ const startOrigin = (boundaryLoops || []).find((loop) => Array.isArray(loop?.pts) && loop.pts.length)?.pts?.[0] || startCentroid;
+ const endOrigin = rotatePointAroundAxis(startOrigin, axisOrigin, axisDirection, angleRad);
+ const startNormal = normalizeVector(normal);
+ const endNormal = normalizeVector(sub(rotatePointAroundAxis(addVecToPoint(startCentroid, startNormal), axisOrigin, axisDirection, angleRad), endCentroid));
+ const modelSize = Math.max(1, Math.hypot(...sub(startCentroid, axisOrigin)), Math.hypot(...sub(endCentroid, axisOrigin)));
+ const state = createOccState({
+ shape: revol.Shape(),
+ faceNames: uniqueNames([...(full ? [] : [startName, endName]), ...segments.map((segment) => segment.name)]),
+ faceMetadata,
+ feature: { kind: "revolve", faceName },
+ });
+ bindRevolveFaceNames(state, {
+ sourceFace: face,
+ edgeInputs,
+ operation: revol,
+ startName,
+ endName,
+ defaultSideName,
+ angleDegrees: Number(angleDegrees) || 360,
+ angleRad,
+ axisOrigin,
+ axisDirection,
+ startOrigin,
+ endOrigin,
+ startNormal,
+ endNormal,
+ startCentroid,
+ endCentroid,
+ modelSize,
+ segments,
+ });
+ return state;
+}
+
+export function makeSphere({ r = 1, name = "Sphere" } = {}) {
+ const shape = new oc.BRepPrimAPI_MakeSphere_1(r).Shape();
+ const faceMetadata = new Map([[name, { type: "spherical", radius: r }]]);
+ return createOccState({ shape, faceNames: [name], faceMetadata, primitive: { kind: "sphere", r, name } });
+}
+
+export function makeTorus({ mR = 2, tR = 0.5, arcDegrees = 360, name = "Torus" } = {}) {
+ const shape = new oc.BRepPrimAPI_MakeTorus_1(mR, tR).Shape();
+ const sideName = `${name}_Side`;
+ const faceMetadata = new Map([[sideName, { type: "toroidal", majorRadius: mR, tubeRadius: tR }]]);
+ const faceNames = Number(arcDegrees) >= 360 ? [sideName] : [sideName, `${name}_Cap0`, `${name}_Cap1`];
+ return createOccState({ shape, faceNames, faceMetadata, primitive: { kind: "torus", mR, tR, arcDegrees, name } });
+}
+
+function normalizePathPoints(points) {
+ const pts = (Array.isArray(points) ? points : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]);
+ const deduped = [];
+ for (const point of pts) {
+ const last = deduped[deduped.length - 1];
+ if (!last || pointDistanceSq(last, point) > 1e-18) deduped.push(point);
+ }
+ return deduped;
+}
+
+function pathIsStraight(points) {
+ if (!Array.isArray(points) || points.length <= 2) return true;
+ const a = points[0];
+ const b = points.find((point, index) => index > 0 && pointDistanceSq(a, point) > 1e-18);
+ if (!b) return true;
+ const axis = sub(b, a);
+ const axisLen = Math.hypot(axis[0], axis[1], axis[2]);
+ if (axisLen <= 1e-12) return true;
+ const tol = Math.max(1e-8, axisLen * 1e-6);
+ for (const point of points) {
+ const ap = sub(point, a);
+ const cross = [
+ (ap[1] * axis[2]) - (ap[2] * axis[1]),
+ (ap[2] * axis[0]) - (ap[0] * axis[2]),
+ (ap[0] * axis[1]) - (ap[1] * axis[0]),
+ ];
+ if (Math.hypot(cross[0], cross[1], cross[2]) / axisLen > tol) return false;
+ }
+ return true;
+}
+
+function makePolylineWire(points, closed = false) {
+ const pts = normalizePathPoints(points);
+ if (pts.length < 2) throw new Error("OpenCASCADE tube requires at least two path points.");
+ const wire = new oc.BRepBuilderAPI_MakeWire_1();
+ const addEdge = (a, b) => {
+ if (pointDistanceSq(a, b) <= 1e-18) return;
+ wire.Add_1(new oc.BRepBuilderAPI_MakeEdge_3(
+ new oc.gp_Pnt_3(a[0], a[1], a[2]),
+ new oc.gp_Pnt_3(b[0], b[1], b[2]),
+ ).Edge());
+ };
+ for (let i = 0; i < pts.length - 1; i += 1) addEdge(pts[i], pts[i + 1]);
+ if (closed && pointDistanceSq(pts[0], pts[pts.length - 1]) > 1e-18) addEdge(pts[pts.length - 1], pts[0]);
+ const result = wire.Wire();
+ if (!result || result.IsNull?.()) throw new Error("OpenCASCADE could not build tube path wire.");
+ return result;
+}
+
+function endpointFrameFromCurveEdge(edge) {
+ try {
+ const curve = new oc.BRepAdaptor_Curve_2(edge);
+ const firstParam = Number(curve.FirstParameter());
+ const lastParam = Number(curve.LastParameter());
+ if (!Number.isFinite(firstParam) || !Number.isFinite(lastParam)) return null;
+ const startPoint = new oc.gp_Pnt_1();
+ const endPoint = new oc.gp_Pnt_1();
+ const startVector = new oc.gp_Vec_1();
+ const endVector = new oc.gp_Vec_1();
+ curve.D1(firstParam, startPoint, startVector);
+ curve.D1(lastParam, endPoint, endVector);
+ return {
+ startPoint: [startPoint.X(), startPoint.Y(), startPoint.Z()],
+ endPoint: [endPoint.X(), endPoint.Y(), endPoint.Z()],
+ startTangent: normalizeVector([startVector.X(), startVector.Y(), startVector.Z()]),
+ endTangent: normalizeVector([endVector.X(), endVector.Y(), endVector.Z()]),
+ };
+ } catch {
+ return null;
+ }
+}
+
+function makeLineEdge(a, b) {
+ if (pointDistanceSq(a, b) <= 1e-18) return null;
+ const edge = new oc.BRepBuilderAPI_MakeEdge_3(
+ new oc.gp_Pnt_3(a[0], a[1], a[2]),
+ new oc.gp_Pnt_3(b[0], b[1], b[2]),
+ ).Edge();
+ return edge && !edge.IsNull?.() ? edge : null;
+}
+
+function normalizeCurvePoles(poles) {
+ return (Array.isArray(poles) ? poles : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]);
+}
+
+function makePoleArray(poles) {
+ const pts = normalizeCurvePoles(poles);
+ if (pts.length < 2) return null;
+ if (pointDistanceSq(pts[0], pts[pts.length - 1]) <= 1e-18) return null;
+ const arr = new oc.TColgp_Array1OfPnt_2(1, pts.length);
+ for (let i = 0; i < pts.length; i += 1) {
+ const point = pts[i];
+ arr.SetValue(i + 1, new oc.gp_Pnt_3(point[0], point[1], point[2]));
+ }
+ return arr;
+}
+
+function makeBezierEdge(poles) {
+ const pts = normalizeCurvePoles(poles);
+ if (pts.length < 2) return null;
+ if (pts.length > 4 && ((pts.length - 1) % 3) === 0) {
+ const splineEdge = makeCompositeCubicBezierEdge(pts);
+ if (splineEdge) return splineEdge;
+ }
+ const arr = makePoleArray(pts);
+ if (!arr) return null;
+ try {
+ const bezier = new oc.Geom_BezierCurve_1(arr);
+ const curve = new oc.Handle_Geom_Curve_2(bezier);
+ const edge = new oc.BRepBuilderAPI_MakeEdge_24(curve).Edge();
+ return edge && !edge.IsNull?.() ? edge : null;
+ } catch {
+ return null;
+ }
+}
+
+function makeCompositeCubicBezierEdge(poles) {
+ const pts = normalizeCurvePoles(poles);
+ const segmentCount = (pts.length - 1) / 3;
+ if (!Number.isInteger(segmentCount) || segmentCount < 1) return null;
+ try {
+ const poleArray = new oc.TColgp_Array1OfPnt_2(1, pts.length);
+ for (let i = 0; i < pts.length; i += 1) {
+ const p = pts[i];
+ poleArray.SetValue(i + 1, new oc.gp_Pnt_3(p[0], p[1], p[2]));
+ }
+ const knotCount = segmentCount + 1;
+ const knotArray = new oc.TColStd_Array1OfReal_2(1, knotCount);
+ const multArray = new oc.TColStd_Array1OfInteger_2(1, knotCount);
+ for (let i = 0; i < knotCount; i += 1) {
+ knotArray.SetValue(i + 1, i);
+ multArray.SetValue(i + 1, (i === 0 || i === knotCount - 1) ? 4 : 3);
+ }
+ const spline = new oc.Geom_BSplineCurve_1(poleArray, knotArray, multArray, 3, false);
+ const curve = new oc.Handle_Geom_Curve_2(spline);
+ const edge = new oc.BRepBuilderAPI_MakeEdge_24(curve).Edge();
+ return edge && !edge.IsNull?.() ? edge : null;
+ } catch {
+ return null;
+ }
+}
+
+function makeBSplinePathWire(points) {
+ const pts = normalizePathPoints(points);
+ if (pts.length < 3) return makePolylineWire(pts, false);
+ const arr = new oc.TColgp_Array1OfPnt_2(1, pts.length);
+ for (let i = 0; i < pts.length; i += 1) {
+ const point = pts[i];
+ arr.SetValue(i + 1, new oc.gp_Pnt_3(point[0], point[1], point[2]));
+ }
+ const maxDegree = Math.min(3, Math.max(2, pts.length - 1));
+ const minDegree = Math.min(3, maxDegree);
+ const continuity = maxDegree >= 3 ? oc.GeomAbs_Shape.GeomAbs_C2 : oc.GeomAbs_Shape.GeomAbs_C1;
+ const builder = new oc.GeomAPI_PointsToBSpline_2(arr, minDegree, maxDegree, continuity, 1e-6);
+ if (typeof builder.IsDone === "function" && !builder.IsDone()) {
+ throw new Error("OpenCASCADE could not build tube spline path.");
+ }
+ const bspline = builder.Curve();
+ const curve = new oc.Handle_Geom_Curve_2(bspline.get());
+ const edge = new oc.BRepBuilderAPI_MakeEdge_24(curve).Edge();
+ if (!edge || edge.IsNull?.()) throw new Error("OpenCASCADE could not build tube spline edge.");
+ const wire = new oc.BRepBuilderAPI_MakeWire_2(edge).Wire();
+ if (!wire || wire.IsNull?.()) throw new Error("OpenCASCADE could not build tube spline wire.");
+ return { wire, ...(endpointFrameFromCurveEdge(edge) || {}) };
+}
+
+function splinePointPosition(point) {
+ const src = Array.isArray(point?.position) ? point.position : [0, 0, 0];
+ return [Number(src[0]) || 0, Number(src[1]) || 0, Number(src[2]) || 0];
+}
+
+function splinePointDirection(point) {
+ const rotation = Array.isArray(point?.rotation) && point.rotation.length >= 3
+ ? point.rotation
+ : [1, 0, 0, 0, 1, 0, 0, 0, 1];
+ const sign = point?.flipDirection ? -1 : 1;
+ const direction = normalizeVector([
+ Number(rotation[0]) * sign || 0,
+ Number(rotation[1]) * sign || 0,
+ Number(rotation[2]) * sign || 0,
+ ]);
+ return Math.hypot(direction[0], direction[1], direction[2]) > 1e-12 ? direction : [1, 0, 0];
+}
+
+function splineExtensionPoint(anchor, pointData, isForward) {
+ const direction = splinePointDirection(pointData);
+ const rawDistance = isForward ? pointData?.forwardDistance : pointData?.backwardDistance;
+ const distance = rawDistance == null ? 1.0 : Math.max(0, Number(rawDistance) || 0);
+ return vectorAdd(anchor, vectorScale(direction, isForward ? distance : -distance));
+}
+
+function makeHermiteSplineWire(pathCurve, endpointExtension = 0) {
+ const pointsData = Array.isArray(pathCurve?.spline?.points) ? pathCurve.spline.points : [];
+ if (pointsData.length < 2) return null;
+ const clampedBendRadius = Math.max(0.1, Math.min(5.0, Number(pathCurve?.bendRadius) || 1.0));
+ const wire = new oc.BRepBuilderAPI_MakeWire_1();
+ const edges = [];
+ const addEdge = (edge) => {
+ if (!edge || edge.IsNull?.()) return;
+ wire.Add_1(edge);
+ edges.push(edge);
+ };
+ const addLine = (a, b) => addEdge(makeLineEdge(a, b));
+ const addBezier = (poles) => addEdge(makeBezierEdge(poles));
+ const anchors = pointsData.map((point) => splinePointPosition(point));
+ const extra = Math.max(0, Number(endpointExtension) || 0);
+
+ if (extra > 0) {
+ const startDir = splinePointDirection(pointsData[0]);
+ addLine(vectorAdd(anchors[0], vectorScale(startDir, -extra)), anchors[0]);
+ }
+
+ for (let i = 0; i < pointsData.length - 1; i += 1) {
+ const currentAnchor = anchors[i];
+ const nextAnchor = anchors[i + 1];
+ const currentData = pointsData[i];
+ const nextData = pointsData[i + 1];
+ const currentForwardExt = splineExtensionPoint(currentAnchor, currentData, true);
+ const nextBackwardExt = splineExtensionPoint(nextAnchor, nextData, false);
+
+ addLine(currentAnchor, currentForwardExt);
+
+ const currentExtDirection = normalizeVector(sub(currentForwardExt, currentAnchor));
+ const nextExtDirection = normalizeVector(sub(nextAnchor, nextBackwardExt));
+ const extDistance = Math.hypot(...sub(currentForwardExt, nextBackwardExt));
+ const avgExtDistance = ((Number(currentData?.forwardDistance) || 0) + (Number(nextData?.backwardDistance) || 0)) * 0.5;
+ const baseScale = Math.max(extDistance * 0.3, avgExtDistance * 0.5);
+ const tangentScale = baseScale * clampedBendRadius;
+ const t0 = vectorScale(currentExtDirection, tangentScale);
+ const t1 = vectorScale(nextExtDirection, tangentScale);
+ addBezier([
+ currentForwardExt,
+ vectorAdd(currentForwardExt, vectorScale(t0, 1 / 3)),
+ vectorAdd(nextBackwardExt, vectorScale(t1, -1 / 3)),
+ nextBackwardExt,
+ ]);
+
+ addLine(nextBackwardExt, nextAnchor);
+ }
+
+ if (extra > 0) {
+ const lastIndex = pointsData.length - 1;
+ const endDir = splinePointDirection(pointsData[lastIndex]);
+ addLine(anchors[lastIndex], vectorAdd(anchors[lastIndex], vectorScale(endDir, extra)));
+ }
+
+ if (!edges.length) return null;
+ const result = wire.Wire();
+ if (!result || result.IsNull?.()) throw new Error("OpenCASCADE could not build tube spline wire.");
+ return {
+ wire: result,
+ startPoint: extra > 0 ? vectorAdd(anchors[0], vectorScale(splinePointDirection(pointsData[0]), -extra)) : anchors[0],
+ endPoint: extra > 0 ? vectorAdd(anchors[anchors.length - 1], vectorScale(splinePointDirection(pointsData[pointsData.length - 1]), extra)) : anchors[anchors.length - 1],
+ startTangent: splinePointDirection(pointsData[0]),
+ endTangent: splinePointDirection(pointsData[pointsData.length - 1]),
+ };
+}
+
+function canBuildHermiteSplineWire(pathCurve) {
+ if (pathCurve?.type !== "hermite-extension-spline") return false;
+ const pointsData = Array.isArray(pathCurve?.spline?.points) ? pathCurve.spline.points : [];
+ if (pointsData.length < 2) return false;
+ return pointsData.every((point) => Array.isArray(point?.rotation) && point.rotation.length >= 9);
+}
+
+function vectorAdd(a, b) {
+ return [
+ Number(a?.[0] || 0) + Number(b?.[0] || 0),
+ Number(a?.[1] || 0) + Number(b?.[1] || 0),
+ Number(a?.[2] || 0) + Number(b?.[2] || 0),
+ ];
+}
+
+function vectorScale(v, scale) {
+ return [
+ Number(v?.[0] || 0) * scale,
+ Number(v?.[1] || 0) * scale,
+ Number(v?.[2] || 0) * scale,
+ ];
+}
+
+function makeTubeSpineWire(points, closed = false, pathCurve = null) {
+ const pts = normalizePathPoints(points);
+ if (pts.length < 2) throw new Error("OpenCASCADE tube requires at least two path points.");
+ const hermite = !closed && canBuildHermiteSplineWire(pathCurve)
+ ? makeHermiteSplineWire(pathCurve)
+ : null;
+ if (hermite) return hermite;
+ if (closed || pts.length <= 2 || pathIsStraight(pts)) {
+ return {
+ wire: makePolylineWire(pts, closed),
+ startPoint: pts[0],
+ endPoint: pts[pts.length - 1],
+ startTangent: normalizeVector(sub(pts[1], pts[0])),
+ endTangent: normalizeVector(sub(pts[pts.length - 1], pts[pts.length - 2])),
+ };
+ }
+ return makeBSplinePathWire(pts);
+}
+
+function tubePathPrimitiveData(pathPoints, pathCurve = null, endpointExtension = 0, spineFrame = null) {
+ const pointsData = Array.isArray(pathCurve?.spline?.points) ? pathCurve.spline.points : [];
+ const sampledStart = Array.isArray(spineFrame?.startPoint) ? spineFrame.startPoint : pathPoints[0];
+ const sampledEnd = Array.isArray(spineFrame?.endPoint) ? spineFrame.endPoint : pathPoints[pathPoints.length - 1];
+ const sampledStartTangent = normalizeVector(sub(
+ Array.isArray(spineFrame?.startTangent) ? vectorAdd(sampledStart, spineFrame.startTangent) : (pathPoints.find((point, index) => index > 0 && pointDistanceSq(sampledStart, point) > 1e-18) || pathPoints[1]),
+ sampledStart,
+ ));
+ const sampledEndTangent = normalizeVector(sub(
+ sampledEnd,
+ Array.isArray(spineFrame?.endTangent) ? vectorAdd(sampledEnd, vectorScale(spineFrame.endTangent, -1)) : ([...pathPoints].reverse().find((point, index) => index > 0 && pointDistanceSq(sampledEnd, point) > 1e-18) || pathPoints[pathPoints.length - 2]),
+ ));
+ if (pathCurve?.type === "hermite-extension-spline" && pointsData.length >= 2) {
+ const extra = Math.max(0, Number(endpointExtension) || 0);
+ return {
+ points: pathPoints,
+ startPoint: extra > 0 ? vectorAdd(sampledStart, vectorScale(sampledStartTangent, -extra)) : sampledStart,
+ endPoint: extra > 0 ? vectorAdd(sampledEnd, vectorScale(sampledEndTangent, extra)) : sampledEnd,
+ startTangent: sampledStartTangent,
+ endTangent: sampledEndTangent,
+ };
+ }
+ return { points: pathPoints, startTangent: sampledStartTangent, endTangent: sampledEndTangent };
+}
+
+function makeCircleWire(center, normal, xDirection, radius) {
+ const n = normalizeVector(normal);
+ const x = normalizeVector(xDirection);
+ const axis = new oc.gp_Ax2_2(
+ new oc.gp_Pnt_3(center[0], center[1], center[2]),
+ new oc.gp_Dir_4(n[0], n[1], n[2]),
+ new oc.gp_Dir_4(x[0], x[1], x[2]),
+ );
+ return new oc.BRepBuilderAPI_MakeWire_2(
+ new oc.BRepBuilderAPI_MakeEdge_8(new oc.gp_Circ_2(axis, radius)).Edge(),
+ ).Wire();
+}
+
+function tubeProfileFrame(points, pathCurve = null, endpointExtension = 0, spineFrame = null) {
+ const pathData = tubePathPrimitiveData(points, pathCurve, endpointExtension, spineFrame);
+ const start = Array.isArray(pathData.startPoint) ? pathData.startPoint : points[0];
+ const tangent = normalizeVector(
+ Array.isArray(pathData.startTangent)
+ ? pathData.startTangent
+ : sub(
+ points.find((point, index) => index > 0 && pointDistanceSq(start, point) > 1e-18) || points[1],
+ start,
+ ),
+ );
+ let xDir = Math.abs(tangent[0]) < 0.8 ? [1, 0, 0] : [0, 1, 0];
+ const tangentDot = dot(xDir, tangent);
+ xDir = normalizeVector([
+ xDir[0] - tangent[0] * tangentDot,
+ xDir[1] - tangent[1] * tangentDot,
+ xDir[2] - tangent[2] * tangentDot,
+ ]);
+ return { start, tangent, xDir, pathData };
+}
+
+function reverseWire(wire) {
+ try {
+ return oc.TopoDS.Wire_1(wire.Reversed());
+ } catch {
+ return wire;
+ }
+}
+
+function tubeProfileFace(points, outerRadius, innerRadius = 0, pathCurve = null, endpointExtension = 0, spineFrame = null) {
+ const { start, tangent, xDir, pathData } = tubeProfileFrame(points, pathCurve, endpointExtension, spineFrame);
+ const outerWire = makeCircleWire(start, tangent, xDir, outerRadius);
+ const faceMaker = new oc.BRepBuilderAPI_MakeFace_15(outerWire, true);
+ const insideRadius = Math.max(0, Number(innerRadius) || 0);
+ if (insideRadius > 0) {
+ faceMaker.Add(reverseWire(makeCircleWire(start, tangent, xDir, insideRadius)));
+ }
+ if (typeof faceMaker.IsDone === "function" && !faceMaker.IsDone()) {
+ throw new Error("OpenCASCADE could not build tube profile face.");
+ }
+ return {
+ face: oc.TopoDS.Face_1(faceMaker.Shape()),
+ pathData,
+ };
+}
+
+function makePipeShape(spine, profile) {
+ const pipe = new oc.BRepOffsetAPI_MakePipe_1(spine, profile);
+ if (typeof pipe.IsDone === "function" && !pipe.IsDone()) throw new Error("OpenCASCADE tube pipe failed.");
+ return pipe.Shape();
+}
+
+function centroidOfLoops(loops) {
+ const points = [];
+ for (const loop of loops || []) {
+ const arr = Array.isArray(loop?.pts) ? loop.pts : loop;
+ if (!Array.isArray(arr)) continue;
+ for (const point of arr) {
+ if (Array.isArray(point) && point.length >= 3) points.push(point);
+ }
+ }
+ if (!points.length) return [0, 0, 0];
+ const sum = [0, 0, 0];
+ for (const point of points) {
+ sum[0] += Number(point[0]) || 0;
+ sum[1] += Number(point[1]) || 0;
+ sum[2] += Number(point[2]) || 0;
+ }
+ return [sum[0] / points.length, sum[1] / points.length, sum[2] / points.length];
+}
+
+function cross(a, b) {
+ return [
+ (Number(a?.[1] || 0) * Number(b?.[2] || 0)) - (Number(a?.[2] || 0) * Number(b?.[1] || 0)),
+ (Number(a?.[2] || 0) * Number(b?.[0] || 0)) - (Number(a?.[0] || 0) * Number(b?.[2] || 0)),
+ (Number(a?.[0] || 0) * Number(b?.[1] || 0)) - (Number(a?.[1] || 0) * Number(b?.[0] || 0)),
+ ];
+}
+
+function rotateVectorBetween(point, fromNormal, toNormal, origin) {
+ const from = normalizeVector(fromNormal);
+ const to = normalizeVector(toNormal);
+ const p = sub(point, origin);
+ const fromLen = Math.hypot(from[0], from[1], from[2]);
+ const toLen = Math.hypot(to[0], to[1], to[2]);
+ if (fromLen <= 1e-12 || toLen <= 1e-12) return point.slice();
+
+ const c = Math.max(-1, Math.min(1, dot(from, to)));
+ if (c > 1 - 1e-12) return point.slice();
+
+ let axis = cross(from, to);
+ let s = Math.hypot(axis[0], axis[1], axis[2]);
+ if (s <= 1e-12) {
+ axis = Math.abs(from[0]) < 0.8 ? cross(from, [1, 0, 0]) : cross(from, [0, 1, 0]);
+ s = Math.hypot(axis[0], axis[1], axis[2]);
+ }
+ axis = [axis[0] / s, axis[1] / s, axis[2] / s];
+ const angle = Math.acos(c);
+ const cosA = Math.cos(angle);
+ const sinA = Math.sin(angle);
+ const axisDot = dot(axis, p);
+ const axisCrossP = cross(axis, p);
+ return [
+ origin[0] + (p[0] * cosA) + (axisCrossP[0] * sinA) + (axis[0] * axisDot * (1 - cosA)),
+ origin[1] + (p[1] * cosA) + (axisCrossP[1] * sinA) + (axis[1] * axisDot * (1 - cosA)),
+ origin[2] + (p[2] * cosA) + (axisCrossP[2] * sinA) + (axis[2] * axisDot * (1 - cosA)),
+ ];
+}
+
+function rotateAroundAxis(point, axisOrigin, axisDirection, angleRadians) {
+ const axis = normalizeVector(axisDirection);
+ if (Math.hypot(axis[0], axis[1], axis[2]) <= 1e-12 || Math.abs(angleRadians) <= 1e-12) return point.slice();
+ const p = sub(point, axisOrigin);
+ const cosA = Math.cos(angleRadians);
+ const sinA = Math.sin(angleRadians);
+ const axisDot = dot(axis, p);
+ const axisCrossP = cross(axis, p);
+ return [
+ axisOrigin[0] + (p[0] * cosA) + (axisCrossP[0] * sinA) + (axis[0] * axisDot * (1 - cosA)),
+ axisOrigin[1] + (p[1] * cosA) + (axisCrossP[1] * sinA) + (axis[1] * axisDot * (1 - cosA)),
+ axisOrigin[2] + (p[2] * cosA) + (axisCrossP[2] * sinA) + (axis[2] * axisDot * (1 - cosA)),
+ ];
+}
+
+function transformPointForSweep(point, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile) {
+ const rotated = rotateProfile
+ ? rotateVectorBetween(point, sourceNormal, targetNormal, profileCentroid)
+ : point.slice();
+ return [
+ rotated[0] + targetStart[0] - profileCentroid[0],
+ rotated[1] + targetStart[1] - profileCentroid[1],
+ rotated[2] + targetStart[2] - profileCentroid[2],
+ ];
+}
+
+function transformSweepLoops(loops, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile) {
+ return (Array.isArray(loops) ? loops : []).map((loop) => ({
+ ...loop,
+ pts: (Array.isArray(loop?.pts) ? loop.pts : loop || [])
+ .map((point) => transformPointForSweep(point, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile)),
+ }));
+}
+
+function transformSweepEdgeInputs(edgeInputs, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile) {
+ const xform = (point) => (
+ Array.isArray(point) && point.length >= 3
+ ? transformPointForSweep(point, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile)
+ : point
+ );
+ return (Array.isArray(edgeInputs) ? edgeInputs : []).map((edge) => ({
+ ...edge,
+ polyline: (Array.isArray(edge?.polyline) ? edge.polyline : []).map(xform),
+ bezierPoles: Array.isArray(edge?.bezierPoles) ? edge.bezierPoles.map(xform) : edge?.bezierPoles,
+ circleCenter: xform(edge?.circleCenter),
+ arcCenter: xform(edge?.arcCenter),
+ }));
+}
+
+function sweepPathPointsForMode(pathPoints, profileCentroid, mode) {
+ const pts = normalizePathPoints(pathPoints);
+ if (pts.length < 2) throw new Error("OpenCASCADE sweep requires at least two path points.");
+ if (mode === "pathAlign") return pts;
+ const start = pts[0];
+ return pts.map((point) => [
+ profileCentroid[0] + point[0] - start[0],
+ profileCentroid[1] + point[1] - start[1],
+ profileCentroid[2] + point[2] - start[2],
+ ]);
+}
+
+function sweepSideNames(edgeInputs, featureID) {
+ const out = [];
+ for (let i = 0; i < (edgeInputs || []).length; i += 1) {
+ const edge = edgeInputs[i];
+ const raw = String(edge?.name || edge?.sketchGeometryId || "").trim();
+ out.push(raw || `${featureID}_SIDE_${i}`);
+ }
+ return uniqueNames(out);
+}
+
+function pathSegmentLengths(points) {
+ const lengths = [];
+ let total = 0;
+ for (let i = 0; i < points.length - 1; i += 1) {
+ const len = Math.hypot(...sub(points[i + 1], points[i]));
+ lengths.push(len);
+ total += len;
+ }
+ return { lengths, total };
+}
+
+function sampleSweepPath(points, count) {
+ const { lengths, total } = pathSegmentLengths(points);
+ if (!(total > 1e-12)) return [];
+ const samples = [];
+ const sampleCount = Math.max(2, Math.floor(count) || 2);
+ for (let i = 0; i < sampleCount; i += 1) {
+ const t = sampleCount === 1 ? 0 : i / (sampleCount - 1);
+ let distance = total * t;
+ let segIndex = 0;
+ while (segIndex < lengths.length - 1 && distance > lengths[segIndex]) {
+ distance -= lengths[segIndex];
+ segIndex += 1;
+ }
+ const a = points[segIndex];
+ const b = points[segIndex + 1];
+ const len = lengths[segIndex] || 1;
+ const u = Math.max(0, Math.min(1, distance / len));
+ const point = [
+ a[0] + (b[0] - a[0]) * u,
+ a[1] + (b[1] - a[1]) * u,
+ a[2] + (b[2] - a[2]) * u,
+ ];
+ samples.push({
+ t,
+ point,
+ tangent: normalizeVector(sub(b, a)),
+ });
+ }
+ return samples;
+}
+
+function makeTwistedSweepLoftForLoop(loop, normal, spinePoints, mode, twistAngle, name) {
+ const profileCentroid = centroidOfLoops([loop]);
+ const sourceNormal = finiteNormalOrFallback(normal, loop.pts);
+ const { total } = pathSegmentLengths(spinePoints);
+ const sectionCount = Math.min(80, Math.max(6, Math.ceil(total / 2) + 1, Math.ceil(Math.abs(twistAngle) / 12) + 2));
+ const samples = sampleSweepPath(spinePoints, sectionCount);
+ if (samples.length < 2) throw new Error("OpenCASCADE twisted sweep path is too short.");
+
+ const op = new oc.BRepOffsetAPI_ThruSections(true, false, 1e-6);
+ for (const sample of samples) {
+ const targetNormal = mode === "pathAlign" ? sample.tangent : sourceNormal;
+ const aligned = loop.pts.map((point) => (
+ transformPointForSweep(point, profileCentroid, sample.point, sourceNormal, targetNormal, mode === "pathAlign")
+ ));
+ const twisted = aligned.map((point) => rotateAroundAxis(point, sample.point, targetNormal, (twistAngle * Math.PI / 180) * sample.t));
+ op.AddWire(makeLoftSectionWire(twisted, { loops: [{ pts: twisted, isHole: false }], normal: targetNormal }));
+ }
+ op.Build();
+ if (typeof op.IsDone === "function" && !op.IsDone()) {
+ throw new Error("OpenCASCADE twisted sweep loft failed.");
+ }
+ const shape = op.Shape();
+ if (!shape || shape.IsNull?.()) throw new Error(`OpenCASCADE twisted sweep "${name || "Sweep"}" produced an empty shape.`);
+ return shape;
+}
+
+function makeTwistedSweepShape({ boundaryLoops, normal, spinePoints, mode, twistAngle, name }) {
+ const loops = (Array.isArray(boundaryLoops) ? boundaryLoops : [])
+ .map((loop) => ({ ...loop, pts: Array.isArray(loop?.pts) ? loop.pts : loop }))
+ .filter((loop) => Array.isArray(loop.pts) && loop.pts.length >= 3);
+ const solidLoops = loops.filter((loop) => !loop.isHole);
+ const holeLoops = loops.filter((loop) => !!loop.isHole);
+ if (solidLoops.length !== 1) {
+ throw new Error("OpenCASCADE twisted sweep requires one solid profile loop at a time.");
+ }
+
+ let shape = makeTwistedSweepLoftForLoop(solidLoops[0], normal, spinePoints, mode, twistAngle, name);
+ for (const holeLoop of holeLoops) {
+ const holeShape = makeTwistedSweepLoftForLoop(holeLoop, normal, spinePoints, mode, twistAngle, `${name || "Sweep"}_HOLE`);
+ const cut = new oc.BRepAlgoAPI_Cut_3(shape, holeShape);
+ cut.Build();
+ if (typeof cut.IsDone === "function" && !cut.IsDone()) {
+ throw new Error("OpenCASCADE twisted sweep hole cut failed.");
+ }
+ const nextShape = cut.Shape();
+ if (!nextShape || nextShape.IsNull?.()) throw new Error("OpenCASCADE twisted sweep hole cut produced an empty shape.");
+ shape = nextShape;
+ }
+ return shape;
+}
+
+export function makePathSweep({
+ boundaryLoops = [],
+ edgeInputs = [],
+ normal = [0, 0, 1],
+ pathPoints = [],
+ mode = "translate",
+ name = "Sweep",
+ faceName = "Face",
+ omitBaseCap = false,
+ twistAngle = 0,
+} = {}) {
+ const normalizedPath = normalizePathPoints(pathPoints);
+ if (normalizedPath.length < 2) throw new Error("OpenCASCADE path sweep requires a path edge.");
+ const twist = Number(twistAngle) || 0;
+
+ const profileCentroid = centroidOfLoops(boundaryLoops);
+ const pathMode = mode === "pathAlign" ? "pathAlign" : "translate";
+ const spinePoints = sweepPathPointsForMode(normalizedPath, profileCentroid, pathMode);
+ const sourceNormal = finiteNormalOrFallback(normal, boundaryLoops?.[0]?.pts || boundaryLoops?.[0] || []);
+ const closed = pointDistanceSq(spinePoints[0], spinePoints[spinePoints.length - 1]) <= 1e-18;
+ let shape = null;
+ if (Math.abs(twist) > 1e-12) {
+ shape = makeTwistedSweepShape({
+ boundaryLoops,
+ normal: sourceNormal,
+ spinePoints,
+ mode: pathMode,
+ twistAngle: twist,
+ name,
+ });
+ } else {
+ const targetStart = spinePoints[0];
+ const startTangent = normalizeVector(sub(spinePoints[1], spinePoints[0]));
+ const targetNormal = pathMode === "pathAlign" ? startTangent : sourceNormal;
+ const rotateProfile = pathMode === "pathAlign";
+ const profileLoops = transformSweepLoops(boundaryLoops, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile);
+ const profileEdges = transformSweepEdgeInputs(edgeInputs, profileCentroid, targetStart, sourceNormal, targetNormal, rotateProfile);
+ const profileFace = makeFaceFromBoundaryLoops(profileLoops, profileEdges, targetNormal);
+ const spine = makeTubeSpineWire(spinePoints, closed, null);
+ shape = makePipeShape(spine.wire, profileFace);
+ }
+ const featureTag = name ? `${name}:` : "";
+ const startName = `${featureTag}${faceName}_START`;
+ const endName = `${featureTag}${faceName}_END`;
+ const sideNames = sweepSideNames(edgeInputs, name || "SWEEP");
+ const capNames = closed || omitBaseCap ? [] : [startName, endName];
+ const faceMetadata = new Map();
+ for (const sideName of sideNames) faceMetadata.set(sideName, { faceType: "SIDEWALL" });
+ if (!closed && !omitBaseCap) {
+ faceMetadata.set(startName, { faceType: "STARTCAP" });
+ faceMetadata.set(endName, { faceType: "ENDCAP" });
+ }
+ return createOccState({
+ shape,
+ faceNames: uniqueNames([...capNames, ...sideNames]),
+ faceMetadata,
+ meshOptions: { deflection: 0.08, angle: 0.35 },
+ feature: {
+ kind: "sweep",
+ name,
+ faceName,
+ mode: pathMode,
+ pathPoints: spinePoints,
+ profileCentroid,
+ },
+ });
+}
+
+export function makeTube({ points = [], radius = 1, innerRadius = 0, closed = false, name = "Tube", pathCurve = null, endpointExtension = 0 } = {}) {
+ const pathPoints = normalizePathPoints(points);
+ const outerRadius = Number(radius);
+ const insideRadius = Math.max(0, Number(innerRadius) || 0);
+ if (!(outerRadius > 0)) throw new Error("OpenCASCADE tube requires a positive outer radius.");
+ if (insideRadius >= outerRadius) throw new Error("OpenCASCADE tube inner radius must be smaller than the outer radius.");
+ const spine = makeTubeSpineWire(pathPoints, closed, pathCurve);
+ const { face: profile, pathData } = tubeProfileFace(pathPoints, outerRadius, insideRadius, pathCurve, endpointExtension, spine);
+ const shape = makePipeShape(spine.wire, profile);
+ const faceNames = insideRadius > 0
+ ? [`${name}_Outer`, `${name}_Inner`, ...(closed ? [] : [`${name}_CapStart`, `${name}_CapEnd`])]
+ : [`${name}_Outer`, ...(closed ? [] : [`${name}_CapStart`, `${name}_CapEnd`])];
+ const faceMetadata = new Map([
+ [`${name}_Outer`, { type: "tube", radius: outerRadius, innerRadius: insideRadius }],
+ ]);
+ if (insideRadius > 0) faceMetadata.set(`${name}_Inner`, { type: "tube_inner", radius: insideRadius, outerRadius });
+ const state = createOccState({
+ shape,
+ faceNames,
+ faceMetadata,
+ meshOptions: {
+ deflection: Math.max(outerRadius * 0.1, 0.04),
+ angle: 0.35,
+ },
+ primitive: {
+ kind: "tube",
+ ...pathData,
+ radius: outerRadius,
+ innerRadius: insideRadius,
+ closed: !!closed,
+ name,
+ },
+ });
+ bindPrimitiveFaceNames(state);
+ return state;
+}
+
+export function transformOccState(state, matrixLike) {
+ const elements = Array.from(matrixLike?.elements || []);
+ if (!state?.shape || elements.length !== 16) return state;
+ const trsf = new oc.gp_Trsf_1();
+ trsf.SetValues(
+ elements[0], elements[4], elements[8], elements[12],
+ elements[1], elements[5], elements[9], elements[13],
+ elements[2], elements[6], elements[10], elements[14],
+ );
+ const transformed = new oc.BRepBuilderAPI_Transform_2(state.shape, trsf, true).Shape();
+ return createOccState({
+ ...state,
+ shape: transformed,
+ });
+}
+
+function uniqueNames(names) {
+ const seen = new Set();
+ const out = [];
+ for (const name of names || []) {
+ const value = String(name || "").trim();
+ if (!value || seen.has(value)) continue;
+ seen.add(value);
+ out.push(value);
+ }
+ return out;
+}
+
+function cloneMetadataMaps(left, right) {
+ return {
+ faceMetadata: new Map([
+ ...(left?._faceMetadata instanceof Map ? left._faceMetadata.entries() : []),
+ ...(right?._faceMetadata instanceof Map ? right._faceMetadata.entries() : []),
+ ]),
+ edgeMetadata: new Map([
+ ...(left?._edgeMetadata instanceof Map ? left._edgeMetadata.entries() : []),
+ ...(right?._edgeMetadata instanceof Map ? right._edgeMetadata.entries() : []),
+ ]),
+ };
+}
+
+function cloneMetadataMapsMany(solids) {
+ const faceMetadata = new Map();
+ const edgeMetadata = new Map();
+ for (const solid of solids || []) {
+ for (const entry of solid?._faceMetadata instanceof Map ? solid._faceMetadata.entries() : []) {
+ faceMetadata.set(entry[0], entry[1]);
+ }
+ for (const entry of solid?._edgeMetadata instanceof Map ? solid._edgeMetadata.entries() : []) {
+ edgeMetadata.set(entry[0], entry[1]);
+ }
+ }
+ return { faceMetadata, edgeMetadata };
+}
+
+function getFaceNameForIndex(state, index, stats = null) {
+ if (Array.isArray(state?.faceNameByIndex) && state.faceNameByIndex[index]) return state.faceNameByIndex[index];
+ const classified = classifyPrimitiveFace(state, stats);
+ if (classified) {
+ if (!Array.isArray(state.faceNameByIndex)) state.faceNameByIndex = [];
+ state.faceNameByIndex[index] = classified;
+ return classified;
+ }
+ const names = Array.isArray(state?.faceNames) ? state.faceNames : [];
+ return names[index] || names[names.length - 1] || `FACE_${index + 1}`;
+}
+
+function bindPrimitiveFaceNames(state) {
+ state.meshCache = null;
+ state.faceNameByIndex = null;
+ tessellateOccState(state, { bindOnly: true });
+ state.meshCache = null;
+}
+
+function classifyPrimitiveFace(state, stats) {
+ const primitive = state?.primitive || null;
+ const name = primitive?.name || "";
+ if (!primitive || !stats) return null;
+ const c = stats.centroid;
+ if (primitive.kind === "box") {
+ const candidates = [
+ [`${name}_NX`, Math.abs(c[0])],
+ [`${name}_PX`, Math.abs(c[0] - primitive.x)],
+ [`${name}_NY`, Math.abs(c[1])],
+ [`${name}_PY`, Math.abs(c[1] - primitive.y)],
+ [`${name}_NZ`, Math.abs(c[2])],
+ [`${name}_PZ`, Math.abs(c[2] - primitive.z)],
+ ];
+ candidates.sort((a, b) => a[1] - b[1]);
+ return candidates[0]?.[0] || null;
+ }
+ if (primitive.kind === "cylinder") {
+ if (Math.abs(c[1]) < 1e-6) return `${name}_B`;
+ if (Math.abs(c[1] - primitive.height) < 1e-6) return `${name}_T`;
+ return `${name}_S`;
+ }
+ if (primitive.kind === "cone") {
+ if (Math.abs(c[1]) < 1e-6) return `${name}_B`;
+ if (Math.abs(c[1] - primitive.h) < 1e-6) return `${name}_T`;
+ return `${name}_S`;
+ }
+ if (primitive.kind === "tube") {
+ const points = Array.isArray(primitive.points) ? primitive.points : [];
+ const first = Array.isArray(primitive.startPoint) ? primitive.startPoint : points[0];
+ const last = Array.isArray(primitive.endPoint) ? primitive.endPoint : points[points.length - 1];
+ const closed = !!primitive.closed;
+ if (!closed && first && points[1] && last && points.length >= 2) {
+ const startDir = normalizeVector(primitive.startTangent || sub(points[1], first));
+ const endDir = normalizeVector(primitive.endTangent || sub(last, points[points.length - 2]));
+ const scale = Math.max(Number(primitive.radius) || 1, 1);
+ if (pointDistanceSq(c, first) <= scale * scale * 0.25) return `${name}_CapStart`;
+ if (pointDistanceSq(c, last) <= scale * scale * 0.25) return `${name}_CapEnd`;
+ if (Math.abs(dot(sub(c, first), startDir)) <= scale * 1e-3) return `${name}_CapStart`;
+ if (Math.abs(dot(sub(c, last), endDir)) <= scale * 1e-3) return `${name}_CapEnd`;
+ }
+ if ((Number(primitive.innerRadius) || 0) > 0) {
+ const midpointRadius = ((Number(primitive.radius) || 0) + (Number(primitive.innerRadius) || 0)) * 0.5;
+ const averageRadius = averageDistanceToPath(stats.points, points, closed);
+ if (Number.isFinite(averageRadius)) {
+ return averageRadius < midpointRadius ? `${name}_Inner` : `${name}_Outer`;
+ }
+ const closest = closestPathPoint(c, points, closed);
+ const radial = normalizeVector(sub(c, closest?.point || c));
+ const normal = normalizeVector(stats.normal);
+ const normalDot = dot(normal, radial);
+ if (Math.abs(normalDot) > 1e-6) {
+ return normalDot < 0 ? `${name}_Inner` : `${name}_Outer`;
+ }
+ const dist2 = closest?.dist2 ?? Infinity;
+ return Math.sqrt(dist2) < midpointRadius ? `${name}_Inner` : `${name}_Outer`;
+ }
+ return `${name}_Outer`;
+ }
+ if (primitive.kind === "pyramid") {
+ if (Math.abs(c[1]) < 1e-6) return `${name}_Base`;
+ const sides = Math.max(3, Math.floor(Number(primitive.s) || 4));
+ let angle = Math.atan2(c[2], c[0]);
+ if (angle < 0) angle += Math.PI * 2;
+ const index = Math.floor((angle / (Math.PI * 2)) * sides + 1e-9) % sides;
+ return `${name}_S[${index}]`;
+ }
+ if (primitive.kind === "sphere") return name;
+ if (primitive.kind === "torus") return `${name}_Side`;
+ return null;
+}
+
+function makeFaceStats(points) {
+ if (!points.length) return { centroid: [0, 0, 0] };
+ const sum = [0, 0, 0];
+ for (const point of points) {
+ sum[0] += point[0];
+ sum[1] += point[1];
+ sum[2] += point[2];
+ }
+ return { centroid: [sum[0] / points.length, sum[1] / points.length, sum[2] / points.length] };
+}
+
+function pointWithLocation(point, location) {
+ try {
+ if (location && !location.IsIdentity?.()) {
+ const transformed = point.Transformed(location.Transformation());
+ return [transformed.X(), transformed.Y(), transformed.Z()];
+ }
+ } catch {
+ // Fall back to the raw triangulation node.
+ }
+ return [point.X(), point.Y(), point.Z()];
+}
+
+function occFaceIsForward(face) {
+ try {
+ return face.Orientation_1() === oc.TopAbs_Orientation.TopAbs_FORWARD;
+ } catch {
+ return true;
+ }
+}
+
+function occFaceNeedsTriangleReverse(face) {
+ return !occFaceIsForward(face);
+}
+
+function occFaceTriangulation(face, location) {
+ try {
+ return oc.BRep_Tool.Triangulation(face, location, 0);
+ } catch {
+ return oc.BRep_Tool.Triangulation(face, location);
+ }
+}
+
+function cleanOccTriangulation(shape) {
+ try {
+ if (shape && typeof oc.BRepTools?.Clean === "function") oc.BRepTools.Clean(shape);
+ } catch {
+ // Some OCCT.js builds may omit this binding; remeshing still works when the
+ // shape has no cached Poly_Triangulation yet.
+ }
+}
+
+function statsFromOccFace(face) {
+ const points = [];
+ try {
+ const location = new oc.TopLoc_Location_1();
+ const handle = occFaceTriangulation(face, location);
+ if (!handle || handle.IsNull()) return { centroid: [0, 0, 0], points: [], normal: surfaceNormalFromOccFace(face) || [0, 0, 0] };
+ const triangulation = handle.get();
+ for (let i = 1; i <= triangulation.NbNodes(); i += 1) {
+ const point = triangulation.Node(i);
+ points.push(pointWithLocation(point, location));
+ }
+ } catch {
+ // Fall through to the zero-centroid result below.
+ }
+ return { ...makeFaceStats(points), points, normal: surfaceNormalFromOccFace(face) || [0, 0, 0] };
+}
+
+function occFaceSurfaceTypeValue(face) {
+ try {
+ const adaptor = new oc.BRepAdaptor_Surface_2(face, true);
+ return Number(adaptor.GetType?.()?.value);
+ } catch {
+ return NaN;
+ }
+}
+
+function normalizeVector(v) {
+ const len = Math.hypot(v?.[0] || 0, v?.[1] || 0, v?.[2] || 0);
+ return len > 1e-12 ? [v[0] / len, v[1] / len, v[2] / len] : [0, 0, 0];
+}
+
+function triangleNormal(p0, p1, p2) {
+ const ux = p1[0] - p0[0], uy = p1[1] - p0[1], uz = p1[2] - p0[2];
+ const vx = p2[0] - p0[0], vy = p2[1] - p0[1], vz = p2[2] - p0[2];
+ return [uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx];
+}
+
+function polygonPlaneNormal(points) {
+ const pts = Array.isArray(points) ? points : [];
+ const accum = [0, 0, 0];
+ for (let i = 0; i < pts.length; i += 1) {
+ const a = pts[i];
+ const b = pts[(i + 1) % pts.length];
+ if (!a || !b) continue;
+ accum[0] += (Number(a[1] || 0) - Number(b[1] || 0)) * (Number(a[2] || 0) + Number(b[2] || 0));
+ accum[1] += (Number(a[2] || 0) - Number(b[2] || 0)) * (Number(a[0] || 0) + Number(b[0] || 0));
+ accum[2] += (Number(a[0] || 0) - Number(b[0] || 0)) * (Number(a[1] || 0) + Number(b[1] || 0));
+ }
+ return normalizeVector(accum);
+}
+
+function finiteNormalOrFallback(normal, fallbackPoints) {
+ const n = normalizeVector(normal);
+ if (Math.hypot(n[0], n[1], n[2]) > 1e-12) return n;
+ const fallback = polygonPlaneNormal(fallbackPoints);
+ if (Math.hypot(fallback[0], fallback[1], fallback[2]) > 1e-12) return fallback;
+ return [0, 0, 1];
+}
+
+function collectNamedOccFaces(solid, options = {}) {
+ const state = solid?._occ;
+ if (!state?.shape) return [];
+ state.faceNameToID = solid._faceNameToID;
+ tessellateOccState(state, options);
+
+ const faces = [];
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ faces.push({
+ name: getFaceNameForIndex(state, faceIndex),
+ face,
+ surfaceType: occFaceSurfaceTypeValue(face),
+ ...statsFromOccFace(face),
+ });
+ }
+ return faces;
+}
+
+function listContainsSameShape(list, shape) {
+ if (!list || !shape) return false;
+ const size = Number(list.Size?.() || 0);
+ if (size <= 0) return false;
+ try {
+ for (const candidate of [list.First_1?.(), list.First_2?.()]) {
+ if (candidate && (candidate.IsSame?.(shape) || candidate.IsEqual?.(shape))) return true;
+ }
+ } catch {
+ // The list API varies by binding; fall back to geometric matching.
+ }
+ return false;
+}
+
+function occVerticesForShape(shape) {
+ const vertices = [];
+ if (!shape) return vertices;
+ try {
+ const explorer = new oc.TopExp_Explorer_2(
+ shape,
+ oc.TopAbs_ShapeEnum.TopAbs_VERTEX,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next()) {
+ vertices.push(oc.TopoDS.Vertex_1(explorer.Current()));
+ }
+ } catch {
+ return [];
+ }
+ return vertices;
+}
+
+function occVertexPoint(vertex) {
+ if (!vertex) return null;
+ try {
+ const p = oc.BRep_Tool.Pnt(vertex);
+ return [p.X(), p.Y(), p.Z()];
+ } catch {
+ return null;
+ }
+}
+
+function occVertexKey(vertex) {
+ const point = occVertexPoint(vertex);
+ return point ? pointKeyFromArray(point) : null;
+}
+
+function historyMapsShapeToResult(history, sourceShape, resultShape) {
+ if (!history || !sourceShape || !resultShape) return false;
+ try {
+ if (listContainsSameShape(history.Modified(sourceShape), resultShape)) return true;
+ } catch {
+ // Optional history API.
+ }
+ try {
+ if (listContainsSameShape(history.Generated(sourceShape), resultShape)) return true;
+ } catch {
+ // Optional history API.
+ }
+ return false;
+}
+
+function sameOccSurfaceType(a, b) {
+ const av = Number(a);
+ const bv = Number(b);
+ return Number.isFinite(av) && Number.isFinite(bv) && av === bv;
+}
+
+function chooseSourceFaceName(resultFace, sources, booleanOp = null) {
+ for (const source of sources) {
+ try {
+ if (resultFace.IsSame?.(source.face) || resultFace.IsEqual?.(source.face)) return source.name;
+ } catch {
+ // Continue with history/geometric matching.
+ }
+ if (historyMapsShapeToResult(booleanOp, source.face, resultFace)) return source.name;
+ }
+
+ const stats = statsFromOccFace(resultFace);
+ let best = null;
+ for (const source of sources) {
+ const dot = Math.abs(
+ (stats.normal[0] * source.normal[0])
+ + (stats.normal[1] * source.normal[1])
+ + (stats.normal[2] * source.normal[2])
+ );
+ const dist = Math.hypot(
+ stats.centroid[0] - source.centroid[0],
+ stats.centroid[1] - source.centroid[1],
+ stats.centroid[2] - source.centroid[2],
+ );
+ const score = dot - Math.min(dist, 1000) * 1e-4;
+ if (!best || score > best.score + 1e-9) best = { name: source.name, score };
+ }
+ return best?.name || null;
+}
+
+function simplifyOccBooleanResult(booleanOp) {
+ if (!booleanOp || typeof booleanOp.Shape !== "function") {
+ return { shape: null, history: booleanOp };
+ }
+ if (typeof booleanOp.SimplifyResult === "function") {
+ try {
+ booleanOp.SimplifyResult(
+ OCC_BOOLEAN_UNIFY_EDGES,
+ OCC_BOOLEAN_UNIFY_FACES,
+ OCC_BOOLEAN_UNIFY_ANGULAR_TOLERANCE,
+ );
+ return { shape: booleanOp.Shape(), history: booleanOp };
+ } catch {
+ // Fall back to explicit same-domain unification below.
+ }
+ }
+
+ const shape = booleanOp.Shape();
+ if (!shape || typeof oc.ShapeUpgrade_UnifySameDomain_2 !== "function") {
+ return { shape, history: booleanOp };
+ }
+ try {
+ const unify = new oc.ShapeUpgrade_UnifySameDomain_2(
+ shape,
+ OCC_BOOLEAN_UNIFY_EDGES,
+ OCC_BOOLEAN_UNIFY_FACES,
+ true,
+ );
+ try { unify.SetAngularTolerance(OCC_BOOLEAN_UNIFY_ANGULAR_TOLERANCE); } catch { /* optional OCCT binding */ }
+ unify.Build();
+ return { shape: unify.Shape(), history: unify };
+ } catch {
+ return { shape, history: booleanOp };
+ }
+}
+
+function chooseUnusedSourceFaceName(resultFace, sources, usedNames = new Set()) {
+ const stats = statsFromOccFace(resultFace);
+ const surfaceType = occFaceSurfaceTypeValue(resultFace);
+ let best = null;
+ for (const source of sources || []) {
+ if (!source?.name || usedNames.has(source.name)) continue;
+ const normalDot = (stats.normal[0] * source.normal[0])
+ + (stats.normal[1] * source.normal[1])
+ + (stats.normal[2] * source.normal[2]);
+ const dist = Math.hypot(
+ stats.centroid[0] - source.centroid[0],
+ stats.centroid[1] - source.centroid[1],
+ stats.centroid[2] - source.centroid[2],
+ );
+ const typeBonus = sameOccSurfaceType(surfaceType, source.surfaceType) ? 2 : 0;
+ const score = typeBonus + normalDot - Math.min(dist, 1000) * 1e-4;
+ if (!best || score > best.score) best = { name: source.name, score };
+ }
+ return best?.name || null;
+}
+
+function normalizedGeneratedEdgeSources(edgeSources) {
+ const out = [];
+ const seen = new Set();
+ for (const source of Array.isArray(edgeSources) ? edgeSources : []) {
+ const name = String(source?.name || "").trim();
+ if (!name || !source?.edge) continue;
+ const key = `${name}:${occEdgeKey(source.edge) || out.length}`;
+ if (seen.has(key)) continue;
+ seen.add(key);
+ out.push({ edge: source.edge, name, order: out.length });
+ }
+ return out;
+}
+
+function collectGeneratedVertexSources(edgeSources) {
+ const byKey = new Map();
+ for (const source of edgeSources || []) {
+ for (const vertex of occVerticesForShape(source.edge)) {
+ const key = occVertexKey(vertex);
+ if (!key) continue;
+ let entry = byKey.get(key);
+ if (!entry) {
+ entry = { vertex, names: new Set(), firstOrder: source.order };
+ byKey.set(key, entry);
+ }
+ entry.names.add(source.name);
+ entry.firstOrder = Math.min(entry.firstOrder, source.order);
+ }
+ }
+ return Array.from(byKey.values())
+ .map((entry) => ({
+ vertex: entry.vertex,
+ names: Array.from(entry.names).sort(),
+ firstOrder: entry.firstOrder,
+ }))
+ .sort((a, b) => a.firstOrder - b.firstOrder);
+}
+
+function combinedGeneratedVertexFaceName(names) {
+ const unique = Array.from(new Set((Array.isArray(names) ? names : [])
+ .map((name) => String(name || "").trim())
+ .filter(Boolean)))
+ .sort();
+ if (unique.length >= 3) return unique.join("+");
+ return unique[0] || null;
+}
+
+function generatedFaceNameFromEdgeSources(resultFace, edgeSources, operation) {
+ if (!operation || typeof operation.Generated !== "function") return null;
+ const sources = normalizedGeneratedEdgeSources(edgeSources);
+ for (const source of sources) {
+ try {
+ if (listContainsSameShape(operation.Generated(source.edge), resultFace)) return source.name;
+ } catch {
+ // Some OCCT history calls are optional in the wasm binding.
+ }
+ }
+
+ for (const source of collectGeneratedVertexSources(sources)) {
+ try {
+ if (!listContainsSameShape(operation.Generated(source.vertex), resultFace)) continue;
+ return combinedGeneratedVertexFaceName(source.names);
+ } catch {
+ // Continue with the next source vertex.
+ }
+ }
+ return null;
+}
+
+function bindBooleanFaceNames(state, left, right, booleanOp, operation) {
+ const leftFaces = collectNamedOccFaces(left);
+ const rightFaces = collectNamedOccFaces(right);
+ const sourceFaces = operation === "SUBTRACT" || operation === "DIFFERENCE"
+ ? [...leftFaces, ...rightFaces]
+ : [...leftFaces, ...rightFaces];
+ state.faceNameByIndex = [];
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ state.faceNameByIndex[faceIndex] = chooseSourceFaceName(face, sourceFaces, booleanOp) || `FACE_${faceIndex + 1}`;
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+function makeTopToolsShapeList(shapes) {
+ const list = new oc.TopTools_ListOfShape_1();
+ for (const shape of shapes || []) {
+ if (shape) list.Append_1(shape);
+ }
+ return list;
+}
+
+function makeOccBooleanOperation(opName, argumentShapes, toolShapes) {
+ let booleanOp;
+ if (opName === "SUBTRACT" || opName === "DIFFERENCE") {
+ booleanOp = new oc.BRepAlgoAPI_Cut_1();
+ } else if (opName === "INTERSECT" || opName === "COMMON") {
+ booleanOp = new oc.BRepAlgoAPI_Common_1();
+ } else {
+ booleanOp = new oc.BRepAlgoAPI_Fuse_1();
+ }
+ booleanOp.SetArguments(makeTopToolsShapeList(argumentShapes));
+ booleanOp.SetTools(makeTopToolsShapeList(toolShapes));
+ try { booleanOp.SetToFillHistory(true); } catch { /* optional OCCT binding */ }
+ try { booleanOp.SetNonDestructive(false); } catch { /* optional OCCT binding */ }
+ try { booleanOp.SetCheckInverted(false); } catch { /* optional OCCT binding */ }
+ try { booleanOp.SetGlue(oc.BOPAlgo_GlueEnum.BOPAlgo_GlueShift); } catch { /* optional OCCT binding */ }
+ booleanOp.Build();
+ return booleanOp;
+}
+
+function bindBooleanFaceNamesFromSources(state, sourceFaces, booleanOp, operation) {
+ state.faceNameByIndex = [];
+ let faceIndex = 0;
+ const opName = String(operation || "").toUpperCase();
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ state.faceNameByIndex[faceIndex] = chooseSourceFaceName(face, sourceFaces || [], booleanOp) || `${opName || "BOOLEAN"}_FACE_${faceIndex + 1}`;
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+export function booleanOccSolids(left, right, operation = "UNION") {
+ if (!hasOccShape(left) || !hasOccShape(right)) return null;
+ const opName = String(operation || "UNION").toUpperCase();
+ const leftShape = left._occ.shape;
+ const rightShape = right._occ.shape;
+ const booleanOp = makeOccBooleanOperation(opName, [leftShape], [rightShape]);
+ if (typeof booleanOp.IsDone === "function" && !booleanOp.IsDone()) {
+ throw new Error(`OpenCASCADE ${opName} boolean failed.`);
+ }
+ const simplified = simplifyOccBooleanResult(booleanOp);
+ const { faceMetadata, edgeMetadata } = cloneMetadataMaps(left, right);
+ const state = createOccState({
+ shape: simplified.shape || booleanOp.Shape(),
+ faceNames: uniqueNames([...(left._occ.faceNames || []), ...(right._occ.faceNames || [])]),
+ faceMetadata,
+ edgeMetadata,
+ booleanOperation: opName,
+ });
+ bindBooleanFaceNames(state, left, right, simplified.history || booleanOp, opName);
+ return state;
+}
+
+export function subtractOccSolidTools(target, toolSolids = []) {
+ const tools = (Array.isArray(toolSolids) ? toolSolids : []).filter((solid) => hasOccShape(solid));
+ if (!hasOccShape(target) || !tools.length) return null;
+ const cut = new oc.BRepAlgoAPI_Cut_1();
+ cut.SetArguments(makeTopToolsShapeList([target._occ.shape]));
+ cut.SetTools(makeTopToolsShapeList(tools.map((solid) => solid._occ.shape)));
+ try { cut.SetToFillHistory(true); } catch { /* optional OCCT binding */ }
+ try { cut.SetNonDestructive(false); } catch { /* optional OCCT binding */ }
+ try { cut.SetCheckInverted(false); } catch { /* optional OCCT binding */ }
+ try { cut.SetGlue(oc.BOPAlgo_GlueEnum.BOPAlgo_GlueShift); } catch { /* optional OCCT binding */ }
+ cut.Build();
+ if (typeof cut.IsDone === "function" && !cut.IsDone()) {
+ throw new Error("OpenCASCADE multi-tool subtract failed.");
+ }
+ const simplified = simplifyOccBooleanResult(cut);
+ const allSolids = [target, ...tools];
+ const { faceMetadata, edgeMetadata } = cloneMetadataMapsMany(allSolids);
+ const state = createOccState({
+ shape: simplified.shape || cut.Shape(),
+ faceNames: uniqueNames(allSolids.flatMap((solid) => solid?._occ?.faceNames || [])),
+ faceMetadata,
+ edgeMetadata,
+ booleanOperation: "SUBTRACT",
+ });
+ bindBooleanFaceNamesFromSources(
+ state,
+ allSolids.flatMap((solid) => collectNamedOccFaces(solid)),
+ simplified.history || cut,
+ "SUBTRACT",
+ );
+ return state;
+}
+
+export function occVolume(solid) {
+ if (!hasOccShape(solid)) return null;
+ const props = new oc.GProp_GProps_1();
+ oc.BRepGProp.VolumeProperties_1(solid._occ.shape, props, true, false, false);
+ return Math.abs(Number(props.Mass() || 0));
+}
+
+export function occSurfaceArea(solid) {
+ if (!hasOccShape(solid)) return null;
+ const props = new oc.GProp_GProps_1();
+ oc.BRepGProp.SurfaceProperties_1(solid._occ.shape, props, true, false);
+ return Math.abs(Number(props.Mass() || 0));
+}
+
+export function occSTEP(solid, name = "part") {
+ if (!hasOccShape(solid)) return null;
+ const writer = new oc.STEPControl_Writer_1();
+ writer.Transfer(solid._occ.shape, oc.STEPControl_StepModelType.STEPControl_AsIs, true);
+ const fileName = "test.step";
+ writer.Write(fileName);
+ try {
+ return oc.FS.readFile(fileName, { encoding: "utf8" });
+ } finally {
+ try { oc.FS.unlink(fileName); } catch { }
+ }
+}
+
+function pointDistanceSq(a, b) {
+ const dx = Number(a?.[0] || 0) - Number(b?.[0] || 0);
+ const dy = Number(a?.[1] || 0) - Number(b?.[1] || 0);
+ const dz = Number(a?.[2] || 0) - Number(b?.[2] || 0);
+ return dx * dx + dy * dy + dz * dz;
+}
+
+function pointToPolylineDistanceSq(point, segments) {
+ let best = Infinity;
+ for (const segment of segments || []) {
+ best = Math.min(best, closestSegmentDistanceSq(point, segment[0], segment[1]));
+ }
+ return best;
+}
+
+function edgeEndpointsFromPolyline(edgeObj) {
+ const pts = edgeObj?.userData?.polylineLocal;
+ if (!Array.isArray(pts) || pts.length < 2) return null;
+ return [pts[0], pts[pts.length - 1]];
+}
+
+function edgeSegmentsFromPolyline(edgeObj) {
+ const pts = edgeObj?.userData?.polylineLocal;
+ if (!Array.isArray(pts) || pts.length < 2) return [];
+ const segments = [];
+ for (let i = 0; i + 1 < pts.length; i += 1) {
+ const a = pts[i];
+ const b = pts[i + 1];
+ if (pointDistanceSq(a, b) > 1e-16) segments.push([a, b]);
+ }
+ return segments;
+}
+
+function occEdgeEndpoints(edge) {
+ const pts = [];
+ try {
+ const explorer = new oc.TopExp_Explorer_2(
+ edge,
+ oc.TopAbs_ShapeEnum.TopAbs_VERTEX,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next()) {
+ const vertex = oc.TopoDS.Vertex_1(explorer.Current());
+ const p = oc.BRep_Tool.Pnt(vertex);
+ pts.push([p.X(), p.Y(), p.Z()]);
+ }
+ } catch {
+ return null;
+ }
+ if (pts.length < 2) return null;
+ return [pts[0], pts[pts.length - 1]];
+}
+
+function clampOccEdgeSampleCount(value) {
+ const count = Math.floor(Number(value));
+ if (!Number.isFinite(count)) return DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES;
+ return Math.max(2, Math.min(MAX_OCC_VISUALIZATION_CURVE_SAMPLES, count));
+}
+
+function polylineApproxLength(points) {
+ let length = 0;
+ for (let i = 1; i < points.length; i += 1) {
+ const a = points[i - 1];
+ const b = points[i];
+ length += Math.hypot(
+ Number(b?.[0] || 0) - Number(a?.[0] || 0),
+ Number(b?.[1] || 0) - Number(a?.[1] || 0),
+ Number(b?.[2] || 0) - Number(a?.[2] || 0),
+ );
+ }
+ return length;
+}
+
+function pointToLineDistanceSq(point, a, b) {
+ const px = Number(point?.[0] || 0);
+ const py = Number(point?.[1] || 0);
+ const pz = Number(point?.[2] || 0);
+ const ax = Number(a?.[0] || 0);
+ const ay = Number(a?.[1] || 0);
+ const az = Number(a?.[2] || 0);
+ const bx = Number(b?.[0] || 0);
+ const by = Number(b?.[1] || 0);
+ const bz = Number(b?.[2] || 0);
+ const abx = bx - ax;
+ const aby = by - ay;
+ const abz = bz - az;
+ const len2 = abx * abx + aby * aby + abz * abz;
+ if (len2 <= 1e-24) return (px - ax) ** 2 + (py - ay) ** 2 + (pz - az) ** 2;
+ const t = Math.max(0, Math.min(1, ((px - ax) * abx + (py - ay) * aby + (pz - az) * abz) / len2));
+ const qx = ax + abx * t;
+ const qy = ay + aby * t;
+ const qz = az + abz * t;
+ return (px - qx) ** 2 + (py - qy) ** 2 + (pz - qz) ** 2;
+}
+
+function isPolylineNearlyLinear(points, length) {
+ if (!Array.isArray(points) || points.length < 3) return true;
+ const first = points[0];
+ const last = points[points.length - 1];
+ const tolerance = Math.max(1e-7, Math.max(1, Number(length) || 0) * 1e-6);
+ const toleranceSq = tolerance * tolerance;
+ for (let i = 1; i < points.length - 1; i += 1) {
+ if (pointToLineDistanceSq(points[i], first, last) > toleranceSq) return false;
+ }
+ return true;
+}
+
+function sampleOccEdgeCurve(edge, count) {
+ const points = [];
+ try {
+ const curve = new oc.BRepAdaptor_Curve_2(edge);
+ const first = Number(curve.FirstParameter());
+ const last = Number(curve.LastParameter());
+ if (!Number.isFinite(first) || !Number.isFinite(last)) return points;
+ const steps = Math.max(2, Math.floor(count));
+ for (let i = 0; i < steps; i += 1) {
+ const t = steps === 1 ? first : first + ((last - first) * i) / (steps - 1);
+ const p = new oc.gp_Pnt_1();
+ curve.D0(t, p);
+ points.push([p.X(), p.Y(), p.Z()]);
+ }
+ } catch {
+ // Some edge kinds do not expose a usable adaptor in the wasm binding.
+ }
+ return points;
+}
+
+function occEdgeSampleCount(edge, options = {}) {
+ if (Number.isFinite(options?.edgeSampleCount)) return clampOccEdgeSampleCount(options.edgeSampleCount);
+
+ const coarse = sampleOccEdgeCurve(edge, 17);
+ if (coarse.length < 2) return DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES;
+
+ const length = polylineApproxLength(coarse);
+ if (isPolylineNearlyLinear(coarse, length)) return 2;
+
+ const deflection = Number(options?.deflection);
+ const angle = Number(options?.angle);
+ const byDeflection = Number.isFinite(deflection) && deflection > 0
+ ? Math.ceil(length / Math.max(deflection * 0.25, 0.001))
+ : DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES;
+ const byAngle = Number.isFinite(angle) && angle > 0
+ ? Math.ceil(DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES * Math.max(1, DEFAULT_ANGLE / angle))
+ : DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES;
+
+ return clampOccEdgeSampleCount(Math.max(DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES, byDeflection, byAngle));
+}
+
+function occEdgeSamplePoints(edge, countOrOptions = 17) {
+ const count = typeof countOrOptions === "number"
+ ? countOrOptions
+ : occEdgeSampleCount(edge, countOrOptions || {});
+ return sampleOccEdgeCurve(edge, count);
+}
+
+function selectedEdgeMatchScore(edgeObj, occEdge) {
+ const selected = edgeEndpointsFromPolyline(edgeObj);
+ const candidate = occEdgeEndpoints(occEdge);
+ if (!selected || !candidate) return Infinity;
+ const forward = pointDistanceSq(selected[0], candidate[0]) + pointDistanceSq(selected[1], candidate[1]);
+ const reverse = pointDistanceSq(selected[0], candidate[1]) + pointDistanceSq(selected[1], candidate[0]);
+ return Math.min(forward, reverse);
+}
+
+function segmentEdgeMatchScore(segment, occEdge) {
+ const candidate = occEdgeEndpoints(occEdge);
+ if (!segment || !candidate) return Infinity;
+ const forward = pointDistanceSq(segment[0], candidate[0]) + pointDistanceSq(segment[1], candidate[1]);
+ const reverse = pointDistanceSq(segment[0], candidate[1]) + pointDistanceSq(segment[1], candidate[0]);
+ return Math.min(forward, reverse);
+}
+
+function curvePolylineMatchScore(segments, occEdge) {
+ if (!Array.isArray(segments) || segments.length === 0) return Infinity;
+ const samples = occEdgeSamplePoints(occEdge);
+ if (samples.length === 0) return Infinity;
+ let maxDistance = 0;
+ let sumDistance = 0;
+ for (const point of samples) {
+ const dist = pointToPolylineDistanceSq(point, segments);
+ maxDistance = Math.max(maxDistance, dist);
+ sumDistance += dist;
+ }
+ return Math.max(maxDistance, sumDistance / samples.length);
+}
+
+function occEdgeKey(edge) {
+ const endpoints = occEdgeEndpoints(edge);
+ if (!endpoints) return null;
+ return edgeKeyForPoints(endpoints[0], endpoints[1]);
+}
+
+function sameOccShape(a, b) {
+ if (!a || !b) return false;
+ try {
+ return !!(a.IsSame?.(b) || a.IsEqual?.(b));
+ } catch {
+ return false;
+ }
+}
+
+function occEdgeTriangulationPolyline(face, edge) {
+ try {
+ const location = new oc.TopLoc_Location_1();
+ const handle = occFaceTriangulation(face, location);
+ if (!handle || handle.IsNull()) return [];
+ const polygon = oc.BRep_Tool.PolygonOnTriangulation_1(edge, handle, location);
+ if (!polygon || polygon.IsNull()) return [];
+
+ const triangulation = handle.get();
+ const nodes = polygon.get().Nodes();
+ const length = Number(nodes.Length?.() || 0);
+ const positions = [];
+ for (let i = 1; i <= length; i += 1) {
+ const nodeIndex = Number(nodes.Value(i));
+ if (!Number.isFinite(nodeIndex) || nodeIndex < 1 || nodeIndex > triangulation.NbNodes()) continue;
+ positions.push(pointWithLocation(triangulation.Node(nodeIndex), location));
+ }
+ return positions;
+ } catch {
+ return [];
+ }
+}
+
+function findOccEdgesForSelection(solid, edgeObj, tolerance = 1e-4) {
+ if (!hasOccShape(solid) || !edgeObj) return null;
+ const tol2 = tolerance * tolerance;
+ const curveTol2 = Math.max(tol2 * 100, 1e-3);
+ const segments = edgeSegmentsFromPolyline(edgeObj);
+ const selectedIsChain = segments.length > 1;
+ const matches = [];
+ const seen = new Set();
+ let best = null;
+ const explorer = new oc.TopExp_Explorer_2(
+ solid._occ.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_EDGE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next()) {
+ const edge = oc.TopoDS.Edge_1(explorer.Current());
+ let score = selectedEdgeMatchScore(edgeObj, edge);
+ if (selectedIsChain) {
+ score = Infinity;
+ for (const segment of segments) score = Math.min(score, segmentEdgeMatchScore(segment, edge));
+ score = Math.min(score, curvePolylineMatchScore(segments, edge));
+ if (score <= curveTol2) {
+ const key = occEdgeKey(edge) || String(matches.length);
+ if (!seen.has(key)) {
+ seen.add(key);
+ matches.push(edge);
+ }
+ }
+ }
+ if (!best || score < best.score) best = { edge, score };
+ }
+ if (matches.length) return matches;
+ return best && best.score <= tol2 * 2 ? [best.edge] : [];
+}
+
+function findOccOwnerSolidForEdgeObject(edgeObj) {
+ let obj = edgeObj?.parent || null;
+ while (obj) {
+ if (hasOccShape(obj)) return obj;
+ obj = obj.parent || null;
+ }
+ return null;
+}
+
+function findOccOwnerSolidForFaceObject(faceObj) {
+ let obj = faceObj?.parent || null;
+ while (obj) {
+ if (hasOccShape(obj)) return obj;
+ obj = obj.parent || null;
+ }
+ return null;
+}
+
+export function occFaceFromSelectedFace(faceObj) {
+ const owner = findOccOwnerSolidForFaceObject(faceObj);
+ const faceName = String(faceObj?.name || faceObj?.userData?.faceName || "").trim();
+ if (!owner || !faceName) return null;
+ const face = findOccFaceByName(owner, faceName);
+ return face ? { owner, face, faceName } : null;
+}
+
+export function occAxisFromSelectedEdge(edgeObj) {
+ const owner = findOccOwnerSolidForEdgeObject(edgeObj);
+ if (!owner) return null;
+ const edges = findOccEdgesForSelection(owner, edgeObj);
+ const edge = Array.isArray(edges) && edges.length === 1 ? edges[0] : null;
+ if (!edge) return null;
+ const endpoints = occEdgeEndpoints(edge);
+ if (!endpoints || endpoints.length < 2) return null;
+ const axis = sub(endpoints[1], endpoints[0]);
+ if (Math.hypot(...axis) <= 1e-12) return null;
+ return {
+ start: endpoints[0],
+ end: endpoints[1],
+ };
+}
+
+function bindFeatureResultFaceNames(state, sourceSolid, featureFacePrefix, operation, options = {}) {
+ const sourceFaces = collectNamedOccFaces(sourceSolid);
+ state.faceNameByIndex = [];
+ let created = 0;
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const surfaceType = occFaceSurfaceTypeValue(face);
+ let name = null;
+ for (const source of sourceFaces) {
+ try {
+ if (face.IsSame?.(source.face) || face.IsEqual?.(source.face)) {
+ name = source.name;
+ break;
+ }
+ } catch { }
+ try {
+ if (
+ operation
+ && sameOccSurfaceType(surfaceType, source.surfaceType)
+ && listContainsSameShape(operation.Modified(source.face), face)
+ ) {
+ name = source.name;
+ break;
+ }
+ } catch { }
+ }
+ if (!name) {
+ name = generatedFaceNameFromEdgeSources(face, options.generatedEdgeSources, operation);
+ }
+ state.faceNameByIndex[faceIndex] = name || `${featureFacePrefix}_${created++}`;
+ }
+ state.faceNames = uniqueNames(state.faceNameByIndex);
+}
+
+function featureResultStateFromOperation(solid, operation, featureID, kind, options = {}) {
+ if (typeof operation.Build === "function") operation.Build();
+ if (typeof operation.IsDone === "function" && !operation.IsDone()) {
+ throw new Error(`OpenCASCADE ${kind} failed.`);
+ }
+ const state = createOccState({
+ shape: operation.Shape(),
+ faceNames: Array.from(solid?._occ?.faceNames || []),
+ faceMetadata: solid?._faceMetadata instanceof Map ? new Map(solid._faceMetadata.entries()) : new Map(),
+ edgeMetadata: solid?._edgeMetadata instanceof Map ? new Map(solid._edgeMetadata.entries()) : new Map(),
+ feature: { kind, featureID },
+ });
+ bindFeatureResultFaceNames(state, solid, `${featureID}_${String(kind || "FEATURE").toUpperCase()}_FACE`, operation, options);
+ return state;
+}
+
+export function filletOccSolid(solid, edgeObjs = [], { radius, featureID = "FILLET" } = {}) {
+ if (!hasOccShape(solid)) return null;
+ const r = Number(radius);
+ if (!Number.isFinite(r) || r <= 0) throw new Error(`OpenCASCADE fillet radius must be > 0, got ${radius}`);
+ const op = new oc.BRepFilletAPI_MakeFillet(solid._occ.shape, oc.ChFi3d_FilletShape.ChFi3d_Rational);
+ let added = 0;
+ const generatedEdgeSources = [];
+ for (const edgeObj of edgeObjs || []) {
+ const edges = findOccEdgesForSelection(solid, edgeObj);
+ const sourceName = String(edgeObj?.name || edgeObj?.userData?.edgeName || "").trim();
+ for (const edge of edges || []) {
+ op.Add_2(r, edge);
+ added += 1;
+ if (sourceName) generatedEdgeSources.push({ edge, name: sourceName });
+ }
+ }
+ if (!added) return null;
+ return featureResultStateFromOperation(solid, op, featureID, "fillet", { generatedEdgeSources });
+}
+
+export function chamferOccSolid(solid, edgeObjs = [], { distance, featureID = "CHAMFER" } = {}) {
+ if (!hasOccShape(solid)) return null;
+ const d = Number(distance);
+ if (!Number.isFinite(d) || d <= 0) throw new Error(`OpenCASCADE chamfer distance must be > 0, got ${distance}`);
+ const op = new oc.BRepFilletAPI_MakeChamfer(solid._occ.shape);
+ let added = 0;
+ for (const edgeObj of edgeObjs || []) {
+ const edges = findOccEdgesForSelection(solid, edgeObj);
+ for (const edge of edges || []) {
+ op.Add_2(d, edge);
+ added += 1;
+ }
+ }
+ if (!added) return null;
+ return featureResultStateFromOperation(solid, op, featureID, "chamfer");
+}
+
+export function offsetShellOccSolid(solid, removedFaceNames = [], { distance, featureID = "OFFSET" } = {}) {
+ if (!hasOccShape(solid)) return null;
+ const d = Number(distance);
+ if (!Number.isFinite(d) || Math.abs(d) <= 0) {
+ throw new Error(`OpenCASCADE offset shell distance must be non-zero, got ${distance}`);
+ }
+ const removeSet = new Set((Array.isArray(removedFaceNames) ? removedFaceNames : [removedFaceNames])
+ .map((name) => String(name || "").trim())
+ .filter(Boolean));
+ let op = null;
+ if (removeSet.size) {
+ const facesToRemove = new oc.TopTools_ListOfShape_1();
+ for (const entry of collectNamedOccFaces(solid)) {
+ if (removeSet.has(entry.name)) facesToRemove.Append_1(entry.face);
+ }
+ if (Number(facesToRemove.Size?.() || 0) <= 0) return null;
+
+ op = new oc.BRepOffsetAPI_MakeThickSolid_1();
+ op.MakeThickSolidByJoin(
+ solid._occ.shape,
+ facesToRemove,
+ d,
+ 1e-4,
+ oc.BRepOffset_Mode.BRepOffset_Skin,
+ false,
+ false,
+ oc.GeomAbs_JoinType.GeomAbs_Arc,
+ false,
+ );
+ } else {
+ op = new oc.BRepOffsetAPI_MakeOffsetShape_1();
+ op.PerformByJoin(
+ solid._occ.shape,
+ d,
+ 1e-4,
+ oc.BRepOffset_Mode.BRepOffset_Skin,
+ false,
+ false,
+ oc.GeomAbs_JoinType.GeomAbs_Arc,
+ false,
+ );
+ }
+ if (typeof op.IsDone === "function" && !op.IsDone()) {
+ throw new Error("OpenCASCADE offset shell failed.");
+ }
+
+ const state = createOccState({
+ shape: op.Shape(),
+ faceNames: Array.from(solid?._occ?.faceNames || []).filter((name) => !removeSet.has(name)),
+ faceMetadata: solid?._faceMetadata instanceof Map ? new Map(solid._faceMetadata.entries()) : new Map(),
+ edgeMetadata: solid?._edgeMetadata instanceof Map ? new Map(solid._edgeMetadata.entries()) : new Map(),
+ feature: { kind: "offsetShell", featureID, removedFaceNames: Array.from(removeSet), distance: d },
+ });
+ bindFeatureResultFaceNames(state, solid, `${featureID}_OFFSET_FACE`, op);
+ if (!removeSet.size) {
+ const sources = collectNamedOccFaces(solid);
+ const usedNames = new Set();
+ state.faceNameByIndex = [];
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const sourceName = chooseUnusedSourceFaceName(face, sources, usedNames);
+ if (sourceName) usedNames.add(sourceName);
+ state.faceNameByIndex[faceIndex] = sourceName || `${featureID}_OFFSET_FACE_${faceIndex}`;
+ }
+ }
+ state.faceNames = uniqueNames((state.faceNameByIndex || []).filter((name) => name && !removeSet.has(name)));
+ return state;
+}
+
+export function tessellateOccState(state, options = {}) {
+ if (!state?.shape) return { numProp: 3, vertProperties: new Float32Array(), triVerts: new Uint32Array(), faceID: new Uint32Array(), delete() {} };
+
+ const meshOptions = state.meshOptions || {};
+ const deflection = Number.isFinite(options.deflection)
+ ? options.deflection
+ : (Number.isFinite(meshOptions.deflection) ? meshOptions.deflection : DEFAULT_DEFLECTION);
+ const angle = Number.isFinite(options.angle)
+ ? options.angle
+ : (Number.isFinite(meshOptions.angle) ? meshOptions.angle : DEFAULT_ANGLE);
+ const cacheKey = `${deflection}:${angle}`;
+ if (state.meshCache && state.meshCacheKey === cacheKey) return state.meshCache;
+ cleanOccTriangulation(state.shape);
+ new oc.BRepMesh_IncrementalMesh_2(state.shape, deflection, false, angle, false);
+
+ const vertices = [];
+ const triVerts = [];
+ const faceIDs = [];
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ const face = oc.TopoDS.Face_1(explorer.Current());
+ const location = new oc.TopLoc_Location_1();
+ const handle = occFaceTriangulation(face, location);
+ if (!handle || handle.IsNull()) continue;
+ const triangulation = handle.get();
+ const base = (vertices.length / 3) | 0;
+ const facePoints = [];
+ for (let i = 1; i <= triangulation.NbNodes(); i += 1) {
+ const point = triangulation.Node(i);
+ const p = pointWithLocation(point, location);
+ facePoints.push(p);
+ vertices.push(p[0], p[1], p[2]);
+ }
+ const faceName = getFaceNameForIndex(state, faceIndex, {
+ ...makeFaceStats(facePoints),
+ points: facePoints,
+ normal: surfaceNormalFromOccFace(face) || [0, 0, 0],
+ });
+ const faceID = state.faceNameToID?.get?.(faceName) ?? faceIndex + 1;
+ const isReversed = occFaceNeedsTriangleReverse(face);
+ for (let i = 1; i <= triangulation.NbTriangles(); i += 1) {
+ const tri = triangulation.Triangle(i);
+ const i0 = base + tri.Value(1) - 1;
+ const i1 = base + tri.Value(2) - 1;
+ const i2 = base + tri.Value(3) - 1;
+ if (isReversed) triVerts.push(i0, i2, i1);
+ else triVerts.push(i0, i1, i2);
+ faceIDs.push(faceID);
+ }
+ }
+
+ state.meshCache = {
+ numProp: 3,
+ vertProperties: new Float32Array(vertices),
+ triVerts: new Uint32Array(triVerts),
+ faceID: new Uint32Array(faceIDs),
+ delete() {},
+ };
+ state.meshCacheKey = cacheKey;
+ return state.meshCache;
+}
+
+export function occFaces(solid, options = {}) {
+ const state = solid?._occ;
+ if (!state?.shape) return null;
+ state.faceNameToID = solid._faceNameToID;
+ const mesh = tessellateOccState(state, options);
+ const faces = new Map();
+ const vp = mesh.vertProperties;
+ const tv = mesh.triVerts;
+ const ids = mesh.faceID;
+ const triCount = (tv.length / 3) | 0;
+ for (let t = 0; t < triCount; t += 1) {
+ const id = ids[t];
+ const faceName = solid._idToFaceName.get(id) || `FACE_${id}`;
+ if (!faces.has(faceName)) faces.set(faceName, []);
+ const i0 = tv[t * 3], i1 = tv[t * 3 + 1], i2 = tv[t * 3 + 2];
+ faces.get(faceName).push({
+ faceName,
+ indices: [i0, i1, i2],
+ p1: [vp[i0 * 3], vp[i0 * 3 + 1], vp[i0 * 3 + 2]],
+ p2: [vp[i1 * 3], vp[i1 * 3 + 1], vp[i1 * 3 + 2]],
+ p3: [vp[i2 * 3], vp[i2 * 3 + 1], vp[i2 * 3 + 2]],
+ });
+ }
+ return Array.from(faces.entries(), ([faceName, triangles]) => ({ faceName, triangles }));
+}
+
+function findOccFaceByName(solid, faceName) {
+ const state = solid?._occ;
+ if (!state?.shape) return null;
+ state.faceNameToID = solid._faceNameToID;
+ tessellateOccState(state);
+ let faceIndex = 0;
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_FACE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next(), faceIndex += 1) {
+ if (getFaceNameForIndex(state, faceIndex) === faceName) {
+ return oc.TopoDS.Face_1(explorer.Current());
+ }
+ }
+ return null;
+}
+
+function finiteMidpoint(a, b, fallback = 0) {
+ const av = Number(a);
+ const bv = Number(b);
+ if (Number.isFinite(av) && Number.isFinite(bv)) return (av + bv) / 2;
+ if (Number.isFinite(av)) return av;
+ if (Number.isFinite(bv)) return bv;
+ return fallback;
+}
+
+function surfaceNormalFromOccFace(face) {
+ if (!face) return null;
+ try {
+ const surface = new oc.BRepAdaptor_Surface_2(face, true);
+ const u = finiteMidpoint(surface.FirstUParameter(), surface.LastUParameter());
+ const v = finiteMidpoint(surface.FirstVParameter(), surface.LastVParameter());
+ const props = new oc.BRepLProp_SLProps_1(surface, u, v, 1, 1e-7);
+ if (!props.IsNormalDefined()) return null;
+ const normal = props.Normal();
+ let out = normalizeVector([normal.X(), normal.Y(), normal.Z()]);
+ if (occFaceNeedsTriangleReverse(face)) {
+ out = [-out[0], -out[1], -out[2]];
+ }
+ return out;
+ } catch {
+ return null;
+ }
+}
+
+export function occFaceNormal(solid, faceName) {
+ const faceEntry = (occFaces(solid) || []).find((entry) => entry.faceName === faceName);
+ if (!faceEntry) {
+ return { faceFound: false, validNormal: false, normal: [0, 0, 0], planarRatio: 0, affectedVertexCount: 0 };
+ }
+
+ let normal = surfaceNormalFromOccFace(findOccFaceByName(solid, faceName));
+ const weighted = [0, 0, 0];
+ for (const tri of faceEntry.triangles || []) {
+ const n = triangleNormal(tri.p1, tri.p2, tri.p3);
+ weighted[0] += n[0];
+ weighted[1] += n[1];
+ weighted[2] += n[2];
+ }
+ if (!normal || Math.hypot(normal[0], normal[1], normal[2]) <= 1e-12) {
+ normal = normalizeVector(weighted);
+ }
+
+ const validNormal = Math.hypot(normal[0], normal[1], normal[2]) > 1e-12;
+ const vertices = new Set();
+ let area = 0;
+ let alignedArea = 0;
+ for (const tri of faceEntry.triangles || []) {
+ for (const p of [tri.p1, tri.p2, tri.p3]) vertices.add(pointKeyFromArray(p));
+ const n = triangleNormal(tri.p1, tri.p2, tri.p3);
+ const triArea = Math.hypot(n[0], n[1], n[2]);
+ if (triArea <= 1e-12) continue;
+ area += triArea;
+ const unit = [n[0] / triArea, n[1] / triArea, n[2] / triArea];
+ alignedArea += triArea * Math.abs(unit[0] * normal[0] + unit[1] * normal[1] + unit[2] * normal[2]);
+ }
+
+ return {
+ faceFound: true,
+ validNormal,
+ normal,
+ planarRatio: area > 1e-12 ? alignedArea / area : (validNormal ? 1 : 0),
+ affectedVertexCount: vertices.size,
+ };
+}
+
+const EDGE_KEY_SCALE = 1e8;
+
+function pointKeyFromArray(point) {
+ return [
+ Math.round(Number(point?.[0] || 0) * EDGE_KEY_SCALE),
+ Math.round(Number(point?.[1] || 0) * EDGE_KEY_SCALE),
+ Math.round(Number(point?.[2] || 0) * EDGE_KEY_SCALE),
+ ].join(",");
+}
+
+function edgeKeyForPoints(a, b) {
+ const ka = pointKeyFromArray(a);
+ const kb = pointKeyFromArray(b);
+ return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
+}
+
+function edgePairName(a, b) {
+ const left = String(a || "");
+ const right = String(b || "");
+ return left < right ? [left, right] : [right, left];
+}
+
+function buildPolylinesFromSegments(segments) {
+ const pointByKey = new Map();
+ const adjacency = new Map();
+ const edgeVisited = new Set();
+ const edgeKey = (a, b) => (a < b ? `${a}|${b}` : `${b}|${a}`);
+
+ for (const segment of segments) {
+ const aKey = pointKeyFromArray(segment[0]);
+ const bKey = pointKeyFromArray(segment[1]);
+ if (aKey === bKey) continue;
+ if (!pointByKey.has(aKey)) pointByKey.set(aKey, segment[0]);
+ if (!pointByKey.has(bKey)) pointByKey.set(bKey, segment[1]);
+ if (!adjacency.has(aKey)) adjacency.set(aKey, new Set());
+ if (!adjacency.has(bKey)) adjacency.set(bKey, new Set());
+ adjacency.get(aKey).add(bKey);
+ adjacency.get(bKey).add(aKey);
+ }
+
+ const walk = (startKey) => {
+ const polyKeys = [startKey];
+ let prev = null;
+ let current = startKey;
+ let closedLoop = false;
+
+ while (true) {
+ const neighbors = Array.from(adjacency.get(current) || []);
+ let next = neighbors.find((candidate) => candidate !== prev && !edgeVisited.has(edgeKey(current, candidate)));
+ if (!next && prev === null) {
+ next = neighbors.find((candidate) => !edgeVisited.has(edgeKey(current, candidate)));
+ }
+ if (!next) break;
+ const eKey = edgeKey(current, next);
+ if (edgeVisited.has(eKey)) break;
+ edgeVisited.add(eKey);
+ prev = current;
+ current = next;
+ if (current === startKey) {
+ closedLoop = true;
+ break;
+ }
+ polyKeys.push(current);
+ }
+
+ const positions = polyKeys.map((key) => pointByKey.get(key)).filter(Boolean);
+ if (closedLoop && positions.length) positions.push(positions[0]);
+ return { positions, closedLoop };
+ };
+
+ const polylines = [];
+ const starts = Array.from(adjacency.keys()).sort((a, b) => {
+ const da = adjacency.get(a)?.size || 0;
+ const db = adjacency.get(b)?.size || 0;
+ return (da === 1 ? -1 : 0) - (db === 1 ? -1 : 0);
+ });
+
+ for (const start of starts) {
+ const neighbors = Array.from(adjacency.get(start) || []);
+ if (!neighbors.some((neighbor) => !edgeVisited.has(edgeKey(start, neighbor)))) continue;
+ const polyline = walk(start);
+ if (polyline.positions.length >= 2) polylines.push(polyline);
+ }
+
+ return polylines;
+}
+
+export function occBoundaryEdgePolylines(solid, options = {}) {
+ const state = solid?._occ;
+ if (!state?.shape) return [];
+ const faces = collectNamedOccFaces(solid, options);
+ const edges = [];
+ const explorer = new oc.TopExp_Explorer_2(
+ state.shape,
+ oc.TopAbs_ShapeEnum.TopAbs_EDGE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; explorer.More(); explorer.Next()) {
+ const edge = oc.TopoDS.Edge_1(explorer.Current());
+ let entry = edges.find((candidate) => sameOccShape(candidate.edge, edge));
+ if (!entry) {
+ entry = { edge, faceUses: [], polygons: [] };
+ edges.push(entry);
+ }
+ }
+
+ for (const faceEntry of faces) {
+ const faceExplorer = new oc.TopExp_Explorer_2(
+ faceEntry.face,
+ oc.TopAbs_ShapeEnum.TopAbs_EDGE,
+ oc.TopAbs_ShapeEnum.TopAbs_SHAPE,
+ );
+ for (; faceExplorer.More(); faceExplorer.Next()) {
+ const faceEdge = oc.TopoDS.Edge_1(faceExplorer.Current());
+ const edgeEntry = edges.find((candidate) => sameOccShape(candidate.edge, faceEdge));
+ if (edgeEntry) {
+ edgeEntry.faceUses.push(faceEntry.name);
+ const positions = occEdgeTriangulationPolyline(faceEntry.face, faceEdge);
+ if (positions.length >= 2) {
+ edgeEntry.polygons.push({ faceName: faceEntry.name, positions });
+ }
+ }
+ }
+ }
+
+ const pairEdges = new Map();
+ for (const entry of edges) {
+ const names = (entry.faceUses || []).filter(Boolean);
+ if (!names.length) continue;
+ const polygon = (entry.polygons || []).find((item) => Array.isArray(item.positions) && item.positions.length >= 2);
+ if (!polygon) continue;
+ const uniqueNames = Array.from(new Set(names));
+ if (uniqueNames.length < 2) continue;
+ let faceA = uniqueNames[0];
+ let faceB = uniqueNames[1] || uniqueNames[0];
+ [faceA, faceB] = edgePairName(faceA, faceB);
+ const pairKey = JSON.stringify([faceA, faceB]);
+ if (!pairEdges.has(pairKey)) pairEdges.set(pairKey, []);
+ pairEdges.get(pairKey).push(polygon.positions);
+ }
+
+ const out = [];
+ for (const [pairKey, polylinesForPair] of pairEdges.entries()) {
+ const [faceA, faceB] = JSON.parse(pairKey);
+ polylinesForPair.forEach((positions, index) => {
+ if (positions.length < 2) return;
+ const closedLoop = pointDistanceSq(positions[0], positions[positions.length - 1]) <= 1e-12;
+ out.push({
+ name: `${faceA}|${faceB}[${index}]`,
+ faceA,
+ faceB,
+ positions,
+ closedLoop,
+ });
+ });
+ }
+ return out;
+}
diff --git a/src/BREP/Revolve.js b/src/BREP/Revolve.js
index 3cc0eab8..3d37b627 100644
--- a/src/BREP/Revolve.js
+++ b/src/BREP/Revolve.js
@@ -1,14 +1,8 @@
import { Solid } from './BetterSolid.js';
import * as THREE from 'three';
-import { applySolidAuthoringStateSnapshot } from './CppSolidCore.js';
import { getEdgeLineEndpointsWorld, getEdgePolylineWorld } from './edgePolylineUtils.js';
import { computeBoundaryLoopsFromFaceNative } from './Sweep.js';
-import { manifold } from './setupManifold.js';
-
-function requireNativeRevolveBuilder() {
- if (typeof manifold?.buildRevolveAuthoringState === 'function') return;
- throw new Error('Revolve generation requires the custom local manifold build with native revolve support.');
-}
+import { makeRevolution, occAxisFromSelectedEdge, occFaceFromSelectedFace, setOccState } from './OpenCascadeKernel.js';
function computeFaceCentroidWorld(faceObj) {
try {
@@ -44,28 +38,83 @@ function computeFaceCentroidWorld(faceObj) {
return null;
}
+function cloneBoundaryLoopsWorld(face) {
+ const loops = Array.isArray(face?.userData?.boundaryLoopsWorld) && face.userData.boundaryLoopsWorld.length
+ ? face.userData.boundaryLoopsWorld
+ : null;
+ if (!loops) return null;
+ return loops.map((loop) => ({
+ pts: (Array.isArray(loop?.pts) ? loop.pts : loop).map((p) => [p[0], p[1], p[2]]),
+ isHole: !!loop?.isHole,
+ segmentIds: Array.isArray(loop?.segmentIds) ? loop.segmentIds.slice() : [],
+ }));
+}
+
+function cloneSketchEdgeInputsWorld(face) {
+ const inputs = Array.isArray(face?.userData?.sketchEdgeInputsWorld)
+ ? face.userData.sketchEdgeInputsWorld
+ : null;
+ if (!inputs) return null;
+ const faceToken = String(face?.name || face?.userData?.faceName || 'Face');
+ return inputs.map((edge, index) => {
+ const sourceName = String(edge?.name || edge?.metadata?.sourceEdgeName || edge?.sketchGeometryId || `EDGE_${index}`);
+ const metadata = (() => {
+ try {
+ const parsed = edge?.metadataJson ? JSON.parse(String(edge.metadataJson)) : {};
+ return parsed && typeof parsed === 'object' ? parsed : {};
+ } catch {
+ return {};
+ }
+ })();
+ if (sourceName) metadata.sourceEdgeName = sourceName;
+ return {
+ ...edge,
+ name: `${faceToken}:${sourceName}_RV`,
+ sketchGeometryId: edge?.sketchGeometryId ?? null,
+ polyline: (Array.isArray(edge?.polyline) ? edge.polyline : []).map((p) => [p[0], p[1], p[2]]),
+ curveType: edge?.curveType || edge?.sketchGeomType || null,
+ bezierPoles: Array.isArray(edge?.bezierPoles) ? edge.bezierPoles.map((p) => p.slice()) : null,
+ circleCenter: Array.isArray(edge?.circleCenter) ? edge.circleCenter.slice() : null,
+ circleRadius: Number.isFinite(Number(edge?.circleRadius)) ? Number(edge.circleRadius) : null,
+ arcCenter: Array.isArray(edge?.arcCenter) ? edge.arcCenter.slice() : null,
+ arcRadius: Number.isFinite(Number(edge?.arcRadius)) ? Number(edge.arcRadius) : null,
+ metadataJson: JSON.stringify(metadata),
+ };
+ }).filter((entry) => Array.isArray(entry.polyline) && entry.polyline.length >= 2);
+}
+
function generateNativeRevolve(target, params = {}) {
- requireNativeRevolveBuilder();
const { face, axis, angle = 360, resolution = 64 } = params;
if (!face || !face.geometry) return false;
+ const occFaceInfo = occFaceFromSelectedFace(face);
const axisObj = Array.isArray(axis) ? (axis[0] || null) : (axis || null);
const A = new THREE.Vector3(0, 0, 0);
const B = new THREE.Vector3(0, 1, 0);
if (axisObj) {
- const endpoints = getEdgeLineEndpointsWorld(axisObj);
- if (endpoints) {
- A.copy(endpoints.start);
- B.copy(endpoints.end);
+ const occAxis = occAxisFromSelectedEdge(axisObj);
+ if (occAxis) {
+ A.set(occAxis.start[0], occAxis.start[1], occAxis.start[2]);
+ B.set(occAxis.end[0], occAxis.end[1], occAxis.end[2]);
+ } else {
+ const endpoints = getEdgeLineEndpointsWorld(axisObj);
+ if (endpoints) {
+ A.copy(endpoints.start);
+ B.copy(endpoints.end);
+ }
}
}
let axisDir = B.clone().sub(A);
if (axisDir.lengthSq() < 1e-12) axisDir.set(0, 1, 0);
axisDir.normalize();
- const faceNormal = (typeof face.getAverageNormal === 'function')
- ? face.getAverageNormal().clone()
- : new THREE.Vector3(0, 1, 0);
+ const storedNormal = Array.isArray(face?.userData?.profileNormal) && face.userData.profileNormal.length >= 3
+ ? new THREE.Vector3(face.userData.profileNormal[0], face.userData.profileNormal[1], face.userData.profileNormal[2])
+ : null;
+ const faceNormal = storedNormal
+ || (typeof face.getAverageNormal === 'function'
+ ? face.getAverageNormal().clone()
+ : new THREE.Vector3(0, 1, 0));
const faceCentroid = computeFaceCentroidWorld(face);
if (faceNormal.lengthSq() < 1e-12) faceNormal.set(0, 1, 0);
faceNormal.normalize();
@@ -78,17 +127,13 @@ function generateNativeRevolve(target, params = {}) {
}
}
- const boundaryLoops = Array.isArray(face?.userData?.boundaryLoopsWorld) && face.userData.boundaryLoopsWorld.length
- ? face.userData.boundaryLoopsWorld.map((loop) => ({
- pts: (Array.isArray(loop?.pts) ? loop.pts : loop).map((p) => [p[0], p[1], p[2]]),
- isHole: !!loop?.isHole,
- }))
- : computeBoundaryLoopsFromFaceNative(face);
- if (!boundaryLoops.length) {
+ const boundaryLoops = cloneBoundaryLoopsWorld(face) || computeBoundaryLoopsFromFaceNative(face);
+ if (!boundaryLoops.length && !occFaceInfo?.face) {
throw new Error('Revolve generation requires boundary loops on the source face.');
}
- const edgeInputs = (Array.isArray(face?.edges) ? face.edges : [])
+ const storedEdgeInputs = cloneSketchEdgeInputsWorld(face);
+ const edgeInputs = storedEdgeInputs || (Array.isArray(face?.edges) ? face.edges : [])
.map((edge) => ({
name: `${edge?.name || 'EDGE'}_RV`,
polyline: getEdgePolylineWorld(edge, { dedupe: false }).map((p) => [p[0], p[1], p[2]]),
@@ -99,44 +144,21 @@ function generateNativeRevolve(target, params = {}) {
}))
.filter((entry) => Array.isArray(entry.polyline) && entry.polyline.length >= 2);
- const snapshot = manifold.buildRevolveAuthoringState({
- name: target.name || params.name || 'Revolve',
+ const occState = makeRevolution({
+ sourceFace: occFaceInfo?.face || null,
faceName: face?.name || 'Face',
+ boundaryLoops,
axisOrigin: [A.x, A.y, A.z],
axisDirection: [axisDir.x, axisDir.y, axisDir.z],
- angle: Number.isFinite(Number(angle)) ? Number(angle) : 360,
- resolution: Number.isFinite(Number(resolution)) ? Number(resolution) : 64,
- faceNormal: [faceNormal.x, faceNormal.y, faceNormal.z],
- boundaryLoops,
- edges: edgeInputs,
+ angleDegrees: Number.isFinite(Number(angle)) ? Number(angle) : 360,
+ edgeInputs,
+ normal: [faceNormal.x, faceNormal.y, faceNormal.z],
});
-
- applySolidAuthoringStateSnapshot(target, snapshot, { remapFaceIDs: true });
- target._dirty = true;
- target._manifold = null;
- target._faceIndex = null;
- try { target.setEpsilon(Number(snapshot?.suggestedEpsilon) || 1e-6); } catch {}
-
- try {
- const vp = Array.isArray(target._vertProperties) ? target._vertProperties : null;
- if (vp && vp.length >= 6) {
- const tmp = new THREE.Vector3();
- let minT = Infinity;
- let maxT = -Infinity;
- for (let i = 0; i < vp.length; i += 3) {
- tmp.set(vp[i], vp[i + 1], vp[i + 2]);
- const t = tmp.clone().sub(A).dot(axisDir);
- if (t < minT) minT = t;
- if (t > maxT) maxT = t;
- }
- if (Number.isFinite(minT) && Number.isFinite(maxT) && maxT - minT > 1e-9) {
- const p0 = A.clone().add(axisDir.clone().multiplyScalar(minT));
- const p1 = A.clone().add(axisDir.clone().multiplyScalar(maxT));
- target.addCenterline(p0, p1, `${target.name || 'Revolve'}_AXIS`, { polylineWorld: true });
- }
- }
- } catch {}
-
+ setOccState(target, occState);
+ try { target.name = params.name || target.name || 'Revolve'; } catch {}
+ if (axisObj) {
+ try { target.addCenterline(A, B, `${target.name || 'Revolve'}_AXIS`, { polylineWorld: true }); } catch {}
+ }
return true;
}
diff --git a/src/BREP/SolidMethods/authoring.js b/src/BREP/SolidMethods/authoring.js
index 549a16c2..6ec2a1b2 100644
--- a/src/BREP/SolidMethods/authoring.js
+++ b/src/BREP/SolidMethods/authoring.js
@@ -1,11 +1,4 @@
-import { Manifold } from "../SolidShared.js";
-import {
- cppSolidCoreHasAuthoringBridge,
- cppSolidCoreHasNativeAuxEdgeMutation,
- getSyncedCppSolidCore,
- requireCppSolidCoreCapability,
- syncSolidAuxEdgesFromCpp,
-} from "../CppSolidCore.js";
+import { reserveFaceID } from "../faceIdAllocator.js";
/**
* Solid authoring helpers: vertex/face ID management and convenience geometry.
@@ -50,10 +43,10 @@ export function _getPointIndex(p) {
return idx;
}
-/** Map face name to unique Manifold ID, creating one if absent. */
+/** Map face name to unique face ID, creating one if absent. */
export function _getOrCreateID(faceName) {
if (!this._faceNameToID.has(faceName)) {
- const id = Manifold.reserveIDs(1); // globally unique, propagates through CSG
+ const id = reserveFaceID();
this._faceNameToID.set(faceName, id);
this._idToFaceName.set(id, faceName);
}
@@ -89,38 +82,33 @@ export function addTriangle(faceName, v1, v2, v3) {
* @param {'OVERLAY'|'BASE'|string} [options.materialKey='OVERLAY'] Material tag for visualization
*/
export function addAuxEdge(name, points, options = {}) {
- try {
- const toArr = (p) => {
- if (Array.isArray(p) && p.length === 3) return [p[0], p[1], p[2]];
- if (p && typeof p === 'object') {
- const x = +p.x, y = +p.y, z = +p.z;
- if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)) return [x, y, z];
- }
- return null;
- };
- const pts = Array.isArray(points)
- ? points.map(toArr).filter(Boolean)
- : [];
- if (pts.length < 2) return this;
- const label = name || 'EDGE';
- const hasCenterlineOption = Object.prototype.hasOwnProperty.call(options || {}, 'centerline');
- const inferredCenterline = typeof label === 'string' && /centerline/i.test(label);
- const centerline = hasCenterlineOption ? !!options.centerline : inferredCenterline;
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeAuxEdgeMutation,
- 'Solid.addAuxEdge()',
- );
- const core = getSyncedCppSolidCore(this);
- core.addAuxEdge(label, pts, {
- closedLoop: !!options.closedLoop,
- polylineWorld: !!options.polylineWorld,
- materialKey: options.materialKey || 'OVERLAY',
- centerline,
- faceA: options.faceA || '',
- faceB: options.faceB || '',
- });
- syncSolidAuxEdgesFromCpp(this, core);
- } catch { /* ignore */ }
+ const toArr = (p) => {
+ if (Array.isArray(p) && p.length === 3) return [p[0], p[1], p[2]];
+ if (p && typeof p === 'object') {
+ const x = +p.x, y = +p.y, z = +p.z;
+ if (Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(z)) return [x, y, z];
+ }
+ return null;
+ };
+ const pts = Array.isArray(points)
+ ? points.map(toArr).filter(Boolean)
+ : [];
+ if (pts.length < 2) return this;
+ const label = name || 'EDGE';
+ const hasCenterlineOption = Object.prototype.hasOwnProperty.call(options || {}, 'centerline');
+ const inferredCenterline = typeof label === 'string' && /centerline/i.test(label);
+ const centerline = hasCenterlineOption ? !!options.centerline : inferredCenterline;
+ this._auxEdges = Array.isArray(this._auxEdges) ? this._auxEdges : [];
+ this._auxEdges.push({
+ name: label,
+ points: pts,
+ closedLoop: !!options.closedLoop,
+ polylineWorld: !!options.polylineWorld,
+ materialKey: options.materialKey || 'OVERLAY',
+ centerline,
+ faceA: options.faceA || '',
+ faceB: options.faceB || '',
+ });
return this;
}
diff --git a/src/BREP/SolidMethods/booleanOps.js b/src/BREP/SolidMethods/booleanOps.js
index 78fbd988..5b739ea1 100644
--- a/src/BREP/SolidMethods/booleanOps.js
+++ b/src/BREP/SolidMethods/booleanOps.js
@@ -1,12 +1,4 @@
-import {
- applySolidAuthoringStateSnapshot,
- cppSolidCoreHasNativeDisconnectedIslandCleanup,
- getSolidAuthoringStateSnapshot,
- getSyncedCppSolidCore,
- requireCppSolidCoreCapability,
- syncSolidAuthoringStateFromCpp,
-} from "../CppSolidCore.js";
-import { manifold } from "../setupManifold.js";
+import { booleanOccSolids, hasOccShape, setOccState } from "../OpenCascadeKernel.js";
/**
* Boolean operations and manifold reconstruction helpers.
@@ -15,15 +7,6 @@ import { manifold } from "../setupManifold.js";
const BOOLEAN_DISCONNECTED_ISLAND_MIN_VOLUME = 0.01;
const BOOLEAN_RESULT_WELD_EPSILON = 0.0015;
-function hasNativeBooleanCombinedBuilder() {
- return typeof manifold?.buildBooleanCombinedAuthoringState === "function";
-}
-
-function requireNativeBooleanCombinedBuilder(methodName) {
- if (hasNativeBooleanCombinedBuilder()) return;
- throw new Error(`${methodName} requires the custom local manifold build with native boolean result reconstruction support.`);
-}
-
function _isFallbackFaceName(name, idHint = null) {
if (name == null) return true;
const raw = String(name).trim();
@@ -70,16 +53,6 @@ function baseSolidCtor(obj) {
return (ctor && ctor.BaseSolid) ? ctor.BaseSolid : ctor;
}
-function solidFromNativeBooleanSnapshot(SolidCtor, snapshot, name) {
- const solid = new SolidCtor();
- applySolidAuthoringStateSnapshot(solid, snapshot);
- solid._dirty = true;
- solid._manifold = null;
- solid._faceIndex = null;
- try { solid.name = name || snapshot?.name || solid?.name; } catch { }
- return solid;
-}
-
function toMetadataJsonEntries(entriesLike) {
if (entriesLike instanceof Map) {
return Array.from(entriesLike.entries(), ([name, metadata]) => [
@@ -106,87 +79,26 @@ function toSnapshotEntries(entriesLike) {
return [];
}
-function toNativeBooleanSnapshot(snapshot) {
- return {
- numProp: Number(snapshot?.numProp ?? 3),
- vertProperties: Array.from(snapshot?.vertProperties ?? []),
- triVerts: Array.from(snapshot?.triVerts ?? []),
- triIDs: Array.from(snapshot?.triIDs ?? []),
- faceNameToID: toSnapshotEntries(snapshot?.faceNameToID),
- idToFaceName: toSnapshotEntries(snapshot?.idToFaceName),
- faceMetadataJson: toMetadataJsonEntries(snapshot?.faceMetadataJson),
- edgeMetadataJson: toMetadataJsonEntries(snapshot?.edgeMetadataJson),
- auxEdges: Array.isArray(snapshot?.auxEdges) ? snapshot.auxEdges : [],
- vertexCount: Number(snapshot?.vertexCount ?? 0),
- triangleCount: Number(snapshot?.triangleCount ?? 0),
- };
-}
-
-function buildNativeSnapshotFromManifold(manifoldObj, idToFaceName, opts = {}) {
- requireCppSolidCoreCapability(
- typeof manifold?.buildSolidAuthoringStateFromMesh === "function",
- "Solid._fromManifold",
- );
- const mesh = manifoldObj.getMesh();
- try {
- const resolvedIdToFaceName = new Map(idToFaceName instanceof Map ? idToFaceName : []);
- const faceNameToID = new Map();
- for (const [id, name] of resolvedIdToFaceName.entries()) {
- if (!faceNameToID.has(name)) faceNameToID.set(name, id);
- }
- return manifold.buildSolidAuthoringStateFromMesh({
- numProp: Number(mesh?.numProp ?? 3),
- vertProperties: Array.from(mesh?.vertProperties ?? []),
- triVerts: Array.from(mesh?.triVerts ?? []),
- faceID: Array.from(mesh?.faceID ?? []),
- faceNameToID: Array.from(faceNameToID.entries()),
- idToFaceName: Array.from(resolvedIdToFaceName.entries()),
- faceMetadataJson: toMetadataJsonEntries(opts?.faceMetadataJson),
- edgeMetadataJson: toMetadataJsonEntries(opts?.edgeMetadataJson),
- auxEdges: Array.isArray(opts?.auxEdges) ? opts.auxEdges : [],
- name: opts?.name || "",
- });
- } finally {
- try { if (mesh && typeof mesh.delete === "function") mesh.delete(); } catch { }
- }
-}
-
function buildNativeBooleanResult(left, right, operation, SolidCtor) {
- requireNativeBooleanCombinedBuilder(`Solid.${String(operation || "boolean").toLowerCase()}`);
- const leftSnapshot = getSolidAuthoringStateSnapshot(left);
- const rightSnapshot = getSolidAuthoringStateSnapshot(right);
- const snapshot = manifold.buildBooleanCombinedAuthoringState({
- // Native synced snapshots expose face/edge tables as JS Maps; serialize them
- // to plain entry arrays so the C++ boolean builder can read them reliably.
- leftSnapshot: toNativeBooleanSnapshot(leftSnapshot),
- rightSnapshot: toNativeBooleanSnapshot(rightSnapshot),
- operation,
- featureID: String(left?.owningFeatureID || left?.name || operation || "BOOLEAN"),
- name: String(left?.name || `${operation}_RESULT`),
- cleanupTinyFaceIslandsArea: BOOLEAN_DISCONNECTED_ISLAND_MIN_VOLUME,
- disconnectedIslandMinVolume: BOOLEAN_DISCONNECTED_ISLAND_MIN_VOLUME,
- });
- return solidFromNativeBooleanSnapshot(SolidCtor, snapshot, left?.name || `${operation}_RESULT`);
+ if (!hasOccShape(left) || !hasOccShape(right)) {
+ throw new Error(`Solid.${String(operation || "boolean").toLowerCase()} requires OpenCASCADE-backed solids.`);
+ }
+ const occState = booleanOccSolids(left, right, operation);
+ if (occState) {
+ const solid = new SolidCtor();
+ setOccState(solid, occState);
+ solid._auxEdges = [
+ ...(Array.isArray(left?._auxEdges) ? left._auxEdges : []),
+ ...(Array.isArray(right?._auxEdges) ? right._auxEdges : []),
+ ];
+ try { solid.name = left?.name || `${operation}_RESULT`; } catch { }
+ return solid;
+ }
+ throw new Error(`OpenCASCADE ${String(operation || "boolean").toLowerCase()} failed.`);
}
function _dropDisconnectedIslandsByVolume(solid, minVolume = BOOLEAN_DISCONNECTED_ISLAND_MIN_VOLUME) {
- const threshold = Number(minVolume);
- if (!Number.isFinite(threshold) || threshold <= 0) return 0;
- if (!solid || typeof solid !== "object") return 0;
- requireCppSolidCoreCapability(
- cppSolidCoreHasNativeDisconnectedIslandCleanup,
- "Solid._dropDisconnectedIslandsByVolume",
- );
- const core = getSyncedCppSolidCore(solid);
- const removed = core.removeDisconnectedIslandsByVolume(threshold);
- if (removed > 0) {
- syncSolidAuthoringStateFromCpp(solid, core);
- solid._dirty = true;
- solid._faceIndex = null;
- try { if (solid._manifold && typeof solid._manifold.delete === 'function') solid._manifold.delete(); } catch { }
- solid._manifold = null;
- }
- return removed;
+ return 0;
}
function _applyFixedBooleanResultWeld(solid) {
@@ -236,50 +148,15 @@ export function difference(other) {
}
export function setTolerance(tolerance) {
- const Solid = baseSolidCtor(this);
- const m = this._manifoldize();
- const outM = m.setTolerance(tolerance);
- const authoringSnapshot = getSolidAuthoringStateSnapshot(this);
- const out = Solid._fromManifold(outM, new Map(this._idToFaceName), {
- faceMetadataJson: authoringSnapshot?.faceMetadataJson,
- edgeMetadataJson: authoringSnapshot?.edgeMetadataJson,
- auxEdges: authoringSnapshot?.auxEdges,
- name: this?.name || "",
- });
- return out;
+ return this.clone();
}
export function simplify(tolerance = undefined, updateInPlace = false) {
- const Solid = this.constructor;
- const m = this._manifoldize();
- const authoringSnapshot = getSolidAuthoringStateSnapshot(this);
-
- // Run simplify on the manifold
- const outM = (tolerance === undefined) ? m.simplify() : m.simplify(tolerance);
- const outSnapshot = buildNativeSnapshotFromManifold(outM, this._idToFaceName, {
- faceMetadataJson: authoringSnapshot?.faceMetadataJson,
- edgeMetadataJson: authoringSnapshot?.edgeMetadataJson,
- auxEdges: authoringSnapshot?.auxEdges,
- name: this?.name || "",
- });
-
- applySolidAuthoringStateSnapshot(this, outSnapshot);
- try { if (this._manifold && this._manifold !== outM && typeof this._manifold.delete === 'function') this._manifold.delete(); } catch { }
- this._manifold = outM;
- this._dirty = false;
- this._faceIndex = null;
-
+ const out = this.clone();
if (updateInPlace) {
+ if (hasOccShape(out)) setOccState(this, out._occ);
return this;
}
-
- // Detach this solid from `outM` before rebuilding a second solid from it.
- // This avoids sharing/deleting one manifold object between two Solid instances.
- this._manifold = null;
- this._dirty = true;
- this._faceIndex = null;
- const returnObject = solidFromNativeBooleanSnapshot(Solid, outSnapshot, this?.name || "");
- this._manifoldize();
- return returnObject;
+ return out;
}
export function _expandTriIDsFromMesh(mesh) {
@@ -290,12 +167,5 @@ export function _expandTriIDsFromMesh(mesh) {
}
export function _fromManifold(manifoldObj, idToFaceName, opts = {}) {
- const Solid = this;
- const solid = new Solid();
- const snapshot = buildNativeSnapshotFromManifold(manifoldObj, idToFaceName, opts);
- applySolidAuthoringStateSnapshot(solid, snapshot);
- solid._manifold = manifoldObj;
- solid._dirty = false;
- solid._faceIndex = null;
- return solid;
+ throw new Error("Solid._fromManifold() is disabled in the OpenCASCADE kernel.");
}
diff --git a/src/BREP/SolidMethods/chamfer.js b/src/BREP/SolidMethods/chamfer.js
index 7029ccd6..011d54b9 100644
--- a/src/BREP/SolidMethods/chamfer.js
+++ b/src/BREP/SolidMethods/chamfer.js
@@ -1,29 +1,7 @@
// Solid.chamfer implementation: wraps native chamfer tool generation and applies booleans.
-import { applySolidAuthoringStateSnapshot, buildSolidAuthoringStateSnapshot } from '../CppSolidCore.js';
-import { manifold } from '../setupManifold.js';
+import { chamferOccSolid, hasOccShape, setOccState } from '../OpenCascadeKernel.js';
import { resolveEdgesFromInputs } from './edgeResolution.js';
-function hasNativeChamferWorkflowBuilder() {
- return typeof manifold?.buildChamferWorkflowAuthoringState === 'function';
-}
-
-function requireNativeChamferWorkflow() {
- if (!hasNativeChamferWorkflowBuilder()) {
- throw new Error('Chamfer generation requires the custom local manifold build with native chamfer workflow support.');
- }
-}
-
-function solidFromSnapshot(snapshot, name, SolidCtor) {
- if (!snapshot) return null;
- const solid = new SolidCtor();
- applySolidAuthoringStateSnapshot(solid, snapshot, { remapFaceIDs: true });
- solid._dirty = true;
- solid._manifold = null;
- solid._faceIndex = null;
- try { solid.name = name; } catch { }
- return solid;
-}
-
/**
* Apply chamfers to this Solid and return a new Solid with the result.
*
@@ -44,125 +22,28 @@ function solidFromSnapshot(snapshot, name, SolidCtor) {
* @returns {import('../BetterSolid.js').Solid}
*/
export async function chamfer(opts = {}) {
- requireNativeChamferWorkflow();
const { Solid } = await import("../BetterSolid.js");
const distance = Number(opts.distance);
if (!Number.isFinite(distance) || distance <= 0) {
throw new Error(`Solid.chamfer: distance must be > 0, got ${opts.distance}`);
}
- const dirMode = String(opts.direction || 'INSET').toUpperCase();
- const autoDirection = dirMode === 'AUTO';
- const dir = dirMode === 'OUTSET' ? 'OUTSET' : (autoDirection ? 'AUTO' : 'INSET');
- const inflateRaw = Number.isFinite(opts.inflate) ? Number(opts.inflate) : 0.1;
- const debug = !!opts.debug;
- const featureID = opts.featureID || 'CHAMFER';
- console.log('[Solid.chamfer] Begin', {
- featureID,
- solid: this?.name,
- distance,
- direction: dirMode,
- inflate: inflateRaw,
- debug,
- requestedEdgeNames: Array.isArray(opts.edgeNames) ? opts.edgeNames : [],
- providedEdgeCount: Array.isArray(opts.edges) ? opts.edges.length : 0,
- });
-
- // Resolve edges from names and/or provided objects
- const unique = resolveEdgesFromInputs(this, { edgeNames: opts.edgeNames, edges: opts.edges });
- if (unique.length === 0) {
- console.warn('[Solid.chamfer] No edges resolved on target solid; returning clone.', { featureID, solid: this?.name });
- const c = this.clone();
- try { c.name = this.name; } catch { }
- return c;
- }
-
- const baseSnapshot = buildSolidAuthoringStateSnapshot(this);
- const edgePayload = [];
- let idx = 0;
- for (const e of unique) {
- const faceAName = String(e?.faces?.[0]?.name || e?.userData?.faceA || '');
- const faceBName = String(e?.faces?.[1]?.name || e?.userData?.faceB || '');
- const polyline = Array.isArray(e?.userData?.polylineLocal) ? e.userData.polylineLocal : [];
- if (!faceAName || !faceBName || polyline.length < 2) {
- console.warn('[Solid.chamfer] Skipping edge with missing faces/polyline.', { edge: e?.name });
- continue;
+ if (hasOccShape(this)) {
+ const unique = resolveEdgesFromInputs(this, { edgeNames: opts.edgeNames, edges: opts.edges });
+ if (unique.length === 0) {
+ const c = this.clone();
+ try { c.name = this.name; } catch { }
+ return c;
}
- edgePayload.push({
- name: `${featureID}_CHAMFER_${idx++}`,
- edgeReference: String(e?.name || `EDGE_${idx}`),
- faceAName,
- faceBName,
- polyline,
- closedLoop: !!(e?.closedLoop || e?.userData?.closedLoop),
- sampleCount: opts.sampleCount,
- snapSeamToEdge: opts.snapSeamToEdge,
- flipSide: opts.flipSide,
- });
- }
-
- if (edgePayload.length === 0) {
- console.error('[Solid.chamfer] All chamfer inputs failed; returning clone.', { featureID, edgeCount: unique.length });
- const c = this.clone();
- try { c.name = this.name; } catch { }
- return c;
- }
-
- const nativeResult = manifold.buildChamferWorkflowAuthoringState({
- snapshot: baseSnapshot,
- edges: edgePayload,
- distance,
- directionMode: dir,
- inflate: inflateRaw,
- featureID,
- name: this?.name || `${featureID}_FINAL_CHAMFER`,
- cleanupTinyFaceIslandsArea: Number.isFinite(opts.cleanupTinyFaceIslandsArea)
- ? Number(opts.cleanupTinyFaceIslandsArea)
- : 0.01,
- debug,
- });
- if (autoDirection && nativeResult?.directionDecision) {
- console.log('[Solid.chamfer] AUTO direction classification complete.', {
- featureID,
- insetEdges: nativeResult.directionDecision.insetEdges,
- outsetEdges: nativeResult.directionDecision.outsetEdges,
- fallbackEdges: nativeResult.directionDecision.fallbackEdges,
- ambiguousEdges: nativeResult.directionDecision.ambiguousEdges,
- });
- }
-
- const finalSnapshot = nativeResult?.finalSnapshot || null;
- const result = solidFromSnapshot(finalSnapshot, this?.name || `${featureID}_FINAL_CHAMFER`, Solid);
- if (!result) {
- throw new Error('Native chamfer workflow returned no result snapshot.');
- }
-
- const debugChamferSolids = [];
- const debugSnapshots = Array.isArray(nativeResult?.debugSnapshots) ? nativeResult.debugSnapshots : [];
- for (const entry of debugSnapshots) {
- const debugSolid = solidFromSnapshot(entry?.snapshot, String(entry?.name || 'CHAMFER_DEBUG'), Solid);
- if (debugSolid) {
- try { debugSolid.__debugChamferKind = String(entry?.kind || ''); } catch { }
- try { debugSolid.__debugChamferName = String(entry?.name || debugSolid?.name || ''); } catch { }
- debugChamferSolids.push(debugSolid);
+ const occState = chamferOccSolid(this, unique, { distance, featureID: opts.featureID || 'CHAMFER' });
+ if (!occState) {
+ const c = this.clone();
+ try { c.name = this.name; } catch { }
+ return c;
}
+ const result = new Solid();
+ setOccState(result, occState);
+ try { result.name = this?.name || `${opts.featureID || 'CHAMFER'}_FINAL_CHAMFER`; } catch { }
+ return result;
}
- try { result.__debugChamferSolids = debugChamferSolids; } catch { }
- try { result.__chamferDirectionDecision = nativeResult?.directionDecision || null; } catch { }
-
- const finalTriCount = Array.isArray(result?._triVerts) ? (result._triVerts.length / 3) : 0;
- const finalVertCount = Array.isArray(result?._vertProperties) ? (result._vertProperties.length / 3) : 0;
- if (!result || finalTriCount === 0 || finalVertCount === 0) {
- console.error('[Solid.chamfer] Chamfer result is empty or missing geometry.', {
- featureID,
- finalTriCount,
- finalVertCount,
- edgeCount: unique.length,
- direction: dir,
- inflate: inflateRaw,
- });
- } else {
- console.log('[Solid.chamfer] Completed', { featureID, triangles: finalTriCount, vertices: finalVertCount });
- }
-
- return result;
+ throw new Error('Solid.chamfer() requires an OpenCASCADE-backed solid; legacy Manifold chamfer fallback has been removed.');
}
diff --git a/src/BREP/SolidMethods/fillet.js b/src/BREP/SolidMethods/fillet.js
index 89bc5211..93e649fb 100644
--- a/src/BREP/SolidMethods/fillet.js
+++ b/src/BREP/SolidMethods/fillet.js
@@ -1,56 +1,14 @@
// Solid.fillet implementation: consolidates fillet logic so features call this API.
// Usage: solid.fillet({ radius, edges, featureID, direction, inflate, resolution, debug, debugSolidsLevel, debugShowCombinedBeforeTarget })
import { resolveEdgesFromInputs } from './edgeResolution.js';
-import {
- applySolidAuthoringStateSnapshot,
- buildSolidAuthoringStateSnapshot,
- getSyncedCppSolidCore,
- syncSolidAuthoringStateFromCpp,
-} from '../CppSolidCore.js';
-import { manifold } from '../setupManifold.js';
-
-function hasNativeFilletCombinedBuilder() {
- return typeof manifold?.buildFilletCombinedAuthoringState === 'function';
-}
-
-function hasNativeFilletBatchBuilder() {
- return typeof manifold?.buildFilletBatchAuthoringState === 'function';
-}
-
-function hasNativeFilletCornerBridgeBuilder() {
- return typeof manifold?.buildFilletCornerBridgeAuthoringState === 'function';
-}
-
-function hasNativeFilletDirectionClassifier() {
- return typeof manifold?.classifyFilletEdgeDirection === 'function';
-}
+import { filletOccSolid, hasOccShape, setOccState } from '../OpenCascadeKernel.js';
function requireNativeFilletCombinedBuilder() {
- if (!hasNativeFilletBatchBuilder()) {
- throw new Error('Solid.fillet() requires the custom local manifold build with native fillet batch support.');
- }
- if (!hasNativeFilletCombinedBuilder()) {
- throw new Error('Solid.fillet() requires the custom local manifold build with native fillet combine support.');
- }
- if (!hasNativeFilletCornerBridgeBuilder()) {
- throw new Error('Solid.fillet() requires the custom local manifold build with native fillet corner-bridge support.');
- }
- if (!hasNativeFilletDirectionClassifier()) {
- throw new Error('Solid.fillet() requires the custom local manifold build with native fillet direction classification support.');
- }
+ throw new Error('Solid.fillet() requires an OpenCASCADE-backed solid; legacy mesh fillet fallback has been removed.');
}
-function solidFromSnapshot(snapshot, SolidClass, name = null) {
- if (!snapshot || !SolidClass) return null;
- const solid = new SolidClass();
- applySolidAuthoringStateSnapshot(solid, snapshot);
- solid._dirty = true;
- solid._manifold = null;
- solid._faceIndex = null;
- if (typeof name === 'string' && name.length > 0) {
- try { solid.name = name; } catch { }
- }
- return solid;
+function solidFromSnapshot(_snapshot, _SolidClass, _name = null) {
+ return null;
}
function deriveSolidToleranceFromVerts(solid, baseTol = 1e-5) {
@@ -731,7 +689,6 @@ function mergeCoplanarAdjacentFilletEndCaps(solid, opts = {}) {
const distanceTolerance = Math.max(solidTol * 4, 2e-4, nudgeDistance * 3);
const planarityTolerance = Math.max(distanceTolerance * 2, 2e-4, nudgeDistance * 4);
const normalTolerance = Math.max(1e-6, Math.abs(Number(opts?.normalTolerance) || 2e-4));
- const core = getSyncedCppSolidCore(solid);
let mergedEndCaps = 0;
const maxPasses = Math.max(1, (solid.getFaceNames() || []).length);
@@ -757,14 +714,14 @@ function mergeCoplanarAdjacentFilletEndCaps(solid, opts = {}) {
});
if (!target?.faceName) continue;
- const targetMetadata = cloneFaceMetadataObject(core.getFaceMetadata(target.faceName) || {});
- if (!core.renameFace(endCapFaceName, target.faceName)) continue;
- core.setFaceMetadata(target.faceName, targetMetadata);
- syncSolidAuthoringStateFromCpp(solid, core);
+ const targetMetadata = cloneFaceMetadataObject(
+ typeof solid.getFaceMetadata === 'function' ? (solid.getFaceMetadata(target.faceName) || {}) : {}
+ );
+ if (typeof solid.renameFace !== 'function') continue;
+ solid.renameFace(endCapFaceName, target.faceName);
+ if (typeof solid.setFaceMetadata === 'function') solid.setFaceMetadata(target.faceName, targetMetadata);
solid._dirty = true;
solid._faceIndex = null;
- try { if (solid._manifold && typeof solid._manifold.delete === 'function') solid._manifold.delete(); } catch { }
- solid._manifold = null;
mergedEndCaps += 1;
mergedThisPass = true;
@@ -1233,8 +1190,6 @@ function reassignTinyFilletSidewallSliverTriangles(solid, opts = {}) {
pruneUnusedFaceLabelsFromTriangles(solid);
solid._faceIndex = null;
solid._dirty = true;
- try { if (solid._manifold && typeof solid._manifold.delete === 'function') solid._manifold.delete(); } catch { }
- solid._manifold = null;
if (debug && mutationDetails.length > 0) {
console.log('[Solid.fillet] Reassigned tiny fillet sliver triangles into planar neighbors.', {
@@ -1280,28 +1235,47 @@ export { reassignTinyFilletSidewallSliverTriangles as __testOnlyReassignTinyFill
* @returns {import('../BetterSolid.js').Solid}
*/
export async function fillet(opts = {}) {
- requireNativeFilletCombinedBuilder();
const radius = Number(opts.radius);
if (!Number.isFinite(radius) || radius <= 0) {
throw new Error(`Solid.fillet: radius must be > 0, got ${opts.radius}`);
}
+ if (hasOccShape(this)) {
+ const unique = resolveEdgesFromInputs(this, { edgeNames: opts.edgeNames, edges: opts.edges });
+ if (unique.length === 0) {
+ const c = this.clone();
+ try { c.name = this.name; } catch { }
+ return c;
+ }
+ const occState = filletOccSolid(this, unique, { radius, featureID: opts.featureID || 'FILLET' });
+ if (!occState) {
+ const c = this.clone();
+ try { c.name = this.name; } catch { }
+ return c;
+ }
+ const SolidClass = this?.constructor?.BaseSolid || this?.constructor || null;
+ const result = new SolidClass();
+ setOccState(result, occState);
+ try { result.name = this?.name || `${opts.featureID || 'FILLET'}_FINAL_FILLET`; } catch { }
+ return result;
+ }
+ requireNativeFilletCombinedBuilder();
const directionMode = normalizeFilletDirectionMode(opts.direction);
const autoDirection = directionMode === 'AUTO';
- const inflate = Number.isFinite(opts.inflate) ? Number(opts.inflate) : 0.1;
+ const _inflate = Number.isFinite(opts.inflate) ? Number(opts.inflate) : 0.1;
const nudgeFaceDistanceRaw = Number(opts.nudgeFaceDistance);
const nudgeFaceDistance = Number.isFinite(nudgeFaceDistanceRaw) ? nudgeFaceDistanceRaw : 0.0001;
const debug = !!opts.debug;
const debugSolidsLevelRaw = Number(opts.debugSolidsLevel);
- const debugSolidsLevel = Number.isFinite(debugSolidsLevelRaw)
+ const _debugSolidsLevel = Number.isFinite(debugSolidsLevelRaw)
? Math.max(-1, Math.min(2, Math.floor(debugSolidsLevelRaw)))
: 0;
- const debugShowCombinedBeforeTarget = !!opts.debugShowCombinedBeforeTarget;
+ const _debugShowCombinedBeforeTarget = !!opts.debugShowCombinedBeforeTarget;
const resolutionRaw = Number(opts.resolution);
- const resolution = (Number.isFinite(resolutionRaw) && resolutionRaw > 0)
+ const _resolution = (Number.isFinite(resolutionRaw) && resolutionRaw > 0)
? Math.max(8, Math.floor(resolutionRaw))
: 32;
const cleanupTinyFaceIslandsAreaRaw = Number(opts.cleanupTinyFaceIslandsArea);
- const cleanupTinyFaceIslandsArea = Number.isFinite(cleanupTinyFaceIslandsAreaRaw)
+ const _cleanupTinyFaceIslandsArea = Number.isFinite(cleanupTinyFaceIslandsAreaRaw)
? cleanupTinyFaceIslandsAreaRaw
: 0.01;
const mergeCoplanarEndCaps = opts.mergeCoplanarEndCaps !== false;
@@ -1318,7 +1292,7 @@ export async function fillet(opts = {}) {
try { c.name = this.name; } catch { }
return c;
}
- const baseSnapshot = buildSolidAuthoringStateSnapshot(this);
+ const _baseSnapshot = null;
const debugAdded = [];
const buildFallbackResult = () => {
const fallback = this.clone();
@@ -1361,21 +1335,7 @@ export async function fillet(opts = {}) {
let nativeResult = null;
try {
- nativeResult = manifold.buildFilletAuthoringState({
- snapshot: baseSnapshot,
- edges: edgePayload,
- radius,
- directionMode,
- inflate,
- nudgeFaceDistance,
- resolution,
- featureID,
- name: this?.name || `${featureID}_FINAL_FILLET`,
- cleanupTinyFaceIslandsArea,
- debug: !!debug,
- debugSolidsLevel,
- debugShowCombinedBeforeTarget,
- });
+ nativeResult = null;
} catch (err) {
console.error('[Solid.fillet] Native fillet build failed; returning clone.', {
featureID,
diff --git a/src/BREP/SolidMethods/index.js b/src/BREP/SolidMethods/index.js
index 7eaddeec..5e7045b9 100644
--- a/src/BREP/SolidMethods/index.js
+++ b/src/BREP/SolidMethods/index.js
@@ -24,13 +24,11 @@ export {
pushFace,
} from "./transforms.js";
export {
- _manifoldize,
setEpsilon,
_weldVerticesByEpsilon,
fixTriangleWindingsByAdjacency,
- _isCoherentlyOrientedManifold,
invertNormals,
-} from "./manifoldOps.js";
+} from "./legacyMeshOps.js";
export {
removeSmallIslands,
removeSmallInternalIslands,
@@ -45,7 +43,7 @@ export {
removeInternalTrianglesByRaycast,
removeInternalTrianglesByWinding,
mergeTinyFaces,
-} from "./meshCleanup.js";
+} from "./meshCleanupStubs.js";
export {
getMesh,
_ensureFaceIndex,
@@ -60,8 +58,6 @@ export {
intersect,
difference,
_combineIdMaps,
- _expandTriIDsFromMesh as _expandTriIDsFromMeshStatic,
- _fromManifold as _fromManifoldStatic,
setTolerance,
simplify,
} from "./booleanOps.js";
diff --git a/src/BREP/SolidMethods/io.js b/src/BREP/SolidMethods/io.js
index 0f982261..00abb45c 100644
--- a/src/BREP/SolidMethods/io.js
+++ b/src/BREP/SolidMethods/io.js
@@ -1,4 +1,5 @@
import { generateSTEP } from '../../exporters/step.js';
+import { hasOccShape, occSTEP } from '../OpenCascadeKernel.js';
/**
* Export helpers (STL + STEP output).
@@ -82,6 +83,9 @@ export function toSTEP(name = undefined, options = {}) {
const scale = Number.isFinite(opts.scale) ? opts.scale : 1;
const applyWorldTransform = opts.applyWorldTransform !== false;
const baseName = name || this?.name || 'part';
+ if (hasOccShape(this) && opts.useTessellatedFaces !== true && scale === 1 && applyWorldTransform) {
+ return occSTEP(this, baseName);
+ }
const stepOpts = { ...opts, name: baseName, unit, precision, scale, applyWorldTransform };
const { data } = generateSTEP([this], stepOpts);
return data;
diff --git a/src/BREP/SolidMethods/legacyMeshOps.js b/src/BREP/SolidMethods/legacyMeshOps.js
new file mode 100644
index 00000000..acede0c1
--- /dev/null
+++ b/src/BREP/SolidMethods/legacyMeshOps.js
@@ -0,0 +1,34 @@
+import { hasOccShape } from "../OpenCascadeKernel.js";
+
+/**
+ * Removed legacy mesh cleanup and orientation helpers.
+ */
+
+/**
+ * Set legacy vertex weld epsilon for compatibility.
+ */
+export function setEpsilon(epsilon = 0) {
+ this._epsilon = Number(epsilon) || 0;
+ return this;
+}
+
+export function _weldVerticesByEpsilon(eps, options = {}) {
+ void eps;
+ void options;
+ if (hasOccShape(this)) return this;
+ throw new Error("Solid._weldVerticesByEpsilon() has been removed. Use OpenCASCADE-backed solids.");
+}
+
+/**
+ * Ensures all triangles have consistent winding by making sure
+ * shared edges are oriented oppositely between adjacent triangles.
+ */
+export function fixTriangleWindingsByAdjacency() {
+ if (hasOccShape(this)) return this;
+ throw new Error("Solid.fixTriangleWindingsByAdjacency() has been removed. Use OpenCASCADE-backed solids.");
+}
+
+export function invertNormals() {
+ if (hasOccShape(this)) return this;
+ throw new Error("Solid.invertNormals() has been removed. Use OpenCASCADE-backed solids.");
+}
diff --git a/src/BREP/SolidMethods/lifecycle.js b/src/BREP/SolidMethods/lifecycle.js
index b67cc6d9..1131ca45 100644
--- a/src/BREP/SolidMethods/lifecycle.js
+++ b/src/BREP/SolidMethods/lifecycle.js
@@ -1,22 +1,18 @@
/**
* Solid lifecycle helpers: constructor, cloning, resource cleanup.
*/
-import {
- applySolidAuthoringStateSnapshot,
- getSolidAuthoringStateSnapshot,
-} from "../CppSolidCore.js";
export function constructorImpl() {
- // Geometry data (MeshGL layout, but we build incrementally in JS arrays)
+ // Legacy authoring buffers retained for metadata, aux-edge, and compatibility paths.
this._numProp = 3; // x,y,z
this._vertProperties = []; // flat [x0,y0,z0, x1,y1,z1, ...]
this._triVerts = []; // flat [i0,i1,i2, i3,i4,i5, ...]
- this._triIDs = []; // per-triangle Manifold ID (mapped from faceName)
+ this._triIDs = []; // per-triangle face ID (mapped from faceName)
// Vertex uniquing
this._vertKeyToIndex = new Map(); // "x,y,z" -> index
- // Face name <-> Manifold ID
+ // Face name <-> stable face ID
this._faceNameToID = new Map();
this._idToFaceName = new Map();
@@ -27,13 +23,12 @@ export function constructorImpl() {
this._edgeMetadataVersion = 0;
// Laziness & caching
- this._dirty = true; // arrays changed and manifold needs rebuild
- this._manifold = null; // cached Manifold object built from arrays
+ this._dirty = true;
this._faceIndex = null; // lazy cache: id -> [triIndices]
this._epsilon = 0; // optional vertex weld tolerance (off by default)
this._freeTimer = null; // handle for scheduled wasm cleanup
- this._cppSolidCore = null; // optional reusable native authoring bridge
- this._cppSolidCoreSyncStamp = null;
+ this._kernel = "opencascade";
+ this._occ = null; // OpenCASCADE TopoDS_Shape-backed state
this.type = 'SOLID';
this.renderOrder = 1;
@@ -49,39 +44,47 @@ export function constructorImpl() {
export function clone() {
const Solid = this.constructor;
const s = new Solid();
- applySolidAuthoringStateSnapshot(s, getSolidAuthoringStateSnapshot(this));
+ s._numProp = this._numProp || 3;
+ s._vertProperties = Array.from(this._vertProperties || []);
+ s._triVerts = Array.from(this._triVerts || []);
+ s._triIDs = Array.from(this._triIDs || []);
+ s._vertKeyToIndex = new Map(this._vertKeyToIndex instanceof Map ? this._vertKeyToIndex.entries() : []);
+ s._faceNameToID = new Map(this._faceNameToID instanceof Map ? this._faceNameToID.entries() : []);
+ s._idToFaceName = new Map(this._idToFaceName instanceof Map ? this._idToFaceName.entries() : []);
+ s._faceMetadata = new Map(this._faceMetadata instanceof Map ? this._faceMetadata.entries() : []);
+ s._edgeMetadata = new Map(this._edgeMetadata instanceof Map ? this._edgeMetadata.entries() : []);
+ s._faceMetadataVersion = this._faceMetadataVersion || 0;
+ s._edgeMetadataVersion = this._edgeMetadataVersion || 0;
+ s._auxEdges = Array.isArray(this._auxEdges)
+ ? this._auxEdges.map((edge) => ({
+ ...(edge || {}),
+ points: Array.isArray(edge?.points) ? edge.points.map((p) => Array.from(p || [])) : [],
+ }))
+ : [];
s._dirty = true;
- s._manifold = null;
s._faceIndex = null;
- s._cppSolidCore = null;
- s._cppSolidCoreSyncStamp = null;
+ s._kernel = this._kernel || "opencascade";
+ s._occ = this._occ ? {
+ ...this._occ,
+ faceNames: Array.from(this._occ.faceNames || []),
+ faceMetadata: new Map(this._occ.faceMetadata instanceof Map ? this._occ.faceMetadata.entries() : []),
+ edgeMetadata: new Map(this._occ.edgeMetadata instanceof Map ? this._occ.edgeMetadata.entries() : []),
+ meshCache: null,
+ } : null;
s.type = 'SOLID';
s.renderOrder = this.renderOrder;
return s;
}
/**
- * Free wasm resources associated with this Solid.
- *
- * Disposes the underlying Manifold instance (if any) to prevent
- * accumulating wasm memory across rebuilds. After calling free(),
- * the Solid remains usable - any subsequent call that needs the
- * manifold will trigger a fresh _manifoldize().
+ * Free cached resources associated with this Solid.
*/
export function free() {
try {
// Clear any pending auto-free timer first
try { if (this._freeTimer) { clearTimeout(this._freeTimer); } } catch (_) { }
this._freeTimer = null;
- if (this._manifold) {
- try { if (typeof this._manifold.delete === 'function') this._manifold.delete(); } catch (_) { }
- this._manifold = null;
- }
- if (this._cppSolidCore) {
- try { if (typeof this._cppSolidCore.dispose === 'function') this._cppSolidCore.dispose(); } catch (_) { }
- this._cppSolidCore = null;
- }
- this._cppSolidCoreSyncStamp = null;
+ if (this._occ) this._occ.meshCache = null;
this._dirty = true;
this._faceIndex = null;
} catch (_) { /* noop */ }
diff --git a/src/BREP/SolidMethods/manifoldOps.js b/src/BREP/SolidMethods/manifoldOps.js
deleted file mode 100644
index 58bf5739..00000000
--- a/src/BREP/SolidMethods/manifoldOps.js
+++ /dev/null
@@ -1,207 +0,0 @@
-import { Manifold, ManifoldMesh, debugMode } from "../SolidShared.js";
-import {
- CppSolidCore,
- cppSolidCoreHasAuthoringBridge,
- cppSolidCoreHasNativeManifoldPrep,
- cppSolidCoreHasNativeWeldVerticesByEpsilon,
- requireCppSolidCoreCapability,
- syncSolidAuthoringStateFromCpp,
- syncSolidAuthoringStateToCpp,
-} from "../CppSolidCore.js";
-
-/**
- * Manifold lifecycle helpers: rebuild, welding, orientation fixes.
- */
-
-/**
- * Build (or rebuild) the Manifold from our MeshGL arrays.
- * Uses faceID per triangle so face names survive CSG operations.
- */
-export function _manifoldize() {
- // Measure timing for manifoldization (cache hits vs rebuilds)
- const nowMs = () => (typeof performance !== 'undefined' && performance?.now ? performance.now() : Date.now());
- const __t0 = nowMs();
- // Reset the auto-free timer: always schedule cleanup 60s after last use
- try { if (this._freeTimer) { clearTimeout(this._freeTimer); } } catch { }
- try {
- this._freeTimer = setTimeout(() => {
- try { this.free(); } catch { }
- }, 60 * 1000);
- } catch { }
- if (!this._dirty && this._manifold) {
- const __t1 = nowMs();
- try { if (debugMode) console.log(`[Solid] _manifoldize cache-hit in ${Math.round(__t1 - __t0)} ms`); } catch { }
- return this._manifold;
- }
- let __logged = false;
- const __logDone = (ok = true) => {
- if (__logged) return; __logged = true;
- const __t1 = nowMs();
- const triCountDbg = (this?._triVerts?.length || 0) / 3 | 0;
- const vertCountDbg = (this?._vertProperties?.length || 0) / 3 | 0;
- try {
- if (debugMode) console.log(`[Solid] _manifoldize ${ok ? 'built' : 'failed'} in ${Math.round(__t1 - __t0)} ms (tris=${triCountDbg}, verts=${vertCountDbg})`);
- } catch { }
- };
- try {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeManifoldPrep,
- "Solid._manifoldize()"
- );
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- const prepared = this._cppSolidCore.prepareManifoldMesh();
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
-
- const mesh = new ManifoldMesh({
- numProp: Number(prepared?.numProp ?? this._numProp ?? 3),
- vertProperties: new Float32Array(prepared?.vertProperties ?? []),
- triVerts: new Uint32Array(prepared?.triVerts ?? []),
- faceID: new Uint32Array(prepared?.faceID ?? []),
- mergeFromVert: new Uint32Array(prepared?.mergeFromVert ?? []),
- mergeToVert: new Uint32Array(prepared?.mergeToVert ?? []),
- });
-
- try {
- this._manifold = new Manifold(mesh);
- } catch (err) {
- // If this Solid is a FilletSolid (identified by presence of edgeToFillet),
- // emit a structured JSON log with diagnostic context for debugging.
- try {
- if (this && Object.prototype.hasOwnProperty.call(this, 'edgeToFillet')) {
- const triCountInfo = (this._triVerts?.length || 0) / 3 | 0;
- const vertCountInfo = (this._vertProperties?.length || 0) / 3 | 0;
- const faces = [];
- try {
- if (this.edgeToFillet && Array.isArray(this.edgeToFillet.faces)) {
- for (const f of this.edgeToFillet.faces) if (f && f.name) faces.push(f.name);
- }
- } catch { }
- const failure = {
- type: 'FilletSolidManifoldFailure',
- message: (err && (err.message || String(err))) || 'unknown',
- params: {
- radius: this.radius,
- arcSegments: this.arcSegments,
- sampleCount: this.sampleCount,
- sideMode: this.sideMode,
- inflate: this.inflate,
- sideStripSubdiv: this.sideStripSubdiv,
- seamInsetScale: this.seamInsetScale,
- projectStripsOpenEdges: this.projectStripsOpenEdges,
- forceSeamInset: this.forceSeamInset,
- },
- edge: {
- name: this.edgeToFillet?.name || null,
- closedLoop: !!(this.edgeToFillet?.closedLoop || this.edgeToFillet?.userData?.closedLoop),
- faces,
- },
- counts: {
- vertices: vertCountInfo,
- triangles: triCountInfo,
- faceLabels: (this._faceNameToID && typeof this._faceNameToID.size === 'number') ? this._faceNameToID.size : undefined,
- },
- };
- try { console.error(JSON.stringify(failure)); } catch { console.error('[FilletSolidManifoldFailure]', failure.message); }
- }
- } catch { }
- __logDone(false);
- throw err;
- }
- finally {
- try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { }
- }
- this._dirty = false;
- this._faceIndex = null; // will rebuild on demand
- __logDone(true);
- return this._manifold;
- } finally {
- // In case of unexpected control flow, ensure we log once with best-effort status.
- const ok = !!(this && this._manifold) && this._dirty === false;
- __logDone(ok);
- }
-}
-
-/**
- * Set vertex weld epsilon and optionally align existing authored
- * vertices that fall within the epsilon distance. Epsilon <= 0
- * disables welding; manifold prep handles later dedup/cleanup.
- */
-export function setEpsilon(epsilon = 0) {
- this._epsilon = Number(epsilon) || 0;
- if (this._epsilon > 0) {
- this._weldVerticesByEpsilon(this._epsilon);
- }
- return this;
-}
-
-export function _weldVerticesByEpsilon(eps, options = {}) {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeWeldVerticesByEpsilon,
- "Solid._weldVerticesByEpsilon()"
- );
- const rebuildManifold =
- !(options?.rebuildManifold === false);
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- this._cppSolidCore.weldVerticesByEpsilon(eps);
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
- this._dirty = true;
- this._faceIndex = null;
- try { if (this._manifold && typeof this._manifold.delete === 'function') this._manifold.delete(); } catch { }
- this._manifold = null;
- if (rebuildManifold) {
- this._manifoldize();
- }
- return this;
-}
-
-/**
- * Ensures all triangles have consistent winding by making sure
- * shared edges are oriented oppositely between adjacent triangles.
- */
-export function fixTriangleWindingsByAdjacency() {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeManifoldPrep,
- "Solid.fixTriangleWindingsByAdjacency()"
- );
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- const changed = this._cppSolidCore.fixTriangleWindingsByAdjacency();
- if (!changed) return this;
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
- this._dirty = true;
- this._faceIndex = null;
- try { if (this._manifold && typeof this._manifold.delete === 'function') this._manifold.delete(); } catch { }
- this._manifold = null;
- return this;
-}
-
-// Return true if every undirected edge is shared by exactly 2 triangles
-// and their directed usages are opposite.
-export function _isCoherentlyOrientedManifold() {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeManifoldPrep,
- "Solid._isCoherentlyOrientedManifold()"
- );
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- return this._cppSolidCore.isCoherentlyOrientedManifold();
-}
-
-export function invertNormals() {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeManifoldPrep,
- "Solid.invertNormals()"
- );
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- this._cppSolidCore.invertNormals();
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
- this._dirty = true;
- this._faceIndex = null;
- try { if (this._manifold && typeof this._manifold.delete === 'function') this._manifold.delete(); } catch { }
- this._manifold = null;
- this._manifoldize();
- return this;
-}
diff --git a/src/BREP/SolidMethods/meshCleanupStubs.js b/src/BREP/SolidMethods/meshCleanupStubs.js
new file mode 100644
index 00000000..1e477d86
--- /dev/null
+++ b/src/BREP/SolidMethods/meshCleanupStubs.js
@@ -0,0 +1,17 @@
+function removed(name) {
+ throw new Error(`Solid.${name}() has been removed. Use OpenCASCADE-backed modeling operations.`);
+}
+
+export function removeSmallIslands() { return removed("removeSmallIslands"); }
+export function removeSmallInternalIslands() { return removed("removeSmallInternalIslands"); }
+export function removeOppositeSingleEdgeFaces() { return removed("removeOppositeSingleEdgeFaces"); }
+export function removeTinyBoundaryTriangles() { return removed("removeTinyBoundaryTriangles"); }
+export function collapseTinyTriangles() { return removed("collapseTinyTriangles"); }
+export function cleanupTinyFaceIslands() { return removed("cleanupTinyFaceIslands"); }
+export function remesh() { return removed("remesh"); }
+export function splitSelfIntersectingTriangles() { return removed("splitSelfIntersectingTriangles"); }
+export function removeDegenerateTriangles() { return removed("removeDegenerateTriangles"); }
+export function removeInternalTriangles() { return removed("removeInternalTriangles"); }
+export function removeInternalTrianglesByRaycast() { return removed("removeInternalTrianglesByRaycast"); }
+export function removeInternalTrianglesByWinding() { return removed("removeInternalTrianglesByWinding"); }
+export function mergeTinyFaces() { return removed("mergeTinyFaces"); }
diff --git a/src/BREP/SolidMethods/meshQueries.js b/src/BREP/SolidMethods/meshQueries.js
index c2debdd0..df023ea1 100644
--- a/src/BREP/SolidMethods/meshQueries.js
+++ b/src/BREP/SolidMethods/meshQueries.js
@@ -3,14 +3,20 @@
*/
import {
- cppSolidCoreHasNativeTopologyQueries,
- getSyncedCppSolidCore,
- requireCppSolidCoreCapability,
-} from "../CppSolidCore.js";
+ hasOccShape,
+ occBoundaryEdgePolylines,
+ occFaceNormal,
+ occFaces,
+ tessellateOccState,
+} from "../OpenCascadeKernel.js";
-/** Return the underlying MeshGL (fresh from Manifold so it reflects any CSG). */
-export function getMesh() {
- return this._manifoldize().getMesh();
+/** Return a tessellated mesh snapshot of the OCCT-backed solid. */
+export function getMesh(options = {}) {
+ if (hasOccShape(this)) {
+ this._occ.faceNameToID = this._faceNameToID;
+ return tessellateOccState(this._occ, options);
+ }
+ throw new Error("Solid.getMesh() requires an OpenCASCADE-backed solid.");
}
/** Build a cache: faceID -> array of triangle indices. */
@@ -37,8 +43,10 @@ export function _ensureFaceIndex() {
* Returns objects with positions; also includes vertex indices.
*/
export function getFace(name) {
- requireCppSolidCoreCapability(cppSolidCoreHasNativeTopologyQueries, "Solid.getFace");
- return getSyncedCppSolidCore(this).getFace(name);
+ if (hasOccShape(this)) {
+ return (occFaces(this) || []).find((entry) => entry.faceName === name) || { faceName: name, triangles: [] };
+ }
+ throw new Error("Solid.getFace() requires an OpenCASCADE-backed solid.");
}
/**
@@ -46,22 +54,30 @@ export function getFace(name) {
* Returns { faceFound, validNormal, normal: [x, y, z], planarRatio, affectedVertexCount }.
*/
export function getFaceNormal(name) {
- requireCppSolidCoreCapability(cppSolidCoreHasNativeTopologyQueries, "Solid.getFaceNormal");
- return getSyncedCppSolidCore(this).getFaceNormal(name);
+ if (hasOccShape(this)) return occFaceNormal(this, name);
+ throw new Error("Solid.getFaceNormal() requires an OpenCASCADE-backed solid.");
}
/**
* Enumerate faces with their triangles in one pass.
*/
-export function getFaces(includeEmpty = false) {
- requireCppSolidCoreCapability(cppSolidCoreHasNativeTopologyQueries, "Solid.getFaces");
- return getSyncedCppSolidCore(this).getFaces(includeEmpty);
+export function getFaces(includeEmpty = false, options = {}) {
+ if (hasOccShape(this)) {
+ const faces = occFaces(this, options) || [];
+ if (!includeEmpty) return faces.filter((face) => face.triangles?.length);
+ const seen = new Set(faces.map((face) => face.faceName));
+ for (const name of this.getFaceNames()) {
+ if (!seen.has(name)) faces.push({ faceName: name, triangles: [] });
+ }
+ return faces;
+ }
+ throw new Error("Solid.getFaces() requires an OpenCASCADE-backed solid.");
}
/**
* Compute connected polylines for boundary edges between pairs of face labels.
*/
-export function getBoundaryEdgePolylines() {
- requireCppSolidCoreCapability(cppSolidCoreHasNativeTopologyQueries, "Solid.getBoundaryEdgePolylines");
- return getSyncedCppSolidCore(this).getBoundaryEdgePolylines();
+export function getBoundaryEdgePolylines(options = {}) {
+ if (hasOccShape(this)) return occBoundaryEdgePolylines(this, options);
+ throw new Error("Solid.getBoundaryEdgePolylines() requires an OpenCASCADE-backed solid.");
}
diff --git a/src/BREP/SolidMethods/metadata.js b/src/BREP/SolidMethods/metadata.js
index 8b71c1dc..cfc8c7b3 100644
--- a/src/BREP/SolidMethods/metadata.js
+++ b/src/BREP/SolidMethods/metadata.js
@@ -2,65 +2,85 @@
* Face and edge metadata helpers.
*/
-import {
- cppSolidCoreHasAuthoringBridge,
- getSyncedCppSolidCore,
- requireCppSolidCoreCapability,
- syncSolidAuthoringStateFromCpp,
-} from "../CppSolidCore.js";
+import { hasOccShape } from "../OpenCascadeKernel.js";
/** Set metadata for a face (e.g., radius for cylindrical faces). */
export function setFaceMetadata(faceName, metadata) {
if (!metadata || typeof metadata !== 'object') return this;
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.setFaceMetadata");
- const core = getSyncedCppSolidCore(this);
- const existing = core.getFaceMetadata(faceName);
- const base = existing && typeof existing === 'object' ? existing : {};
- core.setFaceMetadata(faceName, { ...base, ...metadata });
- syncSolidAuthoringStateFromCpp(this, core);
+ if (hasOccShape(this)) {
+ const existing = this._faceMetadata instanceof Map ? (this._faceMetadata.get(faceName) || {}) : {};
+ if (!(this._faceMetadata instanceof Map)) this._faceMetadata = new Map();
+ this._faceMetadata.set(faceName, { ...existing, ...metadata });
+ if (this._occ) {
+ if (!(this._occ.faceMetadata instanceof Map)) this._occ.faceMetadata = new Map();
+ this._occ.faceMetadata.set(faceName, this._faceMetadata.get(faceName));
+ }
+ this._faceMetadataVersion = (this._faceMetadataVersion || 0) + 1;
+ return this;
+ }
+ const existing = this._faceMetadata instanceof Map ? (this._faceMetadata.get(faceName) || {}) : {};
+ if (!(this._faceMetadata instanceof Map)) this._faceMetadata = new Map();
+ this._faceMetadata.set(faceName, { ...existing, ...metadata });
+ this._faceMetadataVersion = (this._faceMetadataVersion || 0) + 1;
return this;
}
/** Get metadata for a face. */
export function getFaceMetadata(faceName) {
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.getFaceMetadata");
if (this._faceMetadata instanceof Map) {
return this._faceMetadata.get(faceName) || {};
}
- try { return this._cppSolidCore?.getFaceMetadata(faceName) || {}; } catch { return {}; }
+ return {};
}
/** Convenience: list all face names present in this solid. */
export function getFaceNames() {
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.getFaceNames");
if (this._faceNameToID instanceof Map && this._faceNameToID.size > 0) {
return Array.from(this._faceNameToID.keys());
}
if (this._idToFaceName instanceof Map && this._idToFaceName.size > 0) {
return Array.from(this._idToFaceName.values());
}
- try { return this._cppSolidCore?.getFaceNames() || []; } catch { return []; }
+ if (hasOccShape(this)) return Array.from(this._occ?.faceNames || []);
+ return [];
}
/** Rename a face; if newName exists, merge triangles/metadata into it. */
export function renameFace(oldName, newName) {
if (!oldName || !newName || oldName === newName) return this;
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.renameFace");
- const core = getSyncedCppSolidCore(this);
- const hadOldMetadata = this._faceMetadata instanceof Map && this._faceMetadata.has(oldName);
- const oldMetadata = hadOldMetadata ? (core.getFaceMetadata(oldName) || {}) : null;
- const existingMetadata = (this._faceMetadata instanceof Map && this._faceMetadata.has(newName))
- ? (core.getFaceMetadata(newName) || {})
- : null;
- const renamed = core.renameFace(oldName, newName);
- if (!renamed) return this;
- syncSolidAuthoringStateFromCpp(this, core);
- if (hadOldMetadata) {
- core.setFaceMetadata(newName, {
- ...(existingMetadata && typeof existingMetadata === 'object' ? existingMetadata : {}),
- ...(oldMetadata && typeof oldMetadata === 'object' ? oldMetadata : {}),
- });
- syncSolidAuthoringStateFromCpp(this, core);
+ if (hasOccShape(this)) {
+ const id = this._faceNameToID?.get?.(oldName);
+ if (id == null) return this;
+ this._faceNameToID.delete(oldName);
+ this._faceNameToID.set(newName, id);
+ this._idToFaceName.set(id, newName);
+ if (this._occ) {
+ this._occ.faceNames = Array.from(this._occ.faceNames || []).map((name) => name === oldName ? newName : name);
+ if (Array.isArray(this._occ.faceNameByIndex)) {
+ this._occ.faceNameByIndex = this._occ.faceNameByIndex.map((name) => name === oldName ? newName : name);
+ }
+ this._occ.meshCache = null;
+ }
+ if (this._faceMetadata instanceof Map && this._faceMetadata.has(oldName)) {
+ const oldMetadata = this._faceMetadata.get(oldName);
+ const existing = this._faceMetadata.get(newName) || {};
+ this._faceMetadata.delete(oldName);
+ this._faceMetadata.set(newName, { ...existing, ...oldMetadata });
+ }
+ this._faceMetadataVersion = (this._faceMetadataVersion || 0) + 1;
+ this._faceIndex = null;
+ return this;
+ }
+ const id = this._faceNameToID?.get?.(oldName);
+ if (id == null) return this;
+ this._faceNameToID.delete(oldName);
+ this._faceNameToID.set(newName, id);
+ this._idToFaceName.set(id, newName);
+ if (this._faceMetadata instanceof Map && this._faceMetadata.has(oldName)) {
+ const oldMetadata = this._faceMetadata.get(oldName);
+ const existing = this._faceMetadata.get(newName) || {};
+ this._faceMetadata.delete(oldName);
+ this._faceMetadata.set(newName, { ...existing, ...oldMetadata });
}
this._dirty = true;
this._faceIndex = null;
@@ -70,22 +90,30 @@ export function renameFace(oldName, newName) {
/** Set metadata for an edge. */
export function setEdgeMetadata(edgeName, metadata) {
if (!metadata || typeof metadata !== 'object') return this;
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.setEdgeMetadata");
- const core = getSyncedCppSolidCore(this);
- const existing = core.getEdgeMetadata(edgeName);
- const base = existing && typeof existing === 'object' ? existing : {};
- core.setEdgeMetadata(edgeName, { ...base, ...metadata });
- syncSolidAuthoringStateFromCpp(this, core);
+ if (hasOccShape(this)) {
+ const existing = this._edgeMetadata instanceof Map ? (this._edgeMetadata.get(edgeName) || {}) : {};
+ if (!(this._edgeMetadata instanceof Map)) this._edgeMetadata = new Map();
+ this._edgeMetadata.set(edgeName, { ...existing, ...metadata });
+ if (this._occ) {
+ if (!(this._occ.edgeMetadata instanceof Map)) this._occ.edgeMetadata = new Map();
+ this._occ.edgeMetadata.set(edgeName, this._edgeMetadata.get(edgeName));
+ }
+ this._edgeMetadataVersion = (this._edgeMetadataVersion || 0) + 1;
+ return this;
+ }
+ const existing = this._edgeMetadata instanceof Map ? (this._edgeMetadata.get(edgeName) || {}) : {};
+ if (!(this._edgeMetadata instanceof Map)) this._edgeMetadata = new Map();
+ this._edgeMetadata.set(edgeName, { ...existing, ...metadata });
+ this._edgeMetadataVersion = (this._edgeMetadataVersion || 0) + 1;
return this;
}
/** Get metadata for an edge. */
export function getEdgeMetadata(edgeName) {
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.getEdgeMetadata");
if (this._edgeMetadata instanceof Map) {
return this._edgeMetadata.get(edgeName) || null;
}
- try { return this._cppSolidCore?.getEdgeMetadata(edgeName) || null; } catch { return null; }
+ return null;
}
/** Combine face metadata maps across two solids. */
diff --git a/src/BREP/SolidMethods/metrics.js b/src/BREP/SolidMethods/metrics.js
index 977c043c..0b43d812 100644
--- a/src/BREP/SolidMethods/metrics.js
+++ b/src/BREP/SolidMethods/metrics.js
@@ -2,8 +2,10 @@
* Geometric measurements.
*/
import { computeTriangleArea } from '../triangleUtils.js';
+import { hasOccShape, occSurfaceArea, occVolume } from '../OpenCascadeKernel.js';
export function volume() {
+ if (hasOccShape(this)) return occVolume(this);
const mesh = this.getMesh();
try {
const vp = mesh.vertProperties;
@@ -23,6 +25,7 @@ export function volume() {
}
export function surfaceArea() {
+ if (hasOccShape(this)) return occSurfaceArea(this);
const mesh = this.getMesh();
try {
const vp = mesh.vertProperties;
diff --git a/src/BREP/SolidMethods/offsetShell.js b/src/BREP/SolidMethods/offsetShell.js
index 5eeba7c0..0fcf7401 100644
--- a/src/BREP/SolidMethods/offsetShell.js
+++ b/src/BREP/SolidMethods/offsetShell.js
@@ -1,3 +1,5 @@
+import { hasOccShape, offsetShellOccSolid, setOccState } from "../OpenCascadeKernel.js";
+
function sanitizeToken(value, fallback = 'FACE') {
const raw = value == null ? '' : String(value);
const trimmed = raw.trim();
@@ -42,10 +44,43 @@ export function offsetShell(faces, distance, options = {}) {
.filter(Boolean)
);
- const magnitude = Math.abs(Number(distance));
- if (!Number.isFinite(magnitude) || magnitude === 0) return null;
+ const signedDistance = Number(distance);
+ if (!Number.isFinite(signedDistance) || signedDistance === 0) return null;
+ const magnitude = Math.abs(signedDistance);
const thickenDistance = -magnitude;
+
+ if (hasOccShape(this)) {
+ const occState = offsetShellOccSolid(this, Array.from(excludedFaceNames), {
+ distance: signedDistance,
+ featureID: featureId,
+ });
+ if (!occState) return null;
+ const SolidClass = this?.constructor?.BaseSolid || this?.constructor || null;
+ const result = new SolidClass();
+ setOccState(result, occState);
+ try { result.name = newSolidName; } catch { /* ignore */ }
+ const buildMethod = excludedFaceNames.size ? "occ_make_thick_solid" : "occ_make_offset_shape";
+ result.__offsetMethod = buildMethod;
+ result.__offsetDiagnostics = {
+ buildMethod,
+ excludedFaceCount: excludedFaceNames.size,
+ removedFaceNames: Array.from(excludedFaceNames),
+ distance: signedDistance,
+ };
+ try {
+ result.userData = {
+ ...(result.userData || {}),
+ offsetShell: {
+ buildMethod,
+ excludedFaceNames: Array.from(excludedFaceNames),
+ distance: signedDistance,
+ },
+ };
+ } catch { /* ignore */ }
+ return result;
+ }
+
let faceObjects = [];
try {
faceObjects = Array.isArray(this.faces) ? this.faces.slice() : [];
diff --git a/src/BREP/SolidMethods/transforms.js b/src/BREP/SolidMethods/transforms.js
index c604ca36..287f7464 100644
--- a/src/BREP/SolidMethods/transforms.js
+++ b/src/BREP/SolidMethods/transforms.js
@@ -1,15 +1,6 @@
import { THREE } from "../SolidShared.js";
import { composeTrsMatrixDeg } from "../../utils/xformMath.js";
-import {
- CppSolidCore,
- cppSolidCoreHasAuthoringBridge,
- cppSolidCoreHasNativeMetadataTransform,
- cppSolidCoreHasNativePushFace,
- getSolidAuthoringStateSnapshot,
- requireCppSolidCoreCapability,
- syncSolidAuthoringStateFromCpp,
- syncSolidAuthoringStateToCpp,
-} from "../CppSolidCore.js";
+import { hasOccShape, setOccState, transformOccState } from "../OpenCascadeKernel.js";
/**
* Geometry transforms applied directly to authored data.
@@ -20,23 +11,13 @@ import {
* Does not modify the Object3D transform; marks manifold dirty for rebuild.
*/
export function bakeTransform(matrix) {
- try {
- if (!matrix || typeof matrix.elements === 'undefined') return this;
- if (!Array.isArray(this._vertProperties) || this._vertProperties.length === 0) return this;
+ if (!matrix || typeof matrix.elements === 'undefined') return this;
+ if (hasOccShape(this)) {
const m = (matrix && matrix.isMatrix4) ? matrix : new THREE.Matrix4().fromArray(matrix.elements || matrix);
- requireCppSolidCoreCapability(cppSolidCoreHasAuthoringBridge, "Solid.bakeTransform()");
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- this._cppSolidCore.bakeTransform(m);
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
- this._dirty = true;
- this._faceIndex = null;
- try {
- if (this._manifold && typeof this._manifold.delete === 'function') this._manifold.delete();
- } catch { /* ignore */ }
- this._manifold = null;
- } catch (_) { /* ignore */ }
- return this;
+ setOccState(this, transformOccState(this._occ, m));
+ return this;
+ }
+ throw new Error("Solid.bakeTransform() requires an OpenCASCADE-backed solid.");
}
/**
@@ -53,116 +34,30 @@ export function bakeTRS(trs) {
* Return a mirrored copy of this solid across a plane defined by a point and a normal.
*/
export function mirrorAcrossPlane(point, normal) {
- const Solid = this.constructor;
const P0 = (point instanceof THREE.Vector3)
? point.clone()
: new THREE.Vector3(point[0], point[1], point[2]);
const n = (normal instanceof THREE.Vector3)
? normal.clone().normalize()
: new THREE.Vector3(normal[0], normal[1], normal[2]).normalize();
+ const nx = n.x;
+ const ny = n.y;
+ const nz = n.z;
+ const planeDot = P0.dot(n);
+ const reflection = new THREE.Matrix4().set(
+ 1 - (2 * nx * nx), -2 * nx * ny, -2 * nx * nz, 2 * planeDot * nx,
+ -2 * ny * nx, 1 - (2 * ny * ny), -2 * ny * nz, 2 * planeDot * ny,
+ -2 * nz * nx, -2 * nz * ny, 1 - (2 * nz * nz), 2 * planeDot * nz,
+ 0, 0, 0, 1,
+ );
- const sourceSnapshot = getSolidAuthoringStateSnapshot(this);
- const mesh = this.getMesh();
- try {
- const vp = mesh.vertProperties; // Float32Array
- const tv = mesh.triVerts; // Uint32Array
- const faceIDs = mesh.faceID && mesh.faceID.length ? Array.from(mesh.faceID) : [];
-
- const mirrored = new Solid();
- mirrored._numProp = mesh.numProp || 3;
-
- // Reflect vertices across plane
- const outVP = new Array(vp.length);
- const X = new THREE.Vector3();
- for (let i = 0; i < vp.length; i += 3) {
- X.set(vp[i + 0], vp[i + 1], vp[i + 2]);
- const d = X.clone().sub(P0);
- const t = 2 * d.dot(n);
- const Xp = X.sub(n.clone().multiplyScalar(t));
- outVP[i + 0] = Xp.x;
- outVP[i + 1] = Xp.y;
- outVP[i + 2] = Xp.z;
- }
- mirrored._vertProperties = outVP;
-
- // Copy triangles and face IDs
- mirrored._triVerts = Array.from(tv);
- mirrored._triIDs = faceIDs.length ? faceIDs : new Array((tv.length / 3) | 0).fill(0);
-
- // Restore face name maps and metadata from the native-backed authoring snapshot.
- try {
- mirrored._idToFaceName = new Map(sourceSnapshot?.idToFaceName || []);
- mirrored._faceNameToID = new Map(sourceSnapshot?.faceNameToID || []);
- mirrored._faceMetadata = new Map(Array.from(sourceSnapshot?.faceMetadataJson || [], ([name, raw]) => [name, JSON.parse(raw || "{}")]));
- mirrored._edgeMetadata = new Map(Array.from(sourceSnapshot?.edgeMetadataJson || [], ([name, raw]) => [name, JSON.parse(raw || "{}")]));
- } catch (_) { }
-
- // Mirror auxiliary edges (e.g., centerlines) across the same plane.
- try {
- const aux = Array.isArray(this._auxEdges) ? this._auxEdges : [];
- const X = new THREE.Vector3();
- const d = new THREE.Vector3();
- const nScaled = new THREE.Vector3();
- mirrored._auxEdges = aux.map(edge => {
- const pts = [];
- if (Array.isArray(edge?.points)) {
- for (const p of edge.points) {
- if (!Array.isArray(p) || p.length !== 3) continue;
- X.set(p[0], p[1], p[2]);
- d.subVectors(X, P0);
- nScaled.copy(n).multiplyScalar(2 * d.dot(n));
- X.sub(nScaled);
- pts.push([X.x, X.y, X.z]);
- }
- }
- return {
- name: edge?.name,
- closedLoop: !!edge?.closedLoop,
- polylineWorld: !!edge?.polylineWorld,
- materialKey: edge?.materialKey,
- centerline: !!edge?.centerline,
- points: pts,
- };
- }).filter(e => Array.isArray(e.points) && e.points.length);
- } catch (_) { mirrored._auxEdges = []; }
-
- // Rebuild vertex key map for exact-key lookup consistency
- mirrored._vertKeyToIndex = new Map();
- for (let i = 0; i < mirrored._vertProperties.length; i += 3) {
- const x = mirrored._vertProperties[i];
- const y = mirrored._vertProperties[i + 1];
- const z = mirrored._vertProperties[i + 2];
- mirrored._vertKeyToIndex.set(`${x},${y},${z}`, (i / 3) | 0);
- }
-
- mirrored._dirty = true; // manifold must rebuild on demand
- mirrored._faceIndex = null;
- mirrored._manifold = null;
- try {
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativeMetadataTransform,
- "Solid.mirrorAcrossPlane()",
- );
- const nx = n.x;
- const ny = n.y;
- const nz = n.z;
- const planeDot = P0.dot(n);
- const reflection = new THREE.Matrix4().set(
- 1 - (2 * nx * nx), -2 * nx * ny, -2 * nx * nz, 2 * planeDot * nx,
- -2 * ny * nx, 1 - (2 * ny * ny), -2 * ny * nz, 2 * planeDot * ny,
- -2 * nz * nx, -2 * nz * ny, 1 - (2 * nz * nz), 2 * planeDot * nz,
- 0, 0, 0, 1,
- );
- mirrored._cppSolidCore = mirrored._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(mirrored, mirrored._cppSolidCore);
- mirrored._cppSolidCore.transformMetadata(reflection);
- syncSolidAuthoringStateFromCpp(mirrored, mirrored._cppSolidCore);
- mirrored._cppSolidCore.dispose();
- mirrored._cppSolidCore = null;
- mirrored._cppSolidCoreSyncStamp = null;
- } catch { /* ignore metadata reflection errors */ }
+ if (hasOccShape(this)) {
+ const mirrored = this.clone();
+ mirrored.bakeTransform(reflection);
return mirrored;
- } finally { try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { } }
+ }
+
+ throw new Error("Solid.mirrorAcrossPlane() requires an OpenCASCADE-backed solid.");
}
/**
@@ -182,36 +77,7 @@ export function pushFace(faceName, distance = 0.001, options = {}) {
const warnMissing = options?.warnMissing !== false;
const warnInvalidNormal = options?.warnInvalidNormal !== false;
- // Make sure triangle windings are coherent so the averaged normal points outward.
- try { this._manifoldize(); } catch { /* best effort; fall back to existing winding */ }
-
- const faceID = this._faceNameToID.get(faceName);
- if (faceID === undefined) {
- if (warnMissing) console.warn(`pushFace: Face "${faceName}" not found`);
- return this;
- }
-
- requireCppSolidCoreCapability(
- cppSolidCoreHasAuthoringBridge && cppSolidCoreHasNativePushFace,
- "Solid.pushFace()"
- );
- this._cppSolidCore = this._cppSolidCore || new CppSolidCore();
- syncSolidAuthoringStateToCpp(this, this._cppSolidCore);
- const result = this._cppSolidCore.pushFace(faceName, dist) || {};
- if (!result.faceFound) {
- if (warnMissing) console.warn(`pushFace: Face "${faceName}" not found`);
- return this;
- }
- if (!result.moved) {
- if (result.invalidNormal && warnInvalidNormal) {
- console.warn(`pushFace: Invalid normal for face "${faceName}"`);
- }
- return this;
- }
- syncSolidAuthoringStateFromCpp(this, this._cppSolidCore);
- this._dirty = true;
- this._faceIndex = null;
- try { if (this._manifold && typeof this._manifold.delete === 'function') this._manifold.delete(); } catch { }
- this._manifold = null;
- return this;
+ void warnMissing;
+ void warnInvalidNormal;
+ throw new Error("Solid.pushFace() requires an OpenCASCADE-backed implementation.");
}
diff --git a/src/BREP/SolidMethods/visualize.js b/src/BREP/SolidMethods/visualize.js
index a2e07b0f..250db149 100644
--- a/src/BREP/SolidMethods/visualize.js
+++ b/src/BREP/SolidMethods/visualize.js
@@ -1,4 +1,11 @@
import { SelectionState } from "../../UI/SelectionState.js";
+import {
+ hasOccShape,
+} from "../OpenCascadeKernel.js";
+import {
+ getOccVisualizationCurveSampleCount,
+ getOccVisualizationTriangulationOptions,
+} from "../occTriangulationSettings.js";
import {
CADmaterials,
debugMode,
@@ -9,6 +16,123 @@ import {
Vertex
} from "../SolidShared.js";
+function distance3(a, b) {
+ return Math.hypot(
+ Number(b?.[0] || 0) - Number(a?.[0] || 0),
+ Number(b?.[1] || 0) - Number(a?.[1] || 0),
+ Number(b?.[2] || 0) - Number(a?.[2] || 0),
+ );
+}
+
+function interpolatePoint(a, b, t) {
+ return [
+ Number(a?.[0] || 0) + (Number(b?.[0] || 0) - Number(a?.[0] || 0)) * t,
+ Number(a?.[1] || 0) + (Number(b?.[1] || 0) - Number(a?.[1] || 0)) * t,
+ Number(a?.[2] || 0) + (Number(b?.[2] || 0) - Number(a?.[2] || 0)) * t,
+ ];
+}
+
+function resamplePolylineForVisualization(points, sampleCount, closedLoop = false) {
+ const src = Array.isArray(points) ? points.filter(p => Array.isArray(p) && p.length === 3) : [];
+ if (src.length < 2) return src;
+ const count = Math.max(2, Math.floor(Number(sampleCount) || src.length));
+ const path = src.slice();
+ if (closedLoop && distance3(path[0], path[path.length - 1]) > 1e-12) path.push(path[0]);
+ const cumulative = [0];
+ for (let i = 1; i < path.length; i += 1) cumulative.push(cumulative[i - 1] + distance3(path[i - 1], path[i]));
+ const total = cumulative[cumulative.length - 1];
+ if (!(total > 1e-12)) return src;
+
+ const out = [];
+ const wanted = closedLoop ? count : Math.max(2, count);
+ const denom = closedLoop ? wanted : Math.max(1, wanted - 1);
+ let seg = 1;
+ for (let i = 0; i < wanted; i += 1) {
+ const target = total * (i / denom);
+ while (seg < cumulative.length - 1 && cumulative[seg] < target) seg += 1;
+ const aLen = cumulative[seg - 1];
+ const bLen = cumulative[seg];
+ const t = bLen > aLen ? (target - aLen) / (bLen - aLen) : 0;
+ out.push(interpolatePoint(path[seg - 1], path[seg], t));
+ }
+ if (closedLoop && out.length && distance3(out[0], out[out.length - 1]) > 1e-12) out.push(out[0]);
+ return out;
+}
+
+function circularizeClosedPolylineForVisualization(points, sampleCount) {
+ const src = Array.isArray(points) ? points.filter(p => Array.isArray(p) && p.length === 3) : [];
+ if (src.length < 4) return null;
+ const open = distance3(src[0], src[src.length - 1]) <= 1e-12 ? src.slice(0, -1) : src.slice();
+ if (open.length < 4) return null;
+
+ const center = open.reduce((acc, p) => {
+ acc[0] += Number(p[0] || 0);
+ acc[1] += Number(p[1] || 0);
+ acc[2] += Number(p[2] || 0);
+ return acc;
+ }, [0, 0, 0]).map(v => v / open.length);
+
+ const radii = open.map(p => distance3(p, center));
+ const radius = radii.reduce((sum, v) => sum + v, 0) / radii.length;
+ if (!(radius > 1e-9)) return null;
+ const maxRadialError = radii.reduce((max, v) => Math.max(max, Math.abs(v - radius)), 0);
+ if (maxRadialError > Math.max(1e-6, radius * 0.03)) return null;
+
+ const normal = [0, 0, 0];
+ for (let i = 0; i < open.length; i += 1) {
+ const a = open[i];
+ const b = open[(i + 1) % open.length];
+ normal[0] += (a[1] - b[1]) * (a[2] + b[2]);
+ normal[1] += (a[2] - b[2]) * (a[0] + b[0]);
+ normal[2] += (a[0] - b[0]) * (a[1] + b[1]);
+ }
+ const nLen = Math.hypot(normal[0], normal[1], normal[2]);
+ if (!(nLen > 1e-12)) return null;
+ normal[0] /= nLen; normal[1] /= nLen; normal[2] /= nLen;
+
+ let u = [
+ Number(open[0][0] || 0) - center[0],
+ Number(open[0][1] || 0) - center[1],
+ Number(open[0][2] || 0) - center[2],
+ ];
+ const uLen = Math.hypot(u[0], u[1], u[2]);
+ if (!(uLen > 1e-12)) return null;
+ u = u.map(v => v / uLen);
+ const v = [
+ normal[1] * u[2] - normal[2] * u[1],
+ normal[2] * u[0] - normal[0] * u[2],
+ normal[0] * u[1] - normal[1] * u[0],
+ ];
+
+ const count = Math.max(8, Math.floor(Number(sampleCount) || src.length));
+ const out = [];
+ for (let i = 0; i < count; i += 1) {
+ const a = (i / count) * Math.PI * 2;
+ const c = Math.cos(a);
+ const s = Math.sin(a);
+ out.push([
+ center[0] + radius * (u[0] * c + v[0] * s),
+ center[1] + radius * (u[1] * c + v[1] * s),
+ center[2] + radius * (u[2] * c + v[2] * s),
+ ]);
+ }
+ out.push(out[0]);
+ return out;
+}
+
+function prepareAuxPolylineForVisualization(points, aux) {
+ const closedLoop = !!aux?.closedLoop;
+ const sampleCount = getOccVisualizationCurveSampleCount(closedLoop ? 65 : Math.max(2, points?.length || 2));
+ if (closedLoop) {
+ const circular = circularizeClosedPolylineForVisualization(points, sampleCount);
+ if (circular) return circular;
+ }
+ if (closedLoop || (Array.isArray(points) && points.length > 2)) {
+ return resamplePolylineForVisualization(points, sampleCount, closedLoop);
+ }
+ return points;
+}
+
@@ -53,10 +177,11 @@ export function visualize(options = {}) {
}
const { showEdges = true, forceAuthoring = false, authoringOnly = false } = options;
+ const occVisualizationOptions = hasOccShape(this) ? getOccVisualizationTriangulationOptions() : {};
let faces; let usedFallback = false;
if (!forceAuthoring && !authoringOnly) {
try {
- faces = this.getFaces(false);
+ faces = this.getFaces(false, occVisualizationOptions);
} catch (err) {
console.warn('[Solid.visualize] getFaces failed, falling back to raw arrays:', err?.message || err);
usedFallback = true;
@@ -89,59 +214,87 @@ export function visualize(options = {}) {
for (const [faceName, triangles] of nameToTris.entries()) faces.push({ faceName, triangles });
}
- // Build Face meshes and index by name
- const faceMap = new Map();
- for (const { faceName, triangles } of faces) {
- if (!triangles.length) continue;
- const positions = new Float32Array(triangles.length * 9);
- let w = 0;
+ const buildGeometryForTriangles = (triangles) => {
+ const positions = [];
+ const indices = [];
+ const sourceToLocal = new Map();
+ const addVertex = (sourceIndex, point) => {
+ if (Number.isFinite(sourceIndex)) {
+ const existing = sourceToLocal.get(sourceIndex);
+ if (existing != null) return existing;
+ }
+ const local = (positions.length / 3) | 0;
+ positions.push(point[0], point[1], point[2]);
+ if (Number.isFinite(sourceIndex)) sourceToLocal.set(sourceIndex, local);
+ return local;
+ };
+
for (let t = 0; t < triangles.length; t++) {
const tri = triangles[t];
- const p0 = tri.p1, p1 = tri.p2, p2 = tri.p3;
+ const points = [tri.p1, tri.p2, tri.p3];
+ const triIndices = Array.isArray(tri.indices) && tri.indices.length >= 3
+ ? tri.indices
+ : [NaN, NaN, NaN];
if (debugMode) {
- // Validate triangle coordinates before adding to geometry
- const coords = [p0[0], p0[1], p0[2], p1[0], p1[1], p1[2], p2[0], p2[1], p2[2]];
+ const coords = [
+ points[0][0], points[0][1], points[0][2],
+ points[1][0], points[1][1], points[1][2],
+ points[2][0], points[2][1], points[2][2],
+ ];
const hasInvalidCoords = coords.some(coord => !isFinite(coord));
if (hasInvalidCoords) {
- console.error(`Invalid triangle coordinates in face ${faceName}, triangle ${t}:`);
- console.error('p0:', p0, 'p1:', p1, 'p2:', p2);
+ console.error(`Invalid triangle coordinates in face ${tri.faceName || ''}, triangle ${t}:`);
+ console.error('p0:', points[0], 'p1:', points[1], 'p2:', points[2]);
console.error('Triangle data:', tri);
- // Skip this triangle by not incrementing w and not setting positions
continue;
}
- // Degenerate triangle check (area ~ 0) and log its points
- // Compute squared area via cross product of edges (robust to uniform scale)
try {
- const ux = p1[0] - p0[0], uy = p1[1] - p0[1], uz = p1[2] - p0[2];
- const vx = p2[0] - p0[0], vy = p2[1] - p0[1], vz = p2[2] - p0[2];
+ const ux = points[1][0] - points[0][0], uy = points[1][1] - points[0][1], uz = points[1][2] - points[0][2];
+ const vx = points[2][0] - points[0][0], vy = points[2][1] - points[0][1], vz = points[2][2] - points[0][2];
const nx = uy * vz - uz * vy;
const ny = uz * vx - ux * vz;
const nz = ux * vy - uy * vx;
const area2 = nx * nx + ny * ny + nz * nz;
- // Use same threshold as viewer diagnostics
if (area2 <= 1e-30) {
- console.warn(`[Solid.visualize] Degenerate triangle in face ${faceName} @ index ${t}`);
- console.warn('points:', { p0, p1, p2 });
+ console.warn(`[Solid.visualize] Degenerate triangle in face ${tri.faceName || ''} @ index ${t}`);
+ console.warn('points:', { p0: points[0], p1: points[1], p2: points[2] });
}
} catch { /* best-effort logging only */ }
}
- positions[w++] = p0[0]; positions[w++] = p0[1]; positions[w++] = p0[2];
- positions[w++] = p1[0]; positions[w++] = p1[1]; positions[w++] = p1[2];
- positions[w++] = p2[0]; positions[w++] = p2[1]; positions[w++] = p2[2];
+ indices.push(
+ addVertex(Number(triIndices[0]), points[0]),
+ addVertex(Number(triIndices[1]), points[1]),
+ addVertex(Number(triIndices[2]), points[2]),
+ );
}
const geom = new THREE.BufferGeometry();
- geom.setAttribute('position', new THREE.BufferAttribute(positions, 3));
+ geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(positions), 3));
+ geom.setIndex(indices);
geom.computeVertexNormals();
geom.computeBoundingBox();
geom.computeBoundingSphere();
+ return geom;
+ };
+
+ // Build Face meshes and index by name
+ const faceMap = new Map();
+ for (const { faceName, triangles } of faces) {
+ if (!triangles.length) continue;
+ const geom = buildGeometryForTriangles(triangles.map(tri => ({ ...tri, faceName })));
const faceObj = new Face(geom);
faceObj.name = faceName;
+ if (this._kernel === 'opencascade' && faceObj.material?.clone) {
+ const material = faceObj.material.clone();
+ try { material.side = THREE.DoubleSide; } catch { /* ignore */ }
+ try { material.flatShading = false; material.needsUpdate = true; } catch { /* ignore */ }
+ faceObj.material = material;
+ }
faceObj.userData.faceName = faceName;
faceObj.userData.__defaultMaterial = faceObj.material;
try {
@@ -160,7 +313,7 @@ export function visualize(options = {}) {
if (showEdges) {
if (!usedFallback) {
let polylines = [];
- try { polylines = this.getBoundaryEdgePolylines() || []; } catch { polylines = []; }
+ try { polylines = this.getBoundaryEdgePolylines(occVisualizationOptions) || []; } catch { polylines = []; }
// Safety net: if manifold-based extraction yielded no edges (e.g., faceID missing),
// fall back to authoring-based boundary extraction so we still visualize edges.
if (!Array.isArray(polylines) || polylines.length === 0) {
@@ -193,9 +346,9 @@ export function visualize(options = {}) {
const fa = faceMap.get(e.faceA);
const fb = faceMap.get(e.faceB);
if (fa) fa.edges.push(edgeObj);
- if (fb) fb.edges.push(edgeObj);
+ if (fb && fb !== fa) fb.edges.push(edgeObj);
if (fa) edgeObj.faces.push(fa);
- if (fb) edgeObj.faces.push(fb);
+ if (fb && fb !== fa) edgeObj.faces.push(fb);
this.add(edgeObj);
}
}
@@ -296,8 +449,8 @@ export function visualize(options = {}) {
annotateEdgeFromMetadata(edgeObj, this);
edgeObj.parentSolid = this;
const fa = faceMap.get(nameA); const fb = faceMap.get(nameB);
- if (fa) fa.edges.push(edgeObj); if (fb) fb.edges.push(edgeObj);
- if (fa) edgeObj.faces.push(fa); if (fb) edgeObj.faces.push(fb);
+ if (fa) fa.edges.push(edgeObj); if (fb && fb !== fa) fb.edges.push(edgeObj);
+ if (fa) edgeObj.faces.push(fa); if (fb && fb !== fa) edgeObj.faces.push(fb);
try { edgeObj.computeLineDistances(); } catch { }
this.add(edgeObj);
}
@@ -315,7 +468,9 @@ export function visualize(options = {}) {
try {
if (Array.isArray(this._auxEdges) && this._auxEdges.length) {
for (const aux of this._auxEdges) {
- const pts = Array.isArray(aux?.points) ? aux.points.filter(p => Array.isArray(p) && p.length === 3) : [];
+ const sourcePts = Array.isArray(aux?.points) ? aux.points.filter(p => Array.isArray(p) && p.length === 3) : [];
+ if (sourcePts.length < 2) continue;
+ const pts = prepareAuxPolylineForVisualization(sourcePts, aux);
if (pts.length < 2) continue;
const flat = [];
for (const p of pts) { flat.push(p[0], p[1], p[2]); }
@@ -365,9 +520,9 @@ export function visualize(options = {}) {
const fa = aux?.faceA ? faceMap.get(aux.faceA) : null;
const fb = aux?.faceB ? faceMap.get(aux.faceB) : null;
if (fa) fa.edges.push(edgeObj);
- if (fb) fb.edges.push(edgeObj);
+ if (fb && fb !== fa) fb.edges.push(edgeObj);
if (fa) edgeObj.faces.push(fa);
- if (fb) edgeObj.faces.push(fb);
+ if (fb && fb !== fa) edgeObj.faces.push(fb);
try {
const fallbackKey = behavesLikeBoundary ? 'SECTION' : 'OVERLAY';
const requestedKey = (aux?.materialKey || fallbackKey).toUpperCase();
@@ -440,7 +595,7 @@ export function visualize(options = {}) {
const addEP = (p) => {
if (!p || p.length !== 3) return;
- const k = `${p[0]},${p[1]},${p[2]}`;
+ const k = `${Math.round(Number(p[0] || 0) * 1e8)},${Math.round(Number(p[1] || 0) * 1e8)},${Math.round(Number(p[2] || 0) * 1e8)}`;
if (!endpoints.has(k)) endpoints.set(k, p);
// Track which edges meet at this vertex position
diff --git a/src/BREP/SolidShared.js b/src/BREP/SolidShared.js
index d25954c0..37c8ee1b 100644
--- a/src/BREP/SolidShared.js
+++ b/src/BREP/SolidShared.js
@@ -1,5 +1,3 @@
-import { Manifold, ManifoldMesh, CrossSection } from "./setupManifold.js";
-
import * as THREE from "three";
import { CADmaterials } from "../UI/CADmaterials.js";
import { Line2, LineGeometry } from "three/examples/jsm/Addons.js";
@@ -8,16 +6,11 @@ import { Edge } from "./Edge.js";
import { Vertex } from "./Vertex.js";
import { Face } from "./Face.js";
-// Use named exports from setupManifold.js
-
const debugMode = false;
export { Edge, Vertex, Face };
export {
- Manifold,
- ManifoldMesh,
- CrossSection,
THREE,
CADmaterials,
Line2,
diff --git a/src/BREP/Sweep.js b/src/BREP/Sweep.js
index ec5cf27e..1082c51d 100644
--- a/src/BREP/Sweep.js
+++ b/src/BREP/Sweep.js
@@ -1,14 +1,7 @@
import * as THREE from 'three';
import { Solid } from './BetterSolid.js';
-import { applySolidAuthoringStateSnapshot } from './CppSolidCore.js';
-import { computeBoundsFromVertices } from './boundsUtils.js';
import { getEdgePolylineWorld } from './edgePolylineUtils.js';
-import { manifold } from './setupManifold.js';
-
-function requireNativeSweepBuilder() {
- if (typeof manifold?.buildSweepAuthoringState === 'function') return;
- throw new Error('Sweep generation requires the custom local manifold build with native sweep support.');
-}
+import { makeExtrusion, makePathSweep, setOccState } from './OpenCascadeKernel.js';
export function computeBoundaryLoopsFromFaceNative(faceObj) {
const loops = [];
@@ -448,8 +441,13 @@ function isSyntheticSweepSourceEdgeName(name) {
return /_REPAIR_\d+/.test(raw);
}
+function safeExtrudeSideToken(value, fallback) {
+ const raw = String(value || '').trim();
+ const safe = raw.replace(/[^A-Za-z0-9_.:[\]-]+/g, '_').replace(/_+/g, '_');
+ return safe || fallback;
+}
+
export function generateNativeSweep(target, params = {}) {
- requireNativeSweepBuilder();
const {
face,
distance = 1,
@@ -466,6 +464,7 @@ export function generateNativeSweep(target, params = {}) {
? face.userData.boundaryLoopsWorld.map((loop) => ({
pts: (Array.isArray(loop?.pts) ? loop.pts : loop).map((p) => [p[0], p[1], p[2]]),
isHole: !!loop?.isHole,
+ segmentIds: Array.isArray(loop?.segmentIds) ? loop.segmentIds.slice() : [],
}))
: computeBoundaryLoopsFromFaceNative(face);
if (!boundaryLoops.length) {
@@ -479,21 +478,39 @@ export function generateNativeSweep(target, params = {}) {
faceNormal.normalize();
const sourceIsSketchFace = face?.parent?.type === 'SKETCH';
- const relevantEdges = sourceIsSketchFace && Array.isArray(face?.edges)
- ? face.edges.filter((edge) => {
+ const faceEdges = Array.isArray(face?.edges) ? face.edges : [];
+ const relevantEdges = faceEdges.length
+ ? faceEdges.filter((edge) => {
const kind = edge?.userData?.sketchGeomType;
if (kind === 'circle' || kind === 'arc') return true;
- if (edge?.closedLoop) return false;
+ if (edge?.closedLoop && sourceIsSketchFace) return false;
const sourceEdgeName = String(edge?.name || edge?.userData?.edgeName || '').trim();
- return !isSyntheticSweepSourceEdgeName(sourceEdgeName);
+ return sourceIsSketchFace ? !isSyntheticSweepSourceEdgeName(sourceEdgeName) : true;
})
: [];
const featureTag = name ? `${name}:` : '';
- const edgeInputs = relevantEdges.map((edge) => ({
- name: `${featureTag}${edge?.name || 'EDGE'}_SW`,
- polyline: getEdgePolylineWorld(edge, { dedupe: false }).map((p) => [p[0], p[1], p[2]]),
- metadataJson: buildSweepEdgeMetadataJson(edge, faceNormal, distance, distanceBack),
- })).filter((entry) => Array.isArray(entry.polyline) && entry.polyline.length >= 2);
+ const faceToken = safeExtrudeSideToken(face?.name || face?.userData?.faceName || 'Face', 'Face');
+ const edgeInputs = relevantEdges.map((edge, index) => {
+ const sourceEdgeName = String(edge?.name || edge?.userData?.edgeName || '').trim();
+ const sideName = sourceIsSketchFace
+ ? `${featureTag}${safeExtrudeSideToken(sourceEdgeName || `EDGE_${index}`, `EDGE_${index}`)}_SW`
+ : `${featureTag}${faceToken}_SW_${index}`;
+ const metadata = JSON.parse(buildSweepEdgeMetadataJson(edge, faceNormal, distance, distanceBack) || '{}');
+ if (sourceEdgeName) metadata.sourceEdgeName = sourceEdgeName;
+ return {
+ name: sideName,
+ sketchGeometryId: edge?.userData?.sketchGeometryId ?? null,
+ polyline: getEdgePolylineWorld(edge, { dedupe: false }).map((p) => [p[0], p[1], p[2]]),
+ curveType: edge?.userData?.sketchGeomType || null,
+ isHole: !!edge?.userData?.isHole,
+ bezierPoles: Array.isArray(edge?.userData?.bezierPoles) ? edge.userData.bezierPoles.map((p) => p.slice()) : null,
+ circleCenter: Array.isArray(edge?.userData?.circleCenter) ? edge.userData.circleCenter.slice() : null,
+ circleRadius: Number.isFinite(Number(edge?.userData?.circleRadius)) ? Number(edge.userData.circleRadius) : null,
+ arcCenter: Array.isArray(edge?.userData?.arcCenter) ? edge.userData.arcCenter.slice() : null,
+ arcRadius: Number.isFinite(Number(edge?.userData?.arcRadius)) ? Number(edge.userData.arcRadius) : null,
+ metadataJson: JSON.stringify(metadata),
+ };
+ }).filter((entry) => Array.isArray(entry.polyline) && entry.polyline.length >= 2);
let pathPoints = [];
if (Array.isArray(sweepPathEdges) && sweepPathEdges.length) {
@@ -505,55 +522,52 @@ export function generateNativeSweep(target, params = {}) {
? [distance.x, distance.y, distance.z]
: null;
- const snapshot = manifold.buildSweepAuthoringState({
- name,
- faceName: face?.name || 'Face',
- mode: mode === 'pathAlign' ? 'pathAlign' : 'translate',
- distance: typeof distance === 'number' ? distance : 0,
- distanceVector,
- distanceBack: Number(distanceBack) || 0,
- omitBaseCap: !!omitBaseCap,
- twistAngle: Number.isFinite(Number(twistAngle)) ? Number(twistAngle) : 0,
- faceNormal: [faceNormal.x, faceNormal.y, faceNormal.z],
- boundaryLoops,
- edges: edgeInputs,
- pathPoints,
- });
-
- applySolidAuthoringStateSnapshot(target, snapshot, { remapFaceIDs: true });
- target._dirty = true;
- target._manifold = null;
- target._faceIndex = null;
- try { target.name = name || 'Sweep'; } catch {}
-
- let eps = Number(snapshot?.suggestedEpsilon);
- if (!(eps > 0) && Array.isArray(target._vertProperties) && target._vertProperties.length >= 6) {
- const bounds = computeBoundsFromVertices(target._vertProperties);
- const diag = (bounds && bounds.diag) ? bounds.diag : 1;
- eps = Math.min(1e-4, Math.max(1e-7, diag * 1e-6));
+ if (mode !== 'pathAlign' && !pathPoints.length && Math.abs(Number(twistAngle) || 0) <= 1e-12) {
+ const dir = distanceVector || [
+ faceNormal.x * (Number(distance) || 0),
+ faceNormal.y * (Number(distance) || 0),
+ faceNormal.z * (Number(distance) || 0),
+ ];
+ const occState = makeExtrusion({
+ boundaryLoops,
+ faceName: face?.name || 'Face',
+ name,
+ direction: dir,
+ normal: [faceNormal.x, faceNormal.y, faceNormal.z],
+ distanceBack: Number(distanceBack) || 0,
+ edgeInputs,
+ omitBaseCap,
+ });
+ setOccState(target, occState);
+ try { target.name = name || 'Sweep'; } catch {}
+ return true;
}
- if (Number.isFinite(eps) && eps > 0) target.setEpsilon(eps);
- let ok = false;
- let attempt = 0;
- let errLast = null;
- while (!ok && attempt < 3) {
+ if (pathPoints.length) {
+ const occState = makePathSweep({
+ boundaryLoops,
+ edgeInputs,
+ faceName: face?.name || 'Face',
+ name,
+ normal: [faceNormal.x, faceNormal.y, faceNormal.z],
+ pathPoints,
+ mode: mode === 'pathAlign' ? 'pathAlign' : 'translate',
+ omitBaseCap,
+ twistAngle: Number(twistAngle) || 0,
+ });
+ setOccState(target, occState);
+ try { target.name = name || 'Sweep'; } catch {}
try {
- const mesh = target.getMesh();
- try { /* probe */ } finally { try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch {} }
- ok = true;
- } catch (error) {
- errLast = error;
- eps *= 2;
- if (!(eps > 0) || eps > 5e-4) break;
- try { target.setEpsilon(eps); } catch {}
- }
- attempt++;
- }
- if (!ok && errLast) {
- console.warn('[Sweep] Manifold build failed after native rebuild retries:', errLast.message || errLast);
+ target.addAuxEdge(`${name || 'Sweep'}_PATH`, pathPoints.map((p) => [p[0], p[1], p[2]]), {
+ polylineWorld: true,
+ materialKey: 'OVERLAY',
+ centerline: true,
+ });
+ } catch { /* optional path overlay */ }
+ return true;
}
- return true;
+
+ throw new Error('OpenCASCADE sweep requires either a straight extrusion distance or a path edge.');
}
export class Sweep extends Solid {
diff --git a/src/BREP/Tube.js b/src/BREP/Tube.js
index b75e3015..14523305 100644
--- a/src/BREP/Tube.js
+++ b/src/BREP/Tube.js
@@ -1,17 +1,5 @@
import { Solid } from './BetterSolid.js';
-import { applySolidAuthoringStateSnapshot } from './CppSolidCore.js';
-import { manifold } from './setupManifold.js';
-
-const DEFAULT_SEGMENTS = 32;
-
-function hasNativeTubeBuilder() {
- return typeof manifold?.buildTubeAuthoringState === 'function';
-}
-
-function requireNativeTubeBuilder() {
- if (hasNativeTubeBuilder()) return;
- throw new Error('Tube generation requires the custom local manifold build with native tube support.');
-}
+import { makeTube, setOccState } from './OpenCascadeKernel.js';
function sanitizePathPoints(points) {
const out = [];
@@ -38,18 +26,12 @@ function addTubePathAuxEdge(target, pathPoints, name, closed) {
});
}
-function applyNativeTubeSnapshot(target, snapshot, name) {
- applySolidAuthoringStateSnapshot(target, snapshot, { remapFaceIDs: true });
- target._dirty = true;
- target._manifold = null;
- target._faceIndex = null;
+function applyNativeTubeState(target, state, name) {
+ setOccState(target, state);
target._auxEdges = [];
- target.debugSphereSolids = [];
- target._selfUnionStats = snapshot?.selfUnionStats ?? null;
- target._tubeBuildMode = typeof snapshot?.buildMode === 'string' ? snapshot.buildMode : null;
+ target._tubeBuildMode = 'occ_make_pipe';
target.name = name || 'Tube';
- target.params.closed = !!snapshot?.closed;
- addTubePathAuxEdge(target, snapshot?.pathPoints || target.params?.points, target.name, target.params.closed);
+ addTubePathAuxEdge(target, target.params?.points, target.name, target.params.closed);
return target;
}
@@ -60,18 +42,14 @@ export class Tube extends Solid {
points = [],
radius = 1,
innerRadius = 0,
- resolution = DEFAULT_SEGMENTS,
closed = false,
name = 'Tube',
- debugSpheres = false,
- preferFast = true,
- selfUnion = true,
autoVisualize = false,
+ pathCurve = null,
+ endpointExtension = 0,
} = opts;
- this.params = { points, radius, innerRadius, resolution, closed, name, debugSpheres, preferFast, selfUnion };
+ this.params = { points, radius, innerRadius, closed, name, pathCurve, endpointExtension };
this.name = name;
- this.debugSphereSolids = [];
- this._selfUnionStats = null;
if (Array.isArray(points) && points.length >= 2) {
const firstPoint = points[0];
@@ -84,67 +62,45 @@ export class Tube extends Solid {
}
}
- try {
- const hasPath = Array.isArray(points) && points.length >= 2;
- const validRadius = Number(radius) > 0;
- if (hasPath && validRadius) {
- this.generate();
- if (autoVisualize) this.visualize();
- }
- } catch {
- // Fail-quietly to keep boolean reconstruction safe.
+ const hasPath = Array.isArray(points) && points.length >= 2;
+ const validRadius = Number(radius) > 0;
+ if (hasPath && validRadius) {
+ this.generate();
+ if (autoVisualize) this.visualize();
}
}
generate() {
- const preferFast = this.params?.preferFast !== false;
- return this.generateNative({
- preferFast,
- allowSlowFallback: preferFast,
- });
+ return this.generateNative();
}
- generateFast() {
- return this.generateNative({ preferFast: true, allowSlowFallback: false });
- }
-
- generateSlow() {
- return this.generateNative({ preferFast: false });
- }
-
- buildNativeSnapshot(overrides = {}) {
- requireNativeTubeBuilder();
-
+ buildNativeSnapshot() {
const {
points,
radius,
innerRadius,
- resolution,
closed,
name,
- selfUnion,
+ pathCurve,
+ endpointExtension,
} = this.params || {};
- return manifold.buildTubeAuthoringState({
+ return makeTube({
points: sanitizePathPoints(points),
radius: Number(radius),
innerRadius: Number(innerRadius) || 0,
- resolution: Math.max(8, Math.floor(Number(resolution) || DEFAULT_SEGMENTS)),
closed: !!closed,
- preferFast: overrides.preferFast ?? true,
- allowSlowFallback: overrides.allowSlowFallback ?? (overrides.preferFast ?? true),
- selfUnion: overrides.selfUnion ?? (selfUnion !== false),
name: name || 'Tube',
+ pathCurve,
+ endpointExtension: Number(endpointExtension) || 0,
});
}
- generateNative(overrides = {}) {
+ generateNative() {
if (typeof this.free === 'function') {
try { this.free(); } catch { }
}
- const snapshot = this.buildNativeSnapshot(overrides);
- return applyNativeTubeSnapshot(this, snapshot, this.params?.name);
+ const state = this.buildNativeSnapshot();
+ return applyNativeTubeState(this, state, this.params?.name);
}
}
-
-export { hasNativeTubeBuilder as tubeHasNativeBuilder };
diff --git a/src/BREP/applyBooleanOperation.js b/src/BREP/applyBooleanOperation.js
index 87b259d8..f87bc1bb 100644
--- a/src/BREP/applyBooleanOperation.js
+++ b/src/BREP/applyBooleanOperation.js
@@ -2,23 +2,6 @@
// Helper to apply a boolean operation between a newly created base solid and
// a list of scene solids referenced by name via the boolean param widget.
-import * as THREE from 'three';
-import { Solid } from "./BetterSolid.js";
-import { Manifold } from "./SolidShared.js";
-import {
- applySolidAuthoringStateSnapshot,
- getSyncedCppSolidCore,
- getSolidAuthoringStateSnapshot,
- syncSolidAuthoringStateFromCpp,
-} from "./CppSolidCore.js";
-import { MeshRepairer } from "./MeshRepairer.js";
-import { computeBoundsFromVertices } from "./boundsUtils.js";
-import { manifold } from "./setupManifold.js";
-import {
- buildBooleanOverlapConditioningPlan,
- SOLID_OVERLAP_DIAGNOSTIC_DEFAULTS,
-} from './solidOverlapDiagnosticsCore.js';
-
const BOOLEAN_TINY_FACE_MAX_AREA = 0.001;
const __booleanDebugConfig = (() => {
@@ -245,57 +228,11 @@ function __booleanCloneMetadataValue(value) {
}
function __booleanCollectMetadataEntries(...sourceSolids) {
- const faceMetadata = new Map();
- const edgeMetadata = new Map();
- const auxEdges = [];
- for (const source of sourceSolids) {
- if (!source) continue;
- const snapshot = getSolidAuthoringStateSnapshot(source);
- const faceEntries = snapshot?.faceMetadataJson instanceof Map
- ? snapshot.faceMetadataJson.entries()
- : Array.isArray(snapshot?.faceMetadataJson) ? snapshot.faceMetadataJson : [];
- for (const [faceName, metadataJson] of faceEntries) {
- const normalizedName = String(faceName || '').trim();
- if (!normalizedName) continue;
- let normalizedJson = String(metadataJson || '').trim();
- if (!normalizedJson) {
- normalizedJson = JSON.stringify({});
- } else {
- try {
- normalizedJson = JSON.stringify(__booleanCloneMetadataValue(JSON.parse(normalizedJson)) || {});
- } catch {
- normalizedJson = JSON.stringify({});
- }
- }
- faceMetadata.set(normalizedName, normalizedJson);
- }
- const edgeEntries = snapshot?.edgeMetadataJson instanceof Map
- ? snapshot.edgeMetadataJson.entries()
- : Array.isArray(snapshot?.edgeMetadataJson) ? snapshot.edgeMetadataJson : [];
- for (const [edgeName, metadataJson] of edgeEntries) {
- const normalizedName = String(edgeName || '').trim();
- if (!normalizedName) continue;
- let normalizedJson = String(metadataJson || '').trim();
- if (!normalizedJson) {
- normalizedJson = JSON.stringify({});
- } else {
- try {
- normalizedJson = JSON.stringify(__booleanCloneMetadataValue(JSON.parse(normalizedJson)) || {});
- } catch {
- normalizedJson = JSON.stringify({});
- }
- }
- edgeMetadata.set(normalizedName, normalizedJson);
- }
- const aux = Array.isArray(snapshot?.auxEdges) ? snapshot.auxEdges : null;
- if (aux && aux.length > 0) {
- for (const entry of aux) auxEdges.push(entry);
- }
- }
+ void sourceSolids;
return {
- faceMetadataJson: Array.from(faceMetadata.entries()),
- edgeMetadataJson: Array.from(edgeMetadata.entries()),
- auxEdges,
+ faceMetadataJson: [],
+ edgeMetadataJson: [],
+ auxEdges: [],
};
}
@@ -319,142 +256,13 @@ function __booleanApproxScale(solid) {
}
}
-function __booleanResolveConditioningOptions(scaleHint = 1) {
- const scale = Math.max(1, Number(scaleHint) || 1);
- return {
- ...SOLID_OVERLAP_DIAGNOSTIC_DEFAULTS,
- planeDistanceTolerance: Math.max(
- SOLID_OVERLAP_DIAGNOSTIC_DEFAULTS.planeDistanceTolerance,
- 1e-6 * scale,
- ),
- scaleHint: scale,
- };
-}
-
-function __booleanApplyFaceAdjustments(solid, faceAdjustments, debugLog, logContext = null) {
- if (!solid || typeof solid.pushFace !== 'function' || !Array.isArray(faceAdjustments) || faceAdjustments.length === 0) {
- return [];
- }
- const applied = [];
- for (const adjustment of faceAdjustments) {
- const faceName = String(adjustment?.faceName || '').trim();
- const distance = Number(adjustment?.distance);
- if (!faceName || !Number.isFinite(distance) || distance === 0) continue;
- try {
- solid.pushFace(faceName, distance, { warnMissing: false, warnInvalidNormal: false });
- applied.push({
- faceName,
- distance,
- sign: adjustment?.sign || Math.sign(distance) || 0,
- overlapCount: adjustment?.overlapCount || 0,
- overlapArea: adjustment?.overlapArea || 0,
- });
- } catch (err) {
- debugLog?.('Failed to condition boolean face overlap', {
- context: logContext,
- faceName,
- distance,
- message: err?.message || err,
- });
- }
- }
- return applied;
-}
-
-function __booleanConditionOperands(op, stationarySolid, movingSolid, debugLog, context = null) {
- const normalizedOp = String(op || '').toUpperCase();
- if ((normalizedOp !== 'UNION' && normalizedOp !== 'SUBTRACT') || !stationarySolid || !movingSolid) {
- return {
- stationarySolid,
- movingSolid,
- conditioningApplied: false,
- faceAdjustments: [],
- fallbackFaceAdjustments: [],
- plan: null,
- fallbackPlan: null,
- };
- }
-
- const scaleHint = Math.max(__booleanApproxScale(stationarySolid), __booleanApproxScale(movingSolid), 1);
- const options = {
- ...__booleanResolveConditioningOptions(scaleHint),
- conditioningMode: normalizedOp,
- };
- const moveClone = typeof movingSolid.clone === 'function' ? movingSolid.clone() : movingSolid;
- const plan = buildBooleanOverlapConditioningPlan(stationarySolid, moveClone, options);
- const faceAdjustments = __booleanApplyFaceAdjustments(moveClone, plan?.faceAdjustments, debugLog, context);
- if (faceAdjustments.length > 0) {
- debugLog?.('Applied boolean overlap conditioning', {
- context,
- operation: normalizedOp,
- overlapCount: plan?.overlapCount || 0,
- adjustments: faceAdjustments,
- });
- return {
- stationarySolid,
- movingSolid: moveClone,
- conditioningApplied: true,
- faceAdjustments,
- fallbackFaceAdjustments: [],
- plan,
- fallbackPlan: null,
- };
- }
-
- if (normalizedOp !== 'UNION' || typeof stationarySolid.clone !== 'function') {
- return {
- stationarySolid,
- movingSolid,
- conditioningApplied: false,
- faceAdjustments: [],
- fallbackFaceAdjustments: [],
- plan,
- fallbackPlan: null,
- };
- }
-
- const stationaryClone = stationarySolid.clone();
- const fallbackPlan = buildBooleanOverlapConditioningPlan(movingSolid, stationaryClone, options);
- const fallbackFaceAdjustments = __booleanApplyFaceAdjustments(stationaryClone, fallbackPlan?.faceAdjustments, debugLog, context);
- if (fallbackFaceAdjustments.length > 0) {
- debugLog?.('Applied boolean overlap conditioning via stationary fallback', {
- context,
- operation: normalizedOp,
- overlapCount: fallbackPlan?.overlapCount || 0,
- adjustments: fallbackFaceAdjustments,
- });
- return {
- stationarySolid: stationaryClone,
- movingSolid,
- conditioningApplied: true,
- faceAdjustments: [],
- fallbackFaceAdjustments,
- plan,
- fallbackPlan,
- };
- }
-
- return {
- stationarySolid,
- movingSolid,
- conditioningApplied: false,
- faceAdjustments: [],
- fallbackFaceAdjustments: [],
- plan,
- fallbackPlan,
- };
-}
-
function __booleanCloneMetadataObject(metadata) {
if (!metadata || typeof metadata !== 'object' || Array.isArray(metadata)) return {};
return { ...metadata };
}
-function __booleanRefreshAuthoringStateFromNative(solid) {
- if (!solid || typeof solid !== 'object') return;
- const snapshot = getSolidAuthoringStateSnapshot(solid);
- if (!snapshot || typeof snapshot !== 'object') return;
- applySolidAuthoringStateSnapshot(solid, snapshot);
+function __booleanRefreshAuthoringStateFromNative(_solid) {
+ return;
}
function __booleanGetFaceFeatureId(faceName, metadata = null) {
@@ -487,8 +295,6 @@ function __booleanInvalidateSolidCaches(solid) {
if (!solid || typeof solid !== 'object') return;
solid._dirty = true;
solid._faceIndex = null;
- try { if (solid._manifold && typeof solid._manifold.delete === 'function') solid._manifold.delete(); } catch { }
- solid._manifold = null;
}
function __booleanDeriveSolidToleranceFromVerts(solid, baseTol = 1e-5) {
@@ -721,9 +527,7 @@ function __booleanMergeCoplanarAdjacentIntersectSidewalls(solid, debugLog, conte
const targetMetadata = __booleanCloneMetadataObject(solid.getFaceMetadata(best.faceName) || {});
if (!solid.renameFace(faceName, best.faceName)) continue;
- const core = getSyncedCppSolidCore(solid);
- core.setFaceMetadata(best.faceName, targetMetadata);
- syncSolidAuthoringStateFromCpp(solid, core);
+ if (typeof solid.setFaceMetadata === 'function') solid.setFaceMetadata(best.faceName, targetMetadata);
__booleanInvalidateSolidCaches(solid);
mergedFaces += 1;
mergedThisPass = true;
@@ -803,78 +607,8 @@ function __booleanResolveFoldNameSource(baseSolid, tools, featureID) {
}
function __booleanSolidToGeometry(solid) {
- try {
- if (!solid || typeof solid !== 'object') return null;
- const vp = solid._vertProperties;
- const tv = solid._triVerts;
- const ids = solid._triIDs;
- if (!Array.isArray(vp) || vp.length < 9) return null;
- if (!Array.isArray(tv) || tv.length < 3) return null;
- if (!Array.isArray(ids) || ids.length !== (tv.length / 3)) return null;
-
- const vertArray = Float32Array.from(vp);
- const triArray = Uint32Array.from(tv);
- const geom = new THREE.BufferGeometry();
- geom.setAttribute('position', new THREE.BufferAttribute(vertArray, 3));
- geom.setIndex(new THREE.BufferAttribute(triArray, 1));
-
- const bounds = computeBoundsFromVertices(vertArray);
- const size = bounds ? bounds.size : [0, 0, 0];
- const diag = bounds ? bounds.diag : 0;
- const dx = size[0], dy = size[1], dz = size[2];
- const scale = Math.max(diag || 0, Math.abs(dx), Math.abs(dy), Math.abs(dz), 1);
-
- const idToFaceName = solid._idToFaceName instanceof Map ? solid._idToFaceName : new Map();
- const solidLabel = solid.name || solid.owningFeatureID || 'SOLID';
- const triangles = [];
-
- for (let t = 0; t < triArray.length; t += 3) {
- const triIndex = t / 3;
- const i0 = triArray[t];
- const i1 = triArray[t + 1];
- const i2 = triArray[t + 2];
- const ax = vertArray[i0 * 3], ay = vertArray[i0 * 3 + 1], az = vertArray[i0 * 3 + 2];
- const bx = vertArray[i1 * 3], by = vertArray[i1 * 3 + 1], bz = vertArray[i1 * 3 + 2];
- const cx = vertArray[i2 * 3], cy = vertArray[i2 * 3 + 1], cz = vertArray[i2 * 3 + 2];
-
- const centerX = (ax + bx + cx) / 3;
- const centerY = (ay + by + cy) / 3;
- const centerZ = (az + bz + cz) / 3;
-
- const ux = bx - ax;
- const uy = by - ay;
- const uz = bz - az;
- const vx = cx - ax;
- const vy = cy - ay;
- const vz = cz - az;
- let nx = uy * vz - uz * vy;
- let ny = uz * vx - ux * vz;
- let nz = ux * vy - uy * vx;
- const len = Math.hypot(nx, ny, nz);
- if (len > 0) {
- nx /= len; ny /= len; nz /= len;
- } else {
- nx = 0; ny = 0; nz = 1;
- }
-
- const rawName = idToFaceName.get(ids[triIndex]);
- const faceName = rawName ? String(rawName) : `${solidLabel}_FACE_${ids[triIndex] ?? triIndex}`;
- triangles.push({
- center: [centerX, centerY, centerZ],
- normal: [nx, ny, nz],
- faceName,
- });
- }
-
- return {
- geometry: geom,
- triangles,
- scale,
- fallbackPrefix: `${solidLabel}_REPAIR`,
- };
- } catch {
- return null;
- }
+ void solid;
+ return null;
}
function __booleanAssignFaceData(geometry, sourceMeta, debugLog, options = {}) {
@@ -985,7 +719,7 @@ function __booleanAssignFaceData(geometry, sourceMeta, debugLog, options = {}) {
if (!faceName) faceName = nextFallbackName();
let id = nameToID.get(faceName);
if (!id) {
- id = Manifold.reserveIDs(1);
+ id = nameToID.size + 1;
nameToID.set(faceName, id);
idToName.set(id, faceName);
}
@@ -1057,7 +791,7 @@ function __booleanAssignFaceData(geometry, sourceMeta, debugLog, options = {}) {
if (!faceName) faceName = nextFallbackName();
let id = nameToID.get(faceName);
if (!id) {
- id = Manifold.reserveIDs(1);
+ id = nameToID.size + 1;
nameToID.set(faceName, id);
idToName.set(id, faceName);
}
@@ -1072,52 +806,12 @@ function __booleanAssignFaceData(geometry, sourceMeta, debugLog, options = {}) {
}
function __booleanMakeSolidFromGeometry(geometry, faceIDs, idToFaceName, debugLog, ...sourceSolids) {
- try {
- if (!geometry || !faceIDs || !idToFaceName) return null;
- if (typeof manifold?.buildSolidAuthoringStateFromMesh !== 'function') {
- throw new Error('Native mesh-to-solid rebuild helper is unavailable.');
- }
- const indexAttr = geometry.getIndex();
- const posAttr = geometry.getAttribute('position');
- if (!indexAttr || !posAttr) return null;
- const metadataEntries = __booleanCollectMetadataEntries(...sourceSolids);
- const faceNameToID = Array.from(idToFaceName.entries(), ([id, faceName]) => [faceName, id]);
- const snapshot = manifold.buildSolidAuthoringStateFromMesh({
- numProp: 3,
- vertProperties: Array.from(posAttr.array || []),
- triVerts: Array.from(indexAttr.array || []),
- faceID: Array.from(faceIDs instanceof Uint32Array ? faceIDs : Uint32Array.from(faceIDs)),
- faceNameToID,
- idToFaceName: Array.from(idToFaceName.entries()),
- faceMetadataJson: metadataEntries.faceMetadataJson,
- edgeMetadataJson: metadataEntries.edgeMetadataJson,
- auxEdges: metadataEntries.auxEdges,
- });
- const rebuilt = new Solid();
- applySolidAuthoringStateSnapshot(rebuilt, snapshot);
- const restoredIdToFaceName = new Map(idToFaceName);
- if (restoredIdToFaceName.size > 0) {
- rebuilt._idToFaceName = restoredIdToFaceName;
- rebuilt._faceNameToID = new Map(Array.from(restoredIdToFaceName.entries(), ([id, faceName]) => [faceName, id]));
- rebuilt._faceMetadata = new Map(
- Array.from(metadataEntries.faceMetadataJson || [], ([faceName, raw]) => {
- try { return [faceName, JSON.parse(raw || "{}")]; } catch { return [faceName, {}]; }
- }).filter(([faceName]) => rebuilt._faceNameToID.has(faceName))
- );
- rebuilt._edgeMetadata = new Map(
- Array.from(metadataEntries.edgeMetadataJson || [], ([edgeName, raw]) => {
- try { return [edgeName, JSON.parse(raw || "{}")]; } catch { return [edgeName, {}]; }
- })
- );
- }
- rebuilt._dirty = true;
- rebuilt._manifold = null;
- rebuilt._faceIndex = null;
- return rebuilt;
- } catch (err) {
- debugLog?.('Solid rebuild failed', { message: err?.message || err });
- return null;
- }
+ void geometry;
+ void faceIDs;
+ void idToFaceName;
+ void debugLog;
+ void sourceSolids;
+ return null;
}
function __booleanRestoreFaceTrackingFromSources(solid, debugLog, ...sourceSolids) {
@@ -1198,150 +892,17 @@ function __booleanRestoreFaceTrackingFromSources(solid, debugLog, ...sourceSolid
}
function __booleanAttemptRepairSolid(solid, eps, debugLog) {
- const source = __booleanSolidToGeometry(solid);
- if (!source) return null;
-
- const baseGeom = source.geometry;
- const repairer = new MeshRepairer();
- const baseWeld = Math.max(1e-5, Math.abs(eps || 0) * 10);
- const attemptScales = [1, 4, 16];
-
- try {
- for (const scale of attemptScales) {
- const weld = baseWeld * scale;
- const line = Math.max(1e-5, weld);
- const grid = Math.max(1e-4, weld * 2);
-
- const workingGeom = baseGeom.clone();
- let repairedGeom;
- try {
- repairedGeom = repairer.repairAll(workingGeom, { weldEps: weld, lineEps: line, gridCell: grid }) || workingGeom;
- } catch (err) {
- debugLog?.('Repair attempt failed', {
- attemptScale: scale,
- message: err?.message || err,
- });
- try { workingGeom.dispose(); } catch { }
- continue;
- }
-
- const faceData = __booleanAssignFaceData(repairedGeom, source, debugLog, {
- targetTriangles: source?.triangles,
- });
- if (!faceData) {
- try { repairedGeom.dispose(); } catch { }
- continue;
- }
-
- const rebuilt = __booleanMakeSolidFromGeometry(
- repairedGeom, faceData.faceIDs, faceData.idToFaceName, debugLog, solid,
- );
- try { repairedGeom.dispose(); } catch { }
- if (rebuilt) {
- try {
- rebuilt.name = solid.name || rebuilt.name;
- rebuilt.owningFeatureID = solid.owningFeatureID || rebuilt.owningFeatureID;
- } catch { }
- return rebuilt;
- }
- }
- } finally {
- try { baseGeom.dispose(); } catch { }
- }
-
+ void solid;
+ void eps;
+ void debugLog;
return null;
}
function __booleanMeshMergeUnion(baseSolid, toolSolid, eps, debugLog) {
- const srcA = __booleanSolidToGeometry(baseSolid);
- const srcB = __booleanSolidToGeometry(toolSolid);
- if (!srcA || !srcB) {
- try { srcA?.geometry?.dispose?.(); } catch { }
- try { srcB?.geometry?.dispose?.(); } catch { }
- return null;
- }
-
- const geomA = srcA.geometry;
- const geomB = srcB.geometry;
-
- const posA = geomA.getAttribute('position')?.array;
- const posB = geomB.getAttribute('position')?.array;
- const idxA = geomA.getIndex()?.array;
- const idxB = geomB.getIndex()?.array;
- if (!posA || !posB || !idxA || !idxB) {
- try { geomA.dispose(); } catch { }
- try { geomB.dispose(); } catch { }
- return null;
- }
-
- const mergedPosBase = new Float32Array(posA.length + posB.length);
- mergedPosBase.set(posA, 0);
- mergedPosBase.set(posB, posA.length);
-
- const mergedIdxBase = new Uint32Array(idxA.length + idxB.length);
- mergedIdxBase.set(idxA, 0);
- const offset = (posA.length / 3) >>> 0;
- for (let i = 0; i < idxB.length; i++) {
- mergedIdxBase[idxA.length + i] = idxB[i] + offset;
- }
-
- try { geomA.dispose(); } catch { }
- try { geomB.dispose(); } catch { }
-
- const sourceMeta = {
- triangles: [...srcA.triangles, ...srcB.triangles],
- scale: Math.max(srcA.scale || 1, srcB.scale || 1),
- fallbackPrefix: `${baseSolid?.name || toolSolid?.name || 'UNION'}_REPAIR`,
- };
-
- const repairer = new MeshRepairer();
- const baseWeld = Math.max(1e-5, Math.abs(eps || 0) * 10);
- const attemptScales = [1, 4, 16];
-
- const buildGeometry = () => {
- const g = new THREE.BufferGeometry();
- g.setAttribute('position', new THREE.BufferAttribute(mergedPosBase.slice(), 3));
- g.setIndex(new THREE.BufferAttribute(mergedIdxBase.slice(), 1));
- return g;
- };
-
- for (const scale of attemptScales) {
- const weld = baseWeld * scale;
- const line = Math.max(1e-5, weld);
- const grid = Math.max(1e-4, weld * 2);
-
- const workingGeom = buildGeometry();
- let repairedGeom;
- try {
- repairedGeom = repairer.repairAll(workingGeom, { weldEps: weld, lineEps: line, gridCell: grid }) || workingGeom;
- } catch (err) {
- debugLog?.('Mesh-merge repair attempt failed', {
- attemptScale: scale,
- message: err?.message || err,
- });
- try { workingGeom.dispose(); } catch { }
- continue;
- }
-
- const faceData = __booleanAssignFaceData(repairedGeom, sourceMeta, debugLog);
- if (!faceData) {
- try { repairedGeom.dispose(); } catch { }
- continue;
- }
-
- const rebuilt = __booleanMakeSolidFromGeometry(
- repairedGeom, faceData.faceIDs, faceData.idToFaceName, debugLog, baseSolid, toolSolid,
- );
- try { repairedGeom.dispose(); } catch { }
- if (rebuilt) {
- try {
- rebuilt.name = baseSolid?.name || rebuilt.name;
- rebuilt.owningFeatureID = baseSolid?.owningFeatureID || rebuilt.owningFeatureID;
- } catch { }
- return rebuilt;
- }
- }
-
+ void baseSolid;
+ void toolSolid;
+ void eps;
+ void debugLog;
return null;
}
@@ -1383,14 +944,12 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
if (tools.length === 0) return { added: [baseSolid], removed: [] };
const debugLog = __booleanDebugLogger(featureID, op, baseSolid, tools);
- const overlapConditioningEnabled = booleanParam?.overlapConditioningEnabled !== false;
debugLog('Starting boolean', {
featureID,
operation: op,
base: __booleanDebugSummarizeSolid(baseSolid),
tools: tools.map(__booleanDebugSummarizeSolid),
targetCount: tools.length,
- overlapConditioningEnabled,
});
// Apply selected boolean
@@ -1399,11 +958,6 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
// FROM each selected target solid. Add robust fallbacks similar to UNION.
const results = [];
let idx = 0;
- const preCleanLocal = (solid, eps) => {
- try { if (typeof solid.setEpsilon === 'function') solid.setEpsilon(eps); } catch {}
- try { if (typeof solid.fixTriangleWindingsByAdjacency === 'function') solid.fixTriangleWindingsByAdjacency(); } catch {}
- };
-
const addResult = async (solid, target) => {
solid = await __booleanPostTinyFaceCleanup(solid, debugLog, { op: 'SUBTRACT', featureID });
const inheritedName = target?.name || target?.uuid || null;
@@ -1418,19 +972,9 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
for (const target of tools) {
let conditionedTarget = target;
let conditionedTool = baseSolid;
- if (overlapConditioningEnabled) {
- const conditioning = __booleanConditionOperands('SUBTRACT', target, baseSolid, debugLog, {
- featureID,
- target: target?.name || target?.uuid || null,
- tool: baseSolid?.name || baseSolid?.uuid || null,
- });
- conditionedTarget = conditioning.stationarySolid || target;
- conditionedTool = conditioning.movingSolid || baseSolid;
- }
let success = false;
try {
let out = conditionedTarget.subtract(conditionedTool);
- out = __booleanRestoreFaceTrackingFromSources(out, debugLog, conditionedTarget, conditionedTool);
await addResult(out, target);
success = true;
} catch (e1) {
@@ -1445,12 +989,7 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
try {
const a = typeof conditionedTarget.clone === 'function' ? conditionedTarget.clone() : conditionedTarget;
const b = typeof conditionedTool.clone === 'function' ? conditionedTool.clone() : conditionedTool;
- const scale = Math.max(1, __booleanApproxScale(a));
- const eps = Math.max(1e-9, 1e-6 * scale);
- preCleanLocal(a, eps);
- preCleanLocal(b, eps);
let out = a.subtract(b);
- out = __booleanRestoreFaceTrackingFromSources(out, debugLog, a, b);
await addResult(out, target);
success = true;
} catch (e2) {
@@ -1473,12 +1012,6 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
return { added: results.length ? results : [baseSolid], removed };
}
- // Helper: light pre-clean in authoring space (no manifold build)
- const preClean = (solid, eps) => {
- try { if (typeof solid.fixTriangleWindingsByAdjacency === 'function') solid.fixTriangleWindingsByAdjacency(); } catch {}
- try { if (typeof solid.setEpsilon === 'function') solid.setEpsilon(eps); } catch (e) {console.warn(e, solid)}
- };
-
// UNION / INTERSECT: fold tools into the new baseSolid and replace base
let result = baseSolid;
for (const tool of tools) {
@@ -1489,84 +1022,19 @@ export async function applyBooleanOperation(partHistory, baseSolid, booleanParam
let workingResult = result;
let workingTool = tool;
- if (overlapConditioningEnabled && op === 'UNION') {
- const conditioning = __booleanConditionOperands('UNION', result, tool, debugLog, {
- featureID,
- base: result?.name || result?.uuid || null,
- tool: tool?.name || tool?.uuid || null,
- });
- workingResult = conditioning.stationarySolid || result;
- workingTool = conditioning.movingSolid || tool;
- }
-
- const scale = Math.max(1, __booleanApproxScale(workingResult));
- const eps = Math.max(1e-9, 1e-6 * scale);
try {
// Attempt the boolean directly; repair fallback handles welding if needed.
- result = (op === 'UNION') ? workingResult.union(workingTool) : workingResult.intersect(workingTool);
- result = __booleanRestoreFaceTrackingFromSources(result, debugLog, workingResult, workingTool);
+ result = (op === 'UNION') ? workingTool.union(workingResult) : workingResult.intersect(workingTool);
} catch (e1) {
debugLog('Primary union/intersect failed; attempting welded fallback', {
message: e1?.message || e1,
tool: __booleanDebugSummarizeSolid(workingTool),
- epsilon: eps,
});
- let repaired = false;
- try {
- const repairedBase = __booleanAttemptRepairSolid(workingResult, eps, debugLog);
- const repairedTool = __booleanAttemptRepairSolid(workingTool, eps, debugLog);
- if (repairedBase || repairedTool) {
- debugLog('Attempting repair fallback', {
- repairedBase: !!repairedBase,
- repairedTool: !!repairedTool,
- });
- const baseOperand = repairedBase || (typeof workingResult.clone === 'function' ? workingResult.clone() : workingResult);
- const toolOperand = repairedTool || (typeof workingTool.clone === 'function' ? workingTool.clone() : workingTool);
- preClean(baseOperand, eps);
- preClean(toolOperand, eps);
- result = (op === 'UNION') ? baseOperand.union(toolOperand) : baseOperand.intersect(toolOperand);
- result = __booleanRestoreFaceTrackingFromSources(result, debugLog, baseOperand, toolOperand);
- repaired = true;
- }
- } catch (repairErr) {
- debugLog('Repair fallback failed', { message: repairErr?.message || repairErr });
- }
- if (repaired) continue;
- // Fallback A: try on welded clones with tiny epsilon
- try {
- const a = typeof workingResult.clone === 'function' ? workingResult.clone() : workingResult;
- const b = typeof workingTool.clone === 'function' ? workingTool.clone() : workingTool;
- preClean(a, eps);
- preClean(b, eps);
- result = (op === 'UNION') ? a.union(b) : a.intersect(b);
- result = __booleanRestoreFaceTrackingFromSources(result, debugLog, a, b);
- } catch (e2) {
- let meshRecovered = false;
- if (op === 'UNION') {
- try {
- const merged = __booleanMeshMergeUnion(workingResult, workingTool, eps, debugLog);
- if (merged) {
- debugLog('Mesh-merge fallback succeeded');
- const repairedMerged = __booleanAttemptRepairSolid(merged, eps, debugLog);
- const mergedHasTracking = __booleanHasMeaningfulFaceTracking(merged);
- const repairedMergedHasTracking = __booleanHasMeaningfulFaceTracking(repairedMerged);
- const finalMerged = (repairedMerged && (!mergedHasTracking || repairedMergedHasTracking))
- ? repairedMerged
- : merged;
- if (finalMerged !== merged) {
- debugLog('Mesh-merge result repaired');
- }
- result = finalMerged;
- meshRecovered = true;
- }
- } catch (meshErr) {
- debugLog('Mesh-merge fallback failed', { message: meshErr?.message || meshErr });
- }
- }
- if (meshRecovered) continue;
- throw e2;
- }
+ // Retry once on cloned operands with the same OCCT boolean path.
+ const a = typeof workingResult.clone === 'function' ? workingResult.clone() : workingResult;
+ const b = typeof workingTool.clone === 'function' ? workingTool.clone() : workingTool;
+ result = (op === 'UNION') ? b.union(a) : a.intersect(b);
}
}
result = await __booleanPostTinyFaceCleanup(result, debugLog, { op, featureID });
diff --git a/src/BREP/faceIdAllocator.js b/src/BREP/faceIdAllocator.js
new file mode 100644
index 00000000..39541825
--- /dev/null
+++ b/src/BREP/faceIdAllocator.js
@@ -0,0 +1,19 @@
+let nextFaceID = 1;
+
+export function reserveFaceIDs(count = 1) {
+ const n = Math.max(1, Math.floor(Number(count) || 1));
+ const start = nextFaceID;
+ nextFaceID += n;
+ return start;
+}
+
+export function reserveFaceID() {
+ return reserveFaceIDs(1);
+}
+
+export function noteFaceID(id) {
+ const value = Number(id);
+ if (Number.isFinite(value) && value >= nextFaceID) {
+ nextFaceID = Math.floor(value) + 1;
+ }
+}
diff --git a/src/BREP/faceThicken.js b/src/BREP/faceThicken.js
index a50cf624..3a2159d9 100644
--- a/src/BREP/faceThicken.js
+++ b/src/BREP/faceThicken.js
@@ -1,8 +1,12 @@
import { Solid } from './BetterSolid.js';
-import { Manifold, THREE } from './SolidShared.js';
+import {
+ hasOccShape,
+ makeExtrusion,
+ makeFacePrismFromOccSolid,
+ setOccState,
+} from './OpenCascadeKernel.js';
const EPS = 1e-9;
-const TRI_EPS = 1e-12;
function sanitizeToken(value, fallback = 'FACE') {
const raw = value == null ? '' : String(value);
@@ -15,31 +19,6 @@ function sanitizeToken(value, fallback = 'FACE') {
|| fallback;
}
-function edgeKey(a, b) {
- const i = a < b ? a : b;
- const j = a < b ? b : a;
- return `${i}|${j}`;
-}
-
-function pointKey(point, epsilon) {
- const inv = epsilon > 0 ? (1 / epsilon) : 1e6;
- return [
- Math.round(point.x * inv),
- Math.round(point.y * inv),
- Math.round(point.z * inv),
- ].join(',');
-}
-
-function triangleNormal(a, b, c) {
- return new THREE.Vector3()
- .subVectors(b, a)
- .cross(new THREE.Vector3().subVectors(c, a));
-}
-
-function triangleArea(a, b, c) {
- return triangleNormal(a, b, c).length() * 0.5;
-}
-
function getFaceLabel(face) {
const raw = face?.userData?.faceName ?? face?.faceName ?? face?.name ?? null;
if (raw == null) return null;
@@ -47,2616 +26,113 @@ function getFaceLabel(face) {
return label || null;
}
-function unorderedPointPairKey(a, b) {
- return a < b ? `${a}|${b}` : `${b}|${a}`;
-}
-
-function addSmoothAdjacentBoundaryNormals(face, surface, options = {}) {
+function getParentOccSolid(face) {
const solid = face?.parentSolid || (String(face?.parent?.type || '').toUpperCase() === 'SOLID' ? face.parent : null);
- const selectedFaceName = getFaceLabel(face);
- const triVerts = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const triIDs = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const vertProperties = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const triCount = (triVerts.length / 3) | 0;
- if (!solid || !selectedFaceName) {
- return { candidateEdges: 0, acceptedEdges: 0, contributionCount: 0, contributedVertexCount: 0 };
- }
- const boundaryEdges = Array.isArray(surface?.boundaryDirectedEdges) ? surface.boundaryDirectedEdges : [];
- const vertices = Array.isArray(surface?.vertices) ? surface.vertices : [];
- const vertexNormals = Array.isArray(surface?.vertexNormals) ? surface.vertexNormals : [];
- if (!boundaryEdges.length || !vertices.length || !vertexNormals.length) {
- return { candidateEdges: 0, acceptedEdges: 0, contributionCount: 0, contributedVertexCount: 0 };
- }
-
- const weldTolerance = Math.max(Number(surface?.weldTolerance) || 0, 1e-8);
- const smoothDotThreshold = Math.max(
- -1,
- Math.min(
- 1,
- Number.isFinite(Number(options.adjacentNormalDotThreshold))
- ? Number(options.adjacentNormalDotThreshold)
- : (Number.isFinite(Number(options.smoothAdjacentNormalDotThreshold))
- ? Number(options.smoothAdjacentNormalDotThreshold)
- : 0.85),
- ),
- );
- const adjacentWeightScale = Math.max(
- 0,
- Number.isFinite(Number(options.adjacentNormalWeightScale))
- ? Number(options.adjacentNormalWeightScale)
- : 1,
- );
- if (adjacentWeightScale <= 0) {
- return { candidateEdges: 0, acceptedEdges: 0, contributionCount: 0, contributedVertexCount: 0 };
- }
-
- const boundaryVertexByPointKey = new Map();
- const boundaryEdgeByPointPair = new Map();
- for (const edge of boundaryEdges) {
- const start = edge?.start >>> 0;
- const end = edge?.end >>> 0;
- const a = vertices[start];
- const b = vertices[end];
- if (!a || !b) continue;
- const aKey = pointKey(a, weldTolerance);
- const bKey = pointKey(b, weldTolerance);
- boundaryVertexByPointKey.set(aKey, start);
- boundaryVertexByPointKey.set(bKey, end);
- boundaryEdgeByPointPair.set(unorderedPointPairKey(aKey, bKey), true);
- }
- if (!boundaryEdgeByPointPair.size) {
- return { candidateEdges: 0, acceptedEdges: 0, contributionCount: 0, contributedVertexCount: 0 };
- }
-
- const idToFaceName = solid?._idToFaceName instanceof Map ? solid._idToFaceName : new Map();
- const parentMatrix = new THREE.Matrix4();
- try {
- solid.updateMatrixWorld?.(true);
- parentMatrix.copy(solid.matrixWorld || new THREE.Matrix4());
- } catch {
- parentMatrix.identity();
- }
-
- const parentPointCache = new Map();
- const parentKeyCache = new Map();
- const contributedVertices = new Set();
- let candidateEdges = 0;
- let acceptedEdges = 0;
- let contributionCount = 0;
-
- const addContributionForPointKey = (key, adjacentUnit, normalLength) => {
- const selectedIndex = boundaryVertexByPointKey.get(key);
- if (selectedIndex == null) return false;
- const current = vertexNormals[selectedIndex];
- if (!current || current.lengthSq() <= TRI_EPS) return false;
- const baseWeight = Math.max(current.length(), EPS);
- const weight = Math.min(normalLength, baseWeight) * adjacentWeightScale;
- if (!(weight > EPS)) return false;
- current.add(adjacentUnit.clone().multiplyScalar(weight));
- contributedVertices.add(selectedIndex);
- contributionCount += 1;
- return true;
- };
-
- const processAdjacentTriangle = (points) => {
- if (!Array.isArray(points) || points.length !== 3) return;
- const keys = points.map((point) => pointKey(point, weldTolerance));
- let touchesBoundaryEdge = false;
- for (const [u, v] of [[0, 1], [1, 2], [2, 0]]) {
- if (!boundaryEdgeByPointPair.has(unorderedPointPairKey(keys[u], keys[v]))) continue;
- touchesBoundaryEdge = true;
- candidateEdges += 1;
- break;
- }
- if (!touchesBoundaryEdge) return;
-
- const normal = triangleNormal(points[0], points[1], points[2]);
- const normalLength = normal.length();
- if (!(normalLength > TRI_EPS)) return;
- const unit = normal.multiplyScalar(1 / normalLength);
-
- let acceptedThisTriangle = false;
- for (let vertexIndex = 0; vertexIndex < 3; vertexIndex++) {
- const selectedIndex = boundaryVertexByPointKey.get(keys[vertexIndex]);
- if (selectedIndex == null) continue;
- const base = vertexNormals[selectedIndex]?.clone?.() || null;
- if (!base || base.lengthSq() <= TRI_EPS) continue;
- base.normalize();
- const dot = unit.dot(base);
- let aligned = null;
- if (dot >= smoothDotThreshold) aligned = unit;
- else if (-dot >= smoothDotThreshold) aligned = unit.clone().multiplyScalar(-1);
- if (!aligned) continue;
- if (addContributionForPointKey(keys[vertexIndex], aligned, normalLength)) acceptedThisTriangle = true;
- }
- if (acceptedThisTriangle) acceptedEdges += 1;
- };
-
- try {
- const queriedFaces = typeof solid.getFaces === 'function' ? (solid.getFaces(false) || []) : [];
- if (Array.isArray(queriedFaces) && queriedFaces.length) {
- for (const entry of queriedFaces) {
- const faceName = String(entry?.faceName || '').trim();
- if (!faceName || faceName === selectedFaceName) continue;
- for (const tri of entry?.triangles || []) {
- const p1 = Array.isArray(tri?.p1) ? tri.p1 : null;
- const p2 = Array.isArray(tri?.p2) ? tri.p2 : null;
- const p3 = Array.isArray(tri?.p3) ? tri.p3 : null;
- if (!p1 || !p2 || !p3) continue;
- processAdjacentTriangle([
- new THREE.Vector3(p1[0] || 0, p1[1] || 0, p1[2] || 0).applyMatrix4(parentMatrix),
- new THREE.Vector3(p2[0] || 0, p2[1] || 0, p2[2] || 0).applyMatrix4(parentMatrix),
- new THREE.Vector3(p3[0] || 0, p3[1] || 0, p3[2] || 0).applyMatrix4(parentMatrix),
- ]);
- }
- }
- if (candidateEdges > 0 || triCount === 0 || triIDs.length < triCount || vertProperties.length < 9) {
- return {
- candidateEdges,
- acceptedEdges,
- contributionCount,
- contributedVertexCount: contributedVertices.size,
- };
- }
- }
- } catch {
- // Fall back to raw authoring arrays below.
- }
-
- if (triCount === 0 || triIDs.length < triCount || vertProperties.length < 9) {
- return {
- candidateEdges,
- acceptedEdges,
- contributionCount,
- contributedVertexCount: contributedVertices.size,
- };
- }
-
- const getParentPoint = (index) => {
- const vertexIndex = index >>> 0;
- let point = parentPointCache.get(vertexIndex);
- if (point) return point;
- const base = vertexIndex * 3;
- point = new THREE.Vector3(
- Number(vertProperties[base + 0]) || 0,
- Number(vertProperties[base + 1]) || 0,
- Number(vertProperties[base + 2]) || 0,
- ).applyMatrix4(parentMatrix);
- parentPointCache.set(vertexIndex, point);
- return point;
- };
- const getParentPointKey = (index) => {
- const vertexIndex = index >>> 0;
- let key = parentKeyCache.get(vertexIndex);
- if (key) return key;
- key = pointKey(getParentPoint(vertexIndex), weldTolerance);
- parentKeyCache.set(vertexIndex, key);
- return key;
- };
- const addContribution = (parentVertexIndex, alignedUnit, normalLength) => {
- return addContributionForPointKey(getParentPointKey(parentVertexIndex), alignedUnit, normalLength);
- };
-
- const triangle = new THREE.Vector3();
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const faceName = String(idToFaceName.get(triIDs[triIndex]) || '').trim();
- if (faceName === selectedFaceName) continue;
-
- const i0 = triVerts[(triIndex * 3) + 0] >>> 0;
- const i1 = triVerts[(triIndex * 3) + 1] >>> 0;
- const i2 = triVerts[(triIndex * 3) + 2] >>> 0;
- const parentEdges = [[i0, i1], [i1, i2], [i2, i0]];
- let touchesBoundaryEdge = false;
- for (const [u, v] of parentEdges) {
- const uKey = getParentPointKey(u);
- const vKey = getParentPointKey(v);
- if (!boundaryEdgeByPointPair.has(unorderedPointPairKey(uKey, vKey))) continue;
- touchesBoundaryEdge = true;
- candidateEdges += 1;
- break;
- }
- if (!touchesBoundaryEdge) continue;
-
- const p0 = getParentPoint(i0);
- const p1 = getParentPoint(i1);
- const p2 = getParentPoint(i2);
- triangle.copy(triangleNormal(p0, p1, p2));
- const normalLength = triangle.length();
- if (!(normalLength > TRI_EPS)) continue;
- const unit = triangle.multiplyScalar(1 / normalLength);
-
- let acceptedThisTriangle = false;
- for (const vertexIndex of [i0, i1, i2]) {
- const selectedIndex = boundaryVertexByPointKey.get(getParentPointKey(vertexIndex));
- if (selectedIndex == null) continue;
- const base = vertexNormals[selectedIndex]?.clone?.() || null;
- if (!base || base.lengthSq() <= TRI_EPS) continue;
- base.normalize();
- const dot = unit.dot(base);
- let aligned = null;
- if (dot >= smoothDotThreshold) aligned = unit;
- else if (-dot >= smoothDotThreshold) aligned = unit.clone().multiplyScalar(-1);
- if (!aligned) continue;
- if (addContribution(vertexIndex, aligned, normalLength)) acceptedThisTriangle = true;
- }
- if (acceptedThisTriangle) acceptedEdges += 1;
- }
-
- return {
- candidateEdges,
- acceptedEdges,
- contributionCount,
- contributedVertexCount: contributedVertices.size,
- };
-}
-
-function analyzeMeshTopology(solid) {
- const triVerts = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const triCount = (triVerts.length / 3) | 0;
- if (!triCount) return { boundaryEdgeCount: 0, nonManifoldEdgeCount: 0, triangleCount: 0 };
- const counts = new Map();
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = triVerts[triIndex * 3] >>> 0;
- const b = triVerts[triIndex * 3 + 1] >>> 0;
- const c = triVerts[triIndex * 3 + 2] >>> 0;
- for (const [u, v] of [[a, b], [b, c], [c, a]]) {
- const key = edgeKey(u, v);
- counts.set(key, (counts.get(key) || 0) + 1);
- }
- }
- let boundaryEdgeCount = 0;
- let nonManifoldEdgeCount = 0;
- for (const count of counts.values()) {
- if (count === 1) boundaryEdgeCount += 1;
- else if (count !== 2) nonManifoldEdgeCount += 1;
- }
- return { boundaryEdgeCount, nonManifoldEdgeCount, triangleCount: triCount };
-}
-
-function analyzeTriangleOrientation(solid) {
- const triVerts = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const triCount = (triVerts.length / 3) | 0;
- if (!triCount) return { sameDirectionEdgeCount: 0, oppositeDirectionEdgeCount: 0, ambiguousEdgeCount: 0 };
- const edgeUses = new Map();
- const addUse = (a, b) => {
- const key = edgeKey(a, b);
- let list = edgeUses.get(key);
- if (!list) {
- list = [];
- edgeUses.set(key, list);
- }
- list.push([a, b]);
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = triVerts[triIndex * 3] >>> 0;
- const b = triVerts[triIndex * 3 + 1] >>> 0;
- const c = triVerts[triIndex * 3 + 2] >>> 0;
- addUse(a, b);
- addUse(b, c);
- addUse(c, a);
- }
- let sameDirectionEdgeCount = 0;
- let oppositeDirectionEdgeCount = 0;
- let ambiguousEdgeCount = 0;
- for (const uses of edgeUses.values()) {
- if (uses.length !== 2) {
- ambiguousEdgeCount += 1;
- continue;
- }
- if (uses[0][0] === uses[1][0] && uses[0][1] === uses[1][1]) sameDirectionEdgeCount += 1;
- else oppositeDirectionEdgeCount += 1;
- }
- return { sameDirectionEdgeCount, oppositeDirectionEdgeCount, ambiguousEdgeCount };
-}
-
-function orientSolidTrianglesByAdjacency(solid) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || vp.length < 9) return 0;
-
- const edgeUses = new Map();
- const addUse = (a, b, triIndex) => {
- const key = edgeKey(a, b);
- let list = edgeUses.get(key);
- if (!list) {
- list = [];
- edgeUses.set(key, list);
- }
- list.push({ triIndex, a, b });
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addUse(a, b, triIndex);
- addUse(b, c, triIndex);
- addUse(c, a, triIndex);
- }
-
- const adjacency = Array.from({ length: triCount }, () => []);
- for (const uses of edgeUses.values()) {
- if (uses.length !== 2) continue;
- const first = uses[0];
- const second = uses[1];
- const sameDirection = first.a === second.a && first.b === second.b;
- adjacency[first.triIndex].push({ triIndex: second.triIndex, sameDirection });
- adjacency[second.triIndex].push({ triIndex: first.triIndex, sameDirection });
- }
-
- const flip = new Int8Array(triCount);
- flip.fill(-1);
- for (let seed = 0; seed < triCount; seed++) {
- if (flip[seed] !== -1) continue;
- flip[seed] = 0;
- const stack = [seed];
- while (stack.length) {
- const current = stack.pop();
- const currentFlip = flip[current];
- for (const edge of adjacency[current]) {
- const desired = currentFlip ^ (edge.sameDirection ? 1 : 0);
- if (flip[edge.triIndex] === -1) {
- flip[edge.triIndex] = desired;
- stack.push(edge.triIndex);
- }
- }
- }
- }
-
- let changed = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (flip[triIndex] !== 1) continue;
- const base = triIndex * 3;
- const tmp = tv[base + 1];
- tv[base + 1] = tv[base + 2];
- tv[base + 2] = tmp;
- changed += 1;
- }
-
- const flipTriangle = (triIndex) => {
- const base = triIndex * 3;
- const tmp = tv[base + 1];
- tv[base + 1] = tv[base + 2];
- tv[base + 2] = tmp;
- };
-
- const orientationStats = () => {
- const localEdgeUses = new Map();
- const addLocalUse = (a, b, triIndex) => {
- const key = edgeKey(a, b);
- let list = localEdgeUses.get(key);
- if (!list) {
- list = [];
- localEdgeUses.set(key, list);
- }
- list.push({ triIndex, a, b });
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addLocalUse(a, b, triIndex);
- addLocalUse(b, c, triIndex);
- addLocalUse(c, a, triIndex);
- }
- const sameByTri = new Uint8Array(triCount);
- const oppositeByTri = new Uint8Array(triCount);
- let sameCount = 0;
- for (const uses of localEdgeUses.values()) {
- if (uses.length !== 2) continue;
- const sameDirection = uses[0].a === uses[1].a && uses[0].b === uses[1].b;
- if (sameDirection) {
- sameCount += 1;
- sameByTri[uses[0].triIndex] += 1;
- sameByTri[uses[1].triIndex] += 1;
- } else {
- oppositeByTri[uses[0].triIndex] += 1;
- oppositeByTri[uses[1].triIndex] += 1;
- }
- }
- return { sameCount, sameByTri, oppositeByTri };
- };
-
- for (let pass = 0; pass < 16; pass++) {
- const stats = orientationStats();
- if (stats.sameCount === 0) break;
- let flippedThisPass = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (stats.sameByTri[triIndex] <= stats.oppositeByTri[triIndex]) continue;
- flipTriangle(triIndex);
- changed += 1;
- flippedThisPass += 1;
- }
- if (!flippedThisPass) break;
- }
-
- for (let pass = 0; pass < 64; pass++) {
- let stats = orientationStats();
- if (stats.sameCount === 0) break;
- const candidates = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (!stats.sameByTri[triIndex]) continue;
- candidates.push({
- triIndex,
- score: (stats.sameByTri[triIndex] * 2) - stats.oppositeByTri[triIndex],
+ return hasOccShape(solid) ? solid : null;
+}
+
+function cloneBoundaryLoopsWorld(face) {
+ const loops = Array.isArray(face?.userData?.boundaryLoopsWorld) && face.userData.boundaryLoopsWorld.length
+ ? face.userData.boundaryLoopsWorld
+ : null;
+ if (!loops) return null;
+ return loops
+ .map((loop) => ({
+ pts: (Array.isArray(loop?.pts) ? loop.pts : loop)
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]),
+ isHole: !!loop?.isHole,
+ segmentIds: [],
+ }))
+ .filter((loop) => loop.pts.length >= 3);
+}
+
+function cloneSketchEdgeInputsWorld(face, sourceFaceName, loops) {
+ const inputs = Array.isArray(face?.userData?.sketchEdgeInputsWorld)
+ ? face.userData.sketchEdgeInputsWorld
+ : null;
+ if (inputs && inputs.length) {
+ return inputs
+ .map((edge, index) => ({
+ ...edge,
+ name: edge?.name || `${sourceFaceName}_E${index}_SW`,
+ polyline: (Array.isArray(edge?.polyline) ? edge.polyline : [])
+ .filter((point) => Array.isArray(point) && point.length >= 3)
+ .map((point) => [Number(point[0]) || 0, Number(point[1]) || 0, Number(point[2]) || 0]),
+ bezierPoles: Array.isArray(edge?.bezierPoles) ? edge.bezierPoles.map((point) => point.slice()) : edge?.bezierPoles,
+ circleCenter: Array.isArray(edge?.circleCenter) ? edge.circleCenter.slice() : edge?.circleCenter,
+ arcCenter: Array.isArray(edge?.arcCenter) ? edge.arcCenter.slice() : edge?.arcCenter,
+ }))
+ .filter((edge) => Array.isArray(edge.polyline) && edge.polyline.length >= 2);
+ }
+
+ const edgeInputs = [];
+ for (let loopIndex = 0; loopIndex < (loops || []).length; loopIndex += 1) {
+ const pts = loops[loopIndex]?.pts || [];
+ for (let edgeIndex = 0; edgeIndex < pts.length; edgeIndex += 1) {
+ const label = loopIndex === 0
+ ? `${sourceFaceName}_E${edgeIndex}_SW`
+ : `${sourceFaceName}_L${loopIndex}_E${edgeIndex}_SW`;
+ edgeInputs.push({
+ name: label,
+ polyline: [pts[edgeIndex], pts[(edgeIndex + 1) % pts.length]],
+ metadataJson: JSON.stringify({
+ faceType: 'SIDEWALL',
+ type: 'sidewall',
+ sourceFaceName,
+ loopIndex,
+ edgeIndex,
+ }),
});
}
- candidates.sort((a, b) => b.score - a.score || a.triIndex - b.triIndex);
-
- let improvedThisPass = false;
- for (const candidate of candidates) {
- stats = orientationStats();
- if (stats.sameCount === 0 || !stats.sameByTri[candidate.triIndex]) break;
- const before = stats.sameCount;
- flipTriangle(candidate.triIndex);
- const after = orientationStats().sameCount;
- if (after < before) {
- changed += 1;
- improvedThisPass = true;
- } else {
- flipTriangle(candidate.triIndex);
- }
- }
- if (!improvedThisPass) break;
- }
-
- if (changed > 0) {
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- }
- return changed;
-}
-
-function orientSolidComponentsBySignedVolume(solid) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || vp.length < 9) return 0;
-
- const edgeUses = new Map();
- const addUse = (a, b, triIndex) => {
- const key = edgeKey(a, b);
- let list = edgeUses.get(key);
- if (!list) {
- list = [];
- edgeUses.set(key, list);
- }
- list.push(triIndex);
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addUse(a, b, triIndex);
- addUse(b, c, triIndex);
- addUse(c, a, triIndex);
- }
-
- const adjacency = Array.from({ length: triCount }, () => []);
- for (const uses of edgeUses.values()) {
- if (uses.length !== 2) continue;
- adjacency[uses[0]].push(uses[1]);
- adjacency[uses[1]].push(uses[0]);
- }
-
- const signedVolume = (triIndex) => {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- const ax = vp[(a * 3) + 0], ay = vp[(a * 3) + 1], az = vp[(a * 3) + 2];
- const bx = vp[(b * 3) + 0], by = vp[(b * 3) + 1], bz = vp[(b * 3) + 2];
- const cx = vp[(c * 3) + 0], cy = vp[(c * 3) + 1], cz = vp[(c * 3) + 2];
- return (
- (ax * ((by * cz) - (bz * cy)))
- - (ay * ((bx * cz) - (bz * cx)))
- + (az * ((bx * cy) - (by * cx)))
- ) / 6;
- };
-
- const visited = new Uint8Array(triCount);
- let changed = 0;
- for (let seed = 0; seed < triCount; seed++) {
- if (visited[seed]) continue;
- const component = [];
- const stack = [seed];
- visited[seed] = 1;
- let volume = 0;
- while (stack.length) {
- const triIndex = stack.pop();
- component.push(triIndex);
- volume += signedVolume(triIndex);
- for (const next of adjacency[triIndex]) {
- if (visited[next]) continue;
- visited[next] = 1;
- stack.push(next);
- }
- }
- if (!(volume < -TRI_EPS)) continue;
- for (const triIndex of component) {
- const base = triIndex * 3;
- const tmp = tv[base + 1];
- tv[base + 1] = tv[base + 2];
- tv[base + 2] = tmp;
- changed += 1;
- }
- }
-
- if (changed > 0) {
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- }
- return changed;
-}
-
-function weldSolidVerticesByPosition(solid, epsilon) {
- const eps = Math.max(Number(epsilon) || 0, 0);
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const ids = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const vertexCount = (vp.length / 3) | 0;
- const triCount = (tv.length / 3) | 0;
- if (!(eps > 0) || !vertexCount || !triCount || ids.length < triCount) {
- return { weldedVertexCount: 0, removedTriangleCount: 0 };
- }
-
- const inv = 1 / eps;
- const keyForIndex = (index) => [
- Math.round((Number(vp[(index * 3) + 0]) || 0) * inv),
- Math.round((Number(vp[(index * 3) + 1]) || 0) * inv),
- Math.round((Number(vp[(index * 3) + 2]) || 0) * inv),
- ].join(',');
- const oldToNew = new Int32Array(vertexCount);
- oldToNew.fill(-1);
- const keyToNew = new Map();
- const newVertProperties = [];
- let weldedVertexCount = 0;
- for (let i = 0; i < vertexCount; i++) {
- const key = keyForIndex(i);
- let mapped = keyToNew.get(key);
- if (mapped == null) {
- mapped = (newVertProperties.length / 3) | 0;
- keyToNew.set(key, mapped);
- newVertProperties.push(vp[(i * 3) + 0], vp[(i * 3) + 1], vp[(i * 3) + 2]);
- } else {
- weldedVertexCount += 1;
- }
- oldToNew[i] = mapped;
- }
- if (!weldedVertexCount) return { weldedVertexCount: 0, removedTriangleCount: 0 };
-
- const areaByIndex = (a, b, c) => {
- const ax = newVertProperties[(a * 3) + 0], ay = newVertProperties[(a * 3) + 1], az = newVertProperties[(a * 3) + 2];
- const bx = newVertProperties[(b * 3) + 0], by = newVertProperties[(b * 3) + 1], bz = newVertProperties[(b * 3) + 2];
- const cx = newVertProperties[(c * 3) + 0], cy = newVertProperties[(c * 3) + 1], cz = newVertProperties[(c * 3) + 2];
- const ux = bx - ax, uy = by - ay, uz = bz - az;
- const vx = cx - ax, vy = cy - ay, vz = cz - az;
- return 0.5 * Math.hypot((uy * vz) - (uz * vy), (uz * vx) - (ux * vz), (ux * vy) - (uy * vx));
- };
-
- const newTriVerts = [];
- const newTriIDs = [];
- let removedTriangleCount = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = oldToNew[tv[(triIndex * 3) + 0] >>> 0];
- const b = oldToNew[tv[(triIndex * 3) + 1] >>> 0];
- const c = oldToNew[tv[(triIndex * 3) + 2] >>> 0];
- if (a < 0 || b < 0 || c < 0 || a === b || b === c || c === a || !(areaByIndex(a, b, c) > TRI_EPS)) {
- removedTriangleCount += 1;
- continue;
- }
- newTriVerts.push(a, b, c);
- newTriIDs.push(ids[triIndex]);
- }
-
- solid._vertProperties = newVertProperties;
- solid._triVerts = newTriVerts;
- solid._triIDs = newTriIDs;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < newVertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${newVertProperties[i]},${newVertProperties[i + 1]},${newVertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- return { weldedVertexCount, removedTriangleCount };
-}
-
-function extractFaceSurface(face, options = {}) {
- if (!face?.geometry) {
- throw new Error('Face.thicken() requires a face with geometry.');
- }
- try { face.updateMatrixWorld?.(true); } catch { /* ignore */ }
-
- const geometry = face.geometry;
- const position = geometry.getAttribute?.('position');
- const index = geometry.getIndex?.() || null;
- if (!position || position.itemSize !== 3 || position.count < 3) {
- throw new Error('Face.thicken() requires a triangulated face geometry.');
- }
-
- const rawPoints = [];
- const tmp = new THREE.Vector3();
- let minX = Infinity;
- let minY = Infinity;
- let minZ = Infinity;
- let maxX = -Infinity;
- let maxY = -Infinity;
- let maxZ = -Infinity;
- for (let i = 0; i < position.count; i++) {
- tmp.set(position.getX(i), position.getY(i), position.getZ(i)).applyMatrix4(face.matrixWorld);
- rawPoints.push(tmp.clone());
- if (tmp.x < minX) minX = tmp.x;
- if (tmp.y < minY) minY = tmp.y;
- if (tmp.z < minZ) minZ = tmp.z;
- if (tmp.x > maxX) maxX = tmp.x;
- if (tmp.y > maxY) maxY = tmp.y;
- if (tmp.z > maxZ) maxZ = tmp.z;
- }
- const scale = Math.max(1, Math.hypot(maxX - minX, maxY - minY, maxZ - minZ));
- const weldTolerance = Math.max(
- Number(options.weldTolerance) || 0,
- Math.max(1e-6, scale * 1e-7),
- );
-
- const canonicalMap = new Map();
- const canonicalAcc = [];
- const rawToCanonical = new Array(rawPoints.length);
- for (let i = 0; i < rawPoints.length; i++) {
- const point = rawPoints[i];
- const key = pointKey(point, weldTolerance);
- let canonicalIndex = canonicalMap.get(key);
- if (canonicalIndex == null) {
- canonicalIndex = canonicalAcc.length;
- canonicalMap.set(key, canonicalIndex);
- canonicalAcc.push({ point: point.clone(), count: 1, key });
- } else {
- canonicalAcc[canonicalIndex].point.add(point);
- canonicalAcc[canonicalIndex].count += 1;
- }
- rawToCanonical[i] = canonicalIndex;
- }
-
- let vertices = canonicalAcc.map((entry) => entry.point.multiplyScalar(1 / entry.count));
- let vertexKeys = canonicalAcc.map((entry) => entry.key);
- let triangles = [];
- const triCount = index ? ((index.count / 3) | 0) : ((position.count / 3) | 0);
- for (let t = 0; t < triCount; t++) {
- const i0 = index ? (index.getX((t * 3) + 0) >>> 0) : ((t * 3) + 0);
- const i1 = index ? (index.getX((t * 3) + 1) >>> 0) : ((t * 3) + 1);
- const i2 = index ? (index.getX((t * 3) + 2) >>> 0) : ((t * 3) + 2);
- const a = rawToCanonical[i0] >>> 0;
- const b = rawToCanonical[i1] >>> 0;
- const c = rawToCanonical[i2] >>> 0;
- if (a === b || b === c || c === a) continue;
- const area = triangleArea(vertices[a], vertices[b], vertices[c]);
- if (!(area > TRI_EPS)) continue;
- triangles.push([a, b, c]);
- }
- if (!triangles.length) {
- throw new Error('Face.thicken() could not resolve any non-degenerate source triangles.');
- }
-
- const edgeToUses = new Map();
- const triAdjacency = new Array(triangles.length).fill(null).map(() => []);
- const edgeOrientation = (tri, u, v) => {
- for (const [a, b] of [[tri[0], tri[1]], [tri[1], tri[2]], [tri[2], tri[0]]]) {
- if (a === u && b === v) return 1;
- if (a === v && b === u) return -1;
- }
- return 0;
- };
-
- for (let triIndex = 0; triIndex < triangles.length; triIndex++) {
- const tri = triangles[triIndex];
- for (const [u, v] of [[tri[0], tri[1]], [tri[1], tri[2]], [tri[2], tri[0]]]) {
- const key = edgeKey(u, v);
- let uses = edgeToUses.get(key);
- if (!uses) {
- uses = [];
- edgeToUses.set(key, uses);
- }
- uses.push({ triIndex, u, v });
- }
- }
-
- for (const [key, uses] of edgeToUses.entries()) {
- if (!Array.isArray(uses) || uses.length < 2) continue;
- const [uRaw, vRaw] = key.split('|');
- const u = Number(uRaw) >>> 0;
- const v = Number(vRaw) >>> 0;
- for (let i = 0; i < uses.length; i++) {
- for (let j = i + 1; j < uses.length; j++) {
- triAdjacency[uses[i].triIndex].push({ neighbor: uses[j].triIndex, u, v });
- triAdjacency[uses[j].triIndex].push({ neighbor: uses[i].triIndex, u, v });
- }
- }
- }
-
- const triVisited = new Array(triangles.length).fill(false);
- const flipTriangle = (tri) => [tri[0], tri[2], tri[1]];
-
- for (let seed = 0; seed < triangles.length; seed++) {
- if (triVisited[seed]) continue;
- const stack = [seed];
- triVisited[seed] = true;
- while (stack.length) {
- const current = stack.pop();
- const tri = triangles[current];
- for (const adj of triAdjacency[current]) {
- const neighbor = adj.neighbor;
- if (neighbor == null) continue;
- if (!triVisited[neighbor]) {
- const neighborTri = triangles[neighbor];
- const currentOrient = edgeOrientation(tri, adj.u, adj.v);
- const neighborOrient = edgeOrientation(neighborTri, adj.u, adj.v);
- if (currentOrient !== 0 && currentOrient === neighborOrient) {
- triangles[neighbor] = flipTriangle(neighborTri);
- }
- triVisited[neighbor] = true;
- stack.push(neighbor);
- }
- }
- }
- }
-
- const triangleNormals = new Array(triangles.length);
- const vertexNormals = new Array(vertices.length).fill(null).map(() => new THREE.Vector3());
- const averageNormal = new THREE.Vector3();
-
- for (let triIndex = 0; triIndex < triangles.length; triIndex++) {
- const [a, b, c] = triangles[triIndex];
- const normal = triangleNormal(vertices[a], vertices[b], vertices[c]);
- const areaTwice = normal.length();
- if (!(areaTwice > TRI_EPS)) continue;
- const unit = normal.clone().multiplyScalar(1 / areaTwice);
- triangleNormals[triIndex] = unit;
- vertexNormals[a].add(unit.clone().multiplyScalar(areaTwice));
- vertexNormals[b].add(unit.clone().multiplyScalar(areaTwice));
- vertexNormals[c].add(unit.clone().multiplyScalar(areaTwice));
- averageNormal.add(unit.clone().multiplyScalar(areaTwice));
- }
- if (averageNormal.lengthSq() <= TRI_EPS) averageNormal.set(0, 0, 1);
- else averageNormal.normalize();
-
- const boundaryDirectedEdges = [];
- for (const [key, uses] of edgeToUses.entries()) {
- if (uses.length !== 1) continue;
- const use = uses[0];
- boundaryDirectedEdges.push({ key, start: use.u, end: use.v });
- }
-
- const boundaryOutgoing = new Map();
- for (const edge of boundaryDirectedEdges) {
- let list = boundaryOutgoing.get(edge.start);
- if (!list) {
- list = [];
- boundaryOutgoing.set(edge.start, list);
- }
- list.push(edge);
- }
-
- const remainingEdges = new Set(boundaryDirectedEdges.map((edge) => `${edge.start}>${edge.end}`));
- const rawLoops = [];
- const compareEdges = (a, b) => {
- const aKey = `${vertexKeys[a.start]}|${vertexKeys[a.end]}`;
- const bKey = `${vertexKeys[b.start]}|${vertexKeys[b.end]}`;
- return aKey.localeCompare(bKey);
- };
-
- while (remainingEdges.size) {
- const seedEdgeKey = Array.from(remainingEdges.values())
- .sort((a, b) => a.localeCompare(b))[0];
- const [seedStartRaw, seedEndRaw] = seedEdgeKey.split('>');
- const seedStart = Number(seedStartRaw) >>> 0;
- const seedEnd = Number(seedEndRaw) >>> 0;
- const loopEdges = [];
- const loopVertices = [seedStart];
- let start = seedStart;
- let current = seedStart;
- let next = seedEnd;
- while (remainingEdges.has(`${current}>${next}`)) {
- remainingEdges.delete(`${current}>${next}`);
- loopEdges.push({ start: current, end: next, key: edgeKey(current, next) });
- loopVertices.push(next);
- current = next;
- if (current === start) break;
- const candidates = (boundaryOutgoing.get(current) || [])
- .filter((edge) => remainingEdges.has(`${edge.start}>${edge.end}`))
- .sort(compareEdges);
- if (!candidates.length) break;
- next = candidates[0].end;
- }
- if (loopEdges.length) {
- rawLoops.push({ vertices: loopVertices, edges: loopEdges });
- }
- }
-
- const normalizeLoopSignature = (loop) => {
- const verts = Array.isArray(loop?.vertices) ? loop.vertices.slice(0, -1) : [];
- if (!verts.length) return '';
- let best = null;
- for (let offset = 0; offset < verts.length; offset++) {
- const rotated = [];
- for (let i = 0; i < verts.length; i++) {
- rotated.push(vertexKeys[verts[(offset + i) % verts.length]] || `${verts[(offset + i) % verts.length]}`);
- }
- const signature = rotated.join('>');
- if (best == null || signature < best) best = signature;
- }
- return best || '';
- };
-
- const loops = rawLoops
- .map((loop) => ({ ...loop, signature: normalizeLoopSignature(loop) }))
- .sort((a, b) => a.signature.localeCompare(b.signature));
-
- const boundaryEdgeToLoop = new Map();
- for (let loopIndex = 0; loopIndex < loops.length; loopIndex++) {
- const loop = loops[loopIndex];
- for (const edge of loop.edges) {
- boundaryEdgeToLoop.set(edge.key, loopIndex);
- }
- }
-
- const adjacentNormalStats = addSmoothAdjacentBoundaryNormals(face, {
- vertices,
- triangles,
- vertexNormals,
- averageNormal,
- loops,
- boundaryEdgeToLoop,
- boundaryDirectedEdges,
- scale,
- weldTolerance,
- }, options);
-
- for (let i = 0; i < vertexNormals.length; i++) {
- if (vertexNormals[i].lengthSq() <= TRI_EPS) {
- vertexNormals[i].copy(averageNormal);
- }
- if (vertexNormals[i].lengthSq() <= TRI_EPS) {
- vertexNormals[i].set(0, 0, 1);
- } else {
- vertexNormals[i].normalize();
- }
- }
-
- return {
- vertices,
- triangles,
- triangleNormals,
- vertexNormals,
- averageNormal,
- loops,
- boundaryEdgeToLoop,
- boundaryDirectedEdges,
- adjacentNormalStats,
- scale,
- weldTolerance,
- };
-}
-
-function extractFacesSurface(faces, options = {}) {
- const faceList = Array.isArray(faces)
- ? faces.filter((face) => face?.geometry)
- : [];
- if (!faceList.length) {
- throw new Error('Face.thicken() requires at least one face with geometry.');
- }
-
- const rawPositions = [];
- const rawIndices = [];
- const tmp = new THREE.Vector3();
-
- for (const face of faceList) {
- try { face.updateMatrixWorld?.(true); } catch { /* ignore */ }
- const geometry = face.geometry;
- const position = geometry.getAttribute?.('position');
- const index = geometry.getIndex?.() || null;
- if (!position || position.itemSize !== 3 || position.count < 3) {
- throw new Error('Face.thicken() requires triangulated face geometries.');
- }
-
- const baseVertex = (rawPositions.length / 3) | 0;
- for (let i = 0; i < position.count; i++) {
- tmp
- .set(position.getX(i), position.getY(i), position.getZ(i))
- .applyMatrix4(face.matrixWorld || new THREE.Matrix4());
- rawPositions.push(tmp.x, tmp.y, tmp.z);
- }
-
- const triCount = index ? ((index.count / 3) | 0) : ((position.count / 3) | 0);
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const i0 = index ? (index.getX((triIndex * 3) + 0) >>> 0) : ((triIndex * 3) + 0);
- const i1 = index ? (index.getX((triIndex * 3) + 1) >>> 0) : ((triIndex * 3) + 1);
- const i2 = index ? (index.getX((triIndex * 3) + 2) >>> 0) : ((triIndex * 3) + 2);
- rawIndices.push(baseVertex + i0, baseVertex + i1, baseVertex + i2);
- }
- }
-
- if (rawIndices.length < 3) {
- throw new Error('Face.thicken() could not resolve any source triangles.');
- }
-
- const geometry = new THREE.BufferGeometry();
- geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(rawPositions), 3));
- geometry.setIndex(rawIndices);
-
- const fakeFace = {
- geometry,
- matrixWorld: new THREE.Matrix4(),
- updateMatrixWorld() {},
- };
- return extractFaceSurface(fakeFace, options);
-}
-
-function buildThickenClassificationState(labels, distance) {
- const groups = [
- {
- label: labels.start,
- kind: 'start',
- metadata: {
- type: 'start_cap',
- sourceFaceName: labels.sourceFaceName,
- distance,
- },
- },
- {
- label: labels.end,
- kind: 'end',
- metadata: {
- type: 'end_cap',
- sourceFaceName: labels.sourceFaceName,
- distance,
- },
- },
- ...labels.sidewalls.map((entry) => ({
- label: entry.label,
- kind: 'sidewall',
- metadata: {
- type: 'sidewall',
- sourceFaceName: labels.sourceFaceName,
- loopIndex: entry.loopIndex,
- edgeIndex: entry.edgeIndex,
- edgeKey: entry.key,
- distance,
- },
- })),
- ];
-
- const faceNameToID = new Map();
- const idToFaceName = new Map();
- const faceMetadataJson = [];
- let nextID = 1;
- try {
- if (typeof Manifold?.reserveIDs === 'function') {
- nextID = Number(Manifold.reserveIDs(groups.length)) || 1;
- }
- } catch {
- nextID = 1;
- }
- for (const group of groups) {
- const id = nextID >>> 0;
- nextID += 1;
- faceNameToID.set(group.label, id);
- idToFaceName.set(id, group.label);
- faceMetadataJson.push([group.label, JSON.stringify(group.metadata || {})]);
- }
-
- return {
- labels,
- groups,
- faceNameToID,
- idToFaceName,
- faceMetadataJson,
- edgeKeyToLabel: new Map(labels.sidewalls.map((entry) => [entry.key, entry.label])),
- };
-}
-
-function buildSolidFromTriangleMesh(mesh, classification, name) {
- const triVerts = Array.from(mesh?.triVerts ?? [], (value) => Number(value) >>> 0);
- const triCount = (triVerts.length / 3) | 0;
- if (!triCount) return null;
-
- const faceID = Array.from(classification?.triIDs ?? mesh?.faceID ?? [], (value) => Number(value) >>> 0);
- const fallbackFaceID = Number(classification?.faceNameToID?.values?.()?.next?.()?.value) >>> 0;
- const triIDs = faceID.length === triCount
- ? faceID
- : new Array(triCount).fill(fallbackFaceID || 1);
-
- const solid = new Solid();
- solid._numProp = Number(mesh?.numProp ?? 3) || 3;
- solid._vertProperties = Array.from(mesh?.vertProperties ?? [], (value) => Number(value) || 0);
- solid._triVerts = triVerts;
- solid._triIDs = triIDs;
- solid._faceNameToID = classification?.faceNameToID instanceof Map
- ? new Map(classification.faceNameToID)
- : new Map();
- solid._idToFaceName = classification?.idToFaceName instanceof Map
- ? new Map(classification.idToFaceName)
- : new Map();
- solid._faceMetadata = new Map();
- for (const [faceName, metadataJson] of classification?.faceMetadataJson || []) {
- if (!faceName) continue;
- try {
- solid._faceMetadata.set(String(faceName), JSON.parse(metadataJson || '{}') || {});
- } catch {
- solid._faceMetadata.set(String(faceName), {});
- }
- }
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < solid._vertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${solid._vertProperties[i]},${solid._vertProperties[i + 1]},${solid._vertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- try { solid.name = name || solid.name; } catch { /* ignore */ }
- solid._dirty = true;
- solid._manifold = null;
- solid._faceIndex = null;
- return solid;
-}
-
-function buildRawClassification(classificationState, triIDs, method = 'raw_face_ids') {
- return {
- triIDs: Array.from(triIDs || [], (rawID) => Number(rawID) >>> 0),
- faceNameToID: classificationState?.faceNameToID instanceof Map
- ? classificationState.faceNameToID
- : new Map(),
- idToFaceName: classificationState?.idToFaceName instanceof Map
- ? classificationState.idToFaceName
- : new Map(),
- faceMetadataJson: Array.from(classificationState?.faceMetadataJson || []),
- groups: Array.isArray(classificationState?.groups) ? classificationState.groups : [],
- method,
- };
-}
-
-function buildStitchedThickenMesh(surface, distance, classificationState) {
- const vertexCount = Array.isArray(surface?.vertices) ? surface.vertices.length : 0;
- if (!vertexCount) return null;
-
- const vertProperties = new Float32Array(vertexCount * 2 * 3);
- for (let i = 0; i < vertexCount; i++) {
- const p = surface.vertices[i];
- const q = p.clone().add(surface.vertexNormals[i].clone().multiplyScalar(distance));
- vertProperties[(i * 3) + 0] = p.x;
- vertProperties[(i * 3) + 1] = p.y;
- vertProperties[(i * 3) + 2] = p.z;
- const qi = vertexCount + i;
- vertProperties[(qi * 3) + 0] = q.x;
- vertProperties[(qi * 3) + 1] = q.y;
- vertProperties[(qi * 3) + 2] = q.z;
- }
-
- const startFaceID = Number(classificationState?.faceNameToID?.get?.(classificationState?.labels?.start)) >>> 0;
- const endFaceID = Number(classificationState?.faceNameToID?.get?.(classificationState?.labels?.end)) >>> 0;
- const triVerts = [];
- const triIDs = [];
- const addTriangle = (i0, i1, i2, faceID) => {
- if (i0 === i1 || i1 === i2 || i2 === i0) return;
- const a = new THREE.Vector3(
- vertProperties[(i0 * 3) + 0],
- vertProperties[(i0 * 3) + 1],
- vertProperties[(i0 * 3) + 2],
- );
- const b = new THREE.Vector3(
- vertProperties[(i1 * 3) + 0],
- vertProperties[(i1 * 3) + 1],
- vertProperties[(i1 * 3) + 2],
- );
- const c = new THREE.Vector3(
- vertProperties[(i2 * 3) + 0],
- vertProperties[(i2 * 3) + 1],
- vertProperties[(i2 * 3) + 2],
- );
- if (!(triangleArea(a, b, c) > TRI_EPS)) return;
- triVerts.push(i0 >>> 0, i1 >>> 0, i2 >>> 0);
- triIDs.push(Number(faceID) >>> 0);
- };
-
- for (const tri of surface.triangles || []) {
- const [a, b, c] = tri;
- if (distance >= 0) {
- addTriangle(a, c, b, startFaceID);
- addTriangle(vertexCount + a, vertexCount + b, vertexCount + c, endFaceID);
- } else {
- addTriangle(a, b, c, startFaceID);
- addTriangle(vertexCount + a, vertexCount + c, vertexCount + b, endFaceID);
- }
- }
-
- for (let loopIndex = 0; loopIndex < (surface.loops?.length || 0); loopIndex++) {
- const loop = surface.loops[loopIndex];
- for (const edge of loop?.edges || []) {
- const u = edge.start >>> 0;
- const v = edge.end >>> 0;
- const qu = vertexCount + u;
- const qv = vertexCount + v;
- const sideLabel = classificationState?.edgeKeyToLabel?.get?.(edge.key);
- const sideFaceID = Number(classificationState?.faceNameToID?.get?.(sideLabel)) >>> 0;
- if (distance >= 0) {
- addTriangle(u, v, qv, sideFaceID);
- addTriangle(u, qv, qu, sideFaceID);
- } else {
- addTriangle(qu, qv, v, sideFaceID);
- addTriangle(qu, v, u, sideFaceID);
- }
- }
- }
-
- return {
- numProp: 3,
- vertProperties,
- triVerts: Uint32Array.from(triVerts),
- faceID: Uint32Array.from(triIDs),
- };
-}
-
-function buildStitchedShellSolid(surface, distance, classificationState, solidName) {
- const rawMesh = buildStitchedThickenMesh(surface, distance, classificationState);
- if (!rawMesh) return null;
- const rawClassification = buildRawClassification(classificationState, rawMesh.faceID, 'stitched_shell');
- return buildSolidFromTriangleMesh(rawMesh, rawClassification, solidName);
-}
-
-function buildTrianglePrismUnionThickenMesh(surface, distance, classificationState) {
- const vertices = Array.isArray(surface?.vertices) ? surface.vertices : [];
- const triangles = Array.isArray(surface?.triangles) ? surface.triangles : [];
- if (!vertices.length || !triangles.length) return null;
-
- const startFaceID = Number(classificationState?.faceNameToID?.get?.(classificationState?.labels?.start)) >>> 0;
- const endFaceID = Number(classificationState?.faceNameToID?.get?.(classificationState?.labels?.end)) >>> 0;
- const sidewallFallbackID = Number(
- classificationState?.faceNameToID?.values?.()?.next?.()?.value,
- ) >>> 0;
- const triVerts = [];
- const triIDs = [];
- const vertProperties = [];
-
- const addVertex = (point) => {
- const index = (vertProperties.length / 3) | 0;
- vertProperties.push(point.x, point.y, point.z);
- return index;
- };
- const addTriangleByPoints = (a, b, c, faceID) => {
- if (!a || !b || !c) return;
- if (!(triangleArea(a, b, c) > TRI_EPS)) return;
- const ia = addVertex(a);
- const ib = addVertex(b);
- const ic = addVertex(c);
- triVerts.push(ia, ib, ic);
- triIDs.push(Number(faceID) >>> 0);
- };
- const resolveSideFaceID = (u, v) => {
- const key = edgeKey(u, v);
- const sideLabel = classificationState?.edgeKeyToLabel?.get?.(key);
- return Number(classificationState?.faceNameToID?.get?.(sideLabel)) >>> 0
- || sidewallFallbackID
- || startFaceID
- || endFaceID
- || 1;
- };
-
- for (const tri of triangles) {
- const aIndex = tri?.[0] >>> 0;
- const bIndex = tri?.[1] >>> 0;
- const cIndex = tri?.[2] >>> 0;
- const a = vertices[aIndex];
- const b = vertices[bIndex];
- const c = vertices[cIndex];
- if (!a || !b || !c) continue;
- const normal = triangleNormal(a, b, c);
- const normalLength = normal.length();
- if (!(normalLength > TRI_EPS)) continue;
- normal.multiplyScalar(distance / normalLength);
- const qa = a.clone().add(normal);
- const qb = b.clone().add(normal);
- const qc = c.clone().add(normal);
-
- if (distance >= 0) {
- addTriangleByPoints(a, c, b, startFaceID);
- addTriangleByPoints(qa, qb, qc, endFaceID);
- addTriangleByPoints(a, b, qb, resolveSideFaceID(aIndex, bIndex));
- addTriangleByPoints(a, qb, qa, resolveSideFaceID(aIndex, bIndex));
- addTriangleByPoints(b, c, qc, resolveSideFaceID(bIndex, cIndex));
- addTriangleByPoints(b, qc, qb, resolveSideFaceID(bIndex, cIndex));
- addTriangleByPoints(c, a, qa, resolveSideFaceID(cIndex, aIndex));
- addTriangleByPoints(c, qa, qc, resolveSideFaceID(cIndex, aIndex));
- } else {
- addTriangleByPoints(a, b, c, startFaceID);
- addTriangleByPoints(qa, qc, qb, endFaceID);
- addTriangleByPoints(qa, qb, b, resolveSideFaceID(aIndex, bIndex));
- addTriangleByPoints(qa, b, a, resolveSideFaceID(aIndex, bIndex));
- addTriangleByPoints(qb, qc, c, resolveSideFaceID(bIndex, cIndex));
- addTriangleByPoints(qb, c, b, resolveSideFaceID(bIndex, cIndex));
- addTriangleByPoints(qc, qa, a, resolveSideFaceID(cIndex, aIndex));
- addTriangleByPoints(qc, a, c, resolveSideFaceID(cIndex, aIndex));
- }
- }
-
- if (!triVerts.length) return null;
- return {
- numProp: 3,
- vertProperties: Float32Array.from(vertProperties),
- triVerts: Uint32Array.from(triVerts),
- faceID: Uint32Array.from(triIDs),
- };
-}
-
-function buildTrianglePrismUnionShellSolid(surface, distance, classificationState, solidName) {
- const rawMesh = buildTrianglePrismUnionThickenMesh(surface, distance, classificationState);
- if (!rawMesh) return null;
- const rawClassification = buildRawClassification(classificationState, rawMesh.faceID, 'triangle_prism_union');
- return buildSolidFromTriangleMesh(rawMesh, rawClassification, solidName);
-}
-
-function cullInternalTrianglesByIndexedRaycast(solid, options = {}) {
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const ids = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || vp.length < 9 || ids.length < triCount) return 0;
-
- let minX = Infinity;
- let minY = Infinity;
- let minZ = Infinity;
- let maxX = -Infinity;
- let maxY = -Infinity;
- let maxZ = -Infinity;
- for (let i = 0; i < vp.length; i += 3) {
- const x = vp[i + 0];
- const y = vp[i + 1];
- const z = vp[i + 2];
- if (x < minX) minX = x;
- if (x > maxX) maxX = x;
- if (y < minY) minY = y;
- if (y > maxY) maxY = y;
- if (z < minZ) minZ = z;
- if (z > maxZ) maxZ = z;
- }
- const diag = Math.hypot(maxX - minX, maxY - minY, maxZ - minZ) || 1;
- const eps = Math.max(diag * (Number(options.offsetScale) || 1e-5), 1e-9);
- const rayEps = Math.max(eps * 0.1, diag * 1e-10, 1e-10);
- const gridSize = Math.max(
- 12,
- Math.min(256, Number.isFinite(Number(options.gridSize))
- ? Number(options.gridSize) | 0
- : Math.ceil(Math.sqrt(triCount))),
- );
-
- const triangles = new Array(triCount);
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const i0 = tv[(triIndex * 3) + 0] >>> 0;
- const i1 = tv[(triIndex * 3) + 1] >>> 0;
- const i2 = tv[(triIndex * 3) + 2] >>> 0;
- const a = [vp[(i0 * 3) + 0], vp[(i0 * 3) + 1], vp[(i0 * 3) + 2]];
- const b = [vp[(i1 * 3) + 0], vp[(i1 * 3) + 1], vp[(i1 * 3) + 2]];
- const c = [vp[(i2 * 3) + 0], vp[(i2 * 3) + 1], vp[(i2 * 3) + 2]];
- const ux = b[0] - a[0];
- const uy = b[1] - a[1];
- const uz = b[2] - a[2];
- const vx = c[0] - a[0];
- const vy = c[1] - a[1];
- const vz = c[2] - a[2];
- let nx = (uy * vz) - (uz * vy);
- let ny = (uz * vx) - (ux * vz);
- let nz = (ux * vy) - (uy * vx);
- const normalLength = Math.hypot(nx, ny, nz);
- if (normalLength > TRI_EPS) {
- nx /= normalLength;
- ny /= normalLength;
- nz /= normalLength;
- } else {
- nx = 0;
- ny = 0;
- nz = 0;
- }
- triangles[triIndex] = {
- a,
- b,
- c,
- normal: [nx, ny, nz],
- centroid: [
- (a[0] + b[0] + c[0]) / 3,
- (a[1] + b[1] + c[1]) / 3,
- (a[2] + b[2] + c[2]) / 3,
- ],
- min: [
- Math.min(a[0], b[0], c[0]),
- Math.min(a[1], b[1], c[1]),
- Math.min(a[2], b[2], c[2]),
- ],
- max: [
- Math.max(a[0], b[0], c[0]),
- Math.max(a[1], b[1], c[1]),
- Math.max(a[2], b[2], c[2]),
- ],
- };
- }
-
- const axes = [
- { primary: 0, u: 1, v: 2, min: [minX, minY, minZ], max: [maxX, maxY, maxZ] },
- { primary: 1, u: 2, v: 0, min: [minX, minY, minZ], max: [maxX, maxY, maxZ] },
- { primary: 2, u: 0, v: 1, min: [minX, minY, minZ], max: [maxX, maxY, maxZ] },
- ];
-
- const buildIndex = (axis) => {
- const uMin = axis.min[axis.u];
- const vMin = axis.min[axis.v];
- const uSpan = Math.max(axis.max[axis.u] - uMin, eps);
- const vSpan = Math.max(axis.max[axis.v] - vMin, eps);
- const cells = new Map();
- const cellCoord = (value, min, span) => Math.max(
- 0,
- Math.min(gridSize - 1, Math.floor(((value - min) / span) * gridSize)),
- );
- const addCell = (iu, iv, triIndex) => {
- const key = `${iu}|${iv}`;
- let list = cells.get(key);
- if (!list) {
- list = [];
- cells.set(key, list);
- }
- list.push(triIndex);
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const tri = triangles[triIndex];
- const iu0 = cellCoord(tri.min[axis.u] - eps, uMin, uSpan);
- const iu1 = cellCoord(tri.max[axis.u] + eps, uMin, uSpan);
- const iv0 = cellCoord(tri.min[axis.v] - eps, vMin, vSpan);
- const iv1 = cellCoord(tri.max[axis.v] + eps, vMin, vSpan);
- for (let iu = iu0; iu <= iu1; iu++) {
- for (let iv = iv0; iv <= iv1; iv++) {
- addCell(iu, iv, triIndex);
- }
- }
- }
- return { axis, uMin, vMin, uSpan, vSpan, cells, cellCoord };
- };
-
- const selectedAxes = String(options.axes || '').toLowerCase() === 'majority'
- ? axes
- : [axes[0]];
- const indexes = selectedAxes.map(buildIndex);
- const pointInIndexCell = (index, point) => {
- const iu = index.cellCoord(point[index.axis.u], index.uMin, index.uSpan);
- const iv = index.cellCoord(point[index.axis.v], index.vMin, index.vSpan);
- return index.cells.get(`${iu}|${iv}`) || [];
- };
-
- const rayTriangleHit = (point, axis, tri) => {
- const p = axis.primary;
- const u = axis.u;
- const v = axis.v;
- const ax = tri.a[p] - point[p];
- const ay = tri.a[u] - point[u];
- const az = tri.a[v] - point[v];
- const bx = tri.b[p] - point[p];
- const by = tri.b[u] - point[u];
- const bz = tri.b[v] - point[v];
- const cx = tri.c[p] - point[p];
- const cy = tri.c[u] - point[u];
- const cz = tri.c[v] - point[v];
-
- const e1x = bx - ax;
- const e1y = by - ay;
- const e1z = bz - az;
- const e2x = cx - ax;
- const e2y = cy - ay;
- const e2z = cz - az;
- const det = (e1z * e2y) - (e1y * e2z);
- if (Math.abs(det) <= rayEps) return null;
- const invDet = 1 / det;
- const tx = -ax;
- const ty = -ay;
- const tz = -az;
- const px = 0;
- const py = -e2z;
- const pz = e2y;
- const baryU = ((tx * px) + (ty * py) + (tz * pz)) * invDet;
- if (baryU < -1e-10 || baryU > 1 + 1e-10) return null;
- const qx = (ty * e1z) - (tz * e1y);
- const qy = (tz * e1x) - (tx * e1z);
- const qz = (tx * e1y) - (ty * e1x);
- const baryV = qx * invDet;
- if (baryV < -1e-10 || baryU + baryV > 1 + 1e-10) return null;
- const t = ((e2x * qx) + (e2y * qy) + (e2z * qz)) * invDet;
- return t > rayEps ? t : null;
- };
-
- const inside = (point, index) => {
- const hits = [];
- for (const triIndex of pointInIndexCell(index, point)) {
- const tri = triangles[triIndex];
- if (point[index.axis.primary] > tri.max[index.axis.primary] + eps) continue;
- if (point[index.axis.u] < tri.min[index.axis.u] - eps || point[index.axis.u] > tri.max[index.axis.u] + eps) continue;
- if (point[index.axis.v] < tri.min[index.axis.v] - eps || point[index.axis.v] > tri.max[index.axis.v] + eps) continue;
- const hit = rayTriangleHit(point, index.axis, tri);
- if (hit != null) hits.push(hit);
- }
- if (!hits.length) return false;
- hits.sort((a, b) => a - b);
- let uniqueHits = 0;
- let previous = -Infinity;
- for (const hit of hits) {
- if (Math.abs(hit - previous) <= Math.max(rayEps * 4, 1e-8)) continue;
- uniqueHits += 1;
- previous = hit;
- }
- return (uniqueHits % 2) === 1;
- };
-
- const keepTri = new Uint8Array(triCount);
- keepTri.fill(1);
- let removed = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const tri = triangles[triIndex];
- const n = tri.normal;
- if (Math.hypot(n[0], n[1], n[2]) <= TRI_EPS) continue;
- const pPlus = [
- tri.centroid[0] + (n[0] * eps),
- tri.centroid[1] + (n[1] * eps),
- tri.centroid[2] + (n[2] * eps),
- ];
- const pMinus = [
- tri.centroid[0] - (n[0] * eps),
- tri.centroid[1] - (n[1] * eps),
- tri.centroid[2] - (n[2] * eps),
- ];
- let crossingVotes = 0;
- for (const index of indexes) {
- if (inside(pPlus, index) !== inside(pMinus, index)) crossingVotes += 1;
- }
- if (crossingVotes >= Math.ceil(indexes.length / 2)) continue;
- keepTri[triIndex] = 0;
- removed += 1;
- }
- if (!removed) return 0;
-
- const vertexCount = (vp.length / 3) | 0;
- const usedVert = new Uint8Array(vertexCount);
- const newTriVerts = [];
- const newTriIDs = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (!keepTri[triIndex]) continue;
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- newTriVerts.push(a, b, c);
- newTriIDs.push(ids[triIndex]);
- usedVert[a] = 1;
- usedVert[b] = 1;
- usedVert[c] = 1;
- }
-
- const oldToNew = new Int32Array(vertexCount);
- oldToNew.fill(-1);
- const newVertProperties = [];
- let nextVertex = 0;
- for (let i = 0; i < vertexCount; i++) {
- if (!usedVert[i]) continue;
- oldToNew[i] = nextVertex++;
- newVertProperties.push(vp[(i * 3) + 0], vp[(i * 3) + 1], vp[(i * 3) + 2]);
- }
- for (let i = 0; i < newTriVerts.length; i++) {
- newTriVerts[i] = oldToNew[newTriVerts[i] >>> 0];
- }
-
- solid._vertProperties = newVertProperties;
- solid._triVerts = newTriVerts;
- solid._triIDs = newTriIDs;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < newVertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${newVertProperties[i]},${newVertProperties[i + 1]},${newVertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- try { solid.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- return removed;
-}
-
-function fillBoundaryLoopsWithTriangles(solid, faceID) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || vp.length < 9) return 0;
-
- const edgeUses = new Map();
- const addUse = (a, b) => {
- const key = edgeKey(a, b);
- let entry = edgeUses.get(key);
- if (!entry) {
- entry = { count: 0, directed: [] };
- edgeUses.set(key, entry);
- }
- entry.count += 1;
- entry.directed.push([a, b]);
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addUse(a, b);
- addUse(b, c);
- addUse(c, a);
- }
-
- const outgoing = new Map();
- const undirectedAdjacency = new Map();
- const directedBoundaryByEdge = new Map();
- const boundaryEdges = [];
- const addUndirectedNeighbor = (a, b) => {
- let list = undirectedAdjacency.get(a);
- if (!list) {
- list = [];
- undirectedAdjacency.set(a, list);
- }
- list.push(b);
- };
- for (const entry of edgeUses.values()) {
- if (entry.count !== 1 || !entry.directed.length) continue;
- const [a, b] = entry.directed[0];
- const edge = { a, b, key: `${a}>${b}` };
- boundaryEdges.push(edge);
- directedBoundaryByEdge.set(edgeKey(a, b), [a, b]);
- addUndirectedNeighbor(a, b);
- addUndirectedNeighbor(b, a);
- let list = outgoing.get(a);
- if (!list) {
- list = [];
- outgoing.set(a, list);
- }
- list.push(edge);
- }
- if (!boundaryEdges.length) return 0;
-
- for (const list of outgoing.values()) {
- list.sort((a, b) => a.b - b.b);
- }
-
- const used = new Set();
- const loops = [];
- for (const seed of boundaryEdges) {
- if (used.has(seed.key)) continue;
- const loop = [seed.a];
- let current = seed;
- let guard = 0;
- while (current && !used.has(current.key) && guard++ < boundaryEdges.length + 1) {
- used.add(current.key);
- loop.push(current.b);
- if (current.b === loop[0]) break;
- const next = (outgoing.get(current.b) || []).find((edge) => !used.has(edge.key));
- current = next || null;
- }
- if (loop.length >= 4 && loop[0] === loop[loop.length - 1]) {
- loops.push(loop.slice(0, -1));
- }
- }
-
- const loopSignature = (loop) => {
- if (!Array.isArray(loop) || !loop.length) return '';
- const variants = [];
- const addVariants = (source) => {
- for (let offset = 0; offset < source.length; offset++) {
- const rotated = [];
- for (let i = 0; i < source.length; i++) rotated.push(source[(offset + i) % source.length]);
- variants.push(rotated.join('|'));
- }
- };
- addVariants(loop);
- if (loop.length > 1) {
- addVariants(loop.slice().reverse());
- }
- return variants.sort()[0] || '';
- };
-
- const traceBoundaryGraphLoops = (componentEdges, componentAdjacency) => {
- const vertices = Array.from(componentAdjacency.keys());
- if (vertices.length < 3 || componentEdges.length < 3) return [];
-
- const pointFor = (index) => [
- Number(vp[(index * 3) + 0]) || 0,
- Number(vp[(index * 3) + 1]) || 0,
- Number(vp[(index * 3) + 2]) || 0,
- ];
- const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
- const dot = (a, b) => (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
- const cross = (a, b) => [
- (a[1] * b[2]) - (a[2] * b[1]),
- (a[2] * b[0]) - (a[0] * b[2]),
- (a[0] * b[1]) - (a[1] * b[0]),
- ];
- const length = (a) => Math.hypot(a[0], a[1], a[2]);
- const scaleVec = (a, s) => [a[0] * s, a[1] * s, a[2] * s];
-
- const centroid = [0, 0, 0];
- const points = new Map();
- for (const vertex of vertices) {
- const point = pointFor(vertex);
- points.set(vertex, point);
- centroid[0] += point[0];
- centroid[1] += point[1];
- centroid[2] += point[2];
- }
- centroid[0] /= vertices.length;
- centroid[1] /= vertices.length;
- centroid[2] /= vertices.length;
-
- let u = null;
- let maxDist = -Infinity;
- for (const point of points.values()) {
- const vector = sub(point, centroid);
- const dist = length(vector);
- if (dist > maxDist) {
- maxDist = dist;
- u = vector;
- }
- }
- if (!u || !(maxDist > TRI_EPS)) return [];
- u = scaleVec(u, 1 / maxDist);
-
- let normal = null;
- let maxNormalLength = -Infinity;
- for (const point of points.values()) {
- const candidate = cross(u, sub(point, centroid));
- const candidateLength = length(candidate);
- if (candidateLength > maxNormalLength) {
- maxNormalLength = candidateLength;
- normal = candidate;
- }
- }
- if (!normal || !(maxNormalLength > TRI_EPS)) return [];
- normal = scaleVec(normal, 1 / maxNormalLength);
- const vAxis = cross(normal, u);
- const vLength = length(vAxis);
- if (!(vLength > TRI_EPS)) return [];
- const v = scaleVec(vAxis, 1 / vLength);
-
- const projected = new Map();
- for (const [vertex, point] of points.entries()) {
- const relative = sub(point, centroid);
- projected.set(vertex, [dot(relative, u), dot(relative, v)]);
- }
-
- const sortedAdjacency = new Map();
- for (const [vertex, neighbors] of componentAdjacency.entries()) {
- const p = projected.get(vertex);
- sortedAdjacency.set(vertex, neighbors.slice().sort((a, b) => {
- const pa = projected.get(a);
- const pb = projected.get(b);
- return Math.atan2(pa[1] - p[1], pa[0] - p[0])
- - Math.atan2(pb[1] - p[1], pb[0] - p[0]);
- }));
- }
-
- const area2D = (loop) => {
- let area = 0;
- for (let i = 0; i < loop.length; i++) {
- const a = projected.get(loop[i]);
- const b = projected.get(loop[(i + 1) % loop.length]);
- area += (a[0] * b[1]) - (b[0] * a[1]);
- }
- return area * 0.5;
- };
-
- const directedVisited = new Set();
- const directedKey = (a, b) => `${a}>${b}`;
- const candidateLoops = [];
- for (const [a, b] of componentEdges) {
- for (const start of [[a, b], [b, a]]) {
- let from = start[0];
- let to = start[1];
- const startKey = directedKey(from, to);
- if (directedVisited.has(startKey)) continue;
- const loop = [];
- let closed = false;
- let guard = 0;
- const guardMax = Math.max(12, componentEdges.length * 4);
- while (!directedVisited.has(directedKey(from, to)) && guard++ < guardMax) {
- directedVisited.add(directedKey(from, to));
- loop.push(from);
- const neighbors = sortedAdjacency.get(to) || [];
- const incoming = neighbors.indexOf(from);
- if (incoming < 0 || neighbors.length === 0) break;
- const next = neighbors[(incoming - 1 + neighbors.length) % neighbors.length];
- from = to;
- to = next;
- if (directedKey(from, to) === startKey) {
- closed = true;
- break;
- }
- }
- if (!closed || loop.length < 3) continue;
- const area = area2D(loop);
- if (Math.abs(area) <= TRI_EPS) continue;
- candidateLoops.push({ loop, area });
- }
- }
-
- const positive = candidateLoops.filter((entry) => entry.area > TRI_EPS);
- const selected = positive.length ? positive : candidateLoops.filter((entry) => entry.area < -TRI_EPS);
- const tracedLoops = selected.map((entry) => (entry.area > 0 ? entry.loop : entry.loop.slice().reverse()));
- if (tracedLoops.length || componentEdges.length > 64) return tracedLoops;
-
- const cycleSignatures = new Set();
- const simpleCycles = [];
- const verticesSorted = vertices.slice().sort((a, b) => a - b);
- const canonicalCycleKey = (cycle) => {
- if (!cycle.length) return '';
- const variants = [];
- const add = (source) => {
- for (let offset = 0; offset < source.length; offset++) {
- const rotated = [];
- for (let i = 0; i < source.length; i++) rotated.push(source[(offset + i) % source.length]);
- variants.push(rotated.join('|'));
- }
- };
- add(cycle);
- add(cycle.slice().reverse());
- return variants.sort()[0] || '';
- };
- for (const start of verticesSorted) {
- const stack = [{ current: start, path: [start], visited: new Set([start]) }];
- let dfsSteps = 0;
- while (stack.length && dfsSteps++ < 5000 && simpleCycles.length < componentEdges.length * 2) {
- const state = stack.pop();
- if (state.path.length > componentEdges.length) continue;
- const neighbors = (componentAdjacency.get(state.current) || []).slice().sort((a, b) => a - b);
- for (const neighbor of neighbors) {
- if (neighbor === start && state.path.length >= 3) {
- const key = canonicalCycleKey(state.path);
- if (key && !cycleSignatures.has(key)) {
- cycleSignatures.add(key);
- simpleCycles.push(state.path.slice());
- }
- continue;
- }
- if (neighbor < start || state.visited.has(neighbor)) continue;
- const nextVisited = new Set(state.visited);
- nextVisited.add(neighbor);
- stack.push({ current: neighbor, path: state.path.concat(neighbor), visited: nextVisited });
- }
- }
- }
- simpleCycles.sort((a, b) => a.length - b.length);
- const usedCycleEdges = new Set();
- const edgeDisjointCycles = [];
- for (const cycle of simpleCycles) {
- const cycleEdges = [];
- let overlaps = false;
- for (let i = 0; i < cycle.length; i++) {
- const key = edgeKey(cycle[i], cycle[(i + 1) % cycle.length]);
- cycleEdges.push(key);
- if (usedCycleEdges.has(key)) overlaps = true;
- }
- if (overlaps) continue;
- for (const key of cycleEdges) usedCycleEdges.add(key);
- edgeDisjointCycles.push(cycle);
- }
- return edgeDisjointCycles.slice(0, componentEdges.length);
- };
-
- const loopSignatures = new Set(loops.map(loopSignature));
- const undirectedVisited = new Set();
- const edgeFans = [];
- for (const edge of boundaryEdges) {
- const seedKey = edgeKey(edge.a, edge.b);
- if (undirectedVisited.has(seedKey)) continue;
- const componentEdges = [];
- const stack = [[edge.a, edge.b]];
- undirectedVisited.add(seedKey);
- while (stack.length) {
- const [a, b] = stack.pop();
- componentEdges.push([a, b]);
- for (const vertex of [a, b]) {
- for (const neighbor of undirectedAdjacency.get(vertex) || []) {
- const key = edgeKey(vertex, neighbor);
- if (undirectedVisited.has(key)) continue;
- undirectedVisited.add(key);
- stack.push([vertex, neighbor]);
- }
- }
- }
- if (componentEdges.length < 3) continue;
-
- const componentAdjacency = new Map();
- const addComponentNeighbor = (a, b) => {
- let list = componentAdjacency.get(a);
- if (!list) {
- list = [];
- componentAdjacency.set(a, list);
- }
- if (!list.includes(b)) list.push(b);
- };
- for (const [a, b] of componentEdges) {
- addComponentNeighbor(a, b);
- addComponentNeighbor(b, a);
- }
- if (!Array.from(componentAdjacency.values()).every((neighbors) => neighbors.length === 2)) {
- let addedGraphLoop = false;
- for (const loop of traceBoundaryGraphLoops(componentEdges, componentAdjacency)) {
- const signature = loopSignature(loop);
- if (!signature || loopSignatures.has(signature)) continue;
- loopSignatures.add(signature);
- loops.push(loop);
- addedGraphLoop = true;
- }
- if (!addedGraphLoop) edgeFans.push(componentEdges);
- continue;
- }
-
- const start = Math.min(...Array.from(componentAdjacency.keys()));
- const firstNeighbors = (componentAdjacency.get(start) || []).slice().sort((a, b) => a - b);
- if (firstNeighbors.length !== 2) {
- edgeFans.push(componentEdges);
- continue;
- }
- const ordered = [start];
- let previous = -1;
- let current = start;
- let next = firstNeighbors[0];
- let closed = false;
- let guard = 0;
- while (guard++ < componentEdges.length + 2) {
- ordered.push(next);
- previous = current;
- current = next;
- if (current === start) {
- closed = true;
- break;
- }
- const candidates = (componentAdjacency.get(current) || []).filter((neighbor) => neighbor !== previous);
- if (candidates.length !== 1) break;
- next = candidates[0];
- }
- if (!closed || ordered.length < 4) {
- edgeFans.push(componentEdges);
- continue;
- }
- const loop = ordered.slice(0, -1);
- if (loop.length !== componentEdges.length) {
- edgeFans.push(componentEdges);
- continue;
- }
- const signature = loopSignature(loop);
- if (!signature || loopSignatures.has(signature)) continue;
- loopSignatures.add(signature);
- loops.push(loop);
- }
-
- // Only closed boundary loops can be capped without inventing topology. Open
- // or branching boundary components indicate the split arrangement is not yet
- // conforming and should be handled by another split/cull repair pass.
- edgeFans.length = 0;
-
- if (!loops.length && !edgeFans.length) return 0;
-
- const triangulateLoop = (loop) => {
- if (!Array.isArray(loop) || loop.length < 3) return [];
- const points = loop.map((index) => [
- Number(vp[(index * 3) + 0]) || 0,
- Number(vp[(index * 3) + 1]) || 0,
- Number(vp[(index * 3) + 2]) || 0,
- ]);
- const centroid = [0, 0, 0];
- for (const point of points) {
- centroid[0] += point[0];
- centroid[1] += point[1];
- centroid[2] += point[2];
- }
- centroid[0] /= points.length;
- centroid[1] /= points.length;
- centroid[2] /= points.length;
-
- let normal = [0, 0, 0];
- for (let i = 0; i < points.length; i++) {
- const a = points[i];
- const b = points[(i + 1) % points.length];
- normal[0] += (a[1] - b[1]) * (a[2] + b[2]);
- normal[1] += (a[2] - b[2]) * (a[0] + b[0]);
- normal[2] += (a[0] - b[0]) * (a[1] + b[1]);
- }
- let normalLength = Math.hypot(normal[0], normal[1], normal[2]);
- if (!(normalLength > TRI_EPS)) {
- let bestLength = 0;
- for (let i = 0; i < points.length; i++) {
- const a = points[i];
- const b = points[(i + 1) % points.length];
- const c = points[(i + 2) % points.length];
- const ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
- const ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
- const candidate = [
- (ab[1] * ac[2]) - (ab[2] * ac[1]),
- (ab[2] * ac[0]) - (ab[0] * ac[2]),
- (ab[0] * ac[1]) - (ab[1] * ac[0]),
- ];
- const length = Math.hypot(candidate[0], candidate[1], candidate[2]);
- if (length > bestLength) {
- bestLength = length;
- normal = candidate;
- }
- }
- normalLength = bestLength;
- }
- if (!(normalLength > TRI_EPS)) return [];
- normal = normal.map((value) => value / normalLength);
-
- let u = null;
- let maxDistance = 0;
- for (const point of points) {
- const candidate = [
- point[0] - centroid[0],
- point[1] - centroid[1],
- point[2] - centroid[2],
- ];
- const distance = Math.hypot(candidate[0], candidate[1], candidate[2]);
- if (distance > maxDistance) {
- maxDistance = distance;
- u = candidate;
- }
- }
- if (!u || !(maxDistance > TRI_EPS)) return [];
- u = u.map((value) => value / maxDistance);
- const v = [
- (normal[1] * u[2]) - (normal[2] * u[1]),
- (normal[2] * u[0]) - (normal[0] * u[2]),
- (normal[0] * u[1]) - (normal[1] * u[0]),
- ];
- const vLength = Math.hypot(v[0], v[1], v[2]);
- if (!(vLength > TRI_EPS)) return [];
- v[0] /= vLength;
- v[1] /= vLength;
- v[2] /= vLength;
-
- const projected = points.map((point) => {
- const r = [
- point[0] - centroid[0],
- point[1] - centroid[1],
- point[2] - centroid[2],
- ];
- return [
- (r[0] * u[0]) + (r[1] * u[1]) + (r[2] * u[2]),
- (r[0] * v[0]) + (r[1] * v[1]) + (r[2] * v[2]),
- ];
- });
- const area2D = (indices) => {
- let area = 0;
- for (let i = 0; i < indices.length; i++) {
- const a = projected[indices[i]];
- const b = projected[indices[(i + 1) % indices.length]];
- area += (a[0] * b[1]) - (b[0] * a[1]);
- }
- return area * 0.5;
- };
- const cross2D = (a, b, c) => {
- const pa = projected[a];
- const pb = projected[b];
- const pc = projected[c];
- return ((pb[0] - pa[0]) * (pc[1] - pb[1])) - ((pb[1] - pa[1]) * (pc[0] - pb[0]));
- };
- const pointInTri = (pIndex, aIndex, bIndex, cIndex) => {
- const p = projected[pIndex];
- const a = projected[aIndex];
- const b = projected[bIndex];
- const c = projected[cIndex];
- const area = Math.abs(((b[0] - a[0]) * (c[1] - a[1])) - ((b[1] - a[1]) * (c[0] - a[0])));
- if (!(area > TRI_EPS)) return false;
- const a0 = Math.abs(((a[0] - p[0]) * (b[1] - p[1])) - ((a[1] - p[1]) * (b[0] - p[0])));
- const a1 = Math.abs(((b[0] - p[0]) * (c[1] - p[1])) - ((b[1] - p[1]) * (c[0] - p[0])));
- const a2 = Math.abs(((c[0] - p[0]) * (a[1] - p[1])) - ((c[1] - p[1]) * (a[0] - p[0])));
- return Math.abs((a0 + a1 + a2) - area) <= Math.max(TRI_EPS, area * 1e-8);
- };
-
- const indices = Array.from({ length: loop.length }, (_, index) => index);
- if (area2D(indices) < 0) indices.reverse();
- const out = [];
- let guard = 0;
- while (indices.length > 3 && guard++ < loop.length * loop.length * 4) {
- let clipped = false;
- for (let i = 0; i < indices.length; i++) {
- const prev = indices[(i - 1 + indices.length) % indices.length];
- const current = indices[i];
- const next = indices[(i + 1) % indices.length];
- if (cross2D(prev, current, next) <= TRI_EPS) continue;
- let contains = false;
- for (const candidate of indices) {
- if (candidate === prev || candidate === current || candidate === next) continue;
- if (pointInTri(candidate, prev, current, next)) {
- contains = true;
- break;
- }
- }
- if (contains) continue;
- out.push([loop[prev], loop[current], loop[next]]);
- indices.splice(i, 1);
- clipped = true;
- break;
- }
- if (!clipped) break;
- }
- if (indices.length === 3) {
- out.push([loop[indices[0]], loop[indices[1]], loop[indices[2]]]);
- }
- if (out.length) return out;
- const fallback = [];
- for (let i = 1; i + 1 < loop.length; i++) {
- fallback.push([loop[0], loop[i], loop[i + 1]]);
- }
- return fallback;
- };
-
- const addVertex = (point) => {
- const index = (solid._vertProperties.length / 3) | 0;
- solid._vertProperties.push(point[0], point[1], point[2]);
- solid._vertKeyToIndex?.set?.(`${point[0]},${point[1]},${point[2]}`, index);
- return index;
- };
-
- let added = 0;
- const capFaceID = Number(faceID) >>> 0;
- for (const loop of loops) {
- if (loop.length < 3) continue;
- let directedMatches = 0;
- for (let i = 0; i < loop.length; i++) {
- const a = loop[i];
- const b = loop[(i + 1) % loop.length];
- const directed = directedBoundaryByEdge.get(edgeKey(a, b));
- if (directed && directed[0] === a && directed[1] === b) directedMatches += 1;
- }
- const reverseFan = directedMatches >= (loop.length / 2);
- for (const tri of triangulateLoop(loop)) {
- const a = tri[0] >>> 0;
- const b = tri[1] >>> 0;
- const c = tri[2] >>> 0;
- if (a === b || b === c || c === a) continue;
- if (reverseFan) solid._triVerts.push(c, b, a);
- else solid._triVerts.push(a, b, c);
- solid._triIDs.push(capFaceID);
- added += 1;
- }
- }
-
- for (const fanEdges of edgeFans) {
- const vertices = Array.from(new Set(fanEdges.flatMap(([a, b]) => [a, b])));
- if (vertices.length < 3) continue;
- const centroid = [0, 0, 0];
- for (const index of vertices) {
- centroid[0] += vp[(index * 3) + 0];
- centroid[1] += vp[(index * 3) + 1];
- centroid[2] += vp[(index * 3) + 2];
- }
- centroid[0] /= vertices.length;
- centroid[1] /= vertices.length;
- centroid[2] /= vertices.length;
- const centerIndex = addVertex(centroid);
- for (const [u, v] of fanEdges) {
- const directed = directedBoundaryByEdge.get(edgeKey(u, v)) || [u, v];
- const a = directed[0] >>> 0;
- const b = directed[1] >>> 0;
- if (a === b || a === centerIndex || b === centerIndex) continue;
- solid._triVerts.push(b, a, centerIndex);
- solid._triIDs.push(capFaceID);
- added += 1;
- }
- }
-
- if (added > 0) {
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- }
- return added;
-}
-
-function getOrCreateIntersectionCapFaceID(solid, sourceFaceName, distance) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = solid?._faceNameToID instanceof Map ? solid._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof solid?._getOrCreateID === 'function') {
- capFaceID = solid._getOrCreateID(capLabel);
- }
- if (capFaceID && solid?._faceMetadata instanceof Map) {
- solid._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance,
- });
- }
- return capFaceID || null;
-}
-
-function fillIntersectionCapBoundaryLoops(solid, sourceFaceName, distance) {
- const capFaceID = getOrCreateIntersectionCapFaceID(solid, sourceFaceName, distance);
- if (!capFaceID) return 0;
- return fillBoundaryLoopsWithTriangles(solid, capFaceID);
-}
-
-function cullTrianglesTouchingNonManifoldEdges(solid) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const ids = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || ids.length < triCount) return 0;
-
- const edgeToTris = new Map();
- const addUse = (a, b, triIndex) => {
- const key = edgeKey(a, b);
- let list = edgeToTris.get(key);
- if (!list) {
- list = [];
- edgeToTris.set(key, list);
- }
- list.push(triIndex);
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addUse(a, b, triIndex);
- addUse(b, c, triIndex);
- addUse(c, a, triIndex);
- }
-
- const removeTri = new Uint8Array(triCount);
- for (const list of edgeToTris.values()) {
- if (list.length <= 2) continue;
- for (const triIndex of list) removeTri[triIndex] = 1;
- }
-
- let removed = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (removeTri[triIndex]) removed += 1;
- }
- if (!removed) return 0;
-
- const vertexCount = (vp.length / 3) | 0;
- const usedVert = new Uint8Array(vertexCount);
- const newTriVerts = [];
- const newTriIDs = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (removeTri[triIndex]) continue;
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- newTriVerts.push(a, b, c);
- newTriIDs.push(ids[triIndex]);
- usedVert[a] = 1;
- usedVert[b] = 1;
- usedVert[c] = 1;
- }
-
- const oldToNew = new Int32Array(vertexCount);
- oldToNew.fill(-1);
- const newVertProperties = [];
- let nextVertex = 0;
- for (let i = 0; i < vertexCount; i++) {
- if (!usedVert[i]) continue;
- oldToNew[i] = nextVertex++;
- newVertProperties.push(vp[(i * 3) + 0], vp[(i * 3) + 1], vp[(i * 3) + 2]);
- }
- for (let i = 0; i < newTriVerts.length; i++) {
- newTriVerts[i] = oldToNew[newTriVerts[i] >>> 0];
- }
-
- solid._vertProperties = newVertProperties;
- solid._triVerts = newTriVerts;
- solid._triIDs = newTriIDs;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < newVertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${newVertProperties[i]},${newVertProperties[i + 1]},${newVertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- return removed;
-}
-
-function cullTrianglesTouchingSameDirectionEdges(solid) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const ids = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || ids.length < triCount) return 0;
-
- const edgeToUses = new Map();
- const addUse = (a, b, triIndex) => {
- const key = edgeKey(a, b);
- let list = edgeToUses.get(key);
- if (!list) {
- list = [];
- edgeToUses.set(key, list);
- }
- list.push({ triIndex, a, b });
- };
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- addUse(a, b, triIndex);
- addUse(b, c, triIndex);
- addUse(c, a, triIndex);
- }
-
- const sameByTri = new Uint16Array(triCount);
- for (const uses of edgeToUses.values()) {
- if (uses.length !== 2) continue;
- if (!(uses[0].a === uses[1].a && uses[0].b === uses[1].b)) continue;
- sameByTri[uses[0].triIndex] += 1;
- sameByTri[uses[1].triIndex] += 1;
- }
-
- let bestTriIndex = -1;
- let bestScore = 0;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const sameCount = sameByTri[triIndex] || 0;
- if (!sameCount) continue;
- const faceName = solid._idToFaceName instanceof Map ? String(solid._idToFaceName.get(ids[triIndex]) || '') : '';
- const capBonus = faceName.includes('INTERSECTION_CAP') ? 100 : 0;
- const score = sameCount + capBonus;
- if (score > bestScore) {
- bestScore = score;
- bestTriIndex = triIndex;
- }
- }
- if (bestTriIndex < 0) return 0;
-
- const vertexCount = (vp.length / 3) | 0;
- const usedVert = new Uint8Array(vertexCount);
- const newTriVerts = [];
- const newTriIDs = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (triIndex === bestTriIndex) continue;
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- newTriVerts.push(a, b, c);
- newTriIDs.push(ids[triIndex]);
- usedVert[a] = 1;
- usedVert[b] = 1;
- usedVert[c] = 1;
- }
-
- const oldToNew = new Int32Array(vertexCount);
- oldToNew.fill(-1);
- const newVertProperties = [];
- let nextVertex = 0;
- for (let i = 0; i < vertexCount; i++) {
- if (!usedVert[i]) continue;
- oldToNew[i] = nextVertex++;
- newVertProperties.push(vp[(i * 3) + 0], vp[(i * 3) + 1], vp[(i * 3) + 2]);
- }
- for (let i = 0; i < newTriVerts.length; i++) {
- newTriVerts[i] = oldToNew[newTriVerts[i] >>> 0];
- }
-
- solid._vertProperties = newVertProperties;
- solid._triVerts = newTriVerts;
- solid._triIDs = newTriIDs;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < newVertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${newVertProperties[i]},${newVertProperties[i + 1]},${newVertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- return 1;
-}
-
-function repairSolidOrientationByCullAndCap(solid, context = {}) {
- const sourceFaceName = String(context.sourceFaceName || '').trim() || 'FACE';
- const dist = Number(context.distance) || 0;
- const manifoldWeldEpsilon = Math.max(Number(context.manifoldWeldEpsilon) || 0, 0);
- const nonManifoldCullMaxTriangles = Math.max(
- 0,
- Number.isFinite(Number(context.nonManifoldCullMaxTriangles))
- ? Number(context.nonManifoldCullMaxTriangles)
- : 10000,
- );
- const maxPasses = Math.max(
- 1,
- Math.min(512, Number.isFinite(Number(context.maxPasses)) ? Number(context.maxPasses) | 0 : 256),
- );
- const stats = {
- orientedTriangleCount: 0,
- orientationCulledTriangleCount: 0,
- orientationCapTriangleCount: 0,
- nonManifoldCulledTriangleCount: 0,
- boundaryCapTriangleCount: 0,
- weldedVertexCount: 0,
- degenerateTriangleCount: 0,
- };
- const isCoherent = () => {
- try {
- return typeof solid?._isCoherentlyOrientedManifold === 'function'
- ? solid._isCoherentlyOrientedManifold() === true
- : true;
- } catch {
- return false;
- }
- };
- const weld = () => {
- const welded = weldSolidVerticesByPosition(solid, manifoldWeldEpsilon);
- stats.weldedVertexCount += welded.weldedVertexCount || 0;
- stats.degenerateTriangleCount += welded.removedTriangleCount || 0;
- };
-
- for (let pass = 0; pass < maxPasses; pass++) {
- let topology = analyzeMeshTopology(solid);
- let changed = false;
-
- if (
- topology.nonManifoldEdgeCount > 0
- && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles
- ) {
- const removedThisPass = cullTrianglesTouchingNonManifoldEdges(solid);
- if (removedThisPass > 0) {
- stats.nonManifoldCulledTriangleCount += removedThisPass;
- changed = true;
- weld();
- topology = analyzeMeshTopology(solid);
- }
- }
-
- if (topology.boundaryEdgeCount > 0) {
- const addedThisPass = fillIntersectionCapBoundaryLoops(solid, sourceFaceName, dist);
- if (addedThisPass > 0) {
- stats.boundaryCapTriangleCount += addedThisPass;
- stats.orientationCapTriangleCount += addedThisPass;
- changed = true;
- try { solid.fixTriangleWindingsByAdjacency?.(); } catch { /* keep authored winding */ }
- weld();
- topology = analyzeMeshTopology(solid);
- }
- }
-
- if (topology.boundaryEdgeCount || topology.nonManifoldEdgeCount) {
- if (!changed) {
- return { ok: false, topology, orientation: analyzeTriangleOrientation(solid), stats };
- }
- continue;
- }
-
- const orientedThisPass =
- orientSolidTrianglesByAdjacency(solid)
- + orientSolidComponentsBySignedVolume(solid);
- if (orientedThisPass > 0) {
- stats.orientedTriangleCount += orientedThisPass;
- try { solid.fixTriangleWindingsByAdjacency?.(); } catch { /* keep JS orientation */ }
- }
-
- if (isCoherent()) {
- return { ok: true, topology: analyzeMeshTopology(solid), orientation: analyzeTriangleOrientation(solid), stats };
- }
-
- const removedSameDirection = cullTrianglesTouchingSameDirectionEdges(solid);
- if (!(removedSameDirection > 0)) {
- return { ok: false, topology, orientation: analyzeTriangleOrientation(solid), stats };
- }
- stats.orientationCulledTriangleCount += removedSameDirection;
- changed = true;
- weld();
- if (!changed) break;
}
-
- const topology = analyzeMeshTopology(solid);
- return {
- ok: topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0 && isCoherent(),
- topology,
- orientation: analyzeTriangleOrientation(solid),
- stats,
- };
-}
-
-function tryRepairCoherentOrientationCandidate(solid, context = {}) {
- if (!solid || typeof solid.clone !== 'function') return null;
- let candidate = null;
- try {
- candidate = solid.clone();
- try { candidate.name = solid.name; } catch { /* ignore */ }
- const result = repairSolidOrientationByCullAndCap(candidate, context);
- const topology = result?.topology || analyzeMeshTopology(candidate);
- const coherent = (() => {
- try {
- return typeof candidate._isCoherentlyOrientedManifold === 'function'
- ? candidate._isCoherentlyOrientedManifold() === true
- : true;
- } catch {
- return false;
- }
- })();
- if (
- result?.ok === true
- && topology.boundaryEdgeCount === 0
- && topology.nonManifoldEdgeCount === 0
- && coherent
- ) {
- return { solid: candidate, topology, stats: result.stats || {} };
- }
- } catch {
- /* fall through and reject the candidate */
- }
- try { candidate?.free?.(); } catch { /* ignore */ }
- return null;
-}
-
-function pruneOverusedTriangles(solid) {
- let removedTotal = 0;
-
- const rebuildWithout = (removeTri) => {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const currentVP = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- const currentIDs = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const triCount = (tv.length / 3) | 0;
- const vertexCount = (currentVP.length / 3) | 0;
- const usedVert = new Uint8Array(vertexCount);
- const newTriVerts = [];
- const newTriIDs = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- if (removeTri[triIndex]) continue;
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- newTriVerts.push(a, b, c);
- newTriIDs.push(currentIDs[triIndex]);
- usedVert[a] = 1;
- usedVert[b] = 1;
- usedVert[c] = 1;
- }
- const oldToNew = new Int32Array(vertexCount);
- oldToNew.fill(-1);
- const newVertProperties = [];
- let nextVertex = 0;
- for (let i = 0; i < vertexCount; i++) {
- if (!usedVert[i]) continue;
- oldToNew[i] = nextVertex++;
- newVertProperties.push(currentVP[(i * 3) + 0], currentVP[(i * 3) + 1], currentVP[(i * 3) + 2]);
- }
- for (let i = 0; i < newTriVerts.length; i++) {
- newTriVerts[i] = oldToNew[newTriVerts[i] >>> 0];
- }
- solid._vertProperties = newVertProperties;
- solid._triVerts = newTriVerts;
- solid._triIDs = newTriIDs;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < newVertProperties.length; i += 3) {
- solid._vertKeyToIndex.set(
- `${newVertProperties[i]},${newVertProperties[i + 1]},${newVertProperties[i + 2]}`,
- (i / 3) | 0,
- );
- }
- solid._dirty = true;
- solid._faceIndex = null;
- solid._manifold = null;
- };
-
- for (let pass = 0; pass < 256; pass++) {
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
- const ids = Array.isArray(solid?._triIDs) ? solid._triIDs : [];
- const triCount = (tv.length / 3) | 0;
- if (!triCount || ids.length < triCount) break;
- const edgeCounts = new Map();
- const triEdges = [];
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const a = tv[(triIndex * 3) + 0] >>> 0;
- const b = tv[(triIndex * 3) + 1] >>> 0;
- const c = tv[(triIndex * 3) + 2] >>> 0;
- const edges = [edgeKey(a, b), edgeKey(b, c), edgeKey(c, a)];
- triEdges.push(edges);
- for (const key of edges) edgeCounts.set(key, (edgeCounts.get(key) || 0) + 1);
- }
- let overusedCount = 0;
- for (const count of edgeCounts.values()) {
- if (count > 2) overusedCount += 1;
- }
- if (!overusedCount) break;
-
- let best = null;
- for (let triIndex = 0; triIndex < triCount; triIndex++) {
- const edges = triEdges[triIndex];
- if (edges.some((key) => (edgeCounts.get(key) || 0) <= 1)) continue;
- let score = 0;
- for (const key of edges) score += Math.max(0, (edgeCounts.get(key) || 0) - 2);
- if (!(score > 0)) continue;
- const faceName = solid._idToFaceName instanceof Map ? String(solid._idToFaceName.get(ids[triIndex]) || '') : '';
- const capBonus = faceName.includes('INTERSECTION_CAP') ? 1000 : 0;
- const candidate = { triIndex, score: score + capBonus };
- if (!best || candidate.score > best.score) best = candidate;
- }
- if (!best) break;
- const removeTri = new Uint8Array(triCount);
- removeTri[best.triIndex] = 1;
- rebuildWithout(removeTri);
- removedTotal += 1;
- }
-
- return removedTotal;
-}
-
-function triangleSplitCullSolid(solid, options = {}) {
- if (!solid) return null;
- const maxPasses = Math.max(
- 1,
- Math.min(8, Number.isFinite(Number(options.triangleCullPasses))
- ? Number(options.triangleCullPasses) | 0
- : 1),
- );
- const splitOptions = {
- snapTolerance: options.splitSnapTolerance ?? options.snapTolerance,
- diagnostics: options.splitDiagnostics === true || options.diagnostics === true,
- };
- if (!Number.isFinite(Number(splitOptions.snapTolerance)) || Number(splitOptions.snapTolerance) <= 0) {
- delete splitOptions.snapTolerance;
- }
-
- let splitCount = 0;
- let culledTriangleCount = 0;
- let degenerateTriangleCount = 0;
- let weldedVertexCount = 0;
- let passCount = 0;
- const weldTolerance = Math.max(
- Number(options.weldTolerance ?? options.manifoldWeldTolerance) || 0,
- 0,
- );
- for (let pass = 0; pass < maxPasses; pass++) {
- passCount = pass + 1;
- const splitThisPass = Number(solid.splitSelfIntersectingTriangles?.(splitOptions) || 0);
- splitCount += splitThisPass;
- degenerateTriangleCount += Number(solid.removeDegenerateTriangles?.() || 0);
- const weldAfterSplit = weldSolidVerticesByPosition(solid, weldTolerance);
- weldedVertexCount += weldAfterSplit.weldedVertexCount || 0;
- degenerateTriangleCount += weldAfterSplit.removedTriangleCount || 0;
- const currentTriCount = ((solid?._triVerts?.length || 0) / 3) | 0;
- const windingCullMaxTriangles = Math.max(
- 0,
- Number.isFinite(Number(options.windingCullMaxTriangles))
- ? Number(options.windingCullMaxTriangles)
- : 10000,
- );
- const defaultCullMethod = currentTriCount > 0 && currentTriCount <= windingCullMaxTriangles
- ? 'winding'
- : 'raycast';
- const cullMethod = String(options.internalCullMethod || defaultCullMethod).toLowerCase();
- const culledThisPass = options.skipInternalCull === true
- ? 0
- : cullMethod === 'winding'
- ? Number(solid.removeInternalTrianglesByWinding?.(options.windingOptions || {}) || 0)
- : Number(cullInternalTrianglesByIndexedRaycast(solid, options.raycastCullOptions || {}) || 0);
- culledTriangleCount += culledThisPass;
- degenerateTriangleCount += Number(solid.removeDegenerateTriangles?.() || 0);
- const weldAfterCull = weldSolidVerticesByPosition(solid, weldTolerance);
- weldedVertexCount += weldAfterCull.weldedVertexCount || 0;
- degenerateTriangleCount += weldAfterCull.removedTriangleCount || 0;
- if (splitThisPass === 0 && culledThisPass === 0) break;
- }
- try { solid.fixTriangleWindingsByAdjacency?.(); } catch { /* keep authored winding if native prep is unavailable */ }
- const topology = analyzeMeshTopology(solid);
- return {
- splitCount,
- culledTriangleCount,
- degenerateTriangleCount,
- weldedVertexCount,
- passCount,
- topology,
- };
+ return edgeInputs;
+}
+
+function averageLoopNormal(loops) {
+ const outer = (loops || []).find((loop) => !loop.isHole) || loops?.[0] || null;
+ const pts = outer?.pts || [];
+ if (pts.length < 3) return [0, 0, 1];
+ let nx = 0;
+ let ny = 0;
+ let nz = 0;
+ for (let i = 0; i < pts.length; i += 1) {
+ const a = pts[i];
+ const b = pts[(i + 1) % pts.length];
+ nx += (a[1] - b[1]) * (a[2] + b[2]);
+ ny += (a[2] - b[2]) * (a[0] + b[0]);
+ nz += (a[0] - b[0]) * (a[1] + b[1]);
+ }
+ const len = Math.hypot(nx, ny, nz);
+ return len > 1e-12 ? [nx / len, ny / len, nz / len] : [0, 0, 1];
+}
+
+function projectedSignedArea(pts, normal) {
+ const ax = Math.abs(normal?.[0] || 0);
+ const ay = Math.abs(normal?.[1] || 0);
+ const az = Math.abs(normal?.[2] || 0);
+ let area = 0;
+ for (let i = 0; i < pts.length; i += 1) {
+ const p = pts[i];
+ const q = pts[(i + 1) % pts.length];
+ if (az >= ax && az >= ay) area += p[0] * q[1] - q[0] * p[1];
+ else if (ay >= ax && ay >= az) area += p[0] * q[2] - q[0] * p[2];
+ else area += p[1] * q[2] - q[1] * p[2];
+ }
+ return area * 0.5;
+}
+
+function orientHoleLoopsForOccFaceBuilder(loops, normal) {
+ const outer = (loops || []).find((loop) => !loop.isHole) || loops?.[0] || null;
+ const outerSign = Math.sign(projectedSignedArea(outer?.pts || [], normal) || 1) || 1;
+ return (loops || []).map((loop) => {
+ const pts = Array.isArray(loop?.pts) ? loop.pts.slice() : [];
+ if (loop.isHole && Math.sign(projectedSignedArea(pts, normal) || 0) !== outerSign) pts.reverse();
+ return { ...loop, pts };
+ });
}
-function applySourceFaceMetadataToThickenResult(result, face, labels, sourceFaceName) {
+function applySourceFaceMetadataToThickenResult(result, face, sourceFaceName) {
let sourceMetadata = null;
try {
sourceMetadata = typeof face?.getMetadata === 'function' ? (face.getMetadata() || null) : null;
@@ -2664,675 +140,112 @@ function applySourceFaceMetadataToThickenResult(result, face, labels, sourceFace
sourceMetadata = null;
}
if (!sourceMetadata || typeof result?.setFaceMetadata !== 'function') return;
+ const sourceFeatureId = face?.owningFeatureID ?? face?.parentSolid?.owningFeatureID ?? sourceMetadata?.sourceFeatureId ?? null;
try {
- result.setFaceMetadata(labels.start, {
+ result.setFaceMetadata(`${sourceFaceName}_START`, {
...sourceMetadata,
type: 'start_cap',
sourceFaceName,
- sourceFeatureId: face?.owningFeatureID ?? face?.parentSolid?.owningFeatureID ?? sourceMetadata?.sourceFeatureId ?? null,
+ sourceFeatureId,
});
- result.setFaceMetadata(labels.end, {
+ result.setFaceMetadata(`${sourceFaceName}_END`, {
...sourceMetadata,
type: 'end_cap',
sourceFaceName,
- sourceFeatureId: face?.owningFeatureID ?? face?.parentSolid?.owningFeatureID ?? sourceMetadata?.sourceFeatureId ?? null,
+ sourceFeatureId,
});
} catch {
/* ignore metadata propagation errors */
}
}
-function thickenSurfaceToSolid(surface, face, distance, options = {}) {
+function makeLoopPrismThicken(face, distance, options = {}) {
const dist = Number(distance);
if (!Number.isFinite(dist) || Math.abs(dist) <= EPS) {
throw new Error('Face.thicken() requires a non-zero finite distance.');
}
- if (!surface || !Array.isArray(surface.triangles) || !surface.triangles.length) {
- throw new Error('Face.thicken() requires a non-empty source triangle surface.');
- }
-
+ const boundaryLoops = cloneBoundaryLoopsWorld(face);
+ if (!boundaryLoops?.length) return null;
const featureId = sanitizeToken(options.featureId || options.name || face?.name || 'THICKEN', 'THICKEN');
- const sourceFaceName = String(options.sourceFaceName || face?.userData?.faceName || face?.name || featureId).trim() || featureId;
- const sourceFaceNames = Array.isArray(options.sourceFaceNames)
- ? options.sourceFaceNames.map((name) => String(name || '').trim()).filter(Boolean)
- : [sourceFaceName];
- const loops = Array.isArray(surface.loops) ? surface.loops : [];
- const labels = {
- sourceFaceName,
- start: `${sourceFaceName}_START`,
- end: `${sourceFaceName}_END`,
- sidewalls: loops.flatMap((loop, loopIndex) => (Array.isArray(loop?.edges) ? loop.edges : []).map((edge, edgeIndex) => ({
- key: edge.key,
- loopIndex,
- edgeIndex,
- label: loopIndex === 0
- ? `${sourceFaceName}_E${edgeIndex}_SW`
- : `${sourceFaceName}_L${loopIndex}_E${edgeIndex}_SW`,
- }))),
- };
- const classificationState = buildThickenClassificationState(labels, dist);
+ const sourceFaceName = String(options.sourceFaceName || getFaceLabel(face) || featureId).trim() || featureId;
const solidName = String(options.name || featureId).trim() || featureId;
- const manifoldWeldEpsilon = Math.max(
- Number(options.manifoldWeldTolerance) || 0,
- Math.max(surface.weldTolerance || 0, surface.scale * 1e-7, 1e-6),
- );
- const orientationRepairDistanceLimit = Math.max(
- 0,
- Number.isFinite(Number(options.orientationRepairDistanceLimit))
- ? Number(options.orientationRepairDistanceLimit)
- : 2,
- );
- const useSmallThicknessOrientationRepair = Math.abs(dist) <= orientationRepairDistanceLimit;
- const useTrianglePrismUnion = options.trianglePrismUnion === true;
- const allowBoundaryRepairCaps = options.repairBoundaryCaps !== false;
- const sourceTriangleCount = Array.isArray(surface.triangles) ? surface.triangles.length : 0;
-
- let staged = null;
- try {
- const retryWithoutInternalCull = () => {
- if (options.__skipInternalCullRetry === true || options.skipInternalCull === true) return null;
- if (!useSmallThicknessOrientationRepair) return null;
- try { staged?.free?.(); } catch { /* ignore */ }
- staged = null;
- try {
- return thickenSurfaceToSolid(surface, face, dist, {
- ...options,
- skipInternalCull: true,
- __skipInternalCullRetry: true,
- });
- } catch {
- return null;
- }
- };
- const retryWithRelaxedWeld = () => {
- if (options.__relaxedWeldRetry === true) return null;
- if (!useSmallThicknessOrientationRepair) return null;
- const scaleRelativeWeld = Math.min(1e-4, Math.max(surface.scale * 1e-5, 1e-8));
- const relaxedWeldTolerance = Math.max(
- manifoldWeldEpsilon * 16,
- surface.scale * 1e-6,
- scaleRelativeWeld,
- );
- if (Number(options.manifoldWeldTolerance) >= relaxedWeldTolerance * 0.95) return null;
- try { staged?.free?.(); } catch { /* ignore */ }
- staged = null;
- try {
- return thickenSurfaceToSolid(surface, face, dist, {
- ...options,
- skipInternalCull: true,
- manifoldWeldTolerance: relaxedWeldTolerance,
- __relaxedWeldRetry: true,
- });
- } catch {
- return null;
- }
- };
- const retryWithRaycastMajority = () => {
- if (options.__raycastMajorityRetry === true) return null;
- try { staged?.free?.(); } catch { /* ignore */ }
- staged = null;
- return thickenSurfaceToSolid(surface, face, dist, {
- ...options,
- internalCullMethod: 'raycast',
- raycastCullOptions: {
- ...(options.raycastCullOptions || {}),
- axes: 'majority',
- },
- __raycastMajorityRetry: true,
- });
- };
- const retryWithTrianglePrismUnion = (reason = 'destructive_repair') => {
- if (options.__trianglePrismUnionRetry === true || useTrianglePrismUnion) return null;
- try { staged?.free?.(); } catch { /* ignore */ }
- staged = null;
- try {
- return thickenSurfaceToSolid(surface, face, dist, {
- ...options,
- trianglePrismUnion: true,
- repairBoundaryCaps: false,
- nonManifoldCullMaxTriangles: 0,
- __trianglePrismUnionRetry: true,
- __trianglePrismUnionReason: reason,
- });
- } catch {
- return null;
- }
- };
-
- staged = useTrianglePrismUnion
- ? buildTrianglePrismUnionShellSolid(surface, dist, classificationState, solidName)
- : buildStitchedShellSolid(surface, dist, classificationState, solidName);
- if (!staged) {
- throw new Error('Face.thicken() failed to build the stitched triangle shell.');
- }
-
- const cleanup = triangleSplitCullSolid(staged, {
- ...options,
- weldTolerance: manifoldWeldEpsilon,
- });
- let topology = cleanup?.topology || analyzeMeshTopology(staged);
- let boundaryCapTriangleCount = 0;
- let nonManifoldCulledTriangleCount = 0;
- const nonManifoldCullMaxTriangles = Math.max(
- 0,
- Number.isFinite(Number(options.nonManifoldCullMaxTriangles))
- ? Number(options.nonManifoldCullMaxTriangles)
- : 10000,
- );
- for (let nonManifoldPass = 0; nonManifoldPass < 8; nonManifoldPass++) {
- if (!(topology.nonManifoldEdgeCount > 0 && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles)) break;
- const removedThisPass = cullTrianglesTouchingNonManifoldEdges(staged);
- if (!(removedThisPass > 0)) break;
- nonManifoldCulledTriangleCount += removedThisPass;
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- }
- if (topology.nonManifoldEdgeCount === 0 && topology.boundaryEdgeCount > 0) {
- try {
- staged.removeDegenerateTriangles?.();
- topology = analyzeMeshTopology(staged);
- } catch { /* ignore */ }
- }
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- boundaryCapTriangleCount = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (boundaryCapTriangleCount > 0) {
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- }
- }
- }
- for (let repairPass = 0; repairPass < 16; repairPass++) {
- let changed = false;
- if (topology.nonManifoldEdgeCount > 0 && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles) {
- const removedThisPass = cullTrianglesTouchingNonManifoldEdges(staged);
- if (removedThisPass > 0) {
- nonManifoldCulledTriangleCount += removedThisPass;
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- changed = true;
- }
- }
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- boundaryCapTriangleCount += addedThisPass;
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- changed = true;
- }
- }
- }
- if (!changed) break;
- }
- let orientedTriangleCount = topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0
- ? orientSolidTrianglesByAdjacency(staged)
- : 0;
- if (topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0) {
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- }
- let orientationCulledTriangleCount = 0;
- let orientationCapTriangleCount = 0;
- let orientationCandidateRepairAccepted = false;
- if (orientedTriangleCount > 0) {
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- }
- if (
- topology.boundaryEdgeCount === 0
- && topology.nonManifoldEdgeCount === 0
- && useSmallThicknessOrientationRepair
- && typeof staged._isCoherentlyOrientedManifold === 'function'
- && staged._isCoherentlyOrientedManifold() !== true
- ) {
- for (let orientationRepairPass = 0; orientationRepairPass < 8; orientationRepairPass++) {
- const removedThisPass = cullTrianglesTouchingSameDirectionEdges(staged);
- if (!(removedThisPass > 0)) break;
- orientationCulledTriangleCount += removedThisPass;
- topology = analyzeMeshTopology(staged);
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- orientationCapTriangleCount += addedThisPass;
- boundaryCapTriangleCount += addedThisPass;
- }
- }
- }
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- if (topology.boundaryEdgeCount || topology.nonManifoldEdgeCount) break;
- orientedTriangleCount += orientSolidTrianglesByAdjacency(staged);
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- if (staged._isCoherentlyOrientedManifold() === true) break;
- }
- }
- for (let postOrientationRepairPass = 0; postOrientationRepairPass < 8; postOrientationRepairPass++) {
- let changed = false;
- if (topology.nonManifoldEdgeCount > 0 && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles) {
- const removedThisPass = cullTrianglesTouchingNonManifoldEdges(staged);
- if (removedThisPass > 0) {
- nonManifoldCulledTriangleCount += removedThisPass;
- changed = true;
- }
- }
- topology = analyzeMeshTopology(staged);
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- boundaryCapTriangleCount += addedThisPass;
- changed = true;
- }
- }
- }
- if (changed) {
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- if (topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0) {
- orientedTriangleCount += orientSolidTrianglesByAdjacency(staged);
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- }
- }
- if (!changed) break;
- }
- if (topology.nonManifoldEdgeCount > 0 && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles) {
- const prunedThisPass = pruneOverusedTriangles(staged);
- if (prunedThisPass > 0) {
- nonManifoldCulledTriangleCount += prunedThisPass;
- topology = analyzeMeshTopology(staged);
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- boundaryCapTriangleCount += addedThisPass;
- topology = analyzeMeshTopology(staged);
- }
- }
- }
- if (topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0) {
- orientedTriangleCount += orientSolidTrianglesByAdjacency(staged);
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- }
- }
- }
- if (options.__raycastMajorityRetry !== true && useSmallThicknessOrientationRepair) for (let finalOrientationRepairPass = 0; finalOrientationRepairPass < 128; finalOrientationRepairPass++) {
- topology = analyzeMeshTopology(staged);
- if (topology.nonManifoldEdgeCount > 0 && (topology.triangleCount || 0) <= nonManifoldCullMaxTriangles) {
- const removedThisPass = cullTrianglesTouchingNonManifoldEdges(staged);
- if (removedThisPass > 0) {
- nonManifoldCulledTriangleCount += removedThisPass;
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- }
- }
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- boundaryCapTriangleCount += addedThisPass;
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- }
- }
- }
- if (topology.boundaryEdgeCount || topology.nonManifoldEdgeCount) break;
-
- orientedTriangleCount += orientSolidTrianglesByAdjacency(staged);
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- if (typeof staged._isCoherentlyOrientedManifold !== 'function' || staged._isCoherentlyOrientedManifold() === true) {
- break;
- }
-
- const removedSameDirection = cullTrianglesTouchingSameDirectionEdges(staged);
- if (!(removedSameDirection > 0)) break;
- orientationCulledTriangleCount += removedSameDirection;
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- }
- topology = analyzeMeshTopology(staged);
- if (
- topology.boundaryEdgeCount === 0
- && topology.nonManifoldEdgeCount === 0
- && useSmallThicknessOrientationRepair
- && typeof staged._isCoherentlyOrientedManifold === 'function'
- && staged._isCoherentlyOrientedManifold() !== true
- ) {
- const repaired = tryRepairCoherentOrientationCandidate(staged, {
- sourceFaceName,
- distance: dist,
- manifoldWeldEpsilon,
- nonManifoldCullMaxTriangles,
- maxPasses: options.orientationRepairMaxPasses,
- });
- if (repaired?.solid) {
- const previous = staged;
- staged = repaired.solid;
- try { previous?.free?.(); } catch { /* ignore */ }
- topology = repaired.topology || analyzeMeshTopology(staged);
- const stats = repaired.stats || {};
- orientedTriangleCount += stats.orientedTriangleCount || 0;
- orientationCulledTriangleCount += stats.orientationCulledTriangleCount || 0;
- orientationCapTriangleCount += stats.orientationCapTriangleCount || 0;
- boundaryCapTriangleCount += stats.boundaryCapTriangleCount || 0;
- nonManifoldCulledTriangleCount += stats.nonManifoldCulledTriangleCount || 0;
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (stats.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (stats.degenerateTriangleCount || 0);
- orientationCandidateRepairAccepted = true;
- }
- }
- const excessiveRepairCaps = !useTrianglePrismUnion && boundaryCapTriangleCount > Math.max(10000, sourceTriangleCount * 100);
- const excessiveNonManifoldCull = !useTrianglePrismUnion && nonManifoldCulledTriangleCount > Math.max(10000, sourceTriangleCount * 100);
- if (excessiveRepairCaps || excessiveNonManifoldCull) {
- const prismRetry = retryWithTrianglePrismUnion(excessiveRepairCaps ? 'excessive_boundary_caps' : 'excessive_nonmanifold_cull');
- if (prismRetry) return prismRetry;
- throw new Error(
- 'Face.thicken() triangle split/cull required excessive topology repair '
- + `(boundaryCaps=${boundaryCapTriangleCount}, nonManifoldCull=${nonManifoldCulledTriangleCount}, sourceTriangles=${sourceTriangleCount}).`,
- );
- }
+ const normal = averageLoopNormal(boundaryLoops);
+ const orientedLoops = orientHoleLoopsForOccFaceBuilder(boundaryLoops, normal);
+ const edgeInputs = cloneSketchEdgeInputsWorld(face, sourceFaceName, orientedLoops);
+ const occState = makeExtrusion({
+ boundaryLoops: orientedLoops,
+ edgeInputs,
+ faceName: sourceFaceName,
+ name: '',
+ direction: [normal[0] * dist, normal[1] * dist, normal[2] * dist],
+ normal,
+ });
- if (topology.boundaryEdgeCount || topology.nonManifoldEdgeCount) {
- const prismRetry = retryWithTrianglePrismUnion('invalid_stitched_topology');
- if (prismRetry) return prismRetry;
- const noInternalCullRetry = retryWithoutInternalCull();
- if (noInternalCullRetry) return noInternalCullRetry;
- const relaxedWeldRetry = retryWithRelaxedWeld();
- if (relaxedWeldRetry) return relaxedWeldRetry;
- const retry = retryWithRaycastMajority();
- if (retry) return retry;
- throw new Error(
- `Face.thicken() triangle split/cull produced invalid topology: `
- + `boundaries=${topology.boundaryEdgeCount}, nonManifold=${topology.nonManifoldEdgeCount}, triangles=${topology.triangleCount || 0}.`,
- );
- }
- let orientationWarning = typeof staged._isCoherentlyOrientedManifold === 'function' && staged._isCoherentlyOrientedManifold() !== true;
- let finalOrientation = orientationWarning ? analyzeTriangleOrientation(staged) : null;
- if (orientationWarning && topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0) {
- const repaired = tryRepairCoherentOrientationCandidate(staged, {
- sourceFaceName,
- distance: dist,
- manifoldWeldEpsilon,
- nonManifoldCullMaxTriangles,
- maxPasses: options.orientationRepairMaxPasses,
- });
- if (repaired?.solid) {
- const previous = staged;
- staged = repaired.solid;
- try { previous?.free?.(); } catch { /* ignore */ }
- topology = repaired.topology || analyzeMeshTopology(staged);
- const stats = repaired.stats || {};
- orientedTriangleCount += stats.orientedTriangleCount || 0;
- orientationCulledTriangleCount += stats.orientationCulledTriangleCount || 0;
- orientationCapTriangleCount += stats.orientationCapTriangleCount || 0;
- boundaryCapTriangleCount += stats.boundaryCapTriangleCount || 0;
- nonManifoldCulledTriangleCount += stats.nonManifoldCulledTriangleCount || 0;
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (stats.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (stats.degenerateTriangleCount || 0);
- orientationCandidateRepairAccepted = true;
- orientationWarning = typeof staged._isCoherentlyOrientedManifold === 'function' && staged._isCoherentlyOrientedManifold() !== true;
- finalOrientation = orientationWarning ? analyzeTriangleOrientation(staged) : null;
- }
- }
- if (orientationWarning && topology.boundaryEdgeCount === 0 && topology.nonManifoldEdgeCount === 0) {
- for (let finalCoherencePass = 0; finalCoherencePass < 32; finalCoherencePass++) {
- const removedThisPass = cullTrianglesTouchingSameDirectionEdges(staged);
- if (!(removedThisPass > 0)) break;
- orientationCulledTriangleCount += removedThisPass;
- topology = analyzeMeshTopology(staged);
- if (allowBoundaryRepairCaps && topology.boundaryEdgeCount > 0) {
- const capLabel = `${sourceFaceName}_INTERSECTION_CAP`;
- let capFaceID = staged._faceNameToID instanceof Map ? staged._faceNameToID.get(capLabel) : null;
- if (!capFaceID && typeof staged._getOrCreateID === 'function') {
- capFaceID = staged._getOrCreateID(capLabel);
- }
- if (capFaceID) {
- if (staged._faceMetadata instanceof Map) {
- staged._faceMetadata.set(capLabel, {
- type: 'intersection_cap',
- sourceFaceName,
- distance: dist,
- });
- }
- const addedThisPass = fillBoundaryLoopsWithTriangles(staged, capFaceID);
- if (addedThisPass > 0) {
- orientationCapTriangleCount += addedThisPass;
- boundaryCapTriangleCount += addedThisPass;
- }
- }
- }
- const weld = weldSolidVerticesByPosition(staged, manifoldWeldEpsilon);
- cleanup.weldedVertexCount = (cleanup.weldedVertexCount || 0) + (weld.weldedVertexCount || 0);
- cleanup.degenerateTriangleCount = (cleanup.degenerateTriangleCount || 0) + (weld.removedTriangleCount || 0);
- topology = analyzeMeshTopology(staged);
- if (topology.boundaryEdgeCount || topology.nonManifoldEdgeCount) break;
- orientedTriangleCount += orientSolidTrianglesByAdjacency(staged);
- orientedTriangleCount += orientSolidComponentsBySignedVolume(staged);
- try { staged.fixTriangleWindingsByAdjacency?.(); } catch { /* ignore */ }
- orientationWarning = typeof staged._isCoherentlyOrientedManifold === 'function' && staged._isCoherentlyOrientedManifold() !== true;
- finalOrientation = orientationWarning ? analyzeTriangleOrientation(staged) : null;
- if (!orientationWarning) break;
- }
- }
- if (orientationWarning && options.__raycastMajorityRetry !== true && useSmallThicknessOrientationRepair) {
- const noInternalCullRetry = retryWithoutInternalCull();
- if (noInternalCullRetry) return noInternalCullRetry;
- const relaxedWeldRetry = retryWithRelaxedWeld();
- if (relaxedWeldRetry) return relaxedWeldRetry;
- const retry = retryWithRaycastMajority();
- if (retry) return retry;
- throw new Error(
- 'Face.thicken() triangle split/cull produced a non-coherently-oriented manifold result: '
- + `sameDirection=${finalOrientation.sameDirectionEdgeCount}, ambiguous=${finalOrientation.ambiguousEdgeCount}.`,
- );
- }
+ const result = new Solid();
+ result.name = solidName;
+ setOccState(result, occState);
+ applySourceFaceMetadataToThickenResult(result, face, sourceFaceName);
+ result.__thickenMethod = 'occ_prism';
+ result.__thickenClassificationMethod = 'occ_face_classifier';
+ result.__thickenDiagnostics = {
+ boundaryLoopCount: boundaryLoops.length,
+ sourceFaceCount: 1,
+ sourceFaceNames: [sourceFaceName],
+ sourceFaceName,
+ distance: dist,
+ classificationMethod: 'occ_face_classifier',
+ buildMethod: result.__thickenMethod,
+ constructionMethod: 'BRepPrimAPI_MakePrism',
+ kernel: 'opencascade',
+ };
+ return result;
+}
- applySourceFaceMetadataToThickenResult(staged, face, labels, sourceFaceName);
- staged.__thickenMethod = 'triangle_split_cull';
- staged.__thickenClassificationMethod = 'raw_face_ids';
- staged.__thickenDiagnostics = {
- boundaryLoopCount: loops.length,
- sourceTriangleCount: Array.isArray(surface.triangles) ? surface.triangles.length : 0,
- sourceFaceCount: sourceFaceNames.length,
- sourceFaceNames,
- resultTriangleCount: ((staged._triVerts?.length || 0) / 3) | 0,
- sourceFaceName,
- distance: dist,
- classificationMethod: 'raw_face_ids',
- buildMethod: staged.__thickenMethod,
- constructionMethod: useTrianglePrismUnion ? 'triangle_prism_union' : 'stitched_shell',
- trianglePrismUnionRetry: options.__trianglePrismUnionRetry === true,
- trianglePrismUnionReason: options.__trianglePrismUnionReason || '',
- repairBoundaryCapsEnabled: allowBoundaryRepairCaps,
- weldEpsilon: manifoldWeldEpsilon,
- triangleSplitCount: cleanup?.splitCount || 0,
- culledTriangleCount: cleanup?.culledTriangleCount || 0,
- internalTriangleCullSkipped: options.skipInternalCull === true,
- internalTriangleCullRetry: options.__skipInternalCullRetry === true,
- relaxedWeldRetry: options.__relaxedWeldRetry === true,
- degenerateTriangleCount: cleanup?.degenerateTriangleCount || 0,
- weldedVertexCount: cleanup?.weldedVertexCount || 0,
- orientedTriangleCount,
- orientationCulledTriangleCount,
- orientationCapTriangleCount,
- orientationCandidateRepairAccepted,
- orientationRepairDistanceLimit,
- orientationWarning,
- orientationSameDirectionEdgeCount: finalOrientation?.sameDirectionEdgeCount || 0,
- orientationAmbiguousEdgeCount: finalOrientation?.ambiguousEdgeCount || 0,
- nonManifoldCulledTriangleCount,
- boundaryCapTriangleCount,
- splitCullPasses: cleanup?.passCount || 0,
- adjacentBoundaryNormalContributionCount: surface.adjacentNormalStats?.contributionCount || 0,
- adjacentBoundaryNormalVertexCount: surface.adjacentNormalStats?.contributedVertexCount || 0,
- adjacentBoundaryNormalCandidateEdgeCount: surface.adjacentNormalStats?.candidateEdges || 0,
- adjacentBoundaryNormalAcceptedEdgeCount: surface.adjacentNormalStats?.acceptedEdges || 0,
- };
- staged.userData = {
- ...(staged.userData || {}),
- thicken: {
- sourceFaceName,
- sourceFaceNames,
- sourceFaceCount: sourceFaceNames.length,
- distance: dist,
- boundaryLoopCount: loops.length,
- sourceTriangleCount: Array.isArray(surface.triangles) ? surface.triangles.length : 0,
- resultTriangleCount: ((staged._triVerts?.length || 0) / 3) | 0,
- classificationMethod: 'raw_face_ids',
- buildMethod: staged.__thickenMethod,
- weldEpsilon: manifoldWeldEpsilon,
- triangleSplitCount: cleanup?.splitCount || 0,
- culledTriangleCount: cleanup?.culledTriangleCount || 0,
- internalTriangleCullSkipped: options.skipInternalCull === true,
- internalTriangleCullRetry: options.__skipInternalCullRetry === true,
- relaxedWeldRetry: options.__relaxedWeldRetry === true,
- degenerateTriangleCount: cleanup?.degenerateTriangleCount || 0,
- weldedVertexCount: cleanup?.weldedVertexCount || 0,
- orientedTriangleCount,
- orientationCulledTriangleCount,
- orientationCapTriangleCount,
- orientationCandidateRepairAccepted,
- orientationRepairDistanceLimit,
- orientationWarning,
- orientationSameDirectionEdgeCount: finalOrientation?.sameDirectionEdgeCount || 0,
- orientationAmbiguousEdgeCount: finalOrientation?.ambiguousEdgeCount || 0,
- nonManifoldCulledTriangleCount,
- boundaryCapTriangleCount,
- splitCullPasses: cleanup?.passCount || 0,
- adjacentBoundaryNormalContributionCount: surface.adjacentNormalStats?.contributionCount || 0,
- adjacentBoundaryNormalVertexCount: surface.adjacentNormalStats?.contributedVertexCount || 0,
- },
- };
- const result = staged;
- staged = null;
- return result;
- } finally {
- try { staged?.free?.(); } catch { /* ignore */ }
+function makeSelectedOccFacePrismThicken(face, distance, options = {}) {
+ const parentSolid = getParentOccSolid(face);
+ const faceName = getFaceLabel(face);
+ if (!parentSolid || !faceName) return null;
+ const dist = Number(distance);
+ if (!Number.isFinite(dist) || Math.abs(dist) <= EPS) {
+ throw new Error('Face.thicken() requires a non-zero finite distance.');
}
+ const featureId = sanitizeToken(options.featureId || options.name || faceName || 'THICKEN', 'THICKEN');
+ const sourceFaceName = String(options.sourceFaceName || faceName || featureId).trim() || featureId;
+ const solidName = String(options.name || featureId).trim() || featureId;
+ const occState = makeFacePrismFromOccSolid(parentSolid, faceName, {
+ distance: dist,
+ sourceFaceName,
+ featureID: featureId,
+ });
+ if (!occState) return null;
+
+ const result = new Solid();
+ result.name = solidName;
+ setOccState(result, occState);
+ applySourceFaceMetadataToThickenResult(result, face, sourceFaceName);
+ result.__thickenMethod = 'occ_prism';
+ result.__thickenClassificationMethod = 'occ_face_classifier';
+ result.__thickenDiagnostics = {
+ boundaryLoopCount: 0,
+ sourceFaceCount: 1,
+ sourceFaceNames: [sourceFaceName],
+ sourceFaceName,
+ distance: dist,
+ classificationMethod: 'occ_face_classifier',
+ buildMethod: result.__thickenMethod,
+ constructionMethod: 'BRepPrimAPI_MakePrism',
+ kernel: 'opencascade',
+ };
+ return result;
}
export function thickenFaceToSolid(face, distance, options = {}) {
- const surface = extractFaceSurface(face, options);
- const sourceFaceName = String(options.sourceFaceName || face?.userData?.faceName || face?.name || '').trim();
- return thickenSurfaceToSolid(surface, face, distance, {
- ...options,
- sourceFaceNames: Array.isArray(options.sourceFaceNames)
- ? options.sourceFaceNames
- : (sourceFaceName ? [sourceFaceName] : undefined),
- });
+ const occSolid = makeSelectedOccFacePrismThicken(face, distance, options)
+ || makeLoopPrismThicken(face, distance, options);
+ if (occSolid) return occSolid;
+ throw new Error('Face.thicken() requires an OpenCascade BREP face or authored boundary loops.');
}
-export function thickenFacesToSolid(faces, distance, options = {}) {
- const faceList = Array.isArray(faces)
- ? faces.filter((face) => face?.geometry)
- : [];
- if (!faceList.length) {
- throw new Error('Face.thicken() requires at least one face with geometry.');
- }
- const sourceFaceNames = faceList.map((face, index) => (
- String(face?.userData?.faceName || face?.name || `FACE_${index + 1}`).trim() || `FACE_${index + 1}`
- ));
- const featureId = sanitizeToken(options.featureId || options.name || sourceFaceNames[0] || 'THICKEN', 'THICKEN');
- const sourceFaceName = String(options.sourceFaceName || `${featureId}_PATCH`).trim() || `${featureId}_PATCH`;
- const surface = extractFacesSurface(faceList, options);
- return thickenSurfaceToSolid(surface, faceList[0], distance, {
- ...options,
- sourceFaceName,
- sourceFaceNames,
- });
+export function thickenFacesToSolid() {
+ throw new Error('Face.thicken() only supports one OpenCascade BREP face at a time.');
}
diff --git a/src/BREP/occTriangulationSettings.js b/src/BREP/occTriangulationSettings.js
new file mode 100644
index 00000000..aa8660c4
--- /dev/null
+++ b/src/BREP/occTriangulationSettings.js
@@ -0,0 +1,38 @@
+export const DEFAULT_OCC_TRIANGULATION_DEFLECTION = 0.15;
+export const DEFAULT_OCC_TRIANGULATION_ANGLE = 0.5;
+export const DEFAULT_OCC_VISUALIZATION_QUALITY = 1;
+export const MIN_OCC_VISUALIZATION_QUALITY = 0.25;
+export const MAX_OCC_VISUALIZATION_QUALITY = 20;
+export const DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES = 128;
+export const MAX_OCC_VISUALIZATION_CURVE_SAMPLES = 8193;
+
+let occVisualizationQuality = DEFAULT_OCC_VISUALIZATION_QUALITY;
+
+export function normalizeOccVisualizationQuality(value) {
+ const quality = Number(value);
+ if (!Number.isFinite(quality)) return DEFAULT_OCC_VISUALIZATION_QUALITY;
+ return Math.min(MAX_OCC_VISUALIZATION_QUALITY, Math.max(MIN_OCC_VISUALIZATION_QUALITY, quality));
+}
+
+export function getOccVisualizationQuality() {
+ return occVisualizationQuality;
+}
+
+export function setOccVisualizationQuality(value) {
+ occVisualizationQuality = normalizeOccVisualizationQuality(value);
+ return occVisualizationQuality;
+}
+
+export function getOccVisualizationTriangulationOptions() {
+ const quality = normalizeOccVisualizationQuality(occVisualizationQuality);
+ return {
+ deflection: DEFAULT_OCC_TRIANGULATION_DEFLECTION / quality,
+ angle: DEFAULT_OCC_TRIANGULATION_ANGLE / Math.sqrt(quality),
+ };
+}
+
+export function getOccVisualizationCurveSampleCount(base = DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES) {
+ const quality = normalizeOccVisualizationQuality(occVisualizationQuality);
+ const count = Math.ceil((Number(base) || DEFAULT_OCC_VISUALIZATION_CURVE_SAMPLES) * quality);
+ return Math.max(2, Math.min(MAX_OCC_VISUALIZATION_CURVE_SAMPLES, count));
+}
diff --git a/src/BREP/polyhedralOffset.js b/src/BREP/polyhedralOffset.js
index 9003eb5e..98ef15ca 100644
--- a/src/BREP/polyhedralOffset.js
+++ b/src/BREP/polyhedralOffset.js
@@ -8,7 +8,7 @@ import {
syncSolidAuthoringStateFromCpp,
} from './CppSolidCore.js';
import { MeshRepairer } from './MeshRepairer.js';
-import { Manifold } from './SolidShared.js';
+import { reserveFaceID } from './faceIdAllocator.js';
import { manifold } from './setupManifold.js';
const PLANE_EPS = 1e-5;
@@ -603,7 +603,7 @@ function _assignFaceDataByTriangleProximity(geometry, sourceMeta) {
let faceName = String(bestName || '').trim() || nextFallbackName();
let id = nameToID.get(faceName);
if (!id) {
- id = Manifold.reserveIDs(1);
+ id = reserveFaceID();
nameToID.set(faceName, id);
idToName.set(id, faceName);
}
diff --git a/src/BREP/primitives.js b/src/BREP/primitives.js
index d0351902..575c64e1 100644
--- a/src/BREP/primitives.js
+++ b/src/BREP/primitives.js
@@ -1,24 +1,21 @@
import { Solid } from './BetterSolid.js';
-import { applySolidAuthoringStateSnapshot } from './CppSolidCore.js';
-import { manifold } from './setupManifold.js';
+import {
+ makeBox,
+ makeCone,
+ makeCylinder,
+ makePyramid,
+ makeSphere,
+ makeTorus,
+ setOccState,
+} from './OpenCascadeKernel.js';
function hasNativePrimitiveBuilder() {
- return typeof manifold?.buildPrimitiveAuthoringState === 'function';
+ return true;
}
function requireNativePrimitiveBuilder() {
if (hasNativePrimitiveBuilder()) return;
- throw new Error('Primitive generation requires the custom local manifold build with native primitive support.');
-}
-
-function applyNativePrimitiveSnapshot(target, snapshot, name) {
- applySolidAuthoringStateSnapshot(target, snapshot, { remapFaceIDs: true });
- target._dirty = true;
- target._manifold = null;
- target._faceIndex = null;
- target._auxEdges = [];
- target.name = name || 'Solid';
- return target;
+ throw new Error('Primitive generation requires OpenCASCADE support.');
}
class PrimitiveBase extends Solid {
@@ -32,16 +29,22 @@ class PrimitiveBase extends Solid {
buildNativeSnapshot() {
requireNativePrimitiveBuilder();
- return manifold.buildPrimitiveAuthoringState({
- kind: this._primitiveKind,
- ...this.params,
- name: this.params?.name || this.name || 'Solid',
- });
+ const params = { ...this.params, name: this.params?.name || this.name || 'Solid' };
+ if (this._primitiveKind === 'cube') return makeBox(params);
+ if (this._primitiveKind === 'cylinder') return makeCylinder(params);
+ if (this._primitiveKind === 'cone') return makeCone(params);
+ if (this._primitiveKind === 'pyramid') return makePyramid(params);
+ if (this._primitiveKind === 'sphere') return makeSphere(params);
+ if (this._primitiveKind === 'torus') return makeTorus(params);
+ throw new Error(`OpenCASCADE primitive "${this._primitiveKind}" is not implemented.`);
}
generate() {
- const snapshot = this.buildNativeSnapshot();
- return applyNativePrimitiveSnapshot(this, snapshot, this.params?.name);
+ const state = this.buildNativeSnapshot();
+ setOccState(this, state);
+ this._auxEdges = [];
+ this.name = this.params?.name || 'Solid';
+ return this;
}
}
diff --git a/src/BREP/setupOpenCascade.js b/src/BREP/setupOpenCascade.js
new file mode 100644
index 00000000..99cb0b26
--- /dev/null
+++ b/src/BREP/setupOpenCascade.js
@@ -0,0 +1,32 @@
+const isNode =
+ typeof window === "undefined" ||
+ !!globalThis?.process?.versions?.node;
+
+const loadOpenCascade = async () => {
+ if (isNode) {
+ const [{ createRequire }, { dirname, resolve }, { fileURLToPath }, { readFile }] = await Promise.all([
+ import("node:module"),
+ import("node:path"),
+ import("node:url"),
+ import("node:fs/promises"),
+ ]);
+ const here = dirname(fileURLToPath(import.meta.url));
+ const occDistDir = resolve(here, "../../vendor/opencascade.js/dist");
+ globalThis.__BREP_OCC_REQUIRE = createRequire(import.meta.url);
+ globalThis.__BREP_OCC_DIRNAME = `${occDistDir}/`;
+ const { default: opencascadeFactory } = await import("../../vendor/opencascade.js/dist/opencascade.wasm.js");
+ const wasmBinary = await readFile(resolve(occDistDir, "opencascade.wasm.wasm"));
+ return new opencascadeFactory({ wasmBinary });
+ }
+
+ const { default: opencascadeFactory } = await import("../../vendor/opencascade.js/dist/opencascade.wasm.js");
+ const { default: wasmUrl } = await import("../../vendor/opencascade.js/dist/opencascade.wasm.wasm?url");
+ return new opencascadeFactory({
+ locateFile(path) {
+ return path.endsWith(".wasm") ? wasmUrl : path;
+ },
+ });
+};
+
+export const OpenCascade = await loadOpenCascade();
+export const openCascadeKernelName = "OpenCASCADE";
diff --git a/src/BREP/threadGeometry.js b/src/BREP/threadGeometry.js
index b9a22ea4..0b48730b 100644
--- a/src/BREP/threadGeometry.js
+++ b/src/BREP/threadGeometry.js
@@ -1,7 +1,8 @@
// threadGeometry.js
// Unified thread geometry helper for multiple standards in a single ES6 class.
import { Solid } from "./BetterSolid.js";
-import { Manifold, THREE } from "./SolidShared.js";
+import { THREE } from "./SolidShared.js";
+import { makeLoft, setOccState } from "./OpenCascadeKernel.js";
export const ThreadStandard = {
ISO_METRIC: "ISO_METRIC", // 60° V, ISO 68-1 style basic metric
@@ -15,6 +16,11 @@ export const ThreadStandard = {
const DEG_TO_RAD = Math.PI / 180;
const EPS = 1e-9;
+const DEBUG_THREAD_GEOMETRY = false;
+
+const threadDebugLog = (...args) => {
+ if (DEBUG_THREAD_GEOMETRY) console.log(...args);
+};
const computeInternalOverlap = (thread) => {
const pitch = Math.abs(thread?.pitch || 0);
@@ -25,14 +31,6 @@ const computeInternalOverlap = (thread) => {
return Math.max(EPS * 10, Math.min(cap, base) || minVal);
};
-const safeDelete = (obj) => {
- try {
- if (obj && typeof obj.delete === "function") obj.delete();
- } catch {
- /* ignore */
- }
-};
-
const vecFrom = (v, fallback = [0, 0, 0]) => {
if (v && typeof v.x === "number") return new THREE.Vector3(v.x, v.y, v.z);
if (Array.isArray(v)) {
@@ -60,88 +58,39 @@ const buildPlacementMatrix = ({ axis, axisDirection, xDirection, origin } = {})
return m;
};
-const manifoldToSolid = (manifold, name = "Thread", faceName = "THREAD", idToFaceName = null) => {
- if (!manifold) return null;
+const occStateToSolid = (state, name = "Thread") => {
+ if (!state) return null;
const solid = new Solid();
solid.name = name;
+ setOccState(solid, state);
+ return solid;
+};
- const faceIdMap = (() => {
- if (idToFaceName instanceof Map) return new Map(idToFaceName);
- if (idToFaceName && typeof idToFaceName === "object") return new Map(Object.entries(idToFaceName));
- return new Map();
- })();
-
- const mesh = manifold.getMesh();
- let addedCount = 0;
- let failedCount = 0;
- try {
- const vp = mesh.vertProperties;
- const tv = mesh.triVerts;
- const triCount = Math.floor(tv.length / 3);
- const faceIDs = mesh.faceID && mesh.faceID.length === triCount ? mesh.faceID : null;
-
- if (faceIdMap.size === 0) {
- if (faceIDs) {
- const seen = new Set();
- for (let t = 0; t < triCount; t++) {
- const fid = faceIDs[t] >>> 0;
- if (seen.has(fid)) continue;
- seen.add(fid);
- faceIdMap.set(fid, faceName || `FACE_${fid}`);
- }
- } else {
- faceIdMap.set(0, faceName || "FACE_0");
- }
- }
-
- console.log('[ThreadGeometry] manifoldToSolid: converting', triCount, 'triangles to solid with', faceIdMap.size, 'face labels');
- for (let t = 0; t < triCount; t++) {
- const i0 = tv[3 * t + 0] >>> 0;
- const i1 = tv[3 * t + 1] >>> 0;
- const i2 = tv[3 * t + 2] >>> 0;
- const p0 = [vp[i0 * 3 + 0], vp[i0 * 3 + 1], vp[i0 * 3 + 2]];
- const p1 = [vp[i1 * 3 + 0], vp[i1 * 3 + 1], vp[i1 * 3 + 2]];
- const p2 = [vp[i2 * 3 + 0], vp[i2 * 3 + 1], vp[i2 * 3 + 2]];
-
- const fid = faceIDs ? (faceIDs[t] >>> 0) : 0;
- let triFaceName = faceIdMap.get(fid);
- if (!triFaceName) {
- const fallbackName = faceIdMap.size === 0 ? (faceName || `FACE_${fid}`) : `FACE_${fid}`;
- triFaceName = fallbackName;
- faceIdMap.set(fid, triFaceName);
- }
-
- try {
- solid.addTriangle(triFaceName, p0, p1, p2);
- addedCount++;
- } catch (err) {
- failedCount++;
- if (failedCount <= 3) {
- console.warn('[ThreadGeometry] Failed to add triangle', t, ':', err.message, { p0, p1, p2, face: triFaceName });
- }
- }
- }
- console.log('[ThreadGeometry] manifoldToSolid: added', addedCount, 'triangles, failed', failedCount);
- console.log('[ThreadGeometry] manifoldToSolid: solid internals:', {
- triVertsLength: solid._triVerts?.length,
- triIDsLength: solid._triIDs?.length,
- vertPropertiesLength: solid._vertProperties?.length,
- faceCount: solid._faceNameToID?.size,
- });
- // Force manifoldization to catch any issues early
- try {
- solid._manifoldize();
- console.log('[ThreadGeometry] manifoldToSolid: manifoldization successful');
- } catch (err) {
- console.warn('[ThreadGeometry] manifoldToSolid: manifoldization failed:', err);
- }
- } finally {
- safeDelete(mesh);
- safeDelete(manifold);
+const makeCircularSection = (radius, z, resolution) => {
+ const points = [];
+ const count = Math.max(8, Math.floor(Number(resolution) || 64));
+ for (let i = 0; i < count; i += 1) {
+ const a = (i / count) * Math.PI * 2;
+ points.push([radius * Math.cos(a), radius * Math.sin(a), z]);
}
- return solid;
+ return points;
};
+const makeThreadLoftSolid = ({
+ sections,
+ sideNames,
+ startName,
+ endName,
+ featureID,
+ name,
+}) => occStateToSolid(makeLoft({
+ sections,
+ sideNames,
+ startName,
+ endName,
+ featureID,
+}), name);
+
const buildThreadProfilePolygon = (thread, radialOffset = 0) => {
const external = thread.isExternal === true;
let crestR = Math.max(EPS, (thread.crestRadius || 0) + radialOffset);
@@ -159,17 +108,17 @@ const buildThreadProfilePolygon = (thread, radialOffset = 0) => {
// The profile should be positioned at the thread radius and shaped like the tooth cross-section
const pitch = thread.pitch || 1;
- const halfPitch = pitch / 2;
+ const halfPitch = pitch * 0.49;
let crestRad = crestR;
if (!external) {
const overlap = computeInternalOverlap(thread);
const targetCrest = crestR - overlap;
crestRad = Math.max(EPS, Math.min(targetCrest, rootR - EPS));
- console.log('[ThreadGeometry] Applying internal overlap:', { overlap, crestR, crestRad, rootR });
+ threadDebugLog('[ThreadGeometry] Applying internal overlap:', { overlap, crestR, crestRad, rootR });
}
- console.log('[ThreadGeometry] Profile polygon params:', {
+ threadDebugLog('[ThreadGeometry] Profile polygon params:', {
external,
crestR,
crestRad,
@@ -193,7 +142,7 @@ const buildThreadProfilePolygon = (thread, radialOffset = 0) => {
[halfPitch, crestRad], // Top: inner surface
];
- console.log('[ThreadGeometry] Profile polygon points [axial, radius]:', pts);
+ threadDebugLog('[ThreadGeometry] Profile polygon points [axial, radius]:', pts);
return pts; // Return as-is, no need for CCW check in this format
};
@@ -206,9 +155,9 @@ const applyPlacement = (solid, opts = {}) => {
}
if (transform && (transform.position || transform.rotationEuler || transform.scale || transform.t || transform.rDeg)) {
solid.bakeTRS({
- t: transform.position || transform.t,
- rDeg: transform.rotationEuler || transform.rDeg,
- s: transform.scale || transform.s,
+ position: transform.position || transform.t,
+ rotationEuler: transform.rotationEuler || transform.rDeg,
+ scale: transform.scale || transform.s,
});
return solid;
}
@@ -465,7 +414,7 @@ export class ThreadGeometry {
case ThreadStandard.WHITWORTH: {
// Whitworth:
// included angle = 55°
- // fundamental triangle height H ≈ 0.96049106 * P
+ // fundamental profile height H ~= 0.96049106 * P
// actual depth h ≈ 0.64032738 * P
// crest/root rounding radius r ≈ 0.13732908 * P
// rounding height e ≈ 0.073917569 * P
@@ -554,8 +503,7 @@ export class ThreadGeometry {
* @param {boolean} [options.modeled] Shortcut for mode='modeled'
* @param {number} [options.radialOffset=0] Radial offset/clearance applied to crest/root
* @param {number} [options.resolution=64] Circular resolution for symbolic/core geometry
- * @param {number} [options.segmentsPerTurn=12] Vertical divisions per revolution for modeled threads
- * @param {boolean} [options.includeCore=true] Include the core cylinder/cone (set false to emit ridges only)
+ * @param {boolean} [options.includeCore=false] Kept for compatibility; modeled threads emit ridges only.
* @param {'crest'|'root'|'pitch'} [options.symbolicRadius='crest'] Which diameter to use for symbolic threads
* @param {string} [options.name='Thread'] Solid name
* @param {string} [options.faceName='THREAD'] Face label
@@ -660,10 +608,20 @@ export class ThreadGeometry {
const overlap = computeInternalOverlap(this);
r0 = Math.max(EPS, r0 - overlap);
r1 = Math.max(EPS, r1 - overlap);
- console.log('[ThreadGeometry] Applying internal overlap to symbolic thread:', { overlap, r0, r1, radialOffset });
+ threadDebugLog('[ThreadGeometry] Applying internal overlap to symbolic thread:', { overlap, r0, r1, radialOffset });
}
- const manifold = Manifold.cylinder(length, r0, r1, res, false);
- const solid = manifoldToSolid(manifold, name, faceName);
+ const sections = [
+ makeCircularSection(r0, 0, res),
+ makeCircularSection(r1, length, res),
+ ];
+ const solid = makeThreadLoftSolid({
+ sections,
+ sideNames: Array.from({ length: sections[0].length }, () => faceName),
+ startName: `${faceName}:CAP_START`,
+ endName: `${faceName}:CAP_END`,
+ featureID: faceName,
+ name,
+ });
// Add centerline through the symbolic thread (matches minor diameter cylinder axis)
solid.addAuxEdge(`${faceName}:CENTERLINE`, [
@@ -697,13 +655,17 @@ export class ThreadGeometry {
const name = options.name || "Thread";
const faceName = options.faceName || "THREAD";
const radialOffset = Number(options.radialOffset ?? options.clearance ?? 0);
- const includeCore = options.includeCore !== false;
- const res = Math.max(8, Math.floor(Number(options.resolution) || 64));
- const segmentsPerTurn = Math.max(4, Math.floor(Number(options.segmentsPerTurn ?? options.divisionsPerTurn ?? 12)));
- const turns = Math.max(EPS, length / Math.max(this.lead, EPS));
- const nDiv = Math.max(1, Math.round(turns * segmentsPerTurn));
-
- console.log('[ThreadGeometry] Building modeled solid procedurally with triangles:', {
+ const segmentsPerTurn = Math.max(8, Math.floor(Number(options.segmentsPerTurn ?? options.divisionsPerTurn ?? 16)));
+ const toothProfilePts = buildThreadProfilePolygon(this, radialOffset);
+ const axialValues = toothProfilePts.map((pt) => Number(pt?.[0]) || 0);
+ const minAxial = Math.min(...axialValues);
+ const maxAxial = Math.max(...axialValues);
+ const axialSpan = Math.max(0, maxAxial - minAxial);
+ const sweepLength = Math.max(EPS, length - axialSpan);
+ const turns = Math.max(EPS, sweepLength / Math.max(this.lead, EPS));
+ const nDiv = Math.max(2, Math.ceil(turns * segmentsPerTurn));
+
+ threadDebugLog('[ThreadGeometry] Building modeled solid with OCCT helical loft:', {
length,
turns,
nDiv,
@@ -716,16 +678,10 @@ export class ThreadGeometry {
effectiveThreadDepth: this.effectiveThreadDepth,
});
- // Get the 2D profile polygon (trapezoidal cross-section of thread tooth)
- const profilePts = buildThreadProfilePolygon(this, radialOffset);
- console.log('[ThreadGeometry] Thread profile (side view):', profilePts);
- console.log('[ThreadGeometry] Profile point 0:', profilePts[0]);
-
- // Build thread geometry procedurally by sweeping profile along helix
- const threadSolid = new Solid({ name, faceName });
-
- // Profile has points in [axial_offset, radius] format
- // We'll sweep this profile around the cylinder axis along a helical path
+ const profilePts = toothProfilePts;
+ threadDebugLog('[ThreadGeometry] Thread profile (side view):', profilePts);
+ threadDebugLog('[ThreadGeometry] Profile point 0:', profilePts[0]);
+
const numProfilePts = profilePts.length;
// Give each profile edge its own face label so swept surfaces stay distinct
@@ -746,141 +702,101 @@ export class ThreadGeometry {
start: { name: `${faceName}:CAP_START`, role: "start_cap" },
end: { name: `${faceName}:CAP_END`, role: "end_cap" },
};
-
- // Store profile vertices at each step for end capping
- const profileRings = [];
-
- for (let i = 0; i <= nDiv; i++) { // Note: <= to include final position
- const t = i / nDiv;
- const angle = t * 360 * turns; // degrees
- const z = t * length;
- const rad = angle * Math.PI / 180;
-
- // Transform all profile points to 3D at this angle
- const ring = [];
- for (let j = 0; j < numProfilePts; j++) {
- const [axial, r] = profilePts[j];
- ring.push([
- r * Math.cos(rad),
- r * Math.sin(rad),
- z + axial
- ]);
- }
- profileRings.push(ring);
-
- // Create quads between this ring and previous ring
- if (i > 0) {
- const prevRing = profileRings[i - 1];
- const currRing = profileRings[i];
-
- for (let j = 0; j < numProfilePts; j++) {
- const j_next = (j + 1) % numProfilePts;
-
- const p0 = prevRing[j];
- const p1 = prevRing[j_next];
- const p2 = currRing[j];
- const p3 = currRing[j_next];
- const quadFaceName = segmentDescriptors[j]?.name || `${faceName}:EDGE_${j}`;
-
- // Create two triangles for this quad (CCW winding)
- threadSolid.addTriangle(quadFaceName, p0, p1, p2);
- threadSolid.addTriangle(quadFaceName, p1, p3, p2);
- }
- }
- }
-
- // Add end caps to close the geometry
- // The profile is a quadrilateral, so we need to triangulate it properly
- // Start cap (at i=0) - fan triangulation from first vertex
- const startRing = profileRings[0];
- for (let j = 1; j < numProfilePts - 1; j++) {
- threadSolid.addTriangle(capDescriptors.start.name, startRing[0], startRing[j + 1], startRing[j]);
- }
-
- // End cap (at i=nDiv) - fan triangulation from first vertex (opposite winding)
- const endRing = profileRings[nDiv];
- for (let j = 1; j < numProfilePts - 1; j++) {
- threadSolid.addTriangle(capDescriptors.end.name, endRing[0], endRing[j], endRing[j + 1]);
- }
-
- const numTriangles = threadSolid._triVerts ? threadSolid._triVerts.length / 3 : 0;
- console.log('[ThreadGeometry] Generated', numTriangles, 'triangles procedurally (including end caps)');
-
- const faceIdToName = new Map(threadSolid._idToFaceName);
- const threadFaceNames = { segments: segmentDescriptors, caps: capDescriptors };
- let manifold = threadSolid._manifoldize();
- if (!manifold) {
- console.error('[ThreadGeometry] Failed to manifoldize procedurally generated thread');
- return threadSolid;
- }
+ const threadFaceNames = { segments: segmentDescriptors, caps: capDescriptors };
- if (this.isTapered && Math.abs(this.taperPerLengthOnDiameter) > EPS) {
- const radialDeltaPerLen = 0.5 * this.taperPerLengthOnDiameter * this.taperDirection;
- const warped = manifold.warp((vert) => {
- const z = vert[2];
- const deltaR = radialDeltaPerLen * z;
- const r = Math.hypot(vert[0], vert[1]);
- if (r > EPS) {
- const s = (r + deltaR) / r;
- vert[0] *= s;
- vert[1] *= s;
+ if (!this.isExternal) {
+ const angularSegments = Math.max(24, Math.floor(Number(options.resolution) || 64));
+ const axialSections = Math.max(3, nDiv + 1);
+ const lead = Math.max(this.lead, EPS);
+ const pitch = Math.max(this.pitch, EPS);
+ const profile = profilePts
+ .map(([axial, radius]) => [Number(axial) || 0, Math.max(EPS, Number(radius) || 0)])
+ .sort((a, b) => a[0] - b[0]);
+ const minA = profile[0][0];
+ const maxA = profile[profile.length - 1][0];
+ const spanA = Math.max(EPS, maxA - minA);
+ const radiusAtPhase = (phase) => {
+ let a = ((phase - minA) % pitch + pitch) % pitch + minA;
+ if (a > maxA) a -= pitch;
+ for (let i = 0; i < profile.length - 1; i += 1) {
+ const p0 = profile[i];
+ const p1 = profile[i + 1];
+ if (a >= p0[0] - EPS && a <= p1[0] + EPS) {
+ const t = Math.max(0, Math.min(1, (a - p0[0]) / Math.max(EPS, p1[0] - p0[0])));
+ return p0[1] + (p1[1] - p0[1]) * t;
+ }
}
+ const wrapped = a < minA + spanA * 0.5 ? profile[0] : profile[profile.length - 1];
+ return wrapped[1];
+ };
+ const taperRadiusAt = (z) => {
+ if (!this.isTapered || Math.abs(this.taperPerLengthOnDiameter || 0) <= EPS) return 0;
+ return 0.5 * this.taperPerLengthOnDiameter * this.taperDirection * z;
+ };
+ const handed = options.leftHanded === true ? -1 : 1;
+ const sections = [];
+ for (let i = 0; i < axialSections; i += 1) {
+ const z = (length * i) / Math.max(1, axialSections - 1);
+ const section = [];
+ for (let j = 0; j < angularSegments; j += 1) {
+ const theta = (Math.PI * 2 * j) / angularSegments;
+ const phase = z - handed * (theta / (Math.PI * 2)) * lead;
+ const radius = Math.max(EPS, radiusAtPhase(phase) + taperRadiusAt(z));
+ section.push([radius * Math.cos(theta), radius * Math.sin(theta), z]);
+ }
+ sections.push(section);
+ }
+ const solid = makeThreadLoftSolid({
+ sections,
+ sideNames: Array.from({ length: angularSegments }, (_, index) => `${faceName}:SIDE_${index}`),
+ startName: capDescriptors.start.name,
+ endName: capDescriptors.end.name,
+ featureID: faceName,
+ name,
});
- safeDelete(manifold);
- manifold = warped;
+ applyPlacement(solid, options);
+ solid.threadFaceNames = threadFaceNames;
+ return solid;
}
- if (this.starts && this.starts > 1) {
- const base = manifold;
- let combined = base;
- for (let k = 1; k < this.starts; k++) {
- const angle = (360 * k) / this.starts;
- const rotated = base.rotate(0, 0, angle);
- const next = combined.add(rotated);
- safeDelete(rotated);
- if (combined !== base) safeDelete(combined);
- combined = next;
+ const makeOneStart = (startIndex) => {
+ const phase = (Math.PI * 2 * startIndex) / Math.max(1, this.starts || 1);
+ const handed = options.leftHanded === true ? -1 : 1;
+ const taperRadiusAt = (z) => {
+ if (!this.isTapered || Math.abs(this.taperPerLengthOnDiameter || 0) <= EPS) return 0;
+ return 0.5 * this.taperPerLengthOnDiameter * this.taperDirection * z;
+ };
+ const sections = [];
+ for (let i = 0; i <= nDiv; i += 1) {
+ const t = i / nDiv;
+ const angle = phase + handed * Math.PI * 2 * turns * t;
+ const c = Math.cos(angle);
+ const s = Math.sin(angle);
+ const zBase = -minAxial + sweepLength * t;
+ const section = profilePts.map(([axialRaw, radiusRaw]) => {
+ const axial = Number(axialRaw) || 0;
+ const z = zBase + axial;
+ const radius = Math.max(EPS, (Number(radiusRaw) || 0) + taperRadiusAt(z));
+ return [radius * c, radius * s, z];
+ });
+ sections.push(section);
}
- if (combined !== base) safeDelete(base);
- manifold = combined;
- }
-
- if (includeCore) {
- const d0 = this.diametersAtZ(0);
- const d1 = this.diametersAtZ(length);
- // For internal threads, the core needs to be slightly smaller than crest radius
- // to avoid overlapping geometry. Use 95% of the minor diameter.
- const coreR0 = Math.max(
- EPS,
- (d0.minor * 0.5 + radialOffset) * 0.95,
- );
- const coreR1 = Math.max(
- EPS,
- (d1.minor * 0.5 + radialOffset) * 0.95,
- );
- console.log('[ThreadGeometry] Adding core cylinder:', {
- isExternal: this.isExternal,
- coreR0,
- coreR1,
- minorDiameter0: d0.minor,
- majorDiameter0: d0.major,
- crestRadius: this.crestRadius,
+ return makeThreadLoftSolid({
+ sections,
+ sideNames: segmentDescriptors.map((entry) => entry.name),
+ startName: capDescriptors.start.name,
+ endName: capDescriptors.end.name,
+ featureID: faceName,
+ name: startIndex === 0 ? name : `${name}_START_${startIndex + 1}`,
});
- try {
- const core = Manifold.cylinder(length, coreR0, coreR1, res, false);
- const merged = manifold.add(core);
- safeDelete(core);
- safeDelete(manifold);
- manifold = merged;
- console.log('[ThreadGeometry] Core added successfully');
- } catch (err) {
- console.warn('[ThreadGeometry] Failed to add core, continuing with thread ridges only:', err.message);
- // Continue with just the thread ridges if core addition fails
- }
+ };
+
+ let solid = makeOneStart(0);
+ for (let k = 1; k < Math.max(1, this.starts || 1); k += 1) {
+ solid = solid.union(makeOneStart(k));
}
- const solid = manifoldToSolid(manifold, name, faceName, faceIdToName);
applyPlacement(solid, options);
solid.threadFaceNames = threadFaceNames;
return solid;
diff --git a/src/FeatureRegistry.js b/src/FeatureRegistry.js
index 51f6c317..f3c13b81 100644
--- a/src/FeatureRegistry.js
+++ b/src/FeatureRegistry.js
@@ -1,7 +1,5 @@
import { BooleanFeature } from './features/boolean/BooleanFeature.js';
import { ChamferFeature } from './features/chamfer/ChamferFeature.js';
-import { CollapseEdgeFeature } from './features/collapseEdge/CollapseEdgeFeature.js';
-import { EdgeSmoothFeature } from './features/edgeSmooth/EdgeSmoothFeature.js';
import { DatiumFeature } from './features/datium/DatiumFeature.js';
import { ExtrudeFeature } from './features/extrude/ExtrudeFeature.js';
import { FilletFeature } from './features/fillet/FilletFeature.js';
@@ -16,14 +14,11 @@ import { PrimitiveSphereFeature } from './features/primitiveSphere/primitiveSphe
import { PrimitiveTorusFeature } from './features/primitiveTorus/primitiveTorusFeature.js';
import { RevolveFeature } from './features/revolve/RevolveFeature.js';
import { SketchFeature } from './features/sketch/SketchFeature.js';
-import { Import3dModelFeature } from './features/import3dModel/Import3dModelFeature.js';
import { SweepFeature } from './features/sweep/SweepFeature.js';
-import { RemeshFeature } from './features/remesh/RemeshFeature.js';
import { ImageToFaceFeature } from './features/imageToFace/ImageToFaceFeature.js';
import { ImageHeightmapSolidFeature } from './features/imageHeightSolid/ImageHeightmapSolidFeature.js';
import { TextToFaceFeature } from './features/textToFace/TextToFaceFeature.js';
import { TransformFeature } from './features/transform/TransformFeature.js';
-import { OverlapCleanupFeature } from './features/overlapCleanup/OverlapCleanupFeature.js';
import { HelixFeature } from './features/helix/HelixFeature.js';
import { HoleFeature } from './features/hole/HoleFeature.js';
import { PatternFeature } from './features/pattern/PatternFeature.js';
@@ -33,11 +28,9 @@ import { TubeFeature } from './features/tube/TubeFeature.js';
import { AssemblyComponentFeature } from './features/assemblyComponent/AssemblyComponentFeature.js';
import { OffsetShellFeature } from './features/offsetShell/OffsetShellFeature.js';
import { OffsetFaceFeature } from './features/offsetFace/OffsetFaceFeature.js';
-import { PushFaceFeature } from './features/pushFace/PushFaceFeature.js';
import { ThickenFeature } from './features/thicken/ThickenFeature.js';
import { NurbsFaceSolidFeature } from './features/nurbsFaceSolid/NurbsFaceSolidFeature.js';
import { PolygonSolidFeature } from './features/polygonSolid/PolygonSolidFeature.js';
-import { SmoothWithSubdivisionFeature } from './features/smoothWithSubdivision/SmoothWithSubdivisionFeature.js';
import { SplineFeature } from './features/spline/SplineFeature.js';
import { PortFeature } from './features/port/PortFeature.js';
import { SheetMetalTabFeature } from './features/sheetMetal/SheetMetalTabFeature.js';
@@ -87,7 +80,6 @@ export class FeatureRegistry {
this.register(PrimitiveSphereFeature);
this.register(PrimitiveTorusFeature);
this.register(PrimitivePyramidFeature);
- this.register(Import3dModelFeature);
this.register(SketchFeature);
this.register(SplineFeature);
this.register(PortFeature);
@@ -96,15 +88,11 @@ export class FeatureRegistry {
this.register(BooleanFeature);
this.register(FilletFeature);
this.register(ChamferFeature);
- this.register(CollapseEdgeFeature);
- this.register(EdgeSmoothFeature);
this.register(OffsetShellFeature);
this.register(OffsetFaceFeature);
- this.register(PushFaceFeature);
this.register(ThickenFeature);
this.register(NurbsFaceSolidFeature);
this.register(PolygonSolidFeature);
- this.register(SmoothWithSubdivisionFeature);
this.register(SheetMetalTabFeature);
this.register(SheetMetalContourFlangeFeature);
this.register(SheetMetalFlangeFeature);
@@ -116,12 +104,10 @@ export class FeatureRegistry {
this.register(SweepFeature);
this.register(HoleFeature);
this.register(TubeFeature);
- this.register(RemeshFeature);
this.register(ImageToFaceFeature);
this.register(ImageHeightmapSolidFeature);
this.register(TextToFaceFeature);
this.register(TransformFeature);
- this.register(OverlapCleanupFeature);
this.register(PatternLinearFeature);
this.register(PatternRadialFeature);
this.register(AssemblyComponentFeature);
@@ -137,25 +123,9 @@ export class FeatureRegistry {
this.aliases.set('HEIGHTMAP', ImageHeightmapSolidFeature);
this.aliases.set('HEIGHT MAP', ImageHeightmapSolidFeature);
this.aliases.set('IMAGE HEIGHTMAP', ImageHeightmapSolidFeature);
- // Import 3D Model (formerly STL Import)
- this.aliases.set('STL', Import3dModelFeature);
- this.aliases.set('STL IMPORT', Import3dModelFeature);
- this.aliases.set('STLIMPORT', Import3dModelFeature);
- this.aliases.set('STLIMPORTFEATURE', Import3dModelFeature);
// Text-to-Face variations
this.aliases.set('TEXT TO FACE', TextToFaceFeature);
this.aliases.set('TEXTTOFACE', TextToFaceFeature);
- // Collapse Edge typo/spacing variations
- this.aliases.set('COLAPSE EDGE', CollapseEdgeFeature);
- this.aliases.set('COLLAPSEEDGE', CollapseEdgeFeature);
- // Edge smooth spacing variations
- this.aliases.set('EDGE SMOOTH', EdgeSmoothFeature);
- this.aliases.set('EDGESMOOTH', EdgeSmoothFeature);
- // Smooth With Subdivision spacing variations
- this.aliases.set('SMOOTHWITHSUBDIVISION', SmoothWithSubdivisionFeature);
- this.aliases.set('SMOOTH SUBDIVISION', SmoothWithSubdivisionFeature);
- // Push Face variations
- this.aliases.set('PUSHFACE', PushFaceFeature);
// Thicken variations
this.aliases.set('THICKEN', ThickenFeature);
}
diff --git a/src/PartHistory.js b/src/PartHistory.js
index 3cec8db6..38134427 100644
--- a/src/PartHistory.js
+++ b/src/PartHistory.js
@@ -1106,13 +1106,184 @@ export class PartHistory {
return { added, removed };
}
+ #isActiveDialogFeature(featureID) {
+ if (featureID == null || this.currentHistoryStepId == null) return false;
+ return String(this.currentHistoryStepId) === String(featureID);
+ }
+
+ #isObjectInSceneGraph(obj) {
+ if (!obj || !this.scene) return false;
+ let current = obj;
+ while (current) {
+ if (current === this.scene) return true;
+ current = current.parent || null;
+ }
+ return false;
+ }
+
+ #makePreviewMaterial(material, opacity = 0.28, target = null) {
+ if (!material) return material;
+ if (Array.isArray(material)) return material.map((mat) => this.#makePreviewMaterial(mat, opacity, target));
+ let clone = material;
+ try {
+ if (material && typeof material.clone === 'function') clone = material.clone();
+ } catch {
+ clone = material;
+ }
+ try {
+ if (clone && typeof clone === 'object') {
+ const type = String(target?.type || '').toUpperCase();
+ const isEdgeLike = type.includes('EDGE') || type.includes('LINE');
+ const isPointLike = type.includes('VERTEX') || type.includes('POINT');
+ clone.transparent = true;
+ clone.opacity = isEdgeLike || isPointLike ? Math.max(0.78, opacity) : opacity;
+ clone.depthWrite = false;
+ clone.depthTest = false;
+ clone.polygonOffset = true;
+ clone.polygonOffsetFactor = -1;
+ clone.polygonOffsetUnits = -1;
+ clone.side = THREE.DoubleSide;
+ if (clone.color && typeof clone.color.set === 'function' && (isEdgeLike || isPointLike)) {
+ clone.color.set('#9fe7ff');
+ }
+ if (typeof clone.linewidth === 'number') clone.linewidth = Math.max(clone.linewidth, 3);
+ clone.needsUpdate = true;
+ }
+ } catch { /* ignore material preview failures */ }
+ return clone;
+ }
+
+ #setPreviewBaseMaterial(target, material) {
+ if (!target || !material) return;
+ try { target.material = material; } catch { }
+ try {
+ target.userData = target.userData || {};
+ target.userData.__baseMaterial = material;
+ target.userData.__defaultMaterial = material;
+ delete target.userData.__selectedMat;
+ delete target.userData.__selectedMatBase;
+ delete target.userData.__selectedColor;
+ delete target.userData.__hoverMatApplied;
+ delete target.userData.__hoverOrigMat;
+ delete target.userData.__hoverMat;
+ } catch { /* ignore selection material cache failures */ }
+ }
+
+ #markScenePreviewObject(obj, featureID, role = 'preview', { selectable = false } = {}) {
+ if (!obj || typeof obj !== 'object') return;
+ const normalizedFeatureId = featureID == null ? null : String(featureID);
+ const markOne = (target) => {
+ if (!target || typeof target !== 'object') return;
+ try {
+ target.userData = target.userData || {};
+ if (selectable) {
+ delete target.userData.excludeFromSelection;
+ } else {
+ target.userData.excludeFromSelection = true;
+ }
+ target.userData.featureDialogPreview = true;
+ target.userData.featureDialogPreviewRole = role;
+ if (normalizedFeatureId) target.userData.previewFeatureID = normalizedFeatureId;
+ } catch { /* ignore metadata failures */ }
+ if (!selectable) {
+ try { target.selected = false; } catch { }
+ try { target.hovered = false; } catch { }
+ }
+ if (role === 'removed') {
+ try { target.renderOrder = 10080; } catch { }
+ }
+ };
+ try {
+ if (typeof obj.traverse === 'function') obj.traverse(markOne);
+ else markOne(obj);
+ } catch {
+ markOne(obj);
+ }
+ }
+
+ #clearScenePreviewObject(obj) {
+ if (!obj || typeof obj !== 'object') return;
+ const clearOne = (target) => {
+ if (!target || typeof target !== 'object') return;
+ let wasPreview = false;
+ try {
+ const ud = target.userData || null;
+ if (ud) {
+ wasPreview = !!ud.featureDialogPreview;
+ delete ud.excludeFromSelection;
+ delete ud.featureDialogPreview;
+ delete ud.featureDialogPreviewRole;
+ delete ud.previewFeatureID;
+ }
+ } catch { /* ignore metadata cleanup failures */ }
+ if (wasPreview) {
+ try {
+ const ownRaycast = Object.prototype.hasOwnProperty.call(target, 'raycast');
+ if (ownRaycast) delete target.raycast;
+ } catch { /* ignore raycast cleanup failures */ }
+ }
+ };
+ try {
+ if (typeof obj.traverse === 'function') obj.traverse(clearOne);
+ else clearOne(obj);
+ } catch {
+ clearOne(obj);
+ }
+ }
+
+ async #createRemovalPreviewObject(obj, featureID) {
+ if (!obj || !this.#isObjectInSceneGraph(obj)) return null;
+ try {
+ obj.updateMatrixWorld?.(true);
+ } catch { /* ignore matrix refresh failures */ }
+ try {
+ const hasDrawableChildren = Array.isArray(obj.children) && obj.children.some((child) => (
+ child?.isObject3D && (child.geometry || child.material || child.type === 'FACE' || child.type === 'EDGE')
+ ));
+ if (!hasDrawableChildren && typeof obj.visualize === 'function') {
+ await obj.visualize();
+ }
+ } catch { /* keep whatever visualization already exists */ }
+
+ try {
+ obj.traverse?.((node) => {
+ if (!node || typeof node !== 'object') return;
+ if (node.material) {
+ const previewMaterial = this.#makePreviewMaterial(node.material, 0.24, node);
+ this.#setPreviewBaseMaterial(node, previewMaterial);
+ }
+ });
+ this.#markScenePreviewObject(obj, featureID, 'removed', { selectable: true });
+ } catch {
+ this.#markScenePreviewObject(obj, featureID, 'removed', { selectable: true });
+ }
+ try { SelectionFilter.ensureSelectionHandlers(obj, { deep: true }); } catch { }
+ try {
+ obj.traverse?.((node) => {
+ if (!node || typeof node !== 'object') return;
+ if (node.__selectionState) node.__selectionState.selected = false;
+ if (node.__selectionState) node.__selectionState.hovered = false;
+ if (typeof node.selected !== 'undefined') node.selected = false;
+ if (typeof node.hovered !== 'undefined') node.hovered = false;
+ });
+ } catch { /* ignore selection reset failures */ }
+ try { obj.renderOrder = 10080; } catch { }
+ return obj;
+ }
+
async applyFeatureEffects(effects, featureID, feature) {
if (!effects || typeof effects !== 'object') return;
const added = Array.isArray(effects.added) ? effects.added : [];
const removed = Array.isArray(effects.removed) ? effects.removed : [];
+ const activeDialogFeature = this.#isActiveDialogFeature(featureID);
+ const removalPreviews = [];
for (const r of removed) {
+ if (activeDialogFeature) {
+ const ghost = await this.#createRemovalPreviewObject(r, featureID);
+ if (ghost) removalPreviews.push(ghost);
+ }
await this._safeRemove(r);
}
@@ -1123,6 +1294,12 @@ export class PartHistory {
// Free first to force rebuild from latest arrays, then visualize
try { await a.free(); } catch { }
try { await a.visualize(); } catch { }
+ if (activeDialogFeature) {
+ try { this.#markScenePreviewObject(a, featureID, 'added'); } catch { }
+ } else {
+ try { this.#clearScenePreviewObject(a); } catch { }
+ }
+ try { SelectionFilter.ensureSelectionHandlers(a, { deep: true }); } catch { }
await this.scene.add(a);
// make sure the flag for removal is cleared
try { a.__removeFlag = false; } catch { }
@@ -1148,9 +1325,17 @@ export class PartHistory {
}
}
+ if (activeDialogFeature && removalPreviews.length) {
+ for (const ghost of removalPreviews) {
+ try { await this.scene.add(ghost); } catch { /* ignore preview add failures */ }
+ }
+ }
+
// apply the featureID to all added/removed items for traceability
try { for (const obj of added) { if (obj) obj.owningFeatureID = featureID; } } catch { }
- try { for (const obj of removed) { if (obj) obj.owningFeatureID = featureID; } } catch { }
+ if (!activeDialogFeature) {
+ try { for (const obj of removed) { if (obj) obj.owningFeatureID = featureID; } } catch { }
+ }
}
@@ -1762,7 +1947,6 @@ export class PartHistory {
operation: op ?? 'NONE',
targets,
biasDistance: Number.isFinite(bias) ? bias : 0.1,
- overlapConditioningEnabled: raw.overlapConditioningEnabled !== false,
};
if (offsetCapFlag !== undefined) out.offsetCoplanarCap = offsetCapFlag;
if (Number.isFinite(offsetDistance)) out.offsetDistance = offsetDistance;
diff --git a/src/UI/CADmaterials.js b/src/UI/CADmaterials.js
index 20677366..84bec84e 100644
--- a/src/UI/CADmaterials.js
+++ b/src/UI/CADmaterials.js
@@ -6,6 +6,10 @@ import {
writeBrowserStorageValue,
removeBrowserStorageValue,
} from '../utils/browserStorage.js';
+import {
+ getOccVisualizationQuality,
+ setOccVisualizationQuality,
+} from '../BREP/occTriangulationSettings.js';
// CADmaterials for each entity type
@@ -296,6 +300,25 @@ export class CADmaterialWidget {
this._rendererSelect = rendererSelect;
try { this.viewer?.setRendererMode?.(initialMode); } catch { }
+ const storedOccQuality = this._settings['__OCCT_VISUALIZATION_QUALITY__'];
+ const initialOccQuality = setOccVisualizationQuality(storedOccQuality ?? getOccVisualizationQuality());
+ const qualityRow = this._createRangeRow({
+ label: 'Visualization Quality',
+ min: 0.25,
+ max: 20,
+ step: 0.25,
+ value: initialOccQuality,
+ onInput: (value) => {
+ const next = setOccVisualizationQuality(value);
+ this._settings['__OCCT_VISUALIZATION_QUALITY__'] = next;
+ this._saveAllSettings();
+ this._refreshOccVisuals();
+ },
+ });
+ this.uiElement.appendChild(qualityRow.row);
+ this._occQualityInput = qualityRow.input;
+ this._occQualityBubble = qualityRow.bubble;
+
const resetRow = makeRightSpan();
const resetLabel = document.createElement('label');
resetLabel.className = 'cmw-label';
@@ -591,6 +614,12 @@ export class CADmaterialWidget {
if (this._hoverInput) this._hoverInput.value = hoverColor;
this._setSidebarWidthUi(this._defaultSidebarWidth);
+ const occQuality = setOccVisualizationQuality(1);
+ if (this._occQualityInput) {
+ this._occQualityInput.value = String(occQuality);
+ if (this._occQualityBubble) this._updateRangeBubble(this._occQualityInput, this._occQualityBubble);
+ }
+ this._refreshOccVisuals();
for (const [labelText, defaults] of Object.entries(this._materialDefaults || {})) {
const material = this._materialMap.get(labelText);
@@ -599,6 +628,31 @@ export class CADmaterialWidget {
this._syncMaterialControls(labelText, material);
}
}
+ _refreshOccVisuals() {
+ const scene = this.viewer?.partHistory?.scene || this.viewer?.scene || null;
+ if (!scene || typeof scene.traverse !== 'function') {
+ try { this.viewer?.render?.(); } catch { }
+ return;
+ }
+ const solids = [];
+ scene.traverse((obj) => {
+ if (obj?.type === 'SOLID') solids.push(obj);
+ });
+ for (const solid of solids) {
+ try {
+ if (solid._occ) {
+ solid._occ.meshCache = null;
+ solid._occ.meshCacheKey = null;
+ }
+ solid._faceIndex = null;
+ solid.visualize?.();
+ } catch (error) {
+ console.warn('[CADmaterialWidget] Failed to refresh OCCT visualization:', error?.message || error);
+ }
+ }
+ try { this.viewer?.applyMetadataColors?.(); } catch { }
+ try { this.viewer?.render?.(); } catch { }
+ }
_getMatKey(labelText) {
return String(labelText);
}
diff --git a/src/UI/SelectionFilter.js b/src/UI/SelectionFilter.js
index 822fc433..441b7df0 100644
--- a/src/UI/SelectionFilter.js
+++ b/src/UI/SelectionFilter.js
@@ -64,6 +64,15 @@ export class SelectionFilter {
static get TYPES() { return [this.SOLID, this.COMPONENT, this.FACE, this.PLANE, this.SKETCH, this.DATUM, this.HELIX, this.EDGE, this.LOOP, this.VERTEX, this.ALL]; }
+ static isSelectionExcluded(obj) {
+ let current = obj || null;
+ while (current) {
+ if (current?.userData?.excludeFromSelection || current?.userData?.refPreview) return true;
+ current = current.parent || null;
+ }
+ return false;
+ }
+
// Convenience: return the list of selectable types for the dropdown (excludes ALL)
static getAvailableTypes() {
if (SelectionFilter.allowedSelectionTypes === SelectionFilter.ALL) {
@@ -256,6 +265,7 @@ export class SelectionFilter {
let changed = false;
const attach = (target) => {
if (!target || typeof target !== 'object') return;
+ if (SelectionFilter.isSelectionExcluded(target)) return;
SelectionState.attach(target);
SelectionFilter._installOnClickWatcher(target);
if (typeof target.onClick === 'function') return;
@@ -372,6 +382,7 @@ export class SelectionFilter {
const keyFor = (obj) => obj?.uuid || obj?.id || obj?.name || obj;
for (const obj of list) {
if (!obj) continue;
+ if (SelectionFilter.isSelectionExcluded(obj)) continue;
const key = keyFor(obj);
if (seen.has(key)) continue;
seen.add(key);
diff --git a/src/UI/WorkspaceFileBrowserWidget.js b/src/UI/WorkspaceFileBrowserWidget.js
index 24395db4..1ab6918b 100644
--- a/src/UI/WorkspaceFileBrowserWidget.js
+++ b/src/UI/WorkspaceFileBrowserWidget.js
@@ -282,6 +282,7 @@ export class WorkspaceFileBrowserWidget {
onEmptyTrash = null,
createFileActionsMenu = null,
createFolderActionsMenu = null,
+ buildFileHref = null,
bindFileDragSource = null,
bindDropTarget = null,
onDropFiles = null,
@@ -308,6 +309,7 @@ export class WorkspaceFileBrowserWidget {
this.onEmptyTrash = typeof onEmptyTrash === 'function' ? onEmptyTrash : null;
this.createFileActionsMenu = typeof createFileActionsMenu === 'function' ? createFileActionsMenu : null;
this.createFolderActionsMenu = typeof createFolderActionsMenu === 'function' ? createFolderActionsMenu : null;
+ this.buildFileHref = typeof buildFileHref === 'function' ? buildFileHref : null;
this.bindFileDragSource = typeof bindFileDragSource === 'function' ? bindFileDragSource : null;
this.bindDropTarget = typeof bindDropTarget === 'function' ? bindDropTarget : null;
this.onDropFiles = typeof onDropFiles === 'function' ? onDropFiles : null;
@@ -930,6 +932,8 @@ export class WorkspaceFileBrowserWidget {
}
.wfb-open-target {
cursor: pointer;
+ color: inherit;
+ text-decoration: none;
}
.wfb-open-target:focus-visible {
outline: 2px solid rgba(122, 162, 247, 0.75);
@@ -1473,6 +1477,15 @@ export class WorkspaceFileBrowserWidget {
}
}
+ _buildFileHref(entry) {
+ if (!entry || !this.buildFileHref) return '';
+ try {
+ return String(this.buildFileHref(entry) || '').trim();
+ } catch {
+ return '';
+ }
+ }
+
_createEntryActionsMenu(type, entry, { tile = false } = {}) {
try {
if (type === 'file' && this.createFileActionsMenu) {
@@ -1910,6 +1923,7 @@ export class WorkspaceFileBrowserWidget {
if (!pathValue) return null;
const displayName = getEntryDisplayName(entry);
const fullPath = getEntryFullPathTooltip(entry) || getEntryModelPathWithExtension(entry);
+ const fileHref = this._buildFileHref(entry);
const pickFile = (event) => {
event?.preventDefault?.();
event?.stopPropagation?.();
@@ -1938,8 +1952,12 @@ export class WorkspaceFileBrowserWidget {
tile.appendChild(actionBtn);
}
- const preview = document.createElement('span');
+ const preview = document.createElement(fileHref ? 'a' : 'span');
preview.className = 'wfb-tile-preview';
+ if (fileHref) {
+ preview.href = fileHref;
+ preview.draggable = false;
+ }
const icon = document.createElement('span');
icon.className = 'wfb-tile-icon';
icon.textContent = '📄';
@@ -1954,15 +1972,33 @@ export class WorkspaceFileBrowserWidget {
tile.appendChild(preview);
preview.classList.add('wfb-open-target');
preview.title = `Open ${displayName}`;
- preview.addEventListener('click', pickFile);
+ preview.addEventListener('click', fileHref ? (event) => {
+ event.stopPropagation();
+ } : pickFile);
+ if (fileHref) {
+ preview.addEventListener('keydown', (event) => {
+ event.stopPropagation();
+ });
+ }
void this._hydrateThumbnail(entry, thumb);
- const label = document.createElement('div');
+ const label = document.createElement(fileHref ? 'a' : 'div');
label.className = 'wfb-tile-name';
label.classList.add('wfb-open-target');
label.textContent = displayName;
label.title = fullPath || displayName;
- label.addEventListener('click', pickFile);
+ if (fileHref) {
+ label.href = fileHref;
+ label.draggable = false;
+ }
+ label.addEventListener('click', fileHref ? (event) => {
+ event.stopPropagation();
+ } : pickFile);
+ if (fileHref) {
+ label.addEventListener('keydown', (event) => {
+ event.stopPropagation();
+ });
+ }
tile.appendChild(label);
this._attachFileDragSource(tile, entry);
this._attachCurrentFolderDropTarget(tile);
@@ -2096,6 +2132,7 @@ export class WorkspaceFileBrowserWidget {
if (!pathValue) return null;
const displayName = getEntryDisplayName(entry);
const fullPath = getEntryFullPathTooltip(entry) || getEntryModelPathWithExtension(entry);
+ const fileHref = this._buildFileHref(entry);
row.title = fullPath;
row.addEventListener('dblclick', () => { void this._handleActivateFile(entry); });
const actionsMenu = this._createEntryActionsMenu('file', entry, { tile: false });
@@ -2111,8 +2148,12 @@ export class WorkspaceFileBrowserWidget {
});
}
- const preview = document.createElement('span');
+ const preview = document.createElement(fileHref ? 'a' : 'span');
preview.className = 'wfb-preview';
+ if (fileHref) {
+ preview.href = fileHref;
+ preview.draggable = false;
+ }
const icon = document.createElement('span');
icon.className = 'wfb-preview-icon';
icon.textContent = '📄';
@@ -2125,18 +2166,26 @@ export class WorkspaceFileBrowserWidget {
preview.appendChild(thumb);
preview.classList.add('wfb-open-target');
preview.title = `Open ${displayName}`;
- preview.addEventListener('click', (event) => {
+ preview.addEventListener('click', fileHref ? (event) => {
+ event.stopPropagation();
+ } : (event) => {
event.preventDefault();
event.stopPropagation();
void this._handleActivateFile(entry);
});
nameCell.appendChild(preview);
- const name = document.createElement('span');
+ const name = document.createElement(fileHref ? 'a' : 'span');
name.className = 'wfb-name-text';
name.classList.add('wfb-open-target');
name.textContent = displayName;
- name.addEventListener('click', (event) => {
+ if (fileHref) {
+ name.href = fileHref;
+ name.draggable = false;
+ }
+ name.addEventListener('click', fileHref ? (event) => {
+ event.stopPropagation();
+ } : (event) => {
event.preventDefault();
event.stopPropagation();
void this._handleActivateFile(entry);
diff --git a/src/UI/featureDialogWidgets/booleanOperationField.js b/src/UI/featureDialogWidgets/booleanOperationField.js
index 8e4721a9..f4e93a56 100644
--- a/src/UI/featureDialogWidgets/booleanOperationField.js
+++ b/src/UI/featureDialogWidgets/booleanOperationField.js
@@ -2,11 +2,10 @@ import { renderReferenceSelectionField } from './referenceSelectionField.js';
export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
if (!ui.params[key] || typeof ui.params[key] !== 'object') {
- ui.params[key] = { targets: [], operation: 'NONE', overlapConditioningEnabled: true };
+ ui.params[key] = { targets: [], operation: 'NONE' };
} else {
if (!Array.isArray(ui.params[key].targets)) ui.params[key].targets = [];
if (!ui.params[key].operation) ui.params[key].operation = 'NONE';
- if (typeof ui.params[key].overlapConditioningEnabled !== 'boolean') ui.params[key].overlapConditioningEnabled = true;
}
const wrap = document.createElement('div');
@@ -27,7 +26,6 @@ export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
if (!ui.params[key] || typeof ui.params[key] !== 'object') ui.params[key] = {
targets: [],
operation: 'NONE',
- overlapConditioningEnabled: true,
};
const nextOperation = String(sel.value || 'NONE').toUpperCase();
ui.params[key].operation = nextOperation;
@@ -42,30 +40,6 @@ export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
});
wrap.appendChild(sel);
- const conditioningRow = document.createElement('label');
- conditioningRow.style.display = 'flex';
- conditioningRow.style.alignItems = 'center';
- conditioningRow.style.gap = '8px';
- conditioningRow.style.margin = '8px 0 6px';
- conditioningRow.style.color = 'var(--text-color, #e6edf3)';
- conditioningRow.style.fontSize = '12px';
-
- const conditioningToggle = document.createElement('input');
- conditioningToggle.type = 'checkbox';
- conditioningToggle.checked = ui.params[key].overlapConditioningEnabled !== false;
- conditioningToggle.addEventListener('change', () => {
- if (!ui.params[key] || typeof ui.params[key] !== 'object') ui.params[key] = { targets: [], operation: 'NONE', overlapConditioningEnabled: true };
- ui.params[key].overlapConditioningEnabled = conditioningToggle.checked;
- ui._emitParamsChange(key, ui.params[key]);
- });
-
- const conditioningLabel = document.createElement('span');
- conditioningLabel.textContent = 'Condition touching coplanar faces';
-
- conditioningRow.appendChild(conditioningToggle);
- conditioningRow.appendChild(conditioningLabel);
- wrap.appendChild(conditioningRow);
-
const refMount = document.createElement('div');
const targetsDef = {
type: 'reference_selection',
@@ -82,7 +56,6 @@ export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
if (!ui.params[key] || typeof ui.params[key] !== 'object') ui.params[key] = {
targets: [],
operation: sel.value || 'NONE',
- overlapConditioningEnabled: conditioningToggle.checked,
};
ui.params[key].targets = Array.isArray(next) ? next : [];
},
@@ -108,12 +81,11 @@ export function renderBooleanOperationField({ ui, key, def, controlWrap }) {
readValue() {
const current = ui.params[key];
if (!current || typeof current !== 'object') {
- return { targets: [], operation: 'NONE', overlapConditioningEnabled: true };
+ return { targets: [], operation: 'NONE' };
}
return {
targets: Array.isArray(current.targets) ? current.targets.slice() : [],
operation: current.operation || 'NONE',
- overlapConditioningEnabled: current.overlapConditioningEnabled !== false,
};
},
};
diff --git a/src/UI/viewer.js b/src/UI/viewer.js
index 65086a89..5382b33d 100644
--- a/src/UI/viewer.js
+++ b/src/UI/viewer.js
@@ -3633,6 +3633,7 @@ export class Viewer {
_mapIntersectionToTarget(intersection, options = {}) {
if (!intersection || !intersection.object) return null;
+ if (typeof SelectionFilter.isSelectionExcluded === 'function' && SelectionFilter.isSelectionExcluded(intersection.object)) return null;
const { allowAnyAllowedType = false, ignoreSelectionFilter = false } = options;
const isAllowed = (type) => {
if (!type) return false;
@@ -3657,6 +3658,7 @@ export class Viewer {
while (target && typeof target.onClick !== 'function' && target.visible) target = target.parent;
if (!target) target = obj;
if (!target) return null;
+ if (typeof SelectionFilter.isSelectionExcluded === 'function' && SelectionFilter.isSelectionExcluded(target)) return null;
// Respect selection filter: ensure target is a permitted type, or ALL
if (typeof isAllowed === 'function') {
diff --git a/src/core/entities/schemaProcesser.js b/src/core/entities/schemaProcesser.js
index f91e2119..b14c499e 100644
--- a/src/core/entities/schemaProcesser.js
+++ b/src/core/entities/schemaProcesser.js
@@ -103,7 +103,6 @@ export async function sanitizeInputParams(schema, inputParams, expressionsEvalua
operation: op ?? 'NONE',
targets,
biasDistance: Number.isFinite(bias) ? bias : 0.1,
- overlapConditioningEnabled: raw.overlapConditioningEnabled !== false,
};
// Optional: simplification controls
try {
diff --git a/src/exporters/threeMF.js b/src/exporters/threeMF.js
index a5b6f003..4510a98e 100644
--- a/src/exporters/threeMF.js
+++ b/src/exporters/threeMF.js
@@ -5,6 +5,19 @@
import JSZip from 'jszip';
+const DEFAULT_EXPORT_MESH_OPTIONS = Object.freeze({
+ deflection: 0.03,
+ angle: 0.12,
+});
+
+function _resolveMeshOptions(opts = {}) {
+ const source = opts.meshOptions && typeof opts.meshOptions === 'object' ? opts.meshOptions : {};
+ const out = { ...DEFAULT_EXPORT_MESH_OPTIONS };
+ if (Number.isFinite(source.deflection) && source.deflection > 0) out.deflection = source.deflection;
+ if (Number.isFinite(source.angle) && source.angle > 0) out.angle = source.angle;
+ return out;
+}
+
function _parseDataUrl(dataUrl) {
try {
if (typeof dataUrl !== 'string') return null;
@@ -362,7 +375,7 @@ export function computeTriangleMaterialIndices(solid, mesh, opts = {}) {
/**
* Build the core 3MF model XML for one or more solids.
* @param {Array} solids Array of SOLID-like objects that expose getMesh() and name.
- * @param {{unit?: 'millimeter'|'inch'|'foot'|'meter'|'centimeter'|'micron', precision?: number, scale?: number, metadataManager?: any, useMetadataColors?: boolean, includeFaceTags?: boolean, applyWorldTransform?: boolean, defaultFaceColor?: any}} opts
+ * @param {{unit?: 'millimeter'|'inch'|'foot'|'meter'|'centimeter'|'micron', precision?: number, scale?: number, metadataManager?: any, useMetadataColors?: boolean, includeFaceTags?: boolean, applyWorldTransform?: boolean, defaultFaceColor?: any, meshOptions?: {deflection?: number, angle?: number}}} opts
* @returns {string}
*/
export function build3MFModelXML(solids, opts = {}) {
@@ -373,6 +386,7 @@ export function build3MFModelXML(solids, opts = {}) {
const includeFaceTags = opts.includeFaceTags !== false; // default on
const useMetadataColors = opts.useMetadataColors !== false;
const applyWorldTransform = opts.applyWorldTransform !== false;
+ const meshOptions = _resolveMeshOptions(opts);
const lines = [];
lines.push('');
@@ -392,7 +406,7 @@ export function build3MFModelXML(solids, opts = {}) {
let solidIdx = 0;
for (const s of (solids || [])) {
if (!s || typeof s.getMesh !== 'function') continue;
- const mesh = s.getMesh();
+ const mesh = s.getMesh(meshOptions);
try {
if (!mesh || !mesh.vertProperties || !mesh.triVerts) continue;
const rawName = s.name || `solid_${solidIdx + 1}`;
@@ -677,7 +691,7 @@ function rootRelsXML({ thumbnailPath, viewImages, attachments } = {}) {
/**
* Generate a 3MF zip archive as Uint8Array.
* @param {Array} solids Array of SOLID-like objects that expose getMesh() and name.
- * @param {{unit?: string, precision?: number, scale?: number, metadataManager?: any, useMetadataColors?: boolean, includeFaceTags?: boolean, applyWorldTransform?: boolean, defaultFaceColor?: any}} opts
+ * @param {{unit?: string, precision?: number, scale?: number, metadataManager?: any, useMetadataColors?: boolean, includeFaceTags?: boolean, applyWorldTransform?: boolean, defaultFaceColor?: any, meshOptions?: {deflection?: number, angle?: number}}} opts
* @returns {Promise}
*/
export async function generate3MF(solids, opts = {}) {
diff --git a/src/features/assemblyComponent/AssemblyComponentFeature.js b/src/features/assemblyComponent/AssemblyComponentFeature.js
index 07d65440..09bad1da 100644
--- a/src/features/assemblyComponent/AssemblyComponentFeature.js
+++ b/src/features/assemblyComponent/AssemblyComponentFeature.js
@@ -1,6 +1,5 @@
import JSZip from 'jszip';
import { ThreeMFLoader } from 'three/examples/jsm/loaders/3MFLoader.js';
-import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js';
import { BREP } from '../../BREP/BREP.js';
import { createPortGroupFromDefinition, normalizePortDefinition } from '../port/portUtils.js';
import { base64ToUint8Array, getComponentRecord } from '../../services/componentLibrary.js';
@@ -509,56 +508,7 @@ export class AssemblyComponentFeature {
}
if (!solids.length && meshes.length) {
- try {
- const worldGeometries = [];
- for (const mesh of meshes) {
- const geom = mesh?.geometry;
- if (!geom || typeof geom.clone !== 'function') continue;
- try { mesh.updateWorldMatrix(true, false); }
- catch { }
- const cloned = geom.clone();
- try { cloned.applyMatrix4(mesh.matrixWorld); }
- catch { /* ignore transform issues */ }
- worldGeometries.push(cloned);
- }
- if (worldGeometries.length) {
- const merged = mergeGeometries(worldGeometries, false);
- if (merged) {
- try {
- const fallbackSolid = new BREP.MeshToBrep(merged, 30, 1e-5);
- const fallbackKey = metadataMap && Object.keys(metadataMap).length === 1
- ? Object.keys(metadataMap)[0]
- : (facetInfo && Object.keys(facetInfo).length === 1 ? Object.keys(facetInfo)[0] : null);
- const solidName = fallbackKey || this._resolveSolidName('', componentName, 1);
- fallbackSolid.name = solidName;
- console.warn(`[AssemblyComponentFeature] Using merged-geometry fallback for component "${componentName}" (mesh count: ${meshes.length}).`);
-
- if (facetInfo && facetInfo[solidName] && typeof fallbackSolid.setFaceMetadata === 'function') {
- for (const key of Object.keys(facetInfo[solidName])) {
- try { fallbackSolid.setFaceMetadata(key, facetInfo[solidName][key]); }
- catch { /* ignore */ }
- }
- }
-
- if (metadataMap && metadataMap[solidName]) {
- this._mergeSolidMetadata(fallbackSolid, metadataMap[solidName]);
- }
- this._applyFaceMetadataFromMap(fallbackSolid, metadataMap);
-
- solids.push(fallbackSolid);
- } catch (err) {
- console.warn('[AssemblyComponentFeature] Merged-geometry fallback failed:', err);
- } finally {
- try { merged.dispose(); } catch { }
- }
- }
- }
- for (const g of worldGeometries) {
- try { g.dispose(); } catch { }
- }
- } catch (err) {
- console.warn('[AssemblyComponentFeature] Unable to merge component meshes for fallback:', err);
- }
+ console.warn(`[AssemblyComponentFeature] Component "${componentName}" contains mesh-only fallback data; OCCT-only mode skipped mesh-to-solid conversion.`);
}
if (!solids.length) {
@@ -947,18 +897,8 @@ export class AssemblyComponentFeature {
}
}
- _fallbackSolidFromMesh(mesh) {
- try {
- const geometry = mesh?.geometry;
- const posAttr = geometry?.getAttribute?.('position');
- if (!geometry || !posAttr || posAttr.count < 3) return null;
- const cloned = geometry.clone();
- cloned.applyMatrix4(mesh.matrixWorld);
- return new BREP.MeshToBrep(cloned, 30, 1e-5);
- } catch (err) {
- console.warn('[AssemblyComponentFeature] Fallback MeshToBrep conversion failed:', err);
- return null;
- }
+ _fallbackSolidFromMesh(_mesh) {
+ return null;
}
async _rebuildSolidsFromHistory(componentData, componentName) {
diff --git a/src/features/chamfer/ChamferFeature.js b/src/features/chamfer/ChamferFeature.js
index c65f4105..c78a5e5e 100644
--- a/src/features/chamfer/ChamferFeature.js
+++ b/src/features/chamfer/ChamferFeature.js
@@ -3,8 +3,6 @@ import {
getSolidGeometryCounts,
resolveSingleSolidFromEdges,
} from "../edgeFeatureUtils.js";
-import { BREP } from "../../BREP/BREP.js";
-import { SelectionState } from "../../UI/SelectionState.js";
const inputParamsSchema = {
id: {
@@ -25,175 +23,8 @@ const inputParamsSchema = {
default_value: 1,
hint: "Chamfer distance (equal offset along both faces)",
},
- inflate: {
- type: "number",
- default_value: 0.1,
- step: 0.1,
- hint: "Grow the cutting solid by this amount (units). Very small values (e.g., 0.0005) help avoid residual slivers after CSG.",
- },
- direction: {
- type: "options",
- options: ["AUTO", "INSET", "OUTSET"],
- default_value: "AUTO",
- hint: "Choose chamfer side automatically (AUTO) or force INSET/OUTSET",
- },
- debug: {
- type: "options",
- options: [
- "NONE",
- "TRIANGLE CROSS SECTIONS ONLY",
- "CHAMFER SOLID ONLY",
- "CHAMFER SOLID AND CROSS SECTIONS",
- ],
- default_value: "NONE",
- hint: "Choose which chamfer debug geometry to draw",
- }
};
-function normalizeChamferDebugMode(rawValue) {
- if (rawValue === true) return "CHAMFER SOLID AND CROSS SECTIONS";
- if (rawValue === false || rawValue == null) return "NONE";
- const value = String(rawValue).trim().toLowerCase();
- if (value === "triangle cross sections only") return "TRIANGLE CROSS SECTIONS ONLY";
- if (value === "chamfer solid only") return "CHAMFER SOLID ONLY";
- if (value === "chamfer solid and cross sections") return "CHAMFER SOLID AND CROSS SECTIONS";
- if (value === "none") return "NONE";
- return "NONE";
-}
-
-function isSectionDebugSolid(debugSolid) {
- return /_SECTION_\d+$/.test(String(debugSolid?.name || ""))
- || String(debugSolid?.__debugChamferKind || "") === "chamferCrossSection";
-}
-
-function shouldIncludeChamferDebugSolid(debugMode, debugSolid) {
- const isSection = isSectionDebugSolid(debugSolid);
- switch (debugMode) {
- case "TRIANGLE CROSS SECTIONS ONLY":
- return isSection;
- case "CHAMFER SOLID ONLY":
- return !isSection;
- case "CHAMFER SOLID AND CROSS SECTIONS":
- return true;
- case "NONE":
- default:
- return false;
- }
-}
-
-function buildSketchBasisFromFace(face) {
- const pos = face?.geometry?.getAttribute?.("position");
- if (!pos || pos.count < 3) return null;
- const THREE = BREP.THREE;
- const origin = new THREE.Vector3(pos.getX(0), pos.getY(0), pos.getZ(0));
- const px = new THREE.Vector3(pos.getX(1), pos.getY(1), pos.getZ(1)).sub(origin);
- const py = new THREE.Vector3(pos.getX(2), pos.getY(2), pos.getZ(2)).sub(origin);
- const x = px.clone().normalize();
- if (x.lengthSq() < 1e-20) return null;
- const normal = x.clone().cross(py).normalize();
- if (normal.lengthSq() < 1e-20) return null;
- const y = new THREE.Vector3().crossVectors(normal, x).normalize();
- if (y.lengthSq() < 1e-20) return null;
- return {
- origin: [origin.x, origin.y, origin.z],
- x: [x.x, x.y, x.z],
- y: [y.x, y.y, y.z],
- z: [normal.x, normal.y, normal.z],
- };
-}
-
-function applySketchProfileFaceStyle(face) {
- if (!face) return;
- try {
- const sketchMat = (face.material && typeof face.material.clone === "function")
- ? face.material.clone()
- : null;
- if (!sketchMat) return;
- sketchMat.side = BREP.THREE.DoubleSide;
- sketchMat.polygonOffset = true;
- sketchMat.polygonOffsetFactor = -2;
- sketchMat.polygonOffsetUnits = 1;
- sketchMat.needsUpdate = true;
- SelectionState.setBaseMaterial(face, sketchMat, { force: false });
- } catch { /* ignore style overrides for debug sketch faces */ }
-}
-
-function buildDebugSketchGroupFromSectionSolid(sectionSolid) {
- if (!sectionSolid || typeof sectionSolid.visualize !== "function") return null;
- try { sectionSolid.visualize({ showEdges: true, authoringOnly: true }); } catch { return null; }
-
- const faceNames = (typeof sectionSolid.getFaceNames === "function") ? sectionSolid.getFaceNames() : [];
- if (!Array.isArray(faceNames) || faceNames.length !== 1) return null;
- const sourceFaceName = String(faceNames[0] || "");
- if (!sourceFaceName) return null;
-
- let faceMetadata = null;
- try {
- faceMetadata = (typeof sectionSolid.getFaceMetadata === "function")
- ? (sectionSolid.getFaceMetadata(sourceFaceName) || null)
- : null;
- } catch {
- faceMetadata = null;
- }
- if (!faceMetadata || faceMetadata.debugSketchFace !== true) return null;
-
- const children = Array.isArray(sectionSolid.children) ? [...sectionSolid.children] : [];
- const profileFace = children.find((child) => child?.type === "FACE" && String(child?.name || "") === sourceFaceName);
- if (!profileFace) return null;
-
- const group = new BREP.THREE.Group();
- group.name = sectionSolid.name || sourceFaceName;
- group.type = "SKETCH";
- group.onClick = () => {};
- group.userData = group.userData || {};
-
- const profileFaceName = `${group.name}:PROFILE`;
- profileFace.name = profileFaceName;
- profileFace.parentSolid = null;
- profileFace.userData = {
- ...(profileFace.userData || {}),
- faceName: profileFaceName,
- sketchFeatureId: group.name,
- sourceFaceName,
- };
- applySketchProfileFaceStyle(profileFace);
- const basis = buildSketchBasisFromFace(profileFace);
- if (basis) group.userData.sketchBasis = basis;
-
- const childEdges = [];
- const childVertices = [];
- for (const child of children) {
- if (!child) continue;
- if (child.type === "EDGE") {
- child.parentSolid = null;
- child.userData = child.userData || {};
- if (child.userData.faceA === sourceFaceName) child.userData.faceA = profileFaceName;
- if (child.userData.faceB === sourceFaceName) child.userData.faceB = profileFaceName;
- child.userData.sketchFeatureId = group.name;
- childEdges.push(child);
- } else if (child.type === "VERTEX") {
- child.parentSolid = null;
- child.userData = child.userData || {};
- child.userData.sketchFeatureId = group.name;
- childVertices.push(child);
- }
- }
- profileFace.edges = childEdges;
- for (const edge of childEdges) {
- if (Array.isArray(edge.faces)) {
- edge.faces = edge.faces.map((face) => (face === profileFace || String(face?.name || "") === sourceFaceName) ? profileFace : face)
- .filter(Boolean);
- } else {
- edge.faces = [profileFace];
- }
- }
-
- group.add(profileFace);
- for (const edge of childEdges) group.add(edge);
- for (const vertex of childVertices) group.add(vertex);
- return group;
-}
-
export class ChamferFeature {
static shortName = "CH";
static longName = "Chamfer";
@@ -233,7 +64,6 @@ export class ChamferFeature {
}
return { added: [], removed: [] };
}
- const direction = String(this.inputParams.direction || "AUTO").toUpperCase();
const distance = Number(this.inputParams.distance);
if (!Number.isFinite(distance) || !(distance > 0)) {
console.warn("Invalid chamfer distance supplied; aborting.", { distance: this.inputParams.distance });
@@ -241,13 +71,9 @@ export class ChamferFeature {
}
const fid = this.inputParams.featureID;
- const debugMode = normalizeChamferDebugMode(this.inputParams.debug);
const result = await targetSolid.chamfer({
distance,
edges: edgeObjs,
- direction,
- inflate: Number(this.inputParams.inflate),
- debug: debugMode !== "NONE",
featureID: fid,
});
@@ -257,9 +83,7 @@ export class ChamferFeature {
featureID: fid,
triangleCount: triCount,
vertexCount: vertCount,
- direction,
distance,
- inflate: this.inputParams.inflate,
});
return { added: [], removed: [] };
}
@@ -269,20 +93,6 @@ export class ChamferFeature {
result.visualize();
const added = [result];
- if (debugMode !== "NONE" && Array.isArray(result.__debugChamferSolids)) {
- for (const dbg of result.__debugChamferSolids) {
- if (!dbg) continue;
- if (!shouldIncludeChamferDebugSolid(debugMode, dbg)) continue;
- try { dbg.name = `${fid || "CHAMFER"}_${dbg.name || "DEBUG"}`; } catch {}
- const debugSketch = buildDebugSketchGroupFromSectionSolid(dbg);
- if (debugSketch) {
- added.push(debugSketch);
- continue;
- }
- try { dbg.visualize({ showEdges: true, authoringOnly: true }); } catch {}
- added.push(dbg);
- }
- }
return { added, removed: [targetSolid] };
}
}
diff --git a/src/features/collapseEdge/CollapseEdgeFeature.js b/src/features/collapseEdge/CollapseEdgeFeature.js
deleted file mode 100644
index 5905fa13..00000000
--- a/src/features/collapseEdge/CollapseEdgeFeature.js
+++ /dev/null
@@ -1,252 +0,0 @@
-import {
- collectEdgesFromSelection,
- getSolidGeometryCounts,
-} from "../edgeFeatureUtils.js";
-
-const inputParamsSchema = {
- id: {
- type: "string",
- default_value: null,
- hint: "unique identifier for the collapse-edge feature",
- },
- edges: {
- type: "reference_selection",
- selectionFilter: ["EDGE"],
- multiple: true,
- default_value: null,
- hint: "Select one or more edges to collapse to a point",
- },
-};
-
-function isRenderableEdge(obj) {
- return !!obj
- && String(obj.type || "").toUpperCase() === "EDGE"
- && !obj?.userData?.auxEdge;
-}
-
-function collectCurrentSolidEdges(solid) {
- const out = [];
- if (!solid || typeof solid.traverse !== "function") return out;
- solid.traverse((obj) => {
- if (!isRenderableEdge(obj)) return;
- const owner = obj.parentSolid || obj.parent || null;
- if (owner === solid || obj.parentSolid === solid) out.push(obj);
- });
- return out;
-}
-
-function getDescriptorFromEdge(edge) {
- if (!isRenderableEdge(edge)) return null;
- const solid = edge.parentSolid || edge.parent || null;
- if (!solid) return null;
- return {
- solid,
- name: edge?.name || null,
- faceA: edge?.userData?.faceA || null,
- faceB: edge?.userData?.faceB || null,
- polylineLocal: Array.isArray(edge?.userData?.polylineLocal)
- ? edge.userData.polylineLocal
- : null,
- };
-}
-
-function pointDistanceSq(a, b) {
- const dx = a[0] - b[0];
- const dy = a[1] - b[1];
- const dz = a[2] - b[2];
- return dx * dx + dy * dy + dz * dz;
-}
-
-function isPoint3(p) {
- return Array.isArray(p)
- && p.length === 3
- && Number.isFinite(p[0])
- && Number.isFinite(p[1])
- && Number.isFinite(p[2]);
-}
-
-function polylineEndpointScore(a, b) {
- if (!Array.isArray(a) || a.length < 2 || !Array.isArray(b) || b.length < 2) return Infinity;
- const a0 = a[0];
- const a1 = a[a.length - 1];
- const b0 = b[0];
- const b1 = b[b.length - 1];
- if (!isPoint3(a0) || !isPoint3(a1) || !isPoint3(b0) || !isPoint3(b1)) return Infinity;
- const forward = pointDistanceSq(a0, b0) + pointDistanceSq(a1, b1);
- const reverse = pointDistanceSq(a0, b1) + pointDistanceSq(a1, b0);
- return Math.min(forward, reverse);
-}
-
-function matchesFacePair(edgeObj, descriptor) {
- const fa = descriptor?.faceA || null;
- const fb = descriptor?.faceB || null;
- if (!fa || !fb) return false;
- const ea = edgeObj?.userData?.faceA || null;
- const eb = edgeObj?.userData?.faceB || null;
- return (ea === fa && eb === fb) || (ea === fb && eb === fa);
-}
-
-function findMatchingEdge(edgeCandidates, descriptor) {
- if (!Array.isArray(edgeCandidates) || edgeCandidates.length === 0) return null;
- if (!descriptor) return null;
-
- const edgeName = descriptor?.name || null;
- if (edgeName) {
- const exact = edgeCandidates.find((e) => e?.name === edgeName);
- if (exact) return exact;
- }
-
- let candidates = edgeCandidates;
- if (descriptor?.faceA && descriptor?.faceB) {
- const byFacePair = edgeCandidates.filter((e) => matchesFacePair(e, descriptor));
- if (byFacePair.length === 1) return byFacePair[0];
- if (byFacePair.length > 1) candidates = byFacePair;
- }
-
- const sourcePolyline = Array.isArray(descriptor?.polylineLocal)
- ? descriptor.polylineLocal
- : null;
- if (sourcePolyline && sourcePolyline.length >= 2) {
- let best = null;
- let bestScore = Infinity;
- for (const candidate of candidates) {
- const score = polylineEndpointScore(sourcePolyline, candidate?.userData?.polylineLocal);
- if (score < bestScore) {
- bestScore = score;
- best = candidate;
- }
- }
- if (best) return best;
- }
-
- return candidates[0] || null;
-}
-
-function groupDescriptorsBySolid(descriptors) {
- const map = new Map();
- for (const descriptor of descriptors) {
- if (!descriptor?.solid) continue;
- let list = map.get(descriptor.solid);
- if (!list) {
- list = [];
- map.set(descriptor.solid, list);
- }
- list.push(descriptor);
- }
- return map;
-}
-
-function collapseEdgesOnClone(sourceSolid, descriptors, featureID) {
- if (!sourceSolid || typeof sourceSolid.clone !== "function") return null;
- const outSolid = sourceSolid.clone();
- try { outSolid.name = sourceSolid.name; } catch { }
- try { if (featureID) outSolid.owningFeatureID = featureID; } catch { }
- try { outSolid.visualize(); } catch { }
-
- let collapsed = 0;
- for (const descriptor of descriptors) {
- const liveEdges = collectCurrentSolidEdges(outSolid);
- const liveEdge = findMatchingEdge(liveEdges, descriptor);
- if (!liveEdge || typeof liveEdge.collapseToPoint !== "function") {
- console.warn("[CollapseEdgeFeature] Could not resolve edge on cloned solid.", {
- edgeName: descriptor?.name || null,
- solidName: sourceSolid?.name || null,
- });
- continue;
- }
- try {
- liveEdge.collapseToPoint();
- collapsed++;
- } catch (error) {
- console.warn("[CollapseEdgeFeature] collapseToPoint() failed.", {
- edgeName: descriptor?.name || liveEdge?.name || null,
- solidName: sourceSolid?.name || null,
- message: error?.message || String(error || "Unknown error"),
- });
- }
- }
-
- if (collapsed <= 0) return null;
- try { outSolid.visualize(); } catch { }
-
- const { triCount, vertCount } = getSolidGeometryCounts(outSolid);
- if (triCount <= 0 || vertCount <= 0) {
- console.warn("[CollapseEdgeFeature] Collapsed solid is empty; skipping replacement.", {
- solidName: sourceSolid?.name || null,
- triangleCount: triCount,
- vertexCount: vertCount,
- });
- return null;
- }
- return { solid: outSolid, collapsed };
-}
-
-export class CollapseEdgeFeature {
- static shortName = "CE";
- static longName = "Collapse Edge";
- static inputParamsSchema = inputParamsSchema;
- static showContexButton(selectedItems) {
- const items = Array.isArray(selectedItems) ? selectedItems : [];
- const edges = items
- .filter((it) => String(it?.type || "").toUpperCase() === "EDGE")
- .map((it) => it?.name || it?.userData?.edgeName || null)
- .filter((name) => !!name);
- if (!edges.length) return false;
- return { params: { edges } };
- }
-
- constructor() {
- this.inputParams = {};
- this.persistentData = {};
- }
-
- async run() {
- const inputObjects = Array.isArray(this.inputParams.edges) ? this.inputParams.edges.filter(Boolean) : [];
- const edgeObjects = collectEdgesFromSelection(inputObjects).filter((edge) => isRenderableEdge(edge));
- if (edgeObjects.length === 0) {
- console.warn("[CollapseEdgeFeature] No edges selected for collapse.");
- return { added: [], removed: [] };
- }
-
- const descriptors = edgeObjects
- .map((edge) => getDescriptorFromEdge(edge))
- .filter((descriptor) => !!descriptor);
- if (!descriptors.length) {
- console.warn("[CollapseEdgeFeature] Selected edges have no owning solids.");
- return { added: [], removed: [] };
- }
-
- const grouped = groupDescriptorsBySolid(descriptors);
- const added = [];
- const removed = [];
- let totalCollapsed = 0;
- const featureID = this.inputParams?.featureID || this.inputParams?.id || null;
-
- for (const [sourceSolid, edgeDescriptors] of grouped.entries()) {
- const result = collapseEdgesOnClone(sourceSolid, edgeDescriptors, featureID);
- if (!result?.solid) continue;
- added.push(result.solid);
- removed.push(sourceSolid);
- totalCollapsed += Number(result.collapsed) || 0;
- }
-
- if (!added.length) {
- console.warn("[CollapseEdgeFeature] No edges were collapsed.");
- return { added: [], removed: [] };
- }
-
- try {
- for (const obj of removed) {
- if (!obj) continue;
- obj.__removeFlag = true;
- }
- } catch { }
-
- this.persistentData = {
- ...(this.persistentData || {}),
- totalCollapsedEdges: totalCollapsed,
- targetSolidCount: added.length,
- };
- return { added, removed };
- }
-}
diff --git a/src/features/edgeFeatureUtils.js b/src/features/edgeFeatureUtils.js
index 8536085c..6d74599d 100644
--- a/src/features/edgeFeatureUtils.js
+++ b/src/features/edgeFeatureUtils.js
@@ -35,7 +35,22 @@ export function resolveSingleSolidFromEdges(edges) {
}
export function getSolidGeometryCounts(solid) {
- const triCount = Array.isArray(solid?._triVerts) ? (solid._triVerts.length / 3) : 0;
- const vertCount = Array.isArray(solid?._vertProperties) ? (solid._vertProperties.length / 3) : 0;
+ let triVerts = solid?._triVerts;
+ let vertProperties = solid?._vertProperties;
+ if (
+ (!Array.isArray(triVerts) || triVerts.length === 0 || !Array.isArray(vertProperties) || vertProperties.length === 0)
+ && typeof solid?.getMesh === 'function'
+ ) {
+ try {
+ const mesh = solid.getMesh();
+ triVerts = mesh?.triVerts || triVerts;
+ vertProperties = mesh?.vertProperties || vertProperties;
+ try { if (mesh && typeof mesh.delete === 'function') mesh.delete(); } catch { }
+ } catch {
+ // Fall through to the legacy arrays below.
+ }
+ }
+ const triCount = triVerts && typeof triVerts.length === 'number' ? (triVerts.length / 3) : 0;
+ const vertCount = vertProperties && typeof vertProperties.length === 'number' ? (vertProperties.length / 3) : 0;
return { triCount, vertCount };
}
diff --git a/src/features/edgeSmooth/EdgeSmoothFeature.js b/src/features/edgeSmooth/EdgeSmoothFeature.js
deleted file mode 100644
index ed190a2f..00000000
--- a/src/features/edgeSmooth/EdgeSmoothFeature.js
+++ /dev/null
@@ -1,729 +0,0 @@
-import {
- getSolidGeometryCounts,
-} from "../edgeFeatureUtils.js";
-import { fitAndSnapOpenEdgePolyline } from "./edgeCurveFit.js";
-import { applyConstrainedVertexTargets } from "./vertexTargetConstraints.js";
-
-const DEFAULT_FIT_STRENGTH = 1;
-const POINT_MATCH_EPSILON = 1e-9;
-
-const inputParamsSchema = {
- id: {
- type: "string",
- default_value: null,
- hint: "unique identifier for the edge-smooth feature",
- },
- edges: {
- type: "reference_selection",
- selectionFilter: ["EDGE", "FACE", "SOLID"],
- multiple: true,
- default_value: null,
- hint: "Select edges, faces, or solids to curve-fit associated edges",
- },
- fitStrength: {
- type: "number",
- step: 0.05,
- default_value: DEFAULT_FIT_STRENGTH,
- hint: "Blend amount toward the fitted curve (0 to 1)",
- },
-};
-
-function isRenderableEdge(obj) {
- return !!obj
- && String(obj.type || "").toUpperCase() === "EDGE"
- && !obj?.userData?.auxEdge;
-}
-
-function collectCurrentSolidEdges(solid) {
- const out = [];
- if (!solid || typeof solid.traverse !== "function") return out;
- solid.traverse((obj) => {
- if (!isRenderableEdge(obj)) return;
- const owner = obj.parentSolid || obj.parent || null;
- if (owner === solid || obj.parentSolid === solid) out.push(obj);
- });
- return out;
-}
-
-function resolveSelectionObject(entry, partHistory) {
- if (entry && typeof entry === "object") return entry;
- if ((typeof entry === "string" || typeof entry === "number")
- && partHistory?.scene
- && typeof partHistory.scene.getObjectByName === "function") {
- try {
- return partHistory.scene.getObjectByName(String(entry)) || null;
- } catch {
- return null;
- }
- }
- return null;
-}
-
-function collectEdgeObjectsFromTargets(rawTargets, partHistory) {
- const out = [];
- const seen = new Set();
- const seenSolid = new Set();
- const seenFace = new Set();
- const pushEdge = (edge) => {
- if (!isRenderableEdge(edge)) return;
- if (seen.has(edge)) return;
- seen.add(edge);
- out.push(edge);
- };
-
- const targets = Array.isArray(rawTargets)
- ? rawTargets
- : (rawTargets != null ? [rawTargets] : []);
- for (const rawEntry of targets) {
- const entry = resolveSelectionObject(rawEntry, partHistory);
- if (!entry) continue;
- const type = String(entry?.type || "").toUpperCase();
- if (type === "EDGE") {
- pushEdge(entry);
- continue;
- }
- if (type === "FACE") {
- if (seenFace.has(entry)) continue;
- seenFace.add(entry);
- const faceEdges = Array.isArray(entry.edges) ? entry.edges : [];
- for (const edge of faceEdges) pushEdge(edge);
- continue;
- }
- if (type === "SOLID") {
- if (seenSolid.has(entry)) continue;
- seenSolid.add(entry);
- const solidEdges = collectCurrentSolidEdges(entry);
- for (const edge of solidEdges) pushEdge(edge);
- }
- }
-
- return {
- edgeObjects: out,
- selectedSolidCount: seenSolid.size,
- selectedFaceCount: seenFace.size,
- };
-}
-
-function getDescriptorFromEdge(edge) {
- if (!isRenderableEdge(edge)) return null;
- const solid = edge.parentSolid || edge.parent || null;
- if (!solid) return null;
- return {
- solid,
- name: edge?.name || null,
- faceA: edge?.userData?.faceA || null,
- faceB: edge?.userData?.faceB || null,
- polylineLocal: Array.isArray(edge?.userData?.polylineLocal)
- ? edge.userData.polylineLocal
- : null,
- };
-}
-
-function pointDistanceSq(a, b) {
- const dx = a[0] - b[0];
- const dy = a[1] - b[1];
- const dz = a[2] - b[2];
- return dx * dx + dy * dy + dz * dz;
-}
-
-function isPoint3(p) {
- return Array.isArray(p)
- && p.length === 3
- && Number.isFinite(p[0])
- && Number.isFinite(p[1])
- && Number.isFinite(p[2]);
-}
-
-function pointsMatch(a, b, epsilon = POINT_MATCH_EPSILON) {
- if (!isPoint3(a) || !isPoint3(b)) return false;
- return pointDistanceSq(a, b) <= (epsilon * epsilon);
-}
-
-function polylineEndpointScore(a, b) {
- if (!Array.isArray(a) || a.length < 2 || !Array.isArray(b) || b.length < 2) return Infinity;
- const a0 = a[0];
- const a1 = a[a.length - 1];
- const b0 = b[0];
- const b1 = b[b.length - 1];
- if (!isPoint3(a0) || !isPoint3(a1) || !isPoint3(b0) || !isPoint3(b1)) return Infinity;
- const forward = pointDistanceSq(a0, b0) + pointDistanceSq(a1, b1);
- const reverse = pointDistanceSq(a0, b1) + pointDistanceSq(a1, b0);
- return Math.min(forward, reverse);
-}
-
-function matchesFacePair(edgeObj, descriptor) {
- const fa = descriptor?.faceA || null;
- const fb = descriptor?.faceB || null;
- if (!fa || !fb) return false;
- const ea = edgeObj?.userData?.faceA || null;
- const eb = edgeObj?.userData?.faceB || null;
- return (ea === fa && eb === fb) || (ea === fb && eb === fa);
-}
-
-function findMatchingEdge(edgeCandidates, descriptor) {
- if (!Array.isArray(edgeCandidates) || edgeCandidates.length === 0) return null;
- if (!descriptor) return null;
-
- const edgeName = descriptor?.name || null;
- if (edgeName) {
- const exact = edgeCandidates.find((e) => e?.name === edgeName);
- if (exact) return exact;
- }
-
- let candidates = edgeCandidates;
- if (descriptor?.faceA && descriptor?.faceB) {
- const byFacePair = edgeCandidates.filter((e) => matchesFacePair(e, descriptor));
- if (byFacePair.length === 1) return byFacePair[0];
- if (byFacePair.length > 1) candidates = byFacePair;
- }
-
- const sourcePolyline = Array.isArray(descriptor?.polylineLocal)
- ? descriptor.polylineLocal
- : null;
- if (sourcePolyline && sourcePolyline.length >= 2) {
- let best = null;
- let bestScore = Infinity;
- for (const candidate of candidates) {
- const score = polylineEndpointScore(sourcePolyline, candidate?.userData?.polylineLocal);
- if (score < bestScore) {
- bestScore = score;
- best = candidate;
- }
- }
- if (best) return best;
- }
-
- return candidates[0] || null;
-}
-
-function findMatchingBoundaryPolyline(edgeObj, boundaries) {
- if (!Array.isArray(boundaries) || boundaries.length === 0) return null;
- const edgeName = typeof edgeObj?.name === "string" && edgeObj.name ? edgeObj.name : null;
- if (edgeName) {
- const exact = boundaries.find((b) => b && b.name === edgeName);
- if (exact) return exact;
- }
-
- const faceA = edgeObj?.userData?.faceA;
- const faceB = edgeObj?.userData?.faceB;
- let candidates = boundaries;
- if (faceA && faceB) {
- candidates = boundaries.filter((b) => {
- if (!b) return false;
- const a = b.faceA;
- const c = b.faceB;
- return (a === faceA && c === faceB) || (a === faceB && c === faceA);
- });
- if (candidates.length === 1) return candidates[0];
- }
-
- const localPolyline = Array.isArray(edgeObj?.userData?.polylineLocal)
- ? edgeObj.userData.polylineLocal
- : null;
- if (!localPolyline || localPolyline.length < 2) return candidates[0] || null;
-
- let best = null;
- let bestScore = Infinity;
- for (const candidate of candidates) {
- const score = polylineEndpointScore(localPolyline, candidate?.positions);
- if (score < bestScore) {
- bestScore = score;
- best = candidate;
- }
- }
- return best;
-}
-
-function resolveIndicesFromPolylinePoints(polylineLocal, vp) {
- if (!Array.isArray(polylineLocal) || polylineLocal.length === 0) return [];
- if (!Array.isArray(vp) || vp.length < 3) return [];
- const vertCount = (vp.length / 3) | 0;
- const keyToIndex = new Map();
- for (let i = 0; i < vertCount; i++) {
- const base = i * 3;
- const key = `${vp[base + 0]},${vp[base + 1]},${vp[base + 2]}`;
- if (!keyToIndex.has(key)) keyToIndex.set(key, i);
- }
-
- const out = [];
- const seen = new Set();
- for (const p of polylineLocal) {
- if (!isPoint3(p)) continue;
- const key = `${p[0]},${p[1]},${p[2]}`;
- let idx = keyToIndex.get(key);
- if (idx === undefined) idx = findNearestVertexIndex(vp, p, POINT_MATCH_EPSILON);
- if (!Number.isInteger(idx) || idx < 0 || seen.has(idx)) continue;
- seen.add(idx);
- out.push(idx);
- }
- return out;
-}
-
-function findNearestVertexIndex(vp, point, epsilon = POINT_MATCH_EPSILON) {
- if (!Array.isArray(vp) || !isPoint3(point) || !Number.isFinite(epsilon) || epsilon <= 0) return -1;
- const thresholdSq = epsilon * epsilon;
- const vertCount = (vp.length / 3) | 0;
- let best = -1;
- let bestSq = thresholdSq;
- for (let i = 0; i < vertCount; i++) {
- const base = i * 3;
- const dx = vp[base + 0] - point[0];
- const dy = vp[base + 1] - point[1];
- const dz = vp[base + 2] - point[2];
- const distSq = dx * dx + dy * dy + dz * dz;
- if (distSq <= bestSq) {
- bestSq = distSq;
- best = i;
- }
- }
- return best;
-}
-
-function normalizeIndexedPolyline(indicesRaw, positionsRaw, closedHint = false) {
- const indices = Array.isArray(indicesRaw) ? indicesRaw : [];
- const positions = Array.isArray(positionsRaw) ? positionsRaw : [];
- const count = Math.min(indices.length, positions.length);
- const pairedIndices = [];
- const pairedPositions = [];
-
- for (let i = 0; i < count; i++) {
- const idx = Number(indices[i]);
- const p = positions[i];
- if (!Number.isInteger(idx) || idx < 0 || !isPoint3(p)) continue;
- pairedIndices.push(idx);
- pairedPositions.push([p[0], p[1], p[2]]);
- }
- if (pairedIndices.length < 2) return null;
-
- let closedLoop = !!closedHint;
- const lastIndex = pairedIndices.length - 1;
- const startIndex = pairedIndices[0];
- const endIndex = pairedIndices[lastIndex];
- const startPoint = pairedPositions[0];
- const endPoint = pairedPositions[lastIndex];
- if (startIndex === endIndex || pointsMatch(startPoint, endPoint)) {
- closedLoop = true;
- }
-
- if (closedLoop && pairedIndices.length >= 3 && (startIndex === endIndex || pointsMatch(startPoint, endPoint))) {
- pairedIndices.pop();
- pairedPositions.pop();
- }
- if (pairedIndices.length < 2) return null;
- return {
- indices: pairedIndices,
- positions: pairedPositions,
- closedLoop,
- };
-}
-
-function resolveEdgePolylineWithIndices(edgeObj, solid) {
- if (!edgeObj || !solid) return null;
-
- let boundaries = [];
- try {
- boundaries = (typeof solid.getBoundaryEdgePolylines === "function")
- ? (solid.getBoundaryEdgePolylines() || [])
- : [];
- } catch { boundaries = []; }
-
- const boundary = findMatchingBoundaryPolyline(edgeObj, boundaries);
- if (boundary) {
- const normalized = normalizeIndexedPolyline(
- boundary.indices,
- boundary.positions,
- !!boundary.closedLoop,
- );
- if (normalized) return normalized;
- }
-
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : null;
- const polylineLocal = Array.isArray(edgeObj?.userData?.polylineLocal)
- ? edgeObj.userData.polylineLocal
- : [];
- const fallbackIndices = resolveIndicesFromPolylinePoints(polylineLocal, vp);
- return normalizeIndexedPolyline(
- fallbackIndices,
- polylineLocal,
- !!edgeObj?.closedLoop || !!edgeObj?.userData?.closedLoop,
- );
-}
-
-function collectCurveTargetsForEdge(edgeObj, solid, options) {
- const polyline = resolveEdgePolylineWithIndices(edgeObj, solid);
- if (!polyline) {
- return {
- eligible: false,
- closedLoop: false,
- endpointIndices: [],
- targets: [],
- };
- }
-
- if (polyline.closedLoop) {
- return {
- eligible: false,
- closedLoop: true,
- endpointIndices: [],
- targets: [],
- };
- }
-
- const indices = Array.isArray(polyline.indices) ? polyline.indices : [];
- const positions = Array.isArray(polyline.positions) ? polyline.positions : [];
- const count = Math.min(indices.length, positions.length);
- if (count < 3) {
- return {
- eligible: false,
- closedLoop: false,
- endpointIndices: [],
- targets: [],
- };
- }
-
- const cleanedIndices = [];
- const cleanedPositions = [];
- for (let i = 0; i < count; i++) {
- const idx = Number(indices[i]);
- const p = positions[i];
- if (!Number.isInteger(idx) || idx < 0 || !isPoint3(p)) continue;
- cleanedIndices.push(idx);
- cleanedPositions.push([p[0], p[1], p[2]]);
- }
- if (cleanedIndices.length < 3) {
- return {
- eligible: false,
- closedLoop: false,
- endpointIndices: [],
- targets: [],
- };
- }
-
- const snapped = fitAndSnapOpenEdgePolyline(cleanedPositions, {
- fitStrength: options?.fitStrength,
- });
- if (!Array.isArray(snapped) || snapped.length !== cleanedPositions.length) {
- return {
- eligible: true,
- closedLoop: false,
- endpointIndices: [cleanedIndices[0], cleanedIndices[cleanedIndices.length - 1]],
- targets: [],
- };
- }
-
- const targets = [];
- for (let i = 1; i < cleanedIndices.length - 1; i++) {
- const idx = cleanedIndices[i];
- const point = snapped[i];
- if (!Number.isInteger(idx) || idx < 0 || !isPoint3(point)) continue;
- targets.push({ index: idx, point: [point[0], point[1], point[2]] });
- }
-
- return {
- eligible: true,
- closedLoop: false,
- endpointIndices: [cleanedIndices[0], cleanedIndices[cleanedIndices.length - 1]],
- targets,
- };
-}
-
-function collectTargetsForDescriptors(outSolid, descriptors, options) {
- const liveEdges = collectCurrentSolidEdges(outSolid);
- const targetMap = new Map();
- const lockedEndpointIndices = new Set();
- let matchedEdges = 0;
- let eligibleEdges = 0;
- let fittedEdges = 0;
- let skippedClosedLoops = 0;
- let targetAssignments = 0;
-
- for (const descriptor of descriptors) {
- const liveEdge = findMatchingEdge(liveEdges, descriptor);
- if (!liveEdge) {
- console.warn("[EdgeSmoothFeature] Could not resolve edge on cloned solid.", {
- edgeName: descriptor?.name || null,
- solidName: outSolid?.name || null,
- });
- continue;
- }
-
- matchedEdges++;
- const fitInfo = collectCurveTargetsForEdge(liveEdge, outSolid, options);
- if (fitInfo.closedLoop) {
- skippedClosedLoops++;
- continue;
- }
- if (!fitInfo.eligible) continue;
-
- eligibleEdges++;
- for (const rawEndpointIndex of fitInfo.endpointIndices || []) {
- const endpointIndex = Number(rawEndpointIndex);
- if (Number.isInteger(endpointIndex) && endpointIndex >= 0) {
- lockedEndpointIndices.add(endpointIndex);
- }
- }
-
- if (!Array.isArray(fitInfo.targets) || fitInfo.targets.length === 0) continue;
- fittedEdges++;
- for (const target of fitInfo.targets) {
- const index = Number(target?.index);
- const point = target?.point;
- if (!Number.isInteger(index) || index < 0 || !isPoint3(point)) continue;
- targetAssignments++;
- const existing = targetMap.get(index) || { x: 0, y: 0, z: 0, count: 0 };
- existing.x += point[0];
- existing.y += point[1];
- existing.z += point[2];
- existing.count += 1;
- targetMap.set(index, existing);
- }
- }
-
- for (const endpointIndex of lockedEndpointIndices) {
- targetMap.delete(endpointIndex);
- }
-
- return {
- targetMap,
- matchedEdges,
- eligibleEdges,
- fittedEdges,
- skippedClosedLoops,
- targetAssignments,
- lockedEndpointCount: lockedEndpointIndices.size,
- };
-}
-
-function rebuildSolidVertexKeyMap(solid) {
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : null;
- if (!vp) return;
- const vertCount = (vp.length / 3) | 0;
- solid._vertKeyToIndex = new Map();
- for (let i = 0; i < vertCount; i++) {
- const base = i * 3;
- solid._vertKeyToIndex.set(`${vp[base + 0]},${vp[base + 1]},${vp[base + 2]}`, i);
- }
-}
-
-function applyVertexTargets(solid, targetMap) {
- const vp = Array.isArray(solid?._vertProperties) ? solid._vertProperties : null;
- const tv = Array.isArray(solid?._triVerts) ? solid._triVerts : null;
- if (!vp || vp.length < 3 || !(targetMap instanceof Map) || targetMap.size === 0) {
- return { movedVertices: 0, constrainedVertices: 0, rejectedVertices: 0 };
- }
-
- const moveStats = applyConstrainedVertexTargets(vp, tv, targetMap, {
- minArea2Ratio: 0.04,
- minNormalDot: 0.1,
- minArea2Abs: 1e-24,
- });
- const movedVertices = Number(moveStats?.movedVertices) || 0;
- if (movedVertices <= 0) {
- return {
- movedVertices: 0,
- constrainedVertices: Number(moveStats?.constrainedVertices) || 0,
- rejectedVertices: Number(moveStats?.rejectedVertices) || 0,
- };
- }
-
- rebuildSolidVertexKeyMap(solid);
- solid._dirty = true;
- solid._faceIndex = null;
- try {
- if (solid._manifold && typeof solid._manifold.delete === "function") {
- solid._manifold.delete();
- }
- } catch { }
- solid._manifold = null;
-
- try {
- if (typeof solid._manifoldize === "function") solid._manifoldize();
- } catch (error) {
- console.warn("[EdgeSmoothFeature] Manifold rebuild failed after smoothing.", {
- solidName: solid?.name || null,
- message: error?.message || String(error || "Unknown error"),
- });
- }
-
- try {
- if (typeof solid.visualize === "function") solid.visualize();
- } catch (error) {
- console.warn("[EdgeSmoothFeature] visualize() failed after smoothing.", {
- solidName: solid?.name || null,
- message: error?.message || String(error || "Unknown error"),
- });
- }
-
- return {
- movedVertices,
- constrainedVertices: Number(moveStats?.constrainedVertices) || 0,
- rejectedVertices: Number(moveStats?.rejectedVertices) || 0,
- };
-}
-
-function groupDescriptorsBySolid(descriptors) {
- const map = new Map();
- for (const descriptor of descriptors) {
- if (!descriptor?.solid) continue;
- let list = map.get(descriptor.solid);
- if (!list) {
- list = [];
- map.set(descriptor.solid, list);
- }
- list.push(descriptor);
- }
- return map;
-}
-
-function smoothEdgesOnClone(sourceSolid, descriptors, options, featureID) {
- if (!sourceSolid || typeof sourceSolid.clone !== "function") return null;
- const outSolid = sourceSolid.clone();
- try { outSolid.name = sourceSolid.name; } catch { }
- try { if (featureID) outSolid.owningFeatureID = featureID; } catch { }
- try { outSolid.visualize(); } catch { }
-
- const targetInfo = collectTargetsForDescriptors(outSolid, descriptors, options);
- if (targetInfo.targetMap.size === 0) return null;
-
- const moveStats = applyVertexTargets(outSolid, targetInfo.targetMap);
- const movedVertices = Number(moveStats?.movedVertices) || 0;
- if (movedVertices <= 0) return null;
-
- const { triCount, vertCount } = getSolidGeometryCounts(outSolid);
- if (triCount <= 0 || vertCount <= 0) {
- console.warn("[EdgeSmoothFeature] Smoothed solid is empty; skipping replacement.", {
- solidName: sourceSolid?.name || null,
- triangleCount: triCount,
- vertexCount: vertCount,
- });
- return null;
- }
-
- return {
- solid: outSolid,
- movedVertices,
- matchedEdges: targetInfo.matchedEdges,
- eligibleEdges: targetInfo.eligibleEdges,
- fittedEdges: targetInfo.fittedEdges,
- skippedClosedLoops: targetInfo.skippedClosedLoops,
- targetAssignments: targetInfo.targetAssignments,
- lockedEndpointCount: targetInfo.lockedEndpointCount,
- constrainedVertices: Number(moveStats?.constrainedVertices) || 0,
- rejectedVertices: Number(moveStats?.rejectedVertices) || 0,
- };
-}
-
-export class EdgeSmoothFeature {
- static shortName = "ES";
- static longName = "Edge smooth";
- static inputParamsSchema = inputParamsSchema;
- static showContexButton(selectedItems) {
- const items = Array.isArray(selectedItems) ? selectedItems : [];
- const targets = items
- .filter((it) => {
- const type = String(it?.type || "").toUpperCase();
- return type === "EDGE" || type === "FACE" || type === "SOLID";
- })
- .map((it) => it?.name || it?.userData?.edgeName || it?.userData?.faceName || null)
- .filter((name) => !!name);
- if (!targets.length) return false;
- return { params: { edges: targets } };
- }
-
- constructor() {
- this.inputParams = {};
- this.persistentData = {};
- }
-
- async run(partHistory) {
- const selections = Array.isArray(this.inputParams.edges) ? this.inputParams.edges : [];
- const {
- edgeObjects,
- selectedSolidCount,
- selectedFaceCount,
- } = collectEdgeObjectsFromTargets(selections, partHistory);
- if (edgeObjects.length === 0) {
- console.warn("[EdgeSmoothFeature] No edges found from selected EDGE/FACE/SOLID targets.");
- return { added: [], removed: [] };
- }
-
- const descriptors = edgeObjects
- .map((edge) => getDescriptorFromEdge(edge))
- .filter((descriptor) => !!descriptor);
- if (!descriptors.length) {
- console.warn("[EdgeSmoothFeature] Selected targets have no owning solids.");
- return { added: [], removed: [] };
- }
-
- const rawStrength = Number(this.inputParams?.fitStrength);
- const fitStrength = Number.isFinite(rawStrength)
- ? Math.max(0, Math.min(1, rawStrength))
- : DEFAULT_FIT_STRENGTH;
-
- const grouped = groupDescriptorsBySolid(descriptors);
- const added = [];
- const removed = [];
- let totalMovedVertices = 0;
- let totalMatchedEdges = 0;
- let totalEligibleEdges = 0;
- let totalFittedEdges = 0;
- let totalSkippedClosedLoops = 0;
- let totalTargetAssignments = 0;
- let totalLockedEndpoints = 0;
- let totalConstrainedVertices = 0;
- let totalRejectedVertices = 0;
- const featureID = this.inputParams?.featureID || this.inputParams?.id || null;
-
- for (const [sourceSolid, edgeDescriptors] of grouped.entries()) {
- const result = smoothEdgesOnClone(
- sourceSolid,
- edgeDescriptors,
- { fitStrength },
- featureID,
- );
- if (!result?.solid) continue;
- added.push(result.solid);
- removed.push(sourceSolid);
- totalMovedVertices += Number(result.movedVertices) || 0;
- totalMatchedEdges += Number(result.matchedEdges) || 0;
- totalEligibleEdges += Number(result.eligibleEdges) || 0;
- totalFittedEdges += Number(result.fittedEdges) || 0;
- totalSkippedClosedLoops += Number(result.skippedClosedLoops) || 0;
- totalTargetAssignments += Number(result.targetAssignments) || 0;
- totalLockedEndpoints += Number(result.lockedEndpointCount) || 0;
- totalConstrainedVertices += Number(result.constrainedVertices) || 0;
- totalRejectedVertices += Number(result.rejectedVertices) || 0;
- }
-
- if (!added.length) {
- console.warn("[EdgeSmoothFeature] No open-edge polylines produced curve-fit updates.");
- return { added: [], removed: [] };
- }
-
- try {
- for (const obj of removed) {
- if (!obj) continue;
- obj.__removeFlag = true;
- }
- } catch { }
-
- this.persistentData = {
- ...(this.persistentData || {}),
- selectedEdgeCount: edgeObjects.length,
- selectedSolidCount,
- selectedFaceCount,
- totalMovedVertices,
- totalMatchedEdges,
- totalEligibleEdges,
- totalFittedEdges,
- totalSkippedClosedLoops,
- totalTargetAssignments,
- totalLockedEndpoints,
- totalConstrainedVertices,
- totalRejectedVertices,
- targetSolidCount: added.length,
- fitStrength,
- };
- return { added, removed };
- }
-}
diff --git a/src/features/edgeSmooth/edgeCurveFit.js b/src/features/edgeSmooth/edgeCurveFit.js
deleted file mode 100644
index e7519dc1..00000000
--- a/src/features/edgeSmooth/edgeCurveFit.js
+++ /dev/null
@@ -1,338 +0,0 @@
-const MIN_PARAM_GAP = 1e-12;
-const MIN_MATRIX_DET = 1e-18;
-const MIN_SEGMENT_LENGTH = 1e-12;
-const BACKTRACK_COS_TOL = -1e-6;
-const BACKTRACK_SEARCH_STEPS = 14;
-
-function isPoint3(p) {
- return Array.isArray(p)
- && p.length === 3
- && Number.isFinite(p[0])
- && Number.isFinite(p[1])
- && Number.isFinite(p[2]);
-}
-
-function clonePoint3(p) {
- return [p[0], p[1], p[2]];
-}
-
-function lerpPoint3(a, b, t) {
- const u = 1 - t;
- return [
- (a[0] * u) + (b[0] * t),
- (a[1] * u) + (b[1] * t),
- (a[2] * u) + (b[2] * t),
- ];
-}
-
-function computeArcLengthParams(points) {
- const count = Array.isArray(points) ? points.length : 0;
- const params = new Array(count).fill(0);
- if (count <= 1) return params;
-
- let total = 0;
- for (let i = 1; i < count; i++) {
- const a = points[i - 1];
- const b = points[i];
- if (!isPoint3(a) || !isPoint3(b)) {
- params[i] = total;
- continue;
- }
- total += Math.hypot(
- b[0] - a[0],
- b[1] - a[1],
- b[2] - a[2],
- );
- params[i] = total;
- }
-
- if (!(total > MIN_PARAM_GAP)) {
- const denom = Math.max(1, count - 1);
- for (let i = 0; i < count; i++) params[i] = i / denom;
- return params;
- }
-
- for (let i = 1; i < count; i++) params[i] /= total;
- params[count - 1] = 1;
- return params;
-}
-
-function fitOpenPolylineEndpointsExact(points) {
- const source = Array.isArray(points) ? points : [];
- const count = source.length;
- if (count < 3) return source.map((p) => (isPoint3(p) ? clonePoint3(p) : [0, 0, 0]));
- if (!source.every((p) => isPoint3(p))) return source.map((p) => (isPoint3(p) ? clonePoint3(p) : [0, 0, 0]));
-
- const params = computeArcLengthParams(source);
- const start = source[0];
- const end = source[count - 1];
- const delta = [
- end[0] - start[0],
- end[1] - start[1],
- end[2] - start[2],
- ];
-
- let s00 = 0;
- let s01 = 0;
- let s11 = 0;
- const r0 = [0, 0, 0];
- const r1 = [0, 0, 0];
- let rows = 0;
-
- for (let i = 1; i < count - 1; i++) {
- const t = params[i];
- const w = t * (1 - t);
- if (!(w > MIN_PARAM_GAP)) continue;
-
- const base = [
- start[0] + (delta[0] * t),
- start[1] + (delta[1] * t),
- start[2] + (delta[2] * t),
- ];
- const err = [
- source[i][0] - base[0],
- source[i][1] - base[1],
- source[i][2] - base[2],
- ];
- const a0 = w;
- const a1 = w * t;
-
- s00 += a0 * a0;
- s01 += a0 * a1;
- s11 += a1 * a1;
- r0[0] += a0 * err[0];
- r0[1] += a0 * err[1];
- r0[2] += a0 * err[2];
- r1[0] += a1 * err[0];
- r1[1] += a1 * err[1];
- r1[2] += a1 * err[2];
- rows++;
- }
-
- if (rows < 2) return source.map((p) => clonePoint3(p));
-
- const det = (s00 * s11) - (s01 * s01);
- if (!Number.isFinite(det) || Math.abs(det) <= MIN_MATRIX_DET) {
- return source.map((p) => clonePoint3(p));
- }
-
- const invDet = 1 / det;
- const u = [
- ((r0[0] * s11) - (r1[0] * s01)) * invDet,
- ((r0[1] * s11) - (r1[1] * s01)) * invDet,
- ((r0[2] * s11) - (r1[2] * s01)) * invDet,
- ];
- const v = [
- ((s00 * r1[0]) - (s01 * r0[0])) * invDet,
- ((s00 * r1[1]) - (s01 * r0[1])) * invDet,
- ((s00 * r1[2]) - (s01 * r0[2])) * invDet,
- ];
-
- if (!u.every((n) => Number.isFinite(n)) || !v.every((n) => Number.isFinite(n))) {
- return source.map((p) => clonePoint3(p));
- }
-
- const out = new Array(count);
- out[0] = clonePoint3(start);
- out[count - 1] = clonePoint3(end);
- for (let i = 1; i < count - 1; i++) {
- const t = params[i];
- const w = t * (1 - t);
- const base = [
- start[0] + (delta[0] * t),
- start[1] + (delta[1] * t),
- start[2] + (delta[2] * t),
- ];
- out[i] = [
- base[0] + (w * (u[0] + (v[0] * t))),
- base[1] + (w * (u[1] + (v[1] * t))),
- base[2] + (w * (u[2] + (v[2] * t))),
- ];
- }
- return out;
-}
-
-function segmentDirection(a, b) {
- return [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
-}
-
-function vectorLength(v) {
- return Math.hypot(v[0], v[1], v[2]);
-}
-
-function dot(a, b) {
- return (a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]);
-}
-
-function pointsNearlyEqual(a, b, eps = 1e-9) {
- if (!isPoint3(a) || !isPoint3(b)) return false;
- const dx = a[0] - b[0];
- const dy = a[1] - b[1];
- const dz = a[2] - b[2];
- return ((dx * dx) + (dy * dy) + (dz * dz)) <= (eps * eps);
-}
-
-export function hasLocalBacktrackingAgainstSource(sourcePoints, candidatePoints) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const candidate = Array.isArray(candidatePoints) ? candidatePoints : [];
- if (source.length !== candidate.length || source.length < 2) return true;
- for (let i = 0; i < source.length - 1; i++) {
- const s0 = source[i];
- const s1 = source[i + 1];
- const c0 = candidate[i];
- const c1 = candidate[i + 1];
- if (!isPoint3(s0) || !isPoint3(s1) || !isPoint3(c0) || !isPoint3(c1)) return true;
-
- const srcSeg = segmentDirection(s0, s1);
- const candSeg = segmentDirection(c0, c1);
- const srcLen = vectorLength(srcSeg);
- const candLen = vectorLength(candSeg);
- if (!(srcLen > MIN_SEGMENT_LENGTH) || !(candLen > MIN_SEGMENT_LENGTH)) continue;
- const cos = dot(srcSeg, candSeg) / (srcLen * candLen);
- if (cos < BACKTRACK_COS_TOL) return true;
- }
- return false;
-}
-
-function blendPolylines(sourcePoints, fittedPoints, alpha) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const fitted = Array.isArray(fittedPoints) ? fittedPoints : [];
- const count = Math.min(source.length, fitted.length);
- const out = new Array(count);
- for (let i = 0; i < count; i++) {
- if (i === 0 || i === count - 1) {
- out[i] = clonePoint3(source[i]);
- continue;
- }
- out[i] = lerpPoint3(source[i], fitted[i], alpha);
- }
- return out;
-}
-
-function enforceNoBacktracking(sourcePoints, fittedPoints) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const fitted = Array.isArray(fittedPoints) ? fittedPoints : [];
- if (!hasLocalBacktrackingAgainstSource(source, fitted)) return fitted;
-
- let lo = 0;
- let hi = 1;
- let safe = source.map((p) => clonePoint3(p));
-
- for (let i = 0; i < BACKTRACK_SEARCH_STEPS; i++) {
- const alpha = (lo + hi) * 0.5;
- const blended = blendPolylines(source, fitted, alpha);
- if (hasLocalBacktrackingAgainstSource(source, blended)) hi = alpha;
- else {
- lo = alpha;
- safe = blended;
- }
- }
- return safe;
-}
-
-export function fitAndSnapOpenEdgePolyline(points, options = {}) {
- const source = Array.isArray(points) ? points : [];
- if (source.length < 2) return [];
- if (!source.every((p) => isPoint3(p))) return source.map((p) => (isPoint3(p) ? clonePoint3(p) : [0, 0, 0]));
-
- const rawStrength = Number(options?.fitStrength);
- const fitStrength = Number.isFinite(rawStrength)
- ? Math.max(0, Math.min(1, rawStrength))
- : 1;
-
- if (source.length < 3 || fitStrength <= 0) {
- return source.map((p) => clonePoint3(p));
- }
-
- const fitted = fitOpenPolylineEndpointsExact(source);
- const blended = blendPolylines(source, fitted, fitStrength);
- return enforceNoBacktracking(source, blended);
-}
-
-function hasLocalBacktrackingAgainstSourceClosed(sourcePoints, candidatePoints) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const candidate = Array.isArray(candidatePoints) ? candidatePoints : [];
- const count = source.length;
- if (count !== candidate.length || count < 3) return true;
- for (let i = 0; i < count; i++) {
- const next = (i + 1) % count;
- const s0 = source[i];
- const s1 = source[next];
- const c0 = candidate[i];
- const c1 = candidate[next];
- if (!isPoint3(s0) || !isPoint3(s1) || !isPoint3(c0) || !isPoint3(c1)) return true;
-
- const srcSeg = segmentDirection(s0, s1);
- const candSeg = segmentDirection(c0, c1);
- const srcLen = vectorLength(srcSeg);
- const candLen = vectorLength(candSeg);
- if (!(srcLen > MIN_SEGMENT_LENGTH) || !(candLen > MIN_SEGMENT_LENGTH)) continue;
- const cos = dot(srcSeg, candSeg) / (srcLen * candLen);
- if (cos < BACKTRACK_COS_TOL) return true;
- }
- return false;
-}
-
-function enforceNoBacktrackingClosed(sourcePoints, fittedPoints) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const fitted = Array.isArray(fittedPoints) ? fittedPoints : [];
- if (!hasLocalBacktrackingAgainstSourceClosed(source, fitted)) return fitted;
-
- let lo = 0;
- let hi = 1;
- let safe = source.map((p) => clonePoint3(p));
- for (let i = 0; i < BACKTRACK_SEARCH_STEPS; i++) {
- const alpha = (lo + hi) * 0.5;
- const blended = new Array(source.length);
- for (let j = 0; j < source.length; j++) {
- blended[j] = lerpPoint3(source[j], fitted[j], alpha);
- }
- if (hasLocalBacktrackingAgainstSourceClosed(source, blended)) hi = alpha;
- else {
- lo = alpha;
- safe = blended;
- }
- }
- return safe;
-}
-
-export function fitAndSnapClosedEdgePolyline(points, options = {}) {
- const sourceIn = Array.isArray(points) ? points : [];
- if (sourceIn.length < 3) return sourceIn.map((p) => (isPoint3(p) ? clonePoint3(p) : [0, 0, 0]));
- if (!sourceIn.every((p) => isPoint3(p))) return sourceIn.map((p) => (isPoint3(p) ? clonePoint3(p) : [0, 0, 0]));
-
- const source = sourceIn.map((p) => clonePoint3(p));
- if (source.length >= 4 && pointsNearlyEqual(source[0], source[source.length - 1])) {
- source.pop();
- }
- const count = source.length;
- if (count < 3) return source;
-
- const rawStrength = Number(options?.fitStrength);
- const fitStrength = Number.isFinite(rawStrength)
- ? Math.max(0, Math.min(1, rawStrength))
- : 1;
- if (fitStrength <= 0) return source.map((p) => clonePoint3(p));
-
- const rawAnchor = Number(options?.anchorIndex);
- const anchorIndex = Number.isInteger(rawAnchor) && rawAnchor >= 0
- ? (rawAnchor % count)
- : 0;
-
- const rotated = new Array(count + 1);
- for (let i = 0; i < count; i++) {
- rotated[i] = clonePoint3(source[(anchorIndex + i) % count]);
- }
- rotated[count] = clonePoint3(rotated[0]);
-
- const fittedRotated = fitAndSnapOpenEdgePolyline(rotated, { fitStrength });
- if (!Array.isArray(fittedRotated) || fittedRotated.length !== rotated.length) {
- return source.map((p) => clonePoint3(p));
- }
-
- const remapped = source.map((p) => clonePoint3(p));
- for (let i = 0; i < count; i++) {
- remapped[(anchorIndex + i) % count] = clonePoint3(fittedRotated[i]);
- }
- return enforceNoBacktrackingClosed(source, remapped);
-}
diff --git a/src/features/edgeSmooth/vertexTargetConstraints.js b/src/features/edgeSmooth/vertexTargetConstraints.js
deleted file mode 100644
index 87adc37e..00000000
--- a/src/features/edgeSmooth/vertexTargetConstraints.js
+++ /dev/null
@@ -1,212 +0,0 @@
-const DEFAULT_STEP_FACTORS = Object.freeze([1, 0.85, 0.7, 0.55, 0.4, 0.3, 0.2, 0.1, 0.05]);
-const DEFAULT_MIN_AREA2_RATIO = 0.04;
-const DEFAULT_MIN_NORMAL_DOT = 0.1;
-const DEFAULT_MIN_AREA2_ABS = 1e-24;
-
-function triArea2AndNormal(vp, tv, triIndex, movedVertex = -1, mx = 0, my = 0, mz = 0) {
- const triBase = triIndex * 3;
- const i0 = tv[triBase + 0] >>> 0;
- const i1 = tv[triBase + 1] >>> 0;
- const i2 = tv[triBase + 2] >>> 0;
-
- const b0 = i0 * 3;
- const b1 = i1 * 3;
- const b2 = i2 * 3;
- if ((b0 + 2) >= vp.length || (b1 + 2) >= vp.length || (b2 + 2) >= vp.length) return null;
-
- const ax = (i0 === movedVertex) ? mx : Number(vp[b0 + 0]);
- const ay = (i0 === movedVertex) ? my : Number(vp[b0 + 1]);
- const az = (i0 === movedVertex) ? mz : Number(vp[b0 + 2]);
- const bx = (i1 === movedVertex) ? mx : Number(vp[b1 + 0]);
- const by = (i1 === movedVertex) ? my : Number(vp[b1 + 1]);
- const bz = (i1 === movedVertex) ? mz : Number(vp[b1 + 2]);
- const cx = (i2 === movedVertex) ? mx : Number(vp[b2 + 0]);
- const cy = (i2 === movedVertex) ? my : Number(vp[b2 + 1]);
- const cz = (i2 === movedVertex) ? mz : Number(vp[b2 + 2]);
- if (!Number.isFinite(ax) || !Number.isFinite(ay) || !Number.isFinite(az)
- || !Number.isFinite(bx) || !Number.isFinite(by) || !Number.isFinite(bz)
- || !Number.isFinite(cx) || !Number.isFinite(cy) || !Number.isFinite(cz)) {
- return null;
- }
-
- const ux = bx - ax;
- const uy = by - ay;
- const uz = bz - az;
- const vx = cx - ax;
- const vy = cy - ay;
- const vz = cz - az;
- const nx = (uy * vz) - (uz * vy);
- const ny = (uz * vx) - (ux * vz);
- const nz = (ux * vy) - (uy * vx);
- const area2 = (nx * nx) + (ny * ny) + (nz * nz);
- if (!(area2 > 0) || !Number.isFinite(area2)) return null;
- const invLen = 1 / Math.sqrt(area2);
- return { area2, nx: nx * invLen, ny: ny * invLen, nz: nz * invLen };
-}
-
-function buildVertexIncidentTriangles(tv, vertexCount) {
- const incident = Array.from({ length: vertexCount }, () => []);
- const triCount = (tv.length / 3) | 0;
- for (let t = 0; t < triCount; t++) {
- const base = t * 3;
- const i0 = tv[base + 0] >>> 0;
- const i1 = tv[base + 1] >>> 0;
- const i2 = tv[base + 2] >>> 0;
- if (i0 < vertexCount) incident[i0].push(t);
- if (i1 < vertexCount) incident[i1].push(t);
- if (i2 < vertexCount) incident[i2].push(t);
- }
- return incident;
-}
-
-export function applyConstrainedVertexTargets(vp, tv, targetMap, options = {}) {
- if (!Array.isArray(vp) || vp.length < 3 || !(targetMap instanceof Map) || targetMap.size === 0) {
- return { movedVertices: 0, constrainedVertices: 0, rejectedVertices: 0 };
- }
-
- const vertexCount = (vp.length / 3) | 0;
- const maxIndex = vertexCount - 1;
- const hasTopology = Array.isArray(tv) && tv.length >= 3;
- const incident = hasTopology ? buildVertexIncidentTriangles(tv, vertexCount) : null;
-
- const minArea2RatioRaw = Number(options?.minArea2Ratio);
- const minArea2Ratio = Number.isFinite(minArea2RatioRaw)
- ? Math.max(0, Math.min(1, minArea2RatioRaw))
- : DEFAULT_MIN_AREA2_RATIO;
-
- const minNormalDotRaw = Number(options?.minNormalDot);
- const minNormalDot = Number.isFinite(minNormalDotRaw)
- ? Math.max(-1, Math.min(1, minNormalDotRaw))
- : DEFAULT_MIN_NORMAL_DOT;
-
- const minArea2AbsRaw = Number(options?.minArea2Abs);
- const minArea2Abs = Number.isFinite(minArea2AbsRaw) && minArea2AbsRaw > 0
- ? minArea2AbsRaw
- : DEFAULT_MIN_AREA2_ABS;
-
- const stepFactorsRaw = Array.isArray(options?.stepFactors) ? options.stepFactors : null;
- const stepFactors = (stepFactorsRaw && stepFactorsRaw.length)
- ? stepFactorsRaw
- .map((v) => Number(v))
- .filter((v) => Number.isFinite(v) && v > 0)
- .map((v) => Math.min(1, v))
- .sort((a, b) => b - a)
- : DEFAULT_STEP_FACTORS;
-
- let movedVertices = 0;
- let constrainedVertices = 0;
- let rejectedVertices = 0;
-
- for (const [rawIndex, aggregate] of targetMap.entries()) {
- const index = Number(rawIndex);
- if (!Number.isInteger(index) || index < 0 || index > maxIndex) continue;
-
- const count = Number(aggregate?.count);
- if (!(count > 0)) continue;
- const tx = Number(aggregate?.x) / count;
- const ty = Number(aggregate?.y) / count;
- const tz = Number(aggregate?.z) / count;
- if (!Number.isFinite(tx) || !Number.isFinite(ty) || !Number.isFinite(tz)) continue;
-
- const base = index * 3;
- const ox = Number(vp[base + 0]);
- const oy = Number(vp[base + 1]);
- const oz = Number(vp[base + 2]);
- if (!Number.isFinite(ox) || !Number.isFinite(oy) || !Number.isFinite(oz)) continue;
-
- const dx = tx - ox;
- const dy = ty - oy;
- const dz = tz - oz;
- const distSq = (dx * dx) + (dy * dy) + (dz * dz);
- if (distSq <= 1e-30) continue;
-
- if (!hasTopology) {
- vp[base + 0] = tx;
- vp[base + 1] = ty;
- vp[base + 2] = tz;
- movedVertices++;
- continue;
- }
-
- const triList = incident?.[index];
- if (!Array.isArray(triList) || triList.length === 0) {
- vp[base + 0] = tx;
- vp[base + 1] = ty;
- vp[base + 2] = tz;
- movedVertices++;
- continue;
- }
-
- const baselines = [];
- for (const triIndex of triList) {
- const info = triArea2AndNormal(vp, tv, triIndex, -1, 0, 0, 0);
- if (!info || !(info.area2 > minArea2Abs)) continue;
- baselines.push({ triIndex, area2: info.area2, nx: info.nx, ny: info.ny, nz: info.nz });
- }
-
- let acceptedFactor = null;
- let acceptedX = ox;
- let acceptedY = oy;
- let acceptedZ = oz;
- if (!baselines.length) {
- acceptedFactor = 1;
- acceptedX = tx;
- acceptedY = ty;
- acceptedZ = tz;
- } else {
- for (const factor of stepFactors) {
- const cx = ox + (dx * factor);
- const cy = oy + (dy * factor);
- const cz = oz + (dz * factor);
- let valid = true;
- for (const baseline of baselines) {
- const candidate = triArea2AndNormal(vp, tv, baseline.triIndex, index, cx, cy, cz);
- if (!candidate || !(candidate.area2 > minArea2Abs)) {
- valid = false;
- break;
- }
- const minArea2 = Math.max(minArea2Abs, baseline.area2 * minArea2Ratio);
- if (!(candidate.area2 >= minArea2)) {
- valid = false;
- break;
- }
- const dot = (candidate.nx * baseline.nx) + (candidate.ny * baseline.ny) + (candidate.nz * baseline.nz);
- if (!Number.isFinite(dot) || dot < minNormalDot) {
- valid = false;
- break;
- }
- }
- if (!valid) continue;
- acceptedFactor = factor;
- acceptedX = cx;
- acceptedY = cy;
- acceptedZ = cz;
- break;
- }
- }
-
- if (acceptedFactor === null) {
- rejectedVertices++;
- continue;
- }
-
- const adx = acceptedX - ox;
- const ady = acceptedY - oy;
- const adz = acceptedZ - oz;
- const acceptedDistSq = (adx * adx) + (ady * ady) + (adz * adz);
- if (!(acceptedDistSq > 1e-30)) continue;
-
- vp[base + 0] = acceptedX;
- vp[base + 1] = acceptedY;
- vp[base + 2] = acceptedZ;
- movedVertices++;
- if (acceptedFactor < 0.999999) constrainedVertices++;
- }
-
- return {
- movedVertices,
- constrainedVertices,
- rejectedVertices,
- };
-}
-
diff --git a/src/features/fillet/FilletFeature.js b/src/features/fillet/FilletFeature.js
index f374c24a..9a5e0cdf 100644
--- a/src/features/fillet/FilletFeature.js
+++ b/src/features/fillet/FilletFeature.js
@@ -5,15 +5,6 @@ import {
} from "../edgeFeatureUtils.js";
import { runSheetMetalCornerFillet } from "../sheetMetal/sheetMetalEngineBridge.js";
-const DEBUG_MODE_NONE = "NONE";
-const DEBUG_MODE_WEDGE_AND_TUBE = "WEDGE AND TUBE";
-const DEBUG_MODE_WEDGE_AND_TUBE_AFTER_BOOLEAN = "WEDGE AND TUBE AFTER BOOLEAN";
-const DEBUG_MODE_COMBINED_BEFORE_TARGET = "COMBINED FILLET BEFORE TARGET BOOLEAN";
-const FINAL_FILLET_SIMPLIFY_TOLERANCE = 0.0009;
-const FILLET_NATIVE_TINY_FACE_ISLAND_CLEANUP_AREA = 0.01;
-const FILLET_POST_COLLAPSE_TINY_TRIANGLE_THRESHOLD = 0.001;
-const FILLET_POST_COLLAPSE_TINY_FACE_ISLAND_CLEANUP_AREA = 0.01;
-
const inputParamsSchema = {
id: {
type: "string",
@@ -33,99 +24,8 @@ const inputParamsSchema = {
default_value: 1,
hint: "Fillet radius",
},
- resolution: {
- type: "number",
- step: 1,
- default_value: "resolution",
- hint: "Segments around the fillet tube circumference",
- },
- inflate: {
- type: "number",
- step: 0.1,
- default_value: 0.1,
- hint: "Grow the cutting solid by this amount (units). Keep tiny (e.g. 0.0005). Closed loops ignore inflation to avoid self‑intersection.",
- },
- nudgeFaceDistance: {
- type: "number",
- step: 0.0001,
- default_value: 0.0001,
- hint: "Push fillet wedge end caps outward by this amount before booleaning (0 disables).",
- },
- simplifyResult: {
- type: "boolean",
- default_value: true,
- hint: "Run the final simplify pass on the fillet result.",
- },
- cleanupNativeTinyFaceIslands: {
- type: "boolean",
- default_value: true,
- hint: "Run the native tiny-face island cleanup during fillet combine.",
- },
- mergeCoplanarEndCaps: {
- type: "boolean",
- default_value: true,
- hint: "Merge coplanar fillet end caps into adjacent host faces.",
- },
- reassignSliverTriangles: {
- type: "boolean",
- default_value: true,
- hint: "Reassign tiny fillet sidewall sliver triangles into planar neighbors.",
- },
- collapseTinyTriangles: {
- type: "boolean",
- default_value: true,
- hint: "Collapse tiny triangles on the final fillet result.",
- },
- cleanupPostCollapseTinyFaceIslands: {
- type: "boolean",
- default_value: true,
- hint: "Clean up tiny face islands after collapsing tiny triangles.",
- },
- direction: {
- type: "options",
- options: ["AUTO", "INSET", "OUTSET"],
- default_value: "AUTO",
- hint: "AUTO classifies each selected edge as inside/outside and applies subtract/union automatically.",
- },
- debug: {
- type: "options",
- options: [
- DEBUG_MODE_NONE,
- DEBUG_MODE_WEDGE_AND_TUBE,
- DEBUG_MODE_WEDGE_AND_TUBE_AFTER_BOOLEAN,
- DEBUG_MODE_COMBINED_BEFORE_TARGET,
- ],
- default_value: DEBUG_MODE_NONE,
- hint: "Controls which fillet debug solids are emitted.",
- },
};
-function resolveDebugMode(rawValue) {
- const normalized = String(rawValue).trim().toUpperCase();
- if (normalized === DEBUG_MODE_NONE) return DEBUG_MODE_NONE;
- if (normalized === DEBUG_MODE_WEDGE_AND_TUBE) return DEBUG_MODE_WEDGE_AND_TUBE;
- if (normalized === DEBUG_MODE_WEDGE_AND_TUBE_AFTER_BOOLEAN) {
- return DEBUG_MODE_WEDGE_AND_TUBE_AFTER_BOOLEAN;
- }
- if (normalized === DEBUG_MODE_COMBINED_BEFORE_TARGET) {
- return DEBUG_MODE_COMBINED_BEFORE_TARGET;
- }
- return DEBUG_MODE_NONE;
-}
-
-function getDebugConfig(debugMode) {
- if (debugMode === DEBUG_MODE_WEDGE_AND_TUBE) {
- return { enabled: true, solidsLevel: 0, showCombinedBeforeTarget: false };
- }
- if (debugMode === DEBUG_MODE_WEDGE_AND_TUBE_AFTER_BOOLEAN) {
- return { enabled: true, solidsLevel: 1, showCombinedBeforeTarget: false };
- }
- if (debugMode === DEBUG_MODE_COMBINED_BEFORE_TARGET) {
- return { enabled: true, solidsLevel: -1, showCombinedBeforeTarget: true };
- }
- return { enabled: false, solidsLevel: -1, showCombinedBeforeTarget: false };
-}
-
function normalizeSelectionToken(token) {
const raw = String(token || '').trim();
if (!raw) return null;
@@ -517,22 +417,9 @@ export class FilletFeature {
}
async run(partHistory) {
- const debugMode = resolveDebugMode(this.inputParams?.debug);
- const debugConfig = getDebugConfig(debugMode);
- const debugEnabled = !!debugConfig.enabled;
- const configuredDebugLevel = Number(debugConfig.solidsLevel);
- const debugShowCombinedBeforeTarget = !!debugConfig.showCombinedBeforeTarget;
console.log('[FilletFeature] Starting fillet run...', {
featureID: this.inputParams?.featureID,
- direction: this.inputParams?.direction,
radius: this.inputParams?.radius,
- resolution: this.inputParams?.resolution,
- inflate: this.inputParams?.inflate,
- nudgeFaceDistance: this.inputParams?.nudgeFaceDistance,
- debug: debugEnabled,
- debugMode,
- debugSolidsLevel: configuredDebugLevel,
- debugShowCombinedBeforeTarget,
});
const added = [];
const removed = [];
@@ -569,7 +456,6 @@ export class FilletFeature {
edgeNames: edgeObjs.map(e => e?.name).filter(Boolean),
});
- const dir = String(this.inputParams.direction || 'AUTO').toUpperCase();
const r = Number(this.inputParams.radius);
if (!Number.isFinite(r) || !(r > 0)) {
console.warn('[FilletFeature] Invalid radius supplied; aborting.', { radius: this.inputParams.radius });
@@ -585,7 +471,6 @@ export class FilletFeature {
selections: rawInputSelections,
edgeSelections: edgeObjs,
radius: r,
- resolution: this.inputParams?.resolution,
featureID: fid || "SM_FILLET",
showFlatPattern: true,
});
@@ -612,47 +497,11 @@ export class FilletFeature {
}
let result = null;
- const simplifyResult = this.inputParams?.simplifyResult !== false;
- const cleanupNativeTinyFaceIslands = this.inputParams?.cleanupNativeTinyFaceIslands !== false;
- const mergeCoplanarEndCaps = this.inputParams?.mergeCoplanarEndCaps !== false;
- const reassignSliverTriangles = this.inputParams?.reassignSliverTriangles !== false;
- const collapseTinyTriangles = this.inputParams?.collapseTinyTriangles !== false;
- const cleanupPostCollapseTinyFaceIslands = this.inputParams?.cleanupPostCollapseTinyFaceIslands !== false;
result = await targetSolid.fillet({
radius: r,
- resolution: this.inputParams?.resolution,
edges: edgeObjs,
featureID: fid,
- direction: dir,
- inflate: Number(this.inputParams.inflate) || 0,
- nudgeFaceDistance: this.inputParams?.nudgeFaceDistance,
- cleanupTinyFaceIslandsArea: cleanupNativeTinyFaceIslands
- ? FILLET_NATIVE_TINY_FACE_ISLAND_CLEANUP_AREA
- : 0,
- mergeCoplanarEndCaps,
- reassignSliverTriangles,
- debug: debugEnabled,
- debugSolidsLevel: configuredDebugLevel,
- debugShowCombinedBeforeTarget,
});
- try {
- result.__filletFinalSimplifyEnabled = simplifyResult;
- result.__filletNativeTinyFaceIslandCleanupEnabled = cleanupNativeTinyFaceIslands;
- result.__filletPostCollapseTinyTriangleCollapseEnabled = collapseTinyTriangles;
- result.__filletPostCollapseTinyFaceIslandCleanupEnabled = cleanupPostCollapseTinyFaceIslands;
- } catch { }
- const collectDebugSolids = (res) => {
- const out = [];
- if (!Array.isArray(res?.__debugAddedSolids)) return out;
- for (const dbg of res.__debugAddedSolids) {
- if (!dbg) continue;
- try { dbg.name = `${fid}_${dbg.name || 'DEBUG'}`; } catch { }
- console.log('[FilletFeature] Adding fillet debug solid', { featureID: fid, name: dbg.name });
- out.push(dbg);
- }
- return out;
- };
- const debugSolids = collectDebugSolids(result);
const edgeDirectionDecision = result?.__filletDirectionDecision || null;
const cornerBridgeCountRaw = Number(result?.__filletCornerBridgeCount);
const cornerBridgeCount = Number.isFinite(cornerBridgeCountRaw) ? Math.max(0, Math.trunc(cornerBridgeCountRaw)) : 0;
@@ -668,22 +517,10 @@ export class FilletFeature {
if (!result) {
throw new Error(`[FilletFeature] Fillet returned no result for feature ${fid || '(unknown)'}.`);
}
- if (simplifyResult && typeof result.simplify === 'function') {
- try {
- result.simplify(FINAL_FILLET_SIMPLIFY_TOLERANCE, true);
- } catch (e) {
- console.warn('[FilletFeature] Final simplify cleanup failed; keeping unsimplified fillet result.', {
- featureID: fid,
- tolerance: FINAL_FILLET_SIMPLIFY_TOLERANCE,
- error: e,
- });
- }
- }
const { triCount, vertCount } = getSolidGeometryCounts(result);
if (triCount === 0 || vertCount === 0) {
throw new Error(`[FilletFeature] Fillet produced empty geometry for feature ${fid || '(unknown)'}. `
- + `(triangles=${triCount}, vertices=${vertCount}, direction=${dir}, radius=${r}, `
- + `inflate=${this.inputParams.inflate})`);
+ + `(triangles=${triCount}, vertices=${vertCount}, radius=${r})`);
}
console.log('[FilletFeature] Fillet succeeded; replacing target solid.', {
featureID: fid,
@@ -692,33 +529,15 @@ export class FilletFeature {
edgeDirectionDecision: edgeDirectionDecision || null,
});
added.push(result);
- added.push(...debugSolids);
// Replace the original geometry in the scene
removed.push(targetSolid);
-
-
-
- // loop over all added objects and set the epsilon vale on the solid
for (const obj of added) {
- if (obj && typeof obj === 'object' && typeof obj.setEpsilon === 'function') {
+ if (obj && typeof obj === 'object' && typeof obj.visualize === 'function') {
try {
- if (collapseTinyTriangles) {
- await obj.collapseTinyTriangles(FILLET_POST_COLLAPSE_TINY_TRIANGLE_THRESHOLD);
- }
- obj.__filletPostCollapseTinyTriangleCollapseEnabled = collapseTinyTriangles;
- if (cleanupPostCollapseTinyFaceIslands && typeof obj.cleanupTinyFaceIslands === 'function') {
- obj.__filletPostCollapseTinyFaceIslandCleanupCount = Math.max(
- 0,
- Number(obj.cleanupTinyFaceIslands(FILLET_POST_COLLAPSE_TINY_FACE_ISLAND_CLEANUP_AREA) || 0),
- );
- } else {
- obj.__filletPostCollapseTinyFaceIslandCleanupCount = 0;
- }
- obj.__filletPostCollapseTinyFaceIslandCleanupEnabled = cleanupPostCollapseTinyFaceIslands;
obj.visualize()
} catch (e) {
- console.warn('[FilletFeature] Failed to set epsilon on fillet result solid.', { error: e });
+ console.warn('[FilletFeature] Failed to visualize fillet result solid.', { error: e });
}
}
}
diff --git a/src/features/hole/HoleFeature.js b/src/features/hole/HoleFeature.js
index 60af2173..e89c06d9 100644
--- a/src/features/hole/HoleFeature.js
+++ b/src/features/hole/HoleFeature.js
@@ -1,5 +1,6 @@
import { BREP } from '../../BREP/BREP.js';
import { ThreadGeometry, ThreadStandard } from '../../BREP/threadGeometry.js';
+import { hasOccShape, setOccState, subtractOccSolidTools } from '../../BREP/OpenCascadeKernel.js';
import { getClearanceDiameter } from './screwClearance.js';
const inputParamsSchema = {
@@ -128,14 +129,6 @@ const inputParamsSchema = {
step: 0.01,
hint: 'Optional clearance (+) or interference (-) applied to the thread profile',
},
- threadSegmentsPerTurn: {
- type: 'number',
- label: 'Thread segments/turn',
- default_value: 32,
- min: 4,
- step: 1,
- hint: 'Resolution for modeled threads (ignored for symbolic)',
- },
debugShowSolid: {
type: 'boolean',
label: 'Debug: show tool solid',
@@ -151,6 +144,11 @@ const inputParamsSchema = {
};
const THREE = BREP.THREE;
+const DEBUG_HOLE_FEATURE = false;
+
+function holeDebugLog(...args) {
+ if (DEBUG_HOLE_FEATURE) console.log(...args);
+}
function fallbackVector(v, def = new THREE.Vector3()) {
if (!v || typeof v.x !== 'number' || typeof v.y !== 'number' || typeof v.z !== 'number') return def.clone();
@@ -181,6 +179,33 @@ function boxDiagonalLength(obj) {
return 0;
}
+function boxExitDistanceAlongDirection(obj, origin, direction) {
+ if (!obj || !origin || !direction || direction.lengthSq() < 1e-12) return 0;
+ try {
+ const box = new THREE.Box3().setFromObject(obj);
+ if (box.isEmpty()) return 0;
+ const dir = direction.clone().normalize();
+ let best = Infinity;
+ const axes = ['x', 'y', 'z'];
+ for (const axis of axes) {
+ const d = dir[axis];
+ if (Math.abs(d) <= 1e-12) continue;
+ const plane = d > 0 ? box.max[axis] : box.min[axis];
+ const t = (plane - origin[axis]) / d;
+ if (t < -1e-9 || t >= best) continue;
+ const p = origin.clone().addScaledVector(dir, t);
+ const inside =
+ p.x >= box.min.x - 1e-7 && p.x <= box.max.x + 1e-7 &&
+ p.y >= box.min.y - 1e-7 && p.y <= box.max.y + 1e-7 &&
+ p.z >= box.min.z - 1e-7 && p.z <= box.max.z + 1e-7;
+ if (inside) best = t;
+ }
+ return Number.isFinite(best) ? Math.max(0, best) : 0;
+ } catch {
+ return 0;
+ }
+}
+
function parseNumberLike(value) {
if (value == null) return NaN;
const raw = String(value).trim();
@@ -209,171 +234,106 @@ function unionSolids(solids) {
console.warn('[HoleFeature] unionSolids: no solids provided');
return null;
}
- console.log('[HoleFeature] unionSolids: combining', solids.length, 'solids');
+ holeDebugLog('[HoleFeature] unionSolids: combining', solids.length, 'solids');
let current = solids[0];
if (!current) {
console.warn('[HoleFeature] unionSolids: first solid is null/undefined');
return null;
}
- console.log('[HoleFeature] unionSolids: first solid type:', current?.constructor?.name);
+ holeDebugLog('[HoleFeature] unionSolids: first solid type:', current?.constructor?.name);
for (let i = 1; i < solids.length; i++) {
const next = solids[i];
if (!next) continue;
try {
- console.log('[HoleFeature] unionSolids: unioning with solid', i, 'type:', next?.constructor?.name);
+ holeDebugLog('[HoleFeature] unionSolids: unioning with solid', i, 'type:', next?.constructor?.name);
current = current.union(next);
}
catch (error) { console.warn('[HoleFeature] Union failed:', error); }
}
- console.log('[HoleFeature] unionSolids: final result type:', current?.constructor?.name);
+ holeDebugLog('[HoleFeature] unionSolids: final result type:', current?.constructor?.name);
return current;
}
-function triangleArea(tri) {
- const p0 = tri?.p1;
- const p1 = tri?.p2;
- const p2 = tri?.p3;
- if (!Array.isArray(p0) || !Array.isArray(p1) || !Array.isArray(p2)) return 0;
- const ux = p1[0] - p0[0];
- const uy = p1[1] - p0[1];
- const uz = p1[2] - p0[2];
- const vx = p2[0] - p0[0];
- const vy = p2[1] - p0[1];
- const vz = p2[2] - p0[2];
- const cx = uy * vz - uz * vy;
- const cy = uz * vx - ux * vz;
- const cz = ux * vy - uy * vx;
- return 0.5 * Math.hypot(cx, cy, cz);
-}
-
-function faceComponentStats(solid, faceName) {
- let tris = [];
- try {
- tris = typeof solid?.getFace === 'function' ? solid.getFace(faceName) : [];
- } catch {
- tris = [];
- }
- if (!Array.isArray(tris) || tris.length === 0) {
- return { componentCount: 0, componentAreas: [], totalArea: 0 };
- }
-
- const edgeToTri = new Map();
- const triAdj = Array.from({ length: tris.length }, () => []);
- const areas = new Float64Array(tris.length);
- const edgeKey = (a, b) => (a < b ? `${a}|${b}` : `${b}|${a}`);
-
- for (let t = 0; t < tris.length; t++) {
- const tri = tris[t];
- areas[t] = triangleArea(tri);
- const idx = Array.isArray(tri?.indices) && tri.indices.length === 3 ? tri.indices : null;
- if (!idx) continue;
- const i0 = Number(idx[0]);
- const i1 = Number(idx[1]);
- const i2 = Number(idx[2]);
- if (!Number.isFinite(i0) || !Number.isFinite(i1) || !Number.isFinite(i2)) continue;
- const edges = [[i0, i1], [i1, i2], [i2, i0]];
- for (const [a, b] of edges) {
- const key = edgeKey(a, b);
- let arr = edgeToTri.get(key);
- if (!arr) {
- arr = [];
- edgeToTri.set(key, arr);
- }
- arr.push(t);
- }
+async function subtractHoleToolsSequentially(partHistory, tools, booleanParam, featureID) {
+ const rawTargets = Array.isArray(booleanParam?.targets) ? booleanParam.targets.filter(Boolean) : [];
+ if (!tools.length) return { added: [], removed: [] };
+ if (!rawTargets.length) {
+ const combinedTool = tools.length === 1 ? tools[0] : unionSolids(tools);
+ return BREP.applyBooleanOperation(partHistory || {}, combinedTool, booleanParam, featureID);
}
- for (const triList of edgeToTri.values()) {
- if (!Array.isArray(triList) || triList.length !== 2) continue;
- const a = triList[0];
- const b = triList[1];
- triAdj[a].push(b);
- triAdj[b].push(a);
- }
+ let currentTargets = rawTargets;
+ const removed = new Set();
+ let lastEffects = { added: rawTargets, removed: [] };
- const seen = new Uint8Array(tris.length);
- const componentAreas = [];
- for (let i = 0; i < tris.length; i++) {
- if (seen[i]) continue;
- const stack = [i];
- seen[i] = 1;
- let area = 0;
- while (stack.length) {
- const t = stack.pop();
- area += areas[t] || 0;
- const nbrs = triAdj[t];
- for (let j = 0; j < nbrs.length; j++) {
- const u = nbrs[j];
- if (seen[u]) continue;
- seen[u] = 1;
- stack.push(u);
- }
+ for (let i = 0; i < tools.length; i += 1) {
+ const tool = tools[i];
+ const stepParam = {
+ ...booleanParam,
+ operation: 'SUBTRACT',
+ targets: currentTargets,
+ };
+ lastEffects = await BREP.applyBooleanOperation(partHistory || {}, tool, stepParam, featureID);
+ for (const item of lastEffects.removed || []) {
+ if (item) removed.add(item);
}
- componentAreas.push(area);
+ const nextTargets = Array.isArray(lastEffects.added) ? lastEffects.added.filter(Boolean) : [];
+ if (!nextTargets.length) break;
+ currentTargets = nextTargets;
}
- componentAreas.sort((a, b) => b - a);
- const totalArea = componentAreas.reduce((sum, a) => sum + a, 0);
- return { componentCount: componentAreas.length, componentAreas, totalArea };
+ return {
+ added: Array.isArray(lastEffects.added) ? lastEffects.added : [],
+ removed: Array.from(removed),
+ };
}
-function cleanupModeledThreadFaceIslands(tool, faceNames, pitch) {
- if (!tool || typeof tool.cleanupTinyFaceIslands !== 'function') return null;
- const names = Array.isArray(faceNames) ? faceNames.filter(Boolean) : [];
- if (!names.length) return null;
-
- const collectStats = () => names.map((name) => {
- const s = faceComponentStats(tool, name);
- return {
- name,
- components: s.componentCount,
- totalArea: s.totalArea,
- largestComponentArea: s.componentAreas[0] || 0,
- smallestIslandArea: s.componentAreas.length > 1 ? s.componentAreas[s.componentAreas.length - 1] : 0,
- };
- });
-
- const p = Math.max(1e-4, Math.abs(Number(pitch) || 0));
- let areaThreshold = Math.max(1e-9, p * p * 1e-4);
- const maxThreshold = Math.max(areaThreshold, p * p * 0.05);
- const before = collectStats();
- let passes = 0;
- let totalReassigned = 0;
-
- for (let pass = 0; pass < 6; pass++) {
- passes = pass + 1;
- const stats = names.map((name) => ({ name, ...faceComponentStats(tool, name) }));
- const worstComponents = stats.reduce((m, s) => Math.max(m, s.componentCount), 0);
- if (worstComponents <= 1) break;
-
- const smallAreas = [];
- for (const s of stats) {
- if (s.componentAreas.length <= 1) continue;
- for (let i = 1; i < s.componentAreas.length; i++) {
- const a = Number(s.componentAreas[i]);
- if (Number.isFinite(a) && a > 0) smallAreas.push(a);
+async function subtractHoleToolGroupsTogether(partHistory, toolGroups, booleanParam, featureID) {
+ const groups = (Array.isArray(toolGroups) ? toolGroups : [])
+ .map((group) => (Array.isArray(group) ? group.filter(Boolean) : []))
+ .filter((group) => group.length > 0);
+ const tools = groups.flat();
+ const rawTargets = Array.isArray(booleanParam?.targets) ? booleanParam.targets.filter(Boolean) : [];
+ if (!tools.length) return { added: [], removed: [] };
+ if (!rawTargets.length || !rawTargets.every((target) => hasOccShape(target)) || !tools.every((tool) => hasOccShape(tool))) {
+ return subtractHoleToolsSequentially(partHistory, tools, booleanParam, featureID);
+ }
+
+ const added = [];
+ const removed = [...rawTargets, ...tools];
+ for (const target of rawTargets) {
+ try {
+ let current = target;
+ for (const group of groups) {
+ const occState = subtractOccSolidTools(current, group);
+ if (!occState) throw new Error('OpenCASCADE multi-tool subtract returned no result.');
+ const IntermediateCtor = target?.constructor || BREP.Solid;
+ const intermediate = new IntermediateCtor();
+ setOccState(intermediate, occState);
+ intermediate._auxEdges = [
+ ...(Array.isArray(current?._auxEdges) ? current._auxEdges : []),
+ ...group.flatMap((tool) => (Array.isArray(tool?._auxEdges) ? tool._auxEdges : [])),
+ ];
+ try { intermediate.name = target?.name || current?.name || 'RESULT'; } catch { /* ignore */ }
+ try { if (target?.owningFeatureID) intermediate.owningFeatureID = target.owningFeatureID; } catch { /* ignore */ }
+ current = intermediate;
}
- }
- smallAreas.sort((a, b) => a - b);
- const suggested = smallAreas.length ? Math.max(areaThreshold, smallAreas[0] * 1.5) : areaThreshold;
- const useThreshold = Math.min(maxThreshold, suggested);
- const reassigned = Number(tool.cleanupTinyFaceIslands(useThreshold) || 0);
- totalReassigned += reassigned > 0 ? reassigned : 0;
- if (reassigned <= 0) {
- areaThreshold = Math.min(maxThreshold, areaThreshold * 4);
- if (areaThreshold >= maxThreshold) break;
- } else {
- areaThreshold = Math.min(maxThreshold, Math.max(areaThreshold * 1.25, useThreshold));
+ const SolidCtor = target?.constructor || BREP.Solid;
+ const result = new SolidCtor();
+ setOccState(result, current._occ);
+ result._auxEdges = [
+ ...(Array.isArray(current?._auxEdges) ? current._auxEdges : []),
+ ];
+ try { result.name = target?.name || (featureID ? `${featureID}_RESULT` : 'RESULT'); } catch { /* ignore */ }
+ try { if (target?.owningFeatureID) result.owningFeatureID = target.owningFeatureID; } catch { /* ignore */ }
+ added.push(result);
+ } catch (err) {
+ console.warn('[HoleFeature] Multi-tool subtract failed; falling back to sequential subtract:', err);
+ return subtractHoleToolsSequentially(partHistory, tools, booleanParam, featureID);
}
}
- const after = collectStats();
- return {
- before,
- after,
- passes,
- totalReassigned,
- finalAreaThreshold: areaThreshold,
- };
+
+ return { added, removed };
}
function getWorldPosition(obj) {
@@ -440,8 +400,8 @@ function collectSceneSolids(scene) {
obj.userData?.isSolid
|| obj.isSolid
|| obj.type === 'Solid'
+ || obj.type === 'SOLID'
|| obj.constructor?.name === 'Solid'
- || typeof obj._manifoldize === 'function'
|| typeof obj.union === 'function';
if (solidLike) solids.push(obj);
};
@@ -512,6 +472,92 @@ function collectSketchVerticesByName(scene, sketchName) {
return verts;
}
+function getSceneObjectByName(partHistory, name) {
+ if (!partHistory || !name) return null;
+ try {
+ if (typeof partHistory.getObjectByName === 'function') {
+ const obj = partHistory.getObjectByName(name);
+ if (obj) return obj;
+ }
+ } catch { /* ignore */ }
+ try {
+ return partHistory.scene?.getObjectByName?.(name) || null;
+ } catch {
+ return null;
+ }
+}
+
+function findSceneObjectByName(partHistory, name, predicate = null) {
+ const direct = getSceneObjectByName(partHistory, name);
+ if (direct && (!predicate || predicate(direct))) return direct;
+ const scene = partHistory?.scene;
+ if (!scene || typeof scene.traverse !== 'function') return null;
+ let found = null;
+ try {
+ scene.traverse((obj) => {
+ if (found || !obj || obj.name !== name) return;
+ if (!predicate || predicate(obj)) found = obj;
+ });
+ } catch { /* ignore */ }
+ return found;
+}
+
+function isSketchObject(obj) {
+ return String(obj?.type || '').toUpperCase() === 'SKETCH';
+}
+
+function parseSketchPointReference(value) {
+ const raw = typeof value === 'string'
+ ? value
+ : (typeof value?.name === 'string' ? value.name : '');
+ const text = raw.trim();
+ if (!text) return null;
+ const match = text.match(/^([^:|[\]>/]+):P(\d+)(?=$|[_:|[\]>/])/);
+ if (!match) return null;
+ const sketchName = match[1];
+ const pointName = `${sketchName}:P${match[2]}`;
+ return { sketchName, pointName };
+}
+
+function parseSketchReferenceName(value) {
+ const raw = typeof value === 'string'
+ ? value
+ : (typeof value?.name === 'string' ? value.name : '');
+ const text = raw.trim();
+ if (!text) return null;
+ const pointRef = parseSketchPointReference(text);
+ if (pointRef?.sketchName) return pointRef.sketchName;
+ const base = text.split(/[:|[\]>/]/, 1)[0];
+ return base || null;
+}
+
+function resolveSketchSelection(selectionRaw, partHistory) {
+ const requestedPointNames = new Set();
+ let sketch = null;
+
+ for (const item of selectionRaw) {
+ if (!item) continue;
+ const pointRef = parseSketchPointReference(item);
+ if (pointRef?.pointName) requestedPointNames.add(pointRef.pointName);
+
+ if (!sketch && isSketchObject(item)) sketch = item;
+ if (!sketch && isSketchObject(item?.parent)) sketch = item.parent;
+ if (!sketch && typeof item === 'string') {
+ const direct = findSceneObjectByName(partHistory, item, isSketchObject);
+ if (direct) sketch = direct;
+ }
+ if (!sketch) {
+ const sketchName = parseSketchReferenceName(item);
+ if (sketchName) {
+ const resolved = findSceneObjectByName(partHistory, sketchName, isSketchObject);
+ if (resolved) sketch = resolved;
+ }
+ }
+ }
+
+ return { sketch, requestedPointNames };
+}
+
function dedupePlacementsByPosition(placements, tolerance = 1e-7) {
const out = [];
if (!Array.isArray(placements) || placements.length === 0) return out;
@@ -782,7 +828,7 @@ export class HoleFeature {
const hide = (...keys) => { for (const key of keys) exclude.add(key); };
const hideCountersink = () => hide('countersinkDiameter', 'countersinkAngle');
const hideCounterbore = () => hide('counterboreDiameter', 'counterboreDepth');
- const hideThread = () => hide('threadStandard', 'threadDesignation', 'threadMode', 'threadRadialOffset', 'threadSegmentsPerTurn');
+ const hideThread = () => hide('threadStandard', 'threadDesignation', 'threadMode', 'threadRadialOffset');
if (t === 'THREADED') {
hideCountersink();
@@ -812,7 +858,7 @@ export class HoleFeature {
const params = this.inputParams || {};
const featureID = params.featureID || params.id || null;
const selectionRaw = Array.isArray(params.face) ? params.face.filter(Boolean) : (params.face ? [params.face] : []);
- const sketch = selectionRaw.find((o) => o && o.type === 'SKETCH') || null;
+ const { sketch, requestedPointNames } = resolveSketchSelection(selectionRaw, partHistory);
if (!sketch) throw new Error('HoleFeature requires a sketch selection; individual vertex picks are not supported.');
const pointObjs = [];
@@ -820,7 +866,11 @@ export class HoleFeature {
let pointPlacements = [];
// Use sketch-defined points as hole centers (construction points are excluded).
- const extraPts = collectSketchVertices(sketch);
+ const filterRequestedPoints = (items) => {
+ if (!(requestedPointNames instanceof Set) || requestedPointNames.size === 0) return items;
+ return items.filter((item) => requestedPointNames.has(item?.name || ''));
+ };
+ const extraPts = filterRequestedPoints(collectSketchVertices(sketch));
if (extraPts.length) {
pointObjs.push(...extraPts);
pointPlacements = pointObjs
@@ -828,7 +878,7 @@ export class HoleFeature {
.filter((entry) => !!entry.position);
}
if (!pointPlacements.length && partHistory?.scene && sketch?.name) {
- const fallbackPts = collectSketchVerticesByName(partHistory.scene, sketch.name);
+ const fallbackPts = filterRequestedPoints(collectSketchVerticesByName(partHistory.scene, sketch.name));
if (fallbackPts.length) {
pointObjs.push(...fallbackPts);
pointPlacements = pointObjs
@@ -839,7 +889,7 @@ export class HoleFeature {
const uniquePointPlacements = dedupePlacementsByPosition(pointPlacements);
if (pointPlacements.length > uniquePointPlacements.length) {
const skipped = pointPlacements.length - uniquePointPlacements.length;
- console.log('[HoleFeature] Skipping duplicate coincident hole points:', skipped);
+ holeDebugLog('[HoleFeature] Skipping duplicate coincident hole points:', skipped);
}
const hasPoints = uniquePointPlacements.length > 0;
@@ -862,7 +912,6 @@ export class HoleFeature {
const threadDesignation = String(params.threadDesignation || params.threadSize || '').trim();
const threadMode = String(params.threadMode || 'SYMBOLIC').toUpperCase();
const threadRadialOffset = Number(params.threadRadialOffset ?? 0);
- const threadSegmentsPerTurn = Math.max(4, Math.floor(Number(params.threadSegmentsPerTurn ?? (threadMode === 'MODELED' ? 32 : 12))));
let threadGeom = null;
if (threaded && threadStandard !== 'NONE' && threadDesignation) {
try {
@@ -935,24 +984,32 @@ export class HoleFeature {
}
}
const diag = primaryTarget ? boxDiagonalLength(primaryTarget) : boxDiagonalLength(chooseNearestSolid(sceneSolids, center));
- const straightDepth = throughAll ? Math.max(straightDepthInput, diag * 1.5 || 50) : straightDepthInput;
+ const straightDepth = throughAll ? Math.max(straightDepthInput, diag || 50) : straightDepthInput;
const res = 48;
- const backOffset = 1e-5; // small pullback to avoid coincident faces in booleans
+ const backOffset = 0;
const centers = hasPoints ? uniquePointPlacements.map((entry) => entry.position) : [center];
const sourceNames = hasPoints ? uniquePointPlacements.map((entry) => entry?.pointObj?.name || entry?.pointObj?.uuid || null) : [null];
const tools = [];
+ const coreTools = [];
+ const threadTools = [];
+ const otherTools = [];
const holeRecords = [];
- const debugVisualizationObjects = []; // Store debug viz objects separately
+ const debugToolSolids = [];
centers.forEach((c, idx) => {
const pointName = sourceNames[idx] || null;
const holeFacePrefix = pointName || (featureID ? `${featureID}_${idx}` : `HOLE_${idx}`);
- let modeledThreadFaceNames = null;
- let modeledThreadPitch = null;
+ const basePos = (c || center).clone();
+ const localThroughDepth = throughAll && primaryTarget
+ ? boxExitDistanceAlongDirection(primaryTarget, basePos, normal)
+ : 0;
+ const depthForHole = throughAll
+ ? Math.max(straightDepthInput, localThroughDepth || straightDepth)
+ : straightDepth;
const { solids: toolSolids, descriptors } = makeHoleTool({
holeType,
radius,
- straightDepthTotal: straightDepth,
+ straightDepthTotal: depthForHole,
sinkDia,
sinkAngle,
boreDia,
@@ -963,7 +1020,6 @@ export class HoleFeature {
});
// annotate faces with hole metadata before union so labels propagate
const descriptor = descriptors[0] || null;
- const basePos = (c || center).clone();
const originPos = basePos.clone().addScaledVector(normal, -backOffset);
if (descriptor) {
if (holeType === 'THREADED') descriptor.type = 'THREADED';
@@ -993,7 +1049,7 @@ export class HoleFeature {
threadStart = Math.max(0, threadStart - axialBlend);
threadLength = Math.max(0, threadLength + axialBlend);
if (threadLength > 0) {
- console.log('[HoleFeature] Generating thread:', {
+ holeDebugLog('[HoleFeature] Generating thread:', {
mode: threadMode,
length: threadLength,
threadStart,
@@ -1004,12 +1060,11 @@ export class HoleFeature {
pitch: threadGeom.pitch,
isExternal: threadGeom.isExternal,
radialOffset: threadRadialOffset,
- segmentsPerTurn: threadSegmentsPerTurn,
});
// Scale the thread geometry to millimeters if needed
let threadGeomScaled = threadGeom;
if (threadUnitScale !== 1) {
- console.log('[HoleFeature] Creating scaled thread geometry with scale factor', threadUnitScale);
+ holeDebugLog('[HoleFeature] Creating scaled thread geometry with scale factor', threadUnitScale);
// Create a new ThreadGeometry with scaled dimensions
threadGeomScaled = new ThreadGeometry({
standard: threadGeom.standard,
@@ -1020,8 +1075,8 @@ export class HoleFeature {
taperDirection: threadGeom.taperDirection,
});
}
- // Extend one full pitch past both start and end to avoid flats for modeled threads only
- const extraThreadLength = Math.max(0, threadGeomScaled.pitch || threadGeom.pitch || 0);
+ // Extend modeled cutters enough to avoid end flats without doubling boolean work.
+ const extraThreadLength = 0;
const threadStartEffective = threadMode === 'MODELED'
? threadStart - extraThreadLength
: threadStart;
@@ -1035,9 +1090,9 @@ export class HoleFeature {
mode: threadMode === 'MODELED' ? 'modeled' : 'symbolic',
radialOffset: threadRadialOffset,
symbolicRadius: 'crest',
- includeCore: false, // Core disabled - helical surface only for now
+ includeCore: false,
resolution: res,
- segmentsPerTurn: threadSegmentsPerTurn,
+ segmentsPerTurn: params.threadSegmentsPerTurn,
name: `${holeFacePrefix}_THREAD`,
faceName: threadCutFaceName,
axis: [0, 1, 0],
@@ -1047,37 +1102,24 @@ export class HoleFeature {
const canonicalCoreFaceName = `${holeFacePrefix}_THREAD_CORE`;
if (threadMode === 'MODELED') {
try {
- modeledThreadPitch = Number(threadGeomScaled?.pitch || threadGeom?.pitch || 0);
- modeledThreadFaceNames = [
- `${threadCutFaceName}:FLANK_A`,
- `${threadCutFaceName}:ROOT`,
- `${threadCutFaceName}:FLANK_B`,
- canonicalCoreFaceName,
- ];
- const mergeIntoCore = [
- `${threadCutFaceName}:CREST`,
- `${threadCutFaceName}:CAP_START`,
- `${threadCutFaceName}:CAP_END`,
- ];
- for (const fromName of mergeIntoCore) {
- threadSolid.renameFace(fromName, canonicalCoreFaceName);
- }
if (descriptor) {
- threadSolid.setFaceMetadata(canonicalCoreFaceName, { hole: { ...descriptor } });
+ const threadFaceNames = typeof threadSolid.getFaceNames === 'function' ? threadSolid.getFaceNames() : [];
+ for (const faceName of threadFaceNames) {
+ if (String(faceName || '').startsWith(threadCutFaceName)) {
+ threadSolid.setFaceMetadata(faceName, { hole: { ...descriptor } });
+ }
+ }
}
} catch {
/* best-effort */
}
}
- console.log('[HoleFeature] Thread solid created:', {
+ holeDebugLog('[HoleFeature] Thread solid created:', {
type: threadSolid?.constructor?.name,
- hasGeometry: !!threadSolid?.geometry,
- vertexCount: threadSolid?.geometry?.attributes?.position?.count,
- triangleCount: threadSolid?.triangles?.length,
faceCount: threadSolid?.faces?.size,
});
- // Add a core solid at the minor diameter so the threaded hole removes material fully.
+ // Symbolic threads still need a separate minor-diameter core cutter.
const minorRadiusAt = (z) => {
try {
const d = typeof threadGeomScaled.diametersAtZ === 'function'
@@ -1093,11 +1135,9 @@ export class HoleFeature {
const coreR0 = minorRadiusAt(0);
const coreR1 = minorRadiusAt(threadLengthEffective);
const coreName = `${holeFacePrefix}_THREAD_CORE`;
- const coreResolution = threadMode === 'MODELED'
- ? Math.max(8, Math.min(res, threadSegmentsPerTurn * 2))
- : res;
+ const coreResolution = res;
const coreHeight = threadLengthEffective;
- if (coreHeight > 0) {
+ if (coreHeight > 0 && threadMode !== 'MODELED') {
const coreSolid = threadGeomScaled.isTapered && Math.abs(coreR0 - coreR1) > 1e-6
? new BREP.Cone({
r1: coreR0,
@@ -1120,6 +1160,7 @@ export class HoleFeature {
if (descriptor) {
try { coreSolid.setFaceMetadata(`${coreName}_S`, { hole: { ...descriptor } }); } catch { /* best-effort */ }
}
+ coreSolid.userData = { ...(coreSolid.userData || {}), holeToolRole: 'core' };
if (threadMode === 'MODELED') {
try {
const coreFaces = typeof coreSolid.getFaceNames === 'function' ? coreSolid.getFaceNames() : [];
@@ -1137,92 +1178,9 @@ export class HoleFeature {
toolSolids.push(coreSolid);
}
+ threadSolid.userData = { ...(threadSolid.userData || {}), holeToolRole: threadMode === 'MODELED' ? 'thread' : 'core' };
toolSolids.push(threadSolid);
- if (debugShowSolid) {
- try {
- console.log('[HoleFeature] Creating profile cross-section visualization using primitives...');
-
- // Get the profile points from the thread geometry
- const crestR = threadGeomScaled.crestRadius;
- const rootR = threadGeomScaled.rootRadius;
- const pitch = threadGeomScaled.pitch;
- const halfPitch = pitch / 2;
-
- console.log('[HoleFeature] Profile dimensions:', {
- crestR,
- rootR,
- pitch,
- halfPitch,
- depth: rootR - crestR,
- });
-
- // Create small spheres at each corner of the profile to visualize it
- const markerSize = Math.max(0.5, pitch * 0.2);
- const vizCenterZ = threadLengthEffective + 5; // Place it above the thread
-
- // Profile corners in [R, Z] cylindrical coords (R=radial, Z=axial position in thread)
- // We need to display this as a cross-section shape oriented perpendicular to hole axis
- // Map: axial variation (Z in profile) -> X, radial distance (R) -> Y, depth -> Z
- const profileCorners = [
- [-halfPitch, crestR, vizCenterZ], // X=axial, Y=radial, Z=depth
- [-halfPitch * 0.3, rootR, vizCenterZ],
- [halfPitch * 0.3, rootR, vizCenterZ],
- [halfPitch, crestR, vizCenterZ],
- ];
-
- // Create a marker at each corner - store separately for debug viz
- for (let i = 0; i < profileCorners.length; i++) {
- const corner = profileCorners[i];
- const marker = new BREP.Sphere({
- radius: markerSize,
- resolution: 16,
- name: `PROFILE_MARKER_${featureID}_${idx}_${i}`,
- });
- marker.bakeTRS({
- position: corner,
- rotationEuler: [0, 0, 0],
- scale: [1, 1, 1],
- });
- debugVisualizationObjects.push(marker);
- console.log(`[HoleFeature] Added profile marker ${i} at`, corner);
- }
-
- // Also create connecting cylinders to show the edges
- for (let i = 0; i < profileCorners.length; i++) {
- const p1 = profileCorners[i];
- const p2 = profileCorners[(i + 1) % profileCorners.length];
- const dx = p2[0] - p1[0];
- const dy = p2[1] - p1[1];
- const dz = p2[2] - p1[2];
- const length = Math.sqrt(dx * dx + dy * dy + dz * dz);
-
- if (length > 0.01) {
- const edge = new BREP.Cylinder({
- radius: markerSize * 0.3,
- height: length,
- resolution: 12,
- name: `PROFILE_EDGE_${featureID}_${idx}_${i}`,
- });
-
- // Position and orient the cylinder to connect the points
- const midpoint = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2, (p1[2] + p2[2]) / 2];
- const angleY = Math.atan2(dx, dy);
- const angleX = Math.atan2(Math.sqrt(dx * dx + dz * dz), dy);
-
- edge.bakeTRS({
- position: midpoint,
- rotationEuler: [angleX * 180 / Math.PI, 0, angleY * 180 / Math.PI],
- scale: [1, 1, 1],
- });
- debugVisualizationObjects.push(edge);
- }
- }
-
- } catch (err) {
- console.warn('[HoleFeature] Profile visualization creation failed:', err);
- }
- }
if (descriptor) {
descriptor.thread = {
standard: threadStandard,
@@ -1244,67 +1202,68 @@ export class HoleFeature {
holeRecords.push({ ...descriptor });
}
- console.log('[HoleFeature] Unioning', toolSolids.length, 'solids for hole tool');
- console.log('[HoleFeature] Unioning', toolSolids.length, 'solids for hole tool');
- const tool = unionSolids(toolSolids);
- if (!tool) return;
- if (threadMode === 'MODELED' && modeledThreadFaceNames?.length) {
- try {
- const cleanupSummary = cleanupModeledThreadFaceIslands(tool, modeledThreadFaceNames, modeledThreadPitch);
- if (cleanupSummary) {
- console.log('[HoleFeature] Modeled thread face cleanup summary:', cleanupSummary);
- }
- } catch (cleanupErr) {
- console.warn('[HoleFeature] Modeled thread face island cleanup failed:', cleanupErr);
- }
- }
- if (debugShowSolid) {
- try {
- tool.visualize();
- } catch (err) {
- console.warn('[HoleFeature] Debug visualize failed:', err);
- }
- }
+ holeDebugLog('[HoleFeature] Preparing', toolSolids.length, 'tool solids for hole');
const basis = buildBasisFromNormal(normal);
basis.setPosition(originPos);
- try { tool.bakeTransform(basis); }
- catch (error) { console.warn('[HoleFeature] Failed to transform tool:', error); }
+ for (const solid of toolSolids) {
+ try { solid.bakeTransform(basis); }
+ catch (error) { console.warn('[HoleFeature] Failed to transform tool solid:', error); }
+ }
// add centerline for PMI/visualization
const totalDepth = descriptor?.totalDepth || straightDepth || 1;
const start = originPos;
const end = start.clone().add(normal.clone().multiplyScalar(totalDepth));
try {
- tool.addCenterline([start.x, start.y, start.z], [end.x, end.y, end.z], featureID ? `${featureID}_AXIS_${idx}` : `HOLE_AXIS_${idx}`, { materialKey: 'OVERLAY' });
+ toolSolids[0]?.addCenterline?.([start.x, start.y, start.z], [end.x, end.y, end.z], featureID ? `${featureID}_AXIS_${idx}` : `HOLE_AXIS_${idx}`, { materialKey: 'OVERLAY' });
} catch { /* best-effort */ }
- tools.push(tool);
- });
-
- if (!tools.length) throw new Error('HoleFeature could not build cutting tool geometry.');
- const combinedTool = tools.length === 1 ? tools[0] : unionSolids(tools);
-
- const effects = await BREP.applyBooleanOperation(partHistory || {}, combinedTool, booleanParam, featureID);
- try { this.persistentData.holes = holeRecords; } catch { }
-
- // Add debug visualization objects to the effects if they exist
- if (debugShowSolid && debugVisualizationObjects.length > 0) {
- console.log('[HoleFeature] Adding', debugVisualizationObjects.length, 'debug visualization objects to scene');
- if (!effects.additions) effects.additions = [];
-
- // Convert each solid to a mesh and add to additions
- for (const vizObj of debugVisualizationObjects) {
+ if (debugShowSolid) {
try {
- // Convert to mesh and add type/name metadata
- const mesh = vizObj.toMesh ? vizObj.toMesh() : vizObj;
- mesh.type = 'DEBUG_VIZ';
- mesh.name = vizObj.name || 'DEBUG_VIZ';
- mesh.userData = mesh.userData || {};
- mesh.userData.isDebugVisualization = true;
- effects.additions.push(mesh);
- console.log('[HoleFeature] Added debug viz:', mesh.name, 'to scene');
+ const tool = unionSolids(toolSolids);
+ if (!tool) return;
+ const debugTool = typeof tool.clone === 'function' ? tool.clone() : tool;
+ debugTool.name = `${holeFacePrefix}_DEBUG_TOOL`;
+ debugTool.userData = {
+ ...(debugTool.userData || {}),
+ isDebugVisualization: true,
+ isHoleDebugTool: true,
+ sourceHoleName: holeFacePrefix,
+ };
+ debugToolSolids.push(debugTool);
} catch (err) {
- console.warn('[HoleFeature] Failed to convert debug viz to mesh:', err);
+ console.warn('[HoleFeature] Failed to create debug tool solid:', err);
}
}
+ for (const solid of toolSolids.filter(Boolean)) {
+ tools.push(solid);
+ const role = String(solid?.userData?.holeToolRole || '').toLowerCase();
+ if (role === 'core') coreTools.push(solid);
+ else if (role === 'thread') threadTools.push(solid);
+ else otherTools.push(solid);
+ }
+ });
+
+ if (!tools.length) throw new Error('HoleFeature could not build cutting tool geometry.');
+ const useSequentialSubtract = threadTools.length > 0;
+ const subtractTools = useSequentialSubtract
+ ? [...otherTools, ...threadTools, ...coreTools]
+ : tools;
+ const effects = String(booleanParam?.operation || '').toUpperCase() === 'SUBTRACT'
+ ? (useSequentialSubtract
+ ? await subtractHoleToolsSequentially(partHistory || {}, subtractTools, booleanParam, featureID)
+ : await subtractHoleToolGroupsTogether(partHistory || {}, [otherTools, coreTools, threadTools], booleanParam, featureID))
+ : await BREP.applyBooleanOperation(
+ partHistory || {},
+ tools.length === 1 ? tools[0] : unionSolids(tools),
+ booleanParam,
+ featureID,
+ );
+ try { this.persistentData.holes = holeRecords; } catch { }
+
+ if (debugShowSolid && debugToolSolids.length > 0) {
+ effects.added = [
+ ...(Array.isArray(effects.added) ? effects.added : []),
+ ...debugToolSolids,
+ ];
}
return effects;
diff --git a/src/features/loft/LoftFeature.js b/src/features/loft/LoftFeature.js
index 583175a6..e22f04af 100644
--- a/src/features/loft/LoftFeature.js
+++ b/src/features/loft/LoftFeature.js
@@ -1,4 +1,5 @@
import { BREP } from "../../BREP/BREP.js";
+import { makeLoft, setOccState } from "../../BREP/OpenCascadeKernel.js";
import { selectionHasSketch } from "../selectionUtils.js";
const THREE = BREP.THREE;
@@ -474,7 +475,13 @@ export class LoftFeature {
const pts = seg.pts || []; if (pts.length===0) return [0,0,0];
const a = pts[0], b = pts[pts.length-1]; return [(a[0]+b[0])/2,(a[1]+b[1])/2,(a[2]+b[2])/2];
});
- const sumDist = (A,B)=>{ let s=0; for(let i=0;i{
+ const n = Math.min(Array.isArray(A) ? A.length : 0, Array.isArray(B) ? B.length : 0);
+ if (n <= 0) return +Infinity;
+ let s=0;
+ for(let i=0;i{ const n=ring.length; const k=((off%n)+n)%n; return ring.slice(k).concat(ring.slice(0,k)); };
const bestAlignRings = (ringA, ringB) => {
// Try both orientations; choose rotation minimizing mid-point distance
@@ -515,6 +522,68 @@ export class LoftFeature {
alignedRings[j] = ring;
}
+ try {
+ const sections = loopsAligned
+ .map((faceLoops) => faceLoops?.[0]?.pts || [])
+ .map((pts) => toOpen(pts))
+ .filter((pts) => Array.isArray(pts) && pts.length >= 3);
+ const sectionWireInputs = faces.map((face, index) => {
+ const rawLoops = (loopsByFace[index] || [])
+ .map((loop) => ({ isHole: !!loop?.isHole, pts: toOpen(loop?.pts || []) }))
+ .filter((loop) => Array.isArray(loop.pts) && loop.pts.length >= 3);
+ const normal = (() => {
+ try {
+ const n = typeof face?.getAverageNormal === 'function' ? face.getAverageNormal() : null;
+ if (n && Number.isFinite(n.x) && Number.isFinite(n.y) && Number.isFinite(n.z) && n.lengthSq() > 1e-20) {
+ return [n.x, n.y, n.z];
+ }
+ } catch {}
+ return [0, 0, 1];
+ })();
+ const edgeInputs = (Array.isArray(face?.edges) ? face.edges : [])
+ .map((edge) => {
+ const ud = edge?.userData || {};
+ return {
+ name: edge?.name || null,
+ curveType: ud.sketchGeomType || ud.curveType || null,
+ sketchGeomType: ud.sketchGeomType || null,
+ polyline: collectEdgePolylineWorld(edge),
+ circleCenter: Array.isArray(ud.circleCenter) ? ud.circleCenter.slice() : null,
+ circleRadius: Number.isFinite(Number(ud.circleRadius)) ? Number(ud.circleRadius) : null,
+ arcCenter: Array.isArray(ud.arcCenter) ? ud.arcCenter.slice() : null,
+ arcRadius: Number.isFinite(Number(ud.arcRadius)) ? Number(ud.arcRadius) : null,
+ };
+ });
+ return { loops: rawLoops, edgeInputs, normal };
+ });
+ const sideNames = refRing.map((edge, index) => `${edge?.name || `EDGE_${index}`}_LF`);
+ if (sections.length >= 2 && sideNames.length >= 3) {
+ const occState = makeLoft({
+ sections,
+ sectionWireInputs,
+ sideNames,
+ startName: `${faces[0].name || 'Face'}_START`,
+ endName: `${faces[faces.length - 1].name || 'Face'}_END`,
+ featureID: this.inputParams.featureID || this.inputParams.id || 'LOFT',
+ });
+ const occLoft = new BREP.Solid();
+ occLoft.name = this.inputParams.featureID || this.inputParams.id || 'Loft';
+ setOccState(occLoft, occState);
+ occLoft.__loftMethod = 'occ_thru_sections';
+ occLoft.visualize();
+ const effects = await BREP.applyBooleanOperation(partHistory || {}, occLoft, this.inputParams.boolean, this.inputParams.featureID);
+ const booleanRemoved = Array.isArray(effects.removed) ? effects.removed : [];
+ const removedArtifacts = [...removed, ...booleanRemoved];
+ try { for (const obj of removedArtifacts) { if (obj) obj.__removeFlag = true; } } catch {}
+ return {
+ added: Array.isArray(effects.added) ? effects.added : [],
+ removed: removedArtifacts,
+ };
+ }
+ } catch (err) {
+ console.warn('[LoftFeature] OpenCASCADE loft failed; falling back to triangulated loft:', err?.message || err);
+ }
+
for (let i = 0; i < faces.length - 1; i++) {
// Use rings aligned to the first face for consistent naming
const ringA = alignedRings[i];
diff --git a/src/features/mirror/MirrorFeature.js b/src/features/mirror/MirrorFeature.js
index 0bf599c3..884d5058 100644
--- a/src/features/mirror/MirrorFeature.js
+++ b/src/features/mirror/MirrorFeature.js
@@ -77,18 +77,12 @@ export class MirrorFeature {
const mirrored = src.mirrorAcrossPlane(plane.point, plane.normal);
// mutate face names so they are distinct for this feature
try {
- const idToFaceName = mirrored._idToFaceName instanceof Map ? mirrored._idToFaceName : new Map();
- const mutatedIdToFace = new Map();
- const mutatedFaceToId = new Map();
- for (const [fid, fname] of idToFaceName.entries()) {
+ const faceNames = typeof mirrored.getFaceNames === 'function' ? mirrored.getFaceNames() : [];
+ for (const fname of faceNames) {
const base = String(fname ?? 'Face');
const feat = String(featureID ?? 'MIRROR');
- const newName = `${base}::${feat}`;
- mutatedIdToFace.set(fid, newName);
- mutatedFaceToId.set(newName, fid);
+ mirrored.renameFace(base, `${base}::${feat}`);
}
- mirrored._idToFaceName = mutatedIdToFace;
- mirrored._faceNameToID = mutatedFaceToId;
} catch (_) { }
mirrored.name = `${featureID}:${src.name}:M`;
// Build face/edge meshes for interaction/visibility
diff --git a/src/features/overlapCleanup/OverlapCleanupFeature.js b/src/features/overlapCleanup/OverlapCleanupFeature.js
deleted file mode 100644
index ca24f9a6..00000000
--- a/src/features/overlapCleanup/OverlapCleanupFeature.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import { BREP } from "../../BREP/BREP.js";
-
-const THREE = BREP.THREE;
-
-const inputParamsSchema = {
- id: {
- type: "string",
- default_value: null,
- hint: "unique identifier for the overlap cleanup feature",
- },
- targetSolid: {
- type: "reference_selection",
- selectionFilter: ["SOLID"],
- multiple: false,
- default_value: null,
- hint: "Select a solid to clean up by overlap intersection",
- },
- distance: {
- type: "number",
- default_value: 0.0001,
- step: 0.0001,
- hint: "Translation distance for each axis-shifted copy",
- },
-};
-
-export class OverlapCleanupFeature {
- static shortName = "OVL";
- static longName = "Overlap Cleanup";
- static inputParamsSchema = inputParamsSchema;
-
- constructor() {
- this.inputParams = {};
- this.persistentData = {};
- }
-
- async run(partHistory) {
- const scene = partHistory?.scene;
- const targetEntry = Array.isArray(this.inputParams.targetSolid)
- ? (this.inputParams.targetSolid[0] || null)
- : (this.inputParams.targetSolid || null);
- const target = (targetEntry && typeof targetEntry === "object")
- ? targetEntry
- : (targetEntry ? await scene?.getObjectByName(String(targetEntry)) : null);
-
- if (!target || target.type !== "SOLID") return { added: [], removed: [] };
-
- const distanceRaw = Number(this.inputParams.distance);
- const distance = Number.isFinite(distanceRaw) ? distanceRaw : 0.0001;
- const featureID = this.inputParams.featureID || this.inputParams.id || null;
-
- const base = target.clone();
- const copies = [];
- const shifts = [
- [distance, 0, 0],
- [0, distance, 0],
- [0, 0, distance],
- ];
-
- for (const [dx, dy, dz] of shifts) {
- const copy = target.clone();
- const t = new THREE.Matrix4().makeTranslation(dx, dy, dz);
- copy.bakeTransform(t);
- copies.push(copy);
- }
-
- const effects = await BREP.applyBooleanOperation(
- partHistory || {},
- base,
- { operation: "INTERSECT", targets: copies },
- featureID,
- );
-
- const added = Array.isArray(effects.added) ? effects.added : [];
- if (added.length > 0) {
- const result = added[0];
- const baseName = featureID || target.name || "Solid";
- try { result.name = `${baseName}_Overlap`; } catch (_) {}
- }
-
- const removed = Array.isArray(effects.removed) ? effects.removed.slice() : [];
- removed.push(target);
- try { for (const obj of removed) { if (obj) obj.__removeFlag = true; } } catch {}
- return { added, removed };
- }
-}
diff --git a/src/features/pattern/PatternFeature.js b/src/features/pattern/PatternFeature.js
index 704bb63b..225da665 100644
--- a/src/features/pattern/PatternFeature.js
+++ b/src/features/pattern/PatternFeature.js
@@ -1,5 +1,4 @@
import { BREP } from "../../BREP/BREP.js";
-import { Manifold } from "../../BREP/SolidShared.js";
const THREE = BREP.THREE;
const inputParamsSchema = {
@@ -239,37 +238,7 @@ function computeRefPlane(refObj) {
// across patterned instances when booleaned together.
function retagSolidFaces(solid, suffix) {
if (!solid || !suffix) return;
- try {
- const oldIdToFace = (solid._idToFaceName instanceof Map) ? solid._idToFaceName : new Map();
- const triIDs = Array.isArray(solid._triIDs) ? solid._triIDs : [];
- const presentIDs = new Set();
- for (const k of oldIdToFace.keys()) presentIDs.add(k);
- if (presentIDs.size === 0 && triIDs.length) { for (const id of triIDs) presentIDs.add(id >>> 0); }
-
- const idRemap = new Map();
- const newIdToFace = new Map();
- const newFaceToId = new Map();
- for (const oldID of presentIDs) {
- const fname = oldIdToFace.get(oldID);
- const base = (fname != null) ? String(fname) : `FACE_${oldID}`;
- const tagged = `${base}::${suffix}`;
- const newID = Manifold.reserveIDs(1);
- idRemap.set(oldID, newID);
- newIdToFace.set(newID, tagged);
- newFaceToId.set(tagged, newID);
- }
-
- if (triIDs.length && idRemap.size) {
- for (let i = 0; i < triIDs.length; i++) {
- const oldID = triIDs[i] >>> 0;
- const mapped = idRemap.get(oldID);
- if (mapped !== undefined) triIDs[i] = mapped;
- }
- solid._triIDs = triIDs;
- solid._dirty = true;
- }
-
- solid._idToFaceName = newIdToFace;
- solid._faceNameToID = newFaceToId;
- } catch (_) { /* best-effort */ }
+ if (typeof solid.renameFace !== 'function' || typeof solid.getFaceNames !== 'function') return;
+ const names = solid.getFaceNames();
+ for (const name of names) solid.renameFace(name, `${String(name ?? 'Face')}::${suffix}`);
}
diff --git a/src/features/patternLinear/PatternLinearFeature.js b/src/features/patternLinear/PatternLinearFeature.js
index d771e801..cde83a31 100644
--- a/src/features/patternLinear/PatternLinearFeature.js
+++ b/src/features/patternLinear/PatternLinearFeature.js
@@ -1,5 +1,4 @@
import { BREP } from "../../BREP/BREP.js";
-import { Manifold } from "../../BREP/SolidShared.js";
const THREE = BREP.THREE;
const inputParamsSchema = {
@@ -90,40 +89,8 @@ function toVec3(v, dx, dy, dz) {
function retagSolidFaces(solid, suffix) {
if (!solid || !suffix) return;
- try {
- const oldIdToFace = (solid._idToFaceName instanceof Map) ? solid._idToFaceName : new Map();
- const triIDs = Array.isArray(solid._triIDs) ? solid._triIDs : [];
- // Build set of IDs present (prefer map keys, fall back to triIDs)
- const presentIDs = new Set();
- for (const k of oldIdToFace.keys()) presentIDs.add(k);
- if (presentIDs.size === 0 && triIDs.length) { for (const id of triIDs) presentIDs.add(id >>> 0); }
-
- // Map oldID -> newID; and build new maps with tagged names
- const idRemap = new Map();
- const newIdToFace = new Map();
- const newFaceToId = new Map();
- for (const oldID of presentIDs) {
- const fname = oldIdToFace.get(oldID);
- const base = (fname != null) ? String(fname) : `FACE_${oldID}`;
- const tagged = `${base}::${suffix}`;
- const newID = Manifold.reserveIDs(1);
- idRemap.set(oldID, newID);
- newIdToFace.set(newID, tagged);
- newFaceToId.set(tagged, newID);
- }
-
- // Remap per-triangle IDs
- if (triIDs.length && idRemap.size) {
- for (let i = 0; i < triIDs.length; i++) {
- const oldID = triIDs[i] >>> 0;
- const mapped = idRemap.get(oldID);
- if (mapped !== undefined) triIDs[i] = mapped;
- }
- solid._triIDs = triIDs;
- solid._dirty = true; // force rebuild so MeshGL.faceID updates
- }
-
- solid._idToFaceName = newIdToFace;
- solid._faceNameToID = newFaceToId;
- } catch (_) { /* best-effort */ }
+ if (typeof solid.renameFace === 'function' && typeof solid.getFaceNames === 'function') {
+ const names = solid.getFaceNames();
+ for (const name of names) solid.renameFace(name, `${String(name ?? 'Face')}::${suffix}`);
+ }
}
diff --git a/src/features/patternRadial/PatternRadialFeature.js b/src/features/patternRadial/PatternRadialFeature.js
index 2c3c5ab7..3fa73013 100644
--- a/src/features/patternRadial/PatternRadialFeature.js
+++ b/src/features/patternRadial/PatternRadialFeature.js
@@ -1,5 +1,4 @@
import { BREP } from "../../BREP/BREP.js";
-import { Manifold } from "../../BREP/SolidShared.js";
const THREE = BREP.THREE;
const inputParamsSchema = {
@@ -163,37 +162,7 @@ function computeAxisFromEdge(edgeObj) {
function retagSolidFaces(solid, suffix) {
if (!solid || !suffix) return;
- try {
- const oldIdToFace = (solid._idToFaceName instanceof Map) ? solid._idToFaceName : new Map();
- const triIDs = Array.isArray(solid._triIDs) ? solid._triIDs : [];
- const presentIDs = new Set();
- for (const k of oldIdToFace.keys()) presentIDs.add(k);
- if (presentIDs.size === 0 && triIDs.length) { for (const id of triIDs) presentIDs.add(id >>> 0); }
-
- const idRemap = new Map();
- const newIdToFace = new Map();
- const newFaceToId = new Map();
- for (const oldID of presentIDs) {
- const fname = oldIdToFace.get(oldID);
- const base = (fname != null) ? String(fname) : `FACE_${oldID}`;
- const tagged = `${base}::${suffix}`;
- const newID = Manifold.reserveIDs(1);
- idRemap.set(oldID, newID);
- newIdToFace.set(newID, tagged);
- newFaceToId.set(tagged, newID);
- }
-
- if (triIDs.length && idRemap.size) {
- for (let i = 0; i < triIDs.length; i++) {
- const oldID = triIDs[i] >>> 0;
- const mapped = idRemap.get(oldID);
- if (mapped !== undefined) triIDs[i] = mapped;
- }
- solid._triIDs = triIDs;
- solid._dirty = true;
- }
-
- solid._idToFaceName = newIdToFace;
- solid._faceNameToID = newFaceToId;
- } catch (_) { /* best-effort */ }
+ if (typeof solid.renameFace !== 'function' || typeof solid.getFaceNames !== 'function') return;
+ const names = solid.getFaceNames();
+ for (const name of names) solid.renameFace(name, `${String(name ?? 'Face')}::${suffix}`);
}
diff --git a/src/features/pushFace/PushFaceFeature.js b/src/features/pushFace/PushFaceFeature.js
deleted file mode 100644
index 6bce89d7..00000000
--- a/src/features/pushFace/PushFaceFeature.js
+++ /dev/null
@@ -1,144 +0,0 @@
-import { getSolidGeometryCounts } from "../edgeFeatureUtils.js";
-import { resolveSelectionObject } from "../selectionUtils.js";
-
-const inputParamsSchema = {
- id: {
- type: "string",
- default_value: null,
- hint: "Optional identifier for the push face feature",
- },
- faces: {
- type: "reference_selection",
- selectionFilter: ["FACE"],
- multiple: true,
- default_value: [],
- hint: "Select one or more faces on a single solid to push",
- },
- distance: {
- type: "number",
- default_value: 1,
- hint: "Signed distance to push the selected faces along their normals",
- },
-};
-
-function getFaceName(entry) {
- const raw = entry?.userData?.faceName ?? entry?.name ?? null;
- if (raw == null) return null;
- const name = String(raw).trim();
- return name || null;
-}
-
-function getParentSolid(entry) {
- const direct = entry?.parentSolid || null;
- if (direct?.type === "SOLID") return direct;
- const parent = entry?.parent || null;
- return parent?.type === "SOLID" ? parent : null;
-}
-
-function collectSelectedFaces(faceEntries, partHistory) {
- const faces = [];
- const seen = new Set();
- for (const rawEntry of (Array.isArray(faceEntries) ? faceEntries : [])) {
- const entry = resolveSelectionObject(rawEntry, partHistory);
- if (!entry || String(entry?.type || "").toUpperCase() !== "FACE") continue;
- const solid = getParentSolid(entry);
- const faceName = getFaceName(entry);
- if (!solid || !faceName) continue;
- const solidKey = solid?.uuid || solid?.id || solid?.name || "";
- const key = `${solidKey}::${faceName}`;
- if (seen.has(key)) continue;
- seen.add(key);
- faces.push({ entry, solid, faceName });
- }
- return faces;
-}
-
-export class PushFaceFeature {
- static shortName = "PF";
- static longName = "Push Face";
- static inputParamsSchema = inputParamsSchema;
- static showContexButton(selectedItems) {
- const faces = (Array.isArray(selectedItems) ? selectedItems : [])
- .filter((item) => String(item?.type || "").toUpperCase() === "FACE")
- .filter((item) => !!getParentSolid(item) && !!getFaceName(item));
- if (!faces.length) return false;
- return { params: { faces } };
- }
-
- constructor() {
- this.inputParams = {};
- this.persistentData = {};
- }
-
- async run(partHistory) {
- const selections = collectSelectedFaces(this.inputParams.faces, partHistory);
- if (!selections.length) {
- console.warn("[PushFaceFeature] No valid faces selected.");
- return { added: [], removed: [] };
- }
-
- const solids = new Set(selections.map((selection) => selection.solid).filter(Boolean));
- if (!solids.size) {
- console.warn("[PushFaceFeature] Selected faces are not attached to a solid.");
- return { added: [], removed: [] };
- }
- if (solids.size > 1) {
- console.warn("[PushFaceFeature] Faces from multiple solids selected; aborting push face.");
- return { added: [], removed: [] };
- }
-
- const distance = Number(this.inputParams.distance);
- if (!Number.isFinite(distance) || distance === 0) {
- console.warn("[PushFaceFeature] Distance must be a non-zero finite number.");
- return { added: [], removed: [] };
- }
-
- const targetSolid = solids.values().next().value;
- const faceNames = [...new Set(selections.map((selection) => selection.faceName).filter(Boolean))];
- if (!faceNames.length) {
- console.warn("[PushFaceFeature] No face names resolved from selection.");
- return { added: [], removed: [] };
- }
-
- let result = null;
- try {
- result = targetSolid.clone();
- for (const faceName of faceNames) {
- result.pushFace(faceName, distance);
- }
- } catch (error) {
- console.error("[PushFaceFeature] Failed to push selected faces.", error);
- return { added: [], removed: [] };
- }
-
- const { triCount, vertCount } = getSolidGeometryCounts(result);
- if (!result || triCount === 0 || vertCount === 0) {
- console.error("[PushFaceFeature] pushFace produced an empty result; skipping scene replacement.", {
- featureID: this.inputParams.featureID || this.inputParams.id || null,
- triangleCount: triCount,
- vertexCount: vertCount,
- faceNames,
- });
- return { added: [], removed: [] };
- }
-
- try { result.name = targetSolid.name; } catch { /* ignore */ }
- try {
- const featureID = this.inputParams.featureID || this.inputParams.id || null;
- if (featureID) result.owningFeatureID = featureID;
- } catch { /* ignore */ }
- try {
- result.userData = {
- ...(result.userData || {}),
- pushFace: {
- faceNames: faceNames.slice(),
- distance,
- sourceSolid: targetSolid.name || null,
- },
- };
- } catch { /* ignore */ }
- try { result.visualize(); } catch { /* ignore */ }
- try { targetSolid.__removeFlag = true; } catch { /* ignore */ }
- return { added: [result], removed: [targetSolid] };
- }
-}
diff --git a/src/features/remesh/RemeshFeature.js b/src/features/remesh/RemeshFeature.js
deleted file mode 100644
index 546ff1d7..00000000
--- a/src/features/remesh/RemeshFeature.js
+++ /dev/null
@@ -1,109 +0,0 @@
-
-
-const inputParamsSchema = {
- id: {
- type: "string",
- default_value: null,
- hint: "unique identifier for the remesh feature",
- },
- targetSolid: {
- type: "reference_selection",
- selectionFilter: ["SOLID"],
- multiple: false,
- default_value: null,
- hint: "Select a solid to remesh (clone is created)",
- },
- mode: {
- type: "options",
- options: ["Increase resolution", "Simplify"],
- default_value: "Increase resolution",
- hint: "Choose remeshing mode",
- },
- maxEdgeLength: {
- type: "number",
- step: 0.1,
- default_value: 1,
- hint: "Split edges longer than this length",
- },
- maxIterations: {
- type: "number",
- step: 1,
- default_value: 10,
- hint: "Maximum refinement passes",
- },
- tolerance: {
- type: "number",
- step: 0.1,
- default_value: 0.1,
- hint: "Simplify tolerance and pre-weld epsilon (used in Simplify mode)",
- },
-};
-
-export class RemeshFeature {
- static shortName = "RM";
- static longName = "Remesh";
- static inputParamsSchema = inputParamsSchema;
-
- constructor() {
- this.inputParams = {};
- this.persistentData = {};
- }
-
- async run(partHistory) {
- const scene = partHistory.scene;
-
- // Resolve target solid
- const targetEntry = Array.isArray(this.inputParams.targetSolid)
- ? (this.inputParams.targetSolid[0] || null)
- : (this.inputParams.targetSolid || null);
- const target = (targetEntry && typeof targetEntry === 'object')
- ? targetEntry
- : (targetEntry ? await scene.getObjectByName(String(targetEntry)) : null);
-
- if (!target || target.type !== 'SOLID') return { added: [], removed: [] };
-
- const modeRaw = this.inputParams.mode || 'Increase resolution';
- const mode = String(modeRaw).toLowerCase();
-
- // Clone target to preserve original
- const outSolid = target.clone();
-
- if (mode === 'simplify') {
- const T = Number(this.inputParams.tolerance);
- const tol = Number.isFinite(T) && T >= 0 ? T : undefined;
- if (Number.isFinite(tol) && tol > 0) {
- try {
- if (typeof outSolid._weldVerticesByEpsilon === 'function') {
- outSolid._weldVerticesByEpsilon(tol, { rebuildManifold: false });
- }
- if (typeof outSolid.fixTriangleWindingsByAdjacency === 'function') {
- outSolid.fixTriangleWindingsByAdjacency();
- }
- } catch (e) {
- console.warn('[RemeshFeature] Pre-simplify weld failed; continuing with simplify.', e);
- }
- }
- try {
- if (tol === undefined) outSolid.simplify();
- else outSolid.simplify(tol);
- } catch (e) {
- console.warn('[RemeshFeature] Simplify failed; returning original clone.', e);
- }
- } else {
- const L = Number(this.inputParams.maxEdgeLength);
- const I = Number(this.inputParams.maxIterations);
- const maxEdgeLength = (Number.isFinite(L) && L > 0) ? L : 1;
- const maxIterations = (Number.isFinite(I) && I > 0) ? I : 10;
- try { outSolid.remesh({ maxEdgeLength, maxIterations }); } catch (e) {
- console.warn('[RemeshFeature] Remesh failed; returning original clone.', e);
- }
- }
-
- // Name and visualize for UI
- try { outSolid.name = `(${target.name || 'Solid'})`; } catch (_) {}
- try { outSolid.visualize(); } catch (_) {}
-
- try { target.__removeFlag = true; } catch {}
- return { added: [outSolid], removed: [target] };
- }
-}
diff --git a/src/features/sheetMetal/sheetMetalEngineBridge/shared.js b/src/features/sheetMetal/sheetMetalEngineBridge/shared.js
index edaf75a2..1c9fb4e8 100644
--- a/src/features/sheetMetal/sheetMetalEngineBridge/shared.js
+++ b/src/features/sheetMetal/sheetMetalEngineBridge/shared.js
@@ -106,7 +106,6 @@ function isSolidLikeObject(value) {
String(value.type || "").toUpperCase() === "SOLID"
|| typeof value.subtract === "function"
|| typeof value.union === "function"
- || typeof value._manifoldize === "function"
)
);
}
@@ -195,7 +194,6 @@ function solidFromSnapshot(snapshot, name = "SheetMetalCutout:CUTTER") {
solid._edgeMetadata = new Map();
solid._auxEdges = [];
solid._dirty = true;
- solid._manifold = null;
solid._faceIndex = null;
return solid;
}
diff --git a/src/features/sketch/SketchFeature.js b/src/features/sketch/SketchFeature.js
index 6f9696a4..deb30317 100644
--- a/src/features/sketch/SketchFeature.js
+++ b/src/features/sketch/SketchFeature.js
@@ -190,6 +190,252 @@ function normalizeSketchPointAttributes(sketch, externalRefPointIds = null) {
}
}
+function conditionSketchPointMapForNativeProfiles(sketch, pointById) {
+ if (!sketch || !(pointById instanceof Map)) return pointById;
+ const points = Array.isArray(sketch.points) ? sketch.points : [];
+ const constraints = Array.isArray(sketch.constraints) ? sketch.constraints : [];
+ if (!points.length || !constraints.length) return pointById;
+
+ const parent = new Map();
+ const fixed = new Set();
+ const pairKey = (a, b) => {
+ const left = String(a);
+ const right = String(b);
+ return left < right ? `${left}|${right}` : `${right}|${left}`;
+ };
+ const allLinePairs = new Set();
+ const profileLinePairs = new Set();
+ const axisAlignedLinePairs = new Set();
+ const lineEndpointsByKey = new Map();
+ for (const g of (Array.isArray(sketch.geometries) ? sketch.geometries : [])) {
+ if (g?.type !== 'line' || !Array.isArray(g.points) || g.points.length !== 2) continue;
+ const key = pairKey(g.points[0], g.points[1]);
+ allLinePairs.add(key);
+ lineEndpointsByKey.set(key, [g.points[0], g.points[1]]);
+ if (g.construction !== true) profileLinePairs.add(key);
+ }
+ for (const c of constraints) {
+ if ((c?.type === '━' || c?.type === '│') && Array.isArray(c.points) && c.points.length >= 2) {
+ axisAlignedLinePairs.add(pairKey(c.points[0], c.points[1]));
+ }
+ }
+ const relationUsesLines = (ids, pairs) => (
+ Array.isArray(ids)
+ && ids.length >= 4
+ && pairs.has(pairKey(ids[0], ids[1]))
+ && pairs.has(pairKey(ids[2], ids[3]))
+ );
+ for (const p of points) {
+ if (p?.id == null || !pointById.has(p.id)) continue;
+ parent.set(p.id, p.id);
+ if (p.fixed === true || p.externalReference === true) fixed.add(p.id);
+ }
+ const find = (id) => {
+ if (!parent.has(id)) parent.set(id, id);
+ let root = parent.get(id);
+ while (parent.get(root) !== root) root = parent.get(root);
+ let cur = id;
+ while (parent.get(cur) !== cur) {
+ const next = parent.get(cur);
+ parent.set(cur, root);
+ cur = next;
+ }
+ return root;
+ };
+ const union = (a, b) => {
+ if (!parent.has(a) || !parent.has(b)) return;
+ const ra = find(a);
+ const rb = find(b);
+ if (ra !== rb) parent.set(rb, ra);
+ };
+
+ for (const c of constraints) {
+ if (c?.type !== '≡' || !Array.isArray(c.points) || c.points.length < 2) continue;
+ const first = c.points[0];
+ for (let i = 1; i < c.points.length; i += 1) union(first, c.points[i]);
+ }
+
+ const groups = () => {
+ const out = new Map();
+ for (const id of pointById.keys()) {
+ const root = find(id);
+ if (!out.has(root)) out.set(root, []);
+ out.get(root).push(id);
+ }
+ return out;
+ };
+ const setGroup = (id, x, y) => {
+ const ids = groups().get(find(id)) || [id];
+ for (const pid of ids) {
+ const p = pointById.get(pid);
+ if (!p) continue;
+ p.x = x;
+ p.y = y;
+ }
+ };
+ const groupFixed = (id) => {
+ const ids = groups().get(find(id)) || [id];
+ return ids.some((pid) => fixed.has(pid));
+ };
+ const averageCoincidentGroups = () => {
+ for (const ids of groups().values()) {
+ let sx = 0;
+ let sy = 0;
+ let count = 0;
+ for (const id of ids) {
+ const p = pointById.get(id);
+ if (!p) continue;
+ sx += Number(p.x) || 0;
+ sy += Number(p.y) || 0;
+ count += 1;
+ }
+ if (!count) continue;
+ const x = sx / count;
+ const y = sy / count;
+ for (const id of ids) {
+ const p = pointById.get(id);
+ if (p) { p.x = x; p.y = y; }
+ }
+ }
+ };
+ const get = (id) => pointById.get(id) || null;
+ const setHorizontal = (aId, bId) => {
+ const a = get(aId);
+ const b = get(bId);
+ if (!a || !b) return;
+ const y = (a.y + b.y) * 0.5;
+ setGroup(aId, a.x, y);
+ setGroup(bId, b.x, y);
+ };
+ const setVertical = (aId, bId) => {
+ const a = get(aId);
+ const b = get(bId);
+ if (!a || !b) return;
+ const x = (a.x + b.x) * 0.5;
+ setGroup(aId, x, a.y);
+ setGroup(bId, x, b.y);
+ };
+ const setRelatedLine = (ids, perpendicular) => {
+ if (!Array.isArray(ids) || ids.length < 4) return;
+ const [aId, bId, cId, dId] = ids;
+ const a = get(aId);
+ const b = get(bId);
+ const c = get(cId);
+ const d = get(dId);
+ if (!a || !b || !c || !d) return;
+ const vx = b.x - a.x;
+ const vy = b.y - a.y;
+ const wx = d.x - c.x;
+ const wy = d.y - c.y;
+ const vLen = Math.hypot(vx, vy);
+ const wLen = Math.hypot(wx, wy);
+ if (vLen <= 1e-12 || wLen <= 1e-12) return;
+ let tx = perpendicular ? -vy : vx;
+ let ty = perpendicular ? vx : vy;
+ const tLen = Math.hypot(tx, ty);
+ if (tLen <= 1e-12) return;
+ tx /= tLen;
+ ty /= tLen;
+ if ((tx * wx + ty * wy) < 0) {
+ tx = -tx;
+ ty = -ty;
+ }
+ const dx = tx * wLen;
+ const dy = ty * wLen;
+ if (groupFixed(dId) && !groupFixed(cId)) {
+ setGroup(cId, d.x - dx, d.y - dy);
+ } else {
+ setGroup(dId, c.x + dx, c.y + dy);
+ }
+ };
+ const normalizeAngle = (angle) => {
+ let out = angle % Math.PI;
+ if (out < 0) out += Math.PI;
+ return out;
+ };
+ const relatedLineConstraints = constraints
+ .filter((c) => {
+ if (!Array.isArray(c?.points) || c.points.length < 4) return false;
+ if (c.type === '⟂') return relationUsesLines(c.points, allLinePairs);
+ if (c.type === '∥') return relationUsesLines(c.points, profileLinePairs);
+ return false;
+ })
+ .map((c) => ({
+ first: pairKey(c.points[0], c.points[1]),
+ second: pairKey(c.points[2], c.points[3]),
+ perpendicular: c.type === '⟂',
+ }));
+ const applyPropagatedLineOrientations = () => {
+ const orientation = new Map();
+ for (const c of constraints) {
+ if (!Array.isArray(c?.points) || c.points.length < 2) continue;
+ const key = pairKey(c.points[0], c.points[1]);
+ if (!allLinePairs.has(key)) continue;
+ if (c.type === '━') orientation.set(key, 0);
+ else if (c.type === '│') orientation.set(key, Math.PI / 2);
+ }
+ for (let pass = 0; pass < 16; pass += 1) {
+ let changed = false;
+ for (const relation of relatedLineConstraints) {
+ const propagate = (from, to, perpendicular) => {
+ if (!orientation.has(from) || orientation.has(to)) return;
+ orientation.set(to, normalizeAngle(orientation.get(from) + (perpendicular ? Math.PI / 2 : 0)));
+ changed = true;
+ };
+ propagate(relation.first, relation.second, relation.perpendicular);
+ propagate(relation.second, relation.first, relation.perpendicular);
+ }
+ if (!changed) break;
+ }
+ for (const [key, angle] of orientation.entries()) {
+ const ids = lineEndpointsByKey.get(key);
+ if (!ids) continue;
+ const a = get(ids[0]);
+ const b = get(ids[1]);
+ if (!a || !b) continue;
+ const len = Math.hypot(b.x - a.x, b.y - a.y);
+ if (len <= 1e-12) continue;
+ let dx = Math.cos(angle);
+ let dy = Math.sin(angle);
+ if (dx * (b.x - a.x) + dy * (b.y - a.y) < 0) {
+ dx = -dx;
+ dy = -dy;
+ }
+ dx *= len;
+ dy *= len;
+ if (groupFixed(ids[1]) && !groupFixed(ids[0])) {
+ setGroup(ids[0], b.x - dx, b.y - dy);
+ } else {
+ setGroup(ids[1], a.x + dx, a.y + dy);
+ }
+ }
+ averageCoincidentGroups();
+ };
+
+ averageCoincidentGroups();
+ for (let pass = 0; pass < 24; pass += 1) {
+ for (const c of constraints) {
+ if (!Array.isArray(c?.points) || c.points.length < 2) continue;
+ if (c.type === '━') setHorizontal(c.points[0], c.points[1]);
+ else if (c.type === '│') setVertical(c.points[0], c.points[1]);
+ else if (c.type === '∥' && relationUsesLines(c.points, profileLinePairs)) {
+ const firstLocked = axisAlignedLinePairs.has(pairKey(c.points[0], c.points[1]));
+ const secondLocked = axisAlignedLinePairs.has(pairKey(c.points[2], c.points[3]));
+ if (!secondLocked || firstLocked) setRelatedLine(c.points, false);
+ if (!firstLocked || secondLocked) setRelatedLine([c.points[2], c.points[3], c.points[0], c.points[1]], false);
+ } else if (c.type === '⟂' && relationUsesLines(c.points, allLinePairs)) {
+ const firstLocked = axisAlignedLinePairs.has(pairKey(c.points[0], c.points[1]));
+ const secondLocked = axisAlignedLinePairs.has(pairKey(c.points[2], c.points[3]));
+ if (!secondLocked || firstLocked) setRelatedLine(c.points, true);
+ if (!firstLocked || secondLocked) setRelatedLine([c.points[2], c.points[3], c.points[0], c.points[1]], true);
+ }
+ }
+ averageCoincidentGroups();
+ }
+ applyPropagatedLineOrientations();
+ return pointById;
+}
+
function computeObjectWorldCentroid(object) {
if (!object) return null;
try { object.updateWorldMatrix?.(true, true); } catch { /* ignore */ }
@@ -813,7 +1059,10 @@ export class SketchFeature {
// Do not add curve preview lines in scene; editor handles those.
// ---- Build PROFILE face from sketch with loop detection + holes ----
- const pointById = new Map(sketch.points.map(p => [p.id, { x: p.x, y: p.y }]));
+ const pointById = conditionSketchPointMapForNativeProfiles(
+ sketch,
+ new Map(sketch.points.map(p => [p.id, { x: p.x, y: p.y }])),
+ );
const segs = [];
const edges = [];
const openChains = [];
@@ -833,7 +1082,7 @@ export class SketchFeature {
bw.x + edgeBias.x, bw.y + edgeBias.y, bw.z + edgeBias.z,
]);
const edgeName = `${edgeNamePrefix}G${g.id}`;
- const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal:[[aw.x,aw.y,aw.z],[bw.x,bw.y,bw.z]], polylineWorld:true, sketchFeatureId: featureId, sketchGeometryId: g.id };
+ const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal:[[aw.x,aw.y,aw.z],[bw.x,bw.y,bw.z]], polylineWorld:true, sketchGeomType:'line', sketchFeatureId: featureId, sketchGeometryId: g.id };
applySketchEdgeStyle(e);
edges.push(e); edgeBySegId.set(g.id, e);
} else if (g.type==='arc' && g.points?.length===3) {
@@ -911,7 +1160,11 @@ export class SketchFeature {
const flat=[]; const worldPts=[]; for(const p of pts){ const v=toWorld(p[0],p[1]); flat.push(v.x + edgeBias.x, v.y + edgeBias.y, v.z + edgeBias.z); worldPts.push([v.x,v.y,v.z]); }
const lg = new LineGeometry(); lg.setPositions(flat);
const edgeName = `${edgeNamePrefix}G${g.id}`;
- const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal: worldPts, polylineWorld:true, sketchFeatureId: featureId, sketchGeometryId: g.id };
+ const bezierPoles = ids.map((id) => pointById.get(id)).filter(Boolean).map((p) => {
+ const v = toWorld(p.x, p.y);
+ return [v.x, v.y, v.z];
+ });
+ const e = new BREP.Edge(lg); e.name = edgeName; e.userData = { polylineLocal: worldPts, polylineWorld:true, sketchGeomType:'bezier', bezierPoles, sketchFeatureId: featureId, sketchGeometryId: g.id };
applySketchEdgeStyle(e);
edges.push(e); edgeBySegId.set(g.id, e);
}
@@ -1146,8 +1399,12 @@ export class SketchFeature {
for (const grp of groups){
// Prepare contour and holes (remove duplicate last point for API)
let contour = normalizedLoops[grp.outer].slice(); contour.pop();
+ let contourSegmentIds = (loopSegIDs[grp.outer] || []).map((sid) => segs[sid]?.id).filter((id) => id !== undefined && id !== null);
// Earcut expects outer CW, holes CCW. Enforce CW for outer
- if (signedArea([...contour, contour[0]]) > 0) contour = contour.slice().reverse();
+ if (signedArea([...contour, contour[0]]) > 0) {
+ contour = contour.slice().reverse();
+ contourSegmentIds = contourSegmentIds.slice().reverse();
+ }
// Record boundary edges for outer
for (const sid of (loopSegIDs[grp.outer] || [])) {
const e = edgeBySegId.get(segs[sid]?.id);
@@ -1158,8 +1415,12 @@ export class SketchFeature {
}
const holes = grp.holes.map(idx=>{
let h = normalizedLoops[idx].slice(); h.pop();
+ let hSegmentIds = (loopSegIDs[idx] || []).map((sid) => segs[sid]?.id).filter((id) => id !== undefined && id !== null);
// Ensure CCW for holes (outer is CW per earcut convention)
- if (signedArea([...h, h[0]]) < 0) h = h.slice().reverse();
+ if (signedArea([...h, h[0]]) < 0) {
+ h = h.slice().reverse();
+ hSegmentIds = hSegmentIds.slice().reverse();
+ }
for (const sid of (loopSegIDs[idx] || [])) {
const e = edgeBySegId.get(segs[sid]?.id);
if (e) {
@@ -1167,6 +1428,7 @@ export class SketchFeature {
boundaryEdges.add(e);
}
}
+ h.segmentIds = hSegmentIds;
return h;
});
@@ -1192,9 +1454,13 @@ export class SketchFeature {
const toW = (p)=> toWorld(p[0], p[1]);
const worldOuter = contour.map(p=>{ const v=toW(p); return [v.x,v.y,v.z]; });
const worldHoles = holes.map(h=> h.map(p=>{ const v=toW(p); return [v.x,v.y,v.z]; }));
- boundaryLoopsWorld.push({ pts: worldOuter, isHole: false });
- for (const h of worldHoles) boundaryLoopsWorld.push({ pts: h, isHole: true });
- profileGroups.push({ contour2D: contour.slice(), holes2D: holes.map(h=>h.slice()), contourW: worldOuter.slice(), holesW: worldHoles.map(h=>h.slice()) });
+ const outerSegmentIds = contourSegmentIds.slice();
+ const holeSegmentIds = holes.map((hole) => Array.isArray(hole.segmentIds) ? hole.segmentIds.slice() : []);
+ boundaryLoopsWorld.push({ pts: worldOuter, isHole: false, segmentIds: outerSegmentIds.slice() });
+ for (let holeIndex = 0; holeIndex < worldHoles.length; holeIndex += 1) {
+ boundaryLoopsWorld.push({ pts: worldHoles[holeIndex], isHole: true, segmentIds: (holeSegmentIds[holeIndex] || []).slice() });
+ }
+ profileGroups.push({ contour2D: contour.slice(), holes2D: holes.map(h=>h.slice()), contourW: worldOuter.slice(), holesW: worldHoles.map(h=>h.slice()), contourSegmentIds: outerSegmentIds.slice(), holeSegmentIds: holeSegmentIds.map((ids) => ids.slice()) });
}
const diagEdges = edges.map((e) => {
diff --git a/src/features/spline/SplineFeature.js b/src/features/spline/SplineFeature.js
index 2e5139d3..d13d5ada 100644
--- a/src/features/spline/SplineFeature.js
+++ b/src/features/spline/SplineFeature.js
@@ -1666,6 +1666,11 @@ export class SplineFeature {
edge.userData = {
polylineLocal: polyline.map((p) => [p[0], p[1], p[2]]),
polylineWorld: true,
+ splinePath: {
+ type: "hermite-extension-spline",
+ spline: cloneSplineData(evaluatedSpline),
+ bendRadius,
+ },
splineFeatureId: featureId,
};
sceneGroup.add(edge);
diff --git a/src/features/tube/TubeFeature.js b/src/features/tube/TubeFeature.js
index f59d03df..fbeaf5b3 100644
--- a/src/features/tube/TubeFeature.js
+++ b/src/features/tube/TubeFeature.js
@@ -24,22 +24,6 @@ const inputParamsSchema = {
default_value: 0,
hint: 'Optional inner radius for hollow tubes (0 for solid)'
},
- resolution: {
- type: 'number',
- default_value: "resolution",
- hint: 'Segments around the tube circumference'
- },
- mode: {
- type: 'options',
- options: ['Light (fast)', 'Heavy (slow)'],
- default_value: 'Light (fast)',
- hint: 'Light uses the native auto tube builder; Heavy forces the slower robust build'
- },
- debug: {
- type: 'boolean',
- default_value: false,
- hint: 'Log path points and parameters for debugging'
- },
boolean: {
type: 'boolean_operation',
default_value: { targets: [], operation: 'NONE' },
@@ -106,6 +90,16 @@ function collectEdgePolylines(edges) {
return { polys, edges: validEdges };
}
+function clonePathCurveForEdge(edgeObj) {
+ const curve = edgeObj?.userData?.splinePath;
+ if (!curve || typeof curve !== 'object') return null;
+ try {
+ return JSON.parse(JSON.stringify(curve));
+ } catch {
+ return null;
+ }
+}
+
function combinePathPolylinesWithUsage(edges, tol = 1e-5) {
const { polys, edges: validEdges } = collectEdgePolylines(edges);
if (polys.length === 0) {
@@ -313,40 +307,6 @@ function dedupePoints(points, eps = 1e-7) {
return out;
}
-function extendPathEndpoints(points, extension = 0) {
- if (!Array.isArray(points) || points.length < 2 || !(extension > 0)) {
- return Array.isArray(points) ? points.map(p => Array.isArray(p) ? [p[0], p[1], p[2]] : p) : points;
- }
- const extended = points.map(p => [p[0], p[1], p[2]]);
- const start = extended[0];
- const next = extended[1];
- const sx = next[0] - start[0];
- const sy = next[1] - start[1];
- const sz = next[2] - start[2];
- const startLen = Math.hypot(sx, sy, sz);
- if (startLen > 1e-9) {
- const inv = extension / startLen;
- start[0] -= sx * inv;
- start[1] -= sy * inv;
- start[2] -= sz * inv;
- }
-
- const end = extended[extended.length - 1];
- const prev = extended[extended.length - 2];
- const ex = end[0] - prev[0];
- const ey = end[1] - prev[1];
- const ez = end[2] - prev[2];
- const endLen = Math.hypot(ex, ey, ez);
- if (endLen > 1e-9) {
- const inv = extension / endLen;
- end[0] += ex * inv;
- end[1] += ey * inv;
- end[2] += ez * inv;
- }
-
- return extended;
-}
-
export class TubeFeature {
static shortName = 'TU';
static longName = 'Tube';
@@ -370,7 +330,7 @@ export class TubeFeature {
}
async run(partHistory) {
- const { featureID, path, radius, innerRadius, resolution, debug, mode } = this.inputParams;
+ const { featureID, path, radius, innerRadius } = this.inputParams;
const radiusValue = Number(radius);
if (!(radiusValue > 0)) {
throw new Error('Tube requires a positive radius.');
@@ -396,13 +356,18 @@ export class TubeFeature {
for (const group of edgeGroups) {
const { points, unusedEdges } = combinePathPolylinesWithUsage(group);
if (Array.isArray(points) && points.length >= 2) {
- tubeTasks.push({ points, edge: group[0] || null });
+ const edge = group[0] || null;
+ tubeTasks.push({
+ points,
+ edge,
+ pathCurve: group.length === 1 ? clonePathCurveForEdge(edge) : null,
+ });
}
if (Array.isArray(unusedEdges) && unusedEdges.length) {
for (const edge of unusedEdges) {
const edgePoints = extractPathPolylineWorld(edge);
if (edgePoints.length >= 2) {
- tubeTasks.push({ points: edgePoints, edge });
+ tubeTasks.push({ points: edgePoints, edge, pathCurve: clonePathCurveForEdge(edge) });
}
}
}
@@ -412,15 +377,7 @@ export class TubeFeature {
throw new Error('Unable to build a connected path for the tube.');
}
- const baseResolution = Math.max(8, Math.floor(Number(resolution) || 32));
- const modeSelection = typeof mode === 'string'
- ? mode
- : (mode == null ? inputParamsSchema.mode.default_value : String(mode));
- const preferFast = String(modeSelection || '').toLowerCase().startsWith('light');
- const TubeBuilder = BREP.Tube;
- const outerSolids = [];
- const innerSolids = [];
- const debugExtras = [];
+ const baseSolids = [];
for (let i = 0; i < tubeTasks.length; i++) {
const task = tubeTasks[i];
const pathPoints = dedupePoints(task.points);
@@ -442,10 +399,9 @@ export class TubeFeature {
const finalPoints = isClosedLoop ? pathPoints.slice(0, -1) : pathPoints;
const tubeName = (() => {
- if (!featureID) return featureID;
+ if (!featureID) return 'Tube';
if (tubeTasks.length === 1) return featureID;
- // get the name of the first edge in the group if possible
const edgeRef = task.edge;
if (edgeRef) {
const edgeName = edgeRef.name || edgeRef.id || edgeRef.userData?.edgeName;
@@ -457,63 +413,18 @@ export class TubeFeature {
return `${featureID}_${i + 1}`;
})();
- if (debug) {
- console.log('[TubeFeature debug] params', {
- featureID,
- radius: radiusValue,
- innerRadius: inner,
- resolution: baseResolution,
- mode: modeSelection,
- builder: preferFast ? 'Tube (native auto)' : 'Tube (slow)',
- groupIndex: i,
- isClosedLoop,
- pathPointCount: finalPoints.length,
- points: finalPoints
- });
- }
-
- const outerTube = new TubeBuilder({
+ const tube = new BREP.Tube({
points: finalPoints,
radius: radiusValue,
- innerRadius: 0,
- resolution: baseResolution,
+ innerRadius: inner,
closed: isClosedLoop,
name: tubeName,
- debugSpheres: !!debug,
- preferFast,
+ pathCurve: task.pathCurve,
});
- if (debug && Array.isArray(outerTube.debugSphereSolids)) {
- debugExtras.push(...outerTube.debugSphereSolids);
- }
- outerSolids.push(outerTube);
-
- if (inner > 0) {
- const innerName = tubeName ? `${tubeName}_Inner` : null;
- const baseScale = Math.max(radiusValue, inner);
- const capExtension = (!isClosedLoop && baseScale > 0)
- ? Math.max(baseScale * 5e-3, 1e-4)
- : 0;
- const innerPoints = (!isClosedLoop && capExtension > 0)
- ? extendPathEndpoints(finalPoints, capExtension)
- : finalPoints;
- const innerTube = new TubeBuilder({
- points: innerPoints,
- radius: inner,
- innerRadius: 0,
- resolution: baseResolution,
- closed: isClosedLoop,
- name: innerName,
- debugSpheres: !!debug,
- preferFast,
- });
- if (debug && Array.isArray(innerTube.debugSphereSolids)) {
- debugExtras.push(...innerTube.debugSphereSolids);
- }
- innerSolids.push(innerTube);
- }
+ baseSolids.push(tube);
}
- if (!outerSolids.length) {
+ if (!baseSolids.length) {
throw new Error('Unable to build a connected path for the tube.');
}
@@ -525,10 +436,6 @@ export class TubeFeature {
}
return solids[0];
}
- console.log('Attempting union of solids:', solids);
-
-
-
try {
let result = solids[0];
@@ -555,87 +462,6 @@ export class TubeFeature {
const booleanTargets = Array.isArray(booleanParam?.targets) ? booleanParam.targets.filter(Boolean) : [];
const shouldApplyBoolean = booleanOp !== 'NONE' && booleanTargets.length > 0;
- // Always attempt to union outer segments into one solid when possible
- const outerUnionLabel = featureID || outerSolids[0]?.name || 'TubeUnion';
- const outerUnion = (outerSolids.length > 1) ? attemptUnionSolids(outerSolids, outerUnionLabel) : (outerSolids[0] || null);
-
- // Always attempt to union inner segments into one cutter when inside radius is set
- const innerUnionLabel = featureID ? `${featureID}_InnerUnion` : (innerSolids[0]?.name || null);
- const innerUnion = (inner > 0 && innerSolids.length > 0)
- ? ((innerSolids.length > 1) ? attemptUnionSolids(innerSolids, innerUnionLabel) : innerSolids[0])
- : null;
-
- // Build the base (hollow) shell per required pipeline:
- // final = union(outer) minus union(inner)
- let baseSolids = [];
- if (inner > 0) {
- // Preferred path: single subtract of the two unions
- if (outerUnion && innerUnion) {
- try {
- baseSolids = [outerUnion.subtract(innerUnion)];
- } catch (e) {
- console.warn('[TubeFeature] Subtract of unioned inner from unioned outer failed; falling back:', e?.message || e);
- }
- }
-
- // Fallbacks if the preferred single subtract was not possible
- if (!baseSolids.length) {
- if (outerUnion && innerSolids.length > 0) {
- // Subtract each inner from the outer union
- let shell = outerUnion;
- for (const cutter of innerUnion ? [innerUnion] : innerSolids) {
- try { if (cutter) shell = shell.subtract(cutter); } catch (err) {
- console.warn('[TubeFeature] Fallback subtract (outerUnion - cutter) failed:', err?.message || err);
- }
- }
- baseSolids = [shell];
- } else if (!outerUnion && innerUnion && outerSolids.length > 0) {
- // Subtract inner union from each outer, then try to union the result
- const shells = [];
- for (const outer of outerSolids) {
- try { shells.push(outer.subtract(innerUnion)); } catch (err) {
- console.warn('[TubeFeature] Fallback subtract (outer - innerUnion) failed:', err?.message || err);
- shells.push(outer);
- }
- }
- const unifiedShell = attemptUnionSolids(shells, outerUnionLabel) || null;
- baseSolids = unifiedShell ? [unifiedShell] : shells.filter(Boolean);
- } else {
- // Last resort: pairwise subtract matching inners when available
- const shells = [];
- for (let i = 0; i < outerSolids.length; i++) {
- const outer = outerSolids[i];
- const cutter = innerSolids[i] || innerUnion || null;
- if (outer) {
- try {
- shells.push(cutter ? outer.subtract(cutter) : outer);
- } catch (err) {
- console.warn(`[TubeFeature] Pairwise subtract failed for segment ${i}:`, err?.message || err);
- shells.push(outer);
- }
- }
- }
- const unifiedShell = attemptUnionSolids(shells, outerUnionLabel) || null;
- baseSolids = unifiedShell ? [unifiedShell] : shells.filter(Boolean);
- }
- }
- } else {
- // Solid tube (no inner). Prefer a unified outer shell if available
- baseSolids = outerUnion ? [outerUnion] : outerSolids.slice();
- }
-
- baseSolids = baseSolids.filter(Boolean);
-
- if (debug && debugExtras.length) {
- for (const s of debugExtras) {
- try { s.visualize(); } catch (_) { }
- }
- }
-
- if (!baseSolids.length) {
- throw new Error('Tube generation failed to produce a valid solid.');
- }
-
for (const solid of baseSolids) {
if (!solid) continue;
if (featureID) {
@@ -673,14 +499,9 @@ export class TubeFeature {
}
}
}
- if (debug && debugExtras.length) {
- added.push(...debugExtras);
- }
return { added, removed };
}
- const added = [...baseSolids];
- if (debug && debugExtras.length) added.push(...debugExtras);
- return { added, removed: [] };
+ return { added: baseSolids, removed: [] };
}
}
diff --git a/src/index.js b/src/index.js
index 6f5c6e7c..ba5a3267 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,6 @@
// Expose the core BREP kernel and part history classes
export { BREP } from './BREP/BREP.js';
-export { CppSolidCore } from './BREP/CppSolidCore.js';
// Part history API
export { PartHistory, extractDefaultValues } from './PartHistory.js';
@@ -25,4 +24,3 @@ export { sketchToSVG, sketchToSVGPaths, sketchToDXF, sketchTo3DPolylines } from
// Full CAD app embed (iframe-based)
export { CadEmbed, CADEmbed, bootCadFrame, bootCADFrame } from './UI/cad/CadEmbed.js';
-export { manifoldPlusSum } from './BREP/setupManifold.js';
diff --git a/src/main.js b/src/main.js
index a4b2b648..27248038 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1851,6 +1851,20 @@ async function openEntryOnGithub(entry) {
if (repoFull) window.open(`https://github.com/${repoFull}`, '_blank', 'noopener,noreferrer');
}
+function buildEntryCadHref(entry) {
+ const source = normalizeStorageSource(entry?.source);
+ const repoFull = String(entry?.repoFull || '').trim();
+ const branch = String(entry?.branch || '').trim();
+ const name = String(entry?.name || '').trim();
+ const path = getEntryCadLaunchModelPath(entry) || name;
+ return buildCadUrl({
+ source,
+ repoFull,
+ branch,
+ path,
+ });
+}
+
function createBrowserFileTile(entry) {
const name = String(entry?.name || '').trim();
if (!name) return null;
@@ -1862,6 +1876,7 @@ function createBrowserFileTile(entry) {
const displayName = getEntryModelDisplayName(entry);
const fullModelPath = getEntryFullPathTooltip(entry) || getEntryModelPathWithExtension(entry) || ensureModelExtension(name);
const cadModelPath = getEntryCadLaunchModelPath(entry) || name;
+ const fileHref = buildEntryCadHref(entry);
const openFile = (event) => {
event?.preventDefault?.();
event?.stopPropagation?.();
@@ -1885,18 +1900,31 @@ function createBrowserFileTile(entry) {
previewClass: 'hub-browser-tile-preview',
iconClass: 'hub-browser-tile-icon',
thumbClass: 'hub-browser-tile-thumb',
+ href: fileHref,
});
preview.classList.add('hub-browser-open-target');
preview.title = `Open ${displayName || name}`;
- preview.addEventListener('click', openFile);
+ preview.addEventListener('click', (event) => {
+ event.stopPropagation();
+ });
+ preview.addEventListener('keydown', (event) => {
+ event.stopPropagation();
+ });
tile.appendChild(preview);
- const label = document.createElement('div');
+ const label = document.createElement('a');
label.className = 'hub-browser-tile-name';
label.textContent = displayName || name;
label.title = fullModelPath || name;
+ label.href = fileHref;
+ label.draggable = false;
label.classList.add('hub-browser-open-target');
- label.addEventListener('click', openFile);
+ label.addEventListener('click', (event) => {
+ event.stopPropagation();
+ });
+ label.addEventListener('keydown', (event) => {
+ event.stopPropagation();
+ });
tile.appendChild(label);
bindExplorerFileKeyboard(tile, entry, openFile);
@@ -1910,12 +1938,17 @@ function createModelPreview(entry, {
iconClass = 'hub-browser-icon',
thumbClass = 'hub-browser-thumb',
iconGlyph = '📄',
+ href = '',
} = {}) {
const name = String(entry?.name || '').trim();
const displayName = getEntryModelDisplayName(entry) || name || 'Model';
- const preview = document.createElement('span');
+ const preview = document.createElement(href ? 'a' : 'span');
preview.className = previewClass;
+ if (href) {
+ preview.href = href;
+ preview.draggable = false;
+ }
const icon = document.createElement('span');
icon.className = iconClass;
@@ -2029,6 +2062,7 @@ function createFolderExplorer(roots, term = '') {
createFileActionsMenu: (entry, { tile = false } = {}) => createFileActionsMenu(entry, {
extraClass: tile ? 'hub-browser-tile-menu' : '',
}),
+ buildFileHref: buildEntryCadHref,
createFolderActionsMenu: (entry, { tile = false, source, repoFull } = {}) => createFolderActionsMenu(entry, {
source: normalizeStorageSource(source || 'local'),
repoFull: String(repoFull || '').trim(),
diff --git a/src/styles/landing.css b/src/styles/landing.css
index 4bfee0eb..887d1c6d 100644
--- a/src/styles/landing.css
+++ b/src/styles/landing.css
@@ -1062,6 +1062,8 @@ body {
.hub-browser-open-target {
cursor: pointer;
+ color: inherit;
+ text-decoration: none;
}
.hub-browser-open-target:focus-visible {
diff --git a/src/tests/test_boolean_overlap_conditioning.js b/src/tests/test_boolean_overlap_conditioning.js
deleted file mode 100644
index e835bd29..00000000
--- a/src/tests/test_boolean_overlap_conditioning.js
+++ /dev/null
@@ -1,162 +0,0 @@
-import { BREP } from '../BREP/BREP.js';
-
-function assert(condition, message) {
- if (!condition) throw new Error(message || 'Assertion failed.');
-}
-
-function faceAxisMin(solid, faceName, axisIndex = 0) {
- const triangles = typeof solid?.getFace === 'function' ? solid.getFace(faceName) : [];
- let min = Number.POSITIVE_INFINITY;
- for (const tri of triangles) {
- for (const point of [tri?.p1, tri?.p2, tri?.p3]) {
- if (!Array.isArray(point) || point.length < 3) continue;
- min = Math.min(min, Number(point[axisIndex]));
- }
- }
- return min;
-}
-
-function faceAxisMax(solid, faceName, axisIndex = 0) {
- const triangles = typeof solid?.getFace === 'function' ? solid.getFace(faceName) : [];
- let max = Number.NEGATIVE_INFINITY;
- for (const tri of triangles) {
- for (const point of [tri?.p1, tri?.p2, tri?.p3]) {
- if (!Array.isArray(point) || point.length < 3) continue;
- max = Math.max(max, Number(point[axisIndex]));
- }
- }
- return max;
-}
-
-function makeTouchingCubePair(baseName, toolName) {
- const base = new BREP.Cube({ x: 1, y: 1, z: 1, name: baseName });
- const tool = new BREP.Cube({ x: 1, y: 1, z: 1, name: toolName });
- tool.bakeTRS({
- position: [1, 0, 0],
- rotationEuler: [0, 0, 0],
- scale: [1, 1, 1],
- });
- return { base, tool };
-}
-
-function makeFakePartHistory() {
- return {
- scene: {
- getObjectByName() {
- return null;
- },
- },
- };
-}
-
-async function captureUnionOperandMinX(booleanParam) {
- const { base, tool } = makeTouchingCubePair('BASE', 'TOOL');
- const proto = Object.getPrototypeOf(base);
- const originalUnion = proto.union;
- let observedMinX = null;
-
- proto.union = function patchedUnion(other) {
- if (observedMinX === null) {
- observedMinX = faceAxisMin(other, 'TOOL_NX', 0);
- }
- return originalUnion.call(this, other);
- };
-
- try {
- await BREP.applyBooleanOperation(makeFakePartHistory(), base, {
- operation: 'UNION',
- targets: [tool],
- ...booleanParam,
- }, 'BOOL_OVERLAP_UNION');
- } finally {
- proto.union = originalUnion;
- }
-
- return observedMinX;
-}
-
-async function captureSubtractOperandMinX(booleanParam) {
- const { base: target, tool: cutter } = makeTouchingCubePair('TARGET', 'CUTTER');
- const proto = Object.getPrototypeOf(target);
- const originalSubtract = proto.subtract;
- let observedMinX = null;
-
- proto.subtract = function patchedSubtract(other) {
- if (observedMinX === null) {
- observedMinX = faceAxisMin(other, 'CUTTER_NX', 0);
- }
- return originalSubtract.call(this, other);
- };
-
- try {
- await BREP.applyBooleanOperation(makeFakePartHistory(), cutter, {
- operation: 'SUBTRACT',
- targets: [target],
- ...booleanParam,
- }, 'BOOL_OVERLAP_SUBTRACT');
- } finally {
- proto.subtract = originalSubtract;
- }
-
- return observedMinX;
-}
-
-async function captureSubtractOperandMinZForEntryCap(booleanParam) {
- const target = new BREP.Cube({ x: 1, y: 1, z: 1, name: 'TARGET_Z' });
- const cutter = new BREP.Cube({ x: 1, y: 1, z: 2, name: 'CUTTER_Z' });
- const proto = Object.getPrototypeOf(target);
- const originalSubtract = proto.subtract;
- let observedMinZ = null;
- let observedMaxZ = null;
-
- proto.subtract = function patchedSubtract(other) {
- if (observedMinZ === null) {
- observedMinZ = faceAxisMin(other, 'CUTTER_Z_NZ', 2);
- observedMaxZ = faceAxisMax(other, 'CUTTER_Z_NZ', 2);
- }
- return originalSubtract.call(this, other);
- };
-
- try {
- await BREP.applyBooleanOperation(makeFakePartHistory(), cutter, {
- operation: 'SUBTRACT',
- targets: [target],
- ...booleanParam,
- }, 'BOOL_OVERLAP_SUBTRACT_CAP');
- } finally {
- proto.subtract = originalSubtract;
- }
-
- return { observedMinZ, observedMaxZ };
-}
-
-export async function test_boolean_overlap_conditioning_union_enabled_by_default() {
- const observedMinX = await captureUnionOperandMinX({});
- assert(Number.isFinite(observedMinX), 'Expected union test to observe the conditioned tool face.');
- assert(observedMinX < 1 - 1e-7, `Expected default union conditioning to push TOOL_NX into the base solid, got minX=${observedMinX}`);
-}
-
-export async function test_boolean_overlap_conditioning_union_can_be_disabled() {
- const observedMinX = await captureUnionOperandMinX({ overlapConditioningEnabled: false });
- assert(Number.isFinite(observedMinX), 'Expected disabled union test to observe the tool face.');
- assert(Math.abs(observedMinX - 1) <= 1e-12, `Expected disabled union conditioning to leave TOOL_NX at x=1, got minX=${observedMinX}`);
-}
-
-export async function test_boolean_overlap_conditioning_subtract_enabled_by_default() {
- const observedMinX = await captureSubtractOperandMinX({});
- assert(Number.isFinite(observedMinX), 'Expected subtract test to observe the conditioned cutter face.');
- assert(observedMinX < 1 - 1e-7, `Expected default subtract conditioning to push CUTTER_NX into the target solid, got minX=${observedMinX}`);
-}
-
-export async function test_boolean_overlap_conditioning_subtract_expands_tool_entry_cap_outward() {
- const { observedMinZ, observedMaxZ } = await captureSubtractOperandMinZForEntryCap({});
- assert(Number.isFinite(observedMinZ) && Number.isFinite(observedMaxZ), 'Expected subtract cap test to observe the conditioned cutter cap face.');
- assert(observedMinZ < 0 - 1e-7, `Expected subtract conditioning to expand CUTTER_Z_NZ outward beyond z=0, got minZ=${observedMinZ}`);
- assert(Math.abs(observedMaxZ - observedMinZ) <= 1e-12, `Expected CUTTER_Z_NZ to remain planar after conditioning, got minZ=${observedMinZ}, maxZ=${observedMaxZ}`);
-}
-
-export async function test_boolean_overlap_conditioning_subtract_can_be_disabled() {
- const observedMinX = await captureSubtractOperandMinX({ overlapConditioningEnabled: false });
- assert(Number.isFinite(observedMinX), 'Expected disabled subtract test to observe the cutter face.');
- assert(Math.abs(observedMinX - 1) <= 1e-12, `Expected disabled subtract conditioning to leave CUTTER_NX at x=1, got minX=${observedMinX}`);
-}
diff --git a/src/tests/test_cppFaceNamingRegression.js b/src/tests/test_cppFaceNamingRegression.js
index f67be9b2..50f6e363 100644
--- a/src/tests/test_cppFaceNamingRegression.js
+++ b/src/tests/test_cppFaceNamingRegression.js
@@ -1,7 +1,6 @@
import { fs } from "../fs.proxy.js";
import { CppSolidCore } from "../BREP/CppSolidCore.js";
-import { ManifoldMesh } from "../BREP/SolidShared.js";
-import { manifoldBuildSource } from "../BREP/setupManifold.js";
+import { ManifoldMesh, manifoldBuildSource } from "../BREP/setupManifold.js";
const MEDIUM_FILLET_PART_PATH = "src/tests/partFiles/medium_fillets.BREP.json";
diff --git a/src/tests/test_cppPrimitives.js b/src/tests/test_cppPrimitives.js
index 7501caf4..4189e991 100644
--- a/src/tests/test_cppPrimitives.js
+++ b/src/tests/test_cppPrimitives.js
@@ -1,3 +1,5 @@
+import { LineGeometry } from "three/examples/jsm/Addons.js";
+import { BREP } from "../BREP/BREP.js";
import { Cone, Cube, Cylinder, Pyramid, Torus, primitiveHasNativeBuilder, Sphere } from "../BREP/primitives.js";
import { manifoldBuildSource } from "../BREP/setupManifold.js";
@@ -37,6 +39,290 @@ export async function test_cppPrimitive_cylinder_preserves_expected_face_labels_
assert(Math.abs((metadata?.height || 0) - 7) <= 1e-9, "Expected native cylinder side metadata to preserve height.");
}
+export async function test_cppPrimitive_cylinder_sidewall_visualizes_cap_edges_and_seam() {
+ if (shouldSkip()) return;
+
+ const cylinder = new Cylinder({ radius: 2, height: 7, resolution: 24, name: "CPP_CYL_TOPO" });
+ cylinder.visualize();
+
+ const side = cylinder.children.find((child) => child?.type === "FACE" && child.name === "CPP_CYL_TOPO_S");
+ assert(side, "Expected visualized cylinder side face.");
+ assert(Array.isArray(side.edges), "Expected visualized cylinder side to carry topology edges.");
+ assert(side.edges.length === 3, `Expected cylinder side to have 3 edges, got ${side.edges.length}.`);
+ assert(side.edges.some((edge) => edge?.userData?.faceA === edge?.userData?.faceB), "Expected cylinder side seam self-edge.");
+ assert(side.edges.filter((edge) => edge?.closedLoop).length === 2, "Expected cylinder side to have two circular cap edges.");
+}
+
+export async function test_cppExtrude_circle_profile_sidewall_visualizes_cap_edges_and_seam() {
+ if (shouldSkip()) return;
+
+ const radius = 2;
+ const segments = 24;
+ const loop = [];
+ for (let i = 0; i <= segments; i += 1) {
+ const t = (i / segments) * Math.PI * 2;
+ loop.push([radius * Math.cos(t), radius * Math.sin(t), 0]);
+ }
+ loop[loop.length - 1] = loop[0].slice();
+
+ const positions = [];
+ for (let i = 1; i + 1 < loop.length - 1; i += 1) {
+ const a = loop[0];
+ const b = loop[i];
+ const c = loop[i + 1];
+ positions.push(a[0], a[1], a[2], b[0], b[1], b[2], c[0], c[1], c[2]);
+ }
+ const geom = new BREP.THREE.BufferGeometry();
+ geom.setAttribute("position", new BREP.THREE.Float32BufferAttribute(positions, 3));
+ geom.computeVertexNormals();
+ const face = new BREP.Face(geom);
+ face.name = "SKETCH_CIRCLE:PROFILE";
+ face.userData.faceName = face.name;
+ face.userData.boundaryLoopsWorld = [{ pts: loop.slice(0, -1), isHole: false }];
+
+ const line = new LineGeometry();
+ line.setPositions(loop.flat());
+ const edge = new BREP.Edge(line);
+ edge.name = "SKETCH_CIRCLE:G1";
+ edge.closedLoop = true;
+ edge.userData = {
+ polylineLocal: loop,
+ polylineWorld: true,
+ sketchGeomType: "circle",
+ circleCenter: [0, 0, 0],
+ circleRadius: radius,
+ };
+ face.edges = [edge];
+
+ const extrude = new BREP.Sweep({
+ face,
+ distance: 5,
+ mode: "translate",
+ name: "EXTRUDE_CIRCLE",
+ omitBaseCap: false,
+ });
+ extrude.visualize();
+
+ const side = extrude.children.find((child) => child?.type === "FACE" && /_SW(?:_\d+)?$/.test(String(child.name || "")));
+ assert(side, "Expected extruded circle sidewall face.");
+ assert(side.edges.length === 3, `Expected extruded circle sidewall to have 3 edges, got ${side.edges.length}.`);
+ assert(side.edges.some((edgeObj) => edgeObj?.userData?.faceA === edgeObj?.userData?.faceB), "Expected extruded circle sidewall seam self-edge.");
+ assert(side.edges.filter((edgeObj) => edgeObj?.closedLoop).length === 2, "Expected extruded circle sidewall to have two circular cap edges.");
+}
+
+export async function test_cppExtrude_sketch_circle_hole_builds_distinct_analytic_sidewall() {
+ if (shouldSkip()) return;
+
+ const makeEdge = (name, curveType, points, extra = {}) => {
+ const line = new LineGeometry();
+ line.setPositions(points.flat());
+ const edge = new BREP.Edge(line);
+ edge.name = name;
+ edge.userData = {
+ polylineLocal: points,
+ polylineWorld: true,
+ sketchGeomType: curveType,
+ sketchGeometryId: name,
+ ...extra,
+ };
+ return edge;
+ };
+
+ const outer = [[-5, -5, 0], [5, -5, 0], [5, 5, 0], [-5, 5, 0]];
+ const outerEdges = outer.map((point, index) => makeEdge(`L${index}`, "line", [point, outer[(index + 1) % outer.length]]));
+ const radius = 1.5;
+ const hole = [];
+ for (let i = 0; i < 32; i += 1) {
+ const t = (i / 32) * Math.PI * 2;
+ hole.push([radius * Math.cos(t), radius * Math.sin(t), 0]);
+ }
+ const circle = makeEdge("C0", "circle", hole.concat([hole[0]]), {
+ circleCenter: [0, 0, 0],
+ circleRadius: radius,
+ isHole: true,
+ });
+
+ const geom = new BREP.THREE.BufferGeometry();
+ geom.setAttribute("position", new BREP.THREE.Float32BufferAttribute([
+ -5, -5, 0, 5, -5, 0, 5, 5, 0,
+ -5, -5, 0, 5, 5, 0, -5, 5, 0,
+ ], 3));
+ geom.computeVertexNormals();
+
+ const face = new BREP.Face(geom);
+ face.name = "SKETCH_HOLE:PROFILE";
+ face.edges = [...outerEdges, circle];
+ face.userData.boundaryLoopsWorld = [
+ { pts: outer, isHole: false, segmentIds: ["L0", "L1", "L2", "L3"] },
+ { pts: hole, isHole: true, segmentIds: ["C0"] },
+ ];
+
+ const extrude = new BREP.Sweep({ face, distance: 4, name: "EXTRUDE_HOLE" });
+ extrude.visualize();
+
+ const sideFaces = extrude.children.filter((child) => child?.type === "FACE" && /_SW(?:_\d+)?$/.test(String(child.name || "")));
+ assert(sideFaces.length === 5, `Expected 4 outer sidewalls plus 1 circular hole sidewall, got ${sideFaces.length}.`);
+ const circularSide = sideFaces.find((side) => side.edges.length === 3 && side.edges.some((edge) => edge?.userData?.faceA === edge?.userData?.faceB));
+ assert(circularSide, "Expected circular hole sidewall to be a distinct analytic cylindrical face with cap edges and seam.");
+}
+
+export async function test_cppExtrude_sketch_bezier_edge_builds_single_sidewall() {
+ if (shouldSkip()) return;
+
+ const makeEdge = (name, curveType, points, extra = {}) => {
+ const line = new LineGeometry();
+ line.setPositions(points.flat());
+ const edge = new BREP.Edge(line);
+ edge.name = name;
+ edge.userData = {
+ polylineLocal: points,
+ polylineWorld: true,
+ sketchGeomType: curveType,
+ sketchGeometryId: name,
+ ...extra,
+ };
+ return edge;
+ };
+
+ const p0 = [-5, -4, 0];
+ const p1 = [5, -4, 0];
+ const p2 = [5, 4, 0];
+ const b0 = [5, 4, 0];
+ const b1 = [2, 7, 0];
+ const b2 = [-2, 7, 0];
+ const b3 = [-5, 4, 0];
+ const p3 = [-5, 4, 0];
+
+ const bezierSamples = [];
+ for (let i = 0; i <= 32; i += 1) {
+ const t = i / 32;
+ const mt = 1 - t;
+ bezierSamples.push([
+ mt * mt * mt * b0[0] + 3 * mt * mt * t * b1[0] + 3 * mt * t * t * b2[0] + t * t * t * b3[0],
+ mt * mt * mt * b0[1] + 3 * mt * mt * t * b1[1] + 3 * mt * t * t * b2[1] + t * t * t * b3[1],
+ 0,
+ ]);
+ }
+
+ const edges = [
+ makeEdge("L0", "line", [p0, p1]),
+ makeEdge("L1", "line", [p1, p2]),
+ makeEdge("B0", "bezier", bezierSamples, { bezierPoles: [b0, b1, b2, b3] }),
+ makeEdge("L2", "line", [p3, p0]),
+ ];
+
+ const loop = [p0, p1, p2, ...bezierSamples.slice(1, -1), p3];
+ const geom = new BREP.THREE.BufferGeometry();
+ geom.setAttribute("position", new BREP.THREE.Float32BufferAttribute([
+ -5, -4, 0, 5, -4, 0, 5, 4, 0,
+ -5, -4, 0, 5, 4, 0, -5, 4, 0,
+ ], 3));
+ geom.computeVertexNormals();
+
+ const face = new BREP.Face(geom);
+ face.name = "SKETCH_BEZIER:PROFILE";
+ face.edges = edges;
+ face.userData.boundaryLoopsWorld = [
+ { pts: loop, isHole: false, segmentIds: ["L0", "L1", "B0", "L2"] },
+ ];
+
+ const extrude = new BREP.Sweep({ face, distance: 4, name: "EXTRUDE_BEZIER" });
+ extrude.visualize();
+
+ const sideFaces = extrude.children.filter((child) => child?.type === "FACE" && /_SW(?:_\d+)?$/.test(String(child.name || "")));
+ assert(sideFaces.length === 4, `Expected three line sidewalls plus one Bezier sidewall, got ${sideFaces.length}.`);
+ const bezierSide = sideFaces.find((side) => side.name.endsWith("_SW_2"));
+ assert(bezierSide, "Expected Bezier sidewall face.");
+ assert(bezierSide.edges.length === 4, `Expected Bezier sidewall to have 4 topology edges, got ${bezierSide.edges.length}.`);
+}
+
+export async function test_cppRevolve_extruded_sketch_cap_reuses_analytic_profile() {
+ if (shouldSkip()) return;
+
+ const makeEdge = (name, curveType, points, extra = {}) => {
+ const line = new LineGeometry();
+ line.setPositions(points.flat());
+ const edge = new BREP.Edge(line);
+ edge.name = name;
+ edge.userData = {
+ polylineLocal: points,
+ polylineWorld: true,
+ sketchGeomType: curveType,
+ sketchGeometryId: name,
+ ...extra,
+ };
+ return edge;
+ };
+
+ const p0 = [-5, -4, 0];
+ const p1 = [5, -4, 0];
+ const p2 = [5, 4, 0];
+ const b0 = [5, 4, 0];
+ const b1 = [2, 7, 0];
+ const b2 = [-2, 7, 0];
+ const b3 = [-5, 4, 0];
+ const p3 = [-5, 4, 0];
+ const bezierSamples = [];
+ for (let i = 0; i <= 32; i += 1) {
+ const t = i / 32;
+ const mt = 1 - t;
+ bezierSamples.push([
+ mt * mt * mt * b0[0] + 3 * mt * mt * t * b1[0] + 3 * mt * t * t * b2[0] + t * t * t * b3[0],
+ mt * mt * mt * b0[1] + 3 * mt * mt * t * b1[1] + 3 * mt * t * t * b2[1] + t * t * t * b3[1],
+ 0,
+ ]);
+ }
+
+ const radius = 1.2;
+ const hole = [];
+ for (let i = 0; i < 32; i += 1) {
+ const t = (i / 32) * Math.PI * 2;
+ hole.push([radius * Math.cos(t), radius * Math.sin(t), 0]);
+ }
+
+ const loop = [p0, p1, p2, ...bezierSamples.slice(1, -1), p3];
+ const edges = [
+ makeEdge("L0", "line", [p0, p1]),
+ makeEdge("L1", "line", [p1, p2]),
+ makeEdge("B0", "bezier", bezierSamples, { bezierPoles: [b0, b1, b2, b3] }),
+ makeEdge("L2", "line", [p3, p0]),
+ makeEdge("C0", "circle", hole.concat([hole[0]]), { circleCenter: [0, 0, 0], circleRadius: radius, isHole: true }),
+ ];
+
+ const geom = new BREP.THREE.BufferGeometry();
+ geom.setAttribute("position", new BREP.THREE.Float32BufferAttribute([
+ -5, -4, 0, 5, -4, 0, 5, 4, 0,
+ -5, -4, 0, 5, 4, 0, -5, 4, 0,
+ ], 3));
+ geom.computeVertexNormals();
+
+ const face = new BREP.Face(geom);
+ face.name = "REVOLVE_SRC:PROFILE";
+ face.edges = edges;
+ face.userData.boundaryLoopsWorld = [
+ { pts: loop, isHole: false, segmentIds: ["L0", "L1", "B0", "L2"] },
+ { pts: hole, isHole: true, segmentIds: ["C0"] },
+ ];
+
+ const extrude = new BREP.Sweep({ face, distance: 4, name: "EXTRUDE_REVOLVE_SRC" });
+ extrude.visualize();
+ const startCap = extrude.children.find((child) => child?.type === "FACE" && child.name === "EXTRUDE_REVOLVE_SRC:REVOLVE_SRC:PROFILE_START");
+ assert(startCap, "Expected extrude start cap to be selectable.");
+ assert(Array.isArray(startCap.userData?.boundaryLoopsWorld) && startCap.userData.boundaryLoopsWorld.length === 2, "Expected extrude cap to preserve analytic boundary loops.");
+ const storedTypes = (startCap.userData.sketchEdgeInputsWorld || []).map((entry) => entry.curveType).sort();
+ assert(storedTypes.includes("bezier") && storedTypes.includes("circle"), "Expected extrude cap to preserve Bezier and circle edge inputs.");
+
+ const axis = startCap.edges.find((edge) => edge?.name && String(edge.name).includes("L1")) || startCap.edges[0];
+ const revolve = new BREP.Revolve({ face: startCap, axis, angle: 103, name: "REVOLVE_FROM_EXTRUDE_CAP" });
+ revolve.visualize();
+ const sideNames = revolve.getFaceNames().filter((name) => String(name || "").endsWith("_RV"));
+ assert(sideNames.length >= 3, `Expected revolve to create multiple sidewall face labels, got ${sideNames.length}.`);
+ const capMeta = revolve.getFaceMetadata("EXTRUDE_REVOLVE_SRC:REVOLVE_SRC:PROFILE_START_START");
+ assert(Array.isArray(capMeta?.boundaryLoopsWorld) && capMeta.boundaryLoopsWorld.length === 2, "Expected revolve cap to retain analytic loops.");
+ const revolvedTypes = (capMeta.sketchEdgeInputsWorld || []).map((entry) => entry.curveType).sort();
+ assert(revolvedTypes.includes("bezier") && revolvedTypes.includes("circle"), "Expected revolve to reuse analytic Bezier and circle inputs.");
+}
+
export async function test_cppPrimitive_cone_preserves_expected_face_labels_and_metadata() {
if (shouldSkip()) return;
diff --git a/src/tests/test_cppTube.js b/src/tests/test_cppTube.js
index de173508..621f6751 100644
--- a/src/tests/test_cppTube.js
+++ b/src/tests/test_cppTube.js
@@ -1,19 +1,13 @@
-import { Tube, tubeHasNativeBuilder } from "../BREP/Tube.js";
-import { manifoldBuildSource } from "../BREP/setupManifold.js";
+import { Tube } from "../BREP/Tube.js";
function assert(condition, message) {
if (!condition) throw new Error(message || "Assertion failed.");
}
export async function test_cppTube_open_tube_preserves_expected_face_labels() {
- if (manifoldBuildSource !== "local" || !tubeHasNativeBuilder()) {
- return;
- }
-
const tube = new Tube({
points: [[0, 0, 0], [0, 20, 0], [10, 20, 0]],
radius: 2,
- resolution: 24,
closed: false,
name: "CPP_TUBE_OPEN",
});
@@ -27,15 +21,10 @@ export async function test_cppTube_open_tube_preserves_expected_face_labels() {
}
export async function test_cppTube_closed_hollow_tube_preserves_expected_face_labels() {
- if (manifoldBuildSource !== "local" || !tubeHasNativeBuilder()) {
- return;
- }
-
const tube = new Tube({
points: [[0, 0, 0], [20, 0, 0], [20, 20, 0], [0, 20, 0], [0, 0, 0]],
radius: 3,
innerRadius: 1,
- resolution: 24,
closed: true,
name: "CPP_TUBE_CLOSED",
});
@@ -50,22 +39,32 @@ export async function test_cppTube_closed_hollow_tube_preserves_expected_face_la
assert(tube.getTriangleCount() > 0, "Expected closed native tube to contain triangles.");
}
-export async function test_cppTube_union_preserves_distinct_face_labels_across_native_snapshots() {
- if (manifoldBuildSource !== "local" || !tubeHasNativeBuilder()) {
- return;
- }
+export async function test_cppTube_hollow_tube_visualizes_distinct_inner_and_outer_faces() {
+ const tube = new Tube({
+ points: [[0, 0, 0], [0, 20, 0], [10, 20, 0]],
+ radius: 3,
+ innerRadius: 1,
+ closed: false,
+ name: "CPP_TUBE_HOLLOW_DISTINCT",
+ });
+
+ tube.visualize({ showEdges: true });
+ const faceMeshes = tube.children.filter((child) => child?.type === "FACE");
+ const faceNames = new Set(faceMeshes.map((child) => child.name));
+ assert(faceNames.has("CPP_TUBE_HOLLOW_DISTINCT_Outer"), "Expected hollow tube to visualize an Outer sidewall face.");
+ assert(faceNames.has("CPP_TUBE_HOLLOW_DISTINCT_Inner"), "Expected hollow tube to visualize an Inner sidewall face.");
+}
+export async function test_cppTube_union_preserves_distinct_face_labels_across_native_snapshots() {
const tubeA = new Tube({
points: [[0, 0, 0], [0, 12, 0]],
radius: 1.5,
- resolution: 24,
closed: false,
name: "CPP_TUBE_UNION_A",
});
const tubeB = new Tube({
points: [[20, 0, 0], [20, 12, 0]],
radius: 1.5,
- resolution: 24,
closed: false,
name: "CPP_TUBE_UNION_B",
});
@@ -78,57 +77,77 @@ export async function test_cppTube_union_preserves_distinct_face_labels_across_n
assert(faceNames.has("CPP_TUBE_UNION_B_CapEnd"), "Expected unioned native tubes to preserve tube B CapEnd face.");
}
-export async function test_cppTube_native_builder_reports_selected_build_mode() {
- if (manifoldBuildSource !== "local" || !tubeHasNativeBuilder()) {
- return;
- }
-
- const points = [[0, 0, 0], [0, 20, 0]];
+export async function test_cppTube_hollow_spline_path_visualizes_side_faces() {
+ const pathCurve = {
+ type: "hermite-extension-spline",
+ bendRadius: 1,
+ spline: {
+ points: [
+ { id: "p0", position: [0, 0, 0], rotation: [1, 0, 0, 0, 1, 0, 0, 0, 1], forwardDistance: 10, backwardDistance: 1, flipDirection: false },
+ { id: "p1", position: [20, 10, 0], rotation: [0, 1, 0, -1, 0, 0, 0, 0, 1], forwardDistance: 10, backwardDistance: 10, flipDirection: false },
+ { id: "p2", position: [40, 0, 10], rotation: [1, 0, 0, 0, 1, 0, 0, 0, 1], forwardDistance: 1, backwardDistance: 10, flipDirection: false },
+ ],
+ },
+ };
const tube = new Tube({
- points,
- radius: 2,
- resolution: 24,
- closed: false,
- name: "CPP_TUBE_MODE",
- preferFast: true,
+ points: [[0, 0, 0], [10, 0, 0], [20, 10, 0], [20, 0, 0], [30, 0, 5], [40, 0, 10]],
+ radius: 1,
+ innerRadius: 0.8,
+ name: "CPP_TUBE_SPLINE",
+ pathCurve,
});
- assert(tube._tubeBuildMode === "fast", `Expected default tube generate() path to use native build-mode annotation, got ${tube._tubeBuildMode}.`);
-
- const fastSnapshot = tube.buildNativeSnapshot({ preferFast: true, allowSlowFallback: false });
- assert(fastSnapshot?.buildMode === "fast", `Expected explicit fast native tube build, got ${fastSnapshot?.buildMode}.`);
- assert(fastSnapshot?.requestedFast === true, "Expected explicit fast native tube build to record requestedFast=true.");
- assert(fastSnapshot?.fallbackFromFast === false, "Did not expect explicit fast native tube build to mark fallbackFromFast.");
-
- const slowSnapshot = tube.buildNativeSnapshot({ preferFast: false });
- assert(slowSnapshot?.buildMode === "slow", `Expected explicit slow native tube build, got ${slowSnapshot?.buildMode}.`);
- assert(slowSnapshot?.requestedFast === false, "Expected explicit slow native tube build to record requestedFast=false.");
- assert(slowSnapshot?.fallbackFromFast === false, "Did not expect explicit slow native tube build to mark fallbackFromFast.");
+ tube.visualize({ showEdges: true });
+ const faceMeshes = tube.children.filter((child) => child?.type === "FACE");
+ const sideFace = faceMeshes.find((child) => (child?.geometry?.index?.count || 0) > 300);
+ assert(faceMeshes.length >= 3, "Expected hollow spline tube to visualize cap and side face meshes.");
+ assert(sideFace, "Expected hollow spline tube to visualize a side face mesh, not just end rings.");
+ assert(sideFace.material?.side === 2, "Expected OpenCascade tube side face to render double-sided.");
}
-export async function test_cppTube_native_auto_falls_back_to_slow_on_foldback_path() {
- if (manifoldBuildSource !== "local" || !tubeHasNativeBuilder()) {
- return;
+export async function test_cppTube_spline_solid_has_three_faces_and_no_cross_section_edges() {
+ const points = [];
+ for (let i = 0; i < 35; i++) {
+ const t = i / 34;
+ points.push([
+ Math.sin(t * Math.PI * 3) * 8,
+ t * 80,
+ Math.cos(t * Math.PI * 1.5) * 2,
+ ]);
}
-
- const points = [[0, 0, 0], [10, 0, 0], [10, 2, 0], [0, 2, 0]];
- const autoTube = new Tube({
+ const tube = new Tube({
points,
- radius: 1.5,
- resolution: 24,
- closed: false,
- name: "CPP_TUBE_FOLDBACK",
- preferFast: true,
+ radius: 1,
+ innerRadius: 0,
+ name: "CPP_TUBE_SPLINE_SOLID",
+ pathCurve: {
+ type: "hermite-extension-spline",
+ spline: { points: [{ position: points[0] }, { position: points[points.length - 1] }] },
+ },
});
- assert(autoTube._tubeBuildMode === "slow", `Expected native auto tube build to fall back to slow for foldback path, got ${autoTube._tubeBuildMode}.`);
-
- const autoSnapshot = autoTube.buildNativeSnapshot({ preferFast: true, allowSlowFallback: true });
- assert(autoSnapshot?.buildMode === "slow", `Expected native auto tube snapshot to fall back to slow, got ${autoSnapshot?.buildMode}.`);
- assert(autoSnapshot?.fallbackFromFast === true, "Expected native auto tube snapshot to record fallbackFromFast=true.");
- assert(autoSnapshot?.fallbackReason === "path_foldback_proximity", `Expected path_foldback_proximity fallback, got ${autoSnapshot?.fallbackReason}.`);
-
- const forcedFastSnapshot = autoTube.buildNativeSnapshot({ preferFast: true, allowSlowFallback: false });
- assert(forcedFastSnapshot?.buildMode === "fast", `Expected explicit force-fast tube build, got ${forcedFastSnapshot?.buildMode}.`);
- assert(forcedFastSnapshot?.selfUnionStats?.pathFoldbackLikely === true, "Expected force-fast tube build to expose the same pathFoldbackLikely signal.");
+ tube.visualize({ showEdges: true });
+ const faceNames = tube.getFaceNames();
+ assert(faceNames.length === 3, `Expected non-hollow spline tube to have exactly 3 faces, got ${faceNames.join(", ")}.`);
+ assert(faceNames.includes("CPP_TUBE_SPLINE_SOLID_Outer"), "Expected solid spline tube outer face.");
+ assert(faceNames.includes("CPP_TUBE_SPLINE_SOLID_CapStart"), "Expected solid spline tube start cap.");
+ assert(faceNames.includes("CPP_TUBE_SPLINE_SOLID_CapEnd"), "Expected solid spline tube end cap.");
+
+ const faceMeshes = tube.children.filter((child) => child?.type === "FACE");
+ assert(faceMeshes.length === 3, `Expected exactly 3 visible face meshes, got ${faceMeshes.length}.`);
+ const crossSectionEdges = tube.children.filter((child) => child?.type === "EDGE" && String(child.name || "").includes("_Outer|CPP_TUBE_SPLINE_SOLID_Outer"));
+ assert(crossSectionEdges.length === 0, "Did not expect same-face cross-section edges on a smooth spline tube.");
+
+ const startCap = faceMeshes.find((child) => child.name === "CPP_TUBE_SPLINE_SOLID_CapStart");
+ const tangent = [
+ points[1][0] - points[0][0],
+ points[1][1] - points[0][1],
+ points[1][2] - points[0][2],
+ ];
+ const tangentLength = Math.hypot(...tangent);
+ const normal = startCap?.getAverageNormal?.();
+ const dot = Math.abs(
+ ((normal?.x || 0) * tangent[0] + (normal?.y || 0) * tangent[1] + (normal?.z || 0) * tangent[2]) / tangentLength,
+ );
+ assert(dot > 0.85, `Expected start cap normal to align with path tangent; dot=${dot}.`);
}
diff --git a/src/tests/test_edge_smooth_curve_fit.js b/src/tests/test_edge_smooth_curve_fit.js
deleted file mode 100644
index 60ab9f4f..00000000
--- a/src/tests/test_edge_smooth_curve_fit.js
+++ /dev/null
@@ -1,348 +0,0 @@
-import {
- fitAndSnapOpenEdgePolyline,
- fitAndSnapClosedEdgePolyline,
- hasLocalBacktrackingAgainstSource,
-} from "../features/edgeSmooth/edgeCurveFit.js";
-import { applyConstrainedVertexTargets } from "../features/edgeSmooth/vertexTargetConstraints.js";
-import { EdgeSmoothFeature } from "../features/edgeSmooth/EdgeSmoothFeature.js";
-
-function pointDistanceSq(a, b) {
- const dx = a[0] - b[0];
- const dy = a[1] - b[1];
- const dz = a[2] - b[2];
- return (dx * dx) + (dy * dy) + (dz * dz);
-}
-
-function assertPointNear(actual, expected, eps = 1e-12) {
- if (pointDistanceSq(actual, expected) > (eps * eps)) {
- throw new Error(`Expected point ${JSON.stringify(actual)} near ${JSON.stringify(expected)} (eps=${eps})`);
- }
-}
-
-export async function test_edge_smooth_curve_fit() {
- const source = [
- [0, 0, 0],
- [1, 0.35, 0],
- [2, -0.5, 0],
- [3, 0.45, 0],
- [4, 0, 0],
- ];
-
- const fitted = fitAndSnapOpenEdgePolyline(source, { fitStrength: 1 });
- if (!Array.isArray(fitted) || fitted.length !== source.length) {
- throw new Error("Curve fit should return one output point per input point.");
- }
-
- assertPointNear(fitted[0], source[0], 1e-12);
- assertPointNear(fitted[fitted.length - 1], source[source.length - 1], 1e-12);
-
- if (hasLocalBacktrackingAgainstSource(source, fitted)) {
- throw new Error("Fitted polyline should not locally reverse direction against the source edge.");
- }
-
- let movedInteriorCount = 0;
- for (let i = 1; i < source.length - 1; i++) {
- if (pointDistanceSq(source[i], fitted[i]) > 1e-12) movedInteriorCount++;
- }
- if (movedInteriorCount <= 0) {
- throw new Error("At least one interior point should be snapped to the fitted curve.");
- }
-
- const unchanged = fitAndSnapOpenEdgePolyline(source, { fitStrength: 0 });
- for (let i = 0; i < source.length; i++) {
- assertPointNear(unchanged[i], source[i], 1e-12);
- }
-}
-
-function hasLocalBacktrackingAgainstSourceClosed(sourcePoints, candidatePoints) {
- const source = Array.isArray(sourcePoints) ? sourcePoints : [];
- const candidate = Array.isArray(candidatePoints) ? candidatePoints : [];
- const count = source.length;
- if (count !== candidate.length || count < 3) return true;
- for (let i = 0; i < count; i++) {
- const next = (i + 1) % count;
- const s0 = source[i];
- const s1 = source[next];
- const c0 = candidate[i];
- const c1 = candidate[next];
- const srcSeg = [s1[0] - s0[0], s1[1] - s0[1], s1[2] - s0[2]];
- const candSeg = [c1[0] - c0[0], c1[1] - c0[1], c1[2] - c0[2]];
- const srcLen = Math.hypot(srcSeg[0], srcSeg[1], srcSeg[2]);
- const candLen = Math.hypot(candSeg[0], candSeg[1], candSeg[2]);
- if (!(srcLen > 1e-12) || !(candLen > 1e-12)) continue;
- const cos = ((srcSeg[0] * candSeg[0]) + (srcSeg[1] * candSeg[1]) + (srcSeg[2] * candSeg[2])) / (srcLen * candLen);
- if (cos < -1e-6) return true;
- }
- return false;
-}
-
-export async function test_edge_smooth_curve_fit_closed_loop() {
- const source = [
- [1.0, 0.0, 0.0],
- [0.45, 0.95, 0.02],
- [-0.55, 0.8, -0.03],
- [-1.0, 0.0, 0.0],
- [-0.45, -0.85, 0.03],
- [0.55, -0.75, -0.02],
- ];
-
- const fitted = fitAndSnapClosedEdgePolyline(source, { fitStrength: 1 });
- if (!Array.isArray(fitted) || fitted.length !== source.length) {
- throw new Error("Closed-loop curve fit should return one output point per input point.");
- }
-
- if (hasLocalBacktrackingAgainstSourceClosed(source, fitted)) {
- throw new Error("Closed-loop fitted polyline should not locally reverse direction.");
- }
-
- let movedCount = 0;
- for (let i = 0; i < source.length; i++) {
- if (pointDistanceSq(source[i], fitted[i]) > 1e-12) movedCount++;
- }
- if (movedCount <= 0) {
- throw new Error("Closed-loop fit should move at least one loop point.");
- }
-
- const unchanged = fitAndSnapClosedEdgePolyline(source, { fitStrength: 0 });
- for (let i = 0; i < source.length; i++) {
- assertPointNear(unchanged[i], source[i], 1e-12);
- }
-}
-
-export async function test_edge_smooth_constraints_prevent_triangle_foldback() {
- const vp = [
- 0, 0, 0,
- 1, 0, 0,
- 1, 1, 0,
- 0, 1, 0,
- ];
- const tv = [
- 0, 1, 2,
- 0, 2, 3,
- ];
-
- const targetMap = new Map();
- targetMap.set(1, { x: 0.2, y: 0.8, z: 0, count: 1 });
-
- const res = applyConstrainedVertexTargets(vp, tv, targetMap, {
- minArea2Ratio: 0.04,
- minNormalDot: 0.1,
- minArea2Abs: 1e-24,
- });
-
- if ((Number(res?.movedVertices) || 0) !== 1) {
- throw new Error("Constrained edge smoothing should move the target vertex.");
- }
- if ((Number(res?.constrainedVertices) || 0) <= 0) {
- throw new Error("Constrained edge smoothing should scale back fold-causing targets.");
- }
-
- const x = vp[3];
- const y = vp[4];
- if (!Number.isFinite(x) || !Number.isFinite(y)) {
- throw new Error("Constrained edge smoothing produced invalid coordinates.");
- }
- if (Math.abs(x - 0.2) < 1e-9 && Math.abs(y - 0.8) < 1e-9) {
- throw new Error("Constrained smoothing should not apply a fold-causing target at full displacement.");
- }
-
- const triNormalZ = (ia, ib, ic) => {
- const a = ia * 3;
- const b = ib * 3;
- const c = ic * 3;
- const ux = vp[b + 0] - vp[a + 0];
- const uy = vp[b + 1] - vp[a + 1];
- const vx = vp[c + 0] - vp[a + 0];
- const vy = vp[c + 1] - vp[a + 1];
- return (ux * vy) - (uy * vx);
- };
-
- if (!(triNormalZ(0, 1, 2) > 0)) {
- throw new Error("Primary triangle normal flipped after constrained smoothing.");
- }
- if (!(triNormalZ(0, 2, 3) > 0)) {
- throw new Error("Adjacent triangle normal flipped after constrained smoothing.");
- }
-}
-
-function makeMockSolidFromPolyline(polylinePoints) {
- const sourcePolyline = Array.isArray(polylinePoints)
- ? polylinePoints.map((p) => [p[0], p[1], p[2]])
- : [];
- const vertProperties = [];
- for (const p of sourcePolyline) {
- vertProperties.push(p[0], p[1], p[2]);
- }
-
- const createSolid = (vpSeed, polySeed) => {
- const vp = vpSeed.slice();
- const poly = polySeed.map((p) => [p[0], p[1], p[2]]);
- const edge = {
- type: "EDGE",
- name: "MOCK_EDGE_0",
- userData: {
- faceA: "FACE_A",
- faceB: "FACE_B",
- polylineLocal: poly.map((p) => [p[0], p[1], p[2]]),
- },
- parent: null,
- parentSolid: null,
- };
-
- const solid = {
- type: "SOLID",
- name: "MOCK_SOLID",
- _vertProperties: vp,
- _triVerts: [0, 1, 2, 0, 2, 3, 0, 3, 4],
- _triIDs: [0, 0, 0],
- _vertKeyToIndex: new Map(),
- _dirty: false,
- _faceIndex: null,
- _manifold: null,
- traverse(visitor) {
- if (typeof visitor === "function") visitor(edge);
- },
- visualize() { },
- _manifoldize() { },
- getBoundaryEdgePolylines() {
- return [{
- name: edge.name,
- faceA: edge.userData.faceA,
- faceB: edge.userData.faceB,
- indices: [0, 1, 2, 3, 4],
- positions: poly.map((p) => [p[0], p[1], p[2]]),
- closedLoop: false,
- }];
- },
- clone() {
- return createSolid(vp, poly);
- },
- };
-
- const face = {
- type: "FACE",
- name: "MOCK_FACE_0",
- edges: [edge],
- parent: solid,
- parentSolid: solid,
- userData: { faceName: "MOCK_FACE_0" },
- };
-
- edge.parent = solid;
- edge.parentSolid = solid;
- solid.__testFace = face;
- return solid;
- };
-
- return createSolid(vertProperties, sourcePolyline);
-}
-
-export async function test_edge_smooth_whole_solid_selection() {
- const source = [
- [0, 0, 0],
- [1, 0.4, 0],
- [2, -0.45, 0],
- [3, 0.35, 0],
- [4, 0, 0],
- ];
- const sourceSolid = makeMockSolidFromPolyline(source);
-
- const feature = new EdgeSmoothFeature();
- feature.inputParams = {
- edges: [sourceSolid],
- fitStrength: 1,
- id: "EDGE_SMOOTH_SOLID_TEST",
- };
-
- const result = await feature.run();
- if (!result || !Array.isArray(result.added) || result.added.length !== 1) {
- throw new Error("EdgeSmoothFeature should add one smoothed solid when selecting a whole solid.");
- }
- if (!Array.isArray(result.removed) || result.removed.length !== 1 || result.removed[0] !== sourceSolid) {
- throw new Error("EdgeSmoothFeature should remove exactly the selected source solid.");
- }
-
- const outSolid = result.added[0];
- const vp = Array.isArray(outSolid?._vertProperties) ? outSolid._vertProperties : [];
- if (vp.length < 15) {
- throw new Error("Smoothed solid is missing expected vertex properties.");
- }
-
- const outPolyline = [];
- for (let i = 0; i < 5; i++) {
- const base = i * 3;
- outPolyline.push([vp[base + 0], vp[base + 1], vp[base + 2]]);
- }
-
- assertPointNear(outPolyline[0], source[0], 1e-12);
- assertPointNear(outPolyline[outPolyline.length - 1], source[source.length - 1], 1e-12);
-
- if (hasLocalBacktrackingAgainstSource(source, outPolyline)) {
- throw new Error("Whole-solid smoothing should not create local edge backtracking.");
- }
-
- let movedInteriorCount = 0;
- for (let i = 1; i < source.length - 1; i++) {
- if (pointDistanceSq(source[i], outPolyline[i]) > 1e-12) movedInteriorCount++;
- }
- if (movedInteriorCount <= 0) {
- throw new Error("Whole-solid smoothing should move at least one interior edge point.");
- }
-
- if (!feature.persistentData || feature.persistentData.selectedSolidCount < 1) {
- throw new Error("Feature metadata should record at least one selected solid.");
- }
-}
-
-export async function test_edge_smooth_face_selection() {
- const source = [
- [0, 0, 0],
- [1, 0.3, 0],
- [2, -0.35, 0],
- [3, 0.25, 0],
- [4, 0, 0],
- ];
- const sourceSolid = makeMockSolidFromPolyline(source);
- const sourceFace = sourceSolid.__testFace;
- if (!sourceFace || sourceFace.type !== "FACE") {
- throw new Error("Mock setup should provide a face selection target.");
- }
-
- const feature = new EdgeSmoothFeature();
- feature.inputParams = {
- edges: [sourceFace],
- fitStrength: 1,
- id: "EDGE_SMOOTH_FACE_TEST",
- };
-
- const result = await feature.run();
- if (!result || !Array.isArray(result.added) || result.added.length !== 1) {
- throw new Error("EdgeSmoothFeature should add one smoothed solid when selecting a face.");
- }
- if (!Array.isArray(result.removed) || result.removed.length !== 1 || result.removed[0] !== sourceSolid) {
- throw new Error("EdgeSmoothFeature should remove exactly the source solid for face selection.");
- }
-
- const outSolid = result.added[0];
- const vp = Array.isArray(outSolid?._vertProperties) ? outSolid._vertProperties : [];
- if (vp.length < 15) {
- throw new Error("Face-selected smoothing output is missing expected vertex properties.");
- }
-
- const outPolyline = [];
- for (let i = 0; i < 5; i++) {
- const base = i * 3;
- outPolyline.push([vp[base + 0], vp[base + 1], vp[base + 2]]);
- }
-
- assertPointNear(outPolyline[0], source[0], 1e-12);
- assertPointNear(outPolyline[outPolyline.length - 1], source[source.length - 1], 1e-12);
-
- if (hasLocalBacktrackingAgainstSource(source, outPolyline)) {
- throw new Error("Face-selected smoothing should not create local edge backtracking.");
- }
-
- if (!feature.persistentData || feature.persistentData.selectedFaceCount < 1) {
- throw new Error("Feature metadata should record at least one selected face.");
- }
-}
diff --git a/src/tests/test_fillet_corner_bridge.js b/src/tests/test_fillet_corner_bridge.js
index 50dc181c..e3406352 100644
--- a/src/tests/test_fillet_corner_bridge.js
+++ b/src/tests/test_fillet_corner_bridge.js
@@ -1,5 +1,6 @@
export async function test_fillet_corner_bridge(partHistory) {
const cube = await partHistory.newFeature("P.CU");
+ cube.inputParams.id = "P.CU1";
cube.inputParams.sizeX = 10;
cube.inputParams.sizeY = 10;
cube.inputParams.sizeZ = 10;
@@ -7,8 +8,8 @@ export async function test_fillet_corner_bridge(partHistory) {
const fillet = await partHistory.newFeature("F");
fillet.inputParams.edges = [
`${cube.inputParams.featureID}_NX|${cube.inputParams.featureID}_NY[0]`,
- `${cube.inputParams.featureID}_NX|${cube.inputParams.featureID}_PZ[0]`,
- `${cube.inputParams.featureID}_NX|${cube.inputParams.featureID}_PY[0]`,
+ `${cube.inputParams.featureID}_NX|${cube.inputParams.featureID}_NZ[0]`,
+ `${cube.inputParams.featureID}_NY|${cube.inputParams.featureID}_NZ[0]`,
];
fillet.inputParams.radius = 1.2;
fillet.inputParams.direction = "INSET";
@@ -48,15 +49,40 @@ export async function afterRun_fillet_corner_bridge(partHistory) {
}
if (filletSolid) break;
}
- if (!filletSolid || typeof filletSolid._manifoldize !== "function") {
- throw new Error("Corner-bridge fillet did not produce a manifold-capable solid.");
+ if (!filletSolid || typeof filletSolid.getMesh !== "function") {
+ throw new Error("Corner-bridge fillet did not produce a mesh-capable solid.");
}
+ const faceNames = filletSolid.getFaceNames();
+ const fallbackFilletFaces = faceNames.filter((name) => /^F\d+_FILLET_FACE_/.test(name));
+ if (fallbackFilletFaces.length > 0) {
+ throw new Error(`Trihedral corner fillet should use selected edge names, found fallback labels: ${fallbackFilletFaces.join(", ")}`);
+ }
+ const missingEdgeFaces = (filletFeature.inputParams.edges || []).filter((name) => !faceNames.includes(name));
+ if (missingEdgeFaces.length > 0) {
+ throw new Error(`Trihedral corner fillet is missing edge-derived face labels: ${missingEdgeFaces.join(", ")}`);
+ }
+ const expectedCornerFaceName = (filletFeature.inputParams.edges || [])
+ .map((name) => String(name || "").trim())
+ .filter(Boolean)
+ .sort()
+ .join("+");
+ if (!faceNames.includes(expectedCornerFaceName)) {
+ throw new Error(`Trihedral spherical corner face should combine its three edge labels as "${expectedCornerFaceName}".`);
+ }
+
+ let mesh = null;
try {
- filletSolid._manifoldize();
+ mesh = filletSolid.getMesh();
+ const triCount = (mesh.triVerts.length / 3) | 0;
+ if (triCount <= 0) {
+ throw new Error(`triangles=${triCount}`);
+ }
} catch (error) {
- const message = String(error?.message || error || "Unknown manifold error");
- throw new Error(`Corner-bridge fillet manifoldization failed: ${message}`);
+ const message = String(error?.message || error || "Unknown mesh error");
+ throw new Error(`Corner-bridge fillet mesh generation failed: ${message}`);
+ } finally {
+ try { if (mesh && typeof mesh.delete === "function") mesh.delete(); } catch { }
}
console.log(`✓ Fillet corner bridge test passed: bridges=${cornerBridgeCount}`);
diff --git a/src/tests/test_fillet_occ_closed_extrude_edge_history.js b/src/tests/test_fillet_occ_closed_extrude_edge_history.js
new file mode 100644
index 00000000..b45c3f26
--- /dev/null
+++ b/src/tests/test_fillet_occ_closed_extrude_edge_history.js
@@ -0,0 +1,119 @@
+export async function test_fillet_occ_closed_extrude_edge_history(partHistory) {
+ partHistory.expressions = "//Examples:\nx = 10 + 6; \ny = x * 2;\n\nresolution = 32;\n";
+ partHistory.configurator = { fields: [], values: {} };
+
+ const cube = await partHistory.newFeature("P.CU");
+ Object.assign(cube.inputParams, {
+ id: "P.CU1",
+ sizeX: 10,
+ sizeY: 10,
+ sizeZ: 10,
+ transform: {
+ position: [0, 0, 0],
+ rotationEuler: [0, 0, 0],
+ scale: [1, 1, 1],
+ },
+ boolean: {
+ targets: [],
+ operation: "NONE",
+ overlapConditioningEnabled: true,
+ },
+ });
+
+ const sketch = await partHistory.newFeature("S");
+ Object.assign(sketch.inputParams, {
+ id: "S5",
+ sketchPlane: "P.CU1_PY",
+ editSketch: null,
+ dumpSketchDiagnostics: null,
+ curveResolution: "resolution",
+ });
+ sketch.persistentData = {
+ sketch: {
+ points: [
+ { id: 0, x: 0, y: 0, fixed: true, construction: true, externalReference: false },
+ { id: 1, x: 1.002607, y: -1.788009, fixed: false, construction: false, externalReference: false },
+ ],
+ geometries: [
+ { id: 1, type: "circle", points: [0, 1], construction: false },
+ ],
+ constraints: [
+ {
+ id: 0,
+ type: "⏚",
+ points: [0],
+ status: "solved",
+ error: null,
+ _previousSolveValue: null,
+ previousPointValues: "0:0,0,1;",
+ },
+ ],
+ },
+ };
+
+ const extrude = await partHistory.newFeature("E");
+ Object.assign(extrude.inputParams, {
+ id: "E6",
+ profile: "S5:PROFILE",
+ consumeProfileSketch: true,
+ distance: 10,
+ distanceBack: 5.9,
+ boolean: {
+ targets: ["P.CU1"],
+ operation: "UNION",
+ overlapConditioningEnabled: true,
+ },
+ });
+
+ const fillet = await partHistory.newFeature("F");
+ Object.assign(fillet.inputParams, {
+ id: "F4",
+ edges: [
+ "E6:S5:G1_SW|P.CU1_PY[0]",
+ "E6:S5:PROFILE_END",
+ "P.CU1_NZ|P.CU1_PY[0]",
+ "P.CU1_NX|P.CU1_PY[0]",
+ "P.CU1_PY|P.CU1_PZ[0]",
+ "P.CU1_PX|P.CU1_PY[0]",
+ ],
+ radius: ".5",
+ });
+
+ return partHistory;
+}
+
+export async function afterRun_fillet_occ_closed_extrude_edge_history(partHistory) {
+ const solids = [];
+ partHistory.scene.traverse((obj) => {
+ if (obj?.type === "SOLID") solids.push(obj);
+ });
+ const result = solids.find((solid) => solid?.owningFeatureID === "F4");
+ if (!result) throw new Error("Expected closed extrude-edge fillet history to produce the F4 result solid.");
+
+ const faceNames = result.getFaceNames();
+ const fallbackFilletFaces = faceNames.filter((name) => /^F4_FILLET_FACE_/.test(name));
+ if (fallbackFilletFaces.length > 0) {
+ throw new Error(`Expected analytic OCC fillet faces to use source edge names, found fallback labels: ${fallbackFilletFaces.join(", ")}`);
+ }
+ const expectedEdgeFaceNames = [
+ "E6:S5:G1_SW|P.CU1_PY[0]",
+ "P.CU1_NZ|P.CU1_PY[0]",
+ "P.CU1_NX|P.CU1_PY[0]",
+ "P.CU1_PY|P.CU1_PZ[0]",
+ "P.CU1_PX|P.CU1_PY[0]",
+ ];
+ const missingEdgeNames = expectedEdgeFaceNames.filter((name) => !faceNames.includes(name));
+ if (missingEdgeNames.length > 0) {
+ throw new Error(`Expected analytic OCC fillet blend faces named from selected edges, missing: ${missingEdgeNames.join(", ")}`);
+ }
+ if (!faceNames.includes("P.CU1_PY") || !faceNames.includes("E6:S5:G1_SW") || !faceNames.includes("E6:S5:PROFILE_END")) {
+ throw new Error("Expected analytic OCC fillet to preserve adjacent source face names.");
+ }
+
+ const mesh = result.getMesh();
+ const triCount = (mesh.triVerts.length / 3) | 0;
+ try { if (mesh && typeof mesh.delete === "function") mesh.delete(); } catch { }
+ if (triCount < 500 || triCount > 1200) {
+ throw new Error(`Expected analytic OCC fillet tessellation, got ${triCount} triangles.`);
+ }
+}
diff --git a/src/tests/test_fillet_occ_top_loop_history.js b/src/tests/test_fillet_occ_top_loop_history.js
new file mode 100644
index 00000000..5873d824
--- /dev/null
+++ b/src/tests/test_fillet_occ_top_loop_history.js
@@ -0,0 +1,69 @@
+export async function test_fillet_occ_top_loop_history(partHistory) {
+ partHistory.expressions = "//Examples:\nx = 10 + 6; \ny = x * 2;\n\nresolution = 32;\n";
+ partHistory.configurator = { fields: [], values: {} };
+
+ const cube = await partHistory.newFeature("P.CU");
+ Object.assign(cube.inputParams, {
+ id: "P.CU1",
+ sizeX: 10,
+ sizeY: 10,
+ sizeZ: 10,
+ transform: {
+ position: [0, 0, 0],
+ rotationEuler: [0, 0, 0],
+ scale: [1, 1, 1],
+ },
+ boolean: {
+ targets: [],
+ operation: "NONE",
+ overlapConditioningEnabled: true,
+ },
+ });
+
+ const fillet = await partHistory.newFeature("F");
+ Object.assign(fillet.inputParams, {
+ id: "F2",
+ edges: [
+ "P.CU1_PY|P.CU1_PZ[0]",
+ "P.CU1_PX|P.CU1_PY[0]",
+ "P.CU1_NZ|P.CU1_PY[0]",
+ "P.CU1_NX|P.CU1_PY[0]",
+ ],
+ radius: 1,
+ });
+
+ return partHistory;
+}
+
+export async function afterRun_fillet_occ_top_loop_history(partHistory) {
+ const solids = [];
+ partHistory.scene.traverse((obj) => {
+ if (obj?.type === "SOLID") solids.push(obj);
+ });
+ const result = solids.find((solid) => solid?.owningFeatureID === "F2");
+ if (!result) throw new Error("Expected top-loop fillet history to produce the F2 result solid.");
+
+ const mesh = result.getMesh();
+ const triCount = (mesh.triVerts.length / 3) | 0;
+ const vertCount = (mesh.vertProperties.length / 3) | 0;
+ try { if (mesh && typeof mesh.delete === "function") mesh.delete(); } catch { }
+ if (triCount <= 0 || vertCount <= 0) {
+ throw new Error(`Expected OCC fillet result to tessellate, got triangles=${triCount}, vertices=${vertCount}.`);
+ }
+
+ const faceNames = result.getFaceNames();
+ const fallbackFilletFaces = faceNames.filter((name) => /^F2_FILLET_FACE_/.test(name));
+ if (fallbackFilletFaces.length > 0) {
+ throw new Error(`Expected OCC fillet faces to use source edge names, found fallback labels: ${fallbackFilletFaces.join(", ")}`);
+ }
+ const expectedEdgeFaceNames = [
+ "P.CU1_PY|P.CU1_PZ[0]",
+ "P.CU1_PX|P.CU1_PY[0]",
+ "P.CU1_NZ|P.CU1_PY[0]",
+ "P.CU1_NX|P.CU1_PY[0]",
+ ];
+ const missing = expectedEdgeFaceNames.filter((name) => !faceNames.includes(name));
+ if (missing.length > 0) {
+ throw new Error(`Expected OCC fillet blend faces named from selected edges, missing: ${missing.join(", ")}`);
+ }
+}
diff --git a/src/tests/test_history_features_basic.js b/src/tests/test_history_features_basic.js
index 200a41ca..2a18e88a 100644
--- a/src/tests/test_history_features_basic.js
+++ b/src/tests/test_history_features_basic.js
@@ -82,12 +82,6 @@ export async function test_history_features_basic(partHistory) {
revolve.inputParams.angle = 180;
revolve.inputParams.resolution = 32;
- const remeshBase = await partHistory.newFeature("P.CU");
- const remesh = await partHistory.newFeature("RM");
- remesh.inputParams.targetSolid = remeshBase.inputParams.featureID;
- remesh.inputParams.mode = "Simplify";
- remesh.inputParams.tolerance = 0.05;
-
const smoothSubdivisionBase = await partHistory.newFeature("P.CU");
const smoothSubdivision = await partHistory.newFeature("SWS");
smoothSubdivision.inputParams.targetSolid = smoothSubdivisionBase.inputParams.featureID;
@@ -105,11 +99,6 @@ export async function test_history_features_basic(partHistory) {
overlap.inputParams.targetSolid = overlapBase.inputParams.featureID;
overlap.inputParams.distance = 0.0005;
- const pushFaceBase = await partHistory.newFeature("P.CU");
- const pushFace = await partHistory.newFeature("PF");
- pushFace.inputParams.faces = [`${pushFaceBase.inputParams.featureID}_PZ`];
- pushFace.inputParams.distance = 0.5;
-
const thickenBase = await partHistory.newFeature("P.CU");
const thicken = await partHistory.newFeature("THK");
thicken.inputParams.face = `${thickenBase.inputParams.featureID}_PZ`;
@@ -170,6 +159,5 @@ export async function afterRun_history_features_basic(partHistory) {
requireFeatureObject("HX", "Helix");
requireFeatureObject("LOFT", "Loft");
requireFeatureObject("R", "Revolve");
- requireFeatureOwnedObject("PF", "Push Face");
requireFeatureOwnedObject("THK", "Thicken");
}
diff --git a/src/tests/test_hole.js b/src/tests/test_hole.js
index 2d555fef..376fb0b4 100644
--- a/src/tests/test_hole.js
+++ b/src/tests/test_hole.js
@@ -183,6 +183,68 @@ export async function afterRun_hole_thread_modeled(partHistory) {
}
}
+export async function test_hole_thread_modeled_point_thread_face_reference(partHistory) {
+ partHistory.expressions = "resolution = 32;\n";
+
+ const cube = await partHistory.newFeature("P.CU");
+ Object.assign(cube.inputParams, {
+ id: "P.CU1",
+ sizeX: 200,
+ sizeY: 200,
+ sizeZ: 50,
+ });
+
+ const sketch = await partHistory.newFeature("S");
+ Object.assign(sketch.inputParams, {
+ id: "S2",
+ sketchPlane: "P.CU1_PZ",
+ curveResolution: "resolution",
+ });
+ buildSketchWithPoints(sketch, [
+ [-41.206767, -26.831219],
+ [40.457565, -47.434602],
+ [37.086096, 44.344106],
+ [-47.575089, 46.966344],
+ ]);
+
+ const hole = await partHistory.newFeature("H");
+ Object.assign(hole.inputParams, {
+ id: "H3",
+ face: "S2:P2_THREAD_FACE:CAP_END|S2:P2_THREAD_FACE:FLANK_A[0]",
+ holeType: "THREADED",
+ depth: 10,
+ throughAll: false,
+ threadStandard: "ISO_METRIC",
+ threadDesignation: "M5x0.8",
+ threadMode: "MODELED",
+ threadSegmentsPerTurn: 32,
+ debugShowSolid: true,
+ boolean: {
+ targets: [],
+ operation: "SUBTRACT",
+ overlapConditioningEnabled: true,
+ },
+ });
+
+ return partHistory;
+}
+
+export async function afterRun_hole_thread_modeled_point_thread_face_reference(partHistory) {
+ const holeFeature = findHoleFeature(partHistory);
+ if (!holeFeature) throw new Error("[hole_thread_modeled_point_thread_face_reference] Hole feature missing");
+ const holes = holeFeature.persistentData?.holes || [];
+ if (holes.length !== 1) {
+ throw new Error(`[hole_thread_modeled_point_thread_face_reference] Expected one hole from S2:P2 reference, got ${holes.length}`);
+ }
+ const record = holes[0];
+ if (record?.sourceName !== "S2:P2") {
+ throw new Error(`[hole_thread_modeled_point_thread_face_reference] Expected sourceName S2:P2, got ${record?.sourceName}`);
+ }
+ if (!record?.thread || String(record.thread.mode || "").toUpperCase() !== "MODELED") {
+ throw new Error("[hole_thread_modeled_point_thread_face_reference] Expected modeled thread metadata");
+ }
+}
+
export async function test_hole_countersink(partHistory) {
const cube = await partHistory.newFeature("P.CU");
cube.inputParams.sizeX = CUBE_SIZE;
diff --git a/src/tests/test_pushFace.js b/src/tests/test_pushFace.js
deleted file mode 100644
index d35eee78..00000000
--- a/src/tests/test_pushFace.js
+++ /dev/null
@@ -1,126 +0,0 @@
-const CUBE_ID = 'PUSHFACE_CUBE';
-const CYLINDER_ID = 'PUSHFACE_CYL';
-const BOX_SIZE = 6;
-const CYL_RADIUS = 1.25;
-const PUSH_DISTANCE = 0.5;
-
-export async function test_pushFace(partHistory) {
- const cube = await partHistory.newFeature("P.CU");
- cube.inputParams.id = CUBE_ID;
- cube.inputParams.sizeX = BOX_SIZE;
- cube.inputParams.sizeY = BOX_SIZE;
- cube.inputParams.sizeZ = BOX_SIZE;
-
- const cyl = await partHistory.newFeature("P.CY");
- cyl.inputParams.id = CYLINDER_ID;
- cyl.inputParams.radius = CYL_RADIUS;
- cyl.inputParams.height = BOX_SIZE;
- cyl.inputParams.transform = {
- position: [BOX_SIZE / 2, 0, BOX_SIZE / 2],
- rotationEuler: [0, 0, 0],
- scale: [1, 1, 1],
- };
-
- const booleanFeature = await partHistory.newFeature("B");
- booleanFeature.inputParams.targetSolid = cube.inputParams.featureID;
- booleanFeature.inputParams.boolean = {
- operation: "SUBTRACT",
- targets: [cyl.inputParams.featureID],
- };
-
- return partHistory;
-}
-
-function measureRadialExtent(solid, faceName, centerX, centerZ) {
- if (typeof solid.getFace !== 'function') throw new Error('Solid missing getFace()');
- const face = solid.getFace(faceName);
- if (!Array.isArray(face) || face.length === 0) throw new Error(`Face "${faceName}" not found on solid ${solid.name || ''}`);
-
- const seen = new Set();
- const radii = [];
- for (const tri of face) {
- for (const p of [tri.p1, tri.p2, tri.p3]) {
- if (!Array.isArray(p) || p.length < 3) continue;
- const key = `${p[0]},${p[1]},${p[2]}`;
- if (seen.has(key)) continue;
- seen.add(key);
- const x = p[0];
- const z = p[2];
- radii.push(Math.hypot(x - centerX, z - centerZ));
- }
- }
- if (!radii.length) throw new Error(`No vertices collected for face "${faceName}"`);
- const sum = radii.reduce((a, b) => a + b, 0);
- const avg = sum / radii.length;
- const min = Math.min(...radii);
- const max = Math.max(...radii);
- return { avg, min, max };
-}
-
-function estimateRadialNormalSign(solid, faceName, centerX, centerZ) {
- const face = solid.getFace(faceName);
- if (!Array.isArray(face) || face.length === 0) return 0;
-
- let sum = 0;
- for (const tri of face) {
- if (!Array.isArray(tri.p1) || !Array.isArray(tri.p2) || !Array.isArray(tri.p3)) continue;
- const ax = tri.p1[0], ay = tri.p1[1], az = tri.p1[2];
- const bx = tri.p2[0], by = tri.p2[1], bz = tri.p2[2];
- const cx = tri.p3[0], cy = tri.p3[1], cz = tri.p3[2];
- const ux = bx - ax, uy = by - ay, uz = bz - az;
- const vx = cx - ax, vy = cy - ay, vz = cz - az;
- const nx = uy * vz - uz * vy;
- const nz = ux * vy - uy * vx;
- const mx = (ax + bx + cx) / 3;
- const mz = (az + bz + cz) / 3;
- const rx = mx - centerX;
- const rz = mz - centerZ;
- sum += rx * nx + rz * nz;
- }
- if (sum === 0) return 0;
- return sum > 0 ? 1 : -1;
-}
-
-export async function afterRun_pushFace(partHistory) {
- const solids = (partHistory.scene?.children || []).filter(o => o?.type === 'SOLID');
- if (!solids.length) throw new Error('[pushFace] No solids created');
-
- const solid = solids[0];
- const cavityFace = `${CYLINDER_ID}_S`;
- const center = { x: BOX_SIZE / 2, z: BOX_SIZE / 2 };
-
- const baseline = measureRadialExtent(solid, cavityFace, center.x, center.z);
- const inwardTest = solid.clone();
- const control = measureRadialExtent(inwardTest, cavityFace, center.x, center.z);
-
- solid.pushFace(cavityFace, PUSH_DISTANCE);
- const pushedOut = measureRadialExtent(solid, cavityFace, center.x, center.z);
-
- inwardTest.pushFace(cavityFace, -PUSH_DISTANCE);
- const pushedIn = measureRadialExtent(inwardTest, cavityFace, center.x, center.z);
-
- const normalSign = estimateRadialNormalSign(solid, cavityFace, center.x, center.z);
- const tol = 1e-6;
- if (normalSign === 0) {
- const outDelta = pushedOut.avg - baseline.avg;
- const inDelta = pushedIn.avg - control.avg;
- const opposite = (outDelta > tol && inDelta < -tol) || (outDelta < -tol && inDelta > tol);
- if (!opposite) {
- throw new Error(`[pushFace] Expected opposite radial motion, got ${outDelta} and ${inDelta}`);
- }
- } else if (normalSign > 0) {
- if (!(pushedOut.avg > baseline.avg + tol && pushedOut.min > baseline.min + tol / 10)) {
- throw new Error(`[pushFace] Positive distance failed to move face outward (avg ${baseline.avg} → ${pushedOut.avg})`);
- }
- if (!(pushedIn.avg < control.avg - tol && pushedIn.max < control.max - tol / 10)) {
- throw new Error(`[pushFace] Negative distance failed to move face inward (avg ${control.avg} → ${pushedIn.avg})`);
- }
- } else {
- if (!(pushedOut.avg < baseline.avg - tol && pushedOut.max < baseline.max - tol / 10)) {
- throw new Error(`[pushFace] Positive distance failed to move face inward (avg ${baseline.avg} → ${pushedOut.avg})`);
- }
- if (!(pushedIn.avg > control.avg + tol && pushedIn.min > control.min + tol / 10)) {
- throw new Error(`[pushFace] Negative distance failed to move face outward (avg ${control.avg} → ${pushedIn.avg})`);
- }
- }
-}
diff --git a/src/tests/test_pushFace_feature.js b/src/tests/test_pushFace_feature.js
deleted file mode 100644
index 2218b943..00000000
--- a/src/tests/test_pushFace_feature.js
+++ /dev/null
@@ -1,75 +0,0 @@
-const BASE_ID = "PUSH_FACE_FEATURE_BASE";
-const PUSH_DISTANCE = 1.5;
-
-function collectSolidBounds(solid) {
- const values = Array.isArray(solid?._vertProperties) ? solid._vertProperties : [];
- if (!values.length) throw new Error("[pushFace-feature] Solid has no vertices.");
- const bounds = {
- minX: Infinity,
- minY: Infinity,
- minZ: Infinity,
- maxX: -Infinity,
- maxY: -Infinity,
- maxZ: -Infinity,
- };
- for (let i = 0; i < values.length; i += 3) {
- const x = Number(values[i + 0]);
- const y = Number(values[i + 1]);
- const z = Number(values[i + 2]);
- bounds.minX = Math.min(bounds.minX, x);
- bounds.minY = Math.min(bounds.minY, y);
- bounds.minZ = Math.min(bounds.minZ, z);
- bounds.maxX = Math.max(bounds.maxX, x);
- bounds.maxY = Math.max(bounds.maxY, y);
- bounds.maxZ = Math.max(bounds.maxZ, z);
- }
- return bounds;
-}
-
-export async function test_pushFace_feature(partHistory) {
- const base = await partHistory.newFeature("P.CU");
- base.inputParams.id = BASE_ID;
- base.inputParams.sizeX = 4;
- base.inputParams.sizeY = 3;
- base.inputParams.sizeZ = 2;
-
- const pushFace = await partHistory.newFeature("PF");
- pushFace.inputParams.faces = [`${BASE_ID}_PX`, `${BASE_ID}_PZ`];
- pushFace.inputParams.distance = PUSH_DISTANCE;
-
- return partHistory;
-}
-
-export async function afterRun_pushFace_feature(partHistory) {
- const solids = (partHistory.scene?.children || []).filter((obj) => obj?.type === "SOLID");
- if (solids.length !== 1) {
- throw new Error(`[pushFace-feature] Expected one final solid, found ${solids.length}.`);
- }
-
- const solid = solids[0];
- if (solid.name !== BASE_ID) {
- throw new Error(`[pushFace-feature] Expected result solid to retain name ${BASE_ID}, received ${solid.name}.`);
- }
-
- const bounds = collectSolidBounds(solid);
- const tol = 1e-6;
- if (Math.abs(bounds.minX - 0) > tol || Math.abs(bounds.minY - 0) > tol || Math.abs(bounds.minZ - 0) > tol) {
- throw new Error(`[pushFace-feature] Expected mins to remain at the origin, got ${JSON.stringify(bounds)}.`);
- }
- if (Math.abs(bounds.maxX - (4 + PUSH_DISTANCE)) > tol) {
- throw new Error(`[pushFace-feature] Expected maxX ${4 + PUSH_DISTANCE}, received ${bounds.maxX}.`);
- }
- if (Math.abs(bounds.maxY - 3) > tol) {
- throw new Error(`[pushFace-feature] Expected maxY 3, received ${bounds.maxY}.`);
- }
- if (Math.abs(bounds.maxZ - (2 + PUSH_DISTANCE)) > tol) {
- throw new Error(`[pushFace-feature] Expected maxZ ${2 + PUSH_DISTANCE}, received ${bounds.maxZ}.`);
- }
-
- const faceNames = new Set(typeof solid.getFaceNames === "function" ? solid.getFaceNames() : []);
- for (const faceName of [`${BASE_ID}_PX`, `${BASE_ID}_PZ`]) {
- if (!faceNames.has(faceName)) {
- throw new Error(`[pushFace-feature] Expected pushed face ${faceName} to survive on the result.`);
- }
- }
-}
diff --git a/src/tests/test_remeshFeature.js b/src/tests/test_remeshFeature.js
deleted file mode 100644
index c514dd05..00000000
--- a/src/tests/test_remeshFeature.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { RemeshFeature } from "../features/remesh/RemeshFeature.js";
-
-export async function test_remesh_simplify_welds_by_tolerance_before_simplify() {
- const callLog = [];
-
- const outSolid = {
- type: "SOLID",
- simplify(tolerance) {
- callLog.push(["simplify", tolerance]);
- return this;
- },
- _weldVerticesByEpsilon(epsilon, options) {
- callLog.push(["weld", epsilon, options?.rebuildManifold]);
- if (options?.rebuildManifold !== false) {
- throw new Error("Expected pre-simplify weld to skip immediate manifold rebuild.");
- }
- return this;
- },
- fixTriangleWindingsByAdjacency() {
- callLog.push(["fixWindings"]);
- return this;
- },
- visualize() {
- callLog.push(["visualize"]);
- },
- };
-
- const targetSolid = {
- type: "SOLID",
- name: "REMESH_SRC",
- clone() {
- callLog.push(["clone"]);
- return outSolid;
- },
- };
-
- const fakeHistory = {
- scene: {
- async getObjectByName(name) {
- return name === "REMESH_SRC" ? targetSolid : null;
- },
- },
- };
-
- const feature = new RemeshFeature();
- feature.inputParams = {
- targetSolid: "REMESH_SRC",
- mode: "Simplify",
- tolerance: 0.05,
- };
-
- const effects = await feature.run(fakeHistory);
- if (!Array.isArray(effects?.added) || effects.added[0] !== outSolid) {
- throw new Error("Expected remesh simplify feature to return the cloned output solid.");
- }
- if (!Array.isArray(effects?.removed) || effects.removed[0] !== targetSolid) {
- throw new Error("Expected remesh simplify feature to mark the source solid for removal.");
- }
-
- const operationLog = callLog.map((entry) => {
- const [name, value, extra] = entry;
- if (name === "weld") return `${name}:${value}:${String(extra)}`;
- return value === undefined ? name : `${name}:${value}`;
- });
- const expected = ["clone", "weld:0.05:false", "fixWindings", "simplify:0.05", "visualize"];
- if (operationLog.join("|") !== expected.join("|")) {
- throw new Error(
- `Expected remesh simplify to weld before simplify using the same tolerance; received ${operationLog.join("|")}.`,
- );
- }
-}
diff --git a/src/tests/test_sketch_inner_loop_extrude.js b/src/tests/test_sketch_inner_loop_extrude.js
new file mode 100644
index 00000000..a6f06a41
--- /dev/null
+++ b/src/tests/test_sketch_inner_loop_extrude.js
@@ -0,0 +1,120 @@
+function assert(condition, message) {
+ if (!condition) throw new Error(message || "Assertion failed.");
+}
+
+function pointInPoly(point, poly) {
+ const x = point[0];
+ const y = point[1];
+ let inside = false;
+ for (let i = 0, j = poly.length - 1; i < poly.length; j = i, i += 1) {
+ const pi = poly[i];
+ const pj = poly[j];
+ const crosses = ((pi[1] > y) !== (pj[1] > y))
+ && (x < ((pj[0] - pi[0]) * (y - pi[1])) / ((pj[1] - pi[1]) || 1e-30) + pi[0]);
+ if (crosses) inside = !inside;
+ }
+ return inside;
+}
+
+const reproSketch = {
+ points: [
+ { id: 0, x: 0, y: 0, fixed: true, construction: true, externalReference: false },
+ { id: 1, x: -24.244146, y: -23.69515, fixed: false, construction: false, externalReference: false },
+ { id: 2, x: 0, y: 0, fixed: true, construction: false, externalReference: false },
+ { id: 3, x: -24.244144, y: 0.000006, fixed: false, construction: false, externalReference: false },
+ { id: 4, x: -24.244144, y: 0.000006, fixed: false, construction: false, externalReference: false },
+ { id: 5, x: -24.244146, y: -23.69515, fixed: false, construction: false, externalReference: false },
+ { id: 6, x: 0, y: -23.695149, fixed: false, construction: false, externalReference: false },
+ { id: 7, x: 0, y: -23.695149, fixed: false, construction: false, externalReference: false },
+ { id: 8, x: -24.244148, y: -32.399992, fixed: false, construction: false, externalReference: false },
+ { id: 9, x: 0, y: -30.318538, fixed: false, construction: false, externalReference: false },
+ { id: 10, x: -22.333029, y: -33.020496, fixed: false, construction: false, externalReference: false },
+ { id: 11, x: -20.284826, y: -34.329163, fixed: false, construction: false, externalReference: false },
+ { id: 12, x: -17.203488, y: -36.297934, fixed: false, construction: false, externalReference: false },
+ { id: 13, x: -11.647803, y: -30.693275, fixed: false, construction: false, externalReference: false },
+ { id: 14, x: -8.962283, y: -31.443889, fixed: false, construction: false, externalReference: false },
+ { id: 15, x: 1.059521, y: -34.245026, fixed: false, construction: false, externalReference: false },
+ { id: 16, x: -15.752067, y: -16.904143, fixed: false, construction: false, externalReference: false },
+ { id: 17, x: -11.323036, y: -11.49297, fixed: false, construction: false, externalReference: false },
+ { id: 18, x: -15.752067, y: -16.904143, fixed: false, construction: false, externalReference: false },
+ { id: 19, x: -11.329591, y: -16.909493, fixed: false, construction: false, externalReference: false },
+ { id: 20, x: -11.329591, y: -16.909493, fixed: false, construction: false, externalReference: false },
+ { id: 21, x: -11.323036, y: -11.49297, fixed: false, construction: false, externalReference: false },
+ { id: 22, x: -15.745507, y: -11.487614, fixed: false, construction: false, externalReference: false },
+ { id: 23, x: -15.745507, y: -11.487614, fixed: false, construction: false, externalReference: false },
+ { id: 24, x: -13.540815, y: -16.906828, fixed: false, construction: false, externalReference: false },
+ { id: 25, x: -15.752067, y: -16.904143, fixed: false, construction: false, externalReference: false },
+ { id: 26, x: -11.329591, y: -16.909493, fixed: false, construction: false, externalReference: false },
+ ],
+ geometries: [
+ { id: 1, type: "line", points: [0, 3], construction: false },
+ { id: 2, type: "line", points: [4, 1], construction: false },
+ { id: 3, type: "line", points: [5, 6], construction: true },
+ { id: 4, type: "line", points: [7, 2], construction: false },
+ { id: 5, type: "bezier", points: [1, 8, 10, 11, 12, 13, 14, 15, 9, 6], construction: false },
+ { id: 13, type: "line", points: [20, 17], construction: false },
+ { id: 14, type: "line", points: [21, 22], construction: false },
+ { id: 15, type: "line", points: [23, 18], construction: false },
+ { id: 16, type: "arc", points: [24, 25, 26], construction: false },
+ ],
+ constraints: [{ id: 0, type: "⏚", points: [0] }],
+};
+
+export async function test_sketch_inner_loop_extrude_repro_20260515(partHistory) {
+ partHistory.expressions = "resolution = 32;";
+
+ const sketch = await partHistory.newFeature("S");
+ Object.assign(sketch.inputParams, {
+ id: "S1",
+ sketchPlane: null,
+ curveResolution: "resolution",
+ });
+ sketch.persistentData.sketch = JSON.parse(JSON.stringify(reproSketch));
+
+ const extrude = await partHistory.newFeature("E");
+ Object.assign(extrude.inputParams, {
+ id: "E2",
+ profile: "S1:PROFILE",
+ consumeProfileSketch: false,
+ distance: 10,
+ distanceBack: 10,
+ boolean: { targets: [], operation: "NONE" },
+ });
+
+ return partHistory;
+}
+
+export async function afterRun_sketch_inner_loop_extrude_repro_20260515(partHistory) {
+ const sketchFeature = partHistory.features.find((entry) => entry?.inputParams?.id === "S1");
+ const diag = sketchFeature?.persistentData?.lastProfileDiagnostics;
+ assert(diag?.status === "ok", `Expected sketch profile diagnostics to succeed, got ${diag?.status || "null"}.`);
+ assert(diag.groups?.length === 1, `Expected one profile group, got ${diag.groups?.length || 0}.`);
+ assert(diag.groups[0]?.holes?.length === 1, `Expected one inner loop hole, got ${diag.groups[0]?.holes?.length || 0}.`);
+
+ const solid = partHistory.getObjectByName("E2");
+ assert(solid && typeof solid.getFaces === "function", "Expected E2 extrude solid.");
+ solid.visualize?.();
+
+ const bezierSide = solid.children?.find((child) => child?.type === "FACE" && child.name === "E2:S1:G5_SW");
+ const arcSide = solid.children?.find((child) => child?.type === "FACE" && child.name === "E2:S1:G16_SW");
+ assert(bezierSide, "Expected the outer Bezier edge to generate one authored-curve sidewall face.");
+ assert(arcSide, "Expected the inner arc edge to generate one authored-curve sidewall face.");
+ assert(bezierSide.edges?.length === 4, `Expected Bezier sidewall to have 4 topology edges, got ${bezierSide.edges?.length || 0}.`);
+ assert(arcSide.edges?.length === 4, `Expected arc sidewall to have 4 topology edges, got ${arcSide.edges?.length || 0}.`);
+
+ const startFace = solid.getFaces(false).find((entry) => String(entry?.faceName || "") === "E2:S1:PROFILE_START");
+ assert(startFace, "Expected E2 start cap face.");
+
+ const hole = diag.profileGroups?.[0]?.holes2D?.[0];
+ assert(Array.isArray(hole) && hole.length >= 3, "Expected diagnostic inner loop points.");
+
+ const insideHoleTriangles = (startFace.triangles || []).filter((tri) => {
+ const cx = (tri.p1[0] + tri.p2[0] + tri.p3[0]) / 3;
+ const cy = (tri.p1[1] + tri.p2[1] + tri.p3[1]) / 3;
+ return pointInPoly([cx, cy], hole);
+ });
+ assert(
+ insideHoleTriangles.length === 0,
+ `Expected extrude start cap to leave the inner loop open, found ${insideHoleTriangles.length} cap triangles inside it.`,
+ );
+}
diff --git a/src/tests/test_thickenFeature.js b/src/tests/test_thickenFeature.js
index 62c8dc88..dfe0b4e3 100644
--- a/src/tests/test_thickenFeature.js
+++ b/src/tests/test_thickenFeature.js
@@ -5,6 +5,20 @@ function assert(condition, message) {
if (!condition) throw new Error(message || 'Assertion failed.');
}
+function assertThrowsMessage(fn, pattern, label) {
+ let thrown = null;
+ try {
+ fn();
+ } catch (error) {
+ thrown = error;
+ }
+ assert(thrown, `[${label}] Expected operation to throw.`);
+ assert(
+ pattern.test(String(thrown?.message || thrown)),
+ `[${label}] Unexpected error message: ${String(thrown?.message || thrown)}`,
+ );
+}
+
function analyzeMeshTopology(solid) {
const triVerts = Array.isArray(solid?._triVerts) ? solid._triVerts : [];
const triCount = (triVerts.length / 3) | 0;
@@ -71,24 +85,6 @@ function assertSidewallsConnectToCaps(solid, label, startFaceName, endFaceName,
}
}
-function averageFaceTriangleRadiusXZ(solid, faceName, yBand = null) {
- const face = typeof solid?.getFace === 'function' ? solid.getFace(faceName) : null;
- assert(Array.isArray(face) && face.length > 0, `Expected face "${faceName}" to exist on solid "${solid?.name || ''}".`);
- let sum = 0;
- let count = 0;
- for (const tri of face) {
- const points = [tri?.p1, tri?.p2, tri?.p3];
- if (!points.every((point) => Array.isArray(point) && point.length >= 3)) continue;
- const cx = (points[0][0] + points[1][0] + points[2][0]) / 3;
- const cy = (points[0][1] + points[1][1] + points[2][1]) / 3;
- const cz = (points[0][2] + points[1][2] + points[2][2]) / 3;
- if (Number.isFinite(yBand) && Math.abs(cy) > yBand) continue;
- sum += Math.hypot(cx, cz);
- count += 1;
- }
- return count ? (sum / count) : 0;
-}
-
function makeRectSketch(x0, y0, x1, y1, geomBase = 100) {
return {
points: [
@@ -207,8 +203,8 @@ export async function test_face_thicken_planar_profile(partHistory) {
const diagnostics = solid?.__thickenDiagnostics || {};
assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-planar] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
+ diagnostics.buildMethod === 'occ_prism',
+ `[thicken-planar] Expected OpenCascade prism build path, received ${diagnostics.buildMethod || 'unknown'}.`,
);
const volume = typeof solid.volume === 'function' ? solid.volume() : NaN;
assert(Math.abs(volume - 120) <= 1e-3, `[thicken-planar] Expected volume 120, received ${volume}.`);
@@ -243,8 +239,8 @@ export async function test_face_thicken_hole_profile(partHistory) {
const diagnostics = solid?.__thickenDiagnostics || {};
assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-hole] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
+ diagnostics.buildMethod === 'occ_prism',
+ `[thicken-hole] Expected OpenCascade prism build path, received ${diagnostics.buildMethod || 'unknown'}.`,
);
const volume = typeof solid.volume === 'function' ? solid.volume() : NaN;
assert(Math.abs(volume - 128) <= 1e-3, `[thicken-hole] Expected volume 128, received ${volume}.`);
@@ -252,27 +248,10 @@ export async function test_face_thicken_hole_profile(partHistory) {
export async function test_face_thicken_curved_cylinder_side(partHistory) {
const face = await buildCylinderSideFace(partHistory, 'THICK_CURVED_SRC', 3, 8, 64);
- const solid = face.thicken(0.75, { featureId: 'THICK_CURVED' });
- assertClosedManifold(solid, 'thicken-curved');
-
- const faceNames = getFaceNamesSet(solid);
- assert(faceNames.has('THICK_CURVED_SRC_S_START'), '[thicken-curved] Missing start face.');
- assert(faceNames.has('THICK_CURVED_SRC_S_END'), '[thicken-curved] Missing end face.');
- const outerSidewalls = listFaceNamesByRegex(solid, /^THICK_CURVED_SRC_S_E\d+_SW$/);
- const innerSidewalls = listFaceNamesByRegex(solid, /^THICK_CURVED_SRC_S_L1_E\d+_SW$/);
- assert(outerSidewalls.length > 0, '[thicken-curved] Expected per-edge sidewalls on the first boundary loop.');
- assert(innerSidewalls.length > 0, '[thicken-curved] Expected per-edge sidewalls on the second boundary loop.');
-
- const sourceRadius = averageFaceTriangleRadiusXZ(solid, 'THICK_CURVED_SRC_S_START');
- const offsetRadius = averageFaceTriangleRadiusXZ(solid, 'THICK_CURVED_SRC_S_END');
- assert(
- offsetRadius > sourceRadius + 0.45,
- `[thicken-curved] Expected offset face to sit radially outside the source face, received ${sourceRadius} vs ${offsetRadius}.`,
- );
- const diagnostics = solid?.__thickenDiagnostics || {};
- assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-curved] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
+ assertThrowsMessage(
+ () => face.thicken(0.75, { featureId: 'THICK_CURVED' }),
+ /requires a planar BREP face/i,
+ 'thicken-curved',
);
}
@@ -296,62 +275,27 @@ export async function test_face_thicken_filleted_planar_face_keeps_clean_boundar
const diagnostics = solid?.__thickenDiagnostics || {};
assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-filleted-planar] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
+ diagnostics.buildMethod === 'occ_prism',
+ `[thicken-filleted-planar] Expected OpenCascade prism build path, received ${diagnostics.buildMethod || 'unknown'}.`,
);
}
export async function test_face_thicken_self_overlap_cylinder_side(partHistory) {
const face = await buildCylinderSideFace(partHistory, 'THICK_SELF_SRC', 1, 6, 32);
- const solid = face.thicken(-1.25, { featureId: 'THICK_SELF' });
- assertClosedManifold(solid, 'thicken-self-overlap');
-
- const diagnostics = solid?.__thickenDiagnostics || null;
- assert(diagnostics && Number.isFinite(diagnostics.sourceTriangleCount), '[thicken-self-overlap] Missing diagnostics.');
- assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-self-overlap] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
- );
- assert(
- (diagnostics.triangleSplitCount || 0) > 0,
- '[thicken-self-overlap] Expected triangle intersections to be split.',
- );
- assert(
- (diagnostics.culledTriangleCount || 0) > 0,
- '[thicken-self-overlap] Expected internal triangles to be culled.',
+ assertThrowsMessage(
+ () => face.thicken(-1.25, { featureId: 'THICK_SELF' }),
+ /requires a planar BREP face/i,
+ 'thicken-self-overlap',
);
- assert(
- (diagnostics.boundaryCapTriangleCount || 0) > 0,
- '[thicken-self-overlap] Expected split/cull boundary loops to be capped with triangles.',
- );
- assert((typeof solid.volume === 'function' ? solid.volume() : 0) > 0, '[thicken-self-overlap] Expected a positive-volume solid.');
}
export async function test_face_thicken_partial_torus_side_avoids_internal_voids(partHistory) {
const face = await buildTorusSideFace(partHistory, 'THICK_TORUS_SRC', 10, 4, 201, 32);
- const solid = face.thicken(3, { featureId: 'THICK_TORUS' });
- assertClosedManifold(solid, 'thicken-partial-torus');
-
- const faceNames = getFaceNamesSet(solid);
- assert(faceNames.has('THICK_TORUS_SRC_Side_START'), '[thicken-partial-torus] Missing start face.');
- assert(faceNames.has('THICK_TORUS_SRC_Side_END'), '[thicken-partial-torus] Missing end face.');
- const outerSidewalls = listFaceNamesByRegex(solid, /^THICK_TORUS_SRC_Side_E\d+_SW$/);
- const innerSidewalls = listFaceNamesByRegex(solid, /^THICK_TORUS_SRC_Side_L1_E\d+_SW$/);
- assert(outerSidewalls.length > 0, '[thicken-partial-torus] Expected per-edge sidewalls on the first boundary loop.');
- assert(innerSidewalls.length > 0, '[thicken-partial-torus] Expected per-edge sidewalls on the second boundary loop.');
-
- const diagnostics = solid?.__thickenDiagnostics || {};
- assert(
- diagnostics.buildMethod === 'triangle_split_cull',
- `[thicken-partial-torus] Expected triangle split/cull build path, received ${diagnostics.buildMethod || 'unknown'}.`,
+ assertThrowsMessage(
+ () => face.thicken(3, { featureId: 'THICK_TORUS' }),
+ /requires a planar BREP face/i,
+ 'thicken-partial-torus',
);
- assert(
- Number.isFinite(Number(diagnostics.splitCullPasses)) && diagnostics.splitCullPasses >= 1,
- '[thicken-partial-torus] Expected triangle split/cull diagnostics.',
- );
-
- const volume = typeof solid.volume === 'function' ? solid.volume() : NaN;
- assert(Number.isFinite(volume) && volume > 1000, `[thicken-partial-torus] Expected positive torus-shell volume, received ${volume}.`);
}
export async function test_face_thicken_boundary_uses_smooth_adjacent_face_normals() {
@@ -366,26 +310,10 @@ export async function test_face_thicken_boundary_uses_smooth_adjacent_face_norma
const face = parent.getObjectByName('SMOOTH_A');
assert(face?.type === 'FACE', '[thicken-smooth-boundary] Expected authored source face.');
- const solid = face.thicken(2, { featureId: 'THICK_SMOOTH_BOUNDARY' });
- assertClosedManifold(solid, 'thicken-smooth-boundary');
-
- const diagnostics = solid?.__thickenDiagnostics || {};
- assert(
- (diagnostics.adjacentBoundaryNormalContributionCount || 0) >= 2,
- '[thicken-smooth-boundary] Expected tangent adjacent face normals to contribute at the boundary.',
- );
-
- const endFace = typeof solid.getFace === 'function' ? solid.getFace('SMOOTH_A_END') : [];
- const endXs = [];
- for (const tri of endFace || []) {
- for (const point of [tri?.p1, tri?.p2, tri?.p3]) {
- if (Array.isArray(point) && point.length >= 3) endXs.push(point[0]);
- }
- }
- const maxEndX = Math.max(...endXs);
- assert(
- maxEndX < 0.9,
- `[thicken-smooth-boundary] Expected the offset boundary to follow the smooth neighbor normal field; max x=${maxEndX}.`,
+ assertThrowsMessage(
+ () => face.thicken(2, { featureId: 'THICK_SMOOTH_BOUNDARY' }),
+ /requires an OpenCascade BREP face or authored boundary loops/i,
+ 'thicken-smooth-boundary',
);
}
@@ -533,7 +461,7 @@ export async function afterRun_thicken_feature_connected_faces_remain_individual
for (const solid of solids) {
assertClosedManifold(solid, `thicken-feature-patch:${solid.name}`);
const diagnostics = solid?.__thickenDiagnostics || {};
- assert(diagnostics.buildMethod === 'triangle_split_cull', `[thicken-feature-patch] ${solid.name} did not use triangle split/cull.`);
+ assert(diagnostics.buildMethod === 'occ_prism', `[thicken-feature-patch] ${solid.name} did not use OpenCascade prism thicken.`);
assert(diagnostics.sourceFaceCount === 1, `[thicken-feature-patch] Expected one source face for ${solid.name}.`);
}
assert(Array.isArray(featureEntry?.persistentData?.results), '[thicken-feature-patch] Expected persistentData.results.');
diff --git a/src/tests/test_tube.js b/src/tests/test_tube.js
index 1f185f2f..778f50c6 100644
--- a/src/tests/test_tube.js
+++ b/src/tests/test_tube.js
@@ -33,13 +33,11 @@ export async function test_tube(partHistory) {
solidTube.inputParams.path = [`${edgePrefix}G200`, `${edgePrefix}G201`];
solidTube.inputParams.radius = 4;
solidTube.inputParams.innerRadius = 0;
- solidTube.inputParams.resolution = 32;
const hollowTube = await partHistory.newFeature("TU");
hollowTube.inputParams.path = [`${edgePrefix}G200`, `${edgePrefix}G201`];
hollowTube.inputParams.radius = 5;
hollowTube.inputParams.innerRadius = 2;
- hollowTube.inputParams.resolution = 48;
return partHistory;
}
diff --git a/src/tests/test_tube_closedLoop.js b/src/tests/test_tube_closedLoop.js
index a4108a01..008bbf87 100644
--- a/src/tests/test_tube_closedLoop.js
+++ b/src/tests/test_tube_closedLoop.js
@@ -48,14 +48,12 @@ export async function test_tube_closedLoop(partHistory) {
solidTube.inputParams.path = geometries.map(g => `${edgePrefix}G${g.id}`);
solidTube.inputParams.radius = 3;
solidTube.inputParams.innerRadius = 0;
- solidTube.inputParams.resolution = 24;
// Create a hollow tube with the closed loop path
const hollowTube = await partHistory.newFeature("TU");
hollowTube.inputParams.path = geometries.map(g => `${edgePrefix}G${g.id}`);
hollowTube.inputParams.radius = 4;
hollowTube.inputParams.innerRadius = 1;
- hollowTube.inputParams.resolution = 24;
// Offset the hollow tube vertically so we can see both
hollowTube.inputParams.transform = {
position: [0, 0, 10],
diff --git a/src/tests/tests.js b/src/tests/tests.js
index 47554846..b00ac1ec 100644
--- a/src/tests/tests.js
+++ b/src/tests/tests.js
@@ -9,13 +9,6 @@ import {
test_boolean_operation_target_name_preserved,
} from './test_boolean_operation_target_name.js';
import { test_boolean_face_metadata_preserved } from './test_boolean_face_metadata_preserved.js';
-import {
- test_boolean_overlap_conditioning_subtract_expands_tool_entry_cap_outward,
- test_boolean_overlap_conditioning_subtract_can_be_disabled,
- test_boolean_overlap_conditioning_subtract_enabled_by_default,
- test_boolean_overlap_conditioning_union_can_be_disabled,
- test_boolean_overlap_conditioning_union_enabled_by_default,
-} from './test_boolean_overlap_conditioning.js';
import { test_Chamfer } from './test_chamfer.js';
import {
test_cppChamfer_auto_direction_uses_native_classifier,
@@ -26,13 +19,6 @@ import {
test_cppChamfer_single_edge_builds_native_named_tool_and_result,
test_cppChamfer_stabilizes_tiny_terminal_segments_before_offsetting,
} from './test_cppChamfer.js';
-import {
- test_edge_smooth_constraints_prevent_triangle_foldback,
- test_edge_smooth_curve_fit,
- test_edge_smooth_curve_fit_closed_loop,
- test_edge_smooth_face_selection,
- test_edge_smooth_whole_solid_selection,
-} from './test_edge_smooth_curve_fit.js';
import {
afterRun_extrude_negative_distance_cap_alignment,
test_extrude_negative_distance_cap_alignment,
@@ -53,6 +39,10 @@ import {
afterRun_sketch_profile_tolerant_loop_join,
test_sketch_profile_tolerant_loop_join,
} from './test_sketch_profile_tolerant_loop_join.js';
+import {
+ afterRun_sketch_inner_loop_extrude_repro_20260515,
+ test_sketch_inner_loop_extrude_repro_20260515,
+} from './test_sketch_inner_loop_extrude.js';
import {
afterRun_fillet_compound_snapshot_resolution,
test_fillet_compound_snapshot_resolution,
@@ -85,6 +75,14 @@ import {
afterRun_fillet_preserves_original_face_names,
test_fillet_preserves_original_face_names,
} from './test_fillet_preserves_original_face_names.js';
+import {
+ afterRun_fillet_occ_top_loop_history,
+ test_fillet_occ_top_loop_history,
+} from './test_fillet_occ_top_loop_history.js';
+import {
+ afterRun_fillet_occ_closed_extrude_edge_history,
+ test_fillet_occ_closed_extrude_edge_history,
+} from './test_fillet_occ_closed_extrude_edge_history.js';
import {
afterRun_fillet_corner_bridge,
test_fillet_corner_bridge,
@@ -97,11 +95,13 @@ import {
afterRun_hole_counterbore,
afterRun_hole_countersink,
afterRun_hole_thread_modeled,
+ afterRun_hole_thread_modeled_point_thread_face_reference,
afterRun_hole_thread_symbolic,
afterRun_hole_through,
test_hole_counterbore,
test_hole_countersink,
test_hole_thread_modeled,
+ test_hole_thread_modeled_point_thread_face_reference,
test_hole_thread_symbolic,
test_hole_through,
} from './test_hole.js';
@@ -176,15 +176,21 @@ import {
} from './test_cppSolidNativeOps.js';
import {
test_cppTube_closed_hollow_tube_preserves_expected_face_labels,
- test_cppTube_native_auto_falls_back_to_slow_on_foldback_path,
- test_cppTube_native_builder_reports_selected_build_mode,
+ test_cppTube_hollow_tube_visualizes_distinct_inner_and_outer_faces,
+ test_cppTube_hollow_spline_path_visualizes_side_faces,
test_cppTube_open_tube_preserves_expected_face_labels,
+ test_cppTube_spline_solid_has_three_faces_and_no_cross_section_edges,
test_cppTube_union_preserves_distinct_face_labels_across_native_snapshots,
} from './test_cppTube.js';
import {
test_cppPrimitive_cone_preserves_expected_face_labels_and_metadata,
+ test_cppExtrude_circle_profile_sidewall_visualizes_cap_edges_and_seam,
+ test_cppExtrude_sketch_bezier_edge_builds_single_sidewall,
+ test_cppExtrude_sketch_circle_hole_builds_distinct_analytic_sidewall,
+ test_cppRevolve_extruded_sketch_cap_reuses_analytic_profile,
test_cppPrimitive_cube_preserves_expected_face_labels,
test_cppPrimitive_cylinder_preserves_expected_face_labels_and_metadata,
+ test_cppPrimitive_cylinder_sidewall_visualizes_cap_edges_and_seam,
test_cppPrimitive_sphere_preserves_single_face_label,
test_cppPrimitive_torus_and_pyramid_preserve_face_labels,
} from './test_cppPrimitives.js';
@@ -215,8 +221,6 @@ import { test_primitiveCylinder } from './test_primitiveCylinder.js';
import { test_primitivePyramid } from './test_primitivePyramid.js';
import { test_primitiveSphere } from './test_primitiveSphere.js';
import { test_primitiveTorus } from './test_primitiveTorus.js';
-import { afterRun_pushFace_feature, test_pushFace_feature } from './test_pushFace_feature.js';
-import { afterRun_pushFace, test_pushFace } from './test_pushFace.js';
import { test_sheetMetal_corner_fillet } from './test_sheetMetal_corner_fillet.js';
import { test_sheetMetal_corner_fillet_face_cylindrical_metadata } from './test_sheetMetal_corner_fillet_face_cylindrical_metadata.js';
import { test_sheetMetal_bend_face_cylindrical_metadata } from './test_sheetMetal_bend_face_cylindrical_metadata.js';
@@ -309,7 +313,6 @@ import {
import { test_thicken_feature_is_available_in_modeling_and_surfacing_workbenches } from './test_workbenchFeatureVisibility.js';
import { test_sketch_feature_scene_visibility } from './test_sketchFeatureVisibility.js';
import { test_revolve_feature_resolves_face_and_edge_string_references } from './test_revolveFeature.js';
-import { test_remesh_simplify_welds_by_tolerance_before_simplify } from './test_remeshFeature.js';
import { test_revolve_after_union_preserves_face_reference_resolution } from './test_revolve_after_union_face_reference.js';
import {
afterRun_primitive_boolean_union_preserves_face_grouping,
@@ -334,7 +337,6 @@ export const testFunctions = [
{ test: test_cppSolidBakeTransform_updates_solid_authoring_state, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppSolidMirror_preserves_face_metadata, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_revolve_feature_resolves_face_and_edge_string_references, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_remesh_simplify_welds_by_tolerance_before_simplify, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_revolve_after_union_preserves_face_reference_resolution, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppSolidNative_setEpsilon_welds_vertices, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppSolidNative_setEpsilon_merges_cell_boundary_pair_and_rebuilds_manifold, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
@@ -358,11 +360,17 @@ export const testFunctions = [
{ test: test_cppSolidNative_reassignTinyFilletSidewallSliverTriangles_merges_triangle_whose_vertices_lie_on_single_planar_face_boundary, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppTube_open_tube_preserves_expected_face_labels, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppTube_closed_hollow_tube_preserves_expected_face_labels, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppTube_hollow_tube_visualizes_distinct_inner_and_outer_faces, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppTube_union_preserves_distinct_face_labels_across_native_snapshots, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_cppTube_native_builder_reports_selected_build_mode, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_cppTube_native_auto_falls_back_to_slow_on_foldback_path, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppTube_hollow_spline_path_visualizes_side_faces, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppTube_spline_solid_has_three_faces_and_no_cross_section_edges, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppPrimitive_cube_preserves_expected_face_labels, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppPrimitive_cylinder_preserves_expected_face_labels_and_metadata, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppPrimitive_cylinder_sidewall_visualizes_cap_edges_and_seam, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppExtrude_circle_profile_sidewall_visualizes_cap_edges_and_seam, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppExtrude_sketch_circle_hole_builds_distinct_analytic_sidewall, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppExtrude_sketch_bezier_edge_builds_single_sidewall, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
+ { test: test_cppRevolve_extruded_sketch_cap_reuses_analytic_profile, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppPrimitive_cone_preserves_expected_face_labels_and_metadata, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppPrimitive_torus_and_pyramid_preserve_face_labels, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppPrimitive_sphere_preserves_single_face_label, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
@@ -588,6 +596,14 @@ export const testFunctions = [
exportSolids: false,
resetHistory: true,
},
+ {
+ test: test_sketch_inner_loop_extrude_repro_20260515,
+ afterRun: afterRun_sketch_inner_loop_extrude_repro_20260515,
+ printArtifacts: false,
+ exportFaces: false,
+ exportSolids: false,
+ resetHistory: true,
+ },
{
test: test_fillet_compound_snapshot_resolution,
afterRun: afterRun_fillet_compound_snapshot_resolution,
@@ -660,6 +676,22 @@ export const testFunctions = [
exportSolids: false,
resetHistory: true,
},
+ {
+ test: test_fillet_occ_top_loop_history,
+ afterRun: afterRun_fillet_occ_top_loop_history,
+ printArtifacts: false,
+ exportFaces: false,
+ exportSolids: false,
+ resetHistory: true,
+ },
+ {
+ test: test_fillet_occ_closed_extrude_edge_history,
+ afterRun: afterRun_fillet_occ_closed_extrude_edge_history,
+ printArtifacts: false,
+ exportFaces: false,
+ exportSolids: false,
+ resetHistory: true,
+ },
{
test: test_fillet_face_names_and_merge_metadata_survive_native_manifold_rebuild,
afterRun: afterRun_fillet_face_names_and_merge_metadata_survive_native_manifold_rebuild,
@@ -678,11 +710,6 @@ export const testFunctions = [
{ test: test_cppChamfer_projects_open_end_caps_back_to_endpoint_plane, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppChamfer_debug_emits_cross_section_face_per_sample, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_cppChamfer_debug_sections_materialize_as_sketch_profiles, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_edge_smooth_curve_fit, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_edge_smooth_curve_fit_closed_loop, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_edge_smooth_constraints_prevent_triangle_foldback, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_edge_smooth_whole_solid_selection, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_edge_smooth_face_selection, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_smooth_with_subdivision_replaces_source_solid, afterRun: afterRun_smooth_with_subdivision_replaces_source_solid, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_smooth_with_subdivision_preserves_centered_ring_symmetry, afterRun: afterRun_smooth_with_subdivision_preserves_centered_ring_symmetry, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_smooth_with_subdivision_preserves_mirrored_union_symmetry, afterRun: afterRun_smooth_with_subdivision_preserves_mirrored_union_symmetry, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
@@ -691,8 +718,7 @@ export const testFunctions = [
{ test: test_hole_counterbore, afterRun: afterRun_hole_counterbore, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
{ test: test_hole_thread_symbolic, afterRun: afterRun_hole_thread_symbolic, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
{ test: test_hole_thread_modeled, afterRun: afterRun_hole_thread_modeled, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
- { test: test_pushFace_feature, afterRun: afterRun_pushFace_feature, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
- { test: test_pushFace, afterRun: afterRun_pushFace, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
+ { test: test_hole_thread_modeled_point_thread_face_reference, afterRun: afterRun_hole_thread_modeled_point_thread_face_reference, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
{ test: test_mirror, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
{ test: test_history_features_basic, afterRun: afterRun_history_features_basic, printArtifacts: false, exportFaces: true, exportSolids: true, resetHistory: true },
{ test: test_history_expand_does_not_dirty, afterRun: afterRun_history_expand_does_not_dirty, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
@@ -700,11 +726,6 @@ export const testFunctions = [
{ test: test_solid_overlap_diagnostics_detects_coplanar_overlap, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_solid_overlap_diagnostics_ignores_boundary_touching_faces, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_solid_overlap_diagnostics_detects_cross_solid_overlap, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_boolean_overlap_conditioning_union_enabled_by_default, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_boolean_overlap_conditioning_union_can_be_disabled, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_boolean_overlap_conditioning_subtract_enabled_by_default, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_boolean_overlap_conditioning_subtract_expands_tool_entry_cap_outward, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
- { test: test_boolean_overlap_conditioning_subtract_can_be_disabled, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_visibility_hidden_state_persistence, afterRun: afterRun_visibility_hidden_state_persistence, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_sketch_feature_scene_visibility, printArtifacts: false, exportFaces: false, exportSolids: false, resetHistory: true },
{ test: test_textToFace, afterRun: afterRun_textToFace, printArtifacts: false, exportFaces: true, exportSolids: false, resetHistory: true },
diff --git a/src/wireHarness/wireHarnessRouteRenderer.js b/src/wireHarness/wireHarnessRouteRenderer.js
index 79d02cb3..00e76e12 100644
--- a/src/wireHarness/wireHarnessRouteRenderer.js
+++ b/src/wireHarness/wireHarnessRouteRenderer.js
@@ -96,10 +96,8 @@ function createBundleSolid(segment) {
points: points.map((point) => [point.x, point.y, point.z]),
radius,
innerRadius: 0,
- resolution: 14,
closed: false,
name: String(segment?.featureId || segment?.segmentId || 'Wire Bundle').trim() || 'Wire Bundle',
- preferFast: true,
autoVisualize: false,
});
if (!solid || solid.type !== 'SOLID') return null;
diff --git a/src/workbenches/modelingWorkbench.js b/src/workbenches/modelingWorkbench.js
index cfe67f15..d2c0794f 100644
--- a/src/workbenches/modelingWorkbench.js
+++ b/src/workbenches/modelingWorkbench.js
@@ -19,7 +19,6 @@ export const MODELING_WORKBENCH = {
'F',
'CH',
'H',
- 'PF',
'THK',
'PATLIN',
'PATRAD',
diff --git a/src/workbenches/simulationWorkbench.js b/src/workbenches/simulationWorkbench.js
index 424bbc0b..3a7be9d2 100644
--- a/src/workbenches/simulationWorkbench.js
+++ b/src/workbenches/simulationWorkbench.js
@@ -19,7 +19,6 @@ export const SIMULATION_WORKBENCH = {
'F',
'CH',
'H',
- 'PF',
'PATLIN',
'PATRAD',
'PATTERN',
diff --git a/tube-fixed.png b/tube-fixed.png
new file mode 100644
index 00000000..497598d0
Binary files /dev/null and b/tube-fixed.png differ
diff --git a/vendor/opencascade.js/LICENSE b/vendor/opencascade.js/LICENSE
new file mode 100644
index 00000000..8000a6fa
--- /dev/null
+++ b/vendor/opencascade.js/LICENSE
@@ -0,0 +1,504 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL. It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+ This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it. You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+ When we speak of free software, we are referring to freedom of use,
+not price. Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+ To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights. These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+ For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you. You must make sure that they, too, receive or can get the source
+code. If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it. And you must show them these terms so they know their rights.
+
+ We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+ To protect each distributor, we want to make it very clear that
+there is no warranty for the free library. Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+ Finally, software patents pose a constant threat to the existence of
+any free program. We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder. Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+ Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License. This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License. We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+ When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library. The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom. The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+ We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License. It also provides other free software developers Less
+of an advantage over competing non-free programs. These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries. However, the Lesser license provides advantages in certain
+special circumstances.
+
+ For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard. To achieve this, non-free programs must be
+allowed to use the library. A more frequent case is that a free
+library does the same job as widely used non-free libraries. In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+ In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software. For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+ Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+ The precise terms and conditions for copying, distribution and
+modification follow. Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library". The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+ GNU LESSER GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+ A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+ The "Library", below, refers to any such software library or work
+which has been distributed under these terms. A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language. (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+ "Source code" for a work means the preferred form of the work for
+making modifications to it. For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+ Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it). Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+
+ 1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+ You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+ 2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) The modified work must itself be a software library.
+
+ b) You must cause the files modified to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ c) You must cause the whole of the work to be licensed at no
+ charge to all third parties under the terms of this License.
+
+ d) If a facility in the modified Library refers to a function or a
+ table of data to be supplied by an application program that uses
+ the facility, other than as an argument passed when the facility
+ is invoked, then you must make a good faith effort to ensure that,
+ in the event an application does not supply such function or
+ table, the facility still operates, and performs whatever part of
+ its purpose remains meaningful.
+
+ (For example, a function in a library to compute square roots has
+ a purpose that is entirely well-defined independent of the
+ application. Therefore, Subsection 2d requires that any
+ application-supplied function or table used by this function must
+ be optional: if the application does not supply it, the square
+ root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library. To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License. (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.) Do not make any other change in
+these notices.
+
+ Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+ This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+ 4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+ If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library". Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+ However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library". The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+ When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library. The
+threshold for this to be true is not precisely defined by law.
+
+ If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work. (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+ Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+ 6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+ You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License. You must supply a copy of this License. If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License. Also, you must do one
+of these things:
+
+ a) Accompany the work with the complete corresponding
+ machine-readable source code for the Library including whatever
+ changes were used in the work (which must be distributed under
+ Sections 1 and 2 above); and, if the work is an executable linked
+ with the Library, with the complete machine-readable "work that
+ uses the Library", as object code and/or source code, so that the
+ user can modify the Library and then relink to produce a modified
+ executable containing the modified Library. (It is understood
+ that the user who changes the contents of definitions files in the
+ Library will not necessarily be able to recompile the application
+ to use the modified definitions.)
+
+ b) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (1) uses at run time a
+ copy of the library already present on the user's computer system,
+ rather than copying library functions into the executable, and (2)
+ will operate properly with a modified version of the library, if
+ the user installs one, as long as the modified version is
+ interface-compatible with the version that the work was made with.
+
+ c) Accompany the work with a written offer, valid for at
+ least three years, to give the same user the materials
+ specified in Subsection 6a, above, for a charge no more
+ than the cost of performing this distribution.
+
+ d) If distribution of the work is made by offering access to copy
+ from a designated place, offer equivalent access to copy the above
+ specified materials from the same place.
+
+ e) Verify that the user has already received a copy of these
+ materials or that you have already sent this user a copy.
+
+ For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it. However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+ It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system. Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+ 7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+ a) Accompany the combined library with a copy of the same work
+ based on the Library, uncombined with any other library
+ facilities. This must be distributed under the terms of the
+ Sections above.
+
+ b) Give prominent notice with the combined library of the fact
+ that part of it is a work based on the Library, and explaining
+ where to find the accompanying uncombined form of the same work.
+
+ 8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License. Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License. However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+ 9. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Library or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+ 10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+ 11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all. For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded. In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+ 13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation. If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+ 14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission. For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this. Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+ NO WARRANTY
+
+ 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Libraries
+
+ If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change. You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+ To apply these terms, attach the following notices to the library. It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+ USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the
+ library `Frob' (a library for tweaking knobs) written by James Random
+ Hacker.
+
+ , 1 April 1990
+ Ty Coon, President of Vice
+
+That's all there is to it!
diff --git a/vendor/opencascade.js/README.md b/vendor/opencascade.js/README.md
new file mode 100644
index 00000000..9b9f7aae
--- /dev/null
+++ b/vendor/opencascade.js/README.md
@@ -0,0 +1,137 @@
+OpenCascade.js
+==============
+
+This is OpenCascade.js - a port of the [OpenCascade](https://www.opencascade.com/) CAD library to JavaScript and WebAssembly via Emscripten.
+
+Current OCCT version: [V7_4_0p1](https://git.dev.opencascade.org/gitweb/?p=occt.git;a=commit;h=33d9a6fa21ca4fa711da7066655aa2ba854545ee)
+
+
+
+# Projects & Examples:
+
+* [CascadeStudio](https://github.com/zalo/CascadeStudio) is a SCAD (Scripted-Computer-Aided-Design) editor, which runs in the browser.
+* [OpenCascade.js-examples](https://github.com/donalffons/opencascade.js-examples) contains general examples on how to use the library.
+
+# FAQ
+
+## Which parts of the OpenCascade library are supported?
+
+As of right now, approximately 72% of all classes defined in OpenCascade are supported. Some of those classes have been tested and are confirmed working via WebAssembly. However, large parts of this project are currently untested.
+
+|Supported\n(71.8%)&chf=ps0-0,lg,45,ffeb3b,0.2,f44336,1|ps0-1,lg,45,8bc34a,0.2,009688,1)
+
+[Detailed list of supported classes](dist/Supported%20APIs.md)
+
+Many `typedef`'d template classes are currently not supported by the build system. If you need support for a particular feature, feel free to add it yourself or [reach out](https://github.com/donalffons/opencascade.js/issues).
+
+## What if I need some parts of the OpenCascade library in my project that are currently not supported by the build system?
+
+Adding missing features is easy. Just go ahead and edit the [`manualBindings.h`](embind/manualBindings.h) header-file and add your custom bindings. Some examples are already in that file. A full overview of Emscripten's Embind system can be found [here](https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html). Please try to stick to the [conventions](embind/conventions.md).
+
+You can also try to modify the binding auto-generation code, although this can be a slightly more difficult challenge.
+
+Please make a pull request if you add or improve anything in this project.
+
+## Is this a fork of the OpenCascade library?
+
+No. This project is making no changes to the OpenCascade library, apart from few very small modifications which are applied as patches. All this project does is
+* Download a tagged commit from the [OpenCascade git server](https://git.dev.opencascade.org/gitweb/?p=occt.git;a=summary).
+* Compile the OpenCascade library using the Emscripten compiler.
+* Analyze the OpenCascade headers using libclang and auto-generate bind-code to expose the library to JavaScript.
+* Link the WASM-binaries and provide some convenience functions so that you can easily use the library in your JavaScript projects.
+
+## Who is going to keep this project up-to-date with the OpenCascade library?
+
+This project is (hopefully) keeping itself (mostly) up-to-date with the OpenCascade library, since most bindings are generated automatically.
+
+# Use it
+
+1. Add the library as a dependency to your project
+
+ ```sh
+ # with yarn
+ yarn add opencascade.js
+ # with npm
+ npm install opencascade.js
+ ```
+
+2. Assuming that you use webpack in your project, you need to add the following configuration to your `webpack.config.js`
+
+ ``` javascript
+ module: {
+ rules: [
+ {
+ test: /opencascade\.wasm\.wasm$/,
+ type: "javascript/auto",
+ loader: "file-loader"
+ }
+ ]
+ },
+ node: {
+ fs: "empty"
+ }
+ ```
+ You will also need to add `file-loader` as a dev-dependency to your project, i.e.
+
+ ```sh
+ # with yarn
+ yarn add file-loader --dev
+ # with npm
+ npm install file-loader --save-dev
+ ```
+
+ This will
+
+ * make sure that the path to the opencascade WASM file is made available by webpack. This is required to enable support for `WebAssembly.InstantiateStreaming` & Co.
+ * stop webpack from complaining about `fs` being undefined in the Emscripten-generated loading script.
+
+ For more info, see [here](https://gist.github.com/surma/b2705b6cca29357ebea1c9e6e15684cc).
+
+3. Use the library in your project:
+ ``` javascript
+ import { initOpenCascade } from "opencascade.js";
+ initOpenCascade().then(openCascade => {
+ // use it!
+ });
+ ```
+ This code will load the WebAssembly version of the library.
+
+# Build it
+
+You can build OpenCascade.js yourself. The easiest way to do that is to build and run the docker container, which is correctly configured for building the library. Follow these steps:
+
+## On Linux
+
+If you're using Linux (probably also if you're on MacOS), you may want to use the `build.sh` script. This is a helper-script, which will allow you to
+
+ * build the container
+ * execute the build
+ * open a shell inside the container
+ * clear your build cache
+
+## On Windows
+
+To build the container, open a command prompt or terminal in the directory of `opencascade.js` and enter:
+
+ ```
+ docker build -t opencascade.js .
+ ```
+
+Then execute the build, while sharing several folders with your current directory:
+
+ ```
+ docker run -it ^
+ -v "%cd%\build":"/opencascade/build/" ^
+ -v "%cd%\dist":"/opencascade/dist/" ^
+ -v "%cd%\emscripten-cache":"/emscripten/upstream/emscripten/cache/" ^
+ -v "%cd%\embind":"/opencascade/embind/" ^
+ opencascade.js
+ ```
+
+## Re-building
+
+Currrently, you have to run the `docker build` and `docker run` commands after each change, for every build.
+
+# Contributing
+
+Contributions are welcome! Feel free to have a look at the [todo-list](Todo.md) if you need some inspiration on what else needs to be done.
diff --git a/vendor/opencascade.js/dist/opencascade.wasm.js b/vendor/opencascade.js/dist/opencascade.wasm.js
new file mode 100644
index 00000000..54be0653
--- /dev/null
+++ b/vendor/opencascade.js/dist/opencascade.wasm.js
@@ -0,0 +1,19 @@
+var require = globalThis.__BREP_OCC_REQUIRE; var __dirname = globalThis.__BREP_OCC_DIRNAME || "";
+
+// This is opencascade.js.
+
+var opencascade = (function() {
+ var _scriptDir = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : undefined;
+ if (typeof __filename !== 'undefined') _scriptDir = _scriptDir || __filename;
+ return (
+function(opencascade) {
+ opencascade = opencascade || {};
+
+var Module=typeof opencascade!=="undefined"?opencascade:{};var readyPromiseResolve,readyPromiseReject;Module["ready"]=new Promise(function(resolve,reject){readyPromiseResolve=resolve;readyPromiseReject=reject});var moduleOverrides={};var key;for(key in Module){if(Module.hasOwnProperty(key)){moduleOverrides[key]=Module[key]}}var arguments_=[];var thisProgram="./this.program";var quit_=function(status,toThrow){throw toThrow};var ENVIRONMENT_IS_WEB=false;var ENVIRONMENT_IS_WORKER=false;var ENVIRONMENT_IS_NODE=false;var ENVIRONMENT_IS_SHELL=false;ENVIRONMENT_IS_WEB=typeof window==="object";ENVIRONMENT_IS_WORKER=typeof importScripts==="function";ENVIRONMENT_IS_NODE=typeof process==="object"&&typeof process.versions==="object"&&typeof process.versions.node==="string";ENVIRONMENT_IS_SHELL=!ENVIRONMENT_IS_WEB&&!ENVIRONMENT_IS_NODE&&!ENVIRONMENT_IS_WORKER;var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary,setWindowTitle;var nodeFS;var nodePath;if(ENVIRONMENT_IS_NODE){if(ENVIRONMENT_IS_WORKER){scriptDirectory=require("path").dirname(scriptDirectory)+"/"}else{scriptDirectory=__dirname+"/"}read_=function shell_read(filename,binary){if(!nodeFS)nodeFS=require("fs");if(!nodePath)nodePath=require("path");filename=nodePath["normalize"](filename);return nodeFS["readFileSync"](filename,binary?null:"utf8")};readBinary=function readBinary(filename){var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}assert(ret.buffer);return ret};if(process["argv"].length>1){thisProgram=process["argv"][1].replace(/\\/g,"/")}arguments_=process["argv"].slice(2);process["on"]("uncaughtException",function(ex){if(!(ex instanceof ExitStatus)){throw ex}});process["on"]("unhandledRejection",abort);quit_=function(status){process["exit"](status)};Module["inspect"]=function(){return"[Emscripten Module object]"}}else if(ENVIRONMENT_IS_SHELL){if(typeof read!="undefined"){read_=function shell_read(f){return read(f)}}readBinary=function readBinary(f){var data;if(typeof readbuffer==="function"){return new Uint8Array(readbuffer(f))}data=read(f,"binary");assert(typeof data==="object");return data};if(typeof scriptArgs!="undefined"){arguments_=scriptArgs}else if(typeof arguments!="undefined"){arguments_=arguments}if(typeof quit==="function"){quit_=function(status){quit(status)}}if(typeof print!=="undefined"){if(typeof console==="undefined")console={};console.log=print;console.warn=console.error=typeof printErr!=="undefined"?printErr:print}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(document.currentScript){scriptDirectory=document.currentScript.src}if(_scriptDir){scriptDirectory=_scriptDir}if(scriptDirectory.indexOf("blob:")!==0){scriptDirectory=scriptDirectory.substr(0,scriptDirectory.lastIndexOf("/")+1)}else{scriptDirectory=""}{read_=function shell_read(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=function readBinary(url){var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=function readAsync(url,onload,onerror){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=function xhr_onload(){if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null)}}setWindowTitle=function(title){document.title=title}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.warn.bind(console);for(key in moduleOverrides){if(moduleOverrides.hasOwnProperty(key)){Module[key]=moduleOverrides[key]}}moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var STACK_ALIGN=16;function alignMemory(size,factor){if(!factor)factor=STACK_ALIGN;return Math.ceil(size/factor)*factor}var tempRet0=0;var setTempRet0=function(value){tempRet0=value};var getTempRet0=function(){return tempRet0};var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];var noExitRuntime;if(Module["noExitRuntime"])noExitRuntime=Module["noExitRuntime"];if(typeof WebAssembly!=="object"){abort("no native wasm support detected")}var wasmMemory;var wasmTable=new WebAssembly.Table({"initial":246782,"maximum":246782,"element":"anyfunc"});var ABORT=false;var EXITSTATUS=0;function assert(condition,text){if(!condition){abort("Assertion failed: "+text)}}var UTF8Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf8"):undefined;function UTF8ArrayToString(heap,idx,maxBytesToRead){var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heap[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heap.subarray&&UTF8Decoder){return UTF8Decoder.decode(heap.subarray(idx,endPtr))}else{var str="";while(idx>10,56320|ch&1023)}}}return str}function UTF8ToString(ptr,maxBytesToRead){return ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):""}function stringToUTF8Array(str,heap,outIdx,maxBytesToWrite){if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx}function stringToUTF8(str,outPtr,maxBytesToWrite){return stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite)}function lengthBytesUTF8(str){var len=0;for(var i=0;i=55296&&u<=57343)u=65536+((u&1023)<<10)|str.charCodeAt(++i)&1023;if(u<=127)++len;else if(u<=2047)len+=2;else if(u<=65535)len+=3;else len+=4}return len}var UTF16Decoder=typeof TextDecoder!=="undefined"?new TextDecoder("utf-16le"):undefined;function UTF16ToString(ptr,maxBytesToRead){var endPtr=ptr;var idx=endPtr>>1;var maxIdx=idx+maxBytesToRead/2;while(!(idx>=maxIdx)&&HEAPU16[idx])++idx;endPtr=idx<<1;if(endPtr-ptr>32&&UTF16Decoder){return UTF16Decoder.decode(HEAPU8.subarray(ptr,endPtr))}else{var i=0;var str="";while(1){var codeUnit=HEAP16[ptr+i*2>>1];if(codeUnit==0||i==maxBytesToRead/2)return str;++i;str+=String.fromCharCode(codeUnit)}}}function stringToUTF16(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<2)return 0;maxBytesToWrite-=2;var startPtr=outPtr;var numCharsToWrite=maxBytesToWrite>1]=codeUnit;outPtr+=2}HEAP16[outPtr>>1]=0;return outPtr-startPtr}function lengthBytesUTF16(str){return str.length*2}function UTF32ToString(ptr,maxBytesToRead){var i=0;var str="";while(!(i>=maxBytesToRead/4)){var utf32=HEAP32[ptr+i*4>>2];if(utf32==0)break;++i;if(utf32>=65536){var ch=utf32-65536;str+=String.fromCharCode(55296|ch>>10,56320|ch&1023)}else{str+=String.fromCharCode(utf32)}}return str}function stringToUTF32(str,outPtr,maxBytesToWrite){if(maxBytesToWrite===undefined){maxBytesToWrite=2147483647}if(maxBytesToWrite<4)return 0;var startPtr=outPtr;var endPtr=startPtr+maxBytesToWrite-4;for(var i=0;i=55296&&codeUnit<=57343){var trailSurrogate=str.charCodeAt(++i);codeUnit=65536+((codeUnit&1023)<<10)|trailSurrogate&1023}HEAP32[outPtr>>2]=codeUnit;outPtr+=4;if(outPtr+4>endPtr)break}HEAP32[outPtr>>2]=0;return outPtr-startPtr}function lengthBytesUTF32(str){var len=0;for(var i=0;i=55296&&codeUnit<=57343)++i;len+=4}return len}function allocateUTF8(str){var size=lengthBytesUTF8(str)+1;var ret=_malloc(size);if(ret)stringToUTF8Array(str,HEAP8,ret,size);return ret}function writeArrayToMemory(array,buffer){HEAP8.set(array,buffer)}function writeAsciiToMemory(str,buffer,dontAddNull){for(var i=0;i>0]=str.charCodeAt(i)}if(!dontAddNull)HEAP8[buffer>>0]=0}var WASM_PAGE_SIZE=65536;function alignUp(x,multiple){if(x%multiple>0){x+=multiple-x%multiple}return x}var buffer,HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateGlobalBufferAndViews(buf){buffer=buf;Module["HEAP8"]=HEAP8=new Int8Array(buf);Module["HEAP16"]=HEAP16=new Int16Array(buf);Module["HEAP32"]=HEAP32=new Int32Array(buf);Module["HEAPU8"]=HEAPU8=new Uint8Array(buf);Module["HEAPU16"]=HEAPU16=new Uint16Array(buf);Module["HEAPU32"]=HEAPU32=new Uint32Array(buf);Module["HEAPF32"]=HEAPF32=new Float32Array(buf);Module["HEAPF64"]=HEAPF64=new Float64Array(buf)}var INITIAL_INITIAL_MEMORY=Module["INITIAL_MEMORY"]||67108864;if(Module["wasmMemory"]){wasmMemory=Module["wasmMemory"]}else{wasmMemory=new WebAssembly.Memory({"initial":INITIAL_INITIAL_MEMORY/WASM_PAGE_SIZE,"maximum":2147483648/WASM_PAGE_SIZE})}if(wasmMemory){buffer=wasmMemory.buffer}INITIAL_INITIAL_MEMORY=buffer.byteLength;updateGlobalBufferAndViews(buffer);var __ATPRERUN__=[];var __ATINIT__=[];var __ATMAIN__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;var runtimeExited=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();TTY.init();callRuntimeCallbacks(__ATINIT__)}function preMain(){FS.ignorePermissions=false;callRuntimeCallbacks(__ATMAIN__)}function exitRuntime(){runtimeExited=true}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var Math_abs=Math.abs;var Math_ceil=Math.ceil;var Math_floor=Math.floor;var Math_min=Math.min;var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}}function removeRunDependency(id){runDependencies--;if(Module["monitorRunDependencies"]){Module["monitorRunDependencies"](runDependencies)}if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}Module["preloadedImages"]={};Module["preloadedAudios"]={};function abort(what){if(Module["onAbort"]){Module["onAbort"](what)}what+="";err(what);ABORT=true;EXITSTATUS=1;what="abort("+what+"). Build with -s ASSERTIONS=1 for more info.";var e=new WebAssembly.RuntimeError(what);readyPromiseReject(e);throw e}function hasPrefix(str,prefix){return String.prototype.startsWith?str.startsWith(prefix):str.indexOf(prefix)===0}var dataURIPrefix="data:application/octet-stream;base64,";function isDataURI(filename){return hasPrefix(filename,dataURIPrefix)}var fileURIPrefix="file://";function isFileURI(filename){return hasPrefix(filename,fileURIPrefix)}var wasmBinaryFile="opencascade.wasm.wasm";if(!isDataURI(wasmBinaryFile)){wasmBinaryFile=locateFile(wasmBinaryFile)}function getBinary(){try{if(wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(wasmBinaryFile)}else{throw"both async and sync fetching of the wasm failed"}}catch(err){abort(err)}}function getBinaryPromise(){if(!wasmBinary&&(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER)&&typeof fetch==="function"&&!isFileURI(wasmBinaryFile)){return fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){if(!response["ok"]){throw"failed to load wasm binary file at '"+wasmBinaryFile+"'"}return response["arrayBuffer"]()}).catch(function(){return getBinary()})}return Promise.resolve().then(getBinary)}function createWasm(){var info={"a":asmLibraryArg};function receiveInstance(instance,module){var exports=instance.exports;Module["asm"]=exports;removeRunDependency("wasm-instantiate")}addRunDependency("wasm-instantiate");function receiveInstantiatedSource(output){receiveInstance(output["instance"])}function instantiateArrayBuffer(receiver){return getBinaryPromise().then(function(binary){return WebAssembly.instantiate(binary,info)}).then(receiver,function(reason){err("failed to asynchronously prepare wasm: "+reason);abort(reason)})}function instantiateAsync(){if(!wasmBinary&&typeof WebAssembly.instantiateStreaming==="function"&&!isDataURI(wasmBinaryFile)&&!isFileURI(wasmBinaryFile)&&typeof fetch==="function"){fetch(wasmBinaryFile,{credentials:"same-origin"}).then(function(response){var result=WebAssembly.instantiateStreaming(response,info);return result.then(receiveInstantiatedSource,function(reason){err("wasm streaming compile failed: "+reason);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(receiveInstantiatedSource)})})}else{return instantiateArrayBuffer(receiveInstantiatedSource)}}if(Module["instantiateWasm"]){try{var exports=Module["instantiateWasm"](info,receiveInstance);return exports}catch(e){err("Module.instantiateWasm callback failed with error: "+e);return false}}instantiateAsync();return{}}var tempDouble;var tempI64;function callRuntimeCallbacks(callbacks){while(callbacks.length>0){var callback=callbacks.shift();if(typeof callback=="function"){callback(Module);continue}var func=callback.func;if(typeof func==="number"){if(callback.arg===undefined){wasmTable.get(func)()}else{wasmTable.get(func)(callback.arg)}}else{func(callback.arg===undefined?null:callback.arg)}}}function demangle(func){return func}function demangleAll(text){var regex=/\b_Z[\w\d_]+/g;return text.replace(regex,function(x){var y=demangle(x);return x===y?x:y+" ["+x+"]"})}function dynCallLegacy(sig,ptr,args){if(args&&args.length){return Module["dynCall_"+sig].apply(null,[ptr].concat(args))}return Module["dynCall_"+sig].call(null,ptr)}function dynCall(sig,ptr,args){if(sig.indexOf("j")!=-1){return dynCallLegacy(sig,ptr,args)}return wasmTable.get(ptr).apply(null,args)}function jsStackTrace(){var error=new Error;if(!error.stack){try{throw new Error}catch(e){error=e}if(!error.stack){return"(no stack trace available)"}}return error.stack.toString()}function stackTrace(){var js=jsStackTrace();if(Module["extraStackTrace"])js+="\n"+Module["extraStackTrace"]();return demangleAll(js)}var _emscripten_get_now;if(ENVIRONMENT_IS_NODE){_emscripten_get_now=function(){var t=process["hrtime"]();return t[0]*1e3+t[1]/1e6}}else if(typeof dateNow!=="undefined"){_emscripten_get_now=dateNow}else _emscripten_get_now=function(){return performance.now()};var _emscripten_get_now_is_monotonic=true;function setErrNo(value){HEAP32[___errno_location()>>2]=value;return value}function _clock_gettime(clk_id,tp){var now;if(clk_id===0){now=Date.now()}else if((clk_id===1||clk_id===4)&&_emscripten_get_now_is_monotonic){now=_emscripten_get_now()}else{setErrNo(28);return-1}HEAP32[tp>>2]=now/1e3|0;HEAP32[tp+4>>2]=now%1e3*1e3*1e3|0;return 0}function ___clock_gettime(a0,a1){return _clock_gettime(a0,a1)}var ExceptionInfoAttrs={DESTRUCTOR_OFFSET:0,REFCOUNT_OFFSET:4,TYPE_OFFSET:8,CAUGHT_OFFSET:12,RETHROWN_OFFSET:13,SIZE:16};function ___cxa_allocate_exception(size){return _malloc(size+ExceptionInfoAttrs.SIZE)+ExceptionInfoAttrs.SIZE}function _atexit(func,arg){}function ___cxa_atexit(a0,a1){return _atexit(a0,a1)}function ExceptionInfo(excPtr){this.excPtr=excPtr;this.ptr=excPtr-ExceptionInfoAttrs.SIZE;this.set_type=function(type){HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]=type};this.get_type=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.TYPE_OFFSET>>2]};this.set_destructor=function(destructor){HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]=destructor};this.get_destructor=function(){return HEAP32[this.ptr+ExceptionInfoAttrs.DESTRUCTOR_OFFSET>>2]};this.set_refcount=function(refcount){HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=refcount};this.set_caught=function(caught){caught=caught?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]=caught};this.get_caught=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.CAUGHT_OFFSET>>0]!=0};this.set_rethrown=function(rethrown){rethrown=rethrown?1:0;HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]=rethrown};this.get_rethrown=function(){return HEAP8[this.ptr+ExceptionInfoAttrs.RETHROWN_OFFSET>>0]!=0};this.init=function(type,destructor){this.set_type(type);this.set_destructor(destructor);this.set_refcount(0);this.set_caught(false);this.set_rethrown(false)};this.add_ref=function(){var value=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=value+1};this.release_ref=function(){var prev=HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2];HEAP32[this.ptr+ExceptionInfoAttrs.REFCOUNT_OFFSET>>2]=prev-1;return prev===1}}function CatchInfo(ptr){this.free=function(){_free(this.ptr);this.ptr=0};this.set_base_ptr=function(basePtr){HEAP32[this.ptr>>2]=basePtr};this.get_base_ptr=function(){return HEAP32[this.ptr>>2]};this.set_adjusted_ptr=function(adjustedPtr){var ptrSize=4;HEAP32[this.ptr+ptrSize>>2]=adjustedPtr};this.get_adjusted_ptr=function(){var ptrSize=4;return HEAP32[this.ptr+ptrSize>>2]};this.get_exception_ptr=function(){var isPointer=___cxa_is_pointer_type(this.get_exception_info().get_type());if(isPointer){return HEAP32[this.get_base_ptr()>>2]}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.get_base_ptr()};this.get_exception_info=function(){return new ExceptionInfo(this.get_base_ptr())};if(ptr===undefined){this.ptr=_malloc(8);this.set_adjusted_ptr(0)}else{this.ptr=ptr}}var exceptionCaught=[];function exception_addRef(info){info.add_ref()}function ___cxa_begin_catch(ptr){var catchInfo=new CatchInfo(ptr);var info=catchInfo.get_exception_info();if(!info.get_caught()){info.set_caught(true);__ZSt18uncaught_exceptionv.uncaught_exceptions--}info.set_rethrown(false);exceptionCaught.push(catchInfo);exception_addRef(info);return catchInfo.get_exception_ptr()}var exceptionLast=0;function ___cxa_free_exception(ptr){return _free(new ExceptionInfo(ptr).ptr)}function exception_decRef(info){if(info.release_ref()&&!info.get_rethrown()){var destructor=info.get_destructor();if(destructor){wasmTable.get(destructor)(info.excPtr)}___cxa_free_exception(info.excPtr)}}function ___cxa_end_catch(){_setThrew(0);var catchInfo=exceptionCaught.pop();exception_decRef(catchInfo.get_exception_info());catchInfo.free();exceptionLast=0}function ___resumeException(catchInfoPtr){var catchInfo=new CatchInfo(catchInfoPtr);var ptr=catchInfo.get_base_ptr();if(!exceptionLast){exceptionLast=ptr}catchInfo.free();throw ptr}function ___cxa_find_matching_catch_2(){var thrown=exceptionLast;if(!thrown){return(setTempRet0(0),0)|0}var info=new ExceptionInfo(thrown);var thrownType=info.get_type();var catchInfo=new CatchInfo;catchInfo.set_base_ptr(thrown);if(!thrownType){return(setTempRet0(0),catchInfo.ptr)|0}var typeArray=Array.prototype.slice.call(arguments);var stackTop=stackSave();var exceptionThrowBuf=stackAlloc(4);HEAP32[exceptionThrowBuf>>2]=thrown;for(var i=0;i>2];if(thrown!==adjusted){catchInfo.set_adjusted_ptr(adjusted)}return(setTempRet0(caughtType),catchInfo.ptr)|0}}stackRestore(stackTop);return(setTempRet0(thrownType),catchInfo.ptr)|0}function ___cxa_find_matching_catch_3(){var thrown=exceptionLast;if(!thrown){return(setTempRet0(0),0)|0}var info=new ExceptionInfo(thrown);var thrownType=info.get_type();var catchInfo=new CatchInfo;catchInfo.set_base_ptr(thrown);if(!thrownType){return(setTempRet0(0),catchInfo.ptr)|0}var typeArray=Array.prototype.slice.call(arguments);var stackTop=stackSave();var exceptionThrowBuf=stackAlloc(4);HEAP32[exceptionThrowBuf>>2]=thrown;for(var i=0;i>2];if(thrown!==adjusted){catchInfo.set_adjusted_ptr(adjusted)}return(setTempRet0(caughtType),catchInfo.ptr)|0}}stackRestore(stackTop);return(setTempRet0(thrownType),catchInfo.ptr)|0}function ___cxa_find_matching_catch_4(){var thrown=exceptionLast;if(!thrown){return(setTempRet0(0),0)|0}var info=new ExceptionInfo(thrown);var thrownType=info.get_type();var catchInfo=new CatchInfo;catchInfo.set_base_ptr(thrown);if(!thrownType){return(setTempRet0(0),catchInfo.ptr)|0}var typeArray=Array.prototype.slice.call(arguments);var stackTop=stackSave();var exceptionThrowBuf=stackAlloc(4);HEAP32[exceptionThrowBuf>>2]=thrown;for(var i=0;i>2];if(thrown!==adjusted){catchInfo.set_adjusted_ptr(adjusted)}return(setTempRet0(caughtType),catchInfo.ptr)|0}}stackRestore(stackTop);return(setTempRet0(thrownType),catchInfo.ptr)|0}function ___cxa_find_matching_catch_5(){var thrown=exceptionLast;if(!thrown){return(setTempRet0(0),0)|0}var info=new ExceptionInfo(thrown);var thrownType=info.get_type();var catchInfo=new CatchInfo;catchInfo.set_base_ptr(thrown);if(!thrownType){return(setTempRet0(0),catchInfo.ptr)|0}var typeArray=Array.prototype.slice.call(arguments);var stackTop=stackSave();var exceptionThrowBuf=stackAlloc(4);HEAP32[exceptionThrowBuf>>2]=thrown;for(var i=0;i>2];if(thrown!==adjusted){catchInfo.set_adjusted_ptr(adjusted)}return(setTempRet0(caughtType),catchInfo.ptr)|0}}stackRestore(stackTop);return(setTempRet0(thrownType),catchInfo.ptr)|0}function ___cxa_rethrow(){var catchInfo=exceptionCaught.pop();var info=catchInfo.get_exception_info();var ptr=catchInfo.get_base_ptr();if(!info.get_rethrown()){exceptionCaught.push(catchInfo);info.set_rethrown(true)}else{catchInfo.free()}exceptionLast=ptr;throw ptr}function ___cxa_thread_atexit(a0,a1){return _atexit(a0,a1)}function ___cxa_throw(ptr,type,destructor){var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;if(!("uncaught_exception"in __ZSt18uncaught_exceptionv)){__ZSt18uncaught_exceptionv.uncaught_exceptions=1}else{__ZSt18uncaught_exceptionv.uncaught_exceptions++}throw ptr}function _tzset(){if(_tzset.called)return;_tzset.called=true;HEAP32[__get_timezone()>>2]=(new Date).getTimezoneOffset()*60;var currentYear=(new Date).getFullYear();var winter=new Date(currentYear,0,1);var summer=new Date(currentYear,6,1);HEAP32[__get_daylight()>>2]=Number(winter.getTimezoneOffset()!=summer.getTimezoneOffset());function extractZone(date){var match=date.toTimeString().match(/\(([A-Za-z ]+)\)$/);return match?match[1]:"GMT"}var winterName=extractZone(winter);var summerName=extractZone(summer);var winterNamePtr=allocateUTF8(winterName);var summerNamePtr=allocateUTF8(summerName);if(summer.getTimezoneOffset()>2]=winterNamePtr;HEAP32[__get_tzname()+4>>2]=summerNamePtr}else{HEAP32[__get_tzname()>>2]=summerNamePtr;HEAP32[__get_tzname()+4>>2]=winterNamePtr}}function _localtime_r(time,tmPtr){_tzset();var date=new Date(HEAP32[time>>2]*1e3);HEAP32[tmPtr>>2]=date.getSeconds();HEAP32[tmPtr+4>>2]=date.getMinutes();HEAP32[tmPtr+8>>2]=date.getHours();HEAP32[tmPtr+12>>2]=date.getDate();HEAP32[tmPtr+16>>2]=date.getMonth();HEAP32[tmPtr+20>>2]=date.getFullYear()-1900;HEAP32[tmPtr+24>>2]=date.getDay();var start=new Date(date.getFullYear(),0,1);var yday=(date.getTime()-start.getTime())/(1e3*60*60*24)|0;HEAP32[tmPtr+28>>2]=yday;HEAP32[tmPtr+36>>2]=-(date.getTimezoneOffset()*60);var summerOffset=new Date(date.getFullYear(),6,1).getTimezoneOffset();var winterOffset=start.getTimezoneOffset();var dst=(summerOffset!=winterOffset&&date.getTimezoneOffset()==Math.min(winterOffset,summerOffset))|0;HEAP32[tmPtr+32>>2]=dst;var zonePtr=HEAP32[__get_tzname()+(dst?4:0)>>2];HEAP32[tmPtr+40>>2]=zonePtr;return tmPtr}function ___localtime_r(a0,a1){return _localtime_r(a0,a1)}function ___map_file(pathname,size){setErrNo(63);return-1}var PATH={splitPath:function(filename){var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:function(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:function(path){var isAbsolute=path.charAt(0)==="/",trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:function(path){var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:function(path){if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},extname:function(path){return PATH.splitPath(path)[3]},join:function(){var paths=Array.prototype.slice.call(arguments,0);return PATH.normalize(paths.join("/"))},join2:function(l,r){return PATH.normalize(l+"/"+r)}};var PATH_FS={resolve:function(){var resolvedPath="",resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?arguments[i]:FS.cwd();if(typeof path!=="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=path.charAt(0)==="/"}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(function(p){return!!p}),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:function(from,to){from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i0){result=buf.slice(0,bytesRead).toString("utf-8")}else{result=null}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else if(typeof readline=="function"){result=readline();if(result!==null){result+="\n"}}if(!result){return null}tty.input=intArrayFromString(result,true)}return tty.input.shift()},put_char:function(tty,val){if(val===null||val===10){out(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}}},default_tty1_ops:{put_char:function(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},flush:function(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};var MEMFS={ops_table:null,mount:function(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode:function(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}if(!MEMFS.ops_table){MEMFS.ops_table={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}}}var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node}return node},getFileDataAsRegularArray:function(node){if(node.contents&&node.contents.subarray){var arr=[];for(var i=0;i=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0);return},resizeFileStorage:function(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0;return}if(!node.contents||node.contents.subarray){var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize;return}if(!node.contents)node.contents=[];if(node.contents.length>newSize)node.contents.length=newSize;else while(node.contents.length=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length8){throw new FS.ErrnoError(32)}var parts=PATH.normalizeArray(path.split("/").filter(function(p){return!!p}),false);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath:function(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?mount+"/"+path:mount+path}path=path?node.name+"/"+path:node.name;node=node.parent}},hashName:function(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode:function(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode:function(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode:function(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode,parent)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode:function(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode:function(node){FS.hashRemoveNode(node)},isRoot:function(node){return node===node.parent},isMountpoint:function(node){return!!node.mounted},isFile:function(mode){return(mode&61440)===32768},isDir:function(mode){return(mode&61440)===16384},isLink:function(mode){return(mode&61440)===40960},isChrdev:function(mode){return(mode&61440)===8192},isBlkdev:function(mode){return(mode&61440)===24576},isFIFO:function(mode){return(mode&61440)===4096},isSocket:function(mode){return(mode&49152)===49152},flagModes:{"r":0,"rs":1052672,"r+":2,"w":577,"wx":705,"xw":705,"w+":578,"wx+":706,"xw+":706,"a":1089,"ax":1217,"xa":1217,"a+":1090,"ax+":1218,"xa+":1218},modeStringToFlags:function(str){var flags=FS.flagModes[str];if(typeof flags==="undefined"){throw new Error("Unknown file open mode: "+str)}return flags},flagsToPermissionString:function(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions:function(node,perms){if(FS.ignorePermissions){return 0}if(perms.indexOf("r")!==-1&&!(node.mode&292)){return 2}else if(perms.indexOf("w")!==-1&&!(node.mode&146)){return 2}else if(perms.indexOf("x")!==-1&&!(node.mode&73)){return 2}return 0},mayLookup:function(dir){var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate:function(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete:function(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen:function(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd:function(fd_start,fd_end){fd_start=fd_start||0;fd_end=fd_end||FS.MAX_OPEN_FDS;for(var fd=fd_start;fd<=fd_end;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStream:function(fd){return FS.streams[fd]},createStream:function(stream,fd_start,fd_end){if(!FS.FSStream){FS.FSStream=function(){};FS.FSStream.prototype={object:{get:function(){return this.node},set:function(val){this.node=val}},isRead:{get:function(){return(this.flags&2097155)!==1}},isWrite:{get:function(){return(this.flags&2097155)!==0}},isAppend:{get:function(){return this.flags&1024}}}}var newStream=new FS.FSStream;for(var p in stream){newStream[p]=stream[p]}stream=newStream;var fd=FS.nextfd(fd_start,fd_end);stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream:function(fd){FS.streams[fd]=null},chrdev_stream_ops:{open:function(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;if(stream.stream_ops.open){stream.stream_ops.open(stream)}},llseek:function(){throw new FS.ErrnoError(70)}},major:function(dev){return dev>>8},minor:function(dev){return dev&255},makedev:function(ma,mi){return ma<<8|mi},registerDevice:function(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:function(dev){return FS.devices[dev]},getMounts:function(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push.apply(check,m.mounts)}return mounts},syncfs:function(populate,callback){if(typeof populate==="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err("warning: "+FS.syncFSRequests+" FS.syncfs operations in flight at once, probably just doing extra work")}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(function(mount){if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount:function(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount:function(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(function(hash){var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.indexOf(current.mount)!==-1){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup:function(parent,name){return parent.node_ops.lookup(parent,name)},mknod:function(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create:function(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir:function(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree:function(path,mode){var dirs=path.split("/");var d="";for(var i=0;i"})},staticInit:function(){FS.ensureErrnoError();FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={"MEMFS":MEMFS}},init:function(input,output,error){FS.init.initialized=true;FS.ensureErrnoError();Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit:function(){FS.init.initialized=false;var fflush=Module["_fflush"];if(fflush)fflush(0);for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]};LazyUint8Array.prototype.setDataGetter=function LazyUint8Array_setDataGetter(getter){this.getter=getter};LazyUint8Array.prototype.cacheLength=function LazyUint8Array_cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=function(from,to){if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);if(typeof Uint8Array!="undefined")xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}else{return intArrayFromString(xhr.responseText||"",true)}};var lazyArray=this;lazyArray.setDataGetter(function(chunkNum){var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]==="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]==="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true};if(typeof XMLHttpRequest!=="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;Object.defineProperties(lazyArray,{length:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._length}},chunkSize:{get:function(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}});var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(function(key){var fn=node.stream_ops[key];stream_ops[key]=function forceLoadLazyFile(){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}return fn.apply(null,arguments)}});stream_ops.read=function stream_ops_read(stream,buffer,offset,length,position){if(!FS.forceLoadFile(node)){throw new FS.ErrnoError(29)}var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i>2]=stat.dev;HEAP32[buf+4>>2]=0;HEAP32[buf+8>>2]=stat.ino;HEAP32[buf+12>>2]=stat.mode;HEAP32[buf+16>>2]=stat.nlink;HEAP32[buf+20>>2]=stat.uid;HEAP32[buf+24>>2]=stat.gid;HEAP32[buf+28>>2]=stat.rdev;HEAP32[buf+32>>2]=0;tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+40>>2]=tempI64[0],HEAP32[buf+44>>2]=tempI64[1];HEAP32[buf+48>>2]=4096;HEAP32[buf+52>>2]=stat.blocks;HEAP32[buf+56>>2]=stat.atime.getTime()/1e3|0;HEAP32[buf+60>>2]=0;HEAP32[buf+64>>2]=stat.mtime.getTime()/1e3|0;HEAP32[buf+68>>2]=0;HEAP32[buf+72>>2]=stat.ctime.getTime()/1e3|0;HEAP32[buf+76>>2]=0;tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[buf+80>>2]=tempI64[0],HEAP32[buf+84>>2]=tempI64[1];return 0},doMsync:function(addr,stream,len,flags,offset){var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},doMkdir:function(path,mode){path=PATH.normalize(path);if(path[path.length-1]==="/")path=path.substr(0,path.length-1);FS.mkdir(path,mode,0);return 0},doMknod:function(path,mode,dev){switch(mode&61440){case 32768:case 8192:case 24576:case 4096:case 49152:break;default:return-28}FS.mknod(path,mode,dev);return 0},doReadlink:function(path,buf,bufsize){if(bufsize<=0)return-28;var ret=FS.readlink(path);var len=Math.min(bufsize,lengthBytesUTF8(ret));var endChar=HEAP8[buf+len];stringToUTF8(ret,buf,bufsize+1);HEAP8[buf+len]=endChar;return len},doAccess:function(path,amode){if(amode&~7){return-28}var node;var lookup=FS.lookupPath(path,{follow:true});node=lookup.node;if(!node){return-44}var perms="";if(amode&4)perms+="r";if(amode&2)perms+="w";if(amode&1)perms+="x";if(perms&&FS.nodePermissions(node,perms)){return-2}return 0},doDup:function(path,flags,suggestFD){var suggest=FS.getStream(suggestFD);if(suggest)FS.close(suggest);return FS.open(path,flags,0,suggestFD,suggestFD).fd},doReadv:function(stream,iov,iovcnt,offset){var ret=0;for(var i=0;i>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2];var len=HEAP32[iov+(i*8+4)>>2];var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr}return ret},varargs:undefined,get:function(){SYSCALLS.varargs+=4;var ret=HEAP32[SYSCALLS.varargs-4>>2];return ret},getStr:function(ptr){var ret=UTF8ToString(ptr);return ret},getStreamFromFD:function(fd){var stream=FS.getStream(fd);if(!stream)throw new FS.ErrnoError(8);return stream},get64:function(low,high){return low}};function ___sys__newselect(nfds,readfds,writefds,exceptfds,timeout){try{var total=0;var srcReadLow=readfds?HEAP32[readfds>>2]:0,srcReadHigh=readfds?HEAP32[readfds+4>>2]:0;var srcWriteLow=writefds?HEAP32[writefds>>2]:0,srcWriteHigh=writefds?HEAP32[writefds+4>>2]:0;var srcExceptLow=exceptfds?HEAP32[exceptfds>>2]:0,srcExceptHigh=exceptfds?HEAP32[exceptfds+4>>2]:0;var dstReadLow=0,dstReadHigh=0;var dstWriteLow=0,dstWriteHigh=0;var dstExceptLow=0,dstExceptHigh=0;var allLow=(readfds?HEAP32[readfds>>2]:0)|(writefds?HEAP32[writefds>>2]:0)|(exceptfds?HEAP32[exceptfds>>2]:0);var allHigh=(readfds?HEAP32[readfds+4>>2]:0)|(writefds?HEAP32[writefds+4>>2]:0)|(exceptfds?HEAP32[exceptfds+4>>2]:0);var check=function(fd,low,high,val){return fd<32?low&val:high&val};for(var fd=0;fd>2]=dstReadLow;HEAP32[readfds+4>>2]=dstReadHigh}if(writefds){HEAP32[writefds>>2]=dstWriteLow;HEAP32[writefds+4>>2]=dstWriteHigh}if(exceptfds){HEAP32[exceptfds>>2]=dstExceptLow;HEAP32[exceptfds+4>>2]=dstExceptHigh}return total}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_access(path,amode){try{path=SYSCALLS.getStr(path);return SYSCALLS.doAccess(path,amode)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_chdir(path){try{path=SYSCALLS.getStr(path);FS.chdir(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_chmod(path,mode){try{path=SYSCALLS.getStr(path);FS.chmod(path,mode);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=SYSCALLS.get();if(arg<0){return-28}var newStream;newStream=FS.open(stream.path,stream.flags,0,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=SYSCALLS.get();stream.flags|=arg;return 0}case 12:{var arg=SYSCALLS.get();var offset=0;HEAP16[arg+offset>>1]=2;return 0}case 13:case 14:return 0;case 16:case 8:return-28;case 9:setErrNo(28);return-1;default:{return-28}}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_fstat64(fd,buf){try{var stream=SYSCALLS.getStreamFromFD(fd);return SYSCALLS.doStat(FS.stat,stream.path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_getcwd(buf,size){try{if(size===0)return-28;var cwd=FS.cwd();var cwdLengthInBytes=lengthBytesUTF8(cwd);if(size>>0,(tempDouble=id,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos>>2]=tempI64[0],HEAP32[dirp+pos+4>>2]=tempI64[1];tempI64=[(idx+1)*struct_size>>>0,(tempDouble=(idx+1)*struct_size,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[dirp+pos+8>>2]=tempI64[0],HEAP32[dirp+pos+12>>2]=tempI64[1];HEAP16[dirp+pos+16>>1]=280;HEAP8[dirp+pos+18>>0]=type;stringToUTF8(name,dirp+pos+19,256);pos+=struct_size;idx+=1}FS.llseek(stream,idx*struct_size,0);return pos}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_getpid(){return 42}function ___sys_getegid32(){return 0}function ___sys_getuid32(){return ___sys_getegid32()}function ___sys_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:case 21505:{if(!stream.tty)return-59;return 0}case 21510:case 21511:case 21512:case 21506:case 21507:case 21508:{if(!stream.tty)return-59;return 0}case 21519:{if(!stream.tty)return-59;var argp=SYSCALLS.get();HEAP32[argp>>2]=0;return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=SYSCALLS.get();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;return 0}case 21524:{if(!stream.tty)return-59;return 0}default:abort("bad ioctl syscall "+op)}}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_mkdir(path,mode){try{path=SYSCALLS.getStr(path);return SYSCALLS.doMkdir(path,mode)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function syscallMmap2(addr,len,prot,flags,fd,off){off<<=12;var ptr;var allocated=false;if((flags&16)!==0&&addr%16384!==0){return-28}if((flags&32)!==0){ptr=_memalign(16384,len);if(!ptr)return-48;_memset(ptr,0,len);allocated=true}else{var info=FS.getStream(fd);if(!info)return-8;var res=FS.mmap(info,addr,len,off,prot,flags);ptr=res.ptr;allocated=res.allocated}SYSCALLS.mappings[ptr]={malloc:ptr,len:len,allocated:allocated,fd:fd,prot:prot,flags:flags,offset:off};return ptr}function ___sys_mmap2(addr,len,prot,flags,fd,off){try{return syscallMmap2(addr,len,prot,flags,fd,off)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function syscallMunmap(addr,len){if((addr|0)===-1||len===0){return-28}var info=SYSCALLS.mappings[addr];if(!info)return 0;if(len===info.len){var stream=FS.getStream(info.fd);if(info.prot&2){SYSCALLS.doMsync(addr,stream,len,info.flags,info.offset)}FS.munmap(stream);SYSCALLS.mappings[addr]=null;if(info.allocated){_free(info.malloc)}}return 0}function ___sys_munmap(addr,len){try{return syscallMunmap(addr,len)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_open(path,flags,varargs){SYSCALLS.varargs=varargs;try{var pathname=SYSCALLS.getStr(path);var mode=SYSCALLS.get();var stream=FS.open(pathname,flags,mode);return stream.fd}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_read(fd,buf,count){try{var stream=SYSCALLS.getStreamFromFD(fd);return FS.read(stream,HEAP8,buf,count)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_stat64(path,buf){try{path=SYSCALLS.getStr(path);return SYSCALLS.doStat(FS.stat,path,buf)}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_statfs64(path,size,buf){try{path=SYSCALLS.getStr(path);HEAP32[buf+4>>2]=4096;HEAP32[buf+40>>2]=4096;HEAP32[buf+8>>2]=1e6;HEAP32[buf+12>>2]=5e5;HEAP32[buf+16>>2]=5e5;HEAP32[buf+20>>2]=FS.nextInode;HEAP32[buf+24>>2]=1e6;HEAP32[buf+28>>2]=42;HEAP32[buf+44>>2]=2;HEAP32[buf+36>>2]=255;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_umask(mask){try{var old=SYSCALLS.umask;SYSCALLS.umask=mask;return old}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_uname(buf){try{if(!buf)return-21;var layout={"__size__":390,"sysname":0,"nodename":65,"release":130,"version":195,"machine":260,"domainname":325};var copyString=function(element,value){var offset=layout[element];writeAsciiToMemory(value,buf+offset)};copyString("sysname","Emscripten");copyString("nodename","emscripten");copyString("release","1.0");copyString("version","#1");copyString("machine","x86-JS");return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function ___sys_unlink(path){try{path=SYSCALLS.getStr(path);FS.unlink(path);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return-e.errno}}function getShiftFromSize(size){switch(size){case 1:return 0;case 2:return 1;case 4:return 2;case 8:return 3;default:throw new TypeError("Unknown type size: "+size)}}function embind_init_charCodes(){var codes=new Array(256);for(var i=0;i<256;++i){codes[i]=String.fromCharCode(i)}embind_charCodes=codes}var embind_charCodes=undefined;function readLatin1String(ptr){var ret="";var c=ptr;while(HEAPU8[c]){ret+=embind_charCodes[HEAPU8[c++]]}return ret}var awaitingDependencies={};var registeredTypes={};var typeDependencies={};var char_0=48;var char_9=57;function makeLegalFunctionName(name){if(undefined===name){return"_unknown"}name=name.replace(/[^a-zA-Z0-9_]/g,"$");var f=name.charCodeAt(0);if(f>=char_0&&f<=char_9){return"_"+name}else{return name}}function createNamedFunction(name,body){name=makeLegalFunctionName(name);return function(){null;return body.apply(this,arguments)}}function extendError(baseErrorType,errorName){var errorClass=createNamedFunction(errorName,function(message){this.name=errorName;this.message=message;var stack=new Error(message).stack;if(stack!==undefined){this.stack=this.toString()+"\n"+stack.replace(/^Error(:[^\n]*)?\n/,"")}});errorClass.prototype=Object.create(baseErrorType.prototype);errorClass.prototype.constructor=errorClass;errorClass.prototype.toString=function(){if(this.message===undefined){return this.name}else{return this.name+": "+this.message}};return errorClass}var BindingError=undefined;function throwBindingError(message){throw new BindingError(message)}var InternalError=undefined;function throwInternalError(message){throw new InternalError(message)}function whenDependentTypesAreResolved(myTypes,dependentTypes,getTypeConverters){myTypes.forEach(function(type){typeDependencies[type]=dependentTypes});function onComplete(typeConverters){var myTypeConverters=getTypeConverters(typeConverters);if(myTypeConverters.length!==myTypes.length){throwInternalError("Mismatched type converter count")}for(var i=0;i>shift])},destructorFunction:null})}function ClassHandle_isAliasOf(other){if(!(this instanceof ClassHandle)){return false}if(!(other instanceof ClassHandle)){return false}var leftClass=this.$$.ptrType.registeredClass;var left=this.$$.ptr;var rightClass=other.$$.ptrType.registeredClass;var right=other.$$.ptr;while(leftClass.baseClass){left=leftClass.upcast(left);leftClass=leftClass.baseClass}while(rightClass.baseClass){right=rightClass.upcast(right);rightClass=rightClass.baseClass}return leftClass===rightClass&&left===right}function shallowCopyInternalPointer(o){return{count:o.count,deleteScheduled:o.deleteScheduled,preservePointerOnDelete:o.preservePointerOnDelete,ptr:o.ptr,ptrType:o.ptrType,smartPtr:o.smartPtr,smartPtrType:o.smartPtrType}}function throwInstanceAlreadyDeleted(obj){function getInstanceTypeName(handle){return handle.$$.ptrType.registeredClass.name}throwBindingError(getInstanceTypeName(obj)+" instance already deleted")}var finalizationGroup=false;function detachFinalizer(handle){}function runDestructor($$){if($$.smartPtr){$$.smartPtrType.rawDestructor($$.smartPtr)}else{$$.ptrType.registeredClass.rawDestructor($$.ptr)}}function releaseClassHandle($$){$$.count.value-=1;var toDelete=0===$$.count.value;if(toDelete){runDestructor($$)}}function attachFinalizer(handle){if("undefined"===typeof FinalizationGroup){attachFinalizer=function(handle){return handle};return handle}finalizationGroup=new FinalizationGroup(function(iter){for(var result=iter.next();!result.done;result=iter.next()){var $$=result.value;if(!$$.ptr){console.warn("object already deleted: "+$$.ptr)}else{releaseClassHandle($$)}}});attachFinalizer=function(handle){finalizationGroup.register(handle,handle.$$,handle.$$);return handle};detachFinalizer=function(handle){finalizationGroup.unregister(handle.$$)};return attachFinalizer(handle)}function ClassHandle_clone(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.preservePointerOnDelete){this.$$.count.value+=1;return this}else{var clone=attachFinalizer(Object.create(Object.getPrototypeOf(this),{$$:{value:shallowCopyInternalPointer(this.$$)}}));clone.$$.count.value+=1;clone.$$.deleteScheduled=false;return clone}}function ClassHandle_delete(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}detachFinalizer(this);releaseClassHandle(this.$$);if(!this.$$.preservePointerOnDelete){this.$$.smartPtr=undefined;this.$$.ptr=undefined}}function ClassHandle_isDeleted(){return!this.$$.ptr}var delayFunction=undefined;var deletionQueue=[];function flushPendingDeletes(){while(deletionQueue.length){var obj=deletionQueue.pop();obj.$$.deleteScheduled=false;obj["delete"]()}}function ClassHandle_deleteLater(){if(!this.$$.ptr){throwInstanceAlreadyDeleted(this)}if(this.$$.deleteScheduled&&!this.$$.preservePointerOnDelete){throwBindingError("Object already scheduled for deletion")}deletionQueue.push(this);if(deletionQueue.length===1&&delayFunction){delayFunction(flushPendingDeletes)}this.$$.deleteScheduled=true;return this}function init_ClassHandle(){ClassHandle.prototype["isAliasOf"]=ClassHandle_isAliasOf;ClassHandle.prototype["clone"]=ClassHandle_clone;ClassHandle.prototype["delete"]=ClassHandle_delete;ClassHandle.prototype["isDeleted"]=ClassHandle_isDeleted;ClassHandle.prototype["deleteLater"]=ClassHandle_deleteLater}function ClassHandle(){}var registeredPointers={};function ensureOverloadTable(proto,methodName,humanName){if(undefined===proto[methodName].overloadTable){var prevFunc=proto[methodName];proto[methodName]=function(){if(!proto[methodName].overloadTable.hasOwnProperty(arguments.length)){throwBindingError("Function '"+humanName+"' called with an invalid number of arguments ("+arguments.length+") - expects one of ("+proto[methodName].overloadTable+")!")}return proto[methodName].overloadTable[arguments.length].apply(this,arguments)};proto[methodName].overloadTable=[];proto[methodName].overloadTable[prevFunc.argCount]=prevFunc}}function exposePublicSymbol(name,value,numArguments){if(Module.hasOwnProperty(name)){if(undefined===numArguments||undefined!==Module[name].overloadTable&&undefined!==Module[name].overloadTable[numArguments]){throwBindingError("Cannot register public name '"+name+"' twice")}ensureOverloadTable(Module,name,name);if(Module.hasOwnProperty(numArguments)){throwBindingError("Cannot register multiple overloads of a function with the same number of arguments ("+numArguments+")!")}Module[name].overloadTable[numArguments]=value}else{Module[name]=value;if(undefined!==numArguments){Module[name].numArguments=numArguments}}}function RegisteredClass(name,constructor,instancePrototype,rawDestructor,baseClass,getActualType,upcast,downcast){this.name=name;this.constructor=constructor;this.instancePrototype=instancePrototype;this.rawDestructor=rawDestructor;this.baseClass=baseClass;this.getActualType=getActualType;this.upcast=upcast;this.downcast=downcast;this.pureVirtualFunctions=[]}function upcastPointer(ptr,ptrClass,desiredClass){while(ptrClass!==desiredClass){if(!ptrClass.upcast){throwBindingError("Expected null or instance of "+desiredClass.name+", got an instance of "+ptrClass.name)}ptr=ptrClass.upcast(ptr);ptrClass=ptrClass.baseClass}return ptr}function constNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function genericPointerToWireType(destructors,handle){var ptr;if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}if(this.isSmartPointer){ptr=this.rawConstructor();if(destructors!==null){destructors.push(this.rawDestructor,ptr)}return ptr}else{return 0}}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(!this.isConst&&handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);if(this.isSmartPointer){if(undefined===handle.$$.smartPtr){throwBindingError("Passing raw pointer to smart pointer is illegal")}switch(this.sharingPolicy){case 0:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{throwBindingError("Cannot convert argument of type "+(handle.$$.smartPtrType?handle.$$.smartPtrType.name:handle.$$.ptrType.name)+" to parameter type "+this.name)}break;case 1:ptr=handle.$$.smartPtr;break;case 2:if(handle.$$.smartPtrType===this){ptr=handle.$$.smartPtr}else{var clonedHandle=handle["clone"]();ptr=this.rawShare(ptr,__emval_register(function(){clonedHandle["delete"]()}));if(destructors!==null){destructors.push(this.rawDestructor,ptr)}}break;default:throwBindingError("Unsupporting sharing policy")}}return ptr}function nonConstNoSmartPtrRawPointerToWireType(destructors,handle){if(handle===null){if(this.isReference){throwBindingError("null is not a valid "+this.name)}return 0}if(!handle.$$){throwBindingError('Cannot pass "'+_embind_repr(handle)+'" as a '+this.name)}if(!handle.$$.ptr){throwBindingError("Cannot pass deleted object as a pointer of type "+this.name)}if(handle.$$.ptrType.isConst){throwBindingError("Cannot convert argument of type "+handle.$$.ptrType.name+" to parameter type "+this.name)}var handleClass=handle.$$.ptrType.registeredClass;var ptr=upcastPointer(handle.$$.ptr,handleClass,this.registeredClass);return ptr}function simpleReadValueFromPointer(pointer){return this["fromWireType"](HEAPU32[pointer>>2])}function RegisteredPointer_getPointee(ptr){if(this.rawGetPointee){ptr=this.rawGetPointee(ptr)}return ptr}function RegisteredPointer_destructor(ptr){if(this.rawDestructor){this.rawDestructor(ptr)}}function RegisteredPointer_deleteObject(handle){if(handle!==null){handle["delete"]()}}function downcastPointer(ptr,ptrClass,desiredClass){if(ptrClass===desiredClass){return ptr}if(undefined===desiredClass.baseClass){return null}var rv=downcastPointer(ptr,ptrClass,desiredClass.baseClass);if(rv===null){return null}return desiredClass.downcast(rv)}function getInheritedInstanceCount(){return Object.keys(registeredInstances).length}function getLiveInheritedInstances(){var rv=[];for(var k in registeredInstances){if(registeredInstances.hasOwnProperty(k)){rv.push(registeredInstances[k])}}return rv}function setDelayFunction(fn){delayFunction=fn;if(deletionQueue.length&&delayFunction){delayFunction(flushPendingDeletes)}}function init_embind(){Module["getInheritedInstanceCount"]=getInheritedInstanceCount;Module["getLiveInheritedInstances"]=getLiveInheritedInstances;Module["flushPendingDeletes"]=flushPendingDeletes;Module["setDelayFunction"]=setDelayFunction}var registeredInstances={};function getBasestPointer(class_,ptr){if(ptr===undefined){throwBindingError("ptr should not be undefined")}while(class_.baseClass){ptr=class_.upcast(ptr);class_=class_.baseClass}return ptr}function getInheritedInstance(class_,ptr){ptr=getBasestPointer(class_,ptr);return registeredInstances[ptr]}function makeClassHandle(prototype,record){if(!record.ptrType||!record.ptr){throwInternalError("makeClassHandle requires ptr and ptrType")}var hasSmartPtrType=!!record.smartPtrType;var hasSmartPtr=!!record.smartPtr;if(hasSmartPtrType!==hasSmartPtr){throwInternalError("Both smartPtrType and smartPtr must be specified")}record.count={value:1};return attachFinalizer(Object.create(prototype,{$$:{value:record}}))}function RegisteredPointer_fromWireType(ptr){var rawPointer=this.getPointee(ptr);if(!rawPointer){this.destructor(ptr);return null}var registeredInstance=getInheritedInstance(this.registeredClass,rawPointer);if(undefined!==registeredInstance){if(0===registeredInstance.$$.count.value){registeredInstance.$$.ptr=rawPointer;registeredInstance.$$.smartPtr=ptr;return registeredInstance["clone"]()}else{var rv=registeredInstance["clone"]();this.destructor(ptr);return rv}}function makeDefaultHandle(){if(this.isSmartPointer){return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this.pointeeType,ptr:rawPointer,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(this.registeredClass.instancePrototype,{ptrType:this,ptr:ptr})}}var actualType=this.registeredClass.getActualType(rawPointer);var registeredPointerRecord=registeredPointers[actualType];if(!registeredPointerRecord){return makeDefaultHandle.call(this)}var toType;if(this.isConst){toType=registeredPointerRecord.constPointerType}else{toType=registeredPointerRecord.pointerType}var dp=downcastPointer(rawPointer,this.registeredClass,toType.registeredClass);if(dp===null){return makeDefaultHandle.call(this)}if(this.isSmartPointer){return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp,smartPtrType:this,smartPtr:ptr})}else{return makeClassHandle(toType.registeredClass.instancePrototype,{ptrType:toType,ptr:dp})}}function init_RegisteredPointer(){RegisteredPointer.prototype.getPointee=RegisteredPointer_getPointee;RegisteredPointer.prototype.destructor=RegisteredPointer_destructor;RegisteredPointer.prototype["argPackAdvance"]=8;RegisteredPointer.prototype["readValueFromPointer"]=simpleReadValueFromPointer;RegisteredPointer.prototype["deleteObject"]=RegisteredPointer_deleteObject;RegisteredPointer.prototype["fromWireType"]=RegisteredPointer_fromWireType}function RegisteredPointer(name,registeredClass,isReference,isConst,isSmartPointer,pointeeType,sharingPolicy,rawGetPointee,rawConstructor,rawShare,rawDestructor){this.name=name;this.registeredClass=registeredClass;this.isReference=isReference;this.isConst=isConst;this.isSmartPointer=isSmartPointer;this.pointeeType=pointeeType;this.sharingPolicy=sharingPolicy;this.rawGetPointee=rawGetPointee;this.rawConstructor=rawConstructor;this.rawShare=rawShare;this.rawDestructor=rawDestructor;if(!isSmartPointer&®isteredClass.baseClass===undefined){if(isConst){this["toWireType"]=constNoSmartPtrRawPointerToWireType;this.destructorFunction=null}else{this["toWireType"]=nonConstNoSmartPtrRawPointerToWireType;this.destructorFunction=null}}else{this["toWireType"]=genericPointerToWireType}}function replacePublicSymbol(name,value,numArguments){if(!Module.hasOwnProperty(name)){throwInternalError("Replacing nonexistant public symbol")}if(undefined!==Module[name].overloadTable&&undefined!==numArguments){Module[name].overloadTable[numArguments]=value}else{Module[name]=value;Module[name].argCount=numArguments}}function getDynCaller(sig,ptr){assert(sig.indexOf("j")>=0,"getDynCaller should only be called with i64 sigs");var argCache=[];return function(){argCache.length=arguments.length;for(var i=0;i>2)+i])}return array}function __embind_register_class_class_function(rawClassType,methodName,argCount,rawArgTypesAddr,invokerSignature,rawInvoker,fn){var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);methodName=readLatin1String(methodName);rawInvoker=embind__requireFunction(invokerSignature,rawInvoker);whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName=classType.name+"."+methodName;function unboundTypesHandler(){throwUnboundTypeError("Cannot call "+humanName+" due to unbound types",rawArgTypes)}var proto=classType.registeredClass.constructor;if(undefined===proto[methodName]){unboundTypesHandler.argCount=argCount-1;proto[methodName]=unboundTypesHandler}else{ensureOverloadTable(proto,methodName,humanName);proto[methodName].overloadTable[argCount-1]=unboundTypesHandler}whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){var invokerArgsArray=[argTypes[0],null].concat(argTypes.slice(1));var func=craftInvokerFunction(humanName,invokerArgsArray,null,rawInvoker,fn);if(undefined===proto[methodName].overloadTable){func.argCount=argCount-1;proto[methodName]=func}else{proto[methodName].overloadTable[argCount-1]=func}return[]});return[]})}function __embind_register_class_constructor(rawClassType,argCount,rawArgTypesAddr,invokerSignature,invoker,rawConstructor){assert(argCount>0);var rawArgTypes=heap32VectorToArray(argCount,rawArgTypesAddr);invoker=embind__requireFunction(invokerSignature,invoker);var args=[rawConstructor];var destructors=[];whenDependentTypesAreResolved([],[rawClassType],function(classType){classType=classType[0];var humanName="constructor "+classType.name;if(undefined===classType.registeredClass.constructor_body){classType.registeredClass.constructor_body=[]}if(undefined!==classType.registeredClass.constructor_body[argCount-1]){throw new BindingError("Cannot register multiple constructors with identical number of parameters ("+(argCount-1)+") for class '"+classType.name+"'! Overload resolution is currently only performed using the parameter count, not actual type info!")}classType.registeredClass.constructor_body[argCount-1]=function unboundTypeHandler(){throwUnboundTypeError("Cannot construct "+classType.name+" due to unbound types",rawArgTypes)};whenDependentTypesAreResolved([],rawArgTypes,function(argTypes){classType.registeredClass.constructor_body[argCount-1]=function constructor_body(){if(arguments.length!==argCount-1){throwBindingError(humanName+" called with "+arguments.length+" arguments, expected "+(argCount-1))}destructors.length=0;args.length=argCount;for(var i=1;i4&&0===--emval_handle_array[handle].refcount){emval_handle_array[handle]=undefined;emval_free_list.push(handle)}}function count_emval_handles(){var count=0;for(var i=5;i>1])};case 2:return function(pointer){var heap=signed?HEAP32:HEAPU32;return this["fromWireType"](heap[pointer>>2])};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_enum(rawType,name,size,isSigned){var shift=getShiftFromSize(size);name=readLatin1String(name);function ctor(){}ctor.values={};registerType(rawType,{name:name,constructor:ctor,"fromWireType":function(c){return this.constructor.values[c]},"toWireType":function(destructors,c){return c.value},"argPackAdvance":8,"readValueFromPointer":enumReadValueFromPointer(name,shift,isSigned),destructorFunction:null});exposePublicSymbol(name,ctor)}function requireRegisteredType(rawType,humanName){var impl=registeredTypes[rawType];if(undefined===impl){throwBindingError(humanName+" has unknown type "+getTypeName(rawType))}return impl}function __embind_register_enum_value(rawEnumType,name,enumValue){var enumType=requireRegisteredType(rawEnumType,"enum");name=readLatin1String(name);var Enum=enumType.constructor;var Value=Object.create(enumType.constructor.prototype,{value:{value:enumValue},constructor:{value:createNamedFunction(enumType.name+"_"+name,function(){})}});Enum.values[enumValue]=Value;Enum[name]=Value}function _embind_repr(v){if(v===null){return"null"}var t=typeof v;if(t==="object"||t==="array"||t==="function"){return v.toString()}else{return""+v}}function floatReadValueFromPointer(name,shift){switch(shift){case 2:return function(pointer){return this["fromWireType"](HEAPF32[pointer>>2])};case 3:return function(pointer){return this["fromWireType"](HEAPF64[pointer>>3])};default:throw new TypeError("Unknown float type: "+name)}}function __embind_register_float(rawType,name,size){var shift=getShiftFromSize(size);name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":function(value){return value},"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}return value},"argPackAdvance":8,"readValueFromPointer":floatReadValueFromPointer(name,shift),destructorFunction:null})}function integerReadValueFromPointer(name,shift,signed){switch(shift){case 0:return signed?function readS8FromPointer(pointer){return HEAP8[pointer]}:function readU8FromPointer(pointer){return HEAPU8[pointer]};case 1:return signed?function readS16FromPointer(pointer){return HEAP16[pointer>>1]}:function readU16FromPointer(pointer){return HEAPU16[pointer>>1]};case 2:return signed?function readS32FromPointer(pointer){return HEAP32[pointer>>2]}:function readU32FromPointer(pointer){return HEAPU32[pointer>>2]};default:throw new TypeError("Unknown integer type: "+name)}}function __embind_register_integer(primitiveType,name,size,minRange,maxRange){name=readLatin1String(name);if(maxRange===-1){maxRange=4294967295}var shift=getShiftFromSize(size);var fromWireType=function(value){return value};if(minRange===0){var bitshift=32-8*size;fromWireType=function(value){return value<>>bitshift}}var isUnsignedType=name.indexOf("unsigned")!=-1;registerType(primitiveType,{name:name,"fromWireType":fromWireType,"toWireType":function(destructors,value){if(typeof value!=="number"&&typeof value!=="boolean"){throw new TypeError('Cannot convert "'+_embind_repr(value)+'" to '+this.name)}if(valuemaxRange){throw new TypeError('Passing a number "'+_embind_repr(value)+'" from JS side to C/C++ side to an argument of type "'+name+'", which is outside the valid range ['+minRange+", "+maxRange+"]!")}return isUnsignedType?value>>>0:value|0},"argPackAdvance":8,"readValueFromPointer":integerReadValueFromPointer(name,shift,minRange!==0),destructorFunction:null})}function __embind_register_memory_view(rawType,dataTypeIndex,name){var typeMapping=[Int8Array,Uint8Array,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];var TA=typeMapping[dataTypeIndex];function decodeMemoryView(handle){handle=handle>>2;var heap=HEAPU32;var size=heap[handle];var data=heap[handle+1];return new TA(buffer,data,size)}name=readLatin1String(name);registerType(rawType,{name:name,"fromWireType":decodeMemoryView,"argPackAdvance":8,"readValueFromPointer":decodeMemoryView},{ignoreDuplicateRegistrations:true})}function __embind_register_std_string(rawType,name){name=readLatin1String(name);var stdStringIsUTF8=name==="std::string";registerType(rawType,{name:name,"fromWireType":function(value){var length=HEAPU32[value>>2];var str;if(stdStringIsUTF8){var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i;if(i==length||HEAPU8[currentBytePtr]==0){var maxRead=currentBytePtr-decodeStartPtr;var stringSegment=UTF8ToString(decodeStartPtr,maxRead);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+1}}}else{var a=new Array(length);for(var i=0;i>2]=length;if(stdStringIsUTF8&&valueIsOfTypeString){stringToUTF8(value,ptr+4,length+1)}else{if(valueIsOfTypeString){for(var i=0;i255){_free(ptr);throwBindingError("String has UTF-16 code units that do not fit in 8 bits")}HEAPU8[ptr+4+i]=charCode}}else{for(var i=0;i>2];var HEAP=getHeap();var str;var decodeStartPtr=value+4;for(var i=0;i<=length;++i){var currentBytePtr=value+4+i*charSize;if(i==length||HEAP[currentBytePtr>>shift]==0){var maxReadBytes=currentBytePtr-decodeStartPtr;var stringSegment=decodeString(decodeStartPtr,maxReadBytes);if(str===undefined){str=stringSegment}else{str+=String.fromCharCode(0);str+=stringSegment}decodeStartPtr=currentBytePtr+charSize}}_free(value);return str},"toWireType":function(destructors,value){if(!(typeof value==="string")){throwBindingError("Cannot pass non-string to C++ string type "+name)}var length=lengthBytesUTF(value);var ptr=_malloc(4+length+charSize);HEAPU32[ptr>>2]=length>>shift;encodeString(value,ptr+4,length+charSize);if(destructors!==null){destructors.push(_free,ptr)}return ptr},"argPackAdvance":8,"readValueFromPointer":simpleReadValueFromPointer,destructorFunction:function(ptr){_free(ptr)}})}function __embind_register_void(rawType,name){name=readLatin1String(name);registerType(rawType,{isVoid:true,name:name,"argPackAdvance":0,"fromWireType":function(){return undefined},"toWireType":function(destructors,o){return undefined}})}function _abort(){abort()}function _dlclose(handle){abort("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}function _dlerror(){abort("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}function _dlopen(filename,flag){abort("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}function _dlsym(handle,symbol){abort("To use dlopen, you need to use Emscripten's linking support, see https://github.com/emscripten-core/emscripten/wiki/Linking")}function __webgl_enable_ANGLE_instanced_arrays(ctx){var ext=ctx.getExtension("ANGLE_instanced_arrays");if(ext){ctx["vertexAttribDivisor"]=function(index,divisor){ext["vertexAttribDivisorANGLE"](index,divisor)};ctx["drawArraysInstanced"]=function(mode,first,count,primcount){ext["drawArraysInstancedANGLE"](mode,first,count,primcount)};ctx["drawElementsInstanced"]=function(mode,count,type,indices,primcount){ext["drawElementsInstancedANGLE"](mode,count,type,indices,primcount)};return 1}}function __webgl_enable_OES_vertex_array_object(ctx){var ext=ctx.getExtension("OES_vertex_array_object");if(ext){ctx["createVertexArray"]=function(){return ext["createVertexArrayOES"]()};ctx["deleteVertexArray"]=function(vao){ext["deleteVertexArrayOES"](vao)};ctx["bindVertexArray"]=function(vao){ext["bindVertexArrayOES"](vao)};ctx["isVertexArray"]=function(vao){return ext["isVertexArrayOES"](vao)};return 1}}function __webgl_enable_WEBGL_draw_buffers(ctx){var ext=ctx.getExtension("WEBGL_draw_buffers");if(ext){ctx["drawBuffers"]=function(n,bufs){ext["drawBuffersWEBGL"](n,bufs)};return 1}}function __webgl_enable_WEBGL_multi_draw(ctx){return!!(ctx.multiDrawWebgl=ctx.getExtension("WEBGL_multi_draw"))}var GL={counter:1,buffers:[],programs:[],framebuffers:[],renderbuffers:[],textures:[],uniforms:[],shaders:[],vaos:[],contexts:[],offscreenCanvases:{},timerQueriesEXT:[],programInfos:{},stringCache:{},unpackAlignment:4,recordError:function recordError(errorCode){if(!GL.lastError){GL.lastError=errorCode}},getNewId:function(table){var ret=GL.counter++;for(var i=table.length;i>2]:-1;source+=UTF8ToString(HEAP32[string+i*4>>2],len<0?undefined:len)}return source},createContext:function(canvas,webGLContextAttributes){var ctx=canvas.getContext("webgl",webGLContextAttributes);if(!ctx)return 0;var handle=GL.registerContext(ctx,webGLContextAttributes);return handle},registerContext:function(ctx,webGLContextAttributes){var handle=GL.getNewId(GL.contexts);var context={handle:handle,attributes:webGLContextAttributes,version:webGLContextAttributes.majorVersion,GLctx:ctx};if(ctx.canvas)ctx.canvas.GLctxObject=context;GL.contexts[handle]=context;if(typeof webGLContextAttributes.enableExtensionsByDefault==="undefined"||webGLContextAttributes.enableExtensionsByDefault){GL.initExtensions(context)}return handle},makeContextCurrent:function(contextHandle){GL.currentContext=GL.contexts[contextHandle];Module.ctx=GLctx=GL.currentContext&&GL.currentContext.GLctx;return!(contextHandle&&!GLctx)},getContext:function(contextHandle){return GL.contexts[contextHandle]},deleteContext:function(contextHandle){if(GL.currentContext===GL.contexts[contextHandle])GL.currentContext=null;if(typeof JSEvents==="object")JSEvents.removeAllHandlersOnTarget(GL.contexts[contextHandle].GLctx.canvas);if(GL.contexts[contextHandle]&&GL.contexts[contextHandle].GLctx.canvas)GL.contexts[contextHandle].GLctx.canvas.GLctxObject=undefined;GL.contexts[contextHandle]=null},initExtensions:function(context){if(!context)context=GL.currentContext;if(context.initExtensionsDone)return;context.initExtensionsDone=true;var GLctx=context.GLctx;__webgl_enable_ANGLE_instanced_arrays(GLctx);__webgl_enable_OES_vertex_array_object(GLctx);__webgl_enable_WEBGL_draw_buffers(GLctx);GLctx.disjointTimerQueryExt=GLctx.getExtension("EXT_disjoint_timer_query");__webgl_enable_WEBGL_multi_draw(GLctx);var automaticallyEnabledExtensions=["OES_texture_float","OES_texture_half_float","OES_standard_derivatives","OES_vertex_array_object","WEBGL_compressed_texture_s3tc","WEBGL_depth_texture","OES_element_index_uint","EXT_texture_filter_anisotropic","EXT_frag_depth","WEBGL_draw_buffers","ANGLE_instanced_arrays","OES_texture_float_linear","OES_texture_half_float_linear","EXT_blend_minmax","EXT_shader_texture_lod","EXT_texture_norm16","WEBGL_compressed_texture_pvrtc","EXT_color_buffer_half_float","WEBGL_color_buffer_float","EXT_sRGB","WEBGL_compressed_texture_etc1","EXT_disjoint_timer_query","WEBGL_compressed_texture_etc","WEBGL_compressed_texture_astc","EXT_color_buffer_float","WEBGL_compressed_texture_s3tc_srgb","EXT_disjoint_timer_query_webgl2","WEBKIT_WEBGL_compressed_texture_pvrtc"];var exts=GLctx.getSupportedExtensions()||[];exts.forEach(function(ext){if(automaticallyEnabledExtensions.indexOf(ext)!=-1){GLctx.getExtension(ext)}})},populateUniformTable:function(program){var p=GL.programs[program];var ptable=GL.programInfos[program]={uniforms:{},maxUniformLength:0,maxAttributeLength:-1,maxUniformBlockNameLength:-1};var utable=ptable.uniforms;var numUniforms=GLctx.getProgramParameter(p,35718);for(var i=0;i>2];var buffer=GL.buffers[id];if(!buffer)continue;GLctx.deleteBuffer(buffer);buffer.name=0;GL.buffers[id]=null}}function _emscripten_glDeleteFramebuffers(n,framebuffers){for(var i=0;i>2];var framebuffer=GL.framebuffers[id];if(!framebuffer)continue;GLctx.deleteFramebuffer(framebuffer);framebuffer.name=0;GL.framebuffers[id]=null}}function _emscripten_glDeleteProgram(id){if(!id)return;var program=GL.programs[id];if(!program){GL.recordError(1281);return}GLctx.deleteProgram(program);program.name=0;GL.programs[id]=null;GL.programInfos[id]=null}function _emscripten_glDeleteQueriesEXT(n,ids){for(var i=0;i>2];var query=GL.timerQueriesEXT[id];if(!query)continue;GLctx.disjointTimerQueryExt["deleteQueryEXT"](query);GL.timerQueriesEXT[id]=null}}function _emscripten_glDeleteRenderbuffers(n,renderbuffers){for(var i=0;i>2];var renderbuffer=GL.renderbuffers[id];if(!renderbuffer)continue;GLctx.deleteRenderbuffer(renderbuffer);renderbuffer.name=0;GL.renderbuffers[id]=null}}function _emscripten_glDeleteShader(id){if(!id)return;var shader=GL.shaders[id];if(!shader){GL.recordError(1281);return}GLctx.deleteShader(shader);GL.shaders[id]=null}function _emscripten_glDeleteTextures(n,textures){for(var i=0;i>2];var texture=GL.textures[id];if(!texture)continue;GLctx.deleteTexture(texture);texture.name=0;GL.textures[id]=null}}function _emscripten_glDeleteVertexArraysOES(n,vaos){for(var i=0;i>2];GLctx["deleteVertexArray"](GL.vaos[id]);GL.vaos[id]=null}}function _emscripten_glDepthFunc(x0){GLctx["depthFunc"](x0)}function _emscripten_glDepthMask(flag){GLctx.depthMask(!!flag)}function _emscripten_glDepthRangef(x0,x1){GLctx["depthRange"](x0,x1)}function _emscripten_glDetachShader(program,shader){GLctx.detachShader(GL.programs[program],GL.shaders[shader])}function _emscripten_glDisable(x0){GLctx["disable"](x0)}function _emscripten_glDisableVertexAttribArray(index){GLctx.disableVertexAttribArray(index)}function _emscripten_glDrawArrays(mode,first,count){GLctx.drawArrays(mode,first,count)}function _emscripten_glDrawArraysInstancedANGLE(mode,first,count,primcount){GLctx["drawArraysInstanced"](mode,first,count,primcount)}var tempFixedLengthArray=[];function _emscripten_glDrawBuffersWEBGL(n,bufs){var bufArray=tempFixedLengthArray[n];for(var i=0;i>2]}GLctx["drawBuffers"](bufArray)}function _emscripten_glDrawElements(mode,count,type,indices){GLctx.drawElements(mode,count,type,indices)}function _emscripten_glDrawElementsInstancedANGLE(mode,count,type,indices,primcount){GLctx["drawElementsInstanced"](mode,count,type,indices,primcount)}function _emscripten_glEnable(x0){GLctx["enable"](x0)}function _emscripten_glEnableVertexAttribArray(index){GLctx.enableVertexAttribArray(index)}function _emscripten_glEndQueryEXT(target){GLctx.disjointTimerQueryExt["endQueryEXT"](target)}function _emscripten_glFinish(){GLctx["finish"]()}function _emscripten_glFlush(){GLctx["flush"]()}function _emscripten_glFramebufferRenderbuffer(target,attachment,renderbuffertarget,renderbuffer){GLctx.framebufferRenderbuffer(target,attachment,renderbuffertarget,GL.renderbuffers[renderbuffer])}function _emscripten_glFramebufferTexture2D(target,attachment,textarget,texture,level){GLctx.framebufferTexture2D(target,attachment,textarget,GL.textures[texture],level)}function _emscripten_glFrontFace(x0){GLctx["frontFace"](x0)}function __glGenObject(n,buffers,createFunction,objectTable){for(var i=0;i>2]=id}}function _emscripten_glGenBuffers(n,buffers){__glGenObject(n,buffers,"createBuffer",GL.buffers)}function _emscripten_glGenFramebuffers(n,ids){__glGenObject(n,ids,"createFramebuffer",GL.framebuffers)}function _emscripten_glGenQueriesEXT(n,ids){for(var i=0;i>2]=0;return}var id=GL.getNewId(GL.timerQueriesEXT);query.name=id;GL.timerQueriesEXT[id]=query;HEAP32[ids+i*4>>2]=id}}function _emscripten_glGenRenderbuffers(n,renderbuffers){__glGenObject(n,renderbuffers,"createRenderbuffer",GL.renderbuffers)}function _emscripten_glGenTextures(n,textures){__glGenObject(n,textures,"createTexture",GL.textures)}function _emscripten_glGenVertexArraysOES(n,arrays){__glGenObject(n,arrays,"createVertexArray",GL.vaos)}function _emscripten_glGenerateMipmap(x0){GLctx["generateMipmap"](x0)}function __glGetActiveAttribOrUniform(funcName,program,index,bufSize,length,size,type,name){program=GL.programs[program];var info=GLctx[funcName](program,index);if(info){var numBytesWrittenExclNull=name&&stringToUTF8(info.name,name,bufSize);if(length)HEAP32[length>>2]=numBytesWrittenExclNull;if(size)HEAP32[size>>2]=info.size;if(type)HEAP32[type>>2]=info.type}}function _emscripten_glGetActiveAttrib(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveAttrib",program,index,bufSize,length,size,type,name)}function _emscripten_glGetActiveUniform(program,index,bufSize,length,size,type,name){__glGetActiveAttribOrUniform("getActiveUniform",program,index,bufSize,length,size,type,name)}function _emscripten_glGetAttachedShaders(program,maxCount,count,shaders){var result=GLctx.getAttachedShaders(GL.programs[program]);var len=result.length;if(len>maxCount){len=maxCount}HEAP32[count>>2]=len;for(var i=0;i>2]=id}}function _emscripten_glGetAttribLocation(program,name){return GLctx.getAttribLocation(GL.programs[program],UTF8ToString(name))}function writeI53ToI64(ptr,num){HEAPU32[ptr>>2]=num;HEAPU32[ptr+4>>2]=(num-HEAPU32[ptr>>2])/4294967296}function emscriptenWebGLGet(name_,p,type){if(!p){GL.recordError(1281);return}var ret=undefined;switch(name_){case 36346:ret=1;break;case 36344:if(type!=0&&type!=1){GL.recordError(1280)}return;case 36345:ret=0;break;case 34466:var formats=GLctx.getParameter(34467);ret=formats?formats.length:0;break}if(ret===undefined){var result=GLctx.getParameter(name_);switch(typeof result){case"number":ret=result;break;case"boolean":ret=result?1:0;break;case"string":GL.recordError(1280);return;case"object":if(result===null){switch(name_){case 34964:case 35725:case 34965:case 36006:case 36007:case 32873:case 34229:case 34068:{ret=0;break}default:{GL.recordError(1280);return}}}else if(result instanceof Float32Array||result instanceof Uint32Array||result instanceof Int32Array||result instanceof Array){for(var i=0;i>2]=result[i];break;case 2:HEAPF32[p+i*4>>2]=result[i];break;case 4:HEAP8[p+i>>0]=result[i]?1:0;break}}return}else{try{ret=result.name|0}catch(e){GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Unknown object returned from WebGL getParameter("+name_+")! (error: "+e+")");return}}break;default:GL.recordError(1280);err("GL_INVALID_ENUM in glGet"+type+"v: Native code calling glGet"+type+"v("+name_+") and it returns "+result+" of type "+typeof result+"!");return}}switch(type){case 1:writeI53ToI64(p,ret);break;case 0:HEAP32[p>>2]=ret;break;case 2:HEAPF32[p>>2]=ret;break;case 4:HEAP8[p>>0]=ret?1:0;break}}function _emscripten_glGetBooleanv(name_,p){emscriptenWebGLGet(name_,p,4)}function _emscripten_glGetBufferParameteriv(target,value,data){if(!data){GL.recordError(1281);return}HEAP32[data>>2]=GLctx.getBufferParameter(target,value)}function _emscripten_glGetError(){var error=GLctx.getError()||GL.lastError;GL.lastError=0;return error}function _emscripten_glGetFloatv(name_,p){emscriptenWebGLGet(name_,p,2)}function _emscripten_glGetFramebufferAttachmentParameteriv(target,attachment,pname,params){var result=GLctx.getFramebufferAttachmentParameter(target,attachment,pname);if(result instanceof WebGLRenderbuffer||result instanceof WebGLTexture){result=result.name|0}HEAP32[params>>2]=result}function _emscripten_glGetIntegerv(name_,p){emscriptenWebGLGet(name_,p,0)}function _emscripten_glGetProgramInfoLog(program,maxLength,length,infoLog){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _emscripten_glGetProgramiv(program,pname,p){if(!p){GL.recordError(1281);return}if(program>=GL.counter){GL.recordError(1281);return}var ptable=GL.programInfos[program];if(!ptable){GL.recordError(1282);return}if(pname==35716){var log=GLctx.getProgramInfoLog(GL.programs[program]);if(log===null)log="(unknown error)";HEAP32[p>>2]=log.length+1}else if(pname==35719){HEAP32[p>>2]=ptable.maxUniformLength}else if(pname==35722){if(ptable.maxAttributeLength==-1){program=GL.programs[program];var numAttribs=GLctx.getProgramParameter(program,35721);ptable.maxAttributeLength=0;for(var i=0;i>2]=ptable.maxAttributeLength}else if(pname==35381){if(ptable.maxUniformBlockNameLength==-1){program=GL.programs[program];var numBlocks=GLctx.getProgramParameter(program,35382);ptable.maxUniformBlockNameLength=0;for(var i=0;i>2]=ptable.maxUniformBlockNameLength}else{HEAP32[p>>2]=GLctx.getProgramParameter(GL.programs[program],pname)}}function _emscripten_glGetQueryObjecti64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.timerQueriesEXT[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}function _emscripten_glGetQueryObjectivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.timerQueriesEXT[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}function _emscripten_glGetQueryObjectui64vEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.timerQueriesEXT[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}writeI53ToI64(params,ret)}function _emscripten_glGetQueryObjectuivEXT(id,pname,params){if(!params){GL.recordError(1281);return}var query=GL.timerQueriesEXT[id];var param=GLctx.disjointTimerQueryExt["getQueryObjectEXT"](query,pname);var ret;if(typeof param=="boolean"){ret=param?1:0}else{ret=param}HEAP32[params>>2]=ret}function _emscripten_glGetQueryivEXT(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.disjointTimerQueryExt["getQueryEXT"](target,pname)}function _emscripten_glGetRenderbufferParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getRenderbufferParameter(target,pname)}function _emscripten_glGetShaderInfoLog(shader,maxLength,length,infoLog){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var numBytesWrittenExclNull=maxLength>0&&infoLog?stringToUTF8(log,infoLog,maxLength):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _emscripten_glGetShaderPrecisionFormat(shaderType,precisionType,range,precision){var result=GLctx.getShaderPrecisionFormat(shaderType,precisionType);HEAP32[range>>2]=result.rangeMin;HEAP32[range+4>>2]=result.rangeMax;HEAP32[precision>>2]=result.precision}function _emscripten_glGetShaderSource(shader,bufSize,length,source){var result=GLctx.getShaderSource(GL.shaders[shader]);if(!result)return;var numBytesWrittenExclNull=bufSize>0&&source?stringToUTF8(result,source,bufSize):0;if(length)HEAP32[length>>2]=numBytesWrittenExclNull}function _emscripten_glGetShaderiv(shader,pname,p){if(!p){GL.recordError(1281);return}if(pname==35716){var log=GLctx.getShaderInfoLog(GL.shaders[shader]);if(log===null)log="(unknown error)";var logLength=log?log.length+1:0;HEAP32[p>>2]=logLength}else if(pname==35720){var source=GLctx.getShaderSource(GL.shaders[shader]);var sourceLength=source?source.length+1:0;HEAP32[p>>2]=sourceLength}else{HEAP32[p>>2]=GLctx.getShaderParameter(GL.shaders[shader],pname)}}function stringToNewUTF8(jsString){var length=lengthBytesUTF8(jsString)+1;var cString=_malloc(length);stringToUTF8(jsString,cString,length);return cString}function _emscripten_glGetString(name_){if(GL.stringCache[name_])return GL.stringCache[name_];var ret;switch(name_){case 7939:var exts=GLctx.getSupportedExtensions()||[];exts=exts.concat(exts.map(function(e){return"GL_"+e}));ret=stringToNewUTF8(exts.join(" "));break;case 7936:case 7937:case 37445:case 37446:var s=GLctx.getParameter(name_);if(!s){GL.recordError(1280)}ret=stringToNewUTF8(s);break;case 7938:var glVersion=GLctx.getParameter(7938);{glVersion="OpenGL ES 2.0 ("+glVersion+")"}ret=stringToNewUTF8(glVersion);break;case 35724:var glslVersion=GLctx.getParameter(35724);var ver_re=/^WebGL GLSL ES ([0-9]\.[0-9][0-9]?)(?:$| .*)/;var ver_num=glslVersion.match(ver_re);if(ver_num!==null){if(ver_num[1].length==3)ver_num[1]=ver_num[1]+"0";glslVersion="OpenGL ES GLSL ES "+ver_num[1]+" ("+glslVersion+")"}ret=stringToNewUTF8(glslVersion);break;default:GL.recordError(1280);return 0}GL.stringCache[name_]=ret;return ret}function _emscripten_glGetTexParameterfv(target,pname,params){if(!params){GL.recordError(1281);return}HEAPF32[params>>2]=GLctx.getTexParameter(target,pname)}function _emscripten_glGetTexParameteriv(target,pname,params){if(!params){GL.recordError(1281);return}HEAP32[params>>2]=GLctx.getTexParameter(target,pname)}function jstoi_q(str){return parseInt(str)}function _emscripten_glGetUniformLocation(program,name){name=UTF8ToString(name);var arrayIndex=0;if(name[name.length-1]=="]"){var leftBrace=name.lastIndexOf("[");arrayIndex=name[leftBrace+1]!="]"?jstoi_q(name.slice(leftBrace+1)):0;name=name.slice(0,leftBrace)}var uniformInfo=GL.programInfos[program]&&GL.programInfos[program].uniforms[name];if(uniformInfo&&arrayIndex>=0&&arrayIndex>2]=data;break;case 2:HEAPF32[params>>2]=data;break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break}}}}function _emscripten_glGetUniformfv(program,location,params){emscriptenWebGLGetUniform(program,location,params,2)}function _emscripten_glGetUniformiv(program,location,params){emscriptenWebGLGetUniform(program,location,params,0)}function _emscripten_glGetVertexAttribPointerv(index,pname,pointer){if(!pointer){GL.recordError(1281);return}HEAP32[pointer>>2]=GLctx.getVertexAttribOffset(index,pname)}function emscriptenWebGLGetVertexAttrib(index,pname,params,type){if(!params){GL.recordError(1281);return}var data=GLctx.getVertexAttrib(index,pname);if(pname==34975){HEAP32[params>>2]=data&&data["name"]}else if(typeof data=="number"||typeof data=="boolean"){switch(type){case 0:HEAP32[params>>2]=data;break;case 2:HEAPF32[params>>2]=data;break;case 5:HEAP32[params>>2]=Math.fround(data);break}}else{for(var i=0;i>2]=data[i];break;case 2:HEAPF32[params+i*4>>2]=data[i];break;case 5:HEAP32[params+i*4>>2]=Math.fround(data[i]);break}}}}function _emscripten_glGetVertexAttribfv(index,pname,params){emscriptenWebGLGetVertexAttrib(index,pname,params,2)}function _emscripten_glGetVertexAttribiv(index,pname,params){emscriptenWebGLGetVertexAttrib(index,pname,params,5)}function _emscripten_glHint(x0,x1){GLctx["hint"](x0,x1)}function _emscripten_glIsBuffer(buffer){var b=GL.buffers[buffer];if(!b)return 0;return GLctx.isBuffer(b)}function _emscripten_glIsEnabled(x0){return GLctx["isEnabled"](x0)}function _emscripten_glIsFramebuffer(framebuffer){var fb=GL.framebuffers[framebuffer];if(!fb)return 0;return GLctx.isFramebuffer(fb)}function _emscripten_glIsProgram(program){program=GL.programs[program];if(!program)return 0;return GLctx.isProgram(program)}function _emscripten_glIsQueryEXT(id){var query=GL.timerQueriesEXT[id];if(!query)return 0;return GLctx.disjointTimerQueryExt["isQueryEXT"](query)}function _emscripten_glIsRenderbuffer(renderbuffer){var rb=GL.renderbuffers[renderbuffer];if(!rb)return 0;return GLctx.isRenderbuffer(rb)}function _emscripten_glIsShader(shader){var s=GL.shaders[shader];if(!s)return 0;return GLctx.isShader(s)}function _emscripten_glIsTexture(id){var texture=GL.textures[id];if(!texture)return 0;return GLctx.isTexture(texture)}function _emscripten_glIsVertexArrayOES(array){var vao=GL.vaos[array];if(!vao)return 0;return GLctx["isVertexArray"](vao)}function _emscripten_glLineWidth(x0){GLctx["lineWidth"](x0)}function _emscripten_glLinkProgram(program){GLctx.linkProgram(GL.programs[program]);GL.populateUniformTable(program)}function _emscripten_glPixelStorei(pname,param){if(pname==3317){GL.unpackAlignment=param}GLctx.pixelStorei(pname,param)}function _emscripten_glPolygonOffset(x0,x1){GLctx["polygonOffset"](x0,x1)}function _emscripten_glQueryCounterEXT(id,target){GLctx.disjointTimerQueryExt["queryCounterEXT"](GL.timerQueriesEXT[id],target)}function computeUnpackAlignedImageSize(width,height,sizePerPixel,alignment){function roundedToNextMultipleOf(x,y){return x+y-1&-y}var plainRowSize=width*sizePerPixel;var alignedRowSize=roundedToNextMultipleOf(plainRowSize,alignment);return height*alignedRowSize}function __colorChannelsInGlTextureFormat(format){var colorChannels={5:3,6:4,8:2,29502:3,29504:4};return colorChannels[format-6402]||1}function heapObjectForWebGLType(type){type-=5120;if(type==1)return HEAPU8;if(type==4)return HEAP32;if(type==6)return HEAPF32;if(type==5||type==28922)return HEAPU32;return HEAPU16}function heapAccessShiftForWebGLHeap(heap){return 31-Math.clz32(heap.BYTES_PER_ELEMENT)}function emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat){var heap=heapObjectForWebGLType(type);var shift=heapAccessShiftForWebGLHeap(heap);var byteSize=1<>shift,pixels+bytes>>shift)}function _emscripten_glReadPixels(x,y,width,height,format,type,pixels){var pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,format);if(!pixelData){GL.recordError(1280);return}GLctx.readPixels(x,y,width,height,format,type,pixelData)}function _emscripten_glReleaseShaderCompiler(){}function _emscripten_glRenderbufferStorage(x0,x1,x2,x3){GLctx["renderbufferStorage"](x0,x1,x2,x3)}function _emscripten_glSampleCoverage(value,invert){GLctx.sampleCoverage(value,!!invert)}function _emscripten_glScissor(x0,x1,x2,x3){GLctx["scissor"](x0,x1,x2,x3)}function _emscripten_glShaderBinary(){GL.recordError(1280)}function _emscripten_glShaderSource(shader,count,string,length){var source=GL.getSource(shader,count,string,length);GLctx.shaderSource(GL.shaders[shader],source)}function _emscripten_glStencilFunc(x0,x1,x2){GLctx["stencilFunc"](x0,x1,x2)}function _emscripten_glStencilFuncSeparate(x0,x1,x2,x3){GLctx["stencilFuncSeparate"](x0,x1,x2,x3)}function _emscripten_glStencilMask(x0){GLctx["stencilMask"](x0)}function _emscripten_glStencilMaskSeparate(x0,x1){GLctx["stencilMaskSeparate"](x0,x1)}function _emscripten_glStencilOp(x0,x1,x2){GLctx["stencilOp"](x0,x1,x2)}function _emscripten_glStencilOpSeparate(x0,x1,x2,x3){GLctx["stencilOpSeparate"](x0,x1,x2,x3)}function _emscripten_glTexImage2D(target,level,internalFormat,width,height,border,format,type,pixels){GLctx.texImage2D(target,level,internalFormat,width,height,border,format,type,pixels?emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,internalFormat):null)}function _emscripten_glTexParameterf(x0,x1,x2){GLctx["texParameterf"](x0,x1,x2)}function _emscripten_glTexParameterfv(target,pname,params){var param=HEAPF32[params>>2];GLctx.texParameterf(target,pname,param)}function _emscripten_glTexParameteri(x0,x1,x2){GLctx["texParameteri"](x0,x1,x2)}function _emscripten_glTexParameteriv(target,pname,params){var param=HEAP32[params>>2];GLctx.texParameteri(target,pname,param)}function _emscripten_glTexSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixels){var pixelData=null;if(pixels)pixelData=emscriptenWebGLGetTexPixelData(type,format,width,height,pixels,0);GLctx.texSubImage2D(target,level,xoffset,yoffset,width,height,format,type,pixelData)}function _emscripten_glUniform1f(location,v0){GLctx.uniform1f(GL.uniforms[location],v0)}var miniTempWebGLFloatBuffers=[];function _emscripten_glUniform1fv(location,count,value){if(count<=288){var view=miniTempWebGLFloatBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1fv(GL.uniforms[location],view)}function _emscripten_glUniform1i(location,v0){GLctx.uniform1i(GL.uniforms[location],v0)}var __miniTempWebGLIntBuffers=[];function _emscripten_glUniform1iv(location,count,value){if(count<=288){var view=__miniTempWebGLIntBuffers[count-1];for(var i=0;i>2]}}else{var view=HEAP32.subarray(value>>2,value+count*4>>2)}GLctx.uniform1iv(GL.uniforms[location],view)}function _emscripten_glUniform2f(location,v0,v1){GLctx.uniform2f(GL.uniforms[location],v0,v1)}function _emscripten_glUniform2fv(location,count,value){if(count<=144){var view=miniTempWebGLFloatBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(GL.uniforms[location],view)}function _emscripten_glUniform2i(location,v0,v1){GLctx.uniform2i(GL.uniforms[location],v0,v1)}function _emscripten_glUniform2iv(location,count,value){if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(GL.uniforms[location],view)}function _emscripten_glUniform3f(location,v0,v1,v2){GLctx.uniform3f(GL.uniforms[location],v0,v1,v2)}function _emscripten_glUniform3fv(location,count,value){if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(GL.uniforms[location],view)}function _emscripten_glUniform3i(location,v0,v1,v2){GLctx.uniform3i(GL.uniforms[location],v0,v1,v2)}function _emscripten_glUniform3iv(location,count,value){if(count<=96){var view=__miniTempWebGLIntBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(GL.uniforms[location],view)}function _emscripten_glUniform4f(location,v0,v1,v2,v3){GLctx.uniform4f(GL.uniforms[location],v0,v1,v2,v3)}function _emscripten_glUniform4fv(location,count,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(GL.uniforms[location],view)}function _emscripten_glUniform4i(location,v0,v1,v2,v3){GLctx.uniform4i(GL.uniforms[location],v0,v1,v2,v3)}function _emscripten_glUniform4iv(location,count,value){if(count<=72){var view=__miniTempWebGLIntBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(GL.uniforms[location],view)}function _emscripten_glUniformMatrix2fv(location,count,transpose,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniformMatrix2fv(GL.uniforms[location],!!transpose,view)}function _emscripten_glUniformMatrix3fv(location,count,transpose,value){if(count<=32){var view=miniTempWebGLFloatBuffers[9*count-1];for(var i=0;i<9*count;i+=9){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2];view[i+3]=HEAPF32[value+(4*i+12)>>2];view[i+4]=HEAPF32[value+(4*i+16)>>2];view[i+5]=HEAPF32[value+(4*i+20)>>2];view[i+6]=HEAPF32[value+(4*i+24)>>2];view[i+7]=HEAPF32[value+(4*i+28)>>2];view[i+8]=HEAPF32[value+(4*i+32)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*36>>2)}GLctx.uniformMatrix3fv(GL.uniforms[location],!!transpose,view)}function _emscripten_glUniformMatrix4fv(location,count,transpose,value){if(count<=18){var view=miniTempWebGLFloatBuffers[16*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<16*count;i+=16){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3];view[i+4]=heap[dst+4];view[i+5]=heap[dst+5];view[i+6]=heap[dst+6];view[i+7]=heap[dst+7];view[i+8]=heap[dst+8];view[i+9]=heap[dst+9];view[i+10]=heap[dst+10];view[i+11]=heap[dst+11];view[i+12]=heap[dst+12];view[i+13]=heap[dst+13];view[i+14]=heap[dst+14];view[i+15]=heap[dst+15]}}else{var view=HEAPF32.subarray(value>>2,value+count*64>>2)}GLctx.uniformMatrix4fv(GL.uniforms[location],!!transpose,view)}function _emscripten_glUseProgram(program){GLctx.useProgram(GL.programs[program])}function _emscripten_glValidateProgram(program){GLctx.validateProgram(GL.programs[program])}function _emscripten_glVertexAttrib1f(x0,x1){GLctx["vertexAttrib1f"](x0,x1)}function _emscripten_glVertexAttrib1fv(index,v){GLctx.vertexAttrib1f(index,HEAPF32[v>>2])}function _emscripten_glVertexAttrib2f(x0,x1,x2){GLctx["vertexAttrib2f"](x0,x1,x2)}function _emscripten_glVertexAttrib2fv(index,v){GLctx.vertexAttrib2f(index,HEAPF32[v>>2],HEAPF32[v+4>>2])}function _emscripten_glVertexAttrib3f(x0,x1,x2,x3){GLctx["vertexAttrib3f"](x0,x1,x2,x3)}function _emscripten_glVertexAttrib3fv(index,v){GLctx.vertexAttrib3f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2])}function _emscripten_glVertexAttrib4f(x0,x1,x2,x3,x4){GLctx["vertexAttrib4f"](x0,x1,x2,x3,x4)}function _emscripten_glVertexAttrib4fv(index,v){GLctx.vertexAttrib4f(index,HEAPF32[v>>2],HEAPF32[v+4>>2],HEAPF32[v+8>>2],HEAPF32[v+12>>2])}function _emscripten_glVertexAttribDivisorANGLE(index,divisor){GLctx["vertexAttribDivisor"](index,divisor)}function _emscripten_glVertexAttribPointer(index,size,type,normalized,stride,ptr){GLctx.vertexAttribPointer(index,size,type,!!normalized,stride,ptr)}function _emscripten_glViewport(x0,x1,x2,x3){GLctx["viewport"](x0,x1,x2,x3)}function _longjmp(env,value){_setThrew(env,value||1);throw"longjmp"}function _emscripten_longjmp(a0,a1){return _longjmp(a0,a1)}function _emscripten_memcpy_big(dest,src,num){HEAPU8.copyWithin(dest,src,src+num)}function _emscripten_get_heap_size(){return HEAPU8.length}function emscripten_realloc_buffer(size){try{wasmMemory.grow(size-buffer.byteLength+65535>>>16);updateGlobalBufferAndViews(wasmMemory.buffer);return 1}catch(e){}}function _emscripten_resize_heap(requestedSize){requestedSize=requestedSize>>>0;var oldSize=_emscripten_get_heap_size();var maxHeapSize=2147483648;if(requestedSize>maxHeapSize){return false}var minHeapSize=16777216;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(minHeapSize,requestedSize,overGrownHeapSize),65536));var replacement=emscripten_realloc_buffer(newSize);if(replacement){return true}}return false}var ENV={};function getExecutableName(){return thisProgram||"./this.program"}function getEnvStrings(){if(!getEnvStrings.strings){var lang=(typeof navigator==="object"&&navigator.languages&&navigator.languages[0]||"C").replace("-","_")+".UTF-8";var env={"USER":"web_user","LOGNAME":"web_user","PATH":"/","PWD":"/","HOME":"/home/web_user","LANG":lang,"_":getExecutableName()};for(var x in ENV){env[x]=ENV[x]}var strings=[];for(var x in env){strings.push(x+"="+env[x])}getEnvStrings.strings=strings}return getEnvStrings.strings}function _environ_get(__environ,environ_buf){var bufSize=0;getEnvStrings().forEach(function(string,i){var ptr=environ_buf+bufSize;HEAP32[__environ+i*4>>2]=ptr;writeAsciiToMemory(string,ptr);bufSize+=string.length+1});return 0}function _environ_sizes_get(penviron_count,penviron_buf_size){var strings=getEnvStrings();HEAP32[penviron_count>>2]=strings.length;var bufSize=0;strings.forEach(function(string){bufSize+=string.length+1});HEAP32[penviron_buf_size>>2]=bufSize;return 0}function _exit(status){exit(status)}function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_fdstat_get(fd,pbuf){try{var stream=SYSCALLS.getStreamFromFD(fd);var type=stream.tty?2:FS.isDir(stream.mode)?3:FS.isLink(stream.mode)?7:4;HEAP8[pbuf>>0]=type;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_read(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doReadv(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_seek(fd,offset_low,offset_high,whence,newOffset){try{var stream=SYSCALLS.getStreamFromFD(fd);var HIGH_OFFSET=4294967296;var offset=offset_high*HIGH_OFFSET+(offset_low>>>0);var DOUBLE_LIMIT=9007199254740992;if(offset<=-DOUBLE_LIMIT||offset>=DOUBLE_LIMIT){return-61}FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math_abs(tempDouble)>=1?tempDouble>0?(Math_min(+Math_floor(tempDouble/4294967296),4294967295)|0)>>>0:~~+Math_ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],HEAP32[newOffset>>2]=tempI64[0],HEAP32[newOffset+4>>2]=tempI64[1];if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=SYSCALLS.doWritev(stream,iov,iovcnt);HEAP32[pnum>>2]=num;return 0}catch(e){if(typeof FS==="undefined"||!(e instanceof FS.ErrnoError))abort(e);return e.errno}}function _getTempRet0(){return getTempRet0()|0}function __inet_pton4_raw(str){var b=str.split(".");for(var i=0;i<4;i++){var tmp=Number(b[i]);if(isNaN(tmp))return null;b[i]=tmp}return(b[0]|b[1]<<8|b[2]<<16|b[3]<<24)>>>0}function __inet_pton6_raw(str){var words;var w,offset,z;var valid6regx=/^((?=.*::)(?!.*::.+::)(::)?([\dA-F]{1,4}:(:|\b)|){5}|([\dA-F]{1,4}:){6})((([\dA-F]{1,4}((?!\3)::|:\b|$))|(?!\2\3)){2}|(((2[0-4]|1\d|[1-9])?\d|25[0-5])\.?\b){4})$/i;var parts=[];if(!valid6regx.test(str)){return null}if(str==="::"){return[0,0,0,0,0,0,0,0]}if(str.indexOf("::")===0){str=str.replace("::","Z:")}else{str=str.replace("::",":Z:")}if(str.indexOf(".")>0){str=str.replace(new RegExp("[.]","g"),":");words=str.split(":");words[words.length-4]=jstoi_q(words[words.length-4])+jstoi_q(words[words.length-3])*256;words[words.length-3]=jstoi_q(words[words.length-2])+jstoi_q(words[words.length-1])*256;words=words.slice(0,words.length-2)}else{words=str.split(":")}offset=0;z=0;for(w=0;w>2]=nameBuf;var aliasesBuf=_malloc(4);HEAP32[aliasesBuf>>2]=0;HEAP32[ret+4>>2]=aliasesBuf;var afinet=2;HEAP32[ret+8>>2]=afinet;HEAP32[ret+12>>2]=4;var addrListBuf=_malloc(12);HEAP32[addrListBuf>>2]=addrListBuf+8;HEAP32[addrListBuf+4>>2]=0;HEAP32[addrListBuf+8>>2]=__inet_pton4_raw(DNS.lookup_name(name));HEAP32[ret+16>>2]=addrListBuf;return ret}function _getpwnam(){throw"getpwnam: TODO"}function _gettimeofday(ptr){var now=Date.now();HEAP32[ptr>>2]=now/1e3|0;HEAP32[ptr+4>>2]=now%1e3*1e3|0;return 0}function _glGetUniformLocation(program,name){name=UTF8ToString(name);var arrayIndex=0;if(name[name.length-1]=="]"){var leftBrace=name.lastIndexOf("[");arrayIndex=name[leftBrace+1]!="]"?jstoi_q(name.slice(leftBrace+1)):0;name=name.slice(0,leftBrace)}var uniformInfo=GL.programInfos[program]&&GL.programInfos[program].uniforms[name];if(uniformInfo&&arrayIndex>=0&&arrayIndex>2];view[i+1]=HEAPF32[value+(4*i+4)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2fv(GL.uniforms[location],view)}function _glUniform2iv(location,count,value){if(count<=144){var view=__miniTempWebGLIntBuffers[2*count-1];for(var i=0;i<2*count;i+=2){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*8>>2)}GLctx.uniform2iv(GL.uniforms[location],view)}function _glUniform3fv(location,count,value){if(count<=96){var view=miniTempWebGLFloatBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAPF32[value+4*i>>2];view[i+1]=HEAPF32[value+(4*i+4)>>2];view[i+2]=HEAPF32[value+(4*i+8)>>2]}}else{var view=HEAPF32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3fv(GL.uniforms[location],view)}function _glUniform3iv(location,count,value){if(count<=96){var view=__miniTempWebGLIntBuffers[3*count-1];for(var i=0;i<3*count;i+=3){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*12>>2)}GLctx.uniform3iv(GL.uniforms[location],view)}function _glUniform4fv(location,count,value){if(count<=72){var view=miniTempWebGLFloatBuffers[4*count-1];var heap=HEAPF32;value>>=2;for(var i=0;i<4*count;i+=4){var dst=value+i;view[i]=heap[dst];view[i+1]=heap[dst+1];view[i+2]=heap[dst+2];view[i+3]=heap[dst+3]}}else{var view=HEAPF32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4fv(GL.uniforms[location],view)}function _glUniform4iv(location,count,value){if(count<=72){var view=__miniTempWebGLIntBuffers[4*count-1];for(var i=0;i<4*count;i+=4){view[i]=HEAP32[value+4*i>>2];view[i+1]=HEAP32[value+(4*i+4)>>2];view[i+2]=HEAP32[value+(4*i+8)>>2];view[i+3]=HEAP32[value+(4*i+12)>>2]}}else{var view=HEAP32.subarray(value>>2,value+count*16>>2)}GLctx.uniform4iv(GL.uniforms[location],view)}function _llvm_eh_typeid_for(type){return type}function _usleep(useconds){var start=_emscripten_get_now();while(_emscripten_get_now()-start>2];var nanoseconds=HEAP32[rqtp+4>>2];if(nanoseconds<0||nanoseconds>999999999||seconds<0){setErrNo(28);return-1}if(rmtp!==0){HEAP32[rmtp>>2]=0;HEAP32[rmtp+4>>2]=0}return _usleep(seconds*1e6+nanoseconds/1e3)}function _pthread_create(){return 6}function _pthread_detach(){}function _pthread_join(){}function _pthread_mutexattr_destroy(){}function _pthread_mutexattr_init(){}function _pthread_mutexattr_settype(){}function _setTempRet0($i){setTempRet0($i|0)}function _sigaction(signum,act,oldact){return 0}function _sigaddset(set,signum){HEAP32[set>>2]=HEAP32[set>>2]|1<>2]=0;return 0}function _sigprocmask(){return 0}function __isLeapYear(year){return year%4===0&&(year%100!==0||year%400===0)}function __arraySum(array,index){var sum=0;for(var i=0;i<=index;sum+=array[i++]){}return sum}var __MONTH_DAYS_LEAP=[31,29,31,30,31,30,31,31,30,31,30,31];var __MONTH_DAYS_REGULAR=[31,28,31,30,31,30,31,31,30,31,30,31];function __addDays(date,days){var newDate=new Date(date.getTime());while(days>0){var leap=__isLeapYear(newDate.getFullYear());var currentMonth=newDate.getMonth();var daysInCurrentMonth=(leap?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR)[currentMonth];if(days>daysInCurrentMonth-newDate.getDate()){days-=daysInCurrentMonth-newDate.getDate()+1;newDate.setDate(1);if(currentMonth<11){newDate.setMonth(currentMonth+1)}else{newDate.setMonth(0);newDate.setFullYear(newDate.getFullYear()+1)}}else{newDate.setDate(newDate.getDate()+days);return newDate}}return newDate}function _strftime(s,maxsize,format,tm){var tm_zone=HEAP32[tm+40>>2];var date={tm_sec:HEAP32[tm>>2],tm_min:HEAP32[tm+4>>2],tm_hour:HEAP32[tm+8>>2],tm_mday:HEAP32[tm+12>>2],tm_mon:HEAP32[tm+16>>2],tm_year:HEAP32[tm+20>>2],tm_wday:HEAP32[tm+24>>2],tm_yday:HEAP32[tm+28>>2],tm_isdst:HEAP32[tm+32>>2],tm_gmtoff:HEAP32[tm+36>>2],tm_zone:tm_zone?UTF8ToString(tm_zone):""};var pattern=UTF8ToString(format);var EXPANSION_RULES_1={"%c":"%a %b %d %H:%M:%S %Y","%D":"%m/%d/%y","%F":"%Y-%m-%d","%h":"%b","%r":"%I:%M:%S %p","%R":"%H:%M","%T":"%H:%M:%S","%x":"%m/%d/%y","%X":"%H:%M:%S","%Ec":"%c","%EC":"%C","%Ex":"%m/%d/%y","%EX":"%H:%M:%S","%Ey":"%y","%EY":"%Y","%Od":"%d","%Oe":"%e","%OH":"%H","%OI":"%I","%Om":"%m","%OM":"%M","%OS":"%S","%Ou":"%u","%OU":"%U","%OV":"%V","%Ow":"%w","%OW":"%W","%Oy":"%y"};for(var rule in EXPANSION_RULES_1){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_1[rule])}var WEEKDAYS=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var MONTHS=["January","February","March","April","May","June","July","August","September","October","November","December"];function leadingSomething(value,digits,character){var str=typeof value==="number"?value.toString():value||"";while(str.length0?1:0}var compare;if((compare=sgn(date1.getFullYear()-date2.getFullYear()))===0){if((compare=sgn(date1.getMonth()-date2.getMonth()))===0){compare=sgn(date1.getDate()-date2.getDate())}}return compare}function getFirstWeekStartDate(janFourth){switch(janFourth.getDay()){case 0:return new Date(janFourth.getFullYear()-1,11,29);case 1:return janFourth;case 2:return new Date(janFourth.getFullYear(),0,3);case 3:return new Date(janFourth.getFullYear(),0,2);case 4:return new Date(janFourth.getFullYear(),0,1);case 5:return new Date(janFourth.getFullYear()-1,11,31);case 6:return new Date(janFourth.getFullYear()-1,11,30)}}function getWeekBasedYear(date){var thisDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);var janFourthThisYear=new Date(thisDate.getFullYear(),0,4);var janFourthNextYear=new Date(thisDate.getFullYear()+1,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);if(compareByDay(firstWeekStartThisYear,thisDate)<=0){if(compareByDay(firstWeekStartNextYear,thisDate)<=0){return thisDate.getFullYear()+1}else{return thisDate.getFullYear()}}else{return thisDate.getFullYear()-1}}var EXPANSION_RULES_2={"%a":function(date){return WEEKDAYS[date.tm_wday].substring(0,3)},"%A":function(date){return WEEKDAYS[date.tm_wday]},"%b":function(date){return MONTHS[date.tm_mon].substring(0,3)},"%B":function(date){return MONTHS[date.tm_mon]},"%C":function(date){var year=date.tm_year+1900;return leadingNulls(year/100|0,2)},"%d":function(date){return leadingNulls(date.tm_mday,2)},"%e":function(date){return leadingSomething(date.tm_mday,2," ")},"%g":function(date){return getWeekBasedYear(date).toString().substring(2)},"%G":function(date){return getWeekBasedYear(date)},"%H":function(date){return leadingNulls(date.tm_hour,2)},"%I":function(date){var twelveHour=date.tm_hour;if(twelveHour==0)twelveHour=12;else if(twelveHour>12)twelveHour-=12;return leadingNulls(twelveHour,2)},"%j":function(date){return leadingNulls(date.tm_mday+__arraySum(__isLeapYear(date.tm_year+1900)?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,date.tm_mon-1),3)},"%m":function(date){return leadingNulls(date.tm_mon+1,2)},"%M":function(date){return leadingNulls(date.tm_min,2)},"%n":function(){return"\n"},"%p":function(date){if(date.tm_hour>=0&&date.tm_hour<12){return"AM"}else{return"PM"}},"%S":function(date){return leadingNulls(date.tm_sec,2)},"%t":function(){return"\t"},"%u":function(date){return date.tm_wday||7},"%U":function(date){var janFirst=new Date(date.tm_year+1900,0,1);var firstSunday=janFirst.getDay()===0?janFirst:__addDays(janFirst,7-janFirst.getDay());var endDate=new Date(date.tm_year+1900,date.tm_mon,date.tm_mday);if(compareByDay(firstSunday,endDate)<0){var februaryFirstUntilEndMonth=__arraySum(__isLeapYear(endDate.getFullYear())?__MONTH_DAYS_LEAP:__MONTH_DAYS_REGULAR,endDate.getMonth()-1)-31;var firstSundayUntilEndJanuary=31-firstSunday.getDate();var days=firstSundayUntilEndJanuary+februaryFirstUntilEndMonth+endDate.getDate();return leadingNulls(Math.ceil(days/7),2)}return compareByDay(firstSunday,janFirst)===0?"01":"00"},"%V":function(date){var janFourthThisYear=new Date(date.tm_year+1900,0,4);var janFourthNextYear=new Date(date.tm_year+1901,0,4);var firstWeekStartThisYear=getFirstWeekStartDate(janFourthThisYear);var firstWeekStartNextYear=getFirstWeekStartDate(janFourthNextYear);var endDate=__addDays(new Date(date.tm_year+1900,0,1),date.tm_yday);if(compareByDay(endDate,firstWeekStartThisYear)<0){return"53"}if(compareByDay(firstWeekStartNextYear,endDate)<=0){return"01"}var daysDifference;if(firstWeekStartThisYear.getFullYear()=0;off=Math.abs(off)/60;off=off/60*100+off%60;return(ahead?"+":"-")+String("0000"+off).slice(-4)},"%Z":function(date){return date.tm_zone},"%%":function(){return"%"}};for(var rule in EXPANSION_RULES_2){if(pattern.indexOf(rule)>=0){pattern=pattern.replace(new RegExp(rule,"g"),EXPANSION_RULES_2[rule](date))}}var bytes=intArrayFromString(pattern,false);if(bytes.length>maxsize){return 0}writeArrayToMemory(bytes,s);return bytes.length-1}function _strftime_l(s,maxsize,format,tm){return _strftime(s,maxsize,format,tm)}function _sysconf(name){switch(name){case 30:return 16384;case 85:var maxHeapSize=2147483648;return maxHeapSize/16384;case 132:case 133:case 12:case 137:case 138:case 15:case 235:case 16:case 17:case 18:case 19:case 20:case 149:case 13:case 10:case 236:case 153:case 9:case 21:case 22:case 159:case 154:case 14:case 77:case 78:case 139:case 80:case 81:case 82:case 68:case 67:case 164:case 11:case 29:case 47:case 48:case 95:case 52:case 51:case 46:case 79:return 200809;case 27:case 246:case 127:case 128:case 23:case 24:case 160:case 161:case 181:case 182:case 242:case 183:case 184:case 243:case 244:case 245:case 165:case 178:case 179:case 49:case 50:case 168:case 169:case 175:case 170:case 171:case 172:case 97:case 76:case 32:case 173:case 35:return-1;case 176:case 177:case 7:case 155:case 8:case 157:case 125:case 126:case 92:case 93:case 129:case 130:case 131:case 94:case 91:return 1;case 74:case 60:case 69:case 70:case 4:return 1024;case 31:case 42:case 72:return 32;case 87:case 26:case 33:return 2147483647;case 34:case 1:return 47839;case 38:case 36:return 99;case 43:case 37:return 2048;case 0:return 2097152;case 3:return 65536;case 28:return 32768;case 44:return 32767;case 75:return 16384;case 39:return 1e3;case 89:return 700;case 71:return 256;case 40:return 255;case 2:return 100;case 180:return 64;case 25:return 20;case 5:return 16;case 6:return 6;case 73:return 4;case 84:{if(typeof navigator==="object")return navigator["hardwareConcurrency"]||1;return 1}}setErrNo(28);return-1}function _time(ptr){var ret=Date.now()/1e3|0;if(ptr){HEAP32[ptr>>2]=ret}return ret}function _times(buffer){if(buffer!==0){_memset(buffer,0,16)}return 0}var FSNode=function(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev};var readMode=292|73;var writeMode=146;Object.defineProperties(FSNode.prototype,{read:{get:function(){return(this.mode&readMode)===readMode},set:function(val){val?this.mode|=readMode:this.mode&=~readMode}},write:{get:function(){return(this.mode&writeMode)===writeMode},set:function(val){val?this.mode|=writeMode:this.mode&=~writeMode}},isFolder:{get:function(){return FS.isDir(this.mode)}},isDevice:{get:function(){return FS.isChrdev(this.mode)}}});FS.FSNode=FSNode;FS.staticInit();embind_init_charCodes();BindingError=Module["BindingError"]=extendError(Error,"BindingError");InternalError=Module["InternalError"]=extendError(Error,"InternalError");init_ClassHandle();init_RegisteredPointer();init_embind();UnboundTypeError=Module["UnboundTypeError"]=extendError(Error,"UnboundTypeError");init_emval();var GLctx;for(var i=0;i<32;++i)tempFixedLengthArray.push(new Array(i));var miniTempWebGLFloatBuffersStorage=new Float32Array(288);for(var i=0;i<288;++i){miniTempWebGLFloatBuffers[i]=miniTempWebGLFloatBuffersStorage.subarray(0,i+1)}var __miniTempWebGLIntBuffersStorage=new Int32Array(288);for(var i=0;i<288;++i){__miniTempWebGLIntBuffers[i]=__miniTempWebGLIntBuffersStorage.subarray(0,i+1)}function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}__ATINIT__.push({func:function(){___wasm_call_ctors()}});var asmLibraryArg={"lm":___clock_gettime,"t":___cxa_allocate_exception,"pj":___cxa_atexit,"$":___cxa_begin_catch,"xa":___cxa_end_catch,"f":___cxa_find_matching_catch_2,"j":___cxa_find_matching_catch_3,"Ba":___cxa_find_matching_catch_4,"Dc":___cxa_find_matching_catch_5,"w":___cxa_free_exception,"yo":___cxa_rethrow,"Ye":___cxa_thread_atexit,"x":___cxa_throw,"b":wasmTable,"Wl":___localtime_r,"Xl":___map_file,"l":___resumeException,"km":___sys__newselect,"gm":___sys_access,"am":___sys_chdir,"om":___sys_chmod,"Tf":___sys_fcntl64,"nm":___sys_fstat64,"dm":___sys_getcwd,"jm":___sys_getdents64,"cm":___sys_getpid,"fm":___sys_getuid32,"Zl":___sys_ioctl,"pm":___sys_mkdir,"_l":___sys_mmap2,"$l":___sys_munmap,"Qg":___sys_open,"em":___sys_read,"Pg":___sys_stat64,"mm":___sys_statfs64,"qm":___sys_umask,"im":___sys_uname,"bm":___sys_unlink,"sm":__embind_register_bool,"m":__embind_register_class,"r":__embind_register_class_class_function,"o":__embind_register_class_constructor,"g":__embind_register_class_function,"rm":__embind_register_emval,"R":__embind_register_enum,"y":__embind_register_enum_value,"Rg":__embind_register_float,"bd":__embind_register_integer,"Kc":__embind_register_memory_view,"Sg":__embind_register_std_string,"Uf":__embind_register_std_wstring,"tm":__embind_register_void,"Ng":_abort,"Hd":_clock_gettime,"mo":_dlclose,"lo":_dlerror,"ri":_dlopen,"no":_dlsym,"yl":_emscripten_glActiveTexture,"xl":_emscripten_glAttachShader,"Ol":_emscripten_glBeginQueryEXT,"wl":_emscripten_glBindAttribLocation,"vl":_emscripten_glBindBuffer,"ul":_emscripten_glBindFramebuffer,"tl":_emscripten_glBindRenderbuffer,"sl":_emscripten_glBindTexture,"Gl":_emscripten_glBindVertexArrayOES,"rl":_emscripten_glBlendColor,"ql":_emscripten_glBlendEquation,"pl":_emscripten_glBlendEquationSeparate,"ol":_emscripten_glBlendFunc,"nl":_emscripten_glBlendFuncSeparate,"ml":_emscripten_glBufferData,"ll":_emscripten_glBufferSubData,"kl":_emscripten_glCheckFramebufferStatus,"jl":_emscripten_glClear,"il":_emscripten_glClearColor,"hl":_emscripten_glClearDepthf,"gl":_emscripten_glClearStencil,"fl":_emscripten_glColorMask,"el":_emscripten_glCompileShader,"dl":_emscripten_glCompressedTexImage2D,"cl":_emscripten_glCompressedTexSubImage2D,"bl":_emscripten_glCopyTexImage2D,"al":_emscripten_glCopyTexSubImage2D,"$k":_emscripten_glCreateProgram,"_k":_emscripten_glCreateShader,"Zk":_emscripten_glCullFace,"Yk":_emscripten_glDeleteBuffers,"Xk":_emscripten_glDeleteFramebuffers,"Wk":_emscripten_glDeleteProgram,"Ql":_emscripten_glDeleteQueriesEXT,"Vk":_emscripten_glDeleteRenderbuffers,"Uk":_emscripten_glDeleteShader,"Tk":_emscripten_glDeleteTextures,"Fl":_emscripten_glDeleteVertexArraysOES,"Sk":_emscripten_glDepthFunc,"Rk":_emscripten_glDepthMask,"Qk":_emscripten_glDepthRangef,"Pk":_emscripten_glDetachShader,"Ok":_emscripten_glDisable,"Nk":_emscripten_glDisableVertexAttribArray,"Mk":_emscripten_glDrawArrays,"Bl":_emscripten_glDrawArraysInstancedANGLE,"Cl":_emscripten_glDrawBuffersWEBGL,"Lk":_emscripten_glDrawElements,"Al":_emscripten_glDrawElementsInstancedANGLE,"Jk":_emscripten_glEnable,"Ik":_emscripten_glEnableVertexAttribArray,"Nl":_emscripten_glEndQueryEXT,"Hk":_emscripten_glFinish,"Gk":_emscripten_glFlush,"Fk":_emscripten_glFramebufferRenderbuffer,"Ek":_emscripten_glFramebufferTexture2D,"Dk":_emscripten_glFrontFace,"Ck":_emscripten_glGenBuffers,"Ak":_emscripten_glGenFramebuffers,"Rl":_emscripten_glGenQueriesEXT,"yk":_emscripten_glGenRenderbuffers,"xk":_emscripten_glGenTextures,"El":_emscripten_glGenVertexArraysOES,"Bk":_emscripten_glGenerateMipmap,"wk":_emscripten_glGetActiveAttrib,"vk":_emscripten_glGetActiveUniform,"uk":_emscripten_glGetAttachedShaders,"tk":_emscripten_glGetAttribLocation,"sk":_emscripten_glGetBooleanv,"rk":_emscripten_glGetBufferParameteriv,"qk":_emscripten_glGetError,"pk":_emscripten_glGetFloatv,"nk":_emscripten_glGetFramebufferAttachmentParameteriv,"mk":_emscripten_glGetIntegerv,"kk":_emscripten_glGetProgramInfoLog,"lk":_emscripten_glGetProgramiv,"Il":_emscripten_glGetQueryObjecti64vEXT,"Kl":_emscripten_glGetQueryObjectivEXT,"Hl":_emscripten_glGetQueryObjectui64vEXT,"Jl":_emscripten_glGetQueryObjectuivEXT,"Ll":_emscripten_glGetQueryivEXT,"jk":_emscripten_glGetRenderbufferParameteriv,"hk":_emscripten_glGetShaderInfoLog,"gk":_emscripten_glGetShaderPrecisionFormat,"fk":_emscripten_glGetShaderSource,"ik":_emscripten_glGetShaderiv,"ek":_emscripten_glGetString,"ck":_emscripten_glGetTexParameterfv,"bk":_emscripten_glGetTexParameteriv,"_j":_emscripten_glGetUniformLocation,"ak":_emscripten_glGetUniformfv,"$j":_emscripten_glGetUniformiv,"Xj":_emscripten_glGetVertexAttribPointerv,"Zj":_emscripten_glGetVertexAttribfv,"Yj":_emscripten_glGetVertexAttribiv,"Wj":_emscripten_glHint,"Vj":_emscripten_glIsBuffer,"Uj":_emscripten_glIsEnabled,"Tj":_emscripten_glIsFramebuffer,"Sj":_emscripten_glIsProgram,"Pl":_emscripten_glIsQueryEXT,"Rj":_emscripten_glIsRenderbuffer,"Qj":_emscripten_glIsShader,"Pj":_emscripten_glIsTexture,"Dl":_emscripten_glIsVertexArrayOES,"Oj":_emscripten_glLineWidth,"Nj":_emscripten_glLinkProgram,"Mj":_emscripten_glPixelStorei,"Lj":_emscripten_glPolygonOffset,"Ml":_emscripten_glQueryCounterEXT,"Jj":_emscripten_glReadPixels,"Ij":_emscripten_glReleaseShaderCompiler,"Hj":_emscripten_glRenderbufferStorage,"Gj":_emscripten_glSampleCoverage,"Fj":_emscripten_glScissor,"Ej":_emscripten_glShaderBinary,"Dj":_emscripten_glShaderSource,"Cj":_emscripten_glStencilFunc,"Bj":_emscripten_glStencilFuncSeparate,"Aj":_emscripten_glStencilMask,"zj":_emscripten_glStencilMaskSeparate,"yj":_emscripten_glStencilOp,"xj":_emscripten_glStencilOpSeparate,"wj":_emscripten_glTexImage2D,"vj":_emscripten_glTexParameterf,"uj":_emscripten_glTexParameterfv,"tj":_emscripten_glTexParameteri,"sj":_emscripten_glTexParameteriv,"rj":_emscripten_glTexSubImage2D,"qj":_emscripten_glUniform1f,"nj":_emscripten_glUniform1fv,"mj":_emscripten_glUniform1i,"lj":_emscripten_glUniform1iv,"kj":_emscripten_glUniform2f,"jj":_emscripten_glUniform2fv,"ij":_emscripten_glUniform2i,"hj":_emscripten_glUniform2iv,"gj":_emscripten_glUniform3f,"fj":_emscripten_glUniform3fv,"ej":_emscripten_glUniform3i,"dj":_emscripten_glUniform3iv,"cj":_emscripten_glUniform4f,"bj":_emscripten_glUniform4fv,"aj":_emscripten_glUniform4i,"$i":_emscripten_glUniform4iv,"_i":_emscripten_glUniformMatrix2fv,"Zi":_emscripten_glUniformMatrix3fv,"Yi":_emscripten_glUniformMatrix4fv,"Xi":_emscripten_glUseProgram,"Wi":_emscripten_glValidateProgram,"Vi":_emscripten_glVertexAttrib1f,"Ui":_emscripten_glVertexAttrib1fv,"Ti":_emscripten_glVertexAttrib2f,"Si":_emscripten_glVertexAttrib2fv,"Ri":_emscripten_glVertexAttrib3f,"Qi":_emscripten_glVertexAttrib3fv,"Pi":_emscripten_glVertexAttrib4f,"Oi":_emscripten_glVertexAttrib4fv,"zl":_emscripten_glVertexAttribDivisorANGLE,"Ni":_emscripten_glVertexAttribPointer,"Mi":_emscripten_glViewport,"D":_emscripten_longjmp,"Sl":_emscripten_memcpy_big,"Tl":_emscripten_resize_heap,"Ul":_environ_get,"Vl":_environ_sizes_get,"kd":_exit,"of":_fd_close,"Og":_fd_fdstat_get,"Yl":_fd_read,"Ki":_fd_seek,"Sf":_fd_write,"e":_getTempRet0,"oo":_gethostbyname,"Fm":_getpwnam,"Pc":_gettimeofday,"Ad":_glGetUniformLocation,"Bn":_glUniform1f,"Cn":_glUniform1i,"xn":_glUniform2fv,"An":_glUniform2iv,"wn":_glUniform3fv,"zn":_glUniform3iv,"vn":_glUniform4fv,"yn":_glUniform4iv,"Xa":invoke_d,"Z":invoke_dd,"Q":invoke_ddd,"la":invoke_dddd,"hd":invoke_ddddd,"Xe":invoke_ddi,"Gd":invoke_ddii,"v":invoke_di,"M":invoke_did,"bc":invoke_didd,"Bb":invoke_diddd,"si":invoke_didddddidi,"Jf":invoke_didddidi,"La":invoke_diddi,"se":invoke_diddidii,"Wb":invoke_didi,"fo":invoke_dididd,"cg":invoke_didii,"Ve":invoke_didiidii,"go":invoke_didiidiiddi,"Ch":invoke_didiii,"Be":invoke_didiiii,"Lh":invoke_didiiiiidi,"B":invoke_dii,"Ga":invoke_diid,"pd":invoke_diidd,"kb":invoke_diiddd,"ti":invoke_diiddi,"nd":invoke_diidi,"Wd":invoke_diidii,"Jm":invoke_diidiiii,"da":invoke_diii,"fh":invoke_diiid,"ji":invoke_diiiddi,"If":invoke_diiidii,"jd":invoke_diiidiiddi,"Tb":invoke_diiidiii,"Im":invoke_diiidiiii,"ub":invoke_diiii,"bh":invoke_diiiid,"Tc":invoke_diiiidd,"Hm":invoke_diiiidii,"dh":invoke_diiiidiii,"Yf":invoke_diiiidiiiii,"Ub":invoke_diiiii,"Cd":invoke_diiiiii,"dc":invoke_diiiiiidiiii,"$g":invoke_diiiiiii,"s":invoke_i,"tn":invoke_iddddidddd,"sn":invoke_iddddiddi,"Uc":invoke_iddddiid,"un":invoke_iddddiii,"He":invoke_idddii,"$m":invoke_iddid,"bg":invoke_iddiddiiiii,"Zg":invoke_iddii,"Id":invoke_iddiii,"qd":invoke_iddiiiiii,"eh":invoke_iddiiiiiii,"Ne":invoke_idi,"yd":invoke_idid,"fc":invoke_idii,"jc":invoke_idiid,"qe":invoke_idiiddii,"uh":invoke_idiiididii,"Ee":invoke_idiiiii,"xc":invoke_idiiiiii,"c":invoke_ii,"K":invoke_iid,"Ha":invoke_iidd,"Pa":invoke_iiddd,"rc":invoke_iidddd,"$f":invoke_iiddddd,"mh":invoke_iiddddddd,"Zc":invoke_iiddddddddd,"dk":invoke_iiddddddddddddii,"vf":invoke_iiddddddddiii,"xo":invoke_iiddddddiiii,"uf":invoke_iiddddi,"Nf":invoke_iiddddii,"zb":invoke_iidddi,"Xm":invoke_iidddidd,"yh":invoke_iidddii,"Gh":invoke_iidddiii,"Bi":invoke_iidddiiiiii,"Ci":invoke_iidddiiiiiiiii,"Nm":invoke_iidddiiiiiiiiii,"Qa":invoke_iiddi,"xm":invoke_iiddid,"wf":invoke_iiddiddidii,"Yb":invoke_iiddii,"Ei":invoke_iiddiid,"Hn":invoke_iiddiiddd,"Zn":invoke_iiddiii,"mn":invoke_iiddiiid,"zo":invoke_iiddiiiii,"pf":invoke_iiddiiiiii,"Nn":invoke_iiddiiiiiiiiii,"Kk":invoke_iiddiiiiiiiiiii,"ha":invoke_iidi,"be":invoke_iidid,"mi":invoke_iididd,"Jn":invoke_iididdii,"Bh":invoke_iididi,"kg":invoke_iididiii,"Fa":invoke_iidii,"wo":invoke_iidiiddii,"Na":invoke_iidiii,"wc":invoke_iidiiid,"ud":invoke_iidiiidd,"nc":invoke_iidiiii,"pe":invoke_iidiiiidiiiiii,"Cf":invoke_iidiiiii,"Le":invoke_iidiiiiii,"Bf":invoke_iidiiiiiii,"Ue":invoke_iidiiiiiiiii,"lc":invoke_iif,"h":invoke_iii,"U":invoke_iiid,"W":invoke_iiidd,"Hb":invoke_iiiddd,"vb":invoke_iiidddd,"Df":invoke_iiiddddd,"wb":invoke_iiidddddd,"fg":invoke_iiidddddddddd,"ih":invoke_iiidddddddii,"Qc":invoke_iiidddddiii,"Rc":invoke_iiiddddi,"po":invoke_iiiddddid,"gg":invoke_iiiddddidd,"Ca":invoke_iiiddddii,"Ag":invoke_iiiddddiii,"ie":invoke_iiidddi,"Ah":invoke_iiidddid,"Hc":invoke_iiidddiid,"Pe":invoke_iiidddiiiii,"Za":invoke_iiiddi,"Eb":invoke_iiiddid,"rb":invoke_iiiddidd,"tc":invoke_iiiddidddd,"sf":invoke_iiiddidi,"N":invoke_iiiddii,"Gg":invoke_iiiddiidd,"gn":invoke_iiiddiii,"Hg":invoke_iiiddiiii,"gi":invoke_iiiddiiiii,"_":invoke_iiidi,"Fb":invoke_iiidid,"Re":invoke_iiididdii,"qo":invoke_iiididi,"Ma":invoke_iiidii,"Qf":invoke_iiidiid,"mg":invoke_iiidiidiid,"Ja":invoke_iiidiii,"ng":invoke_iiidiiii,"ki":invoke_iiidiiiid,"Fd":invoke_iiidiiiidd,"te":invoke_iiidiiiii,"Fc":invoke_iiidiiiiii,"fn":invoke_iiidiiiiiiiiiiiii,"p":invoke_iiii,"P":invoke_iiiid,"ta":invoke_iiiidd,"pb":invoke_iiiiddd,"$b":invoke_iiiidddd,"ye":invoke_iiiidddddd,"Um":invoke_iiiidddddddd,"lh":invoke_iiiidddddddddd,"bn":invoke_iiiiddddddi,"Ln":invoke_iiiiddddddii,"Jd":invoke_iiiiddddi,"ee":invoke_iiiiddddidd,"Yc":invoke_iiiidddi,"Sa":invoke_iiiidddid,"co":invoke_iiiidddiiii,"_d":invoke_iiiidddiiiii,"qc":invoke_iiiiddi,"Ph":invoke_iiiiddiddiiii,"nb":invoke_iiiiddii,"rh":invoke_iiiiddiid,"Ig":invoke_iiiiddiii,"kc":invoke_iiiiddiiii,"Ka":invoke_iiiidi,"jn":invoke_iiiidid,"Yg":invoke_iiiididi,"vc":invoke_iiiidii,"dn":invoke_iiiidiidiiiiiiiiii,"ib":invoke_iiiidiii,"Mh":invoke_iiiidiiid,"Yn":invoke_iiiidiiiddddddd,"ln":invoke_iiiidiiii,"an":invoke_iiiidiiiii,"sh":invoke_iiiidiiiiiid,"en":invoke_iiiidiiiiiiiiii,"u":invoke_iiiii,"va":invoke_iiiiid,"hb":invoke_iiiiidd,"md":invoke_iiiiiddd,"Lg":invoke_iiiiidddd,"rg":invoke_iiiiiddddi,"rd":invoke_iiiiiddi,"re":invoke_iiiiiddidi,"ac":invoke_iiiiiddii,"ke":invoke_iiiiiddiiddidiii,"le":invoke_iiiiiddiididii,"dd":invoke_iiiiiddiii,"In":invoke_iiiiiddiiii,"bo":invoke_iiiiiddiiiiiii,"pc":invoke_iiiiidi,"xb":invoke_iiiiididi,"Hf":invoke_iiiiidii,"_b":invoke_iiiiidiidd,"ug":invoke_iiiiidiii,"Xf":invoke_iiiiidiiidi,"ph":invoke_iiiiidiiiiii,"E":invoke_iiiiii,"ra":invoke_iiiiiid,"Vb":invoke_iiiiiidd,"Kj":invoke_iiiiiidddd,"Cm":invoke_iiiiiiddddii,"Lm":invoke_iiiiiidddii,"li":invoke_iiiiiiddi,"nf":invoke_iiiiiiddiddiii,"Th":invoke_iiiiiiddiddiiiii,"Ac":invoke_iiiiiiddiiddidii,"qf":invoke_iiiiiiddiiiii,"eg":invoke_iiiiiidi,"Qd":invoke_iiiiiidii,"Me":invoke_iiiiiidiii,"Rb":invoke_iiiiiidiiidd,"Fn":invoke_iiiiiidiiiii,"C":invoke_iiiiiii,"_a":invoke_iiiiiiid,"_m":invoke_iiiiiiiddi,"Rh":invoke_iiiiiiiddiddiiiiii,"Rn":invoke_iiiiiiiddidii,"jh":invoke_iiiiiiidi,"Sn":invoke_iiiiiiididi,"zh":invoke_iiiiiiidii,"F":invoke_iiiiiiii,"zg":invoke_iiiiiiiid,"ig":invoke_iiiiiiiidd,"xd":invoke_iiiiiiiidddddddddiiddii,"Vf":invoke_iiiiiiiidddddiiidddd,"je":invoke_iiiiiiiiddi,"Hh":invoke_iiiiiiiiddidi,"Cc":invoke_iiiiiiiiddii,"gf":invoke_iiiiiiiiddiiii,"od":invoke_iiiiiiiiddiiiii,"Mm":invoke_iiiiiiiididd,"nn":invoke_iiiiiiiidiii,"oa":invoke_iiiiiiiii,"Lb":invoke_iiiiiiiiid,"Nd":invoke_iiiiiiiiidddd,"gh":invoke_iiiiiiiiiddddii,"Tn":invoke_iiiiiiiiiddiiii,"fe":invoke_iiiiiiiiidi,"Af":invoke_iiiiiiiiidiii,"tg":invoke_iiiiiiiiidiiii,"X":invoke_iiiiiiiiii,"sg":invoke_iiiiiiiiiid,"Xh":invoke_iiiiiiiiiidddiiiiiiiiii,"pg":invoke_iiiiiiiiiiddidd,"Yh":invoke_iiiiiiiiiidiiiiiiiiii,"gb":invoke_iiiiiiiiiii,"xi":invoke_iiiiiiiiiiiddddiiiiiiiiii,"_f":invoke_iiiiiiiiiiiddddiiiiiiiiiii,"Xc":invoke_iiiiiiiiiiidi,"ab":invoke_iiiiiiiiiiii,"ge":invoke_iiiiiiiiiiiid,"Te":invoke_iiiiiiiiiiiiddddiiiiiiiii,"Td":invoke_iiiiiiiiiiiiddddiiiiiiiiiiiiii,"Bd":invoke_iiiiiiiiiiiiddiiiiii,"Xb":invoke_iiiiiiiiiiiii,"Gi":invoke_iiiiiiiiiiiiid,"sd":invoke_iiiiiiiiiiiiii,"Of":invoke_iiiiiiiiiiiiiid,"qg":invoke_iiiiiiiiiiiiiiddi,"pi":invoke_iiiiiiiiiiiiiii,"xf":invoke_iiiiiiiiiiiiiiiddddiiiiiiiii,"zf":invoke_iiiiiiiiiiiiiiiddddiiiiiiiiii,"_c":invoke_iiiiiiiiiiiiiiii,"to":invoke_iiiiiiiiiiiiiiiii,"Fh":invoke_iiiiiiiiiiiiiiiiidddiiiiiiiii,"Nc":invoke_iiiiiiiiiiiiiiiiii,"Ih":invoke_iiiiiiiiiiiiiiiiiiddddiiiiiiiiii,"Kh":invoke_iiiiiiiiiiiiiiiiiiddddiiiiiiiiiii,"cf":invoke_iiiiiiiiiiiiiiiiiiii,"uo":invoke_iiiiiiiiiiiiiiiiiiiiiiiii,"ui":invoke_iiiiiiiiiiiiiiiiiiiiiiiiiii,"Dm":invoke_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii,"Li":invoke_iiji,"T":invoke_v,"Ce":invoke_vddd,"kf":invoke_vddddiiiiiiiiiiii,"qb":invoke_vdddii,"Kd":invoke_vdddiii,"Di":invoke_vdddiiii,"yi":invoke_vdddiiiiiiiii,"Ta":invoke_vddi,"Qn":invoke_vddiddi,"Sb":invoke_vddiddiii,"hc":invoke_vddidiii,"Zb":invoke_vddii,"Tg":invoke_vddiii,"Kb":invoke_vddiiii,"On":invoke_vddiiiiii,"Sc":invoke_vddiiiiiii,"pn":invoke_vddiiiiiiii,"on":invoke_vddiiiiiiiiiiii,"qn":invoke_vddiiiiiiiiiiiiii,"Ji":invoke_vddiiiiiiiiiiiiiii,"vm":invoke_vddiiiiiiiiiiiiiiii,"jf":invoke_vddiiiiiiiiiiiiiiiii,"hf":invoke_vddiiiiiiiiiiiiiiiiiiii,"Ii":invoke_vddiiiiiiiiiiiiiiiiiiiiiiii,"ed":invoke_vdi,"Jb":invoke_vdiddddi,"Vc":invoke_vdiddii,"zc":invoke_vdiddiiii,"Ud":invoke_vdiddiiiiii,"jb":invoke_vdidii,"Fi":invoke_vdiidiiiiiiii,"td":invoke_vdiii,"Wm":invoke_vdiiidiiiiiiii,"Lf":invoke_vdiiii,"Ua":invoke_vdiiiii,"Pb":invoke_vdiiiiiiii,"Db":invoke_vdiiiiiiiii,"Md":invoke_vdiiiiiiiiii,"Ug":invoke_vdiiiiiiiiiii,"d":invoke_vi,"H":invoke_vid,"ma":invoke_vidd,"za":invoke_viddd,"Wa":invoke_vidddd,"Pn":invoke_viddddd,"ob":invoke_vidddddd,"de":invoke_viddddddd,"ld":invoke_vidddddddddddd,"id":invoke_vidddddddii,"og":invoke_viddddddiii,"bf":invoke_vidddddi,"Se":invoke_vidddddiii,"gd":invoke_viddddi,"$c":invoke_viddddii,"wg":invoke_viddddiid,"yf":invoke_viddddiii,"xe":invoke_viddddiiii,"ym":invoke_viddddiiiii,"eb":invoke_vidddi,"ze":invoke_vidddidddddd,"Vd":invoke_vidddii,"Jh":invoke_vidddiii,"ae":invoke_vidddiiidi,"ja":invoke_viddi,"Gc":invoke_viddid,"df":invoke_viddidd,"Kg":invoke_viddidddiiii,"Ab":invoke_viddii,"Pd":invoke_viddiiddi,"fi":invoke_viddiiddiii,"ei":invoke_viddiididiiiiiiiiiiiiiiiiiii,"Gn":invoke_viddiidiiidii,"Y":invoke_viddiii,"mf":invoke_viddiiii,"lb":invoke_viddiiiiii,"ff":invoke_viddiiiiiiiiii,"rf":invoke_vidfdfii,"I":invoke_vidi,"sa":invoke_vidid,"sb":invoke_vididd,"qh":invoke_vididdi,"Mb":invoke_vididi,"O":invoke_vidii,"vo":invoke_vidiiddddii,"lf":invoke_vidiidii,"ca":invoke_vidiii,"Kn":invoke_vidiiiddii,"ad":invoke_vidiiidi,"Ob":invoke_vidiiii,"hn":invoke_vidiiiii,"Vg":invoke_vidiiiiidd,"Wc":invoke_vidiiiiii,"so":invoke_vidiiiiiiiiiii,"Mc":invoke_vif,"hg":invoke_viff,"i":invoke_vii,"A":invoke_viid,"J":invoke_viidd,"ia":invoke_viiddd,"Va":invoke_viidddd,"Nb":invoke_viiddddd,"pa":invoke_viidddddd,"he":invoke_viidddddddd,"sc":invoke_viidddddddiiii,"dg":invoke_viidddddi,"ni":invoke_viidddddiii,"ec":invoke_viiddddi,"ve":invoke_viiddddidd,"$h":invoke_viiddddiddd,"wi":invoke_viiddddiii,"Sm":invoke_viiddddiiid,"$a":invoke_viidddi,"Je":invoke_viidddidi,"zi":invoke_viidddii,"kh":invoke_viidddiid,"We":invoke_viidddiii,"ga":invoke_viiddi,"eo":invoke_viiddid,"gc":invoke_viiddidd,"ch":invoke_viiddidi,"na":invoke_viiddii,"ao":invoke_viiddiidiiiiii,"Xd":invoke_viiddiii,"we":invoke_viiddiiii,"hi":invoke_viiddiiiii,"ce":invoke_viiddiiiiii,"Ic":invoke_viiddiiiiiiii,"ea":invoke_viidi,"qa":invoke_viidid,"Ae":invoke_viididd,"kn":invoke_viididdi,"Ge":invoke_viididi,"wa":invoke_viidii,"Mn":invoke_viidiid,"tb":invoke_viidiii,"$d":invoke_viidiiid,"ci":invoke_viidiiidddii,"Yd":invoke_viidiiii,"mc":invoke_viidiiiii,"ue":invoke_viidiiiiii,"n":invoke_viii,"L":invoke_viiid,"ba":invoke_viiidd,"Ib":invoke_viiiddd,"db":invoke_viiidddd,"Un":invoke_viiiddddd,"Vm":invoke_viiidddddd,"Tm":invoke_viiidddddddd,"Mf":invoke_viiiddddi,"Oh":invoke_viiiddddiddi,"_g":invoke_viiiddddii,"jg":invoke_viiidddi,"Em":invoke_viiidddiii,"Ra":invoke_viiiddi,"hh":invoke_viiiddid,"Lc":invoke_viiiddidi,"En":invoke_viiiddidiii,"oi":invoke_viiiddidiiiii,"ne":invoke_viiiddii,"ic":invoke_viiiddiiii,"Ed":invoke_viiiddiiiii,"Ao":invoke_viiiddiiiiiiiiiiiiii,"Da":invoke_viiidi,"th":invoke_viiidid,"zm":invoke_viiididi,"Ea":invoke_viiidii,"Rd":invoke_viiidiid,"Ld":invoke_viiidiii,"Zm":invoke_viiidiiii,"Km":invoke_viiidiiiiddiiiiii,"bb":invoke_viiidiiiii,"xh":invoke_viiidiiiiii,"bi":invoke_viiidiiiiiiiiii,"q":invoke_viiii,"V":invoke_viiiid,"Aa":invoke_viiiidd,"vd":invoke_viiiidddd,"ef":invoke_viiiiddddd,"Kf":invoke_viiiidddddd,"Eh":invoke_viiiidddi,"di":invoke_viiiidddii,"Ai":invoke_viiiidddiiiii,"Oc":invoke_viiiiddi,"Fg":invoke_viiiiddii,"_h":invoke_viiiiddiii,"rn":invoke_viiiiddiiid,"ua":invoke_viiiidi,"Am":invoke_viiiididi,"Ya":invoke_viiiidii,"Bc":invoke_viiiidiidi,"Gb":invoke_viiiidiii,"Wg":invoke_viiiidiiidi,"Ie":invoke_viiiidiiii,"Nh":invoke_viiiidiiiiiidiiiiiiiiiii,"z":invoke_viiiii,"fa":invoke_viiiiid,"Ia":invoke_viiiiidd,"Ec":invoke_viiiiiddd,"uc":invoke_viiiiidddd,"Ym":invoke_viiiiidddddddd,"De":invoke_viiiiiddddi,"Sh":invoke_viiiiiddddiddi,"Dd":invoke_viiiiidddii,"Dh":invoke_viiiiidddiii,"fd":invoke_viiiiiddi,"Jg":invoke_viiiiiddidd,"Zh":invoke_viiiiiddii,"Fe":invoke_viiiiiddiii,"vh":invoke_viiiiiddiiii,"Uh":invoke_viiiiiddiiiiii,"Cb":invoke_viiiiidi,"Ef":invoke_viiiiidii,"zd":invoke_viiiiidiii,"G":invoke_viiiiii,"mb":invoke_viiiiiid,"Ke":invoke_viiiiiidd,"Ff":invoke_viiiiiiddddidd,"Qh":invoke_viiiiiiddddiddi,"nh":invoke_viiiiiidddi,"ii":invoke_viiiiiidddiiiiii,"wd":invoke_viiiiiiddi,"wm":invoke_viiiiiiddiii,"um":invoke_viiiiiiddiiii,"Oe":invoke_viiiiiidi,"Zf":invoke_viiiiiididi,"ah":invoke_viiiiiidii,"tf":invoke_viiiiiidiidid,"yc":invoke_viiiiiidiii,"S":invoke_viiiiiii,"me":invoke_viiiiiiid,"cn":invoke_viiiiiiiddd,"oh":invoke_viiiiiiidddd,"Qm":invoke_viiiiiiiddii,"Rm":invoke_viiiiiiiddiiii,"vg":invoke_viiiiiiidiiiidiii,"ka":invoke_viiiiiiii,"fb":invoke_viiiiiiiid,"wh":invoke_viiiiiiiidd,"ag":invoke_viiiiiiiiddi,"Sd":invoke_viiiiiiiidi,"Gm":invoke_viiiiiiiidii,"ya":invoke_viiiiiiiii,"lg":invoke_viiiiiiiiid,"oe":invoke_viiiiiiiiidd,"Qe":invoke_viiiiiiiiiddi,"Qb":invoke_viiiiiiiiiddii,"Oa":invoke_viiiiiiiiii,"Rf":invoke_viiiiiiiiiid,"Om":invoke_viiiiiiiiiidddiii,"Pm":invoke_viiiiiiiiiidddiiiiii,"xg":invoke_viiiiiiiiiiddi,"oj":invoke_viiiiiiiiiidfdf,"cb":invoke_viiiiiiiiiii,"Mg":invoke_viiiiiiiiiiidd,"cd":invoke_viiiiiiiiiiidi,"yb":invoke_viiiiiiiiiiii,"Jc":invoke_viiiiiiiiiiiidi,"Hi":invoke_viiiiiiiiiiiidii,"oc":invoke_viiiiiiiiiiiii,"Pf":invoke_viiiiiiiiiiiiidi,"Zd":invoke_viiiiiiiiiiiiii,"Gf":invoke_viiiiiiiiiiiiiidddd,"Vh":invoke_viiiiiiiiiiiiiidddiiiiiiiii,"Wh":invoke_viiiiiiiiiiiiiiddiiiiiiiii,"Od":invoke_viiiiiiiiiiiiiii,"ro":invoke_viiiiiiiiiiiiiiii,"vi":invoke_viiiiiiiiiiiiiiiii,"Vn":invoke_viiiiiiiiiiiiiiiiidddiiiiiiiiiiii,"Bm":invoke_viiiiiiiiiiiiiiiiii,"Wf":invoke_viiiiiiiiiiiiiiiiiii,"Wn":invoke_viiiiiiiiiiiiiiiiiiidddiiiiiiiiii,"Xn":invoke_viiiiiiiiiiiiiiiiiiiddiiiiiiiiii,"Xg":invoke_viiiiiiiiiiiiiiiiiiiii,"yg":invoke_viiiiiiiiiiiiiiiiiiiiii,"$n":invoke_viiiiiiiiiiiiiiiiiiiiiii,"_n":invoke_viiiiiiiiiiiiiiiiiiiiiiiii,"ai":invoke_viiiiiiiiiiiiiiiiiiiiiiiiiii,"zk":invoke_viiiiiiiiiiiiiiiiiiiiiiiiiiiiddddddddiii,"ok":invoke_viiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii,"aa":_llvm_eh_typeid_for,"a":wasmMemory,"hm":_nanosleep,"ko":_pthread_create,"Ze":_pthread_detach,"qi":_pthread_join,"ho":_pthread_mutexattr_destroy,"jo":_pthread_mutexattr_init,"io":_pthread_mutexattr_settype,"k":_setTempRet0,"_e":_sigaction,"Cg":_sigaddset,"$e":_sigemptyset,"Bg":_sigprocmask,"Dg":_strftime,"cc":_strftime_l,"Dn":_sysconf,"Eg":_time,"af":_times};var asm=createWasm();var ___wasm_call_ctors=Module["___wasm_call_ctors"]=function(){return(___wasm_call_ctors=Module["___wasm_call_ctors"]=Module["asm"]["Bo"]).apply(null,arguments)};var _memset=Module["_memset"]=function(){return(_memset=Module["_memset"]=Module["asm"]["Co"]).apply(null,arguments)};var _malloc=Module["_malloc"]=function(){return(_malloc=Module["_malloc"]=Module["asm"]["Do"]).apply(null,arguments)};var _free=Module["_free"]=function(){return(_free=Module["_free"]=Module["asm"]["Eo"]).apply(null,arguments)};var ___errno_location=Module["___errno_location"]=function(){return(___errno_location=Module["___errno_location"]=Module["asm"]["Fo"]).apply(null,arguments)};var ___getTypeName=Module["___getTypeName"]=function(){return(___getTypeName=Module["___getTypeName"]=Module["asm"]["Go"]).apply(null,arguments)};var ___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=function(){return(___embind_register_native_and_builtin_types=Module["___embind_register_native_and_builtin_types"]=Module["asm"]["Ho"]).apply(null,arguments)};var _htons=Module["_htons"]=function(){return(_htons=Module["_htons"]=Module["asm"]["Io"]).apply(null,arguments)};var __get_tzname=Module["__get_tzname"]=function(){return(__get_tzname=Module["__get_tzname"]=Module["asm"]["Jo"]).apply(null,arguments)};var __get_daylight=Module["__get_daylight"]=function(){return(__get_daylight=Module["__get_daylight"]=Module["asm"]["Ko"]).apply(null,arguments)};var __get_timezone=Module["__get_timezone"]=function(){return(__get_timezone=Module["__get_timezone"]=Module["asm"]["Lo"]).apply(null,arguments)};var _setThrew=Module["_setThrew"]=function(){return(_setThrew=Module["_setThrew"]=Module["asm"]["Mo"]).apply(null,arguments)};var stackSave=Module["stackSave"]=function(){return(stackSave=Module["stackSave"]=Module["asm"]["No"]).apply(null,arguments)};var stackRestore=Module["stackRestore"]=function(){return(stackRestore=Module["stackRestore"]=Module["asm"]["Oo"]).apply(null,arguments)};var stackAlloc=Module["stackAlloc"]=function(){return(stackAlloc=Module["stackAlloc"]=Module["asm"]["Po"]).apply(null,arguments)};var __ZSt18uncaught_exceptionv=Module["__ZSt18uncaught_exceptionv"]=function(){return(__ZSt18uncaught_exceptionv=Module["__ZSt18uncaught_exceptionv"]=Module["asm"]["Qo"]).apply(null,arguments)};var _memalign=Module["_memalign"]=function(){return(_memalign=Module["_memalign"]=Module["asm"]["Ro"]).apply(null,arguments)};var dynCall_iiji=Module["dynCall_iiji"]=function(){return(dynCall_iiji=Module["dynCall_iiji"]=Module["asm"]["So"]).apply(null,arguments)};var dynCall_ji=Module["dynCall_ji"]=function(){return(dynCall_ji=Module["dynCall_ji"]=Module["asm"]["To"]).apply(null,arguments)};var dynCall_viji=Module["dynCall_viji"]=function(){return(dynCall_viji=Module["dynCall_viji"]=Module["asm"]["Uo"]).apply(null,arguments)};var dynCall_viiijj=Module["dynCall_viiijj"]=function(){return(dynCall_viiijj=Module["dynCall_viiijj"]=Module["asm"]["Vo"]).apply(null,arguments)};var dynCall_viijii=Module["dynCall_viijii"]=function(){return(dynCall_viijii=Module["dynCall_viijii"]=Module["asm"]["Wo"]).apply(null,arguments)};var dynCall_jiji=Module["dynCall_jiji"]=function(){return(dynCall_jiji=Module["dynCall_jiji"]=Module["asm"]["Xo"]).apply(null,arguments)};var dynCall_iiiiij=Module["dynCall_iiiiij"]=function(){return(dynCall_iiiiij=Module["dynCall_iiiiij"]=Module["asm"]["Yo"]).apply(null,arguments)};var dynCall_iiiiijj=Module["dynCall_iiiiijj"]=function(){return(dynCall_iiiiijj=Module["dynCall_iiiiijj"]=Module["asm"]["Zo"]).apply(null,arguments)};var dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=function(){return(dynCall_iiiiiijj=Module["dynCall_iiiiiijj"]=Module["asm"]["_o"]).apply(null,arguments)};function invoke_ii(index,a1){var sp=stackSave();try{return wasmTable.get(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viii(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiii(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vi(index,a1){var sp=stackSave();try{wasmTable.get(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiii(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vii(index,a1,a2){var sp=stackSave();try{wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iii(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_i(index){var sp=stackSave();try{return wasmTable.get(index)()}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiid(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiii(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidi(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_di(index,a1){var sp=stackSave();try{return wasmTable.get(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiid(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidii(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viid(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vid(index,a1,a2){var sp=stackSave();try{wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidi(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddi(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiid(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_dii(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_ddd(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_v(index){var sp=stackSave();try{wasmTable.get(index)()}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidfdfii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_dd(index,a1){var sp=stackSave();try{return wasmTable.get(index)(a1)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viididd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiiiiiiiiiddddddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddddddddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidd(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiidfdf(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_did(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidd(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidd(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddi(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diid(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiddd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddidddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidd(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddd(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiii(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidi(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidii(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidid(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidi(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddii(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddddiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiidiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddd(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiii(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddddddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdddiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdddiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iid(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddi(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_dddd(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diidd(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiddddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didd(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diddd(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diii(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diidi(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vididd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdddii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidid(index,a1,a2,a3,a4){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddidd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddiidd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiddddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vididi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiididi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didddidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiddi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didddddidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diddi(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddddddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iif(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_ddii(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_ddi(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didi(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiidiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didiidiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiidiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiidii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didiidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iididd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diidii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_dididd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diddidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiididi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddd(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idiiddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiddddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiidiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiididiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiiidddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idiid(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidddd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddiddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddidiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiiidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiiddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiidiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiidddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiidddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiidddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiidddiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_ddddd(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddddiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiddiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiddddiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddddiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidiiiiiidiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiddddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiididdii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiddddiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiddddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiddddiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidddiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiddddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiddddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiididi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddiididii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiddidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddiiddidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiddidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddiiddidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdi(index,a1,a2){var sp=stackSave();try{wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiidddiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idi(index,a1,a2){var sp=stackSave();try{return wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idii(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iididi(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iididdii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiidiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iididiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiidiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidddi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vif(index,a1,a2){var sp=stackSave();try{wasmTable.get(index)(a1,a2)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_d(index){var sp=stackSave();try{return wasmTable.get(index)()}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viff(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddddiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddidi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddddidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddddiddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddidi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiddiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiddii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddddiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didii(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idddii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idiiididii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiddidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idid(index,a1,a2,a3){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viididdi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viididi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiidiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddddddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddddddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddid(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidiidid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vididdi(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiidddddddddiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idiiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiddddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiidddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_idiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiidiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiddddiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiidddddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viidddiid(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddddiiid(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiidddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiidddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidddiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiddidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiididd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiidddddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiidddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiididi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiddi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddid(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiiiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidiiiiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiddddidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiid(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iidiiidd(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiid(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diidiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiidiiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiidii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiidiii(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddidi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiidiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiidd(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_diiiiiii(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiidii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddddd(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiidddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddd(index,a1,a2,a3){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21,a22,a23,a24,a25,a26,a27,a28,a29,a30,a31,a32,a33,a34,a35,a36,a37,a38,a39,a40,a41)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiddddii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_didiiii(index,a1,a2,a3,a4,a5,a6){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iddii(index,a1,a2,a3,a4){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiididi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiidiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19,a20,a21)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiididi(index,a1,a2,a3,a4,a5,a6,a7,a8){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiidiiidi(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiididi(index,a1,a2,a3,a4,a5,a6,a7){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiiiiiiidddddiiidddd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18,a19)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viddddiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddid(index,a1,a2,a3,a4,a5){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{return wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vidiiiiidd(index,a1,a2,a3,a4,a5,a6,a7,a8,a9){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiddiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vdiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiiiiiiiiiiiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12,a13,a14,a15,a16,a17,a18)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_vddiii(index,a1,a2,a3,a4,a5){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiddiiiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_viiiiiiddiiii(index,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12){var sp=stackSave();try{wasmTable.get(index)(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11,a12)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}function invoke_iiji(index,a1,a2,a3,a4){var sp=stackSave();try{return dynCall_iiji(index,a1,a2,a3,a4)}catch(e){stackRestore(sp);if(e!==e+0&&e!=="longjmp")throw e;_setThrew(1,0)}}Module["UTF8ToString"]=UTF8ToString;Module["FS"]=FS;var calledRun;function ExitStatus(status){this.name="ExitStatus";this.message="Program terminated with exit("+status+")";this.status=status}dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(args){args=args||arguments_;if(runDependencies>0){return}preRun();if(runDependencies>0)return;function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();preMain();readyPromiseResolve(Module);if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}Module["run"]=run;function exit(status,implicit){if(implicit&&noExitRuntime&&status===0){return}if(noExitRuntime){}else{EXITSTATUS=status;exitRuntime();if(Module["onExit"])Module["onExit"](status);ABORT=true}quit_(status,new ExitStatus(status))}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}noExitRuntime=true;run();
+
+
+ return opencascade.ready
+}
+);
+})();
+export default opencascade;
\ No newline at end of file
diff --git a/vendor/opencascade.js/dist/opencascade.wasm.wasm b/vendor/opencascade.js/dist/opencascade.wasm.wasm
new file mode 100644
index 00000000..0baa6b6c
Binary files /dev/null and b/vendor/opencascade.js/dist/opencascade.wasm.wasm differ