brepjs 18.112.0 → 18.114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/shapeRef.cjs CHANGED
@@ -1,8 +1,22 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_shapeRefFns = require("./shapeRefFns-CuepnFOm.cjs");
3
- exports.assignRoles = require_shapeRefFns.assignRoles;
4
- exports.captureHint = require_shapeRefFns.captureHint;
5
- exports.createRef = require_shapeRefFns.createRef;
6
- exports.defaultScorer = require_shapeRefFns.defaultScorer;
7
- exports.resolveRef = require_shapeRefFns.resolveRef;
8
- exports.updateRoles = require_shapeRefFns.updateRoles;
2
+ const require_refResolveFns = require("./refResolveFns-BK5NSDFd.cjs");
3
+ exports.assignRoles = require_refResolveFns.assignRoles;
4
+ exports.captureHint = require_refResolveFns.captureHint;
5
+ exports.createDerivedFaceRef = require_refResolveFns.createDerivedFaceRef;
6
+ exports.createEdgeRef = require_refResolveFns.createEdgeRef;
7
+ exports.createRef = require_refResolveFns.createRef;
8
+ exports.createVertexRef = require_refResolveFns.createVertexRef;
9
+ exports.defaultScorer = require_refResolveFns.defaultScorer;
10
+ exports.isDerivedFaceRef = require_refResolveFns.isDerivedFaceRef;
11
+ exports.isEdgeRef = require_refResolveFns.isEdgeRef;
12
+ exports.isFaceRef = require_refResolveFns.isFaceRef;
13
+ exports.isLineageRef = require_refResolveFns.isLineageRef;
14
+ exports.isVertexRef = require_refResolveFns.isVertexRef;
15
+ exports.resolveDerivedFaceRef = require_refResolveFns.resolveDerivedFaceRef;
16
+ exports.resolveEdgeRef = require_refResolveFns.resolveEdgeRef;
17
+ exports.resolveLineageRef = require_refResolveFns.resolveLineageRef;
18
+ exports.resolveRef = require_refResolveFns.resolveRef;
19
+ exports.resolveRefIn = require_refResolveFns.resolveRefIn;
20
+ exports.resolveRefParams = require_refResolveFns.resolveRefParams;
21
+ exports.resolveVertexRef = require_refResolveFns.resolveVertexRef;
22
+ exports.updateRoles = require_refResolveFns.updateRoles;
@@ -1,4 +1,12 @@
1
1
  /**
2
- * brepjs/shapeRef — Stable face references for parametric replay.
2
+ * brepjs/shapeRef — stable, lineage-based references for parametric replay.
3
+ *
4
+ * Name a topological entity by its stable adjacent-neighbor roles (not its
5
+ * kernel hash) so it survives edits that re-hash the model:
6
+ * - faces — role + geometric hint (`createRef`/`resolveRef`)
7
+ * - edges — the 2 adjacent face-roles (`createEdgeRef`/`resolveEdgeRef`)
8
+ * - vertices — the ≥3 adjacent face-roles (`createVertexRef`/`resolveVertexRef`)
9
+ * - generated faces (fillet/chamfer) — the 2 bridged face-roles
10
+ * (`createDerivedFaceRef`/`resolveDerivedFaceRef`)
3
11
  */
