void update boundary provenance
This commit is contained in:
parent
6948e7ef25
commit
5e071d9ff3
2 changed files with 69 additions and 83 deletions
|
|
@ -192,6 +192,7 @@ Regression guardrails:
|
||||||
2. No schema changes required to add spline segment kind later.
|
2. No schema changes required to add spline segment kind later.
|
||||||
3. Selection never reports mixed-source face entities.
|
3. Selection never reports mixed-source face entities.
|
||||||
4. Any debug/runtime geometry under solids root must explicitly convert GeometryStore world coordinates to root-local coordinates.
|
4. Any debug/runtime geometry under solids root must explicitly convert GeometryStore world coordinates to root-local coordinates.
|
||||||
|
5. Boundary debug rendering must use GeometryStore (`boundaries` / `surface_patches`) as source of truth, not reconstructed sketch-plane loops.
|
||||||
|
|
||||||
## Implementation Checklist
|
## Implementation Checklist
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -724,7 +724,6 @@ function edgeKey(a, b) {
|
||||||
|
|
||||||
const triRegionIds = new Array(triCount);
|
const triRegionIds = new Array(triCount);
|
||||||
const triRegionKeys = new Array(triCount);
|
const triRegionKeys = new Array(triCount);
|
||||||
const sourceRegionIdsSet = new Set();
|
|
||||||
|
|
||||||
for (let ti = 0; ti < triCount; ti++) {
|
for (let ti = 0; ti < triCount; ti++) {
|
||||||
const globalTri = Number(faceTris[ti]);
|
const globalTri = Number(faceTris[ti]);
|
||||||
|
|
@ -757,28 +756,6 @@ function edgeKey(a, b) {
|
||||||
const ids = normalizeRegionIds(fromSolids, sourceProfileKeys);
|
const ids = normalizeRegionIds(fromSolids, sourceProfileKeys);
|
||||||
triRegionIds[ti] = ids;
|
triRegionIds[ti] = ids;
|
||||||
triRegionKeys[ti] = regionKey(ids);
|
triRegionKeys[ti] = regionKey(ids);
|
||||||
for (const id of ids) sourceRegionIdsSet.add(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const triNeighbors = Array.from({ length: triCount }, () => []);
|
|
||||||
const firstByEdge = new Map();
|
|
||||||
for (let ti = 0; ti < triCount; ti++) {
|
|
||||||
const a = Number(triIndex[ti * 3]);
|
|
||||||
const b = Number(triIndex[ti * 3 + 1]);
|
|
||||||
const c = Number(triIndex[ti * 3 + 2]);
|
|
||||||
const edges = [[a, b], [b, c], [c, a]];
|
|
||||||
for (const [v0, v1] of edges) {
|
|
||||||
const key = edgeKey(v0, v1);
|
|
||||||
if (!firstByEdge.has(key)) {
|
|
||||||
firstByEdge.set(key, ti);
|
|
||||||
} else {
|
|
||||||
const other = firstByEdge.get(key);
|
|
||||||
if (Number.isFinite(other) && other !== ti) {
|
|
||||||
triNeighbors[ti].push(other);
|
|
||||||
triNeighbors[other].push(ti);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const localToWorld = new Map();
|
const localToWorld = new Map();
|
||||||
|
|
@ -791,6 +768,36 @@ function edgeKey(a, b) {
|
||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const geomEdgeKey = (a, b) => {
|
||||||
|
const ka = quantPointKey(a);
|
||||||
|
const kb = quantPointKey(b);
|
||||||
|
return ka < kb ? `${ka}|${kb}` : `${kb}|${ka}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const triNeighbors = Array.from({ length: triCount }, () => []);
|
||||||
|
const firstByEdge = new Map();
|
||||||
|
for (let ti = 0; ti < triCount; ti++) {
|
||||||
|
const ia = Number(triIndex[ti * 3]);
|
||||||
|
const ib = Number(triIndex[ti * 3 + 1]);
|
||||||
|
const ic = Number(triIndex[ti * 3 + 2]);
|
||||||
|
const a = worldVertex(ia);
|
||||||
|
const b = worldVertex(ib);
|
||||||
|
const c = worldVertex(ic);
|
||||||
|
const edges = [[a, b], [b, c], [c, a]];
|
||||||
|
for (const [v0, v1] of edges) {
|
||||||
|
const key = geomEdgeKey(v0, v1);
|
||||||
|
if (!firstByEdge.has(key)) {
|
||||||
|
firstByEdge.set(key, ti);
|
||||||
|
} else {
|
||||||
|
const other = firstByEdge.get(key);
|
||||||
|
if (Number.isFinite(other) && other !== ti) {
|
||||||
|
triNeighbors[ti].push(other);
|
||||||
|
triNeighbors[other].push(ti);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const visited = new Uint8Array(triCount);
|
const visited = new Uint8Array(triCount);
|
||||||
const groups = [];
|
const groups = [];
|
||||||
for (let seed = 0; seed < triCount; seed++) {
|
for (let seed = 0; seed < triCount; seed++) {
|
||||||
|
|
@ -825,14 +832,17 @@ function edgeKey(a, b) {
|
||||||
const i0 = Number(triIndex[localTri * 3]);
|
const i0 = Number(triIndex[localTri * 3]);
|
||||||
const i1 = Number(triIndex[localTri * 3 + 1]);
|
const i1 = Number(triIndex[localTri * 3 + 1]);
|
||||||
const i2 = Number(triIndex[localTri * 3 + 2]);
|
const i2 = Number(triIndex[localTri * 3 + 2]);
|
||||||
|
const v0 = worldVertex(i0);
|
||||||
|
const v1 = worldVertex(i1);
|
||||||
|
const v2 = worldVertex(i2);
|
||||||
const globalTri = Number(faceTris[localTri]);
|
const globalTri = Number(faceTris[localTri]);
|
||||||
if (Number.isFinite(globalTri)) triIdsGlobal.push(globalTri);
|
if (Number.isFinite(globalTri)) triIdsGlobal.push(globalTri);
|
||||||
const edges = [[i0, i1], [i1, i2], [i2, i0]];
|
const edges = [[v0, v1], [v1, v2], [v2, v0]];
|
||||||
for (const [a, b] of edges) {
|
for (const [a, b] of edges) {
|
||||||
const ek = edgeKey(a, b);
|
const ek = geomEdgeKey(a, b);
|
||||||
const rec = boundaryEdges.get(ek);
|
const rec = boundaryEdges.get(ek);
|
||||||
if (!rec) {
|
if (!rec) {
|
||||||
boundaryEdges.set(ek, { a, b, count: 1 });
|
boundaryEdges.set(ek, { a: a.clone(), b: b.clone(), count: 1 });
|
||||||
} else {
|
} else {
|
||||||
rec.count++;
|
rec.count++;
|
||||||
}
|
}
|
||||||
|
|
@ -841,10 +851,10 @@ function edgeKey(a, b) {
|
||||||
const loopSegments = [];
|
const loopSegments = [];
|
||||||
for (const rec of boundaryEdges.values()) {
|
for (const rec of boundaryEdges.values()) {
|
||||||
if (Number(rec?.count) !== 1) continue;
|
if (Number(rec?.count) !== 1) continue;
|
||||||
const a = worldVertex(rec.a);
|
const a = rec?.a;
|
||||||
const b = worldVertex(rec.b);
|
const b = rec?.b;
|
||||||
if (!a || !b || a.distanceToSquared?.(b) <= 1e-16) continue;
|
if (!a || !b || a.distanceToSquared?.(b) <= 1e-16) continue;
|
||||||
loopSegments.push({ a: a.clone(), b: b.clone() });
|
loopSegments.push({ a, b });
|
||||||
}
|
}
|
||||||
const loops = buildBoundaryLoopsFromSegments(loopSegments);
|
const loops = buildBoundaryLoopsFromSegments(loopSegments);
|
||||||
if (!loops.length) continue;
|
if (!loops.length) continue;
|
||||||
|
|
@ -1129,70 +1139,45 @@ function edgeKey(a, b) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prefs.showBoundaries) {
|
if (prefs.showBoundaries) {
|
||||||
const sketchDerived = this.getSketchDerivedBoundarySegments();
|
const preferredBoundaryIds = new Set();
|
||||||
if (sketchDerived.length) {
|
for (const patch of patches) {
|
||||||
|
const ids = Array.isArray(patch?.boundary_ids) ? patch.boundary_ids : [];
|
||||||
|
for (const id of ids) {
|
||||||
|
const bid = String(id || '').trim();
|
||||||
|
if (bid) preferredBoundaryIds.add(bid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let toDraw = preferredBoundaryIds.size
|
||||||
|
? boundaries.filter(boundary => preferredBoundaryIds.has(String(boundary?.id || '')))
|
||||||
|
: boundaries;
|
||||||
|
if (!toDraw.length && boundaries.length) {
|
||||||
|
toDraw = boundaries;
|
||||||
|
}
|
||||||
|
for (const boundary of toDraw) {
|
||||||
|
const segmentIds = Array.isArray(boundary?.segment_ids) ? boundary.segment_ids : [];
|
||||||
|
if (!segmentIds.length) continue;
|
||||||
const positions = [];
|
const positions = [];
|
||||||
for (const seg of sketchDerived) {
|
for (const segmentId of segmentIds) {
|
||||||
// Sketch-derived loop points are generated from sketch plane
|
const seg = segById.get(String(segmentId || ''));
|
||||||
// frames in the same modeling frame as `space.WORLD` (root local).
|
if (!seg?.a || !seg?.b) continue;
|
||||||
// Do not apply an additional world->root conversion here or the
|
const a = toRootLocal(vec3FromRecord(seg.a));
|
||||||
// debug boundaries will rotate off-axis by the WORLD transform.
|
const b = toRootLocal(vec3FromRecord(seg.b));
|
||||||
const a = seg.a;
|
|
||||||
const b = seg.b;
|
|
||||||
positions.push(a.x, a.y, a.z, b.x, b.y, b.z);
|
positions.push(a.x, a.y, a.z, b.x, b.y, b.z);
|
||||||
}
|
}
|
||||||
if (positions.length) {
|
if (!positions.length) continue;
|
||||||
const geom = new THREE.BufferGeometry();
|
const color = colorFromId(boundary?.id, 62, 56);
|
||||||
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
const geom = new THREE.BufferGeometry();
|
||||||
const mat = new THREE.LineBasicMaterial({
|
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
||||||
color: 0x6ee7b7,
|
const mat = new THREE.LineBasicMaterial({
|
||||||
transparent: true,
|
color,
|
||||||
opacity: 0.95,
|
transparent: true,
|
||||||
depthTest: false,
|
opacity: 0.9,
|
||||||
depthWrite: false
|
depthTest: false,
|
||||||
});
|
depthWrite: false
|
||||||
const lines = new THREE.LineSegments(geom, mat);
|
});
|
||||||
lines.renderOrder = 95;
|
const lines = new THREE.LineSegments(geom, mat);
|
||||||
this._debugGroup.add(lines);
|
lines.renderOrder = 95;
|
||||||
}
|
this._debugGroup.add(lines);
|
||||||
} else {
|
|
||||||
const preferredBoundaryIds = new Set();
|
|
||||||
for (const patch of patches) {
|
|
||||||
const ids = Array.isArray(patch?.boundary_ids) ? patch.boundary_ids : [];
|
|
||||||
for (const id of ids) {
|
|
||||||
const bid = String(id || '').trim();
|
|
||||||
if (bid) preferredBoundaryIds.add(bid);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const toDraw = preferredBoundaryIds.size
|
|
||||||
? boundaries.filter(boundary => preferredBoundaryIds.has(String(boundary?.id || '')))
|
|
||||||
: boundaries;
|
|
||||||
for (const boundary of toDraw) {
|
|
||||||
const segmentIds = Array.isArray(boundary?.segment_ids) ? boundary.segment_ids : [];
|
|
||||||
if (!segmentIds.length) continue;
|
|
||||||
const positions = [];
|
|
||||||
for (const segmentId of segmentIds) {
|
|
||||||
const seg = segById.get(String(segmentId || ''));
|
|
||||||
if (!seg?.a || !seg?.b) continue;
|
|
||||||
const a = toRootLocal(vec3FromRecord(seg.a));
|
|
||||||
const b = toRootLocal(vec3FromRecord(seg.b));
|
|
||||||
positions.push(a.x, a.y, a.z, b.x, b.y, b.z);
|
|
||||||
}
|
|
||||||
if (!positions.length) continue;
|
|
||||||
const color = colorFromId(boundary?.id, 62, 56);
|
|
||||||
const geom = new THREE.BufferGeometry();
|
|
||||||
geom.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
|
|
||||||
const mat = new THREE.LineBasicMaterial({
|
|
||||||
color,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.9,
|
|
||||||
depthTest: false,
|
|
||||||
depthWrite: false
|
|
||||||
});
|
|
||||||
const lines = new THREE.LineSegments(geom, mat);
|
|
||||||
lines.renderOrder = 95;
|
|
||||||
this._debugGroup.add(lines);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue