make delete key work for solids (#2752)
* failing test Signed-off-by: Jess Frazelle <github@jessfraz.com> * failing test Signed-off-by: Jess Frazelle <github@jessfraz.com> * push up progress Signed-off-by: Jess Frazelle <github@jessfraz.com> * improve travers * basic deleteFromSelection * remove .only * delete depended on extrude * fix * fix selection override * add selection test * Revert "add selection test" This reverts commit40a414b612
. * Revert "fix selection override" This reverts commit68e66e2980
. * more progress * add toast message when we're not able to delet * add e2e tests * tweak test timeout * more test tweaks * fix back space cmd bar conflic * clean up --------- Signed-off-by: Jess Frazelle <github@jessfraz.com> Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
This commit is contained in:
@ -1894,6 +1894,239 @@ test.describe('Testing selections', () => {
|
|||||||
await selectionSequence()
|
await selectionSequence()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Solids should be select and deletable', async ({ page }) => {
|
||||||
|
test.setTimeout(90_000)
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|
|> line([112.54, 127.64], %, $seg02)
|
||||||
|
|> line([170.36, -121.61], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(50, sketch001)
|
||||||
|
const sketch005 = startSketchOn(extrude001, 'END')
|
||||||
|
|> startProfileAt([23.24, 136.52], %)
|
||||||
|
|> line([-8.44, 36.61], %)
|
||||||
|
|> line([49.4, 2.05], %)
|
||||||
|
|> line([29.69, -46.95], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch003 = startSketchOn(extrude001, seg01)
|
||||||
|
|> startProfileAt([21.23, 17.81], %)
|
||||||
|
|> line([51.97, 21.32], %)
|
||||||
|
|> line([4.07, -22.75], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch002 = startSketchOn(extrude001, seg02)
|
||||||
|
|> startProfileAt([-100.54, 16.99], %)
|
||||||
|
|> line([0, 20.03], %)
|
||||||
|
|> line([62.61, 0], %, $seg03)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude002 = extrude(50, sketch002)
|
||||||
|
const sketch004 = startSketchOn(extrude002, seg03)
|
||||||
|
|> startProfileAt([57.07, 134.77], %)
|
||||||
|
|> line([-4.72, 22.84], %)
|
||||||
|
|> line([28.8, 6.71], %)
|
||||||
|
|> line([9.19, -25.33], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude003 = extrude(20, sketch004)
|
||||||
|
const pipeLength = 40
|
||||||
|
const pipeSmallDia = 10
|
||||||
|
const pipeLargeDia = 20
|
||||||
|
const thickness = 0.5
|
||||||
|
const part009 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|
||||||
|
|> line([thickness, 0], %)
|
||||||
|
|> line([0, -1], %)
|
||||||
|
|> angledLineToX({
|
||||||
|
angle: 60,
|
||||||
|
to: pipeSmallDia + thickness
|
||||||
|
}, %)
|
||||||
|
|> line([0, -pipeLength], %)
|
||||||
|
|> angledLineToX({
|
||||||
|
angle: -60,
|
||||||
|
to: pipeLargeDia + thickness
|
||||||
|
}, %)
|
||||||
|
|> line([0, -1], %)
|
||||||
|
|> line([-thickness, 0], %)
|
||||||
|
|> line([0, 1], %)
|
||||||
|
|> angledLineToX({ angle: 120, to: pipeSmallDia }, %)
|
||||||
|
|> line([0, pipeLength], %)
|
||||||
|
|> angledLineToX({ angle: 60, to: pipeLargeDia }, %)
|
||||||
|
|> close(%)
|
||||||
|
const rev = revolve({ axis: 'y' }, part009)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||||
|
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
const revolve = { x: 646, y: 248 }
|
||||||
|
const parentExtrude = { x: 915, y: 133 }
|
||||||
|
const solid2d = { x: 770, y: 167 }
|
||||||
|
|
||||||
|
// DELETE REVOLVE
|
||||||
|
await page.mouse.click(revolve.x, revolve.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
|
'|> line([0, -pipeLength], %)'
|
||||||
|
)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await expect(u.codeLocator).not.toContainText(
|
||||||
|
`const rev = revolve({ axis: 'y' }, part009)`
|
||||||
|
)
|
||||||
|
|
||||||
|
// DELETE PARENT EXTRUDE
|
||||||
|
await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
|
'|> line([170.36, -121.61], %, $seg01)'
|
||||||
|
)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
await expect(u.codeLocator).not.toContainText(
|
||||||
|
`const extrude001 = extrude(50, sketch001)`
|
||||||
|
)
|
||||||
|
await expect(u.codeLocator).toContainText(`const sketch005 = startSketchOn({
|
||||||
|
plane: {
|
||||||
|
origin: { x: 0, y: -50, z: 0 },
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
z_axis: { x: 0, y: -1, z: 0 }
|
||||||
|
}
|
||||||
|
})`)
|
||||||
|
await expect(u.codeLocator).toContainText(`const sketch003 = startSketchOn({
|
||||||
|
plane: {
|
||||||
|
origin: { x: 116.53, y: 0, z: 163.25 },
|
||||||
|
x_axis: { x: -0.81, y: 0, z: 0.58 },
|
||||||
|
y_axis: { x: 0, y: -1, z: 0 },
|
||||||
|
z_axis: { x: 0.58, y: 0, z: 0.81 }
|
||||||
|
}
|
||||||
|
})`)
|
||||||
|
await expect(u.codeLocator).toContainText(`const sketch002 = startSketchOn({
|
||||||
|
plane: {
|
||||||
|
origin: { x: -91.74, y: 0, z: 80.89 },
|
||||||
|
x_axis: { x: -0.66, y: 0, z: -0.75 },
|
||||||
|
y_axis: { x: 0, y: -1, z: 0 },
|
||||||
|
z_axis: { x: -0.75, y: 0, z: 0.66 }
|
||||||
|
}
|
||||||
|
})`)
|
||||||
|
|
||||||
|
// DELETE SOLID 2D
|
||||||
|
await page.mouse.click(solid2d.x, solid2d.y)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
|
'|> startProfileAt([23.24, 136.52], %)'
|
||||||
|
)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
await expect(u.codeLocator).not.toContainText(
|
||||||
|
`const sketch005 = startSketchOn({`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([-79.26, 95.04], %)
|
||||||
|
|> line([112.54, 127.64], %, $seg02)
|
||||||
|
|> line([170.36, -121.61], %, $seg01)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(50, sketch001)
|
||||||
|
const launderExtrudeThroughVar = extrude001
|
||||||
|
const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||||
|
|> startProfileAt([-100.54, 16.99], %)
|
||||||
|
|> line([0, 20.03], %)
|
||||||
|
|> line([62.61, 0], %, $seg03)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
}, KCL_DEFAULT_LENGTH)
|
||||||
|
await page.setViewportSize({ width: 1000, height: 500 })
|
||||||
|
await page.goto('/')
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await u.openAndClearDebugPanel()
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_look_at',
|
||||||
|
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||||
|
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||||
|
up: { x: 0, y: 0, z: 1 },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await u.sendCustomCmd({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'default_camera_get_settings',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
|
// attempt delete
|
||||||
|
await page.mouse.click(930, 139)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||||
|
'|> line([170.36, -121.61], %, $seg01)'
|
||||||
|
)
|
||||||
|
await u.clearCommandLogs()
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
|
await expect(page.getByText('Unable to delete part')).toBeVisible()
|
||||||
|
})
|
||||||
test('Hovering over 3d features highlights code', async ({ page }) => {
|
test('Hovering over 3d features highlights code', async ({ page }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
|
@ -45,8 +45,8 @@ async function clearCommandLogs(page: Page) {
|
|||||||
await page.getByTestId('clear-commands').click()
|
await page.getByTestId('clear-commands').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function expectCmdLog(page: Page, locatorStr: string) {
|
async function expectCmdLog(page: Page, locatorStr: string, timeout = 5000) {
|
||||||
await expect(page.locator(locatorStr).last()).toBeVisible()
|
await expect(page.locator(locatorStr).last()).toBeVisible({ timeout })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForDefaultPlanesToBeVisible(page: Page) {
|
async function waitForDefaultPlanesToBeVisible(page: Page) {
|
||||||
@ -228,7 +228,8 @@ export async function getUtils(page: Page) {
|
|||||||
await fillInput('z', xyz[2])
|
await fillInput('z', xyz[2])
|
||||||
},
|
},
|
||||||
clearCommandLogs: () => clearCommandLogs(page),
|
clearCommandLogs: () => clearCommandLogs(page),
|
||||||
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
expectCmdLog: (locatorStr: string, timeout = 5000) =>
|
||||||
|
expectCmdLog(page, locatorStr, timeout),
|
||||||
openKclCodePanel: () => openKclCodePanel(page),
|
openKclCodePanel: () => openKclCodePanel(page),
|
||||||
closeKclCodePanel: () => closeKclCodePanel(page),
|
closeKclCodePanel: () => closeKclCodePanel(page),
|
||||||
openDebugPanel: () => openDebugPanel(page),
|
openDebugPanel: () => openDebugPanel(page),
|
||||||
|
@ -1967,9 +1967,9 @@ export async function getSketchOrientationDetails(
|
|||||||
* @param entityId - The ID of the entity for which orientation details are being fetched.
|
* @param entityId - The ID of the entity for which orientation details are being fetched.
|
||||||
* @returns A promise that resolves with the orientation details of the face.
|
* @returns A promise that resolves with the orientation details of the face.
|
||||||
*/
|
*/
|
||||||
async function getFaceDetails(
|
export async function getFaceDetails(
|
||||||
entityId: string
|
entityId: string
|
||||||
): Promise<Models['FaceIsPlanar_type']> {
|
): Promise<Models['GetSketchModePlane_type']> {
|
||||||
// TODO mode engine connection to allow batching returns and batch the following
|
// TODO mode engine connection to allow batching returns and batch the following
|
||||||
await engineCommandManager.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
@ -1982,8 +1982,7 @@ async function getFaceDetails(
|
|||||||
entity_id: entityId,
|
entity_id: entityId,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
// TODO change typing to get_sketch_mode_plane once lib is updated
|
const faceInfo: Models['GetSketchModePlane_type'] = (
|
||||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
|
||||||
await engineCommandManager.sendSceneCommand({
|
await engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
|
@ -23,6 +23,7 @@ import {
|
|||||||
editorManager,
|
editorManager,
|
||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
import {
|
import {
|
||||||
angleBetweenInfo,
|
angleBetweenInfo,
|
||||||
@ -78,6 +79,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
|
|||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -140,6 +142,7 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
const { commandBarState } = useCommandsContext()
|
||||||
|
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
// const retrievedSettings = useRef(
|
// const retrievedSettings = useRef(
|
||||||
@ -464,6 +467,11 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
return canExtrudeSelection(selectionRanges)
|
return canExtrudeSelection(selectionRanges)
|
||||||
},
|
},
|
||||||
|
'has valid selection for deletion': ({ selectionRanges }) => {
|
||||||
|
if (!commandBarState.matches('Closed')) return false
|
||||||
|
if (selectionRanges.codeBasedSelections.length <= 0) return false
|
||||||
|
return true
|
||||||
|
},
|
||||||
'Sketch is empty': ({ sketchDetails }) => {
|
'Sketch is empty': ({ sketchDetails }) => {
|
||||||
const node = getNodeFromPath<VariableDeclaration>(
|
const node = getNodeFromPath<VariableDeclaration>(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
@ -927,6 +935,11 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
}, [modelingSend])
|
}, [modelingSend])
|
||||||
|
|
||||||
|
// Allow using the delete key to delete solids
|
||||||
|
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||||
|
modelingSend({ type: 'Delete selection' })
|
||||||
|
})
|
||||||
|
|
||||||
useStateMachineCommands({
|
useStateMachineCommands({
|
||||||
machineId: 'modeling',
|
machineId: 'modeling',
|
||||||
state: modelingState,
|
state: modelingState,
|
||||||
|
@ -83,6 +83,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
if (state.matches('Sketch')) return
|
if (state.matches('Sketch')) return
|
||||||
if (state.matches('Sketch no face')) return
|
if (state.matches('Sketch no face')) return
|
||||||
|
|
||||||
const { x, y } = getNormalisedCoordinates({
|
const { x, y } = getNormalisedCoordinates({
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
clientY: e.clientY,
|
clientY: e.clientY,
|
||||||
|
@ -15,6 +15,7 @@ import {
|
|||||||
sketchOnExtrudedFace,
|
sketchOnExtrudedFace,
|
||||||
deleteSegmentFromPipeExpression,
|
deleteSegmentFromPipeExpression,
|
||||||
removeSingleConstraintInfo,
|
removeSingleConstraintInfo,
|
||||||
|
deleteFromSelection,
|
||||||
} from './modifyAst'
|
} from './modifyAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
||||||
@ -696,3 +697,196 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('Testing deleteFromSelection', () => {
|
||||||
|
const cases = [
|
||||||
|
[
|
||||||
|
'basicCase',
|
||||||
|
{
|
||||||
|
codeBefore: `const myVar = 5
|
||||||
|
const sketch003 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.82, 13.6], %)
|
||||||
|
|> line([-2.94, 2.7], %)
|
||||||
|
|> line([7.7, 0.16], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`,
|
||||||
|
codeAfter: `const myVar = 5\n`,
|
||||||
|
lineOfInterest: 'line([-2.94, 2.7], %)',
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'delete extrude',
|
||||||
|
{
|
||||||
|
codeBefore: `const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|
|> line([2.48, 2.44], %)
|
||||||
|
|> line([2.66, 1.17], %)
|
||||||
|
|> line([3.75, 0.46], %)
|
||||||
|
|> line([4.99, -0.46], %, $seg01)
|
||||||
|
|> line([-3.86, -2.73], %)
|
||||||
|
|> line([-17.67, 0.85], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(10, sketch001)`,
|
||||||
|
codeAfter: `const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([3.29, 7.86], %)
|
||||||
|
|> line([2.48, 2.44], %)
|
||||||
|
|> line([2.66, 1.17], %)
|
||||||
|
|> line([3.75, 0.46], %)
|
||||||
|
|> line([4.99, -0.46], %, $seg01)
|
||||||
|
|> line([-3.86, -2.73], %)
|
||||||
|
|> line([-17.67, 0.85], %)
|
||||||
|
|> close(%)\n`,
|
||||||
|
lineOfInterest: 'line([2.66, 1.17], %)',
|
||||||
|
type: 'extrude-wall',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'delete extrude with sketch on it',
|
||||||
|
{
|
||||||
|
codeBefore: `const myVar = 5
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||||
|
|> line([0.08, myVar], %)
|
||||||
|
|> line([13.03, 2.02], %, $seg01)
|
||||||
|
|> line([3.9, -7.6], %)
|
||||||
|
|> line([-11.18, -2.15], %)
|
||||||
|
|> line([5.41, -9.61], %)
|
||||||
|
|> line([-8.54, -2.51], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)
|
||||||
|
const sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
|
|> line([3.02, 1.9], %)
|
||||||
|
|> line([1.82, -1.49], %, $seg02)
|
||||||
|
|> angledLine([-86, segLen(seg02, %)], %)
|
||||||
|
|> line([-3.97, -0.53], %)
|
||||||
|
|> line([0.3, 0.84], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`,
|
||||||
|
codeAfter: `const myVar = 5
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||||
|
|> line([0.08, myVar], %)
|
||||||
|
|> line([13.03, 2.02], %, $seg01)
|
||||||
|
|> line([3.9, -7.6], %)
|
||||||
|
|> line([-11.18, -2.15], %)
|
||||||
|
|> line([5.41, -9.61], %)
|
||||||
|
|> line([-8.54, -2.51], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch002 = startSketchOn({
|
||||||
|
plane: {
|
||||||
|
origin: { x: 1, y: 2, z: 3 },
|
||||||
|
x_axis: { x: 4, y: 5, z: 6 },
|
||||||
|
y_axis: { x: 7, y: 8, z: 9 },
|
||||||
|
z_axis: { x: 10, y: 11, z: 12 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
|
|> line([3.02, 1.9], %)
|
||||||
|
|> line([1.82, -1.49], %, $seg02)
|
||||||
|
|> angledLine([-86, segLen(seg02, %)], %)
|
||||||
|
|> line([-3.97, -0.53], %)
|
||||||
|
|> line([0.3, 0.84], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
`,
|
||||||
|
lineOfInterest: 'line([-11.18, -2.15], %)',
|
||||||
|
type: 'extrude-wall',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'delete extrude with sketch on it',
|
||||||
|
{
|
||||||
|
codeBefore: `const myVar = 5
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||||
|
|> line([0.08, myVar], %)
|
||||||
|
|> line([13.03, 2.02], %, $seg01)
|
||||||
|
|> line([3.9, -7.6], %)
|
||||||
|
|> line([-11.18, -2.15], %)
|
||||||
|
|> line([5.41, -9.61], %)
|
||||||
|
|> line([-8.54, -2.51], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const extrude001 = extrude(5, sketch001)
|
||||||
|
const sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
|
|> line([3.02, 1.9], %)
|
||||||
|
|> line([1.82, -1.49], %, $seg02)
|
||||||
|
|> angledLine([-86, segLen(seg02, %)], %)
|
||||||
|
|> line([-3.97, -0.53], %)
|
||||||
|
|> line([0.3, 0.84], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)`,
|
||||||
|
codeAfter: `const myVar = 5
|
||||||
|
const sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([4.46, 5.12], %, $tag)
|
||||||
|
|> line([0.08, myVar], %)
|
||||||
|
|> line([13.03, 2.02], %, $seg01)
|
||||||
|
|> line([3.9, -7.6], %)
|
||||||
|
|> line([-11.18, -2.15], %)
|
||||||
|
|> line([5.41, -9.61], %)
|
||||||
|
|> line([-8.54, -2.51], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
const sketch002 = startSketchOn({
|
||||||
|
plane: {
|
||||||
|
origin: { x: 1, y: 2, z: 3 },
|
||||||
|
x_axis: { x: 4, y: 5, z: 6 },
|
||||||
|
y_axis: { x: 7, y: 8, z: 9 },
|
||||||
|
z_axis: { x: 10, y: 11, z: 12 }
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
|
|> line([3.02, 1.9], %)
|
||||||
|
|> line([1.82, -1.49], %, $seg02)
|
||||||
|
|> angledLine([-86, segLen(seg02, %)], %)
|
||||||
|
|> line([-3.97, -0.53], %)
|
||||||
|
|> line([0.3, 0.84], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
`,
|
||||||
|
lineOfInterest: 'startProfileAt([4.46, 5.12], %, $tag)',
|
||||||
|
type: 'end-cap',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
] as const
|
||||||
|
test.each(cases)(
|
||||||
|
'%s',
|
||||||
|
async (name, { codeBefore, codeAfter, lineOfInterest, type }) => {
|
||||||
|
// const lineOfInterest = 'line([-2.94, 2.7], %)'
|
||||||
|
const ast = parse(codeBefore)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
|
||||||
|
// deleteFromSelection
|
||||||
|
const range: [number, number] = [
|
||||||
|
codeBefore.indexOf(lineOfInterest),
|
||||||
|
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
|
||||||
|
]
|
||||||
|
const newAst = await deleteFromSelection(
|
||||||
|
ast,
|
||||||
|
{
|
||||||
|
range,
|
||||||
|
type,
|
||||||
|
},
|
||||||
|
programMemory,
|
||||||
|
async () => {
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
|
return {
|
||||||
|
origin: { x: 1, y: 2, z: 3 },
|
||||||
|
x_axis: { x: 4, y: 5, z: 6 },
|
||||||
|
y_axis: { x: 7, y: 8, z: 9 },
|
||||||
|
z_axis: { x: 10, y: 11, z: 12 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if (err(newAst)) throw newAst
|
||||||
|
const newCode = recast(newAst)
|
||||||
|
expect(newCode).toBe(codeAfter)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
PathToNode,
|
PathToNode,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
SketchGroup,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
isNodeSafeToReplacePath,
|
isNodeSafeToReplacePath,
|
||||||
@ -25,6 +26,7 @@ import {
|
|||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
|
traverse,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
|
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
|
||||||
import {
|
import {
|
||||||
@ -38,6 +40,7 @@ import { isOverlap, roundOff } from 'lib/utils'
|
|||||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||||
import { ConstrainInfo } from './std/stdTypes'
|
import { ConstrainInfo } from './std/stdTypes'
|
||||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||||
|
import { Models } from '@kittycad/lib'
|
||||||
|
|
||||||
export function startSketchOnDefault(
|
export function startSketchOnDefault(
|
||||||
node: Program,
|
node: Program,
|
||||||
@ -873,3 +876,175 @@ export function removeSingleConstraintInfo(
|
|||||||
if (err(retval)) return false
|
if (err(retval)) return false
|
||||||
return retval
|
return retval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteFromSelection(
|
||||||
|
ast: Program,
|
||||||
|
selection: Selection,
|
||||||
|
programMemory: ProgramMemory,
|
||||||
|
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
|
||||||
|
({} as any)
|
||||||
|
): Promise<Program | Error> {
|
||||||
|
const astClone = JSON.parse(JSON.stringify(ast))
|
||||||
|
const range = selection.range
|
||||||
|
const path = getNodePathFromSourceRange(ast, range)
|
||||||
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
ast,
|
||||||
|
path,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
if (err(varDec)) return varDec
|
||||||
|
if (
|
||||||
|
(selection.type === 'extrude-wall' ||
|
||||||
|
selection.type === 'end-cap' ||
|
||||||
|
selection.type === 'start-cap') &&
|
||||||
|
varDec.node.init.type === 'PipeExpression'
|
||||||
|
) {
|
||||||
|
const varDecName = varDec.node.id.name
|
||||||
|
let pathToNode: PathToNode | null = null
|
||||||
|
let extrudeNameToDelete = ''
|
||||||
|
traverse(astClone, {
|
||||||
|
enter: (node, path) => {
|
||||||
|
if (node.type === 'VariableDeclaration') {
|
||||||
|
const dec = node.declarations[0]
|
||||||
|
if (
|
||||||
|
dec.init.type === 'CallExpression' &&
|
||||||
|
(dec.init.callee.name === 'extrude' ||
|
||||||
|
dec.init.callee.name === 'revolve') &&
|
||||||
|
dec.init.arguments?.[1].type === 'Identifier' &&
|
||||||
|
dec.init.arguments?.[1].name === varDecName
|
||||||
|
) {
|
||||||
|
pathToNode = path
|
||||||
|
extrudeNameToDelete = dec.id.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if (!pathToNode) return new Error('Could not find extrude variable')
|
||||||
|
|
||||||
|
const expressionIndex = pathToNode[1][0] as number
|
||||||
|
astClone.body.splice(expressionIndex, 1)
|
||||||
|
if (extrudeNameToDelete) {
|
||||||
|
await new Promise(async (resolve) => {
|
||||||
|
let currentVariableName = ''
|
||||||
|
const pathsDependingOnExtrude: Array<{
|
||||||
|
path: PathToNode
|
||||||
|
sketchName: string
|
||||||
|
}> = []
|
||||||
|
traverse(astClone, {
|
||||||
|
leave: (node) => {
|
||||||
|
if (node.type === 'VariableDeclaration') {
|
||||||
|
currentVariableName = ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enter: async (node, path) => {
|
||||||
|
if (node.type === 'VariableDeclaration') {
|
||||||
|
currentVariableName = node.declarations[0].id.name
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
// match startSketchOn(${extrudeNameToDelete})
|
||||||
|
node.type === 'CallExpression' &&
|
||||||
|
node.callee.name === 'startSketchOn' &&
|
||||||
|
node.arguments[0].type === 'Identifier' &&
|
||||||
|
node.arguments[0].name === extrudeNameToDelete
|
||||||
|
) {
|
||||||
|
pathsDependingOnExtrude.push({
|
||||||
|
path,
|
||||||
|
sketchName: currentVariableName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const roundLiteral = (x: number) => createLiteral(roundOff(x))
|
||||||
|
const modificationDetails: {
|
||||||
|
parent: PipeExpression['body']
|
||||||
|
faceDetails: Models['FaceIsPlanar_type']
|
||||||
|
lastKey: number
|
||||||
|
}[] = []
|
||||||
|
for (const { path, sketchName } of pathsDependingOnExtrude) {
|
||||||
|
const parent = getNodeFromPath<PipeExpression['body']>(
|
||||||
|
astClone,
|
||||||
|
path.slice(0, -1)
|
||||||
|
)
|
||||||
|
if (err(parent)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const sketchToPreserve = programMemory.root[sketchName] as SketchGroup
|
||||||
|
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)
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
faceDetails.origin &&
|
||||||
|
faceDetails.x_axis &&
|
||||||
|
faceDetails.y_axis &&
|
||||||
|
faceDetails.z_axis
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const lastKey = Number(path.slice(-1)[0][0])
|
||||||
|
modificationDetails.push({
|
||||||
|
parent: parent.node,
|
||||||
|
faceDetails,
|
||||||
|
lastKey,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for (const { parent, faceDetails, lastKey } of modificationDetails) {
|
||||||
|
if (
|
||||||
|
!(
|
||||||
|
faceDetails.origin &&
|
||||||
|
faceDetails.x_axis &&
|
||||||
|
faceDetails.y_axis &&
|
||||||
|
faceDetails.z_axis
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parent[lastKey] = createCallExpressionStdLib('startSketchOn', [
|
||||||
|
createObjectExpression({
|
||||||
|
plane: createObjectExpression({
|
||||||
|
origin: createObjectExpression({
|
||||||
|
x: roundLiteral(faceDetails.origin.x),
|
||||||
|
y: roundLiteral(faceDetails.origin.y),
|
||||||
|
z: roundLiteral(faceDetails.origin.z),
|
||||||
|
}),
|
||||||
|
x_axis: createObjectExpression({
|
||||||
|
x: roundLiteral(faceDetails.x_axis.x),
|
||||||
|
y: roundLiteral(faceDetails.x_axis.y),
|
||||||
|
z: roundLiteral(faceDetails.x_axis.z),
|
||||||
|
}),
|
||||||
|
y_axis: createObjectExpression({
|
||||||
|
x: roundLiteral(faceDetails.y_axis.x),
|
||||||
|
y: roundLiteral(faceDetails.y_axis.y),
|
||||||
|
z: roundLiteral(faceDetails.y_axis.z),
|
||||||
|
}),
|
||||||
|
z_axis: createObjectExpression({
|
||||||
|
x: roundLiteral(faceDetails.z_axis.x),
|
||||||
|
y: roundLiteral(faceDetails.z_axis.y),
|
||||||
|
z: roundLiteral(faceDetails.z_axis.z),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
resolve(true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// await prom
|
||||||
|
return astClone
|
||||||
|
} else if (varDec.node.init.type === 'PipeExpression') {
|
||||||
|
const pipeBody = varDec.node.init.body
|
||||||
|
if (
|
||||||
|
pipeBody[0].type === 'CallExpression' &&
|
||||||
|
pipeBody[0].callee.name === 'startSketchOn'
|
||||||
|
) {
|
||||||
|
// remove varDec
|
||||||
|
const varDecIndex = varDec.shallowPath[1][0] as number
|
||||||
|
astClone.body.splice(varDecIndex, 1)
|
||||||
|
return astClone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Error('Selection not recognised, could not delete')
|
||||||
|
}
|
||||||
|
@ -26,7 +26,11 @@ import {
|
|||||||
applyConstraintEqualLength,
|
applyConstraintEqualLength,
|
||||||
setEqualLengthInfo,
|
setEqualLengthInfo,
|
||||||
} from 'components/Toolbar/EqualLength'
|
} from 'components/Toolbar/EqualLength'
|
||||||
import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
import {
|
||||||
|
addStartProfileAt,
|
||||||
|
deleteFromSelection,
|
||||||
|
extrudeSketch,
|
||||||
|
} from 'lang/modifyAst'
|
||||||
import { getNodeFromPath } from '../lang/queryAst'
|
import { getNodeFromPath } from '../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
applyConstraintEqualAngle,
|
applyConstraintEqualAngle,
|
||||||
@ -44,12 +48,14 @@ import {
|
|||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
import { Models } from '@kittycad/lib/dist/types/src'
|
||||||
import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig'
|
import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
|
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||||
import { Vector3 } from 'three'
|
import { Vector3 } from 'three'
|
||||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { Coords2d } from 'lang/std/sketch'
|
import { Coords2d } from 'lang/std/sketch'
|
||||||
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
|
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
|
||||||
|
import { executeAst } from 'useStore'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
|
||||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||||
|
|
||||||
@ -157,6 +163,9 @@ export type ModelingMachineEvent =
|
|||||||
type: 'Set selection'
|
type: 'Set selection'
|
||||||
data: SetSelections
|
data: SetSelections
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'Delete selection'
|
||||||
|
}
|
||||||
| { type: 'Sketch no face' }
|
| { type: 'Sketch no face' }
|
||||||
| { type: 'Toggle gui mode' }
|
| { type: 'Toggle gui mode' }
|
||||||
| { type: 'Cancel' }
|
| { type: 'Cancel' }
|
||||||
@ -273,6 +282,13 @@ export const modelingMachine = createMachine(
|
|||||||
cond: 'Has exportable geometry',
|
cond: 'Has exportable geometry',
|
||||||
actions: 'Engine export',
|
actions: 'Engine export',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Delete selection': {
|
||||||
|
target: 'idle',
|
||||||
|
cond: 'has valid selection for deletion',
|
||||||
|
actions: ['AST delete selection'],
|
||||||
|
internal: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: 'reset client scene mouse handlers',
|
entry: 'reset client scene mouse handlers',
|
||||||
@ -963,6 +979,42 @@ export const modelingMachine = createMachine(
|
|||||||
editorManager.selectRange(updatedAst?.selections)
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'AST delete selection': async ({ sketchDetails, selectionRanges }) => {
|
||||||
|
let ast = kclManager.ast
|
||||||
|
|
||||||
|
const getScaledFaceDetails = async (entityId: string) => {
|
||||||
|
const faceDetails = await getFaceDetails(entityId)
|
||||||
|
if (err(faceDetails)) return {}
|
||||||
|
return {
|
||||||
|
...faceDetails,
|
||||||
|
origin: {
|
||||||
|
x: faceDetails.origin.x / sceneInfra._baseUnitMultiplier,
|
||||||
|
y: faceDetails.origin.y / sceneInfra._baseUnitMultiplier,
|
||||||
|
z: faceDetails.origin.z / sceneInfra._baseUnitMultiplier,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedAst = await deleteFromSelection(
|
||||||
|
ast,
|
||||||
|
selectionRanges.codeBasedSelections[0],
|
||||||
|
kclManager.programMemory,
|
||||||
|
getScaledFaceDetails
|
||||||
|
)
|
||||||
|
if (err(modifiedAst)) return
|
||||||
|
|
||||||
|
const testExecute = await executeAst({
|
||||||
|
ast: modifiedAst,
|
||||||
|
useFakeExecutor: true,
|
||||||
|
engineCommandManager,
|
||||||
|
})
|
||||||
|
if (testExecute.errors.length) {
|
||||||
|
toast.error('Unable to delete part')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
},
|
||||||
'conditionally equip line tool': (_, { type }) => {
|
'conditionally equip line tool': (_, { type }) => {
|
||||||
if (type === 'done.invoke.animate-to-face') {
|
if (type === 'done.invoke.animate-to-face') {
|
||||||
sceneInfra.modelingSend('Equip Line tool')
|
sceneInfra.modelingSend('Equip Line tool')
|
||||||
|
Reference in New Issue
Block a user