4
- export { type GeometricHint, type ShapeRef, type RoleTable, type ResolvedRef, type BrokenRef, type FaceScorer, defaultScorer, captureHint, assignRoles, createRef, updateRoles, resolveRef, } from './topology/shapeRef/index.js';
12
+ export { type GeometricHint, type ShapeRef, type RoleTable, type ResolvedRef, type BrokenRef, type FaceScorer, type EdgeHint, type EdgeRef, type ResolvedEdgeRef, type BrokenEdgeRef, type VertexHint, type VertexRef, type ResolvedVertexRef, type BrokenVertexRef, type DerivedFaceHint, type DerivedFaceRef, type ResolvedDerivedFaceRef, type BrokenDerivedFaceRef, defaultScorer, captureHint, assignRoles, createRef, updateRoles, resolveRef, createEdgeRef, resolveEdgeRef, createVertexRef, resolveVertexRef, createDerivedFaceRef, resolveDerivedFaceRef, type LineageRef, type ResolvedEntity, type BrokenReason, type LineageResolution, isLineageRef, isFaceRef, isEdgeRef, isVertexRef, isDerivedFaceRef, resolveLineageRef, resolveRefIn, resolveRefParams, } from './topology/shapeRef/index.js';
package/dist/shapeRef.js CHANGED
@@ -1,2 +1,2 @@
1
- import { a as updateRoles, i as resolveRef, n as captureHint, o as defaultScorer, r as createRef, t as assignRoles } from "./shapeRefFns-ChMnsOAF.js";
2
- export { assignRoles, captureHint, createRef, defaultScorer, resolveRef, updateRoles };
1
+ import { _ as createRef, a as isVertexRef, b as defaultScorer, c as resolveRefParams, d as createVertexRef, f as resolveVertexRef, g as captureHint, h as assignRoles, i as isLineageRef, l as createDerivedFaceRef, m as resolveEdgeRef, n as isEdgeRef, o as resolveLineageRef, p as createEdgeRef, r as isFaceRef, s as resolveRefIn, t as isDerivedFaceRef, u as resolveDerivedFaceRef, v as resolveRef, y as updateRoles } from "./refResolveFns-CayDQeug.js";
2
+ export { assignRoles, captureHint, createDerivedFaceRef, createEdgeRef, createRef, createVertexRef, defaultScorer, isDerivedFaceRef, isEdgeRef, isFaceRef, isLineageRef, isVertexRef, resolveDerivedFaceRef, resolveEdgeRef, resolveLineageRef, resolveRef, resolveRefIn, resolveRefParams, resolveVertexRef, updateRoles };
@@ -0,0 +1,16 @@
1
+ import { Edge, Shape3D } from '../../core/shapeTypes.js';
2
+ import { DerivedFaceRef, ResolvedDerivedFaceRef, BrokenDerivedFaceRef, RoleTable } from './shapeRefTypes.js';
3
+ /**
4
+ * Capture a reference to the face an `op` (fillet/chamfer) will generate across
5
+ * `edge`: the roles of the edge's two faces, their outward normals, and the edge
6
+ * midpoint. Call this on the PRE-op shape. Returns undefined when the edge
7
+ * doesn't bound two named faces.
8
+ */
9
+ export declare function createDerivedFaceRef(origin: string, op: 'fillet' | 'chamfer', edge: Edge, preShape: Shape3D, roles: RoleTable): DerivedFaceRef | undefined;
10
+ /**
11
+ * Resolve a DerivedFaceRef in the post-op `shape`: re-derive the two bridged
12
+ * faces (role table, else by captured normal), then return the face adjacent to
13
+ * both whose normal blends both. One survivor → resolved; several →
14
+ * nearest-to-edge-midpoint; none → broken.
15
+ */
16
+ export declare function resolveDerivedFaceRef(ref: DerivedFaceRef, roles: RoleTable, shape: Shape3D): ResolvedDerivedFaceRef | BrokenDerivedFaceRef;
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * ShapeRef — stable, serializable face references for parametric replay.
3
3
  */
4
- export type { GeometricHint, ShapeRef, RoleTable, ResolvedRef, BrokenRef, EdgeHint, EdgeRef, ResolvedEdgeRef, BrokenEdgeRef, VertexHint, VertexRef, ResolvedVertexRef, BrokenVertexRef, } from './shapeRefTypes.js';
4
+ export type { GeometricHint, ShapeRef, RoleTable, ResolvedRef, BrokenRef, EdgeHint, EdgeRef, ResolvedEdgeRef, BrokenEdgeRef, VertexHint, VertexRef, ResolvedVertexRef, BrokenVertexRef, DerivedFaceHint, DerivedFaceRef, ResolvedDerivedFaceRef, BrokenDerivedFaceRef, } from './shapeRefTypes.js';
5
5
  export { type FaceScorer, defaultScorer } from './scoring.js';
6
6
  export { captureHint, assignRoles, createRef, updateRoles, resolveRef } from './shapeRefFns.js';
7
7
  export { createEdgeRef, resolveEdgeRef } from './edgeRefFns.js';
8
8
  export { createVertexRef, resolveVertexRef } from './vertexRefFns.js';
