Skip to content

Commit b1801f9

Browse files
committed
allow deriving lines and points from faces
1 parent 34a935f commit b1801f9

10 files changed

Lines changed: 389 additions & 136 deletions

File tree

src/void/api/solids.js

Lines changed: 95 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,6 @@ function createSolidsApi(getApi) {
2020
}
2121
const loop = view?.object?.userData?.sketchProfileLoop || view?.entity?.loop || null;
2222
if (Array.isArray(loop) && loop.length >= 3) return [loop];
23-
if (Array.isArray(profileTarget?.loops) && profileTarget.loops.length) {
24-
const cached = profileTarget.loops
25-
.filter(item => Array.isArray(item) && item.length >= 3)
26-
.map(item => item.map(p => ({ x: p?.x || 0, y: p?.y || 0 })));
27-
if (cached.length) return cached;
28-
}
2923
return null;
3024
}
3125

@@ -296,13 +290,29 @@ function createSolidsApi(getApi) {
296290
center,
297291
normal,
298292
xAxis,
299-
planar
293+
planar,
294+
boundarySegmentsLocal: null
300295
});
301296
groupId++;
302297
}
303298
return { triToGroup, groups };
304299
}
305300

301+
function buildBoundarySegmentsFromGeometry(geometry) {
302+
if (!geometry) return [];
303+
const edgesGeom = new THREE.EdgesGeometry(geometry, 1);
304+
const pos = edgesGeom.getAttribute?.('position');
305+
if (!pos) return [];
306+
const out = [];
307+
for (let i = 0; i + 1 < pos.count; i += 2) {
308+
const a = new THREE.Vector3().fromBufferAttribute(pos, i);
309+
const b = new THREE.Vector3().fromBufferAttribute(pos, i + 1);
310+
out.push({ a, b, mid: a.clone().add(b).multiplyScalar(0.5) });
311+
}
312+
edgesGeom.dispose?.();
313+
return out;
314+
}
315+
306316
function makeFaceMaterials() {
307317
return {
308318
hover: new THREE.MeshBasicMaterial({
@@ -621,6 +631,16 @@ function createSolidsApi(getApi) {
621631
return out;
622632
},
623633

634+
getPickEdgeForSolid(solidId) {
635+
const id = String(solidId || '');
636+
if (!id) return null;
637+
const view = this._meshViews.get(id);
638+
if (!view || view?.group?.visible === false || view?.mesh?.visible === false || view?.edges?.visible === false) {
639+
return null;
640+
}
641+
return view.edges || null;
642+
},
643+
624644
getEdgeSegmentWorld(object, segmentIndex) {
625645
if (!object?.geometry || segmentIndex < 0) return null;
626646
object.updateMatrixWorld?.(true);
@@ -677,43 +697,53 @@ function createSolidsApi(getApi) {
677697
const srcA = new THREE.Vector3(Number(sa.x || 0), Number(sa.y || 0), Number(sa.z || 0));
678698
const srcB = new THREE.Vector3(Number(sb.x || 0), Number(sb.y || 0), Number(sb.z || 0));
679699
const solids = this.list() || [];
680-
const wanted = [];
700+
const searchSets = [];
681701
if (targetSolidId) {
682-
wanted.push(targetSolidId);
683-
} else if (targetFeatureId) {
702+
searchSets.push([targetSolidId]);
703+
}
704+
if (targetFeatureId) {
705+
const byFeature = [];
684706
for (const solid of solids) {
685707
if (String(solid?.source?.feature_id || '') === targetFeatureId && solid?.id) {
686-
wanted.push(String(solid.id));
708+
byFeature.push(String(solid.id));
687709
}
688710
}
689-
} else {
690-
for (const solid of solids) {
691-
if (solid?.id) wanted.push(String(solid.id));
692-
}
711+
if (byFeature.length) searchSets.push(byFeature);
712+
}
713+
const all = [];
714+
for (const solid of solids) {
715+
if (solid?.id) all.push(String(solid.id));
693716
}
717+
if (all.length) searchSets.push(all);
694718
let best = null;
695719
let bestScore = Infinity;
696-
for (const solidId of wanted) {
697-
const view = this._meshViews.get(solidId);
698-
const edgesObj = view?.edges;
699-
if (!edgesObj?.geometry) continue;
700-
const pos = edgesObj.geometry.getAttribute?.('position');
701-
const idx = edgesObj.geometry.getIndex?.();
702-
if (!pos) continue;
703-
const segCount = idx?.array?.length
704-
? Math.floor(idx.array.length / 2)
705-
: Math.floor(pos.count / 2);
706-
for (let i = 0; i < segCount; i++) {
707-
const seg = this.getEdgeSegmentWorld(edgesObj, i);
708-
if (!seg) continue;
709-
const d1 = seg.a.distanceTo(srcA) + seg.b.distanceTo(srcB);
710-
const d2 = seg.a.distanceTo(srcB) + seg.b.distanceTo(srcA);
711-
const score = Math.min(d1, d2);
712-
if (score < bestScore) {
713-
bestScore = score;
714-
best = { solidId, index: i, aWorld: seg.a, bWorld: seg.b };
720+
const scanSet = (wanted = []) => {
721+
for (const solidId of wanted) {
722+
const view = this._meshViews.get(solidId);
723+
const edgesObj = view?.edges;
724+
if (!edgesObj?.geometry) continue;
725+
const pos = edgesObj.geometry.getAttribute?.('position');
726+
const idx = edgesObj.geometry.getIndex?.();
727+
if (!pos) continue;
728+
const segCount = idx?.array?.length
729+
? Math.floor(idx.array.length / 2)
730+
: Math.floor(pos.count / 2);
731+
for (let i = 0; i < segCount; i++) {
732+
const seg = this.getEdgeSegmentWorld(edgesObj, i);
733+
if (!seg) continue;
734+
const d1 = seg.a.distanceTo(srcA) + seg.b.distanceTo(srcB);
735+
const d2 = seg.a.distanceTo(srcB) + seg.b.distanceTo(srcA);
736+
const score = Math.min(d1, d2);
737+
if (score < bestScore) {
738+
bestScore = score;
739+
best = { solidId, index: i, aWorld: seg.a, bWorld: seg.b };
740+
}
715741
}
716742
}
743+
};
744+
for (const wanted of searchSets) {
745+
if (best) break;
746+
scanSet(wanted);
717747
}
718748
if (!best) return null;
719749
best.midWorld = best.aWorld.clone().add(best.bWorld).multiplyScalar(0.5);
@@ -752,6 +782,31 @@ function createSolidsApi(getApi) {
752782
return { key: `${solidId}:${faceId}`, solidId, faceId, view, meta };
753783
},
754784

785+
getFaceBoundarySegments(key) {
786+
const face = this.getFaceByKey(key);
787+
if (!face?.meta?.geometry) return [];
788+
if (!Array.isArray(face.meta.boundarySegmentsLocal)) {
789+
face.meta.boundarySegmentsLocal = buildBoundarySegmentsFromGeometry(face.meta.geometry);
790+
}
791+
const local = face.meta.boundarySegmentsLocal || [];
792+
if (!local.length) return [];
793+
const mesh = face?.view?.mesh || null;
794+
if (!mesh?.matrixWorld) return [];
795+
mesh.updateMatrixWorld?.(true);
796+
const out = [];
797+
for (const seg of local) {
798+
if (!seg?.a || !seg?.b) continue;
799+
const a = seg.a.clone().applyMatrix4(mesh.matrixWorld);
800+
const b = seg.b.clone().applyMatrix4(mesh.matrixWorld);
801+
out.push({
802+
a,
803+
b,
804+
mid: a.clone().add(b).multiplyScalar(0.5)
805+
});
806+
}
807+
return out;
808+
},
809+
755810
setHoveredFace(key = null) {
756811
const next = key && this.getFaceByKey(key) ? key : null;
757812
if (next === this._hoveredFaceKey) return;
@@ -1106,6 +1161,7 @@ function createSolidsApi(getApi) {
11061161
let result = null;
11071162
let passReason = reason;
11081163
for (let pass = 0; pass < 2; pass++) {
1164+
api.sketchRuntime?.sync?.();
11091165
const snapshot = buildRebuildSnapshot(api);
11101166
try {
11111167
const workerReply = await this.requestWorkerRebuild(snapshot, passReason);
@@ -1147,6 +1203,11 @@ function createSolidsApi(getApi) {
11471203
passReason = 'sketch.face.rebind';
11481204
continue;
11491205
}
1206+
if (derivedChanged && pass === 0) {
1207+
api.sketchRuntime?.sync?.();
1208+
passReason = 'sketch.derived.refresh';
1209+
continue;
1210+
}
11501211
if (rebound || derivedChanged) {
11511212
api.sketchRuntime?.sync?.();
11521213
}

src/void/interact.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,6 @@ const interact = {
4646
hoveredSketchProfileKey: null,
4747
hoveredSolidFaceKey: null,
4848
hoveredSketchConstraintId: null,
49-
_debugDerivedHoverKey: null,
50-
_debugDerivedRenderKey: null,
5149
sketchPointerDown: null,
5250
sketchDrag: null,
5351
sketchLineStart: null,

src/void/interact/planes.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,11 @@ function updateHandleScreenScales() {
130130
}
131131

132132
function handleHover(intersection, event, allIntersections) {
133-
if (!(this.isSketchEditing && this.isSketchEditing()) || (this.isSketchRetargetMode && this.isSketchRetargetMode())) {
134-
const primaryHit = this.getPrimarySurfaceHitFromIntersections(allIntersections || (intersection ? [intersection] : []));
133+
const sketchEditing = !!(this.isSketchEditing && this.isSketchEditing());
134+
const retargetMode = !!(this.isSketchRetargetMode && this.isSketchRetargetMode());
135+
const primaryHit = this.getPrimarySurfaceHitFromIntersections(allIntersections || (intersection ? [intersection] : []));
136+
137+
if (!sketchEditing || retargetMode) {
135138
if (primaryHit?.type === 'profile') {
136139
const profileHit = primaryHit.hit;
137140
if (this.hoveredSolidFaceKey) {
@@ -172,6 +175,26 @@ function handleHover(intersection, event, allIntersections) {
172175
api.solids?.setHoveredFace?.(null);
173176
window.dispatchEvent(new CustomEvent('void-state-change'));
174177
}
178+
} else if (primaryHit?.type === 'solid-face') {
179+
const solidFaceHit = primaryHit.hit;
180+
this.hoveredSolidFaceKey = solidFaceHit.key;
181+
api.solids?.setHoveredFace?.(solidFaceHit.key);
182+
this.hoverIntersection = solidFaceHit.intersection || intersection || null;
183+
this.setHoveredPoint(null);
184+
this.hoveredSketchProfileKey = null;
185+
api.sketchRuntime?.setHoveredProfile?.(null);
186+
if (this.hoveredPlane && !this.hoveredPlane.isSelected()) {
187+
this.hoveredPlane.setHovered(false);
188+
this.hoveredPlane = null;
189+
}
190+
this.updateSketchInteractionVisuals?.();
191+
window.dispatchEvent(new CustomEvent('void-state-change'));
192+
return;
193+
} else if (this.hoveredSolidFaceKey) {
194+
this.hoveredSolidFaceKey = null;
195+
api.solids?.setHoveredFace?.(null);
196+
this.updateSketchInteractionVisuals?.();
197+
window.dispatchEvent(new CustomEvent('void-state-change'));
175198
}
176199

177200
const pointHit = this.getPointHitFromEvent(event);

0 commit comments

Comments
 (0)