2025-04-01 23:54:26 -07:00
|
|
|
import type { SelectionRange } from '@codemirror/state'
|
|
|
|
import { EditorSelection } from '@codemirror/state'
|
|
|
|
import type { Models } from '@kittycad/lib'
|
|
|
|
import type { Object3D, Object3DEventMap } from 'three'
|
|
|
|
import { Mesh } from 'three'
|
|
|
|
|
|
|
|
import type { Node } from '@rust/kcl-lib/bindings/Node'
|
|
|
|
|
|
|
|
import {
|
|
|
|
SEGMENT_BODIES_PLUS_PROFILE_START,
|
|
|
|
getParentGroup,
|
|
|
|
} from '@src/clientSideScene/sceneConstants'
|
2025-04-02 15:10:57 -07:00
|
|
|
import { AXIS_GROUP, X_AXIS } from '@src/clientSideScene/sceneUtils'
|
2025-04-01 23:54:26 -07:00
|
|
|
import { getNodeFromPath, isSingleCursorInPipe } from '@src/lang/queryAst'
|
|
|
|
import { getNodePathFromSourceRange } from '@src/lang/queryAstNodePathUtils'
|
|
|
|
import type { Artifact, ArtifactId, CodeRef } from '@src/lang/std/artifactGraph'
|
|
|
|
import { getCodeRefsByArtifactId } from '@src/lang/std/artifactGraph'
|
|
|
|
import type { PathToNodeMap } from '@src/lang/std/sketchcombos'
|
|
|
|
import { isCursorInSketchCommandRange, topLevelRange } from '@src/lang/util'
|
|
|
|
import type {
|
|
|
|
ArtifactGraph,
|
|
|
|
CallExpressionKw,
|
|
|
|
Expr,
|
|
|
|
Program,
|
|
|
|
SourceRange,
|
|
|
|
} from '@src/lang/wasm'
|
|
|
|
import { defaultSourceRange } from '@src/lang/wasm'
|
|
|
|
import type { ArtifactEntry, ArtifactIndex } from '@src/lib/artifactIndex'
|
|
|
|
import type { CommandArgument } from '@src/lib/commandTypes'
|
|
|
|
import type { DefaultPlaneStr } from '@src/lib/planes'
|
2024-03-22 16:55:30 +11:00
|
|
|
import {
|
2025-04-01 15:31:19 -07:00
|
|
|
codeManager,
|
|
|
|
engineCommandManager,
|
|
|
|
kclManager,
|
2025-04-01 23:54:26 -07:00
|
|
|
rustContext,
|
2025-04-01 15:31:19 -07:00
|
|
|
sceneEntitiesManager,
|
2025-04-01 23:54:26 -07:00
|
|
|
} from '@src/lib/singletons'
|
|
|
|
import { err } from '@src/lib/trap'
|
2025-04-01 15:31:19 -07:00
|
|
|
import {
|
2025-04-01 23:54:26 -07:00
|
|
|
getNormalisedCoordinates,
|
2025-04-27 16:54:32 -07:00
|
|
|
isArray,
|
2025-04-01 23:54:26 -07:00
|
|
|
isNonNullable,
|
|
|
|
isOverlap,
|
|
|
|
uuidv4,
|
|
|
|
} from '@src/lib/utils'
|
2025-04-24 13:32:49 -05:00
|
|
|
import { engineStreamActor } from '@src/lib/singletons'
|
2025-04-01 23:54:26 -07:00
|
|
|
import type { ModelingMachineEvent } from '@src/machines/modelingMachine'
|
2025-04-17 21:32:13 +10:00
|
|
|
import { showUnsupportedSelectionToast } from '@src/components/ToastUnsupportedSelection'
|
2023-10-16 21:20:05 +11:00
|
|
|
|
2023-12-01 20:18:51 +11:00
|
|
|
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
|
|
|
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
|
|
|
|
2023-10-16 21:20:05 +11:00
|
|
|
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
2024-11-26 11:36:14 -05:00
|
|
|
export type DefaultPlaneSelection = {
|
|
|
|
name: DefaultPlaneStr
|
|
|
|
id: string
|
|
|
|
}
|
2023-10-16 21:20:05 +11:00
|
|
|
|
2024-11-26 11:36:14 -05:00
|
|
|
export type NonCodeSelection = Axis | DefaultPlaneSelection
|
2024-11-21 15:04:30 +11:00
|
|
|
export interface Selection {
|
|
|
|
artifact?: Artifact
|
|
|
|
codeRef: CodeRef
|
|
|
|
}
|
|
|
|
export type Selections = {
|
2024-11-26 11:36:14 -05:00
|
|
|
otherSelections: Array<NonCodeSelection>
|
2024-11-21 15:04:30 +11:00
|
|
|
graphSelections: Array<Selection>
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
export async function getEventForSelectWithPoint({
|
|
|
|
data,
|
|
|
|
}: Extract<
|
|
|
|
Models['OkModelingCmdResponse_type'],
|
|
|
|
{ type: 'select_with_point' }
|
|
|
|
>): Promise<ModelingMachineEvent | null> {
|
|
|
|
if (!data?.entity_id) {
|
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: { selectionType: 'singleCodeCursor' },
|
2024-09-17 05:38:58 +10:00
|
|
|
}
|
2024-11-21 15:04:30 +11:00
|
|
|
}
|
|
|
|
if ([X_AXIS_UUID, Y_AXIS_UUID].includes(data.entity_id)) {
|
2024-08-30 19:46:48 +10:00
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
2024-11-26 11:36:14 -05:00
|
|
|
selectionType: 'axisSelection',
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: X_AXIS_UUID === data.entity_id ? 'x-axis' : 'y-axis',
|
2024-08-30 19:46:48 +10:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-11-26 11:36:14 -05:00
|
|
|
|
|
|
|
// Check for default plane selection
|
|
|
|
const foundDefaultPlane =
|
2025-03-15 10:08:39 -07:00
|
|
|
rustContext.defaultPlanes !== null &&
|
|
|
|
Object.entries(rustContext.defaultPlanes).find(
|
2024-11-26 11:36:14 -05:00
|
|
|
([, plane]) => plane === data.entity_id
|
|
|
|
)
|
|
|
|
if (foundDefaultPlane) {
|
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'defaultPlaneSelection',
|
|
|
|
selection: {
|
|
|
|
name: foundDefaultPlane[0] as DefaultPlaneStr,
|
|
|
|
id: data.entity_id,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-29 17:25:26 -07:00
|
|
|
let _artifact = kclManager.artifactGraph.get(data.entity_id)
|
2025-04-17 21:32:13 +10:00
|
|
|
if (!_artifact) {
|
|
|
|
// if there's no artifact but there is a data.entity_id, it means we don't recognize the engine entity
|
|
|
|
// we should still return an empty singleCodeCursor to plug into the selection logic
|
|
|
|
// (i.e. if the user is holding shift they can keep selecting)
|
|
|
|
// but we should also put up a toast
|
|
|
|
// toast.error('some edges or faces are not currently selectable')
|
|
|
|
showUnsupportedSelectionToast()
|
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: { selectionType: 'singleCodeCursor' },
|
|
|
|
}
|
|
|
|
}
|
2024-11-21 15:04:30 +11:00
|
|
|
const codeRefs = getCodeRefsByArtifactId(
|
|
|
|
data.entity_id,
|
2025-03-29 17:25:26 -07:00
|
|
|
kclManager.artifactGraph
|
2024-11-21 15:04:30 +11:00
|
|
|
)
|
|
|
|
if (_artifact && codeRefs) {
|
2024-09-26 18:25:05 +10:00
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'singleCodeCursor',
|
|
|
|
selection: {
|
2024-11-21 15:04:30 +11:00
|
|
|
artifact: _artifact,
|
|
|
|
codeRef: codeRefs[0],
|
2024-09-26 18:25:05 +10:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-08-03 18:08:51 +10:00
|
|
|
return null
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
export function getEventForSegmentSelection(
|
2024-03-22 10:23:04 +11:00
|
|
|
obj: Object3D<Object3DEventMap>
|
2024-02-11 12:59:00 +11:00
|
|
|
): ModelingMachineEvent | null {
|
2024-09-13 21:14:14 +10:00
|
|
|
const group = getParentGroup(obj, SEGMENT_BODIES_PLUS_PROFILE_START)
|
2024-02-11 12:59:00 +11:00
|
|
|
const axisGroup = getParentGroup(obj, [AXIS_GROUP])
|
|
|
|
if (!group && !axisGroup) return null
|
|
|
|
if (axisGroup?.userData.type === AXIS_GROUP) {
|
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
2024-11-26 11:36:14 -05:00
|
|
|
selectionType: 'axisSelection',
|
2024-02-11 12:59:00 +11:00
|
|
|
selection: obj?.userData?.type === X_AXIS ? 'x-axis' : 'y-axis',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2024-11-21 15:04:30 +11:00
|
|
|
// 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
|
2025-03-29 17:25:26 -07:00
|
|
|
const segWithMatchingPathToNode__Id = [...kclManager.artifactGraph].find(
|
|
|
|
(entry) => {
|
|
|
|
return (
|
|
|
|
entry[1].type === 'segment' &&
|
|
|
|
JSON.stringify(entry[1].codeRef.pathToNode) ===
|
|
|
|
JSON.stringify(group?.userData?.pathToNode)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)?.[0]
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
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: {
|
2025-01-17 14:34:36 -05:00
|
|
|
range: topLevelRange(node.node.start, node.node.end),
|
2024-11-21 15:04:30 +11:00
|
|
|
pathToNode: group.userData.pathToNode,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!id || !group) return null
|
2025-03-29 17:25:26 -07:00
|
|
|
const artifact = kclManager.artifactGraph.get(id)
|
2025-02-15 00:57:04 +11:00
|
|
|
if (!artifact) return null
|
|
|
|
const node = getNodeFromPath<Expr>(kclManager.ast, group.userData.pathToNode)
|
|
|
|
if (err(node)) return null
|
2024-02-11 12:59:00 +11:00
|
|
|
return {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'singleCodeCursor',
|
2024-11-21 15:04:30 +11:00
|
|
|
selection: {
|
|
|
|
artifact,
|
2025-02-15 00:57:04 +11:00
|
|
|
codeRef: {
|
|
|
|
pathToNode: group?.userData?.pathToNode,
|
|
|
|
range: [node.node.start, node.node.end, 0],
|
|
|
|
},
|
2024-11-21 15:04:30 +11:00
|
|
|
},
|
2024-02-11 12:59:00 +11:00
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-16 21:20:05 +11:00
|
|
|
export function handleSelectionBatch({
|
|
|
|
selections,
|
|
|
|
}: {
|
|
|
|
selections: Selections
|
|
|
|
}): {
|
2024-03-22 10:23:04 +11:00
|
|
|
engineEvents: Models['WebSocketRequest_type'][]
|
|
|
|
codeMirrorSelection: EditorSelection
|
|
|
|
updateSceneObjectColors: () => void
|
2023-10-16 21:20:05 +11:00
|
|
|
} {
|
|
|
|
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
2024-11-21 15:04:30 +11:00
|
|
|
const selectionToEngine: SelectionToEngine[] = []
|
|
|
|
|
|
|
|
selections.graphSelections.forEach(({ artifact }) => {
|
|
|
|
artifact?.id &&
|
|
|
|
selectionToEngine.push({
|
|
|
|
id: artifact?.id,
|
2024-12-06 13:57:31 +13:00
|
|
|
range:
|
2025-03-29 17:25:26 -07:00
|
|
|
getCodeRefsByArtifactId(artifact.id, kclManager.artifactGraph)?.[0]
|
|
|
|
.range || defaultSourceRange(),
|
2024-11-21 15:04:30 +11:00
|
|
|
})
|
|
|
|
})
|
2024-03-22 10:23:04 +11:00
|
|
|
const engineEvents: Models['WebSocketRequest_type'][] =
|
2024-11-21 15:04:30 +11:00
|
|
|
resetAndSetEngineEntitySelectionCmds(selectionToEngine)
|
|
|
|
selections.graphSelections.forEach(({ codeRef }) => {
|
|
|
|
if (codeRef.range?.[1]) {
|
2025-01-06 17:18:58 -08:00
|
|
|
const safeEnd = Math.min(codeRef.range[1], codeManager.code.length)
|
|
|
|
ranges.push(EditorSelection.cursor(safeEnd))
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
})
|
|
|
|
if (ranges.length)
|
|
|
|
return {
|
2024-03-22 10:23:04 +11:00
|
|
|
engineEvents,
|
2023-10-16 21:20:05 +11:00
|
|
|
codeMirrorSelection: EditorSelection.create(
|
|
|
|
ranges,
|
2024-11-21 15:04:30 +11:00
|
|
|
selections.graphSelections.length - 1
|
2023-10-16 21:20:05 +11:00
|
|
|
),
|
2024-03-22 10:23:04 +11:00
|
|
|
updateSceneObjectColors: () =>
|
2024-11-21 15:04:30 +11:00
|
|
|
updateSceneObjectColors(selections.graphSelections),
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2024-03-22 10:23:04 +11:00
|
|
|
codeMirrorSelection: EditorSelection.create(
|
2024-04-17 20:18:07 -07:00
|
|
|
[EditorSelection.cursor(codeManager.code.length)],
|
2024-03-22 10:23:04 +11:00
|
|
|
0
|
|
|
|
),
|
|
|
|
engineEvents,
|
|
|
|
updateSceneObjectColors: () =>
|
2024-11-21 15:04:30 +11:00
|
|
|
updateSceneObjectColors(selections.graphSelections),
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
type SelectionToEngine = {
|
|
|
|
id?: string
|
|
|
|
range: SourceRange
|
|
|
|
}
|
2023-10-16 21:20:05 +11:00
|
|
|
|
|
|
|
export function processCodeMirrorRanges({
|
|
|
|
codeMirrorRanges,
|
|
|
|
selectionRanges,
|
2023-12-01 20:18:51 +11:00
|
|
|
isShiftDown,
|
2024-11-21 15:04:30 +11:00
|
|
|
ast,
|
2025-02-25 23:51:25 +11:00
|
|
|
artifactGraph,
|
2023-10-16 21:20:05 +11:00
|
|
|
}: {
|
|
|
|
codeMirrorRanges: readonly SelectionRange[]
|
|
|
|
selectionRanges: Selections
|
2023-12-01 20:18:51 +11:00
|
|
|
isShiftDown: boolean
|
2024-11-21 15:04:30 +11:00
|
|
|
ast: Program
|
2025-02-25 23:51:25 +11:00
|
|
|
artifactGraph: ArtifactGraph
|
2023-10-16 21:20:05 +11:00
|
|
|
}): null | {
|
|
|
|
modelingEvent: ModelingMachineEvent
|
|
|
|
engineEvents: Models['WebSocketRequest_type'][]
|
|
|
|
} {
|
|
|
|
const isChange =
|
2024-11-21 15:04:30 +11:00
|
|
|
codeMirrorRanges.length !== selectionRanges?.graphSelections?.length ||
|
2023-10-16 21:20:05 +11:00
|
|
|
codeMirrorRanges.some(({ from, to }, i) => {
|
|
|
|
return (
|
2024-11-21 15:04:30 +11:00
|
|
|
from !== selectionRanges.graphSelections[i]?.codeRef?.range[0] ||
|
|
|
|
to !== selectionRanges.graphSelections[i]?.codeRef?.range[1]
|
2023-10-16 21:20:05 +11:00
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
if (!isChange) return null
|
2024-11-21 15:04:30 +11:00
|
|
|
const codeBasedSelections: Selections['graphSelections'] =
|
2023-10-16 21:20:05 +11:00
|
|
|
codeMirrorRanges.map(({ from, to }) => {
|
2025-01-17 14:34:36 -05:00
|
|
|
const pathToNode = getNodePathFromSourceRange(
|
|
|
|
ast,
|
|
|
|
topLevelRange(from, to)
|
|
|
|
)
|
2023-10-16 21:20:05 +11:00
|
|
|
return {
|
2024-11-21 15:04:30 +11:00
|
|
|
codeRef: {
|
2025-01-17 14:34:36 -05:00
|
|
|
range: topLevelRange(from, to),
|
2024-11-21 15:04:30 +11:00
|
|
|
pathToNode,
|
|
|
|
},
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
})
|
2025-02-25 23:51:25 +11:00
|
|
|
const idBasedSelections: SelectionToEngine[] = codeToIdSelections(
|
|
|
|
codeBasedSelections,
|
|
|
|
artifactGraph,
|
2025-03-29 17:25:26 -07:00
|
|
|
kclManager.artifactIndex
|
2025-02-25 23:51:25 +11:00
|
|
|
)
|
2024-11-21 15:04:30 +11:00
|
|
|
const selections: Selection[] = []
|
|
|
|
for (const { id, range } of idBasedSelections) {
|
|
|
|
if (!id) {
|
|
|
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
|
|
|
selections.push({
|
|
|
|
codeRef: {
|
|
|
|
range,
|
|
|
|
pathToNode,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
continue
|
|
|
|
}
|
2025-02-25 23:51:25 +11:00
|
|
|
const artifact = artifactGraph.get(id)
|
|
|
|
const codeRefs = getCodeRefsByArtifactId(id, artifactGraph)
|
2024-11-21 15:04:30 +11:00
|
|
|
if (artifact && codeRefs) {
|
|
|
|
selections.push({ artifact, codeRef: codeRefs[0] })
|
|
|
|
} else if (codeRefs) {
|
|
|
|
selections.push({ codeRef: codeRefs[0] })
|
|
|
|
}
|
|
|
|
}
|
2023-10-16 21:20:05 +11:00
|
|
|
|
|
|
|
if (!selectionRanges) return null
|
2024-02-11 12:59:00 +11:00
|
|
|
updateSceneObjectColors(codeBasedSelections)
|
2023-10-16 21:20:05 +11:00
|
|
|
return {
|
|
|
|
modelingEvent: {
|
|
|
|
type: 'Set selection',
|
|
|
|
data: {
|
|
|
|
selectionType: 'mirrorCodeMirrorSelections',
|
|
|
|
selection: {
|
2023-12-01 20:18:51 +11:00
|
|
|
otherSelections: isShiftDown ? selectionRanges.otherSelections : [],
|
2024-11-21 15:04:30 +11:00
|
|
|
graphSelections: selections,
|
2023-10-16 21:20:05 +11:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2024-11-21 15:04:30 +11:00
|
|
|
engineEvents: resetAndSetEngineEntitySelectionCmds(
|
|
|
|
idBasedSelections.filter(({ id }) => !!id)
|
|
|
|
),
|
2023-10-16 21:20:05 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
2024-09-06 17:52:52 -04:00
|
|
|
const updated = kclManager.ast
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-02-14 08:03:20 +11:00
|
|
|
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
2024-09-13 21:14:14 +10:00
|
|
|
if (!SEGMENT_BODIES_PLUS_PROFILE_START.includes(segmentGroup?.name)) return
|
2025-05-02 16:08:12 -05:00
|
|
|
const nodeMeta = getNodeFromPath<Node<CallExpressionKw>>(
|
2024-02-11 12:59:00 +11:00
|
|
|
updated,
|
|
|
|
segmentGroup.userData.pathToNode,
|
2025-05-02 16:08:12 -05:00
|
|
|
['CallExpressionKw']
|
2024-06-24 11:45:40 -04:00
|
|
|
)
|
|
|
|
if (err(nodeMeta)) return
|
|
|
|
const node = nodeMeta.node
|
2024-02-11 12:59:00 +11:00
|
|
|
const groupHasCursor = codeBasedSelections.some((selection) => {
|
2025-01-17 14:34:36 -05:00
|
|
|
return isOverlap(
|
|
|
|
selection?.codeRef?.range,
|
|
|
|
topLevelRange(node.start, node.end)
|
|
|
|
)
|
2024-02-11 12:59:00 +11:00
|
|
|
})
|
2024-06-22 04:49:31 -04:00
|
|
|
|
2024-03-04 14:18:08 +11:00
|
|
|
const color = groupHasCursor
|
|
|
|
? 0x0000ff
|
|
|
|
: segmentGroup?.userData?.baseColor || 0xffffff
|
2024-02-11 12:59:00 +11:00
|
|
|
segmentGroup.traverse(
|
|
|
|
(child) => child instanceof Mesh && child.material.color.set(color)
|
|
|
|
)
|
|
|
|
// TODO if we had access to the xstate context and therefore selections
|
|
|
|
// we wouldn't need to set this here,
|
|
|
|
// it would be better to treat xstate context as the source of truth instead of having
|
|
|
|
// extra redundant state floating around
|
|
|
|
segmentGroup.userData.isSelected = groupHasCursor
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-12-01 20:18:51 +11:00
|
|
|
function resetAndSetEngineEntitySelectionCmds(
|
2023-10-16 21:20:05 +11:00
|
|
|
selections: SelectionToEngine[]
|
|
|
|
): Models['WebSocketRequest_type'][] {
|
|
|
|
if (!engineCommandManager.engineConnection?.isReady()) {
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
return [
|
|
|
|
{
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'select_clear',
|
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'select_add',
|
2024-11-21 15:04:30 +11:00
|
|
|
entities: selections.map(({ id }) => id).filter(isNonNullable),
|
2023-10-16 21:20:05 +11:00
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
},
|
|
|
|
]
|
|
|
|
}
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
|
2024-12-19 18:42:39 -05:00
|
|
|
/**
|
|
|
|
* Is the selection a single cursor in a sketch pipe expression chain?
|
|
|
|
*/
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
export function isSketchPipe(selectionRanges: Selections) {
|
2024-02-19 17:23:03 +11:00
|
|
|
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast)) return false
|
2025-03-29 17:25:26 -07:00
|
|
|
return isCursorInSketchCommandRange(kclManager.artifactGraph, selectionRanges)
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// This accounts for non-geometry selections under "other"
|
2024-11-26 11:36:14 -05:00
|
|
|
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
|
|
|
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* In the future, I'd like this function to properly return the type of each selected entity based on
|
|
|
|
* its code source range, so that we can show something like "0 objects" or "1 face" or "1 line, 2 edges",
|
|
|
|
* and then validate the selection in CommandBarSelectionInput.tsx and show the proper label.
|
|
|
|
* @param selection
|
|
|
|
* @returns
|
|
|
|
*/
|
2024-11-26 11:36:14 -05:00
|
|
|
export function getSelectionCountByType(
|
2024-09-23 14:35:38 -04:00
|
|
|
selection?: Selections
|
2024-11-26 11:36:14 -05:00
|
|
|
): SelectionCountsByType | 'none' {
|
|
|
|
const selectionsByType: SelectionCountsByType = new Map()
|
|
|
|
if (
|
|
|
|
!selection ||
|
|
|
|
(!selection.graphSelections.length && !selection.otherSelections.length)
|
2024-11-21 15:04:30 +11:00
|
|
|
)
|
2024-11-26 11:36:14 -05:00
|
|
|
return 'none'
|
|
|
|
|
|
|
|
function incrementOrInitializeSelectionType(type: ResolvedSelectionType) {
|
|
|
|
const count = selectionsByType.get(type) || 0
|
|
|
|
selectionsByType.set(type, count + 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
selection.otherSelections.forEach((selection) => {
|
|
|
|
if (typeof selection === 'string') {
|
|
|
|
incrementOrInitializeSelectionType('other')
|
|
|
|
} else if ('name' in selection) {
|
|
|
|
incrementOrInitializeSelectionType('plane')
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-12-09 16:43:58 -05:00
|
|
|
selection.graphSelections.forEach((graphSelection) => {
|
|
|
|
if (!graphSelection.artifact) {
|
|
|
|
/**
|
|
|
|
* TODO: remove this heuristic-based selection type detection.
|
|
|
|
* Currently, if you've created a sketch and have not left sketch mode,
|
|
|
|
* the selection will be a segment selection with no artifact.
|
|
|
|
* This is because the mock execution does not update the artifact graph.
|
|
|
|
* Once we move the artifactGraph creation to WASM, we can remove this,
|
|
|
|
* as the artifactGraph will always be up-to-date.
|
|
|
|
*/
|
|
|
|
if (isSingleCursorInPipe(selection, kclManager.ast)) {
|
|
|
|
incrementOrInitializeSelectionType('segment')
|
|
|
|
return
|
|
|
|
} else {
|
|
|
|
console.warn(
|
|
|
|
'Selection is outside of a sketch but has no artifact. Sketch segment selections are the only kind that can have a valid selection with no artifact.',
|
|
|
|
JSON.stringify(graphSelection)
|
|
|
|
)
|
|
|
|
incrementOrInitializeSelectionType('other')
|
|
|
|
return
|
|
|
|
}
|
2024-11-26 11:36:14 -05:00
|
|
|
}
|
2024-12-09 16:43:58 -05:00
|
|
|
incrementOrInitializeSelectionType(graphSelection.artifact.type)
|
2024-11-26 11:36:14 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
return selectionsByType
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
export function getSelectionTypeDisplayText(
|
2024-09-23 14:35:38 -04:00
|
|
|
selection?: Selections
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
): string | null {
|
2024-11-26 11:36:14 -05:00
|
|
|
const selectionsByType = getSelectionCountByType(selection)
|
|
|
|
if (selectionsByType === 'none') return null
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
|
2025-02-15 00:57:04 +11:00
|
|
|
return [...selectionsByType.entries()]
|
2024-04-24 16:34:56 -04:00
|
|
|
.map(
|
|
|
|
// Hack for showing "face" instead of "extrude-wall" in command bar text
|
|
|
|
([type, count]) =>
|
2025-05-19 11:21:29 -04:00
|
|
|
`${count} ${type.replace('wall', 'face').replace('solid2d', 'profile')}${
|
2025-01-31 16:49:57 -05:00
|
|
|
count > 1 ? 's' : ''
|
|
|
|
}`
|
2024-04-24 16:34:56 -04:00
|
|
|
)
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
.join(', ')
|
|
|
|
}
|
|
|
|
|
|
|
|
export function canSubmitSelectionArg(
|
2024-11-26 11:36:14 -05:00
|
|
|
selectionsByType: 'none' | Map<ResolvedSelectionType, number>,
|
2025-02-26 14:06:51 +11:00
|
|
|
argument: CommandArgument<unknown> & {
|
|
|
|
inputType: 'selection' | 'selectionMixed'
|
|
|
|
}
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
) {
|
|
|
|
return (
|
|
|
|
selectionsByType !== 'none' &&
|
2025-02-15 00:57:04 +11:00
|
|
|
[...selectionsByType.entries()].every(([type, count]) => {
|
Command bar: add extrude command, nonlinear editing, etc (#1204)
* Tweak toaster look and feel
* Add icons, tweak plus icon names
* Rename commandBarMeta to commandBarConfig
* Refactor command bar, add support for icons
* Create a tailwind plugin for aria-pressed button state
* Remove overlay from behind command bar
* Clean up toolbar
* Button and other style tweaks
* Icon tweaks follow-up: make old icons work with new sizing
* Delete unused static icons
* More CSS tweaks
* Small CSS tweak to project sidebar
* Add command bar E2E test
* fumpt
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* fix typo in a comment
* Fix icon padding (built version only)
* Update onboarding and warning banner icons padding
* Misc minor style fixes
* Get Extrude opening and canceling from command bar
* Iconography tweaks
* Get extrude kind of working
* Refactor command bar config types and organization
* Move command bar configs to be co-located with each other
* Start building a state machine for the command bar
* Start converting command bar to state machine
* Add support for multiple args, confirmation step
* Submission behavior, hotkeys, code organization
* Add new test for extruding from command bar
* Polish step back and selection hotkeys, CSS tweaks
* Loading style tweaks
* Validate selection inputs, polish UX of args re-editing
* Prevent submission with multiple selection on singlular arg
* Remove stray console logs
* Tweak test, CSS nit, remove extrude "result" argument
* Fix linting warnings
* Show Ctrl+/ instead of ⌘K on all platforms but Mac
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)
* Add "Enter sketch" to command bar
* fix command bar test
* Fix flaky cmd bar extrude test by waiting for engine select response
* Cover both button labels '⌘K' and 'Ctrl+/' in test
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-13 12:49:01 -05:00
|
|
|
const foundIndex = argument.selectionTypes.findIndex((s) => s === type)
|
|
|
|
return (
|
|
|
|
foundIndex !== -1 &&
|
|
|
|
(!argument.multiple ? count < 2 && count > 0 : count > 0)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
)
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
|
2025-02-25 23:51:25 +11:00
|
|
|
/**
|
2025-05-02 16:08:12 -05:00
|
|
|
* Find the index of the last range where range.start < targetStart. When there
|
|
|
|
* are ranges with equal start positions just before the targetStart, the first
|
|
|
|
* one is returned. The returned index can be used as a starting point for
|
|
|
|
* linear search of overlapping ranges.
|
|
|
|
*
|
2025-02-25 23:51:25 +11:00
|
|
|
* @param index The sorted array of ranges to search through
|
|
|
|
* @param targetStart The start position to compare against
|
|
|
|
* @returns The index of the last range where range[0] < targetStart
|
|
|
|
*/
|
|
|
|
export function findLastRangeStartingBefore(
|
|
|
|
index: ArtifactIndex,
|
|
|
|
targetStart: number
|
|
|
|
): number {
|
2025-05-02 16:08:12 -05:00
|
|
|
if (index.length === 0) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2025-02-25 23:51:25 +11:00
|
|
|
let left = 0
|
|
|
|
let right = index.length - 1
|
|
|
|
let lastValidIndex = 0
|
|
|
|
|
|
|
|
while (left <= right) {
|
|
|
|
const mid = left + Math.floor((right - left) / 2)
|
|
|
|
const midRange = index[mid].range
|
|
|
|
|
|
|
|
if (midRange[0] < targetStart) {
|
|
|
|
// This range starts before our selection, look in right half for later ones
|
|
|
|
lastValidIndex = mid
|
|
|
|
left = mid + 1
|
|
|
|
} else {
|
|
|
|
// This range starts at or after our selection, look in left half
|
|
|
|
right = mid - 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-05-02 16:08:12 -05:00
|
|
|
// We may have passed the correct index. Consider what happens when there are
|
|
|
|
// duplicates. We found the last one, but earlier ones need to be checked too.
|
|
|
|
let resultIndex = lastValidIndex
|
|
|
|
let resultRange = index[resultIndex].range
|
|
|
|
for (let i = lastValidIndex - 1; i >= 0; i--) {
|
|
|
|
const range = index[i].range
|
|
|
|
if (range[0] === resultRange[0]) {
|
|
|
|
resultIndex = i
|
|
|
|
resultRange = range
|
|
|
|
} else {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return resultIndex
|
2025-02-25 23:51:25 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
function findOverlappingArtifactsFromIndex(
|
|
|
|
selection: Selection,
|
|
|
|
index: ArtifactIndex
|
|
|
|
): ArtifactEntry[] {
|
|
|
|
if (!selection.codeRef?.range) {
|
|
|
|
console.warn('Selection missing code reference range')
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
const selectionRange = selection.codeRef.range
|
|
|
|
const results: ArtifactEntry[] = []
|
|
|
|
|
|
|
|
// Binary search to find the last range where range[0] < selectionRange[0]
|
|
|
|
// This search does not take into consideration the end range, so it's possible
|
|
|
|
// the index it finds dose not have any overlap (depending on the end range)
|
|
|
|
// but it's main purpose is to act as a starting point for the linear part of the search
|
|
|
|
// so a tiny loss in efficiency is acceptable to keep the code simple
|
|
|
|
const startIndex = findLastRangeStartingBefore(index, selectionRange[0])
|
|
|
|
|
|
|
|
// Check all potential overlaps from the found position
|
|
|
|
for (let i = startIndex; i < index.length; i++) {
|
|
|
|
const { range, entry } = index[i]
|
|
|
|
// Stop if we've gone past possible overlaps
|
|
|
|
if (range[0] > selectionRange[1]) break
|
|
|
|
|
|
|
|
if (isOverlap(range, selectionRange)) {
|
|
|
|
results.push(entry)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBestCandidate(
|
|
|
|
entries: ArtifactEntry[],
|
|
|
|
artifactGraph: ArtifactGraph
|
|
|
|
): ArtifactEntry | undefined {
|
|
|
|
if (!entries.length) {
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const entry of entries) {
|
|
|
|
// Segments take precedence
|
|
|
|
if (entry.artifact.type === 'segment') {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Handle paths and their solid2d references
|
|
|
|
if (entry.artifact.type === 'path') {
|
|
|
|
const solid2dId = entry.artifact.solid2dId
|
|
|
|
if (!solid2dId) {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
const solid2d = artifactGraph.get(solid2dId)
|
|
|
|
if (solid2d?.type === 'solid2d') {
|
|
|
|
return { id: solid2dId, artifact: solid2d }
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Other valid artifact types
|
|
|
|
if (['plane', 'cap', 'wall', 'sweep'].includes(entry.artifact.type)) {
|
|
|
|
return entry
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
function createSelectionToEngine(
|
|
|
|
selection: Selection,
|
|
|
|
candidateId?: ArtifactId
|
|
|
|
): SelectionToEngine {
|
|
|
|
return {
|
|
|
|
...(candidateId && { id: candidateId }),
|
|
|
|
range: selection.codeRef.range,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
export function codeToIdSelections(
|
2025-02-25 23:51:25 +11:00
|
|
|
selections: Selection[],
|
|
|
|
artifactGraph: ArtifactGraph,
|
|
|
|
artifactIndex: ArtifactIndex
|
2024-03-22 10:23:04 +11:00
|
|
|
): SelectionToEngine[] {
|
2025-02-25 23:51:25 +11:00
|
|
|
if (!selections?.length) {
|
|
|
|
return []
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
|
2025-02-25 23:51:25 +11:00
|
|
|
if (!artifactGraph) {
|
|
|
|
console.warn('Artifact graph is missing or empty')
|
|
|
|
return selections.map((selection) => createSelectionToEngine(selection))
|
|
|
|
}
|
|
|
|
|
|
|
|
return selections
|
|
|
|
.flatMap((selection): SelectionToEngine[] => {
|
|
|
|
if (!selection) {
|
|
|
|
console.warn('Null or undefined selection encountered')
|
|
|
|
return []
|
2024-03-22 10:23:04 +11:00
|
|
|
}
|
2025-02-25 23:51:25 +11:00
|
|
|
|
|
|
|
// Direct artifact case
|
|
|
|
if (selection.artifact?.id) {
|
|
|
|
return [createSelectionToEngine(selection, selection.artifact.id)]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find matching artifacts by code range overlap
|
|
|
|
const overlappingEntries = findOverlappingArtifactsFromIndex(
|
|
|
|
selection,
|
|
|
|
artifactIndex
|
|
|
|
)
|
|
|
|
const bestCandidate = getBestCandidate(overlappingEntries, artifactGraph)
|
|
|
|
|
|
|
|
return [createSelectionToEngine(selection, bestCandidate?.id)]
|
2024-03-22 10:23:04 +11:00
|
|
|
})
|
2024-09-27 09:37:27 -04:00
|
|
|
.filter(isNonNullable)
|
2024-03-22 10:23:04 +11:00
|
|
|
}
|
|
|
|
|
2024-06-18 16:08:41 +10:00
|
|
|
export async function sendSelectEventToEngine(
|
2025-03-06 11:19:13 -05:00
|
|
|
e: React.MouseEvent<HTMLDivElement, MouseEvent>
|
2024-03-22 10:23:04 +11:00
|
|
|
) {
|
2025-03-06 11:19:13 -05:00
|
|
|
// No video stream to normalise against, return immediately
|
2025-04-07 07:08:31 -04:00
|
|
|
const engineStreamState = engineStreamActor.getSnapshot().context
|
|
|
|
if (!engineStreamState.videoRef.current)
|
2025-03-06 11:19:13 -05:00
|
|
|
return Promise.reject('video element not ready')
|
|
|
|
|
|
|
|
const { x, y } = getNormalisedCoordinates(
|
|
|
|
e,
|
2025-04-07 07:08:31 -04:00
|
|
|
engineStreamState.videoRef.current,
|
2025-03-06 11:19:13 -05:00
|
|
|
engineCommandManager.streamDimensions
|
|
|
|
)
|
2025-04-27 16:54:32 -07:00
|
|
|
let res = await engineCommandManager.sendSceneCommand({
|
2024-07-23 17:13:23 +10:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'select_with_point',
|
|
|
|
selected_at_window: { x, y },
|
|
|
|
selection_type: 'add',
|
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
})
|
2025-04-27 16:54:32 -07:00
|
|
|
if (!res) {
|
2025-05-30 10:36:33 -04:00
|
|
|
return Promise.reject('no response')
|
2025-04-27 16:54:32 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isArray(res)) {
|
|
|
|
res = res[0]
|
|
|
|
}
|
2024-07-23 17:13:23 +10:00
|
|
|
if (
|
|
|
|
res?.success &&
|
|
|
|
res?.resp?.type === 'modeling' &&
|
|
|
|
res?.resp?.data?.modeling_response.type === 'select_with_point'
|
|
|
|
)
|
|
|
|
return res?.resp?.data?.modeling_response?.data
|
|
|
|
return { entity_id: '' }
|
2024-03-22 10:23:04 +11:00
|
|
|
}
|
2024-05-30 13:28:29 +10:00
|
|
|
|
|
|
|
export function updateSelections(
|
|
|
|
pathToNodeMap: PathToNodeMap,
|
|
|
|
prevSelectionRanges: Selections,
|
2024-06-24 11:45:40 -04:00
|
|
|
ast: Program | Error
|
|
|
|
): Selections | Error {
|
|
|
|
if (err(ast)) return ast
|
|
|
|
|
|
|
|
const newSelections = Object.entries(pathToNodeMap)
|
|
|
|
.map(([index, pathToNode]): Selection | undefined => {
|
2024-11-21 15:04:30 +11:00
|
|
|
const previousSelection =
|
|
|
|
prevSelectionRanges.graphSelections[Number(index)]
|
2024-08-12 15:38:42 -05:00
|
|
|
const nodeMeta = getNodeFromPath<Expr>(ast, pathToNode)
|
2024-06-24 11:45:40 -04:00
|
|
|
if (err(nodeMeta)) return undefined
|
|
|
|
const node = nodeMeta.node
|
2024-11-21 15:04:30 +11:00
|
|
|
let artifact: Artifact | null = null
|
2025-03-29 17:25:26 -07:00
|
|
|
for (const [id, a] of kclManager.artifactGraph) {
|
2024-11-21 15:04:30 +11:00
|
|
|
if (previousSelection?.artifact?.type === a.type) {
|
2025-03-29 17:25:26 -07:00
|
|
|
const codeRefs = getCodeRefsByArtifactId(id, kclManager.artifactGraph)
|
2024-11-21 15:04:30 +11:00
|
|
|
if (!codeRefs) continue
|
|
|
|
if (
|
|
|
|
JSON.stringify(codeRefs[0].pathToNode) ===
|
|
|
|
JSON.stringify(pathToNode)
|
|
|
|
) {
|
|
|
|
artifact = a
|
|
|
|
break
|
|
|
|
}
|
2024-09-26 18:25:05 +10:00
|
|
|
}
|
2024-11-21 15:04:30 +11:00
|
|
|
}
|
|
|
|
if (!artifact) return undefined
|
2024-06-24 11:45:40 -04:00
|
|
|
return {
|
2024-11-21 15:04:30 +11:00
|
|
|
artifact: artifact,
|
|
|
|
codeRef: {
|
2025-01-17 14:34:36 -05:00
|
|
|
range: topLevelRange(node.start, node.end),
|
2024-11-21 15:04:30 +11:00
|
|
|
pathToNode: pathToNode,
|
|
|
|
},
|
2024-05-30 13:28:29 +10:00
|
|
|
}
|
2024-06-24 11:45:40 -04:00
|
|
|
})
|
2025-03-28 00:24:24 -04:00
|
|
|
.filter((x?: Selection) => x !== undefined)
|
2024-06-24 11:45:40 -04:00
|
|
|
|
2024-11-21 15:04:30 +11:00
|
|
|
// 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: {
|
2025-01-17 14:34:36 -05:00
|
|
|
range: topLevelRange(node.node.start, node.node.end),
|
2024-11-21 15:04:30 +11:00
|
|
|
pathToNode: pathToNode,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2024-06-24 11:45:40 -04:00
|
|
|
return {
|
2024-11-21 15:04:30 +11:00
|
|
|
graphSelections:
|
|
|
|
newSelections.length >= pathToNodeBasedSelections.length
|
2024-06-24 11:45:40 -04:00
|
|
|
? newSelections
|
2024-11-21 15:04:30 +11:00
|
|
|
: pathToNodeBasedSelections,
|
2024-06-24 11:45:40 -04:00
|
|
|
otherSelections: prevSelectionRanges.otherSelections,
|
2024-05-30 13:28:29 +10:00
|
|
|
}
|
|
|
|
}
|