9
+ export { createDerivedFaceRef, resolveDerivedFaceRef } from './derivedFaceRefFns.js';
10
+ export { type LineageRef, type ResolvedEntity, type BrokenReason, type LineageResolution, isLineageRef, isFaceRef, isEdgeRef, isVertexRef, isDerivedFaceRef, resolveLineageRef, resolveRefIn, resolveRefParams, } from './refResolveFns.js';
@@ -0,0 +1,53 @@
1
+ import { Edge, Face, Shape3D, Vertex } from '../../core/shapeTypes.js';
2
+ import { ShapeRef, EdgeRef, VertexRef, DerivedFaceRef, RoleTable } from './shapeRefTypes.js';
3
+ /** Any of the four lineage reference kinds. */
4
+ export type LineageRef = ShapeRef | EdgeRef | VertexRef | DerivedFaceRef;
5
+ /** The live entity a lineage ref resolves to. */
6
+ export type ResolvedEntity = Face | Edge | Vertex;
7
+ /** Why a lineage ref failed to resolve (preserved from the underlying resolver). */
8
+ export type BrokenReason = 'ambiguous' | 'not-found' | 'deleted';
9
+ /**
10
+ * Outcome of resolving a lineage ref. Carries the failure `reason` (and any tied
11
+ * `candidates`) rather than collapsing to undefined, so a replay engine can tell
12
+ * "deleted by the edit" (expected — skip) from "ambiguous" (warn the author).
13
+ */
14
+ export type LineageResolution = {
15
+ readonly ok: true;
16
+ readonly entity: ResolvedEntity;
17
+ } | {
18
+ readonly ok: false;
19
+ readonly reason: BrokenReason;
20
+ readonly candidates?: readonly ResolvedEntity[];
21
+ };
22
+ /** A generated-face ref: carries the bridged roles + the op that made it. */
23
+ export declare function isDerivedFaceRef(v: unknown): v is DerivedFaceRef;
24
+ /** An edge ref: exactly two adjacent face-roles. */
25
+ export declare function isEdgeRef(v: unknown): v is EdgeRef;
26
+ /** A vertex ref: three or more adjacent face-roles. */
27
+ export declare function isVertexRef(v: unknown): v is VertexRef;
28
+ /** A face ref: a single role name. */
29
+ export declare function isFaceRef(v: unknown): v is ShapeRef;
30
+ /** True for any of the four lineage reference kinds. */
31
+ export declare function isLineageRef(v: unknown): v is LineageRef;
32
+ /**
33
+ * Resolve a lineage ref against `shape` using a prepared role table (the robust
34
+ * path — the table is maintained across edits via `updateRoles`). Returns the
35
+ * live entity on success, or the failure reason (and tied candidates) on failure.
36
+ */
37
+ export declare function resolveLineageRef(ref: LineageRef, roles: RoleTable, shape: Shape3D): LineageResolution;
38
+ /**
39
+ * Resolve a lineage ref against a freshly rebuilt `shape` with no maintained
40
+ * role table, re-deriving roles via `assignRoles(shape, ref.origin)`. The ref's
41
+ * `origin` must therefore be the role-assignment scheme (e.g. `'box'`), and
42
+ * stability is bounded by that scheme — `'box'` names faces semantically
43
+ * (rebuild-stable); other schemes fall back to positional `face_N`.
44
+ */
45
+ export declare function resolveRefIn(ref: LineageRef, shape: Shape3D): LineageResolution;
46
+ /**
47
+ * Replace every lineage ref in an operation's params with the live entity it
48
+ * resolves to in `shape`, recursing into arrays (multi-entity selections like a
49
+ * fillet's edge list). Refs that can't resolve are left as-is. Role tables are
50
+ * re-derived once per `origin` and reused across the whole params map. Lets a
51
+ * replay engine pass stable entity selections that survive upstream edits.
52
+ */
53
+ export declare function resolveRefParams(params: Readonly<Record<string, unknown>>, shape: Shape3D): Record<string, unknown>;
@@ -1,8 +1,10 @@
1
- import { Face, Shape3D } from '../../core/shapeTypes.js';
1
+ import { Face, Shape3D, Vertex } from '../../core/shapeTypes.js';
2
2
  import { Vec3 } from '../../core/types.js';
3
3
  import { RoleTable } from './shapeRefTypes.js';
4
4
  /** Euclidean distance between two points. */
5
5
  export declare function distance(a: Vec3, b: Vec3): number;
6
+ /** Mean of a set of vertices' positions (e.g. an edge's endpoints). */
7
+ export declare function vertexCentroid(verts: readonly Vertex[]): Vec3 | undefined;
6
8
  /** The role whose tracked hashes include this face (reverse lookup). */
7
9
  export declare function roleOfFace(face: Face, origin: string, roles: RoleTable): string | undefined;
8
10
  /** Current faces a role resolves to — its tracked successors present in `shape`. */
@@ -104,3 +104,45 @@ export interface BrokenVertexRef {
104
104
  readonly reason: 'ambiguous' | 'not-found';
105
105
  readonly candidates?: readonly Vertex[];
106
106
  }
