Compare commits
5 Commits
pierremtb/
...
remove-the
Author | SHA1 | Date | |
---|---|---|---|
f46edcddf3 | |||
68fd921a64 | |||
a20e710e8f | |||
9daf2d7794 | |||
f86473d13b |
@ -1214,12 +1214,18 @@ test('Auto complete works', async ({ page }) => {
|
||||
await page.waitForTimeout(100)
|
||||
// press arrow down twice then enter to accept xLine
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(100)
|
||||
// finish line with comment
|
||||
await page.keyboard.type('5')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.press('Tab')
|
||||
await page.waitForTimeout(100)
|
||||
await page.keyboard.type(' // lin')
|
||||
await page.waitForTimeout(100)
|
||||
// there shouldn't be any auto complete options for 'lin' in the comment
|
||||
@ -1689,6 +1695,7 @@ test.describe('Onboarding tests', () => {
|
||||
})
|
||||
|
||||
test.describe('Testing selections', () => {
|
||||
test.setTimeout(90_000)
|
||||
test('Selections work on fresh and edited sketch', async ({ page }) => {
|
||||
// tests mapping works on fresh sketch and edited sketch
|
||||
// tests using hovers which is the same as selections, because if
|
||||
@ -1894,6 +1901,239 @@ test.describe('Testing selections', () => {
|
||||
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 }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||
@ -2654,6 +2894,7 @@ fn yohey = (pos) => {
|
||||
// selecting an editable sketch but clicking "start sketch" should start a new sketch and not edit the existing one
|
||||
await page.getByText(selectionsSnippets.extrudeAndEditAllowed).click()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(200)
|
||||
await page.getByTestId('KCL Code').click()
|
||||
await page.waitForTimeout(200)
|
||||
await page.mouse.click(734, 134)
|
||||
@ -3278,6 +3519,7 @@ test.describe('Snap to close works (at any scale)', () => {
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page }) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -5471,6 +5713,7 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
||||
)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText('Delete Segment').click()
|
||||
|
||||
await page.getByText('Cancel').click()
|
||||
@ -5483,6 +5726,7 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
||||
)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText('Delete Segment').click()
|
||||
|
||||
await page.getByText('Continue and unconstrain').last().click()
|
||||
@ -5631,6 +5875,7 @@ ${extraLine ? 'const myVar = segLen(seg01, part001)' : ''}`
|
||||
await expect(page.locator('.cm-content')).toContainText(before)
|
||||
|
||||
await page.getByTestId('overlay-menu').click()
|
||||
await page.waitForTimeout(100)
|
||||
await page.getByText('Remove constraints').click()
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(after)
|
||||
|
@ -45,8 +45,8 @@ async function clearCommandLogs(page: Page) {
|
||||
await page.getByTestId('clear-commands').click()
|
||||
}
|
||||
|
||||
async function expectCmdLog(page: Page, locatorStr: string) {
|
||||
await expect(page.locator(locatorStr).last()).toBeVisible()
|
||||
async function expectCmdLog(page: Page, locatorStr: string, timeout = 5000) {
|
||||
await expect(page.locator(locatorStr).last()).toBeVisible({ timeout })
|
||||
}
|
||||
|
||||
async function waitForDefaultPlanesToBeVisible(page: Page) {
|
||||
@ -228,7 +228,8 @@ export async function getUtils(page: Page) {
|
||||
await fillInput('z', xyz[2])
|
||||
},
|
||||
clearCommandLogs: () => clearCommandLogs(page),
|
||||
expectCmdLog: (locatorStr: string) => expectCmdLog(page, locatorStr),
|
||||
expectCmdLog: (locatorStr: string, timeout = 5000) =>
|
||||
expectCmdLog(page, locatorStr, timeout),
|
||||
openKclCodePanel: () => openKclCodePanel(page),
|
||||
closeKclCodePanel: () => closeKclCodePanel(page),
|
||||
openDebugPanel: () => openDebugPanel(page),
|
||||
|
4
src-tauri/Cargo.lock
generated
4
src-tauri/Cargo.lock
generated
@ -4546,9 +4546,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.116"
|
||||
version = "1.0.118"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"itoa 1.0.11",
|
||||
|
@ -25,6 +25,7 @@ import { LowerRightControls } from 'components/LowerRightControls'
|
||||
import ModalContainer from 'react-modal-promise'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import Gizmo from 'components/Gizmo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
|
||||
export function App() {
|
||||
useRefreshSettings(paths.FILE + 'SETTINGS')
|
||||
@ -55,7 +56,11 @@ export function App() {
|
||||
setHtmlRef(ref)
|
||||
}, [ref])
|
||||
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const { auth, settings } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
|
||||
const coreDumpManager = new CoreDumpManager(engineCommandManager, ref, token)
|
||||
|
||||
const {
|
||||
app: { onboardingStatus },
|
||||
} = settings.context
|
||||
@ -129,7 +134,7 @@ export function App() {
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<Stream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls>
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<Gizmo />
|
||||
</LowerRightControls>
|
||||
</div>
|
||||
|
@ -534,7 +534,7 @@ export class SceneEntities {
|
||||
segmentName: 'line' | 'tangentialArcTo' = 'line',
|
||||
shouldTearDown = true
|
||||
) => {
|
||||
const _ast = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const _ast = kclManager.ast
|
||||
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
@ -692,7 +692,7 @@ export class SceneEntities {
|
||||
sketchOrigin: [number, number, number],
|
||||
rectangleOrigin: [x: number, y: number]
|
||||
) => {
|
||||
let _ast = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
let _ast = kclManager.ast
|
||||
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
@ -723,7 +723,9 @@ export class SceneEntities {
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
|
||||
_ast = parse(recast(_ast))
|
||||
let result = parse(recast(_ast))
|
||||
if (trap(result)) return Promise.reject(result)
|
||||
_ast = result
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
@ -737,7 +739,7 @@ export class SceneEntities {
|
||||
sceneInfra.setCallbacks({
|
||||
onMove: async (args) => {
|
||||
// Update the width and height of the draft rectangle
|
||||
const pathToNodeTwo = JSON.parse(JSON.stringify(sketchPathToNode))
|
||||
const pathToNodeTwo = sketchPathToNode
|
||||
pathToNodeTwo[1][0] = 0
|
||||
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
@ -799,7 +801,9 @@ export class SceneEntities {
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||
|
||||
_ast = parse(recast(_ast))
|
||||
let result = parse(recast(_ast))
|
||||
if (trap(result)) return Promise.reject(result)
|
||||
_ast = result
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1003,10 +1007,8 @@ export class SceneEntities {
|
||||
PROFILE_START,
|
||||
])
|
||||
if (!group) return
|
||||
const pathToNode: PathToNode = JSON.parse(
|
||||
JSON.stringify(group.userData.pathToNode)
|
||||
)
|
||||
const varDecIndex = JSON.parse(JSON.stringify(pathToNode[1][0]))
|
||||
const pathToNode: PathToNode = group.userData.pathToNode
|
||||
const varDecIndex: number = pathToNode[1][0] as number
|
||||
if (draftInfo) {
|
||||
pathToNode[1][0] = 0
|
||||
}
|
||||
@ -1719,7 +1721,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
}
|
||||
| Error {
|
||||
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
|
||||
const _ast = JSON.parse(JSON.stringify(ast))
|
||||
const _ast = ast
|
||||
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
@ -1778,7 +1780,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
}
|
||||
const truncatedAst: Program = {
|
||||
..._ast,
|
||||
body: [JSON.parse(JSON.stringify(_ast.body[bodyIndex]))],
|
||||
body: [_ast.body[bodyIndex]],
|
||||
}
|
||||
const programMemoryOverride = programMemoryInit()
|
||||
if (err(programMemoryOverride)) return programMemoryOverride
|
||||
@ -1804,7 +1806,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
}
|
||||
|
||||
if (value.type === 'TagIdentifier') {
|
||||
programMemoryOverride.root[key] = JSON.parse(JSON.stringify(value))
|
||||
programMemoryOverride.root[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
@ -1819,7 +1821,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
if (!memoryItem) {
|
||||
continue
|
||||
}
|
||||
programMemoryOverride.root[name] = JSON.parse(JSON.stringify(memoryItem))
|
||||
programMemoryOverride.root[name] = memoryItem
|
||||
}
|
||||
return {
|
||||
truncatedAst,
|
||||
@ -1967,9 +1969,9 @@ export async function getSketchOrientationDetails(
|
||||
* @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.
|
||||
*/
|
||||
async function getFaceDetails(
|
||||
export async function getFaceDetails(
|
||||
entityId: string
|
||||
): Promise<Models['FaceIsPlanar_type']> {
|
||||
): Promise<Models['GetSketchModePlane_type']> {
|
||||
// TODO mode engine connection to allow batching returns and batch the following
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
@ -1982,8 +1984,7 @@ async function getFaceDetails(
|
||||
entity_id: entityId,
|
||||
},
|
||||
})
|
||||
// TODO change typing to get_sketch_mode_plane once lib is updated
|
||||
const faceInfo: Models['FaceIsPlanar_type'] = (
|
||||
const faceInfo: Models['GetSketchModePlane_type'] = (
|
||||
await engineCommandManager.sendSceneCommand({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
|
@ -151,9 +151,7 @@ export function useCalc({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
programMemoryOverride: JSON.parse(
|
||||
JSON.stringify(kclManager.programMemory)
|
||||
),
|
||||
programMemoryOverride: kclManager.programMemory,
|
||||
}).then(({ programMemory }) => {
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
|
@ -6,8 +6,18 @@ import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
|
||||
import { HelpMenu } from './HelpMenu'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import toast from 'react-hot-toast'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
|
||||
export function LowerRightControls(props: React.PropsWithChildren) {
|
||||
export function LowerRightControls({
|
||||
children,
|
||||
coreDumpManager,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
coreDumpManager?: CoreDumpManager
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const linkOverrideClassName =
|
||||
@ -15,9 +25,42 @@ export function LowerRightControls(props: React.PropsWithChildren) {
|
||||
|
||||
const isPlayWright = window?.localStorage.getItem('playwright') === 'true'
|
||||
|
||||
async function reportbug(event: { preventDefault: () => void }) {
|
||||
event?.preventDefault()
|
||||
|
||||
if (!coreDumpManager) {
|
||||
// open default reporting option
|
||||
openWindow('https://github.com/KittyCAD/modeling-app/issues/new/choose')
|
||||
} else {
|
||||
toast
|
||||
.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Preparing bug report...',
|
||||
success: 'Bug report opened in new window',
|
||||
error: 'Unable to export a core dump. Using default reporting.',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err: Error) => {
|
||||
if (err) {
|
||||
openWindow(
|
||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||
{props.children}
|
||||
{children}
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
<a
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
@ -28,6 +71,7 @@ export function LowerRightControls(props: React.PropsWithChildren) {
|
||||
v{isPlayWright ? '11.22.33' : APP_VERSION}
|
||||
</a>
|
||||
<a
|
||||
onClick={reportbug}
|
||||
href="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
|
@ -30,7 +30,7 @@ import { wasmUrl } from 'lang/wasm'
|
||||
import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||
return []
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
editorManager,
|
||||
sceneEntitiesManager,
|
||||
} from 'lib/singletons'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||
import {
|
||||
angleBetweenInfo,
|
||||
@ -78,6 +79,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -124,7 +126,6 @@ export const ModelingMachineProvider = ({
|
||||
token
|
||||
)
|
||||
useHotkeyWrapper(['meta + shift + .'], () => {
|
||||
console.warn('CoreDump: Initializing core dump')
|
||||
toast.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
@ -141,6 +142,7 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
)
|
||||
})
|
||||
const { commandBarState } = useCommandsContext()
|
||||
|
||||
// Settings machine setup
|
||||
// const retrievedSettings = useRef(
|
||||
@ -465,6 +467,11 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
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 }) => {
|
||||
const node = getNodeFromPath<VariableDeclaration>(
|
||||
kclManager.ast,
|
||||
@ -506,7 +513,7 @@ export const ModelingMachineProvider = ({
|
||||
services: {
|
||||
'AST-undo-startSketchOn': async ({ sketchDetails }) => {
|
||||
if (!sketchDetails) return
|
||||
const newAst: Program = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
const newAst: Program = kclManager.ast
|
||||
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
|
||||
// remove body item at varDecIndex
|
||||
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
|
||||
@ -928,6 +935,11 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
}, [modelingSend])
|
||||
|
||||
// Allow using the delete key to delete solids
|
||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||
modelingSend({ type: 'Delete selection' })
|
||||
})
|
||||
|
||||
useStateMachineCommands({
|
||||
machineId: 'modeling',
|
||||
state: modelingState,
|
||||
|
@ -1,7 +1,25 @@
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import React from 'react'
|
||||
import toast from 'react-hot-toast'
|
||||
import Tooltip from './Tooltip'
|
||||
import { useStore } from 'useStore'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
|
||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context?.token
|
||||
const { htmlRef } = useStore((s) => ({
|
||||
htmlRef: s.htmlRef,
|
||||
}))
|
||||
const coreDumpManager = new CoreDumpManager(
|
||||
engineCommandManager,
|
||||
htmlRef,
|
||||
token
|
||||
)
|
||||
|
||||
export function RefreshButton() {
|
||||
async function refresh() {
|
||||
if (window && 'plausible' in window) {
|
||||
const p = window.plausible as (
|
||||
@ -17,8 +35,26 @@ export function RefreshButton() {
|
||||
})
|
||||
}
|
||||
|
||||
// Window may not be available in some environments
|
||||
window?.location.reload()
|
||||
toast
|
||||
.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Starting core dump...',
|
||||
success: 'Core dump completed successfully',
|
||||
error: 'Error while exporting core dump',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
// Window may not be available in some environments
|
||||
window?.location.reload()
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -83,6 +83,7 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
||||
if (!videoRef.current) return
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches('Sketch no face')) return
|
||||
|
||||
const { x, y } = getNormalisedCoordinates({
|
||||
clientX: e.clientX,
|
||||
clientY: e.clientY,
|
||||
|
@ -145,7 +145,7 @@ export async function applyConstraintIntersect({
|
||||
const { transforms, forcedSelectionRanges } = info
|
||||
|
||||
const transform1 = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: forcedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
|
@ -106,7 +106,7 @@ export async function applyConstraintAbsDistance({
|
||||
const transformInfos = info.transforms
|
||||
|
||||
const transform1 = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -128,7 +128,7 @@ export async function applyConstraintAbsDistance({
|
||||
)
|
||||
|
||||
const transform2 = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -176,7 +176,7 @@ export function applyConstraintAxisAlign({
|
||||
let finalValue = createIdentifier('ZERO')
|
||||
|
||||
return transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
|
@ -100,7 +100,7 @@ export async function applyConstraintAngleBetween({
|
||||
const transformInfos = info.transforms
|
||||
|
||||
const transformed1 = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
|
@ -108,7 +108,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const transformInfos = info.transforms
|
||||
const transformed = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
|
@ -84,7 +84,7 @@ export async function applyConstraintAngleLength({
|
||||
|
||||
const { transforms } = angleLength
|
||||
const sketched = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
@ -139,7 +139,7 @@ export async function applyConstraintAngleLength({
|
||||
}
|
||||
|
||||
const retval = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
|
@ -42,9 +42,8 @@ function registerServerCapability(
|
||||
serverCapabilities: ServerCapabilities,
|
||||
registration: Registration
|
||||
): ServerCapabilities | Error {
|
||||
const serverCapabilitiesCopy = JSON.parse(
|
||||
JSON.stringify(serverCapabilities)
|
||||
) as IFlexibleServerCapabilities
|
||||
const serverCapabilitiesCopy =
|
||||
serverCapabilities as IFlexibleServerCapabilities
|
||||
const { method, registerOptions } = registration
|
||||
const providerName = ServerCapabilitiesProviders[method]
|
||||
|
||||
@ -52,10 +51,7 @@ function registerServerCapability(
|
||||
if (!registerOptions) {
|
||||
serverCapabilitiesCopy[providerName] = true
|
||||
} else {
|
||||
serverCapabilitiesCopy[providerName] = Object.assign(
|
||||
{},
|
||||
JSON.parse(JSON.stringify(registerOptions))
|
||||
)
|
||||
serverCapabilitiesCopy[providerName] = Object.assign({}, registerOptions)
|
||||
}
|
||||
} else {
|
||||
return new Error('Could not register server capability.')
|
||||
@ -68,9 +64,8 @@ function unregisterServerCapability(
|
||||
serverCapabilities: ServerCapabilities,
|
||||
unregistration: Unregistration
|
||||
): ServerCapabilities {
|
||||
const serverCapabilitiesCopy = JSON.parse(
|
||||
JSON.stringify(serverCapabilities)
|
||||
) as IFlexibleServerCapabilities
|
||||
const serverCapabilitiesCopy =
|
||||
serverCapabilities as IFlexibleServerCapabilities
|
||||
const { method } = unregistration
|
||||
const providerName = ServerCapabilitiesProviders[method]
|
||||
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
sketchOnExtrudedFace,
|
||||
deleteSegmentFromPipeExpression,
|
||||
removeSingleConstraintInfo,
|
||||
deleteFromSelection,
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
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,
|
||||
ProgramMemory,
|
||||
SourceRange,
|
||||
SketchGroup,
|
||||
} from './wasm'
|
||||
import {
|
||||
isNodeSafeToReplacePath,
|
||||
@ -25,6 +26,7 @@ import {
|
||||
getNodeFromPath,
|
||||
getNodePathFromSourceRange,
|
||||
isNodeSafeToReplace,
|
||||
traverse,
|
||||
} from './queryAst'
|
||||
import { addTagForSketchOnFace, getConstraintInfo } from './std/sketch'
|
||||
import {
|
||||
@ -38,6 +40,7 @@ import { isOverlap, roundOff } from 'lib/utils'
|
||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||
import { ConstrainInfo } from './std/stdTypes'
|
||||
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
|
||||
import { Models } from '@kittycad/lib'
|
||||
|
||||
export function startSketchOnDefault(
|
||||
node: Program,
|
||||
@ -707,7 +710,7 @@ export function moveValueIntoNewVariablePath(
|
||||
programMemory,
|
||||
pathToNode
|
||||
)
|
||||
let _node = JSON.parse(JSON.stringify(ast))
|
||||
let _node = ast
|
||||
const boop = replacer(_node, variableName)
|
||||
if (trap(boop)) return { modifiedAst: ast }
|
||||
|
||||
@ -739,7 +742,7 @@ export function moveValueIntoNewVariable(
|
||||
programMemory,
|
||||
sourceRange
|
||||
)
|
||||
let _node = JSON.parse(JSON.stringify(ast))
|
||||
let _node = ast
|
||||
const replaced = replacer(_node, variableName)
|
||||
if (trap(replaced)) return { modifiedAst: ast }
|
||||
|
||||
@ -764,7 +767,7 @@ export function deleteSegmentFromPipeExpression(
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
): Program | Error {
|
||||
let _modifiedAst: Program = JSON.parse(JSON.stringify(modifiedAst))
|
||||
let _modifiedAst: Program = modifiedAst
|
||||
|
||||
dependentRanges.forEach((range) => {
|
||||
const path = getNodePathFromSourceRange(_modifiedAst, range)
|
||||
@ -873,3 +876,175 @@ export function removeSingleConstraintInfo(
|
||||
if (err(retval)) return false
|
||||
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 = 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')
|
||||
}
|
||||
|
@ -87,10 +87,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('100 + 100')
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const replaced = result.replacer(ast, 'replaceName')
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
@ -114,10 +111,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe("def('yo')")
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const replaced = result.replacer(ast, 'replaceName')
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
@ -154,10 +148,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('5 + 6')
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const replaced = result.replacer(ast, 'replaceName')
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`const yo = replaceName`)
|
||||
@ -173,10 +164,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe(
|
||||
"jkl('yo') + 2"
|
||||
)
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const replaced = result.replacer(ast, 'replaceName')
|
||||
if (err(replaced)) throw replaced
|
||||
const { modifiedAst } = replaced
|
||||
const outCode = recast(modifiedAst)
|
||||
@ -195,10 +183,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe(
|
||||
'identifierGuy + 5'
|
||||
)
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const replaced = result.replacer(ast, 'replaceName')
|
||||
if (err(replaced)) throw replaced
|
||||
const { modifiedAst } = replaced
|
||||
const outCode = recast(modifiedAst)
|
||||
|
@ -520,8 +520,8 @@ export function isNodeSafeToReplacePath(
|
||||
const replaceNodeWithIdentifier: ReplacerFn = (_ast, varName) => {
|
||||
const identifier = createIdentifier(varName)
|
||||
const last = finPath[finPath.length - 1]
|
||||
const pathToReplaced = JSON.parse(JSON.stringify(finPath))
|
||||
pathToReplaced[1][0] = pathToReplaced[1][0] + 1
|
||||
const pathToReplaced = finPath
|
||||
pathToReplaced[1][0] = (pathToReplaced[1][0] as number) + 1
|
||||
const startPath = finPath.slice(0, -1)
|
||||
const _nodeToReplace = getNodeFromPath(_ast, startPath)
|
||||
if (err(_nodeToReplace)) return _nodeToReplace
|
||||
|
@ -24,11 +24,7 @@ import {
|
||||
isNotLiteralArrayOrStatic,
|
||||
} from 'lang/std/sketchcombos'
|
||||
import { toolTips, ToolTip } from '../../useStore'
|
||||
import {
|
||||
createIdentifier,
|
||||
createPipeExpression,
|
||||
splitPathAtPipeExpression,
|
||||
} from '../modifyAst'
|
||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||
|
||||
import {
|
||||
SketchLineHelper,
|
||||
|
@ -1496,7 +1496,7 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
}
|
||||
}
|
||||
| Error {
|
||||
// let node = JSON.parse(JSON.stringify(ast))
|
||||
// let node = ast
|
||||
const primarySelection = selectionRanges.codeBasedSelections[0].range
|
||||
|
||||
const _tag = giveSketchFnCallTag(ast, primarySelection, forceSegName)
|
||||
@ -1565,7 +1565,7 @@ export function transformAstSketchLines({
|
||||
}
|
||||
| Error {
|
||||
// deep clone since we are mutating in a loop, of which any could fail
|
||||
let node = JSON.parse(JSON.stringify(ast))
|
||||
let node = ast
|
||||
let _valueUsedInTransform // TODO should this be an array?
|
||||
const pathToNodeMap: PathToNodeMap = {}
|
||||
|
||||
|
@ -33,7 +33,7 @@ export function updatePathToNodeFromMap(
|
||||
oldPath: PathToNode,
|
||||
pathToNodeMap: { [key: number]: PathToNode }
|
||||
): PathToNode {
|
||||
const updatedPathToNode = JSON.parse(JSON.stringify(oldPath))
|
||||
const updatedPathToNode = oldPath
|
||||
let max = 0
|
||||
Object.values(pathToNodeMap).forEach((path) => {
|
||||
const index = Number(path[1][0])
|
||||
|
@ -334,6 +334,7 @@ export async function coreDump(
|
||||
openGithubIssue: boolean = false
|
||||
): Promise<CoreDumpInfo> {
|
||||
try {
|
||||
console.warn('CoreDump: Initializing core dump')
|
||||
const dump: CoreDumpInfo = await coredump(coreDumpManager)
|
||||
/* NOTE: this console output of the coredump should include the field
|
||||
`github_issue_url` which is not in the uploaded coredump file.
|
||||
|
@ -13,6 +13,14 @@ import screenshot from 'lib/screenshot'
|
||||
import React from 'react'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
||||
/* eslint-disable suggest-no-throw/suggest-no-throw --
|
||||
* All the throws in CoreDumpManager are intentional and should be caught and handled properly
|
||||
* by the calling Promises with a catch block. The throws are essential to properly handling
|
||||
* when the app isn't ready enough or otherwise unable to produce a core dump. By throwing
|
||||
* instead of simply erroring, the code halts execution at the first point which it cannot
|
||||
* complete the core dump request.
|
||||
**/
|
||||
|
||||
/**
|
||||
* CoreDumpManager module
|
||||
* - for getting all the values from the JS world to pass to the Rust world for a core dump.
|
||||
@ -22,6 +30,7 @@ import { VITE_KC_API_BASE_URL } from 'env'
|
||||
// CoreDumpManager is instantiated in ModelingMachineProvider and passed to coreDump() in wasm.ts
|
||||
// The async function coreDump() handles any errors thrown in its Promise catch method and rethrows
|
||||
// them to so the toast handler in ModelingMachineProvider can show the user an error message toast
|
||||
// TODO: Throw more
|
||||
export class CoreDumpManager {
|
||||
engineCommandManager: EngineCommandManager
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
|
@ -96,9 +96,7 @@ export function useCalculateKclExpression({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
programMemoryOverride: JSON.parse(
|
||||
JSON.stringify(kclManager.programMemory)
|
||||
),
|
||||
programMemoryOverride: kclManager.programMemory,
|
||||
})
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
|
@ -26,7 +26,11 @@ import {
|
||||
applyConstraintEqualLength,
|
||||
setEqualLengthInfo,
|
||||
} from 'components/Toolbar/EqualLength'
|
||||
import { addStartProfileAt, extrudeSketch } from 'lang/modifyAst'
|
||||
import {
|
||||
addStartProfileAt,
|
||||
deleteFromSelection,
|
||||
extrudeSketch,
|
||||
} from 'lang/modifyAst'
|
||||
import { getNodeFromPath } from '../lang/queryAst'
|
||||
import {
|
||||
applyConstraintEqualAngle,
|
||||
@ -44,12 +48,14 @@ import {
|
||||
import { Models } from '@kittycad/lib/dist/types/src'
|
||||
import { ModelingCommandSchema } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
|
||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||
import { Vector3 } from 'three'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { Coords2d } from 'lang/std/sketch'
|
||||
import { deleteSegment } from 'clientSideScene/ClientSideSceneComp'
|
||||
import { executeAst } from 'useStore'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
@ -157,6 +163,9 @@ export type ModelingMachineEvent =
|
||||
type: 'Set selection'
|
||||
data: SetSelections
|
||||
}
|
||||
| {
|
||||
type: 'Delete selection'
|
||||
}
|
||||
| { type: 'Sketch no face' }
|
||||
| { type: 'Toggle gui mode' }
|
||||
| { type: 'Cancel' }
|
||||
@ -273,6 +282,13 @@ export const modelingMachine = createMachine(
|
||||
cond: 'Has exportable geometry',
|
||||
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',
|
||||
@ -963,6 +979,42 @@ export const modelingMachine = createMachine(
|
||||
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 }) => {
|
||||
if (type === 'done.invoke.animate-to-face') {
|
||||
sceneInfra.modelingSend('Equip Line tool')
|
||||
|
Reference in New Issue
Block a user