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 feature dialog](Chamfer_dialog.png) -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 feature dialog](Fillet_dialog.png) -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) + +![opencascade.js - Build Library](https://github.com/donalffons/opencascade.js/workflows/opencascade.js%20-%20Build%20Library/badge.svg) + +# 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. + +![](https://image-charts.com/chart?cht=p3&chs=700x250&chd=t:25.6,74.4&chl=Unsupported\n(28.2%)|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;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