107
+ /**
108
+ * Snapshot for re-deriving a generated face. fillet/chamfer evolution is empty
109
+ * on the OCCT kernels, so the role table can't track the bridged faces — they
110
+ * are re-found by their captured outward normals; the edge midpoint breaks ties.
111
+ */
112
+ export interface DerivedFaceHint {
113
+ readonly entityType: 'derived-face';
114
+ readonly normalA: Vec3;
115
+ readonly normalB: Vec3;
116
+ readonly edgeMidpoint?: Vec3 | undefined;
117
+ }
118
+ /**
119
+ * A reference to a *generated* face — a fillet round or chamfer bevel — named by
120
+ * the two faces it bridges. The generated face has no stable hash (it didn't
121
+ * exist at capture time, and fillet evolution is empty), so it's resolved as the
122
+ * face adjacent to both bridged faces whose normal blends both — the orthogonal
123
+ * flanking faces are rejected. Lineage-by-neighbors for geometry created by the
124
+ * very edit being referenced across.
125
+ */
126
+ export interface DerivedFaceRef {
127
+ readonly origin: string;
128
+ /**
129
+ * Which op generated the face — descriptive metadata for consumers. Resolution
130
+ * is op-agnostic (the normal-blend isolates both a fillet round and a chamfer
131
+ * bevel), so the resolver doesn't consult it.
132
+ */
133
+ readonly op: 'fillet' | 'chamfer';
134
+ /** Roles of the two faces the generated face bridges. */
135
+ readonly betweenRoles: readonly [string, string];
136
+ readonly hint: DerivedFaceHint;
137
+ }
138
+ /** A successfully resolved derived-face reference (always geometric). */
139
+ export interface ResolvedDerivedFaceRef {
140
+ readonly face: Face;
141
+ readonly confidence: 'geometric-fallback';
142
+ }
143
+ /** A derived-face reference that could not be resolved. */
144
+ export interface BrokenDerivedFaceRef {
145
+ readonly ref: DerivedFaceRef;
146
+ readonly reason: 'ambiguous' | 'not-found';
147
+ readonly candidates?: readonly Face[];
148
+ }
package/dist/topology.cjs CHANGED
@@ -5,10 +5,11 @@ const require_shapeFns = require("./shapeFns-ybo0INGL.cjs");
5
5
  const require_curveFns = require("./curveFns-DgUJpH4z.cjs");
6
6
  const require_meshFns = require("./meshFns-1hQSn1p2.cjs");
7
7
  const require_solidBuilders = require("./solidBuilders-DiUv_mD0.cjs");
8
- const require_healingFns = require("./healingFns-x1sHV-HO.cjs");
8
+ const require_healingFns = require("./healingFns-xLJ6Cp_O.cjs");
9
+ const require_adjacencyFns = require("./adjacencyFns-d8ZabkvX.cjs");
9
10
  const require_primitiveFns = require("./primitiveFns-DLIWtQWA.cjs");
10
11
  exports.addHoles = require_primitiveFns.addHoles;
11
- exports.adjacentFaces = require_healingFns.adjacentFaces;
12
+ exports.adjacentFaces = require_adjacencyFns.adjacentFaces;
12
13
  exports.approximateCurve = require_curveFns.approximateCurve;
13
14
  exports.asTopo = require_faceFns.asTopo;
14
15
  exports.autoHeal = require_healingFns.autoHeal;
@@ -40,7 +41,7 @@ exports.cutWithEvolution = require_healingFns.cutWithEvolution;
40
41
  exports.cylinder = require_primitiveFns.cylinder;
41
42
  exports.deserializeShape = require_faceFns.fromBREP;
42
43
  exports.downcast = require_faceFns.downcast;
43
- exports.edgesOfFace = require_healingFns.edgesOfFace;
44
+ exports.edgesOfFace = require_adjacencyFns.edgesOfFace;
44
45
  exports.ellipse = require_primitiveFns.ellipse;
45
46
  exports.ellipseArc = require_primitiveFns.ellipseArc;
46
47
  exports.ellipsoid = require_primitiveFns.ellipsoid;
@@ -52,7 +53,7 @@ exports.faceAxis = require_faceFns.faceAxis;
52
53
  exports.faceCenter = require_faceFns.faceCenter;
53
54
  exports.faceGeomType = require_faceFns.faceGeomType;
54
55
  exports.faceOrientation = require_faceFns.faceOrientation;
55
- exports.facesOfEdge = require_healingFns.facesOfEdge;
56
+ exports.facesOfEdge = require_adjacencyFns.facesOfEdge;
56
57
  exports.filledFace = require_primitiveFns.filledFace;
