brepjs 18.109.0 → 18.110.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/brepjs.cjs +1 -1
- package/dist/brepjs.js +1 -1
- package/dist/shapeRef.cjs +1 -1
- package/dist/shapeRef.js +1 -1
- package/dist/{shapeRefFns-C1DefNzr.js → shapeRefFns-ChMnsOAF.js} +97 -45
- package/dist/{shapeRefFns--2FPCL7P.cjs → shapeRefFns-CuepnFOm.cjs} +97 -45
- package/dist/topology/shapeRef/shapeRefFns.d.ts +19 -13
- package/dist/topology/shapeRef/shapeRefTypes.d.ts +6 -2
- package/package.json +1 -1
package/dist/brepjs.cjs
CHANGED
|
@@ -29,7 +29,7 @@ const require_importFns = require("./importFns-CgMQIi0z.cjs");
|
|
|
29
29
|
const require_loftFns = require("./loftFns-fpTmrIdd.cjs");
|
|
30
30
|
const require_cameraFns = require("./cameraFns-uo1sZbBT.cjs");
|
|
31
31
|
const require_textMetrics = require("./textMetrics-IC_IoI7l.cjs");
|
|
32
|
-
const require_shapeRefFns = require("./shapeRefFns
|
|
32
|
+
const require_shapeRefFns = require("./shapeRefFns-CuepnFOm.cjs");
|
|
33
33
|
const require_workerPool = require("./workerPool-D30NsiIW.cjs");
|
|
34
34
|
const require_primitiveFns = require("./primitiveFns-DLIWtQWA.cjs");
|
|
35
35
|
//#region src/topology/shapeBooleans.ts
|
package/dist/brepjs.js
CHANGED
|
@@ -28,7 +28,7 @@ import { a as revolve$1, c as multiSectionSweep, d as twistExtrude, i as extrude
|
|
|
28
28
|
import { a as Sketch, c as compoundSketchLoft, d as sketchFace, f as sketchLoft, h as sketchWires, i as Sketches, l as compoundSketchRevolve, m as sketchSweep, n as getFont, o as compoundSketchExtrude, p as sketchRevolve, r as loadFont, s as compoundSketchFace, t as textBlueprints, u as sketchExtrude, v as CompoundSketch } from "./textBlueprints-BnkWAqwS.js";
|
|
29
29
|
import { a as makeProjectedEdges, i as projectEdges, n as cameraLookAt, r as createCamera, s as isProjectionPlane, t as cameraFromPlane } from "./cameraFns-bk9e0F-T.js";
|
|
30
30
|
import { n as textMetrics, r as sketchText, t as fontMetrics } from "./textMetrics-COGYk0LE.js";
|
|
31
|
-
import { a as updateRoles, i as resolveRef, n as captureHint, o as defaultScorer, r as createRef, t as assignRoles } from "./shapeRefFns-
|
|
31
|
+
import { a as updateRoles, i as resolveRef, n as captureHint, o as defaultScorer, r as createRef, t as assignRoles } from "./shapeRefFns-ChMnsOAF.js";
|
|
32
32
|
import { _ as isSuccessResponse, a as createWorkerClient, c as enqueueTask, d as rejectAll, f as isBatchRequest, g as isOperationRequest, h as isInitRequest, i as registerHandler, l as isEmpty$1, m as isErrorResponse, n as createOperationRegistry, o as createTaskQueue, p as isDisposeRequest, r as createWorkerHandler, s as dequeueTask, t as createWorkerPool, u as pendingCount } from "./workerPool-XcYX2UZ0.js";
|
|
33
33
|
import { C as threePointArc, D as wireLoop, E as wire, S as tangentArc, T as vertex, _ as polygon, a as circle, b as sphere$1, 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$1, x as subFace, y as solid } from "./primitiveFns-CBrOyk5S.js";
|
|
34
34
|
//#region \0rolldown/runtime.js
|
package/dist/shapeRef.cjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_shapeRefFns = require("./shapeRefFns
|
|
2
|
+
const require_shapeRefFns = require("./shapeRefFns-CuepnFOm.cjs");
|
|
3
3
|
exports.assignRoles = require_shapeRefFns.assignRoles;
|
|
4
4
|
exports.captureHint = require_shapeRefFns.captureHint;
|
|
5
5
|
exports.createRef = require_shapeRefFns.createRef;
|
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-
|
|
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
2
|
export { assignRoles, captureHint, createRef, defaultScorer, resolveRef, updateRoles };
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { n as wasmIndex } from "./vec3-Dpha8d5k.js";
|
|
2
1
|
import { c as getFaces } from "./topologyQueryFns-BeKaWpNm.js";
|
|
3
2
|
import { i as faceGeomType, l as normalAt, r as faceCenter } from "./faceFns-B86WGqDg.js";
|
|
4
3
|
import { n as getHashCode } from "./shapeFns-DrZOVwHX.js";
|
|
@@ -81,7 +80,8 @@ function boxRoleFromNormal(n) {
|
|
|
81
80
|
*
|
|
82
81
|
* For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
|
|
83
82
|
*
|
|
84
|
-
* @returns Map from role name to face hash
|
|
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
85
|
*/
|
|
86
86
|
function assignRoles(shape, operationType) {
|
|
87
87
|
const faces = getFaces(shape);
|
|
@@ -89,13 +89,13 @@ function assignRoles(shape, operationType) {
|
|
|
89
89
|
if (operationType === "box") {
|
|
90
90
|
for (const face of faces) {
|
|
91
91
|
const role = boxRoleFromNormal(normalAt(face));
|
|
92
|
-
if (role !== void 0 && !roles.has(role)) roles.set(role, getHashCode(face));
|
|
92
|
+
if (role !== void 0 && !roles.has(role)) roles.set(role, [getHashCode(face)]);
|
|
93
93
|
}
|
|
94
94
|
return roles;
|
|
95
95
|
}
|
|
96
96
|
let index = 0;
|
|
97
97
|
for (const face of faces) {
|
|
98
|
-
roles.set(`${operationType}:face_${index}`, getHashCode(face));
|
|
98
|
+
roles.set(`${operationType}:face_${index}`, [getHashCode(face)]);
|
|
99
99
|
index++;
|
|
100
100
|
}
|
|
101
101
|
return roles;
|
|
@@ -109,31 +109,47 @@ function createRef(origin, role, face) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
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
|
+
/**
|
|
112
128
|
* Propagate a role table through a ShapeEvolution record.
|
|
113
129
|
* Returns a new RoleTable with hashes updated according to the evolution.
|
|
114
130
|
*
|
|
115
|
-
* - Deleted faces: role removed
|
|
116
|
-
* - Modified faces: hash
|
|
117
|
-
*
|
|
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.
|
|
118
135
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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.
|
|
123
142
|
*/
|
|
124
143
|
function updateRoles(roles, origin, evolution) {
|
|
125
144
|
const originRoles = roles.get(origin);
|
|
126
145
|
if (!originRoles) return roles;
|
|
127
146
|
const updatedOriginRoles = /* @__PURE__ */ new Map();
|
|
128
|
-
for (const [role,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (modifiedHashes && modifiedHashes.length > 0) updatedOriginRoles.set(role, wasmIndex(modifiedHashes, 0));
|
|
132
|
-
else updatedOriginRoles.set(role, hash);
|
|
147
|
+
for (const [role, hashes] of originRoles) {
|
|
148
|
+
const successors = nextHashes(hashes, evolution);
|
|
149
|
+
if (successors.length > 0) updatedOriginRoles.set(role, successors);
|
|
133
150
|
}
|
|
134
151
|
const newRoles = /* @__PURE__ */ new Map();
|
|
135
|
-
for (const [key, value] of roles)
|
|
136
|
-
else newRoles.set(key, value);
|
|
152
|
+
for (const [key, value] of roles) newRoles.set(key, key === origin ? updatedOriginRoles : value);
|
|
137
153
|
return newRoles;
|
|
138
154
|
}
|
|
139
155
|
/** Ambiguity threshold: if two scores are within this range, it's ambiguous. */
|
|
@@ -141,34 +157,19 @@ var AMBIGUITY_THRESHOLD = .1;
|
|
|
141
157
|
/** Minimum score for geometric fallback to accept a match. */
|
|
142
158
|
var MIN_SCORE = .5;
|
|
143
159
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* 3. Ambiguous if multiple faces score within threshold
|
|
150
|
-
* 4. Not-found if no match above minimum score
|
|
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.
|
|
151
165
|
*/
|
|
152
|
-
function
|
|
153
|
-
const faces = getFaces(currentShape);
|
|
154
|
-
const scoreFn = scorer ?? defaultScorer;
|
|
155
|
-
const targetHash = roles.get(ref.origin)?.get(ref.role);
|
|
156
|
-
if (targetHash !== void 0) {
|
|
157
|
-
for (const face of faces) if (getHashCode(face) === targetHash) return {
|
|
158
|
-
face,
|
|
159
|
-
confidence: "exact"
|
|
160
|
-
};
|
|
161
|
-
return {
|
|
162
|
-
ref,
|
|
163
|
-
reason: "deleted"
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
+
function scoreFaces(hint, candidates, scoreFn) {
|
|
166
167
|
let bestScore = -Infinity;
|
|
167
168
|
let bestFace;
|
|
168
169
|
let secondBestScore = -Infinity;
|
|
169
170
|
const scored = [];
|
|
170
|
-
for (const face of
|
|
171
|
-
const score = scoreFn(
|
|
171
|
+
for (const face of candidates) {
|
|
172
|
+
const score = scoreFn(hint, face);
|
|
172
173
|
if (score > MIN_SCORE) scored.push([face, score]);
|
|
173
174
|
if (score > bestScore) {
|
|
174
175
|
secondBestScore = bestScore;
|
|
@@ -178,15 +179,66 @@ function resolveRef(ref, roles, currentShape, scorer) {
|
|
|
178
179
|
}
|
|
179
180
|
if (bestFace !== void 0 && bestScore > MIN_SCORE) {
|
|
180
181
|
if (bestScore - secondBestScore < AMBIGUITY_THRESHOLD && scored.length > 1) return {
|
|
181
|
-
|
|
182
|
-
reason: "ambiguous",
|
|
182
|
+
kind: "ambiguous",
|
|
183
183
|
candidates: scored.filter(([, s]) => s >= bestScore - AMBIGUITY_THRESHOLD).map(([f]) => f)
|
|
184
184
|
};
|
|
185
185
|
return {
|
|
186
|
-
|
|
187
|
-
|
|
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"
|
|
188
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
|
+
}
|
|
189
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
|
+
};
|
|
190
242
|
return {
|
|
191
243
|
ref,
|
|
192
244
|
reason: "not-found"
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const require_vec3 = require("./vec3-CFwOI0ZI.cjs");
|
|
2
1
|
const require_topologyQueryFns = require("./topologyQueryFns-CoXVHuN7.cjs");
|
|
3
2
|
const require_faceFns = require("./faceFns-BV0GnTlt.cjs");
|
|
4
3
|
const require_shapeFns = require("./shapeFns-ybo0INGL.cjs");
|
|
@@ -81,7 +80,8 @@ function boxRoleFromNormal(n) {
|
|
|
81
80
|
*
|
|
82
81
|
* For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
|
|
83
82
|
*
|
|
84
|
-
* @returns Map from role name to face hash
|
|
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
85
|
*/
|
|
86
86
|
function assignRoles(shape, operationType) {
|
|
87
87
|
const faces = require_topologyQueryFns.getFaces(shape);
|
|
@@ -89,13 +89,13 @@ function assignRoles(shape, operationType) {
|
|
|
89
89
|
if (operationType === "box") {
|
|
90
90
|
for (const face of faces) {
|
|
91
91
|
const role = boxRoleFromNormal(require_faceFns.normalAt(face));
|
|
92
|
-
if (role !== void 0 && !roles.has(role)) roles.set(role, require_shapeFns.getHashCode(face));
|
|
92
|
+
if (role !== void 0 && !roles.has(role)) roles.set(role, [require_shapeFns.getHashCode(face)]);
|
|
93
93
|
}
|
|
94
94
|
return roles;
|
|
95
95
|
}
|
|
96
96
|
let index = 0;
|
|
97
97
|
for (const face of faces) {
|
|
98
|
-
roles.set(`${operationType}:face_${index}`, require_shapeFns.getHashCode(face));
|
|
98
|
+
roles.set(`${operationType}:face_${index}`, [require_shapeFns.getHashCode(face)]);
|
|
99
99
|
index++;
|
|
100
100
|
}
|
|
101
101
|
return roles;
|
|
@@ -109,31 +109,47 @@ function createRef(origin, role, face) {
|
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
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
|
+
/**
|
|
112
128
|
* Propagate a role table through a ShapeEvolution record.
|
|
113
129
|
* Returns a new RoleTable with hashes updated according to the evolution.
|
|
114
130
|
*
|
|
115
|
-
* - Deleted faces: role removed
|
|
116
|
-
* - Modified faces: hash
|
|
117
|
-
*
|
|
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.
|
|
118
135
|
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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.
|
|
123
142
|
*/
|
|
124
143
|
function updateRoles(roles, origin, evolution) {
|
|
125
144
|
const originRoles = roles.get(origin);
|
|
126
145
|
if (!originRoles) return roles;
|
|
127
146
|
const updatedOriginRoles = /* @__PURE__ */ new Map();
|
|
128
|
-
for (const [role,
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (modifiedHashes && modifiedHashes.length > 0) updatedOriginRoles.set(role, require_vec3.wasmIndex(modifiedHashes, 0));
|
|
132
|
-
else updatedOriginRoles.set(role, hash);
|
|
147
|
+
for (const [role, hashes] of originRoles) {
|
|
148
|
+
const successors = nextHashes(hashes, evolution);
|
|
149
|
+
if (successors.length > 0) updatedOriginRoles.set(role, successors);
|
|
133
150
|
}
|
|
134
151
|
const newRoles = /* @__PURE__ */ new Map();
|
|
135
|
-
for (const [key, value] of roles)
|
|
136
|
-
else newRoles.set(key, value);
|
|
152
|
+
for (const [key, value] of roles) newRoles.set(key, key === origin ? updatedOriginRoles : value);
|
|
137
153
|
return newRoles;
|
|
138
154
|
}
|
|
139
155
|
/** Ambiguity threshold: if two scores are within this range, it's ambiguous. */
|
|
@@ -141,34 +157,19 @@ var AMBIGUITY_THRESHOLD = .1;
|
|
|
141
157
|
/** Minimum score for geometric fallback to accept a match. */
|
|
142
158
|
var MIN_SCORE = .5;
|
|
143
159
|
/**
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
* 3. Ambiguous if multiple faces score within threshold
|
|
150
|
-
* 4. Not-found if no match above minimum score
|
|
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.
|
|
151
165
|
*/
|
|
152
|
-
function
|
|
153
|
-
const faces = require_topologyQueryFns.getFaces(currentShape);
|
|
154
|
-
const scoreFn = scorer ?? defaultScorer;
|
|
155
|
-
const targetHash = roles.get(ref.origin)?.get(ref.role);
|
|
156
|
-
if (targetHash !== void 0) {
|
|
157
|
-
for (const face of faces) if (require_shapeFns.getHashCode(face) === targetHash) return {
|
|
158
|
-
face,
|
|
159
|
-
confidence: "exact"
|
|
160
|
-
};
|
|
161
|
-
return {
|
|
162
|
-
ref,
|
|
163
|
-
reason: "deleted"
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
+
function scoreFaces(hint, candidates, scoreFn) {
|
|
166
167
|
let bestScore = -Infinity;
|
|
167
168
|
let bestFace;
|
|
168
169
|
let secondBestScore = -Infinity;
|
|
169
170
|
const scored = [];
|
|
170
|
-
for (const face of
|
|
171
|
-
const score = scoreFn(
|
|
171
|
+
for (const face of candidates) {
|
|
172
|
+
const score = scoreFn(hint, face);
|
|
172
173
|
if (score > MIN_SCORE) scored.push([face, score]);
|
|
173
174
|
if (score > bestScore) {
|
|
174
175
|
secondBestScore = bestScore;
|
|
@@ -178,15 +179,66 @@ function resolveRef(ref, roles, currentShape, scorer) {
|
|
|
178
179
|
}
|
|
179
180
|
if (bestFace !== void 0 && bestScore > MIN_SCORE) {
|
|
180
181
|
if (bestScore - secondBestScore < AMBIGUITY_THRESHOLD && scored.length > 1) return {
|
|
181
|
-
|
|
182
|
-
reason: "ambiguous",
|
|
182
|
+
kind: "ambiguous",
|
|
183
183
|
candidates: scored.filter(([, s]) => s >= bestScore - AMBIGUITY_THRESHOLD).map(([f]) => f)
|
|
184
184
|
};
|
|
185
185
|
return {
|
|
186
|
-
|
|
187
|
-
|
|
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 = require_topologyQueryFns.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(require_shapeFns.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"
|
|
188
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
|
+
}
|
|
189
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
|
+
};
|
|
190
242
|
return {
|
|
191
243
|
ref,
|
|
192
244
|
reason: "not-found"
|
|
@@ -15,32 +15,38 @@ export declare function captureHint(face: Face): GeometricHint;
|
|
|
15
15
|
*
|
|
16
16
|
* For other types: sequential naming ('opType:face_0', 'opType:face_1', ...).
|
|
17
17
|
*
|
|
18
|
-
* @returns Map from role name to face hash
|
|
18
|
+
* @returns Map from role name to its face hash codes (one at assignment time;
|
|
19
|
+
* a role accrues more hashes only later, when `updateRoles` tracks a split).
|
|
19
20
|
*/
|
|
20
|
-
export declare function assignRoles(shape: Shape3D, operationType: string): Map<string, number>;
|
|
21
|
+
export declare function assignRoles(shape: Shape3D, operationType: string): Map<string, number[]>;
|
|
21
22
|
/** Create a ShapeRef from an origin ID, role name, and face. */
|
|
22
23
|
export declare function createRef(origin: string, role: string, face: Face): ShapeRef;
|
|
23
24
|
/**
|
|
24
25
|
* Propagate a role table through a ShapeEvolution record.
|
|
25
26
|
* Returns a new RoleTable with hashes updated according to the evolution.
|
|
26
27
|
*
|
|
27
|
-
* - Deleted faces: role removed
|
|
28
|
-
* - Modified faces: hash
|
|
29
|
-
*
|
|
28
|
+
* - Deleted faces: hash dropped (role removed once all its hashes are gone).
|
|
29
|
+
* - Modified faces: hash replaced by **all** successor hashes — so a 1→many
|
|
30
|
+
* split keeps every fragment, and `resolveRef` disambiguates among them.
|
|
31
|
+
* - Unchanged faces: hash preserved.
|
|
30
32
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
33
|
+
* Note: `evolution.generated` is intentionally not consumed here — on the OCCT
|
|
34
|
+
* kernels its hashes refer to an intermediate shape, not the final result, so
|
|
35
|
+
* naming generated faces produces roles that never resolve (verified: 0 live
|
|
36
|
+
* generated hashes across cut/fuse on occt-wasm). Stable names for generated
|
|
37
|
+
* geometry (fillet rounds, boolean seams) need history-fidelity work tracked
|
|
38
|
+
* separately.
|
|
35
39
|
*/
|
|
36
40
|
export declare function updateRoles(roles: RoleTable, origin: string, evolution: ShapeEvolution): RoleTable;
|
|
37
41
|
/**
|
|
38
42
|
* Resolve a ShapeRef to a face in the current shape.
|
|
39
43
|
*
|
|
40
44
|
* Resolution strategy:
|
|
41
|
-
* 1. Exact
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
+
* 1. Exact: the role's tracked successor hashes. One survivor → exact match;
|
|
46
|
+
* several survivors (a face that split) → disambiguate among *only those*
|
|
47
|
+
* fragments; none survive → deleted.
|
|
48
|
+
* 2. Geometric fallback over the whole shape when the role isn't tracked (or a
|
|
49
|
+
* scoped score turned up nothing): best-scoring face, else ambiguous /
|
|
50
|
+
* not-found.
|
|
45
51
|
*/
|
|
46
52
|
export declare function resolveRef(ref: ShapeRef, roles: RoleTable, currentShape: Shape3D, scorer?: FaceScorer): ResolvedRef | BrokenRef;
|
|
@@ -22,10 +22,14 @@ export interface ShapeRef {
|
|
|
22
22
|
readonly hint: GeometricHint;
|
|
23
23
|
}
|
|
24
24
|
/**
|
|
25
|
-
* Immutable table mapping `origin -> role ->
|
|
25
|
+
* Immutable table mapping `origin -> role -> faceHashes`.
|
|
26
26
|
* Updated through evolution records when the model is rebuilt.
|
|
27
|
+
*
|
|
28
|
+
* A role usually maps to a single hash, but maps to several after its face
|
|
29
|
+
* splits (a 1→many `modified` evolution); resolution then disambiguates among
|
|
30
|
+
* the surviving successors rather than competing against the whole shape.
|
|
27
31
|
*/
|
|
28
|
-
export type RoleTable = ReadonlyMap<string, ReadonlyMap<string, number>>;
|
|
32
|
+
export type RoleTable = ReadonlyMap<string, ReadonlyMap<string, readonly number[]>>;
|
|
29
33
|
/** A successfully resolved face reference. */
|
|
30
34
|
export interface ResolvedRef {
|
|
31
35
|
readonly face: Face;
|