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:
Kurt Hutten
2024-11-21 15:04:30 +11:00
committed by GitHub
parent c17cb1067f
commit 59e0df7879
49 changed files with 885 additions and 763 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -38,9 +38,8 @@ export function Toolbar({
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
const sketchPathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast)) {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
return false
}
return isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
context.selectionRanges

View File

@ -578,10 +578,8 @@ export class SceneEntities {
draftExpressionsIndices &&
index <= draftExpressionsIndices.end &&
index >= draftExpressionsIndices.start
const isSelected = selectionRanges?.codeBasedSelections.some(
(selection) => {
return isOverlap(selection.range, segment.__geoMeta.sourceRange)
}
const isSelected = selectionRanges?.graphSelections.some((selection) =>
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
)
let seg: Group

View File

@ -1,15 +1,17 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { editorManager, kclManager } from 'lib/singletons'
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { trap } from 'lib/trap'
import { codeToIdSelections } from 'lib/selections'
import { codeRefFromRange } from 'lang/std/artifactGraph'
export function AstExplorer() {
const { context } = useModelingContext()
const pathToNode = getNodePathFromSourceRange(
// TODO maybe need to have callback to make sure it stays in sync
kclManager.ast,
context.selectionRanges.codeBasedSelections?.[0]?.range
context.selectionRanges.graphSelections?.[0]?.codeRef?.range
)
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
@ -121,13 +123,21 @@ function DisplayObj({
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
}}
onClick={(e) => {
const range: [number, number] = [obj?.start || 0, obj.end || 0]
const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) },
])[0]
const artifact = engineCommandManager.artifactGraph.get(
idInfo?.id || ''
)
if (!artifact) return
send({
type: 'Set selection',
data: {
selectionType: 'singleCodeCursor',
selection: {
type: 'default',
range: [obj?.start || 0, obj.end || 0],
artifact: artifact,
codeRef: codeRefFromRange(range, kclManager.ast),
},
},
})

View File

@ -96,7 +96,8 @@ export function useCalc({
} {
const { programMemory } = useKclContext()
const { context } = useModelingContext()
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
const selectionRange =
context.selectionRanges?.graphSelections[0]?.codeRef?.range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -157,6 +158,7 @@ export function useCalc({
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
}).then(({ execState }) => {
const resultDeclaration = ast.body.find(
(a) =>

View File

@ -1,9 +1,9 @@
import { useSelector } from '@xstate/react'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useKclContext } from 'lang/KclProvider'
import { Artifact } from 'lang/std/artifactGraph'
import { CommandArgument } from 'lib/commandTypes'
import {
Selection,
canSubmitSelectionArg,
getSelectionType,
getSelectionTypeDisplayText,
@ -12,13 +12,13 @@ import { modelingMachine } from 'machines/modelingMachine'
import { useEffect, useMemo, useRef, useState } from 'react'
import { StateFrom } from 'xstate'
const semanticEntityNames: { [key: string]: Array<Selection['type']> } = {
face: ['extrude-wall', 'start-cap', 'end-cap'],
edge: ['edge', 'line', 'arc'],
point: ['point', 'line-end', 'line-mid'],
const semanticEntityNames: { [key: string]: Array<Artifact['type']> } = {
face: ['wall', 'cap', 'solid2D'],
edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
point: [],
}
function getSemanticSelectionType(selectionType: Array<Selection['type']>) {
function getSemanticSelectionType(selectionType: Array<Artifact['type']>) {
const semanticSelectionType = new Set()
selectionType.forEach((type) => {
Object.entries(semanticEntityNames).forEach(([entity, entityTypes]) => {
@ -49,8 +49,12 @@ function CommandBarSelectionInput({
const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector)
const selectionsByType = useMemo(() => {
const selectionRangeEnd = selection?.codeBasedSelections[0]?.range[1]
return !selectionRangeEnd || selectionRangeEnd === code.length
const selectionRangeEnd = !selection
? null
: selection?.graphSelections[0]?.codeRef?.range[1]
return !selectionRangeEnd || selectionRangeEnd === code.length || !selection
? 'none'
: !selection
? 'none'
: getSelectionType(selection)
}, [selection, code])

View File

@ -1170,15 +1170,15 @@ const CustomIconMap = {
xmlns="http://www.w3.org/2000/svg"
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M7.95705 5.99046C7.05643 6.44935 6.33654 7.19809 5.91336 8.11602C5.49019 9.03396 5.38838 10.0676 5.62434 11.0505C5.8603 12.0334 6.42029 12.9081 7.21408 13.5339C8.00787 14.1597 8.98922 14.5 10 14.5C11.0108 14.5 11.9921 14.1597 12.7859 13.5339C13.5797 12.9082 14.1397 12.0334 14.3757 11.0505C14.6116 10.0676 14.5098 9.03396 14.0866 8.11603C13.6635 7.19809 12.9436 6.44935 12.043 5.99046L12.497 5.09946C13.5977 5.66032 14.4776 6.57544 14.9948 7.69737C15.512 8.81929 15.6364 10.0827 15.348 11.2839C15.0596 12.4852 14.3752 13.5544 13.405 14.3192C12.4348 15.0841 11.2354 15.5 10 15.5C8.7646 15.5 7.56517 15.0841 6.59499 14.3192C5.6248 13.5544 4.94037 12.4852 4.65197 11.2839C4.36357 10.0827 4.488 8.81929 5.00522 7.69736C5.52243 6.57544 6.40231 5.66032 7.50306 5.09946L7.95705 5.99046Z"
fill="currentColor"
/>
<path d="M10 5.5V4M10 4H8M10 4H12" stroke="currentColor" />
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M12.8536 7.85356L10.3536 10.3536C10.1583 10.5488 9.84171 10.5488 9.64645 10.3536C9.45118 10.1583 9.45118 9.84172 9.64645 9.64645L12.1464 7.14645L12.8536 7.85356Z"
fill="currentColor"
/>

View File

@ -64,7 +64,10 @@ export const EngineCommands = () => {
)
})}
</div>
<button data-testid="clear-commands" onClick={clearEngineCommands}>
<button
data-testid="clear-commands"
onClick={() => clearEngineCommands()}
>
Clear
</button>
<br />

View File

@ -43,12 +43,12 @@ import {
} from './Toolbar/SetAngleBetween'
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import {
Selections,
canSweepSelection,
handleSelectionBatch,
isSelectionLastLine,
isRangeBetweenCharacters,
isSketchPipe,
Selections,
updateSelections,
} from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect'
@ -318,7 +318,7 @@ export const ModelingMachineProvider = ({
})
}
let selections: Selections = {
codeBasedSelections: [],
graphSelections: [],
otherSelections: [],
}
if (setSelections.selectionType === 'singleCodeCursor') {
@ -328,7 +328,7 @@ export const ModelingMachineProvider = ({
!editorManager.isShiftDown
) {
selections = {
codeBasedSelections: [],
graphSelections: [],
otherSelections: [],
}
} else if (
@ -336,13 +336,13 @@ export const ModelingMachineProvider = ({
!editorManager.isShiftDown
) {
selections = {
codeBasedSelections: [setSelections.selection],
graphSelections: [setSelections.selection],
otherSelections: [],
}
} else if (setSelections.selection && editorManager.isShiftDown) {
selections = {
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
graphSelections: [
...selectionRanges.graphSelections,
setSelections.selection,
],
otherSelections: selectionRanges.otherSelections,
@ -378,18 +378,18 @@ export const ModelingMachineProvider = ({
if (setSelections.selectionType === 'otherSelection') {
if (editorManager.isShiftDown) {
selections = {
codeBasedSelections: selectionRanges.codeBasedSelections,
graphSelections: selectionRanges.graphSelections,
otherSelections: [setSelections.selection],
}
} else {
selections = {
codeBasedSelections: [],
graphSelections: [],
otherSelections: [setSelections.selection],
}
}
const { engineEvents, updateSceneObjectColors } =
handleSelectionBatch({
selections,
selections: selections,
})
engineEvents &&
engineEvents.forEach((event) => {
@ -558,7 +558,7 @@ export const ModelingMachineProvider = ({
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
const hasNoSelection =
selectionRanges.codeBasedSelections.length === 0 ||
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
@ -570,21 +570,24 @@ export const ModelingMachineProvider = ({
}
if (!isSketchPipe(selectionRanges)) return false
return canSweepSelection(selectionRanges)
const canSweep = canSweepSelection(selectionRanges)
if (err(canSweep)) return false
return canSweep
},
'has valid selection for deletion': ({
context: { selectionRanges },
}) => {
if (!commandBarState.matches('Closed')) return false
if (selectionRanges.codeBasedSelections.length <= 0) return false
if (selectionRanges.graphSelections.length <= 0) return false
return true
},
'has valid fillet selection': ({ context: { selectionRanges } }) =>
hasValidFilletSelection({
'has valid fillet selection': ({ context: { selectionRanges } }) => {
return hasValidFilletSelection({
selectionRanges,
ast: kclManager.ast,
code: codeManager.code,
}),
})
},
'Selection is on face': ({ context: { selectionRanges }, event }) => {
if (event.type !== 'Enter sketch') return false
if (event.data?.forceNewSketch) return false
@ -691,7 +694,8 @@ export const ModelingMachineProvider = ({
}),
'animate-to-sketch': fromPromise(
async ({ input: { selectionRanges } }) => {
const sourceRange = selectionRanges.codeBasedSelections[0].range
const sourceRange =
selectionRanges.graphSelections[0]?.codeRef?.range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
@ -713,6 +717,7 @@ export const ModelingMachineProvider = ({
}
}
),
'Get horizontal info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
@ -848,7 +853,9 @@ export const ModelingMachineProvider = ({
'Get length info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
await applyConstraintAngleLength({ selectionRanges })
await applyConstraintAngleLength({
selectionRanges,
})
const _modifiedAst = parse(recast(modifiedAst))
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))

View File

@ -25,8 +25,8 @@ export function equalAngleInfo({
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
const paths = selectionRanges.graphSelections.map(({ codeRef }) =>
getNodePathFromSourceRange(kclManager.ast, codeRef.range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
@ -64,7 +64,7 @@ export function equalAngleInfo({
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
graphSelections: selectionRanges.graphSelections.slice(1),
},
kclManager.ast,
'equalAngle'

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { getNodeFromPath } from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
transformSecondarySketchLinesTagFirst,
@ -26,11 +23,8 @@ export function setEqualLengthInfo({
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})
@ -38,10 +32,10 @@ export function setEqualLengthInfo({
if (err(_err1)) return _err1
const nodes = _nodes as Expr[]
const _varDecs = paths.map((pathToNode) => {
const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
codeRef.pathToNode,
'VariableDeclarator'
)
if (err(tmp)) return tmp
@ -65,7 +59,7 @@ export function setEqualLengthInfo({
const transforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
graphSelections: selectionRanges.graphSelections.slice(1),
},
kclManager.ast,
'equalLength'

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { getNodeFromPath } from '../../lang/queryAst'
import {
PathToNodeMap,
getTransformInfos,
@ -24,11 +21,8 @@ export function horzVertInfo(
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})

View File

@ -1,8 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import { Selections } from 'lib/selections'
import {
getNodePathFromSourceRange,
getNodeFromPath,
isLinesParallelAndConstrained,
} from '../../lang/queryAst'
@ -17,7 +16,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
import { createVariableDeclaration } from '../../lang/modifyAst'
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { kclManager } from 'lib/singletons'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
@ -34,7 +33,7 @@ export function intersectInfo({
forcedSelectionRanges: Selections
}
| Error {
if (selectionRanges.codeBasedSelections.length < 2) {
if (selectionRanges.graphSelections.length < 2) {
return {
enabled: false,
transforms: [],
@ -43,38 +42,35 @@ export function intersectInfo({
}
const previousSegment =
selectionRanges.codeBasedSelections.length > 1 &&
selectionRanges.graphSelections.length > 1 &&
isLinesParallelAndConstrained(
kclManager.ast,
engineCommandManager.artifactGraph,
kclManager.programMemory,
selectionRanges.codeBasedSelections[0],
selectionRanges.codeBasedSelections[1]
selectionRanges.graphSelections[0],
selectionRanges.graphSelections[1]
)
if (err(previousSegment)) return previousSegment
const artifact = selectionRanges.graphSelections[1]?.artifact
const shouldUsePreviousSegment =
selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
(!artifact || artifact.type === 'segment') &&
previousSegment &&
previousSegment.isParallelAndConstrained
const _forcedSelectionRanges: typeof selectionRanges = {
...selectionRanges,
codeBasedSelections: [
selectionRanges.codeBasedSelections?.[0],
shouldUsePreviousSegment
? {
range: previousSegment.sourceRange,
type: 'line-end',
}
: selectionRanges.codeBasedSelections?.[1],
graphSelections: [
selectionRanges.graphSelections?.[0],
shouldUsePreviousSegment && previousSegment.selection
? previousSegment.selection
: selectionRanges.graphSelections?.[1],
],
}
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = _forcedSelectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})
@ -82,10 +78,10 @@ export function intersectInfo({
if (err(_err1)) return _err1
const nodes = _nodes as Expr[]
const _varDecs = paths.map((pathToNode) => {
const _varDecs = _forcedSelectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
codeRef.pathToNode,
'VariableDeclarator'
)
if (err(tmp)) return tmp
@ -112,18 +108,19 @@ export function intersectInfo({
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: _forcedSelectionRanges.codeBasedSelections.slice(1),
graphSelections: _forcedSelectionRanges.graphSelections.slice(1),
},
kclManager.ast,
'intersect'
)
const forcedArtifact = _forcedSelectionRanges?.graphSelections?.[1]?.artifact
const _enableEqual =
secondaryVarDecs.length === 1 &&
isAllTooltips &&
isOthersLinkedToPrimary &&
theTransforms.every(Boolean) &&
_forcedSelectionRanges?.codeBasedSelections?.[1]?.type === 'line-end'
(!forcedArtifact || forcedArtifact.type === 'segment')
return {
enabled: _enableEqual,

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selection, Selections } from 'lib/selections'
import { PathToNode, Program, Expr } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { getNodeFromPath } from '../../lang/queryAst'
import {
PathToNodeMap,
getRemoveConstraintsTransforms,
@ -14,6 +11,7 @@ import { TransformInfo } from 'lang/std/stdTypes'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { codeRefFromRange } from 'lang/std/artifactGraph'
export function removeConstrainingValuesInfo({
selectionRanges,
@ -28,13 +26,8 @@ export function removeConstrainingValuesInfo({
updatedSelectionRanges: Selections
}
| Error {
const paths =
pathToNodes ||
selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})
@ -45,10 +38,9 @@ export function removeConstrainingValuesInfo({
const updatedSelectionRanges = pathToNodes
? {
otherSelections: [],
codeBasedSelections: nodes.map(
graphSelections: nodes.map(
(node): Selection => ({
range: [node.start, node.end],
type: 'default',
codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
})
),
}

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, Expr } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { Selections } from 'lib/selections'
import { getNodeFromPath } from '../../lang/queryAst'
import {
getTransformInfos,
transformAstSketchLines,
@ -47,13 +44,10 @@ export function absDistanceInfo({
: constraint === 'snapToYAxis'
? 'xAbs'
: 'yAbs'
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(
kclManager.ast,
pathToNode,
codeRef.pathToNode,
'CallExpression'
)
if (err(tmp)) return tmp
@ -84,7 +78,7 @@ export function absDistanceInfo({
const enabled =
isAllTooltips &&
transforms.every(Boolean) &&
selectionRanges.codeBasedSelections.length === 1 &&
selectionRanges.graphSelections.length === 1 &&
(enableX || enableY)
return { enabled, transforms }
@ -109,7 +103,7 @@ export async function applyConstraintAbsDistance({
const transform1 = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges: selectionRanges,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',
@ -129,7 +123,7 @@ export async function applyConstraintAbsDistance({
const transform2 = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges: selectionRanges,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',
@ -177,7 +171,7 @@ export function applyConstraintAxisAlign({
return transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges: selectionRanges,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
referenceSegName: '',

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { Selections } from 'lib/selections'
import { getNodeFromPath } from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
transformSecondarySketchLinesTagFirst,
@ -31,12 +28,8 @@ export function angleBetweenInfo({
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})
@ -44,10 +37,10 @@ export function angleBetweenInfo({
if (err(_err1)) return _err1
const nodes = _nodes as Expr[]
const _varDecs = paths.map((pathToNode) => {
const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
codeRef.pathToNode,
'VariableDeclarator'
)
if (err(tmp)) return tmp
@ -71,7 +64,7 @@ export function angleBetweenInfo({
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
graphSelections: selectionRanges.graphSelections.slice(1),
},
kclManager.ast,
'setAngleBetween'
@ -88,10 +81,8 @@ export function angleBetweenInfo({
export async function applyConstraintAngleBetween({
selectionRanges,
}: // constraint,
{
}: {
selectionRanges: Selections
// constraint: 'setHorzDistance' | 'setVertDistance'
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap

View File

@ -1,9 +1,6 @@
import { toolTips } from 'lang/langHelpers'
import { Program, Expr, VariableDeclarator } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { getNodeFromPath } from '../../lang/queryAst'
import { isSketchVariablesLinked } from '../../lang/std/sketchConstraints'
import {
transformSecondarySketchLinesTagFirst,
@ -34,11 +31,8 @@ export function horzVertDistanceInfo({
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const _nodes = paths.map((pathToNode) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, pathToNode)
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
return tmp.node
})
@ -47,10 +41,10 @@ export function horzVertDistanceInfo({
if (hasErr) return nodesWErrs[0]
const nodes = _nodes as Expr[]
const _varDecs = paths.map((pathToNode) => {
const _varDecs = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<VariableDeclarator>(
kclManager.ast,
pathToNode,
codeRef.pathToNode,
'VariableDeclarator'
)
if (err(tmp)) return tmp
@ -77,7 +71,7 @@ export function horzVertDistanceInfo({
const theTransforms = getTransformInfos(
{
...selectionRanges,
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
graphSelections: selectionRanges.graphSelections.slice(1),
},
kclManager.ast,
constraint
@ -104,7 +98,7 @@ export async function applyConstraintHorzVertDistance({
pathToNodeMap: PathToNodeMap
}> {
const info = horzVertDistanceInfo({
selectionRanges,
selectionRanges: selectionRanges,
constraint,
})
if (err(info)) return Promise.reject(info)

View File

@ -1,10 +1,7 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, Expr } from '../../lang/wasm'
import {
getNodePathFromSourceRange,
getNodeFromPath,
} from '../../lang/queryAst'
import { Selections } from 'lib/selections'
import { getNodeFromPath } from '../../lang/queryAst'
import {
PathToNodeMap,
getTransformInfos,
@ -40,15 +37,11 @@ export function angleLengthInfo({
enabled: boolean
}
| Error {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(kclManager.ast, range)
)
const nodes = paths.map((pathToNode) =>
getNodeFromPath<Expr>(kclManager.ast, pathToNode, 'CallExpression')
const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode, 'CallExpression')
)
const _err1 = nodes.find(err)
if (err(_err1)) return _err1
if (_err1 instanceof Error) return _err1
const isAllTooltips = nodes.every((meta) => {
if (err(meta)) return false
@ -64,7 +57,7 @@ export function angleLengthInfo({
angleOrLength
)
const enabled =
selectionRanges.codeBasedSelections.length <= 1 &&
selectionRanges.graphSelections.length <= 1 &&
isAllTooltips &&
transforms.every(Boolean)
return { enabled, transforms }

View File

@ -1,9 +1,9 @@
import { EditorView, ViewUpdate } from '@codemirror/view'
import { syntaxTree } from '@codemirror/language'
import { EditorSelection, Annotation, Transaction } from '@codemirror/state'
import { engineCommandManager } from 'lib/singletons'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
import { undo, redo } from '@codemirror/commands'
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
@ -31,7 +31,7 @@ export default class EditorManager {
private _isShiftDown: boolean = false
private _selectionRanges: Selections = {
otherSelections: [],
codeBasedSelections: [],
graphSelections: [],
}
private _lastEvent: { event: string; time: number } | null = null
@ -138,10 +138,10 @@ export default class EditorManager {
return this._highlightRange
}
setHighlightRange(selections: Array<Selection['range']>): void {
this._highlightRange = selections
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
this._highlightRange = range
const selectionsWithSafeEnds = selections.map((s): [number, number] => {
const selectionsWithSafeEnds = range.map((s): [number, number] => {
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
return [s[0], safeEnd]
})
@ -254,21 +254,23 @@ export default class EditorManager {
}
selectRange(selections: Selections) {
if (selections.codeBasedSelections.length === 0) {
if (selections?.graphSelections?.length === 0) {
return
}
let codeBasedSelections = []
for (const selection of selections.codeBasedSelections) {
for (const selection of selections.graphSelections) {
codeBasedSelections.push(
EditorSelection.range(selection.range[0], selection.range[1])
EditorSelection.range(
selection.codeRef.range[0],
selection.codeRef.range[1]
)
)
}
codeBasedSelections.push(
EditorSelection.cursor(
selections.codeBasedSelections[
selections.codeBasedSelections.length - 1
].range[1]
selections.graphSelections[selections.graphSelections.length - 1]
.codeRef.range[1]
)
)
@ -311,6 +313,7 @@ export default class EditorManager {
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges: this._selectionRanges,
isShiftDown: this._isShiftDown,
ast: kclManager.ast,
})
if (!eventInfo) {

View File

@ -1,4 +1,4 @@
import { useEffect } from 'react'
import { useEffect, useRef } from 'react'
import {
editorManager,
engineCommandManager,
@ -9,11 +9,9 @@ import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
import {
getCapCodeRef,
getSweepEdgeCodeRef,
getSweepFromSuspectedSweepSurface,
getEdgeCuteConsumedCodeRef,
getSolid2dCodeRef,
getWallCodeRef,
getCodeRefsByArtifactId,
getArtifactOfTypes,
SegmentArtifact,
} from 'lang/std/artifactGraph'
@ -25,6 +23,8 @@ import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
export function useEngineConnectionSubscriptions() {
const { send, context, state } = useModelingContext()
const stateRef = useRef(state)
stateRef.current = state
useEffect(() => {
if (!engineCommandManager) return
@ -34,66 +34,12 @@ export function useEngineConnectionSubscriptions() {
event: 'highlight_set_entity',
callback: ({ data }) => {
if (data?.entity_id) {
const artifact = engineCommandManager.artifactGraph.get(
data.entity_id
const codeRefs = getCodeRefsByArtifactId(
data.entity_id,
engineCommandManager.artifactGraph
)
if (artifact?.type === 'solid2D') {
const codeRef = getSolid2dCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else if (artifact?.type === 'cap') {
const codeRef = getCapCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else if (artifact?.type === 'wall') {
const extrusion = getSweepFromSuspectedSweepSurface(
data.entity_id,
engineCommandManager.artifactGraph
)
const codeRef = getWallCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange(
err(extrusion)
? [codeRef.range]
: [codeRef.range, extrusion.codeRef.range]
)
} else if (artifact?.type === 'sweepEdge') {
const codeRef = getSweepEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else if (artifact?.type === 'segment') {
editorManager.setHighlightRange([
artifact?.codeRef?.range || [0, 0],
])
} else if (artifact?.type === 'edgeCut') {
const codeRef = artifact.codeRef
const consumedCodeRef = getEdgeCuteConsumedCodeRef(
artifact,
engineCommandManager.artifactGraph
)
editorManager.setHighlightRange(
err(consumedCodeRef)
? [codeRef.range]
: [codeRef.range, consumedCodeRef.range]
)
} else if (artifact?.type === 'plane') {
const codeRef = artifact.codeRef
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else {
editorManager.setHighlightRange([[0, 0]])
if (codeRefs) {
editorManager.setHighlightRange(codeRefs.map(({ range }) => range))
}
} else if (
!editorManager.highlightRange ||
@ -108,6 +54,7 @@ export function useEngineConnectionSubscriptions() {
event: 'select_with_point',
callback: (engineEvent) => {
;(async () => {
if (stateRef.current.matches('Sketch no face')) return
const event = await getEventForSelectWithPoint(engineEvent)
event && send(event)
})().catch(reportRejection)

View File

@ -28,14 +28,16 @@ export function useConvertToVariable(range?: SourceRange) {
const meta = isNodeSafeToReplace(
parsed,
range || context.selectionRanges.codeBasedSelections?.[0]?.range || []
range ||
context.selectionRanges.graphSelections?.[0]?.codeRef?.range ||
[]
)
if (trap(meta)) return
const { isSafe, value } = meta
const canReplace = isSafe && value.type !== 'Identifier'
const isOnlyOneSelection =
!!range || context.selectionRanges.codeBasedSelections.length === 1
!!range || context.selectionRanges.graphSelections.length === 1
setEnabled(canReplace && isOnlyOneSelection)
}, [context.selectionRanges])
@ -52,7 +54,7 @@ export function useConvertToVariable(range?: SourceRange) {
moveValueIntoNewVariable(
ast,
kclManager.programMemory,
range || context.selectionRanges.codeBasedSelections[0].range,
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
variableName
)

View File

@ -463,7 +463,7 @@ export class KclManager {
if (optionalParams?.focusPath) {
returnVal = {
codeBasedSelections: [],
graphSelections: [],
otherSelections: [],
}
@ -485,9 +485,11 @@ export class KclManager {
}
if (start && end) {
returnVal.codeBasedSelections.push({
type: 'default',
range: [start, end],
returnVal.graphSelections.push({
codeRef: {
range: [start, end],
pathToNode: path,
},
})
}
}

View File

@ -81,7 +81,7 @@ export async function executeAst({
false
))
await engineCommandManager.waitForAllCommands()
await engineCommandManager.waitForAllCommands(useFakeExecutor)
return {
logs: [],
errors: [],

View File

@ -22,6 +22,7 @@ import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
import { err } from 'lib/trap'
import { SimplifiedArgDetails } from './std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Artifact, codeRefFromRange } from './std/artifactGraph'
beforeAll(async () => {
await initPromise
@ -735,7 +736,7 @@ sketch003 = startSketchOn('XZ')
|> close(%)`,
codeAfter: `myVar = 5\n`,
lineOfInterest: 'line([-2.94, 2.7], %)',
type: 'default',
type: 'segment',
},
],
[
@ -761,7 +762,7 @@ const extrude001 = extrude(10, sketch001)`,
|> line([-17.67, 0.85], %)
|> close(%)\n`,
lineOfInterest: 'line([2.66, 1.17], %)',
type: 'extrude-wall',
type: 'wall',
},
],
[
@ -817,7 +818,7 @@ sketch002 = startSketchOn({
|> close(%)
`,
lineOfInterest: 'line([-11.18, -2.15], %)',
type: 'extrude-wall',
type: 'wall',
},
],
[
@ -873,7 +874,7 @@ sketch002 = startSketchOn({
|> close(%)
`,
lineOfInterest: 'startProfileAt([4.46, 5.12], %, $tag)',
type: 'end-cap',
type: 'cap',
},
],
] as const
@ -890,11 +891,12 @@ sketch002 = startSketchOn({
codeBefore.indexOf(lineOfInterest),
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
]
const artifact = { type } as Artifact
const newAst = await deleteFromSelection(
ast,
{
range,
type,
codeRef: codeRefFromRange(range, ast),
artifact,
},
execState.memory,
async () => {

View File

@ -1,5 +1,5 @@
import { Selection } from 'lib/selections'
import { err, reportRejection, trap } from 'lib/trap'
import { Selection } from 'lib/selections'
import {
Program,
CallExpression,
@ -836,7 +836,7 @@ export function createBinaryExpressionWithUnary([left, right]: [
export function giveSketchFnCallTag(
ast: Node<Program>,
range: Selection['range'],
range: SourceRange,
tag?: string
):
| {
@ -910,7 +910,7 @@ export function moveValueIntoNewVariablePath(
export function moveValueIntoNewVariable(
ast: Node<Program>,
programMemory: ProgramMemory,
sourceRange: Selection['range'],
sourceRange: SourceRange,
variableName: string
): {
modifiedAst: Node<Program>
@ -1035,18 +1035,15 @@ export async function deleteFromSelection(
({} as any)
): Promise<Node<Program> | Error> {
const astClone = structuredClone(ast)
const range = selection.range
const path = getNodePathFromSourceRange(ast, range)
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
path,
selection?.codeRef?.pathToNode,
'VariableDeclarator'
)
if (err(varDec)) return varDec
if (
(selection.type === 'extrude-wall' ||
selection.type === 'end-cap' ||
selection.type === 'start-cap') &&
(selection?.artifact?.type === 'wall' ||
selection?.artifact?.type === 'cap') &&
varDec.node.init.type === 'PipeExpression'
) {
const varDecName = varDec.node.id.name
@ -1126,7 +1123,6 @@ export async function deleteFromSelection(
sketchName
)
if (err(sketchToPreserve)) return sketchToPreserve
console.log('sketchName', sketchName)
// Can't kick off multiple requests at once as getFaceDetails
// is three engine calls in one and they conflict
const faceDetails = await getFaceDetails(sketchToPreserve.on.id)

View File

@ -13,7 +13,7 @@ import {
getPathToExtrudeForSegmentSelection,
hasValidFilletSelection,
isTagUsedInFillet,
modifyAstCloneWithFilletAndTag,
modifyAstWithFilletAndTag,
} from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst'
@ -22,6 +22,8 @@ import { Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env'
import { KclCommandValue } from 'lib/commandTypes'
import { isOverlap } from 'lib/utils'
import { codeRefFromRange } from 'lang/std/artifactGraph'
beforeAll(async () => {
await initPromise
@ -114,10 +116,9 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
]
const selection: Selections = {
codeBasedSelections: [
graphSelections: [
{
range: segmentRange,
type: 'default',
codeRef: codeRefFromRange(segmentRange, ast),
},
],
otherSelections: [],
@ -272,13 +273,6 @@ const runModifyAstCloneWithFilletAndTag = async (
code.indexOf(selectionSnippet) + selectionSnippet.length,
]
)
const selection: Selections = {
codeBasedSelections: segmentRanges.map((segmentRange) => ({
range: segmentRange,
type: 'default',
})),
otherSelections: [],
}
// radius
const radius: KclCommandValue = {
@ -289,9 +283,24 @@ const runModifyAstCloneWithFilletAndTag = async (
// executeAst
await kclManager.executeAst({ ast })
const artifactGraph = engineCommandManager.artifactGraph
const selection: Selections = {
graphSelections: segmentRanges.map((segmentRange) => {
const maybeArtifact = [...artifactGraph].find(([, a]) => {
if (!('codeRef' in a)) return false
return isOverlap(a.codeRef.range, segmentRange)
})
return {
codeRef: codeRefFromRange(segmentRange, ast),
artifact: maybeArtifact ? maybeArtifact[1] : undefined,
}
}),
otherSelections: [],
}
// apply fillet to selection
const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
const result = modifyAstWithFilletAndTag(ast, selection, radius)
if (err(result)) {
return result
}
@ -612,7 +621,6 @@ describe('Testing button states', () => {
}
const ast = astOrError
// selectionRanges
const range: [number, number] = segmentSnippet
? [
code.indexOf(segmentSnippet),
@ -621,10 +629,9 @@ describe('Testing button states', () => {
: [ast.end, ast.end] // empty line in the end of the code
const selectionRanges: Selections = {
codeBasedSelections: [
graphSelections: [
{
range,
type: 'default',
codeRef: codeRefFromRange(range, ast),
},
],
otherSelections: [],

View File

@ -32,6 +32,7 @@ import { err, trap } from 'lib/trap'
import { Selections } from 'lib/selections'
import { KclCommandValue } from 'lib/commandTypes'
import {
Artifact,
ArtifactGraph,
getSweepFromSuspectedPath,
} from 'lang/std/artifactGraph'
@ -51,7 +52,7 @@ export function applyFilletToSelection(
radius: KclCommandValue
): void | Error {
// 1. clone and modify with fillet and tag
const result = modifyAstCloneWithFilletAndTag(ast, selection, radius)
const result = modifyAstWithFilletAndTag(ast, selection, radius)
if (err(result)) return result
const { modifiedAst, pathToFilletNode } = result
@ -60,9 +61,9 @@ export function applyFilletToSelection(
updateAstAndFocus(modifiedAst, pathToFilletNode)
}
export function modifyAstCloneWithFilletAndTag(
export function modifyAstWithFilletAndTag(
ast: Node<Program>,
selection: Selections,
selections: Selections,
radius: KclCommandValue
): { modifiedAst: Node<Program>; pathToFilletNode: Array<PathToNode> } | Error {
let clonedAst = structuredClone(ast)
@ -76,16 +77,15 @@ export function modifyAstCloneWithFilletAndTag(
// Step 1: modify ast with tags and group them by extrude nodes (bodies)
const extrudeToTagsMap: Map<
PathToNode,
Array<{ tag: string; selectionType: string }>
Array<{ tag: string; artifact: Artifact }>
> = new Map()
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
for (const selectionRange of selection.codeBasedSelections) {
for (const selection of selections.graphSelections) {
const singleSelection = {
codeBasedSelections: [selectionRange],
graphSelections: [selection],
otherSelections: [],
}
const selectionType = singleSelection.codeBasedSelections[0].type
const result = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude,
@ -101,18 +101,21 @@ export function modifyAstCloneWithFilletAndTag(
)
if (err(tagResult)) return tagResult
const { tag } = tagResult
const tagInfo = { tag, selectionType }
// Group tags by their corresponding extrude node
const extrudeKey = JSON.stringify(pathToExtrudeNode)
if (lookupMap.has(extrudeKey)) {
if (lookupMap.has(extrudeKey) && selection.artifact) {
const existingPath = lookupMap.get(extrudeKey)
if (!existingPath) return new Error('Path to extrude node not found.')
extrudeToTagsMap.get(existingPath)?.push(tagInfo)
} else {
extrudeToTagsMap
.get(existingPath)
?.push({ tag, artifact: selection.artifact } as const)
} else if (selection.artifact) {
lookupMap.set(extrudeKey, pathToExtrudeNode)
extrudeToTagsMap.set(pathToExtrudeNode, [tagInfo])
extrudeToTagsMap.set(pathToExtrudeNode, [
{ tag, artifact: selection.artifact } as const,
])
}
}
@ -123,8 +126,8 @@ export function modifyAstCloneWithFilletAndTag(
const radiusValue =
'variableName' in radius ? radius.variableIdentifierAst : radius.valueAst
const tagCalls = tagInfos.map(({ tag, selectionType }) => {
return getEdgeTagCall(tag, selectionType)
const tagCalls = tagInfos.map(({ tag, artifact }) => {
return getEdgeTagCall(tag, artifact)
})
const firstTag = tagCalls[0] // can be Identifier or CallExpression (for opposite and adjacent edges)
@ -214,7 +217,7 @@ export function getPathToExtrudeForSegmentSelection(
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
const pathToSegmentNode = getNodePathFromSourceRange(
ast,
selection.codeBasedSelections[0].range
selection.graphSelections[0]?.codeRef?.range
)
const varDecNode = getNodeFromPath<VariableDeclaration>(
@ -292,14 +295,14 @@ function mutateAstWithTagForSketchSegment(
function getEdgeTagCall(
tag: string,
selectionType: string
artifact: Artifact
): Node<Identifier | CallExpression> {
let tagCall: Expr = createIdentifier(tag)
// Modify the tag based on selectionType
if (selectionType === 'edge') {
if (artifact.type === 'sweepEdge' && artifact.subType === 'opposite') {
tagCall = createCallExpressionStdLib('getOppositeEdge', [tagCall])
} else if (selectionType === 'adjacent-edge') {
} else if (artifact.type === 'sweepEdge' && artifact.subType === 'adjacent') {
tagCall = createCallExpressionStdLib('getNextAdjacentEdge', [tagCall])
}
return tagCall
@ -442,22 +445,21 @@ export const hasValidFilletSelection = ({
if (!extrudeExists) return false
// check if nothing is selected
if (selectionRanges.codeBasedSelections.length === 0) {
if (selectionRanges.graphSelections.length === 0) {
return true
}
// check if selection is last string in code
if (selectionRanges.codeBasedSelections[0].range[0] === code.length) {
if (selectionRanges.graphSelections[0]?.codeRef?.range[0] === code.length) {
return true
}
// selection exists:
for (const selection of selectionRanges.codeBasedSelections) {
for (const selection of selectionRanges.graphSelections) {
// check if all selections are in sketchLineHelperMap
const path = getNodePathFromSourceRange(ast, selection.range)
const segmentNode = getNodeFromPath<Node<CallExpression>>(
ast,
path,
selection.codeRef.pathToNode,
'CallExpression'
)
if (err(segmentNode)) return false
@ -493,9 +495,9 @@ export const hasValidFilletSelection = ({
})
// check if tag is used in fillet
if (tagExists) {
if (tagExists && selection.artifact) {
// create tag call
let tagCall: Expr = getEdgeTagCall(tag, selection.type)
let tagCall: Expr = getEdgeTagCall(tag, selection.artifact)
// check if tag is used in fillet
let inFillet = false

View File

@ -20,6 +20,7 @@ import {
createPipeSubstitution,
} from './modifyAst'
import { err } from 'lib/trap'
import { codeRefFromRange } from './std/artifactGraph'
beforeAll(async () => {
await initPromise
@ -365,7 +366,9 @@ part001 = startSketchAt([-1.41, 3.46])
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
selection: {
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(true)
})
@ -385,7 +388,9 @@ part001 = startSketchAt([-1.41, 3.46])
const result = doesPipeHaveCallExp({
calleeName: 'extrude',
ast,
selection: { type: 'default', range: [100, 101] },
selection: {
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(true)
})
@ -403,7 +408,9 @@ part001 = startSketchAt([-1.41, 3.46])
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [100, 101] },
selection: {
codeRef: codeRefFromRange([100, 101], ast),
},
})
expect(result).toEqual(false)
})
@ -415,7 +422,9 @@ part001 = startSketchAt([-1.41, 3.46])
const result = doesPipeHaveCallExp({
calleeName: 'close',
ast,
selection: { type: 'default', range: [9, 10] },
selection: {
codeRef: codeRefFromRange([9, 10], ast),
},
})
expect(result).toEqual(false)
})
@ -435,7 +444,9 @@ part001 = startSketchAt([-1.41, 3.46])
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: { type: 'default', range: [100, 101] },
selection: {
codeRef: codeRefFromRange([100, 101], ast),
},
programMemory: execState.memory,
})
expect(result).toEqual(true)
@ -454,7 +465,9 @@ part001 = startSketchAt([-1.41, 3.46])
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: { type: 'default', range: [100, 101] },
selection: {
codeRef: codeRefFromRange([100, 101], ast),
},
programMemory: execState.memory,
})
expect(result).toEqual(true)
@ -467,7 +480,9 @@ part001 = startSketchAt([-1.41, 3.46])
const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({
ast,
selection: { type: 'default', range: [10, 11] },
selection: {
codeRef: codeRefFromRange([10, 11], ast),
},
programMemory: execState.memory,
})
expect(result).toEqual(false)
@ -556,8 +571,7 @@ sketch003 = startSketchOn(extrude001, 'END')
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
range: [characterIndex, characterIndex],
type: 'default',
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)
@ -571,8 +585,7 @@ sketch003 = startSketchOn(extrude001, 'END')
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
range: [characterIndex, characterIndex],
type: 'default',
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)
@ -586,8 +599,7 @@ sketch003 = startSketchOn(extrude001, 'END')
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
range: [characterIndex, characterIndex],
type: 'default',
codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
},
ast
)

View File

@ -31,6 +31,7 @@ import {
import { err, Reason } from 'lib/trap'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
/**
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
@ -130,7 +131,7 @@ function moreNodePathFromSourceRange(
| VariableDeclaration
| ReturnStatement
>,
sourceRange: Selection['range'],
sourceRange: SourceRange,
previousPath: PathToNode = [['body', '']]
): PathToNode {
const [start, end] = sourceRange
@ -381,7 +382,7 @@ function moreNodePathFromSourceRange(
export function getNodePathFromSourceRange(
node: Program,
sourceRange: Selection['range'],
sourceRange: SourceRange,
previousPath: PathToNode = [['body', '']]
): PathToNode {
const [start, end] = sourceRange || []
@ -560,7 +561,7 @@ export function findAllPreviousVariablesPath(
export function findAllPreviousVariables(
ast: Program,
programMemory: ProgramMemory,
sourceRange: Selection['range'],
sourceRange: SourceRange,
type: 'number' | 'string' = 'number'
): {
variables: PrevVariable<typeof type extends 'number' ? number : string>[]
@ -705,19 +706,26 @@ export function isValueZero(val?: Expr): boolean {
export function isLinesParallelAndConstrained(
ast: Program,
artifactGraph: ArtifactGraph,
programMemory: ProgramMemory,
primaryLine: Selection,
secondaryLine: Selection
):
| {
isParallelAndConstrained: boolean
sourceRange: SourceRange
selection: Selection | null
}
| Error {
try {
const EPSILON = 0.005
const primaryPath = getNodePathFromSourceRange(ast, primaryLine.range)
const secondaryPath = getNodePathFromSourceRange(ast, secondaryLine.range)
const primaryPath = getNodePathFromSourceRange(
ast,
primaryLine?.codeRef?.range
)
const secondaryPath = getNodePathFromSourceRange(
ast,
secondaryLine?.codeRef?.range
)
const _secondaryNode = getNodeFromPath<CallExpression>(
ast,
secondaryPath,
@ -733,12 +741,15 @@ export function isLinesParallelAndConstrained(
if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange(
sg,
primaryLine.range
primaryLine?.codeRef?.range
)
if (err(_primarySegment)) return _primarySegment
const primarySegment = _primarySegment.segment
const _segment = getSketchSegmentFromSourceRange(sg, secondaryLine.range)
const _segment = getSketchSegmentFromSourceRange(
sg,
secondaryLine?.codeRef?.range
)
if (err(_segment)) return _segment
const { segment: secondarySegment, index: secondaryIndex } = _segment
const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
@ -751,7 +762,7 @@ export function isLinesParallelAndConstrained(
Math.abs(primaryAngle - secondaryAngle) < EPSILON ||
Math.abs(primaryAngle - secondaryAngleAlt) < EPSILON
// is secordary line fully constrain, or has constrain type of 'angle'
// is secondary line fully constrain, or has constrain type of 'angle'
const secondaryFirstArg = getFirstArg(secondaryNode)
if (err(secondaryFirstArg)) return secondaryFirstArg
@ -761,14 +772,14 @@ export function isLinesParallelAndConstrained(
)
const constraintLevelMeta = getConstraintLevelFromSourceRange(
secondaryLine.range,
secondaryLine?.codeRef.range,
ast
)
if (err(constraintLevelMeta)) {
console.error(constraintLevelMeta)
return {
isParallelAndConstrained: false,
sourceRange: [0, 0],
selection: null,
}
}
const constraintLevel = constraintLevelMeta.level
@ -785,12 +796,15 @@ export function isLinesParallelAndConstrained(
return {
isParallelAndConstrained,
sourceRange: prevSourceRange,
selection: {
codeRef: codeRefFromRange(prevSourceRange, ast),
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
},
}
} catch (e) {
return {
isParallelAndConstrained: false,
sourceRange: [0, 0],
selection: null,
}
}
}
@ -804,10 +818,9 @@ export function doesPipeHaveCallExp({
ast: Program
selection: Selection
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
ast,
pathToNode,
selection?.codeRef?.pathToNode,
'PipeExpression'
)
if (err(pipeExpressionMeta)) {
@ -832,10 +845,9 @@ export function hasExtrudeSketch({
selection: Selection
programMemory: ProgramMemory
}): boolean {
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
const varDecMeta = getNodeFromPath<VariableDeclaration>(
ast,
pathToNode,
selection?.codeRef?.pathToNode,
'VariableDeclaration'
)
if (err(varDecMeta)) {
@ -856,9 +868,9 @@ export function isSingleCursorInPipe(
selectionRanges: Selections,
ast: Program
) {
if (selectionRanges.codeBasedSelections.length !== 1) return false
const selection = selectionRanges.codeBasedSelections[0]
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
if (selectionRanges.graphSelections.length !== 1) return false
const selection = selectionRanges.graphSelections[0]
const pathToNode = getNodePathFromSourceRange(ast, selection?.codeRef?.range)
const nodeTypes = pathToNode.map(([, type]) => type)
if (nodeTypes.includes('FunctionExpression')) return false
if (!nodeTypes.includes('VariableDeclaration')) return false
@ -928,10 +940,9 @@ export function findUsesOfTagInPipe(
}
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const path = getNodePathFromSourceRange(ast, selection.range)
const _node = getNodeFromPath<Node<PipeExpression>>(
ast,
path,
selection.codeRef.pathToNode,
'PipeExpression'
)
if (err(_node)) return false
@ -939,7 +950,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
if (pipeExpression.type !== 'PipeExpression') return false
const _varDec = getNodeFromPath<VariableDeclarator>(
ast,
path,
selection.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(_varDec)) return false

View File

@ -16,6 +16,7 @@ Map {
0,
],
},
"id": "UUID",
"pathIds": [
"UUID",
],
@ -35,6 +36,7 @@ Map {
0,
],
},
"id": "UUID",
"planeId": "UUID",
"segIds": [
"UUID",
@ -65,6 +67,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -88,6 +91,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -110,6 +114,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -132,6 +137,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -151,10 +157,12 @@ Map {
],
},
"edgeIds": [],
"id": "UUID",
"pathId": "UUID",
"type": "segment",
},
"UUID-7" => {
"id": "UUID",
"pathId": "UUID",
"type": "solid2D",
},
@ -182,6 +190,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"subType": "extrusion",
"surfaceIds": [
@ -196,6 +205,7 @@ Map {
},
"UUID-9" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -203,6 +213,7 @@ Map {
},
"UUID-10" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [
"UUID",
],
@ -212,6 +223,7 @@ Map {
},
"UUID-11" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -219,6 +231,7 @@ Map {
},
"UUID-12" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -226,6 +239,7 @@ Map {
},
"UUID-13" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"subType": "start",
"sweepId": "UUID",
@ -233,54 +247,63 @@ Map {
},
"UUID-14" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"subType": "end",
"sweepId": "UUID",
"type": "cap",
},
"UUID-15" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-16" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-17" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-18" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-19" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-20" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-21" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-22" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
@ -302,6 +325,7 @@ Map {
},
"consumedEdgeId": "UUID",
"edgeIds": [],
"id": "UUID",
"subType": "fillet",
"type": "edgeCut",
},
@ -319,6 +343,7 @@ Map {
0,
],
},
"id": "UUID",
"planeId": "UUID",
"segIds": [
"UUID",
@ -348,6 +373,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -370,6 +396,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -392,6 +419,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"surfaceId": "UUID",
"type": "segment",
@ -411,10 +439,12 @@ Map {
],
},
"edgeIds": [],
"id": "UUID",
"pathId": "UUID",
"type": "segment",
},
"UUID-29" => {
"id": "UUID",
"pathId": "UUID",
"type": "solid2D",
},
@ -440,6 +470,7 @@ Map {
"UUID",
"UUID",
],
"id": "UUID",
"pathId": "UUID",
"subType": "extrusion",
"surfaceIds": [
@ -453,6 +484,7 @@ Map {
},
"UUID-31" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -460,6 +492,7 @@ Map {
},
"UUID-32" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -467,6 +500,7 @@ Map {
},
"UUID-33" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"segId": "UUID",
"sweepId": "UUID",
@ -474,6 +508,7 @@ Map {
},
"UUID-34" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"subType": "start",
"sweepId": "UUID",
@ -481,42 +516,49 @@ Map {
},
"UUID-35" => {
"edgeCutEdgeIds": [],
"id": "UUID",
"pathIds": [],
"subType": "end",
"sweepId": "UUID",
"type": "cap",
},
"UUID-36" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-37" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-38" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-39" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-40" => {
"id": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
},
"UUID-41" => {
"id": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",

View File

@ -438,7 +438,8 @@ async function GraphTheGraph(
if (
propName === 'type' ||
propName === 'codeRef' ||
propName === 'subType'
propName === 'subType' ||
propName === 'id'
)
return
if (Array.isArray(value))
@ -662,6 +663,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'path',
segIds: [],
id: expect.any(String),
planeId: 'UUID-1',
sweepId: '',
codeRef: {
@ -675,6 +677,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'sweep',
subType: 'extrusion',
pathId: expect.any(String),
id: expect.any(String),
surfaceIds: [],
edgeIds: [],
codeRef: {
@ -684,6 +687,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'path',
id: expect.any(String),
segIds: expect.any(Array),
planeId: expect.any(String),
sweepId: expect.any(String),
@ -697,6 +701,7 @@ describe('testing getArtifactsToUpdate', () => {
expect(getUpdateObjects('extend_path')).toEqual([
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: '',
edgeIds: [],
@ -707,6 +712,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'path',
id: expect.any(String),
segIds: expect.any(Array),
planeId: expect.any(String),
sweepId: expect.any(String),
@ -721,6 +727,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'edgeCut',
subType: 'fillet',
id: expect.any(String),
consumedEdgeId: expect.any(String),
edgeIds: [],
surfaceId: '',
@ -731,6 +738,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: expect.any(String),
edgeIds: expect.any(Array),
@ -744,6 +752,7 @@ describe('testing getArtifactsToUpdate', () => {
expect(getUpdateObjects('solid3d_get_extrusion_face_info')).toEqual([
{
type: 'wall',
id: expect.any(String),
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
@ -751,6 +760,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: expect.any(String),
edgeIds: expect.any(Array),
@ -762,6 +772,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -772,6 +783,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'wall',
id: expect.any(String),
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
@ -779,6 +791,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: expect.any(String),
edgeIds: expect.any(Array),
@ -790,6 +803,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -800,6 +814,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'wall',
id: expect.any(String),
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
@ -807,6 +822,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: expect.any(String),
edgeIds: expect.any(Array),
@ -819,6 +835,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -829,6 +846,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'wall',
id: expect.any(String),
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
@ -836,6 +854,7 @@ describe('testing getArtifactsToUpdate', () => {
},
{
type: 'segment',
id: expect.any(String),
pathId: expect.any(String),
surfaceId: expect.any(String),
edgeIds: expect.any(Array),
@ -847,6 +866,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -858,6 +878,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'cap',
subType: 'start',
id: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
pathIds: [],
@ -865,6 +886,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -876,6 +898,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'cap',
subType: 'end',
id: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
pathIds: [],
@ -883,6 +906,7 @@ describe('testing getArtifactsToUpdate', () => {
{
type: 'sweep',
subType: 'extrusion',
id: expect.any(String),
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),

View File

@ -5,36 +5,40 @@ import { err } from 'lib/trap'
export type ArtifactId = string
interface CommonCommandProperties {
interface BaseArtifact {
id: ArtifactId
}
export interface CodeRef {
range: SourceRange
pathToNode: PathToNode
}
export interface PlaneArtifact {
export interface PlaneArtifact extends BaseArtifact {
type: 'plane'
pathIds: Array<ArtifactId>
codeRef: CommonCommandProperties
codeRef: CodeRef
}
export interface PlaneArtifactRich {
export interface PlaneArtifactRich extends BaseArtifact {
type: 'plane'
paths: Array<PathArtifact>
codeRef: CommonCommandProperties
codeRef: CodeRef
}
export interface PathArtifact {
export interface PathArtifact extends BaseArtifact {
type: 'path'
planeId: ArtifactId
segIds: Array<ArtifactId>
sweepId: ArtifactId
solid2dId?: ArtifactId
codeRef: CommonCommandProperties
codeRef: CodeRef
}
interface solid2D {
interface solid2D extends BaseArtifact {
type: 'solid2D'
pathId: ArtifactId
}
export interface PathArtifactRich {
export interface PathArtifactRich extends BaseArtifact {
type: 'path'
/** A path must always lie on a plane */
plane: PlaneArtifact | WallArtifact
@ -42,52 +46,52 @@ export interface PathArtifactRich {
segments: Array<SegmentArtifact>
/** A path may not result in a sweep artifact */
sweep?: SweepArtifact
codeRef: CommonCommandProperties
codeRef: CodeRef
}
export interface SegmentArtifact {
export interface SegmentArtifact extends BaseArtifact {
type: 'segment'
pathId: ArtifactId
surfaceId: ArtifactId
edgeIds: Array<ArtifactId>
edgeCutId?: ArtifactId
codeRef: CommonCommandProperties
codeRef: CodeRef
}
interface SegmentArtifactRich {
interface SegmentArtifactRich extends BaseArtifact {
type: 'segment'
path: PathArtifact
surf: WallArtifact
edges: Array<SweepEdge>
edgeCut?: EdgeCut
codeRef: CommonCommandProperties
codeRef: CodeRef
}
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
interface SweepArtifact {
interface SweepArtifact extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve'
pathId: string
surfaceIds: Array<string>
edgeIds: Array<string>
codeRef: CommonCommandProperties
codeRef: CodeRef
}
interface SweepArtifactRich {
interface SweepArtifactRich extends BaseArtifact {
type: 'sweep'
subType: 'extrusion' | 'revolve'
path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge>
codeRef: CommonCommandProperties
codeRef: CodeRef
}
interface WallArtifact {
interface WallArtifact extends BaseArtifact {
type: 'wall'
segId: ArtifactId
edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId
pathIds: Array<ArtifactId>
}
interface CapArtifact {
interface CapArtifact extends BaseArtifact {
type: 'cap'
subType: 'start' | 'end'
edgeCutEdgeIds: Array<ArtifactId>
@ -95,7 +99,7 @@ interface CapArtifact {
pathIds: Array<ArtifactId>
}
interface SweepEdge {
interface SweepEdge extends BaseArtifact {
type: 'sweepEdge'
segId: ArtifactId
sweepId: ArtifactId
@ -103,16 +107,16 @@ interface SweepEdge {
}
/** A edgeCut is a more generic term for both fillet or chamfer */
interface EdgeCut {
interface EdgeCut extends BaseArtifact {
type: 'edgeCut'
subType: 'fillet' | 'chamfer'
consumedEdgeId: ArtifactId
edgeIds: Array<ArtifactId>
surfaceId: ArtifactId
codeRef: CommonCommandProperties
codeRef: CodeRef
}
interface EdgeCutEdge {
interface EdgeCutEdge extends BaseArtifact {
type: 'edgeCutEdge'
edgeCutId: ArtifactId
surfaceId: ArtifactId
@ -257,6 +261,7 @@ export function getArtifactsToUpdate({
id,
artifact: {
type: 'plane',
id,
pathIds: [],
codeRef: { range, pathToNode },
},
@ -274,6 +279,7 @@ export function getArtifactsToUpdate({
id: currentPlaneId,
artifact: {
type: 'wall',
id: currentPlaneId,
segId: existingPlane.segId,
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
sweepId: existingPlane.sweepId,
@ -283,7 +289,10 @@ export function getArtifactsToUpdate({
]
} else {
return [
{ id: currentPlaneId, artifact: { type: 'plane', pathIds, codeRef } },
{
id: currentPlaneId,
artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
},
]
}
} else if (cmd.type === 'start_path') {
@ -291,6 +300,7 @@ export function getArtifactsToUpdate({
id,
artifact: {
type: 'path',
id,
segIds: [],
planeId: currentPlaneId,
sweepId: '',
@ -303,7 +313,7 @@ export function getArtifactsToUpdate({
if (plane?.type === 'plane') {
returnArr.push({
id: currentPlaneId,
artifact: { type: 'plane', pathIds: [id], codeRef },
artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
})
}
if (plane?.type === 'wall') {
@ -311,6 +321,7 @@ export function getArtifactsToUpdate({
id: currentPlaneId,
artifact: {
type: 'wall',
id: currentPlaneId,
segId: plane.segId,
edgeCutEdgeIds: plane.edgeCutEdgeIds,
sweepId: plane.sweepId,
@ -325,6 +336,7 @@ export function getArtifactsToUpdate({
id,
artifact: {
type: 'segment',
id,
pathId,
surfaceId: '',
edgeIds: [],
@ -343,7 +355,11 @@ export function getArtifactsToUpdate({
) {
returnArr.push({
id: response.data.modeling_response.data.face_id,
artifact: { type: 'solid2D', pathId },
artifact: {
type: 'solid2D',
id: response.data.modeling_response.data.face_id,
pathId,
},
})
const path = getArtifact(pathId)
if (path?.type === 'path')
@ -363,6 +379,7 @@ export function getArtifactsToUpdate({
artifact: {
type: 'sweep',
subType: subType,
id,
pathId: cmd.target,
surfaceIds: [],
edgeIds: [],
@ -394,6 +411,7 @@ export function getArtifactsToUpdate({
id: face_id,
artifact: {
type: 'wall',
id: face_id,
segId: curve_id,
edgeCutEdgeIds: [],
sweepId: path.sweepId,
@ -426,6 +444,7 @@ export function getArtifactsToUpdate({
id: face_id,
artifact: {
type: 'cap',
id: face_id,
subType: cap === 'bottom' ? 'start' : 'end',
edgeCutEdgeIds: [],
sweepId: path.sweepId,
@ -472,6 +491,7 @@ export function getArtifactsToUpdate({
id: response.data.modeling_response.data.edge,
artifact: {
type: 'sweepEdge',
id: response.data.modeling_response.data.edge,
subType:
cmd.type === 'solid3d_get_next_adjacent_edge'
? 'adjacent'
@ -500,6 +520,7 @@ export function getArtifactsToUpdate({
id,
artifact: {
type: 'edgeCut',
id,
subType: cmd.cut_type,
consumedEdgeId: cmd.edge_id,
edgeIds: [],
@ -590,6 +611,7 @@ export function expandPlane(
)
return {
type: 'plane',
id: plane.id,
paths: Array.from(paths.values()),
codeRef: plane.codeRef,
}
@ -620,6 +642,7 @@ export function expandPath(
if (err(plane)) return plane
return {
type: 'path',
id: path.id,
segments: Array.from(segs.values()),
sweep,
plane,
@ -647,6 +670,7 @@ export function expandSweep(
return {
type: 'sweep',
subType: sweep.subType,
id: sweep.id,
surfaces: Array.from(surfs.values()),
edges: Array.from(edges.values()),
path,
@ -682,6 +706,7 @@ export function expandSegment(
return {
type: 'segment',
id: segment.id,
path,
surf,
edges: Array.from(edges.values()),
@ -693,7 +718,7 @@ export function expandSegment(
export function getCapCodeRef(
cap: CapArtifact,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
): CodeRef | Error {
const sweep = getArtifactOfTypes(
{ key: cap.sweepId, types: ['sweep'] },
artifactGraph
@ -710,7 +735,7 @@ export function getCapCodeRef(
export function getSolid2dCodeRef(
solid2D: solid2D,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
): CodeRef | Error {
const path = getArtifactOfTypes(
{ key: solid2D.pathId, types: ['path'] },
artifactGraph
@ -722,7 +747,7 @@ export function getSolid2dCodeRef(
export function getWallCodeRef(
wall: WallArtifact,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
): CodeRef | Error {
const seg = getArtifactOfTypes(
{ key: wall.segId, types: ['segment'] },
artifactGraph
@ -734,7 +759,7 @@ export function getWallCodeRef(
export function getSweepEdgeCodeRef(
edge: SweepEdge,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
): CodeRef | Error {
const seg = getArtifactOfTypes(
{ key: edge.segId, types: ['segment'] },
artifactGraph
@ -742,10 +767,10 @@ export function getSweepEdgeCodeRef(
if (err(seg)) return seg
return seg.codeRef
}
export function getEdgeCuteConsumedCodeRef(
export function getEdgeCutConsumedCodeRef(
edge: EdgeCut,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
): CodeRef | Error {
const seg = getArtifactOfTypes(
{ key: edge.consumedEdgeId, types: ['segment', 'sweepEdge'] },
artifactGraph
@ -803,3 +828,46 @@ export function getSweepFromSuspectedPath(
artifactGraph
)
}
export function getCodeRefsByArtifactId(
id: string,
artifactGraph: ArtifactGraph
): Array<CodeRef> | null {
const artifact = artifactGraph.get(id)
if (artifact?.type === 'solid2D') {
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
if (err(codeRef)) return null
return [codeRef]
} else if (artifact?.type === 'cap') {
const codeRef = getCapCodeRef(artifact, artifactGraph)
if (err(codeRef)) return null
return [codeRef]
} else if (artifact?.type === 'wall') {
const extrusion = getSweepFromSuspectedSweepSurface(id, artifactGraph)
const codeRef = getWallCodeRef(artifact, artifactGraph)
if (err(codeRef)) return null
return err(extrusion) ? [codeRef] : [codeRef, extrusion.codeRef]
} else if (artifact?.type === 'sweepEdge') {
const codeRef = getSweepEdgeCodeRef(artifact, artifactGraph)
if (err(codeRef)) return null
return [codeRef]
} else if (artifact?.type === 'segment') {
return [artifact.codeRef]
} else if (artifact?.type === 'edgeCut') {
const codeRef = artifact.codeRef
const consumedCodeRef = getEdgeCutConsumedCodeRef(artifact, artifactGraph)
if (err(consumedCodeRef)) return [codeRef]
return [codeRef, consumedCodeRef]
} else if (artifact && 'codeRef' in artifact) {
return [artifact.codeRef]
} else {
return null
}
}
export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
return {
range,
pathToNode: getNodePathFromSourceRange(ast, range),
}
}

View File

@ -1,4 +1,4 @@
import { Program, SourceRange } from 'lang/wasm'
import { SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
@ -1398,11 +1398,6 @@ export class EngineCommandManager extends EventTarget {
this._camControlsCameraChange = cb
}
private getAst: () => Program = () =>
({ start: 0, end: 0, body: [], nonCodeMeta: {} } as any)
set getAstCb(cb: () => Program) {
this.getAst = cb
}
private makeDefaultPlanes: () => Promise<DefaultPlanes> | null = () => null
private modifyGrid: (hidden: boolean) => Promise<void> | null = () => null
@ -2125,18 +2120,30 @@ export class EngineCommandManager extends EventTarget {
* When an execution takes place we want to wait until we've got replies for all of the commands
* When this is done when we build the artifact map synchronously.
*/
async waitForAllCommands() {
async waitForAllCommands(useFakeExecutor = false) {
await Promise.all(Object.values(this.pendingCommands).map((a) => a.promise))
this.artifactGraph = createArtifactGraph({
orderedCommands: this.orderedCommands,
responseMap: this.responseMap,
ast: this.getAst(),
setTimeout(() => {
// the ast is wrong without this one tick timeout.
// an example is `Solids should be select and deletable` e2e test will fail
// because the out of date ast messes with selections
// TODO: race condition
if (!this?.kclManager) return
this.artifactGraph = createArtifactGraph({
orderedCommands: this.orderedCommands,
responseMap: this.responseMap,
ast: this.kclManager.ast,
})
if (useFakeExecutor) {
// mock executions don't produce an artifactGraph, so this will always be empty
// skipping the below logic to wait for the next real execution
return
}
if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null)
} else {
this.deferredArtifactPopulated(null)
}
})
if (this.artifactGraph.size) {
this.deferredArtifactEmptied(null)
} else {
this.deferredArtifactPopulated(null)
}
}
/**

View File

@ -1,13 +1,20 @@
import { parse, Sketch, recast, initPromise, sketchFromKclValue } from '../wasm'
import {
parse,
Sketch,
recast,
initPromise,
sketchFromKclValue,
SourceRange,
} from '../wasm'
import {
ConstraintType,
getTransformInfos,
transformAstSketchLines,
} from './sketchcombos'
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from 'lib/selections'
import { enginelessExecutor } from '../../lib/testHelpers'
import { err } from 'lib/trap'
import { codeRefFromRange } from './artifactGraph'
beforeAll(async () => {
await initPromise
@ -27,16 +34,17 @@ async function testingSwapSketchFnCall({
originalRange: [number, number]
}> {
const startIndex = inputCode.indexOf(callToSwap)
const range: Selection = {
type: 'default',
range: [startIndex, startIndex + callToSwap.length],
}
const range: SourceRange = [startIndex, startIndex + callToSwap.length]
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast)
const selections = {
codeBasedSelections: [range],
graphSelections: [
{
codeRef: codeRefFromRange(range, ast),
},
],
otherSelections: [],
}
const transformInfos = getTransformInfos(selections, ast, constraintType)
@ -57,7 +65,7 @@ async function testingSwapSketchFnCall({
return {
newCode,
originalRange: range.range,
originalRange: range,
}
}

View File

@ -1,4 +1,4 @@
import { parse, Expr, recast, initPromise } from '../wasm'
import { parse, Expr, recast, initPromise, Program } from '../wasm'
import {
getConstraintType,
getTransformInfos,
@ -9,9 +9,10 @@ import {
getConstraintLevelFromSourceRange,
} from './sketchcombos'
import { ToolTip } from 'lang/langHelpers'
import { Selection, Selections } from 'lib/selections'
import { Selections, Selection } from 'lib/selections'
import { err } from 'lib/trap'
import { enginelessExecutor } from '../../lib/testHelpers'
import { codeRefFromRange } from './artifactGraph'
beforeAll(async () => {
await initPromise
@ -87,10 +88,10 @@ function getConstraintTypeFromSourceHelper2(
}
function makeSelections(
codeBaseSelections: Selections['codeBasedSelections']
graphSelections: Selections['graphSelections']
): Selections {
return {
codeBasedSelections: codeBaseSelections,
graphSelections: graphSelections,
otherSelections: [],
}
}
@ -112,7 +113,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|> close(%)
`
const selectLine = (script: string, lineNumber: number): Selection => {
const selectLine = (
script: string,
lineNumber: number,
ast: Program
): Selection => {
const lines = script.split('\n')
const codeBeforeLine = lines.slice(0, lineNumber).join('\n').length
const line = lines.find((_, i) => i === lineNumber)
@ -124,14 +129,13 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
const start = codeBeforeLine + line.indexOf('|> ' + 5)
const range: [number, number] = [start, start]
return {
type: 'default',
range,
codeRef: codeRefFromRange(range, ast),
}
}
async function applyTransformation(
inputCode: string,
selectionRanges: Selections['codeBasedSelections']
selectionRanges: Selections['graphSelections']
) {
const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
@ -157,9 +161,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
}
it(`Should reorder when user selects first-to-last`, async () => {
const selectionRanges: Selections['codeBasedSelections'] = [
selectLine(inputScript, 3),
selectLine(inputScript, 4),
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 3, ast),
selectLine(inputScript, 4, ast),
]
const newCode = await applyTransformation(inputScript, selectionRanges)
@ -167,9 +173,11 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
})
it(`Should reorder when user selects last-to-first`, async () => {
const selectionRanges: Selections['codeBasedSelections'] = [
selectLine(inputScript, 4),
selectLine(inputScript, 3),
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 4, ast),
selectLine(inputScript, 3, ast),
]
const newCode = await applyTransformation(inputScript, selectionRanges)
@ -288,15 +296,14 @@ part001 = startSketchOn('XY')
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('//'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
type: 'default',
range: [start, start],
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -379,15 +386,14 @@ part001 = startSketchOn('XY')
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for horizontal constraint'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
type: 'default',
range: [start, start],
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -441,15 +447,14 @@ part001 = startSketchOn('XY')
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for vertical constraint'))
.map((ln) => {
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
type: 'default',
range: [start, start],
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -536,7 +541,7 @@ async function helperThing(
const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n')
.filter((ln) =>
linesOfInterest.some((lineOfInterest) => ln.includes(lineOfInterest))
@ -545,8 +550,7 @@ async function helperThing(
const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7
return {
type: 'default',
range: [start, start],
codeRef: codeRefFromRange([start, start], ast),
}
})
@ -605,7 +609,7 @@ part001 = startSketchOn('XY')
const ast = parse(code)
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
constraintLevels.forEach((constraintLevel) => {
const recursivelySeachCommentsAndCheckConstraintLevel = (
const recursivelySearchCommentsAndCheckConstraintLevel = (
str: string,
offset: number = 0
): null => {
@ -622,12 +626,12 @@ part001 = startSketchOn('XY')
throw expectedConstraintLevel
}
expect(expectedConstraintLevel.level).toBe(constraintLevel)
return recursivelySeachCommentsAndCheckConstraintLevel(
return recursivelySearchCommentsAndCheckConstraintLevel(
str,
index + constraintLevel.length
)
}
recursivelySeachCommentsAndCheckConstraintLevel(code)
recursivelySearchCommentsAndCheckConstraintLevel(code)
})
})
})

View File

@ -7,7 +7,7 @@ import {
TransformInfo,
} from './stdTypes'
import { ToolTip, toolTips } from 'lang/langHelpers'
import { Selections, Selection } from 'lib/selections'
import { Selections } from 'lib/selections'
import { cleanErrs, err } from 'lib/trap'
import {
CallExpression,
@ -19,6 +19,7 @@ import {
ProgramMemory,
sketchFromKclValue,
Literal,
SourceRange,
} from '../wasm'
import {
getNodeFromPath,
@ -1483,11 +1484,8 @@ export function getTransformInfos(
ast: Program,
constraintType: ConstraintType
): TransformInfo[] {
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
getNodePathFromSourceRange(ast, range)
)
const nodes = paths.map((pathToNode) =>
getNodeFromPath<Expr>(ast, pathToNode, 'CallExpression')
const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
getNodeFromPath<Expr>(ast, codeRef.pathToNode, 'CallExpression')
)
try {
@ -1515,12 +1513,8 @@ export function getRemoveConstraintsTransforms(
ast: Program,
constraintType: ConstraintType
): TransformInfo[] | Error {
// return ()
const paths = selectionRanges.codeBasedSelections.map((selectionRange) =>
getNodePathFromSourceRange(ast, selectionRange.range)
)
const nodes = paths.map((pathToNode) =>
getNodeFromPath<Expr>(ast, pathToNode)
const nodes = selectionRanges.graphSelections.map(({ codeRef }) =>
getNodeFromPath<Expr>(ast, codeRef.pathToNode)
)
const theTransforms = nodes.map((nodeMeta) => {
@ -1571,11 +1565,10 @@ export function transformSecondarySketchLinesTagFirst({
// We need to sort the selections by their start position
// so that we can process them in dependency order and not write invalid KCL.
const sortedCodeBasedSelections =
selectionRanges.codeBasedSelections.toSorted(
(a, b) => a.range[0] - b.range[0]
)
const primarySelection = sortedCodeBasedSelections[0].range
const sortedCodeBasedSelections = selectionRanges.graphSelections.toSorted(
(a, b) => a?.codeRef?.range[0] - b?.codeRef?.range[0]
)
const primarySelection = sortedCodeBasedSelections[0]?.codeRef?.range
const secondarySelections = sortedCodeBasedSelections.slice(1)
const _tag = giveSketchFnCallTag(ast, primarySelection, forceSegName)
@ -1586,7 +1579,7 @@ export function transformSecondarySketchLinesTagFirst({
ast: modifiedAst,
selectionRanges: {
...selectionRanges,
codeBasedSelections: secondarySelections,
graphSelections: secondarySelections,
},
referencedSegmentRange: primarySelection,
transformInfos,
@ -1634,8 +1627,8 @@ export function transformAstSketchLines({
transformInfos: TransformInfo[]
programMemory: ProgramMemory
referenceSegName: string
referencedSegmentRange?: SourceRange
forceValueUsedInTransform?: BinaryPart
referencedSegmentRange?: Selection['range']
}):
| {
modifiedAst: Node<Program>
@ -1786,11 +1779,11 @@ export function transformAstSketchLines({
}
}
if ('codeBasedSelections' in selectionRanges) {
if ('graphSelections' in selectionRanges) {
// If the processing of any of the selections failed, return the first error
const maybeProcessErrors = selectionRanges.codeBasedSelections
.map(({ range }, index) =>
processSelection(getNodePathFromSourceRange(node, range), index)
const maybeProcessErrors = selectionRanges.graphSelections
.map(({ codeRef }, index) =>
processSelection(getNodePathFromSourceRange(node, codeRef.range), index)
)
.filter(err)
@ -1838,7 +1831,7 @@ function getArgLiteralVal(arg: Literal): number | Error {
export type ConstraintLevel = 'free' | 'partial' | 'full'
export function getConstraintLevelFromSourceRange(
cursorRange: Selection['range'],
cursorRange: SourceRange,
ast: Program | Error
): Error | { range: [number, number]; level: ConstraintLevel } {
if (err(ast)) return ast

View File

@ -1,52 +1,13 @@
import { Selections } from 'lib/selections'
import {
Program,
PathToNode,
CallExpression,
Literal,
ArrayExpression,
BinaryExpression,
} from './wasm'
import { getNodeFromPath } from './queryAst'
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
import { isOverlap } from 'lib/utils'
import { err } from 'lib/trap'
export function pathMapToSelections(
ast: Program,
prevSelections: Selections,
pathToNodeMap: { [key: number]: PathToNode }
): Selections {
const newSelections: Selections = {
...prevSelections,
codeBasedSelections: [],
}
Object.entries(pathToNodeMap).forEach(([index, path]) => {
const nodeMeta = getNodeFromPath<any>(ast, path)
if (err(nodeMeta)) return
const node = nodeMeta.node as any
const selection = prevSelections.codeBasedSelections[Number(index)]
if (node) {
if (
selection.type === 'base-edgeCut' ||
selection.type === 'adjacent-edgeCut' ||
selection.type === 'opposite-edgeCut'
) {
newSelections.codeBasedSelections.push({
range: [node.start, node.end],
type: selection.type,
secondaryRange: selection.secondaryRange,
})
} else {
newSelections.codeBasedSelections.push({
range: [node.start, node.end],
type: selection.type,
})
}
}
})
return newSelections
}
export function updatePathToNodeFromMap(
oldPath: PathToNode,
@ -72,11 +33,11 @@ export function isCursorInSketchCommandRange(
{
types: ['segment', 'path'],
predicate: (artifact) => {
return selectionRanges.codeBasedSelections.some(
return selectionRanges.graphSelections.some(
(selection) =>
Array.isArray(selection?.range) &&
Array.isArray(selection?.codeRef?.range) &&
Array.isArray(artifact?.codeRef?.range) &&
isOverlap(selection.range, artifact.codeRef.range)
isOverlap(selection?.codeRef?.range, artifact.codeRef.range)
)
},
},

View File

@ -233,8 +233,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
args: {
selection: {
inputType: 'selection',
// TODO: These are products of an extrude
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
selectionTypes: ['solid2D', 'segment'],
multiple: false, // TODO: multiple selection
required: true,
skip: true,
@ -265,7 +264,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
args: {
selection: {
inputType: 'selection',
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
selectionTypes: ['solid2D', 'segment'],
multiple: false, // TODO: multiple selection
required: true,
skip: true,
@ -284,20 +283,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
args: {
selection: {
inputType: 'selection',
selectionTypes: [
'default',
'line-end',
'line-mid',
'extrude-wall',
'solid2D',
'start-cap',
'end-cap',
'point',
'edge',
'line',
'arc',
'all',
],
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: true,
required: true,
skip: false,

View File

@ -1,12 +1,12 @@
import { CustomIconName } from 'components/CustomIcon'
import { AllMachines } from 'hooks/useStateMachineCommands'
import { Actor, AnyStateMachine, ContextFrom, EventFrom } from 'xstate'
import { Selection } from './selections'
import { Identifier, Expr, VariableDeclaration } from 'lang/wasm'
import { commandBarMachine } from 'machines/commandBarMachine'
import { ReactNode } from 'react'
import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Artifact } from 'lang/std/artifactGraph'
type Icon = CustomIconName
const PLATFORMS = ['both', 'web', 'desktop'] as const
@ -144,7 +144,7 @@ export type CommandArgumentConfig<
}
| {
inputType: 'selection'
selectionTypes: Selection['type'][]
selectionTypes: Artifact['type'][]
multiple: boolean
}
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
@ -218,7 +218,7 @@ export type CommandArgument<
}
| {
inputType: 'selection'
selectionTypes: Selection['type'][]
selectionTypes: Artifact['type'][]
multiple: boolean
}
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value

View File

@ -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,
}
}

View File

@ -18,8 +18,6 @@ window.tearDown = engineCommandManager.tearDown
export const kclManager = new KclManager(engineCommandManager)
engineCommandManager.kclManager = kclManager
engineCommandManager.getAstCb = () => kclManager.ast
export const sceneInfra = new SceneInfra(engineCommandManager)
engineCommandManager.camControlsCameraChange = sceneInfra.onCameraChange

View File

@ -35,8 +35,8 @@ export function useCalculateKclExpression({
const { programMemory, code } = useKclContext()
const { context } = useModelingContext()
const selectionRange:
| (typeof context.selectionRanges.codeBasedSelections)[number]['range']
| undefined = context.selectionRanges.codeBasedSelections[0]?.range
| (typeof context)['selectionRanges']['graphSelections'][number]['codeRef']['range']
| undefined = context.selectionRanges.graphSelections[0]?.codeRef?.range
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -102,6 +102,7 @@ export function useCalculateKclExpression({
engineCommandManager,
useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
})
const resultDeclaration = ast.body.find(
(a) =>

View File

@ -7,7 +7,7 @@ import { useEffect, useState } from 'react'
export function usePreviousVariables() {
const { programMemory, code } = useKclContext()
const { context } = useModelingContext()
const selectionRange = context.selectionRanges.codeBasedSelections[0]
const selectionRange = context.selectionRanges.graphSelections[0]?.codeRef
?.range || [code.length, code.length]
const [previousVariablesInfo, setPreviousVariablesInfo] = useState<
ReturnType<typeof findAllPreviousVariables>

View File

@ -5,7 +5,7 @@ import {
CommandArgumentWithName,
KclCommandValue,
} from 'lib/commandTypes'
import { Selections } from 'lib/selections'
import { Selections__old } from 'lib/selections'
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
import { MachineManager } from 'components/MachineManagerProvider'
@ -13,7 +13,7 @@ export type CommandBarContext = {
commands: Command[]
selectedCommand?: Command
currentArgument?: CommandArgument<unknown> & { name: string }
selectionRanges: Selections
selectionRanges: Selections__old
argumentsToSubmit: { [x: string]: unknown }
machineManager: MachineManager
}

File diff suppressed because one or more lines are too long