57
58
  exports.filletWithEvolution = require_healingFns.filletWithEvolution;
58
59
  exports.fixSelfIntersection = require_healingFns.fixSelfIntersection;
@@ -100,7 +101,7 @@ exports.positionOnCurve = require_healingFns.positionOnCurve;
100
101
  exports.projectPointOnFace = require_faceFns.projectPointOnFace;
101
102
  exports.sewShells = require_primitiveFns.sewShells;
102
103
  exports.shapeType = require_faceFns.shapeType;
103
- exports.sharedEdges = require_healingFns.sharedEdges;
104
+ exports.sharedEdges = require_adjacencyFns.sharedEdges;
104
105
  exports.shellWithEvolution = require_healingFns.shellWithEvolution;
105
106
  exports.solid = require_primitiveFns.solid;
106
107
  exports.solidFromShell = require_healingFns.solidFromShell;
@@ -117,7 +118,7 @@ exports.uvCoordinates = require_faceFns.uvCoordinates;
117
118
  exports.variableFillet = require_healingFns.variableFillet;
118
119
  exports.vertex = require_primitiveFns.vertex;
119
120
  exports.vertexPosition = require_topologyQueryFns.vertexPosition;
120
- exports.verticesOfEdge = require_healingFns.verticesOfEdge;
121
+ exports.verticesOfEdge = require_adjacencyFns.verticesOfEdge;
121
122
  exports.wire = require_primitiveFns.wire;
122
123
  exports.wireLoop = require_primitiveFns.wireLoop;
123
- exports.wiresOfFace = require_healingFns.wiresOfFace;
124
+ exports.wiresOfFace = require_adjacencyFns.wiresOfFace;
package/dist/topology.js CHANGED
@@ -4,6 +4,7 @@ import { a as isSameShape, i as isEqualShape, n as getHashCode } from "./shapeFn
4
4
  import { a as curveIsPeriodic, c as curvePointAt, d as flipOrientation, f as getCurveType, h as offsetWire2D, i as curveIsClosed, l as curveStartPoint, m as interpolateCurve, n as curveAxis, o as curveLength, p as getOrientation, r as curveEndPoint, s as curvePeriod, t as approximateCurve, u as curveTangentAt } from "./curveFns-CcSn4-Rf.js";
5
5
  import { d as createMeshCache, n as exportSTEP, r as exportSTL, t as exportIGES, u as clearMeshCache } from "./meshFns-DbmidUzH.js";
6
6
  import { h as fuseAll, p as cutAll } from "./solidBuilders-C32wXQWG.js";
7
- import { A as edgesOfFace, C as shellWithEvolution, D as getNurbsCurveData, E as fuseAllBisect, H as toLineGeometryData, I as wiresOfFace, L as chamferDistAngle, N as sharedEdges, O as getNurbsSurfaceData, P as verticesOfEdge, R as toBufferGeometryData, S as intersectWithEvolution, T as cutAllBisect, _ as positionOnCurve, a as healFace, b as filletWithEvolution, g as variableFillet, j as facesOfEdge, k as adjacentFaces, l as solidFromShell, n as fixSelfIntersection, o as healSolid, r as fixShape, s as healWire, t as autoHeal, v as chamferWithEvolution, w as checkBoolean, x as fuseWithEvolution, y as cutWithEvolution, z as toGroupedBufferGeometryData } from "./healingFns-D00TPE9p.js";
7
+ import { A as toBufferGeometryData, C as shellWithEvolution, D as getNurbsCurveData, E as fuseAllBisect, O as getNurbsSurfaceData, P as toLineGeometryData, S as intersectWithEvolution, T as cutAllBisect, _ as positionOnCurve, a as healFace, b as filletWithEvolution, g as variableFillet, j as toGroupedBufferGeometryData, k as chamferDistAngle, l as solidFromShell, n as fixSelfIntersection, o as healSolid, r as fixShape, s as healWire, t as autoHeal, v as chamferWithEvolution, w as checkBoolean, x as fuseWithEvolution, y as cutWithEvolution } from "./healingFns-2TJHHDg5.js";
8
+ import { a as sharedEdges, c as wiresOfFace, n as edgesOfFace, o as verticesOfEdge, r as facesOfEdge, t as adjacentFaces } from "./adjacencyFns-BYnyXXqt.js";
8
9
  import { C as threePointArc, D as wireLoop, E as wire, S as tangentArc, T as vertex, _ as polygon, a as circle, b as sphere, c as cylinder, d as ellipsoid, f as face, g as offsetFace, h as line, i as bsplineApprox, l as ellipse, m as helix, n as bezier, o as compound, p as filledFace, r as box, s as cone, t as addHoles, u as ellipseArc, v as sewShells, w as torus, x as subFace, y as solid } from "./primitiveFns-CBrOyk5S.js";
9
10
  export { addHoles, adjacentFaces, approximateCurve, asTopo, autoHeal, bezier, box, bsplineApprox, cast, chamferDistAngle as chamferDistAngleShape, chamferWithEvolution, checkBoolean, circle, classifyPointOnFace, clearMeshCache, compound, cone, createMeshCache, curveAxis, curveEndPoint, curveIsClosed, curveIsPeriodic, curveLength, curvePeriod, curvePointAt, curveStartPoint, curveTangentAt, cutAll, cutAllBisect, cutWithEvolution, cylinder, fromBREP as deserializeShape, downcast, edgesOfFace, ellipse, ellipseArc, ellipsoid, exportIGES, exportSTEP, exportSTL, face, faceAxis, faceCenter, faceGeomType, faceOrientation, facesOfEdge, filledFace, filletWithEvolution, fixSelfIntersection, fixShape, flipFaceOrientation, flipOrientation, fuseAll, fuseAllBisect, fuseWithEvolution, getBounds, getCurveType, getEdges, getFaces, getHashCode, getNurbsCurveData, getNurbsSurfaceData, getOrientation, getSurfaceType, getVertices, getWires, healFace, healSolid, healWire, helix, innerWires, interpolateCurve, intersectWithEvolution, invalidateShapeCache, isCompSolid, isEqualShape, isSameShape, iterEdges, iterFaces, iterTopo, iterVertices, iterWires, line, normalAt, offsetFace, offsetWire2D, outerWire, pointOnSurface, polygon, positionOnCurve, projectPointOnFace, sewShells, shapeType, sharedEdges, shellWithEvolution, solid, solidFromShell, sphere, subFace, tangentArc, threePointArc, toBufferGeometryData, toGroupedBufferGeometryData, toLineGeometryData, torus, uvBounds, uvCoordinates, variableFillet, vertex, vertexPosition, verticesOfEdge, wire, wireLoop, wiresOfFace };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brepjs",
3
- "version": "18.112.0",
3
+ "version": "18.114.0",
4
4
  "description": "Web CAD library with pluggable geometry kernel",
5
5
  "keywords": [
6
6
  "cad",
@@ -1,248 +0,0 @@
1
- import { c as getFaces } from "./topologyQueryFns-BeKaWpNm.js";
2
- import { i as faceGeomType, l as normalAt, r as faceCenter } from "./faceFns-B86WGqDg.js";
3
- import { n as getHashCode } from "./shapeFns-DrZOVwHX.js";
4
- import { n as measureArea } from "./measureFns-a5Vx25or.js";
5
- //#region src/topology/shapeRef/scoring.ts
6
- /**
7
- * Default face scorer combining surface type, normal alignment, centroid proximity,
8
- * and area similarity.
9
- *
10
- * Scoring breakdown:
11
- * - Surface type match: +1.0 (mismatch when both defined: -Infinity)
12
- * - Normal dot product: weighted contribution (rejected if < 0.707)
13
- * - Centroid distance: quadratic penalty (rejected if distSq > 100)
14
- * - Area ratio: penalized if |log(hintArea / faceArea)| > 1.0
15
- */
16
- function defaultScorer(hint, face) {
17
- let score = 0;
18
- const faceType = faceGeomType(face);
19
- if (hint.surfaceType !== void 0) if (faceType === hint.surfaceType) score += 1;
20
- else return -Infinity;
21
- if (hint.normal !== void 0) {
22
- const faceNormal = normalAt(face);
23
- const dot = hint.normal[0] * faceNormal[0] + hint.normal[1] * faceNormal[1] + hint.normal[2] * faceNormal[2];
24
- if (dot < .707) return -Infinity;
25
- score += dot;
26
- }
27
- if (hint.centroid !== void 0) {
28
- const faceCentroid = faceCenter(face);
29
- const dx = hint.centroid[0] - faceCentroid[0];
30
- const dy = hint.centroid[1] - faceCentroid[1];
31
- const dz = hint.centroid[2] - faceCentroid[2];
32
- const distSq = dx * dx + dy * dy + dz * dz;
33
- if (distSq > 100) return -Infinity;
34
- score -= distSq / 100;
35
- }
36
- if (hint.area !== void 0 && hint.area > 0) {
37
- const areaResult = measureArea(face);
38
- if (areaResult.ok && areaResult.value > 0) {
39
- const logRatio = Math.abs(Math.log(hint.area / areaResult.value));
40
- if (logRatio > 1) score -= logRatio;
41
- }
42
- }
43
- return score;
44
- }
45
- //#endregion
46
- //#region src/topology/shapeRef/shapeRefFns.ts
47
- /** Snapshot the geometric properties of a face for later matching. */
48
- function captureHint(face) {
49
- const surfaceType = faceGeomType(face);
50
- const normal = normalAt(face);
51
- const centroid = faceCenter(face);
52
- const areaResult = measureArea(face);
53
- return {
54
- entityType: "face",
55
- surfaceType,
56
- normal,
57
- centroid,
58
- area: areaResult.ok ? areaResult.value : void 0
59
- };
60
- }
61
- /** Threshold for dominant-axis detection (abs(component) > 0.9). */
62
- var AXIS_THRESHOLD = .9;
63
- /** Determine the cardinal role name for a box face from its outward normal. */
64
- function boxRoleFromNormal(n) {
65
- if (n[2] > AXIS_THRESHOLD) return "box:top";
66
- if (n[2] < -.9) return "box:bottom";
67
- if (n[1] > AXIS_THRESHOLD) return "box:back";
68
- if (n[1] < -.9) return "box:front";
69
- if (n[0] > AXIS_THRESHOLD) return "box:right";
70
- if (n[0] < -.9) return "box:left";
71
- }
72
- /**
73
- * Auto-assign role names to the faces of a shape based on operation type.
74
- *
75
- * For 'box': uses face normals to assign cardinal names
76
- * ('box:top', 'box:bottom', 'box:front', 'box:back', 'box:left', 'box:right').
77
- * **Note:** Box role detection assumes axis-aligned faces (normal within 0.9 of
78
- * a cardinal axis). Rotated boxes may receive fewer than 6 named roles; remaining
79
- * faces fall through to sequential naming.
80
- *
81
- * For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
82
- *
83
- * @returns Map from role name to its face hash codes (one at assignment time;
84
- * a role accrues more hashes only later, when `updateRoles` tracks a split).
85
- */
86
- function assignRoles(shape, operationType) {
87
- const faces = getFaces(shape);
88
- const roles = /* @__PURE__ */ new Map();
89
- if (operationType === "box") {
90
- for (const face of faces) {
91
- const role = boxRoleFromNormal(normalAt(face));
92
- if (role !== void 0 && !roles.has(role)) roles.set(role, [getHashCode(face)]);
93
- }
94
- return roles;
95
- }
96
- let index = 0;
97
- for (const face of faces) {
98
- roles.set(`${operationType}:face_${index}`, [getHashCode(face)]);
99
- index++;
100
- }
101
- return roles;
102
- }
103
- /** Create a ShapeRef from an origin ID, role name, and face. */
104
- function createRef(origin, role, face) {
105
- return {
106
- origin,
107
- role,
108
- hint: captureHint(face)
109
- };
110
- }
111
- /**
112
- * Advance a role's face hashes through one evolution: drop deleted faces,
113
- * replace a modified face with *all* its successors (a 1→many split keeps every
114
- * fragment), and keep unchanged faces. Deduped so a shared successor isn't
115
- * doubled.
116
- */
117
- function nextHashes(hashes, evolution) {
118
- const successors = [];
119
- for (const hash of hashes) {
120
- if (evolution.deleted.has(hash)) continue;
121
- const modified = evolution.modified.get(hash);
122
- const targets = modified && modified.length > 0 ? modified : [hash];
123
- for (const h of targets) if (!successors.includes(h)) successors.push(h);
124
- }
125
- return successors;
126
- }
127
- /**
128
- * Propagate a role table through a ShapeEvolution record.
129
- * Returns a new RoleTable with hashes updated according to the evolution.
130
- *
131
- * - Deleted faces: hash dropped (role removed once all its hashes are gone).
132
- * - Modified faces: hash replaced by **all** successor hashes — so a 1→many
133
- * split keeps every fragment, and `resolveRef` disambiguates among them.
134
- * - Unchanged faces: hash preserved.
135
- *
136
- * Note: `evolution.generated` is intentionally not consumed here — on the OCCT
137
- * kernels its hashes refer to an intermediate shape, not the final result, so
138
- * naming generated faces produces roles that never resolve (verified: 0 live
139
- * generated hashes across cut/fuse on occt-wasm). Stable names for generated
140
- * geometry (fillet rounds, boolean seams) need history-fidelity work tracked
141
- * separately.
142
- */
143
- function updateRoles(roles, origin, evolution) {
144
- const originRoles = roles.get(origin);
145
- if (!originRoles) return roles;
146
- const updatedOriginRoles = /* @__PURE__ */ new Map();
147
- for (const [role, hashes] of originRoles) {
148
- const successors = nextHashes(hashes, evolution);
149
- if (successors.length > 0) updatedOriginRoles.set(role, successors);
150
- }
151
- const newRoles = /* @__PURE__ */ new Map();
152
- for (const [key, value] of roles) newRoles.set(key, key === origin ? updatedOriginRoles : value);
153
- return newRoles;
154
- }
155
- /** Ambiguity threshold: if two scores are within this range, it's ambiguous. */
156
- var AMBIGUITY_THRESHOLD = .1;
157
- /** Minimum score for geometric fallback to accept a match. */
158
- var MIN_SCORE = .5;
159
- /**
160
- * Score `candidates` against `hint`, returning the best match, a tie within
161
- * {@link AMBIGUITY_THRESHOLD}, or nothing above {@link MIN_SCORE}. Scoping the
162
- * candidates to a role's tracked successors (rather than every face) is what
163
- * makes split-face disambiguation reliable — the fragments compete only with
164
- * each other, not with unrelated geometry.
165
- */
166
- function scoreFaces(hint, candidates, scoreFn) {
167
- let bestScore = -Infinity;
168
- let bestFace;
169
- let secondBestScore = -Infinity;
170
- const scored = [];
171
- for (const face of candidates) {
172
- const score = scoreFn(hint, face);
173
- if (score > MIN_SCORE) scored.push([face, score]);
174
- if (score > bestScore) {
175
- secondBestScore = bestScore;
176
- bestScore = score;
177
- bestFace = face;
178
- } else if (score > secondBestScore) secondBestScore = score;
179
- }
180
- if (bestFace !== void 0 && bestScore > MIN_SCORE) {
181
- if (bestScore - secondBestScore < AMBIGUITY_THRESHOLD && scored.length > 1) return {
182
- kind: "ambiguous",
183
- candidates: scored.filter(([, s]) => s >= bestScore - AMBIGUITY_THRESHOLD).map(([f]) => f)
184
- };
185
- return {
186
- kind: "match",
187
- face: bestFace
188
- };
189
- }
190
- return { kind: "none" };
191
- }
192
- /**
193
- * Resolve a ShapeRef to a face in the current shape.
194
- *
195
- * Resolution strategy:
196
- * 1. Exact: the role's tracked successor hashes. One survivor → exact match;
197
- * several survivors (a face that split) → disambiguate among *only those*
198
- * fragments; none survive → deleted.
199
- * 2. Geometric fallback over the whole shape when the role isn't tracked (or a
200
- * scoped score turned up nothing): best-scoring face, else ambiguous /
201
- * not-found.
202
- */
203
- function resolveRef(ref, roles, currentShape, scorer) {
204
- const faces = getFaces(currentShape);
205
- const scoreFn = scorer ?? defaultScorer;
206
- const targetHashes = roles.get(ref.origin)?.get(ref.role);
207
- if (targetHashes !== void 0 && targetHashes.length > 0) {
208
- const survivors = faces.filter((f) => targetHashes.includes(getHashCode(f)));
209
- if (survivors.length === 1) {
210
- const [only] = survivors;
211
- if (only !== void 0) return {
212
- face: only,
213
- confidence: "exact"
214
- };
215
- } else if (survivors.length === 0) return {
216
- ref,
217
- reason: "deleted"
218
- };
219
- else {
220
- const outcome = scoreFaces(ref.hint, survivors, scoreFn);
221
- if (outcome.kind === "match") return {
222
- face: outcome.face,
223
- confidence: "geometric-fallback"
224
- };
225
- if (outcome.kind === "ambiguous") return {
226
- ref,
227
- reason: "ambiguous",
228
- candidates: outcome.candidates
229
- };
230
- }
231
- }
232
- const outcome = scoreFaces(ref.hint, faces, scoreFn);
233
- if (outcome.kind === "match") return {
234
- face: outcome.face,
235
- confidence: "geometric-fallback"
236
- };
237
- if (outcome.kind === "ambiguous") return {
238
- ref,
239
- reason: "ambiguous",
240
- candidates: outcome.candidates
241
- };
242
- return {
243
- ref,
244
- reason: "not-found"
245
- };
246
- }
247
- //#endregion
248
- export { updateRoles as a, resolveRef as i, captureHint as n, defaultScorer as o, createRef as r, assignRoles as t };