Selections Refactor (#4381)
* selection stuff
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)
* trigger CI
* fix bugs
* some edge cut stuff
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)
* trigger CI
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* fix sketch mode issues
* fix more tests, selection in sketch related
* more test fixing
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* Trigger ci
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* Trigger ci
* more sketch mode selection fixes
* fix unit tests
* rename function
* remove .only
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* lint
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* fix bad pathToNode issue
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)
* fix sketch on face
* migrate a more selections types
* migrate a more selections types
* fix code selection of fillets
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* migrate a more selections types
* fix bad path to node, looks like a race
* migrate a more selections types
* migrate a more selections types
* fix cmd bar selections
* fix cmd bar selections
* fix display issues
* migrate a more selections types
* Revert "migrate a more selections types"
This reverts commit 0d0e453bbb
.
* migrate a more selections types
* clean up1
* clean up 2
* fix types after main merge
* review tweaks
* fix wall selection bug
* Update src/lang/std/engineConnection.ts
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
* add franks TODO comment
* fix type after main merge, plus a touch of clean up
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
This commit is contained in:
@ -5,7 +5,7 @@ import {
|
||||
kclManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { CallExpression, SourceRange, Expr, parse } from 'lang/wasm'
|
||||
import { CallExpression, SourceRange, Expr } from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { isNonNullable, uuidv4 } from 'lib/utils'
|
||||
import { EditorSelection, SelectionRange } from '@codemirror/state'
|
||||
@ -15,6 +15,7 @@ import { Program } from 'lang/wasm'
|
||||
import {
|
||||
doesPipeHaveCallExp,
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
hasSketchPipeBeenExtruded,
|
||||
isSingleCursorInPipe,
|
||||
} from 'lang/queryAst'
|
||||
@ -28,12 +29,15 @@ import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||
import { PathToNodeMap } from 'lang/std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
Artifact,
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getCapCodeRef,
|
||||
getSweepEdgeCodeRef,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
CodeRef,
|
||||
getCodeRefsByArtifactId,
|
||||
ArtifactId,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
@ -43,7 +47,8 @@ export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
|
||||
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||
|
||||
export type Selection =
|
||||
/** @deprecated Use {@link Artifact} instead. */
|
||||
type Selection__old =
|
||||
| {
|
||||
type:
|
||||
| 'default'
|
||||
@ -67,9 +72,88 @@ export type Selection =
|
||||
// TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
|
||||
secondaryRange: SourceRange
|
||||
}
|
||||
export type Selections = {
|
||||
/** @deprecated Use {@link Selection} instead. */
|
||||
export type Selections__old = {
|
||||
otherSelections: Axis[]
|
||||
codeBasedSelections: Selection[]
|
||||
codeBasedSelections: Selection__old[]
|
||||
}
|
||||
export interface Selection {
|
||||
artifact?: Artifact
|
||||
codeRef: CodeRef
|
||||
}
|
||||
export type Selections = {
|
||||
otherSelections: Array<Axis>
|
||||
graphSelections: Array<Selection>
|
||||
}
|
||||
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
function convertSelectionToOld(selection: Selection): Selection__old | null {
|
||||
// return {} as Selection__old
|
||||
// TODO implementation
|
||||
const _artifact = selection.artifact
|
||||
if (_artifact?.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'solid2D' }
|
||||
}
|
||||
if (_artifact?.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
range: codeRef.range,
|
||||
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
}
|
||||
}
|
||||
if (_artifact?.type === 'wall') {
|
||||
const codeRef = getWallCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'extrude-wall' }
|
||||
}
|
||||
if (_artifact?.type === 'segment' || _artifact?.type === 'path') {
|
||||
return { range: _artifact.codeRef.range, type: 'default' }
|
||||
}
|
||||
if (_artifact?.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return { range: codeRef.range, type: 'adjacent-edge' }
|
||||
}
|
||||
return { range: codeRef.range, type: 'edge' }
|
||||
}
|
||||
if (_artifact?.type === 'edgeCut') {
|
||||
const codeRef = _artifact.codeRef
|
||||
return { range: codeRef.range, type: 'default' }
|
||||
}
|
||||
if (selection?.codeRef?.range) {
|
||||
return { range: selection.codeRef.range, type: 'default' }
|
||||
}
|
||||
return null
|
||||
}
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
export function convertSelectionsToOld(selection: Selections): Selections__old {
|
||||
const selections: Selection__old[] = []
|
||||
for (const artifact of selection.graphSelections) {
|
||||
const converted = convertSelectionToOld(artifact)
|
||||
if (converted) selections.push(converted)
|
||||
}
|
||||
const selectionsOld: Selections__old = {
|
||||
otherSelections: selection.otherSelections,
|
||||
codeBasedSelections: selections,
|
||||
}
|
||||
return selectionsOld
|
||||
}
|
||||
|
||||
export async function getEventForSelectWithPoint({
|
||||
@ -94,127 +178,18 @@ export async function getEventForSelectWithPoint({
|
||||
}
|
||||
}
|
||||
let _artifact = engineCommandManager.artifactGraph.get(data.entity_id)
|
||||
if (!_artifact)
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: { selectionType: 'singleCodeCursor' },
|
||||
}
|
||||
if (_artifact.type === 'solid2D') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'solid2D' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
data.entity_id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (_artifact && codeRefs) {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
range: codeRef.range,
|
||||
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'wall') {
|
||||
const codeRef = getWallCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'extrude-wall' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'segment' || _artifact.type === 'path') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: _artifact.codeRef.range, type: 'default' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'adjacent-edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'edgeCut') {
|
||||
const consumedEdge = getArtifactOfTypes(
|
||||
{ key: _artifact.consumedEdgeId, types: ['segment', 'sweepEdge'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(consumedEdge))
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: _artifact.codeRef.range, type: 'default' },
|
||||
},
|
||||
}
|
||||
if (consumedEdge.type === 'segment') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
range: _artifact.codeRef.range,
|
||||
type: 'base-edgeCut',
|
||||
secondaryRange: consumedEdge.codeRef.range,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
const segment = getArtifactOfTypes(
|
||||
{ key: consumedEdge.segId, types: ['segment'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(segment)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
range: _artifact.codeRef.range,
|
||||
type:
|
||||
consumedEdge.subType === 'adjacent'
|
||||
? 'adjacent-edgeCut'
|
||||
: 'opposite-edgeCut',
|
||||
secondaryRange: segment.codeRef.range,
|
||||
artifact: _artifact,
|
||||
codeRef: codeRefs[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -237,28 +212,55 @@ export function getEventForSegmentSelection(
|
||||
},
|
||||
}
|
||||
}
|
||||
const pathToNode = group?.userData?.pathToNode
|
||||
if (!pathToNode) return null
|
||||
// previous drags don't update ast for efficiency reasons
|
||||
// So we want to make sure we have and updated ast with
|
||||
// accurate source ranges
|
||||
const updatedAst = parse(codeManager.code)
|
||||
if (err(updatedAst)) return null
|
||||
// id does not match up with the artifact graph when in sketch mode, because mock executions
|
||||
// do not update the artifact graph, therefore we match up the pathToNode instead
|
||||
// we can reliably use `type === 'segment'` since it's in sketch mode and we're concerned with segments
|
||||
const segWithMatchingPathToNode__Id = [
|
||||
...engineCommandManager.artifactGraph,
|
||||
].find((entry) => {
|
||||
return (
|
||||
entry[1].type === 'segment' &&
|
||||
JSON.stringify(entry[1].codeRef.pathToNode) ===
|
||||
JSON.stringify(group?.userData?.pathToNode)
|
||||
)
|
||||
})?.[0]
|
||||
|
||||
const nodeMeta = getNodeFromPath<Node<CallExpression>>(
|
||||
updatedAst,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
const id = segWithMatchingPathToNode__Id
|
||||
|
||||
if (!id && group) {
|
||||
const node = getNodeFromPath<Expr>(
|
||||
kclManager.ast,
|
||||
group.userData.pathToNode
|
||||
)
|
||||
if (err(node)) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: {
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end],
|
||||
pathToNode: group.userData.pathToNode,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
if (!id || !group) return null
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(nodeMeta)) return null
|
||||
|
||||
const node = nodeMeta.node
|
||||
const range: SourceRange = [node.start, node.end]
|
||||
if (!artifact || !codeRefs) return null
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range, type: 'default' },
|
||||
selection: {
|
||||
artifact,
|
||||
codeRef: codeRefs[0],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -274,13 +276,24 @@ export function handleSelectionBatch({
|
||||
updateSceneObjectColors: () => void
|
||||
} {
|
||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||
const selectionToEngine: SelectionToEngine[] = []
|
||||
|
||||
selections.graphSelections.forEach(({ artifact }) => {
|
||||
artifact?.id &&
|
||||
selectionToEngine.push({
|
||||
type: 'default',
|
||||
id: artifact?.id,
|
||||
range: getCodeRefsByArtifactId(
|
||||
artifact.id,
|
||||
engineCommandManager.artifactGraph
|
||||
)?.[0].range || [0, 0],
|
||||
})
|
||||
})
|
||||
const engineEvents: Models['WebSocketRequest_type'][] =
|
||||
resetAndSetEngineEntitySelectionCmds(
|
||||
codeToIdSelections(selections.codeBasedSelections)
|
||||
)
|
||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||
if (range?.[1]) {
|
||||
ranges.push(EditorSelection.cursor(range[1]))
|
||||
resetAndSetEngineEntitySelectionCmds(selectionToEngine)
|
||||
selections.graphSelections.forEach(({ codeRef }) => {
|
||||
if (codeRef.range?.[1]) {
|
||||
ranges.push(EditorSelection.cursor(codeRef.range[1]))
|
||||
}
|
||||
})
|
||||
if (ranges.length)
|
||||
@ -288,11 +301,11 @@ export function handleSelectionBatch({
|
||||
engineEvents,
|
||||
codeMirrorSelection: EditorSelection.create(
|
||||
ranges,
|
||||
selections.codeBasedSelections.length - 1
|
||||
selections.graphSelections.length - 1
|
||||
),
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.codeBasedSelections),
|
||||
updateSceneObjectColors(selections.graphSelections),
|
||||
}
|
||||
|
||||
return {
|
||||
@ -303,43 +316,75 @@ export function handleSelectionBatch({
|
||||
engineEvents,
|
||||
otherSelections: selections.otherSelections,
|
||||
updateSceneObjectColors: () =>
|
||||
updateSceneObjectColors(selections.codeBasedSelections),
|
||||
updateSceneObjectColors(selections.graphSelections),
|
||||
}
|
||||
}
|
||||
|
||||
type SelectionToEngine = { type: Selection['type']; id: string }
|
||||
type SelectionToEngine = {
|
||||
type: Selection__old['type']
|
||||
id?: string
|
||||
range: SourceRange
|
||||
}
|
||||
|
||||
export function processCodeMirrorRanges({
|
||||
codeMirrorRanges,
|
||||
selectionRanges,
|
||||
isShiftDown,
|
||||
ast,
|
||||
}: {
|
||||
codeMirrorRanges: readonly SelectionRange[]
|
||||
selectionRanges: Selections
|
||||
isShiftDown: boolean
|
||||
ast: Program
|
||||
}): null | {
|
||||
modelingEvent: ModelingMachineEvent
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
} {
|
||||
const isChange =
|
||||
codeMirrorRanges.length !== selectionRanges.codeBasedSelections.length ||
|
||||
codeMirrorRanges.length !== selectionRanges?.graphSelections?.length ||
|
||||
codeMirrorRanges.some(({ from, to }, i) => {
|
||||
return (
|
||||
from !== selectionRanges.codeBasedSelections[i].range[0] ||
|
||||
to !== selectionRanges.codeBasedSelections[i].range[1]
|
||||
from !== selectionRanges.graphSelections[i]?.codeRef?.range[0] ||
|
||||
to !== selectionRanges.graphSelections[i]?.codeRef?.range[1]
|
||||
)
|
||||
})
|
||||
|
||||
if (!isChange) return null
|
||||
const codeBasedSelections: Selections['codeBasedSelections'] =
|
||||
const codeBasedSelections: Selections['graphSelections'] =
|
||||
codeMirrorRanges.map(({ from, to }) => {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, [from, to])
|
||||
return {
|
||||
type: 'default',
|
||||
range: [from, to],
|
||||
codeRef: {
|
||||
range: [from, to],
|
||||
pathToNode,
|
||||
},
|
||||
}
|
||||
})
|
||||
const idBasedSelections: SelectionToEngine[] =
|
||||
codeToIdSelections(codeBasedSelections)
|
||||
const selections: Selection[] = []
|
||||
for (const { id, range } of idBasedSelections) {
|
||||
if (!id) {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||
selections.push({
|
||||
codeRef: {
|
||||
range,
|
||||
pathToNode,
|
||||
},
|
||||
})
|
||||
continue
|
||||
}
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (artifact && codeRefs) {
|
||||
selections.push({ artifact, codeRef: codeRefs[0] })
|
||||
} else if (codeRefs) {
|
||||
selections.push({ codeRef: codeRefs[0] })
|
||||
}
|
||||
}
|
||||
|
||||
if (!selectionRanges) return null
|
||||
updateSceneObjectColors(codeBasedSelections)
|
||||
@ -350,11 +395,13 @@ export function processCodeMirrorRanges({
|
||||
selectionType: 'mirrorCodeMirrorSelections',
|
||||
selection: {
|
||||
otherSelections: isShiftDown ? selectionRanges.otherSelections : [],
|
||||
codeBasedSelections,
|
||||
graphSelections: selections,
|
||||
},
|
||||
},
|
||||
},
|
||||
engineEvents: resetAndSetEngineEntitySelectionCmds(idBasedSelections),
|
||||
engineEvents: resetAndSetEngineEntitySelectionCmds(
|
||||
idBasedSelections.filter(({ id }) => !!id)
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -371,7 +418,7 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
if (err(nodeMeta)) return
|
||||
const node = nodeMeta.node
|
||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||
return isOverlap(selection.range, [node.start, node.end])
|
||||
return isOverlap(selection?.codeRef?.range, [node.start, node.end])
|
||||
})
|
||||
|
||||
const color = groupHasCursor
|
||||
@ -406,7 +453,7 @@ function resetAndSetEngineEntitySelectionCmds(
|
||||
type: 'modeling_cmd_req',
|
||||
cmd: {
|
||||
type: 'select_add',
|
||||
entities: selections.map(({ id }) => id),
|
||||
entities: selections.map(({ id }) => id).filter(isNonNullable),
|
||||
},
|
||||
cmd_id: uuidv4(),
|
||||
},
|
||||
@ -426,14 +473,14 @@ export function isSelectionLastLine(
|
||||
code: string,
|
||||
i = 0
|
||||
) {
|
||||
return selectionRanges.codeBasedSelections[i].range[1] === code.length
|
||||
return selectionRanges.graphSelections[i]?.codeRef?.range[1] === code.length
|
||||
}
|
||||
|
||||
export function isRangeBetweenCharacters(selectionRanges: Selections) {
|
||||
return (
|
||||
selectionRanges.codeBasedSelections.length === 1 &&
|
||||
selectionRanges.codeBasedSelections[0].range[0] === 0 &&
|
||||
selectionRanges.codeBasedSelections[0].range[1] === 0
|
||||
selectionRanges.graphSelections.length === 1 &&
|
||||
selectionRanges.graphSelections[0]?.codeRef?.range[0] === 0 &&
|
||||
selectionRanges.graphSelections[0]?.codeRef?.range[1] === 0
|
||||
)
|
||||
}
|
||||
|
||||
@ -444,7 +491,7 @@ export type CommonASTNode = {
|
||||
|
||||
function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
||||
return {
|
||||
selection: selectionRanges.codeBasedSelections[i],
|
||||
selection: selectionRanges.graphSelections[i],
|
||||
ast: kclManager.ast,
|
||||
}
|
||||
}
|
||||
@ -476,7 +523,7 @@ function nodeHasCircle(node: CommonASTNode) {
|
||||
}
|
||||
|
||||
export function canSweepSelection(selection: Selections) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
)
|
||||
return (
|
||||
@ -488,33 +535,8 @@ export function canSweepSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canFilletSelection(selection: Selections) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
) // TODO FILLET DUMMY PLACEHOLDER
|
||||
return (
|
||||
!!isSketchPipe(selection) &&
|
||||
commonNodes.every((n) => nodeHasClose(n)) &&
|
||||
commonNodes.every((n) => !nodeHasExtrude(n))
|
||||
)
|
||||
}
|
||||
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
const isolatedSelection = {
|
||||
...selection,
|
||||
codeBasedSelections: [selection.codeBasedSelections[i]],
|
||||
}
|
||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||
|
||||
return (
|
||||
!!isSketchPipe(isolatedSelection) &&
|
||||
(nodeHasClose(commonNode) || nodeHasCircle(commonNode)) &&
|
||||
!nodeHasExtrude(commonNode)
|
||||
)
|
||||
}
|
||||
|
||||
// This accounts for non-geometry selections under "other"
|
||||
export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
export type ResolvedSelectionType = [Artifact['type'] | 'other', number]
|
||||
|
||||
/**
|
||||
* In the future, I'd like this function to properly return the type of each selected entity based on
|
||||
@ -527,17 +549,16 @@ export function getSelectionType(
|
||||
selection?: Selections
|
||||
): ResolvedSelectionType[] {
|
||||
if (!selection) return []
|
||||
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
|
||||
const singleSelection = {
|
||||
...selection,
|
||||
codeBasedSelections: [selection.codeBasedSelections[i]],
|
||||
}
|
||||
return canExtrudeSelectionItem(singleSelection, 0)
|
||||
}).length
|
||||
|
||||
return extrudableCount === selection.codeBasedSelections.length
|
||||
? [['extrude-wall', extrudableCount]]
|
||||
: [['other', selection.codeBasedSelections.length]]
|
||||
const selectionsWithArtifacts = selection.graphSelections.filter(
|
||||
(s) => !!s.artifact
|
||||
)
|
||||
const firstSelection = selectionsWithArtifacts[0]
|
||||
const firstSelectionType = firstSelection?.artifact?.type
|
||||
if (!firstSelectionType) return []
|
||||
const selectionsWithSameType = selectionsWithArtifacts.filter(
|
||||
(s) => s.artifact?.type === firstSelection.artifact?.type
|
||||
)
|
||||
return [[firstSelectionType, selectionsWithSameType.length]]
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
@ -549,9 +570,10 @@ export function getSelectionTypeDisplayText(
|
||||
.map(
|
||||
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
||||
([type, count]) =>
|
||||
`${count} ${type.replace('extrude-wall', 'face')}${
|
||||
count > 1 ? 's' : ''
|
||||
}`
|
||||
`${count} ${type
|
||||
.replace('wall', 'face')
|
||||
.replace('solid2D', 'face')
|
||||
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
||||
)
|
||||
.join(', ')
|
||||
}
|
||||
@ -572,10 +594,14 @@ export function canSubmitSelectionArg(
|
||||
)
|
||||
}
|
||||
|
||||
function codeToIdSelections(
|
||||
codeBasedSelections: Selection[]
|
||||
export function codeToIdSelections(
|
||||
selections: Selection[]
|
||||
): SelectionToEngine[] {
|
||||
return codeBasedSelections
|
||||
const selectionsOld = convertSelectionsToOld({
|
||||
graphSelections: selections,
|
||||
otherSelections: [],
|
||||
}).codeBasedSelections
|
||||
return selectionsOld
|
||||
.flatMap((selection): null | SelectionToEngine[] => {
|
||||
const { type } = selection
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
@ -607,19 +633,26 @@ function codeToIdSelections(
|
||||
| {
|
||||
id: ArtifactId
|
||||
artifact: unknown
|
||||
selection: Selection
|
||||
selection: Selection__old
|
||||
}
|
||||
| undefined
|
||||
overlappingEntries.forEach((entry) => {
|
||||
// TODO probably need to remove much of the `type === 'xyz'` below
|
||||
if (type === 'default' && entry.artifact.type === 'segment') {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
if (type === 'solid2D' && entry.artifact.type === 'path') {
|
||||
const solid = engineCommandManager.artifactGraph.get(
|
||||
if (entry.artifact.type === 'path') {
|
||||
const artifact = engineCommandManager.artifactGraph.get(
|
||||
entry.artifact.solid2dId || ''
|
||||
)
|
||||
if (solid?.type !== 'solid2D') return
|
||||
if (artifact?.type !== 'solid2D') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
}
|
||||
if (!entry.artifact.solid2dId) {
|
||||
console.error(
|
||||
'Expected PathArtifact to have solid2dId, but none found'
|
||||
@ -627,7 +660,7 @@ function codeToIdSelections(
|
||||
return
|
||||
}
|
||||
bestCandidate = {
|
||||
artifact: solid,
|
||||
artifact: artifact,
|
||||
selection,
|
||||
id: entry.artifact.solid2dId,
|
||||
}
|
||||
@ -752,10 +785,11 @@ function codeToIdSelections(
|
||||
{
|
||||
type,
|
||||
id: bestCandidate.id,
|
||||
range: bestCandidate.selection.range,
|
||||
},
|
||||
]
|
||||
}
|
||||
return null
|
||||
return [selection]
|
||||
})
|
||||
.filter(isNonNullable)
|
||||
}
|
||||
@ -798,32 +832,58 @@ export function updateSelections(
|
||||
|
||||
const newSelections = Object.entries(pathToNodeMap)
|
||||
.map(([index, pathToNode]): Selection | undefined => {
|
||||
const previousSelection =
|
||||
prevSelectionRanges.graphSelections[Number(index)]
|
||||
const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
|
||||
if (err(nodeMeta)) return undefined
|
||||
const node = nodeMeta.node
|
||||
const selection = prevSelectionRanges.codeBasedSelections[Number(index)]
|
||||
if (
|
||||
selection?.type === 'base-edgeCut' ||
|
||||
selection?.type === 'adjacent-edgeCut' ||
|
||||
selection?.type === 'opposite-edgeCut'
|
||||
)
|
||||
return {
|
||||
range: [node.start, node.end],
|
||||
type: selection?.type,
|
||||
secondaryRange: selection?.secondaryRange,
|
||||
let artifact: Artifact | null = null
|
||||
for (const [id, a] of engineCommandManager.artifactGraph) {
|
||||
if (previousSelection?.artifact?.type === a.type) {
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (!codeRefs) continue
|
||||
if (
|
||||
JSON.stringify(codeRefs[0].pathToNode) ===
|
||||
JSON.stringify(pathToNode)
|
||||
) {
|
||||
artifact = a
|
||||
console.log('found artifact', a)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!artifact) return undefined
|
||||
return {
|
||||
range: [node.start, node.end],
|
||||
type: selection?.type,
|
||||
artifact: artifact,
|
||||
codeRef: {
|
||||
range: [node.start, node.end],
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
}
|
||||
})
|
||||
.filter((x?: Selection) => x !== undefined) as Selection[]
|
||||
|
||||
// for when there is no artifact (sketch mode since mock execute does not update artifactGraph)
|
||||
const pathToNodeBasedSelections: Selections['graphSelections'] = []
|
||||
for (const pathToNode of Object.values(pathToNodeMap)) {
|
||||
const node = getNodeFromPath<Expr>(ast, pathToNode)
|
||||
if (err(node)) return node
|
||||
pathToNodeBasedSelections.push({
|
||||
codeRef: {
|
||||
range: [node.node.start, node.node.end],
|
||||
pathToNode: pathToNode,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
codeBasedSelections:
|
||||
newSelections.length > 0
|
||||
graphSelections:
|
||||
newSelections.length >= pathToNodeBasedSelections.length
|
||||
? newSelections
|
||||
: prevSelectionRanges.codeBasedSelections,
|
||||
: pathToNodeBasedSelections,
|
||||
otherSelections: prevSelectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user