Skip to content

Commit e97c869

Browse files
committed
Resolve auto-hole edge selections
Point-and-click selection can land on either a generated auto-hole region segment or the original sketch segment that produced it. The generated segment has the sweep and adjacency data needed for edge tagging, while the original sketch segment may not. Teach artifact graph selection helpers to resolve inner region paths through outerPathId, and to follow originalSegId mappings from source sketch segments to generated region segments for sweep and common-face lookup. Cover the direct inner-path selection case plus source-segment sweep and common-face resolution.
1 parent da18da4 commit e97c869

2 files changed

Lines changed: 266 additions & 19 deletions

File tree

src/lang/std/artifactGraph.test.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import {
22
type Artifact,
33
coerceSelectionsToBody,
44
getBodiesFromArtifactGraph,
5+
getCommonFacesForEdge,
56
getSketchBlockForArtifact,
67
getSweepArtifactFromSelection,
78
isFaceFromLegacySketch,
@@ -10,6 +11,62 @@ import type { ArtifactGraph, PathToNode } from '@src/lang/wasm'
1011
import type { Selection, Selections } from '@src/machines/modelingSharedTypes'
1112
import { describe, expect, it } from 'vitest'
1213

14+
const codeRef = {
15+
range: [0, 0, 0] as [number, number, number],
16+
pathToNode: [],
17+
nodePath: { steps: [] },
18+
}
19+
20+
function addArtifacts(artifactGraph: ArtifactGraph, artifacts: Artifact[]) {
21+
for (const artifact of artifacts) {
22+
artifactGraph.set(artifact.id, artifact)
23+
}
24+
}
25+
26+
function pathArtifact(
27+
overrides: Partial<Extract<Artifact, { type: 'path' }>> &
28+
Pick<Extract<Artifact, { type: 'path' }>, 'id'>
29+
): Extract<Artifact, { type: 'path' }> {
30+
return {
31+
type: 'path',
32+
subType: 'region',
33+
codeRef,
34+
planeId: 'plane-1',
35+
segIds: [],
36+
trajectorySweepId: null,
37+
consumed: true,
38+
...overrides,
39+
}
40+
}
41+
42+
function segmentArtifact(
43+
overrides: Partial<Extract<Artifact, { type: 'segment' }>> &
44+
Pick<Extract<Artifact, { type: 'segment' }>, 'id' | 'pathId'>
45+
): Extract<Artifact, { type: 'segment' }> {
46+
return {
47+
type: 'segment',
48+
edgeIds: [],
49+
commonSurfaceIds: [],
50+
codeRef,
51+
...overrides,
52+
}
53+
}
54+
55+
function sweepArtifact(pathId: string): Extract<Artifact, { type: 'sweep' }> {
56+
return {
57+
type: 'sweep',
58+
id: 'sweep-1',
59+
codeRef,
60+
pathId,
61+
subType: 'extrusion',
62+
surfaceIds: [],
63+
edgeIds: [],
64+
method: 'merge',
65+
trajectoryId: null,
66+
consumed: false,
67+
}
68+
}
69+
1370
describe('getSweepArtifactFromSelection', () => {
1471
it('should return sweep from edgeCut -> segment selection', () => {
1572
const artifactGraph: ArtifactGraph = new Map()
@@ -150,6 +207,135 @@ describe('getSweepArtifactFromSelection', () => {
150207
expect(result.id).toBe('sweep-1')
151208
}
152209
})
210+
211+
it('should return sweep from segment on inner region path', () => {
212+
const artifactGraph: ArtifactGraph = new Map()
213+
const segment = segmentArtifact({
214+
id: 'inner-segment',
215+
pathId: 'inner-path',
216+
})
217+
addArtifacts(artifactGraph, [
218+
pathArtifact({
219+
id: 'outer-path',
220+
segIds: ['outer-segment'],
221+
sweepId: 'sweep-1',
222+
innerPathId: 'inner-path',
223+
}),
224+
pathArtifact({
225+
id: 'inner-path',
226+
segIds: ['inner-segment'],
227+
outerPathId: 'outer-path',
228+
}),
229+
sweepArtifact('outer-path'),
230+
segment,
231+
])
232+
233+
const selection: Selection = {
234+
artifact: segment,
235+
codeRef: { range: [0, 0, 0], pathToNode: [] },
236+
}
237+
238+
const result = getSweepArtifactFromSelection(selection, artifactGraph)
239+
240+
expect(result).not.toBeInstanceOf(Error)
241+
if (!(result instanceof Error)) {
242+
expect(result.id).toBe('sweep-1')
243+
}
244+
})
245+
246+
it('should return sweep from source segment mapped to an inner region path segment', () => {
247+
const artifactGraph: ArtifactGraph = new Map()
248+
const sourceSegment = segmentArtifact({
249+
id: 'source-segment',
250+
pathId: 'source-path',
251+
})
252+
addArtifacts(artifactGraph, [
253+
pathArtifact({
254+
id: 'source-path',
255+
subType: 'sketch',
256+
segIds: ['source-segment'],
257+
}),
258+
sourceSegment,
259+
pathArtifact({
260+
id: 'outer-path',
261+
segIds: ['outer-segment'],
262+
sweepId: 'sweep-1',
263+
originPathId: 'source-path',
264+
innerPathId: 'inner-path',
265+
}),
266+
pathArtifact({
267+
id: 'inner-path',
268+
segIds: ['inner-segment'],
269+
originPathId: 'source-path',
270+
outerPathId: 'outer-path',
271+
}),
272+
sweepArtifact('outer-path'),
273+
segmentArtifact({
274+
id: 'inner-segment',
275+
pathId: 'inner-path',
276+
originalSegId: 'source-segment',
277+
}),
278+
])
279+
280+
const result = getSweepArtifactFromSelection(
281+
{
282+
artifact: sourceSegment,
283+
codeRef: { range: [0, 0, 0], pathToNode: [] },
284+
},
285+
artifactGraph
286+
)
287+
288+
expect(result).not.toBeInstanceOf(Error)
289+
if (!(result instanceof Error)) {
290+
expect(result.id).toBe('sweep-1')
291+
}
292+
})
293+
})
294+
295+
describe('getCommonFacesForEdge', () => {
296+
it('should return common faces from a generated region segment mapped to a source segment', () => {
297+
const artifactGraph: ArtifactGraph = new Map()
298+
const sourceSegment = segmentArtifact({
299+
id: 'source-segment',
300+
pathId: 'source-path',
301+
})
302+
addArtifacts(artifactGraph, [
303+
sourceSegment,
304+
segmentArtifact({
305+
id: 'inner-segment',
306+
pathId: 'inner-path',
307+
originalSegId: 'source-segment',
308+
commonSurfaceIds: ['wall-1', 'cap-1'],
309+
}),
310+
{
311+
type: 'wall',
312+
id: 'wall-1',
313+
segId: 'inner-segment',
314+
sweepId: 'sweep-1',
315+
pathIds: [],
316+
edgeCutEdgeIds: [],
317+
cmdId: 'cmd-1',
318+
faceCodeRef: codeRef,
319+
},
320+
{
321+
type: 'cap',
322+
id: 'cap-1',
323+
subType: 'end',
324+
sweepId: 'sweep-1',
325+
pathIds: [],
326+
edgeCutEdgeIds: [],
327+
cmdId: 'cmd-1',
328+
faceCodeRef: codeRef,
329+
},
330+
])
331+
332+
const result = getCommonFacesForEdge(sourceSegment, artifactGraph)
333+
334+
expect(result).not.toBeInstanceOf(Error)
335+
if (!(result instanceof Error)) {
336+
expect(result.map(({ id }) => id).sort()).toEqual(['cap-1', 'wall-1'])
337+
}
338+
})
153339
})
154340

155341
describe('coerceSelectionsToBody', () => {

src/lang/std/artifactGraph.ts

Lines changed: 80 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,59 @@ export function getArtifactOfTypes<T extends Artifact['type'][]>(
137137
return artifact as Extract<Artifact, { type: T[number] }>
138138
}
139139

140+
function getSweepFromPath(
141+
path: PathArtifact,
142+
artifactGraph: ArtifactGraph
143+
): SweepArtifact | Error {
144+
if (path.sweepId) {
145+
return getArtifactOfTypes(
146+
{ key: path.sweepId, types: ['sweep'] },
147+
artifactGraph
148+
)
149+
}
150+
151+
if (path.outerPathId) {
152+
const outerPath = getArtifactOfTypes(
153+
{ key: path.outerPathId, types: ['path'] },
154+
artifactGraph
155+
)
156+
if (err(outerPath)) return outerPath
157+
return getSweepFromPath(outerPath, artifactGraph)
158+
}
159+
160+
return new Error('Path does not have a sweepId')
161+
}
162+
163+
function getMappedRegionSegments(
164+
segment: SegmentArtifact,
165+
artifactGraph: ArtifactGraph
166+
): SegmentArtifact[] {
167+
return [...artifactGraph.values()].filter(
168+
(artifact): artifact is SegmentArtifact =>
169+
artifact.type === 'segment' &&
170+
artifact.originalSegId === segment.id &&
171+
artifact.id !== segment.id
172+
)
173+
}
174+
175+
function getSweepFromMappedRegionSegment(
176+
segment: SegmentArtifact,
177+
artifactGraph: ArtifactGraph
178+
): SweepArtifact | Error {
179+
for (const mappedSegment of getMappedRegionSegments(segment, artifactGraph)) {
180+
const path = getArtifactOfTypes(
181+
{ key: mappedSegment.pathId, types: ['path'] },
182+
artifactGraph
183+
)
184+
if (path instanceof Error) continue
185+
186+
const sweep = getSweepFromPath(path, artifactGraph)
187+
if (!(sweep instanceof Error)) return sweep
188+
}
189+
190+
return new Error('Path does not have a sweepId')
191+
}
192+
140193
export function getPatternArtifactForCopyId(
141194
id: ArtifactId,
142195
artifactGraph: ArtifactGraph
@@ -333,11 +386,7 @@ export function getSweepFromSuspectedSweepSurface(
333386
artifactGraph
334387
)
335388
if (err(path)) return path
336-
if (!path.sweepId) return new Error('Path does not have a sweepId')
337-
return getArtifactOfTypes(
338-
{ key: path.sweepId, types: ['sweep'] },
339-
artifactGraph
340-
)
389+
return getSweepFromPath(path, artifactGraph)
341390
}
342391
return getArtifactOfTypes(
343392
{ key: segOrEdge.sweepId, types: ['sweep'] },
@@ -349,13 +398,21 @@ export function getCommonFacesForEdge(
349398
artifact: SweepEdge | SegmentArtifact,
350399
artifactGraph: ArtifactGraph
351400
): Extract<Artifact, { type: 'wall' | 'cap' }>[] | Error {
352-
const faces = getArtifactsOfTypes(
353-
{ keys: artifact.commonSurfaceIds, types: ['wall', 'cap'] },
354-
artifactGraph
355-
)
356-
if (err(faces)) return faces
357-
if (faces.size === 0) return new Error('No common face found')
358-
return [...faces.values()]
401+
const edgeArtifacts =
402+
artifact.type === 'segment'
403+
? [artifact, ...getMappedRegionSegments(artifact, artifactGraph)]
404+
: [artifact]
405+
406+
for (const edgeArtifact of edgeArtifacts) {
407+
const faces = getArtifactsOfTypes(
408+
{ keys: edgeArtifact.commonSurfaceIds, types: ['wall', 'cap'] },
409+
artifactGraph
410+
)
411+
if (err(faces)) return faces
412+
if (faces.size > 0) return [...faces.values()]
413+
}
414+
415+
return new Error('No common face found')
359416
}
360417

361418
export function getSweepArtifactFromSelection(
@@ -376,13 +433,17 @@ export function getSweepArtifactFromSelection(
376433
artifactGraph
377434
)
378435
if (err(_pathArtifact)) return _pathArtifact
379-
if (!_pathArtifact.sweepId) return new Error('Path does not have a sweepId')
380-
const _artifact = getArtifactOfTypes(
381-
{ key: _pathArtifact.sweepId, types: ['sweep'] },
382-
artifactGraph
383-
)
384-
if (err(_artifact)) return _artifact
385-
sweepArtifact = _artifact
436+
const _artifact = getSweepFromPath(_pathArtifact, artifactGraph)
437+
if (_artifact instanceof Error) {
438+
const mappedSweep = getSweepFromMappedRegionSegment(
439+
selection.artifact,
440+
artifactGraph
441+
)
442+
if (mappedSweep instanceof Error) return _artifact
443+
sweepArtifact = mappedSweep
444+
} else {
445+
sweepArtifact = _artifact
446+
}
386447
} else if (
387448
selection.artifact?.type === 'cap' ||
388449
selection.artifact?.type === 'wall'

0 commit comments

Comments
 (0)