Skip to content

Commit ce16d01

Browse files
committed
work on corner cases deriving elements
1 parent b1801f9 commit ce16d01

7 files changed

Lines changed: 247 additions & 42 deletions

File tree

src/void/api/solids.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -691,12 +691,37 @@ function createSolidsApi(getApi) {
691691
if (source?.type !== 'solid-edge') return null;
692692
const targetSolidId = String(source?.solid_id || '');
693693
const targetFeatureId = String(source?.solid_feature_id || '');
694+
const sourceFaceId = Number(source?.face_id);
694695
const sa = source?.a;
695696
const sb = source?.b;
696697
if (!sa || !sb) return null;
697698
const srcA = new THREE.Vector3(Number(sa.x || 0), Number(sa.y || 0), Number(sa.z || 0));
698699
const srcB = new THREE.Vector3(Number(sb.x || 0), Number(sb.y || 0), Number(sb.z || 0));
699700
const solids = this.list() || [];
701+
const scoreSegment = (aWorld, bWorld) => {
702+
const d1 = aWorld.distanceTo(srcA) + bWorld.distanceTo(srcB);
703+
const d2 = aWorld.distanceTo(srcB) + bWorld.distanceTo(srcA);
704+
return Math.min(d1, d2);
705+
};
706+
if (targetSolidId && Number.isFinite(sourceFaceId)) {
707+
const faceKey = `${targetSolidId}:${sourceFaceId}`;
708+
const segs = this.getFaceBoundarySegments(faceKey) || [];
709+
let bestFace = null;
710+
let bestFaceScore = Infinity;
711+
for (let i = 0; i < segs.length; i++) {
712+
const seg = segs[i];
713+
if (!seg?.a || !seg?.b) continue;
714+
const score = scoreSegment(seg.a, seg.b);
715+
if (score < bestFaceScore) {
716+
bestFaceScore = score;
717+
bestFace = { solidId: targetSolidId, index: i, aWorld: seg.a, bWorld: seg.b };
718+
}
719+
}
720+
if (bestFace) {
721+
bestFace.midWorld = bestFace.aWorld.clone().add(bestFace.bWorld).multiplyScalar(0.5);
722+
return bestFace;
723+
}
724+
}
700725
const searchSets = [];
701726
if (targetSolidId) {
702727
searchSets.push([targetSolidId]);
@@ -731,9 +756,7 @@ function createSolidsApi(getApi) {
731756
for (let i = 0; i < segCount; i++) {
732757
const seg = this.getEdgeSegmentWorld(edgesObj, i);
733758
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);
759+
const score = scoreSegment(seg.a, seg.b);
737760
if (score < bestScore) {
738761
bestScore = score;
739762
best = { solidId, index: i, aWorld: seg.a, bWorld: seg.b };

src/void/interact.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const interact = {
4343
selectedSketchConstraints: new Set(),
4444
hoveredSketchEntityId: null,
4545
hoveredDerivedCandidate: null,
46+
selectedDerivedSelections: new Map(),
4647
hoveredSketchProfileKey: null,
4748
hoveredSolidFaceKey: null,
4849
hoveredSketchConstraintId: null,

src/void/sketch/create.js

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,7 @@ function createDerivedSketchLine(feature, candidate) {
896896
y: candidate.aLocal.y || 0,
897897
fixed: true,
898898
derived: true,
899-
source: { ...(source || {}), point_kind: 'a' }
899+
source: null
900900
};
901901
const p2 = {
902902
id: this.newSketchEntityId('point'),
@@ -905,7 +905,7 @@ function createDerivedSketchLine(feature, candidate) {
905905
y: candidate.bLocal.y || 0,
906906
fixed: true,
907907
derived: true,
908-
source: { ...(source || {}), point_kind: 'b' }
908+
source: null
909909
};
910910
const line = {
911911
id: this.newSketchEntityId('line'),
@@ -930,6 +930,129 @@ function createDerivedSketchLine(feature, candidate) {
930930
return created;
931931
}
932932

933+
function deriveSelectionsAtomic(feature, selection = {}) {
934+
if (!feature || feature.type !== 'sketch') return false;
935+
const edges = Array.isArray(selection?.edges) ? selection.edges : [];
936+
const points = Array.isArray(selection?.points) ? selection.points : [];
937+
const faces = Array.isArray(selection?.faces) ? selection.faces : [];
938+
const basis = this.getSketchBasis(feature);
939+
if (!basis) return false;
940+
let added = 0;
941+
const createdIds = [];
942+
api.features.update(feature.id, sketch => {
943+
sketch.entities = Array.isArray(sketch.entities) ? sketch.entities : [];
944+
const entities = sketch.entities;
945+
const pointById = new Map(entities.filter(e => e?.id).map(e => [e.id, e]));
946+
const ensurePoint = (local, source = null) => {
947+
const existing = this.findPointByCoord(sketch, local, SKETCH_POINT_MERGE_EPS);
948+
if (existing) {
949+
existing.derived = true;
950+
existing.fixed = true;
951+
if (source) existing.source = source;
952+
return existing;
953+
}
954+
const point = {
955+
id: this.newSketchEntityId('point'),
956+
type: 'point',
957+
x: local.x || 0,
958+
y: local.y || 0,
959+
fixed: true,
960+
derived: true,
961+
source: source || null
962+
};
963+
entities.push(point);
964+
pointById.set(point.id, point);
965+
added++;
966+
createdIds.push(point.id);
967+
return point;
968+
};
969+
const hasDerivedLine = (source = null) => {
970+
if (!source?.a || !source?.b) return null;
971+
for (const line of entities) {
972+
if (line?.type !== 'line' || !line?.derived || line?.source?.type !== 'solid-edge') continue;
973+
const ls = line.source || {};
974+
if (String(ls.solid_id || '') !== String(source.solid_id || '')) continue;
975+
if (String(ls.solid_feature_id || '') !== String(source.solid_feature_id || '')) continue;
976+
if (!ls?.a || !ls?.b) continue;
977+
const sameA = Math.hypot((ls.a.x || 0) - (source.a.x || 0), (ls.a.y || 0) - (source.a.y || 0), (ls.a.z || 0) - (source.a.z || 0)) < 1e-6;
978+
const sameB = Math.hypot((ls.b.x || 0) - (source.b.x || 0), (ls.b.y || 0) - (source.b.y || 0), (ls.b.z || 0) - (source.b.z || 0)) < 1e-6;
979+
const swapA = Math.hypot((ls.a.x || 0) - (source.b.x || 0), (ls.a.y || 0) - (source.b.y || 0), (ls.a.z || 0) - (source.b.z || 0)) < 1e-6;
980+
const swapB = Math.hypot((ls.b.x || 0) - (source.a.x || 0), (ls.b.y || 0) - (source.a.y || 0), (ls.b.z || 0) - (source.a.z || 0)) < 1e-6;
981+
if ((sameA && sameB) || (swapA && swapB)) return line;
982+
}
983+
return null;
984+
};
985+
const ensureLine = (aLocal, bLocal, source = null) => {
986+
if (!aLocal || !bLocal) return null;
987+
if (source) {
988+
const existing = hasDerivedLine(source);
989+
if (existing) return existing;
990+
}
991+
// Endpoint points are often shared by multiple derived edges.
992+
// Keep them unsourced so refresh uses line sources only.
993+
const p1 = ensurePoint(aLocal, null);
994+
const p2 = ensurePoint(bLocal, null);
995+
if (!p1 || !p2 || p1.id === p2.id) return null;
996+
const line = {
997+
id: this.newSketchEntityId('line'),
998+
type: 'line',
999+
a: p1.id,
1000+
b: p2.id,
1001+
construction: false,
1002+
fixed: true,
1003+
derived: true,
1004+
source: source || null
1005+
};
1006+
entities.push(line);
1007+
added++;
1008+
createdIds.push(line.id);
1009+
return line;
1010+
};
1011+
1012+
for (const p of points) {
1013+
const local = p?.local || null;
1014+
if (!local) continue;
1015+
ensurePoint(local, p?.source || null);
1016+
}
1017+
for (const e of edges) {
1018+
ensureLine(e?.aLocal || null, e?.bLocal || null, e?.source || null);
1019+
}
1020+
for (const faceKey of faces) {
1021+
const segs = api.solids?.getFaceBoundarySegments?.(faceKey) || [];
1022+
const solidId = String(faceKey || '').split(':').slice(0, -1).join(':');
1023+
const faceIdRaw = String(faceKey || '').split(':').slice(-1)[0];
1024+
const faceId = Number(faceIdRaw);
1025+
for (const seg of segs) {
1026+
if (!seg?.a || !seg?.b) continue;
1027+
const aLocal = this.worldToSketchLocal(seg.a, basis);
1028+
const bLocal = this.worldToSketchLocal(seg.b, basis);
1029+
if (!aLocal || !bLocal) continue;
1030+
ensureLine(aLocal, bLocal, {
1031+
type: 'solid-edge',
1032+
solid_id: solidId,
1033+
solid_feature_id: null,
1034+
face_id: Number.isFinite(faceId) ? faceId : null,
1035+
edge_index: null,
1036+
a: { x: seg.a.x, y: seg.a.y, z: seg.a.z },
1037+
b: { x: seg.b.x, y: seg.b.y, z: seg.b.z }
1038+
});
1039+
}
1040+
}
1041+
}, {
1042+
opType: 'feature.update',
1043+
payload: {
1044+
field: 'entities.add',
1045+
entity: 'derived-batch',
1046+
counts: { edges: edges.length, points: points.length, faces: faces.length }
1047+
}
1048+
});
1049+
if (!added) return false;
1050+
this.selectedSketchEntities.clear();
1051+
this.selectedSketchArcCenters?.clear?.();
1052+
for (const id of createdIds) this.selectedSketchEntities.add(id);
1053+
return true;
1054+
}
1055+
9331056
function refreshDerivedSketchGeometry(feature) {
9341057
if (!feature || feature.type !== 'sketch') return false;
9351058
const entities = Array.isArray(feature.entities) ? feature.entities : [];
@@ -1003,5 +1126,6 @@ export {
10031126
convertArcToCircleInSketch,
10041127
createDerivedSketchPoint,
10051128
createDerivedSketchLine,
1129+
deriveSelectionsAtomic,
10061130
refreshDerivedSketchGeometry
10071131
};

src/void/sketch/geometry.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ function resolveDerivedEdgeCandidate(event, intersections, feature) {
203203
const faceKey = faceHit?.key || null;
204204
const frontSolidId = faceHit?.solidId || null;
205205
if (!faceKey || !frontSolidId) return null;
206+
const faceIdRaw = String(faceKey).split(':').slice(-1)[0];
207+
const faceId = Number(faceIdRaw);
206208
const facePoint = faceHit?.intersection?.point || null;
207209
const toSketchScreen = local => {
208210
const world = this.sketchLocalToWorld(local, basis);
@@ -274,6 +276,7 @@ function resolveDerivedEdgeCandidate(event, intersections, feature) {
274276
solidId: best.solidId,
275277
solidFeatureId: solid?.source?.feature_id || null,
276278
index: best.segIndex,
279+
segDist: bestSegDist,
277280
aLocal: best.aLocal,
278281
bLocal: best.bLocal,
279282
midLocal: best.midLocal,
@@ -285,6 +288,7 @@ function resolveDerivedEdgeCandidate(event, intersections, feature) {
285288
type: 'solid-edge',
286289
solid_id: best.solidId,
287290
solid_feature_id: solid?.source?.feature_id || null,
291+
face_id: Number.isFinite(faceId) ? faceId : null,
288292
edge_index: best.segIndex,
289293
a: { x: best.a.x, y: best.a.y, z: best.a.z },
290294
b: { x: best.b.x, y: best.b.y, z: best.b.z }

src/void/sketch/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ function createDerivedSketchLine(feature, candidate) {
108108
return sketchCreate.createDerivedSketchLine.call(this, feature, candidate);
109109
}
110110

111+
function deriveSelectionsAtomic(feature, selection) {
112+
return sketchCreate.deriveSelectionsAtomic.call(this, feature, selection);
113+
}
114+
111115
function refreshDerivedSketchGeometry(feature) {
112116
return sketchCreate.refreshDerivedSketchGeometry.call(this, feature);
113117
}
@@ -369,6 +373,7 @@ export {
369373
createSketchLine,
370374
createDerivedSketchPoint,
371375
createDerivedSketchLine,
376+
deriveSelectionsAtomic,
372377
refreshDerivedSketchGeometry,
373378
createSketchPolygonFromSelectedCircle,
374379
deleteSelectedSketchEntities,

src/void/sketch/pointer.js

Lines changed: 45 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -281,16 +281,50 @@ function handleSketchMouseUp(event, intersections) {
281281
} else {
282282
const derived = this.hoveredDerivedCandidate || this.resolveDerivedEdgeCandidate(event, intersections, feature);
283283
if (derived?.aLocal && derived?.bLocal) {
284-
const localPoint = derived?.hoverPoint?.local || derived?.midLocal || null;
285-
if (localPoint) {
286-
this.createDerivedSketchPoint(feature, localPoint, {
287-
...(derived.source || {}),
288-
point_kind: derived?.hoverPoint?.kind || 'mid'
289-
});
284+
const multi = !!(event?.ctrlKey || event?.metaKey || event?.shiftKey);
285+
if (!multi) {
286+
this.selectedDerivedSelections?.clear?.();
287+
this.selectedSolidFaceKeys?.clear?.();
288+
api.solids?.clearFaceSelection?.();
289+
}
290+
const pointKind = derived?.hoverPoint?.kind || null;
291+
if (pointKind && derived?.hoverPoint?.local) {
292+
const key = `point:${derived?.solidId || ''}:${derived?.index ?? -1}:${pointKind}`;
293+
if (this.selectedDerivedSelections?.has?.(key)) {
294+
this.selectedDerivedSelections.delete(key);
295+
} else {
296+
this.selectedDerivedSelections?.set?.(key, {
297+
type: 'point',
298+
local: derived.hoverPoint.local,
299+
source: {
300+
...(derived.source || {}),
301+
point_kind: pointKind || 'mid'
302+
}
303+
});
304+
}
290305
} else {
291-
this.createDerivedSketchLine(feature, derived);
306+
const key = `edge:${derived?.solidId || ''}:${derived?.index ?? -1}`;
307+
if (this.selectedDerivedSelections?.has?.(key)) {
308+
this.selectedDerivedSelections.delete(key);
309+
} else {
310+
this.selectedDerivedSelections?.set?.(key, {
311+
type: 'edge',
312+
aLocal: derived.aLocal,
313+
bLocal: derived.bLocal,
314+
source: derived.source || null
315+
});
316+
}
317+
}
318+
this.updateSketchInteractionVisuals();
319+
return true;
320+
}
321+
if (this.hoveredSolidFaceKey) {
322+
const multi = !!(event?.ctrlKey || event?.metaKey || event?.shiftKey);
323+
if (!multi) {
324+
this.selectedDerivedSelections?.clear?.();
292325
}
293-
this.hoveredDerivedCandidate = null;
326+
const selected = api.solids?.toggleSelectedFace?.(this.hoveredSolidFaceKey, multi) || [];
327+
this.selectedSolidFaceKeys = new Set(selected);
294328
this.updateSketchInteractionVisuals();
295329
return true;
296330
}
@@ -301,6 +335,9 @@ function handleSketchMouseUp(event, intersections) {
301335
}
302336
this.selectedSketchEntities.clear();
303337
this.selectedSketchArcCenters?.clear?.();
338+
this.selectedDerivedSelections?.clear?.();
339+
this.selectedSolidFaceKeys?.clear?.();
340+
api.solids?.clearFaceSelection?.();
304341
}
305342
this.updateSketchInteractionVisuals();
306343
return true;

0 commit comments

Comments
 (0)