Compare commits
30 Commits
v0.25.2
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
fc95f9e49e | |||
93d9b10e11 | |||
166487433c | |||
5512f99997 | |||
01cc9e751b | |||
bfac6b7dc8 | |||
d1f9a02ffa | |||
d8236dd8da | |||
dabf256e2b | |||
4285e81001 | |||
370375c328 | |||
9f22882c68 | |||
db5331d9b9 | |||
5cc92f0162 | |||
2978e80226 | |||
4a74c60150 | |||
00fa40bbc9 | |||
62b78840b6 | |||
f828c36e58 | |||
8c5b146c94 | |||
61c7d9844d | |||
8d48c17395 | |||
0ff820d4da | |||
c4ff1c2ef1 | |||
b6aba2f29c | |||
7467f7ea50 | |||
0c6d3e0ccf | |||
e82917ea01 | |||
857c1aad3d | |||
dc73acb1b1 |
11470
docs/kcl/std.json
@ -13,14 +13,16 @@ arrays can hold objects and vice versa.
|
||||
|
||||
`true` or `false` work when defining values.
|
||||
|
||||
## Variable declaration
|
||||
## Constant declaration
|
||||
|
||||
Variables are defined with the `let` keyword like so:
|
||||
Constants are defined with the `let` keyword like so:
|
||||
|
||||
```
|
||||
let myBool = false
|
||||
```
|
||||
|
||||
Currently you cannot redeclare a constant.
|
||||
|
||||
## Array
|
||||
|
||||
An array is defined with `[]` braces. What is inside the brackets can
|
||||
|
@ -1,6 +1,15 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import * as fsp from 'fs/promises'
|
||||
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
|
||||
import * as fs from 'fs'
|
||||
import {
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
setup,
|
||||
setupElectron,
|
||||
tearDown,
|
||||
} from './test-utils'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.beforeEach(async ({ context, page }, testInfo) => {
|
||||
await setup(context, page, testInfo)
|
||||
@ -277,3 +286,584 @@ test.describe('when using the file tree to', () => {
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Renaming in the file tree', () => {
|
||||
test(
|
||||
'A file you have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const newFileName = 'newFileName'
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await fileToRename.click()
|
||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||
const url = page.url()
|
||||
expect(url).toContain(newFileName)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await expect(projectMenuButton).not.toContainText('main.kcl')
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
expect(url).not.toContain('main.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'A file you do not have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const newFileName = 'newFileName'
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: newFileName + FILE_EXT }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we have not navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(projectMenuButton).not.toContainText(
|
||||
newFileName + FILE_EXT
|
||||
)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain(newFileName)
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('fillet(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you're not inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you are inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and navigate into folder', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
await folderToRename.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
const newUrl = page.url()
|
||||
expect(newUrl).toContain('folderToRename')
|
||||
expect(newUrl).toContain('someFileWithin.kcl')
|
||||
expect(newUrl).not.toContain('main.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(60000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
||||
const urlSnippet = encodeURIComponent(
|
||||
join(newFolderName, 'someFileWithin.kcl')
|
||||
)
|
||||
await page.waitForURL(new RegExp(urlSnippet))
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
|
||||
// URL is synchronous, so we check the other stuff first
|
||||
const url = page.url()
|
||||
expect(url).not.toContain('main.kcl')
|
||||
expect(url).toContain(newFolderName)
|
||||
expect(url).toContain('someFileWithin.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Deleting items from the file pane', () => {
|
||||
test(
|
||||
`delete file when main.kcl exists, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const testDir = join(dir, 'testProject')
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(testDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(testDir, 'fileToDelete.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('testProject')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToDelete = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete fileToDelete.kcl', async () => {
|
||||
await fileToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation', async () => {
|
||||
await u.waitForPageLoad()
|
||||
await expect(fileToDelete).not.toBeVisible()
|
||||
await u.closeFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('circle(')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme(
|
||||
'TODO - delete file we have open when main.kcl does not exist',
|
||||
async () => {}
|
||||
)
|
||||
|
||||
test(
|
||||
`Delete folder we are not in, don't navigate`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToDelete = page.getByRole('button', {
|
||||
name: 'folderToDelete',
|
||||
})
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and open project pane', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and no navigation', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`Delete folder we are in, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToDelete'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToDelete', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToDelete = page.getByRole('button', {
|
||||
name: 'folderToDelete',
|
||||
})
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate into folderToDelete', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await u.closeKclCodePanel()
|
||||
await u.openFilePanel()
|
||||
|
||||
await folderToDelete.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
})
|
||||
|
||||
await test.step('Delete folderToDelete', async () => {
|
||||
await folderToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation to main.kcl', async () => {
|
||||
await expect(folderToDelete).not.toBeAttached()
|
||||
await expect(fileWithinFolder).not.toBeAttached()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme('TODO - delete folder we are in, with no main.kcl', async () => {})
|
||||
})
|
||||
|
@ -38,7 +38,7 @@ test(
|
||||
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
|
||||
|
||||
// Find the make button
|
||||
const makeButton = page.getByRole('button', { name: 'Make' })
|
||||
const makeButton = page.getByRole('button', { name: 'Make part' })
|
||||
// Make sure the button is visible but disabled
|
||||
await expect(makeButton).toBeVisible()
|
||||
await expect(makeButton).toBeDisabled()
|
||||
|
@ -12,7 +12,6 @@ import {
|
||||
import fsp from 'fs/promises'
|
||||
import fs from 'fs'
|
||||
import { join } from 'path'
|
||||
import { FILE_EXT } from 'lib/constants'
|
||||
|
||||
test.afterEach(async ({ page }, testInfo) => {
|
||||
await tearDown(page, testInfo)
|
||||
@ -1371,455 +1370,6 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test.describe('Renaming in the file tree', () => {
|
||||
test(
|
||||
'A file you have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const newFileName = 'newFileName'
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'newFileName.kcl' }) })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
await fileToRename.click()
|
||||
await expect(projectMenuButton).toContainText('fileToRename.kcl')
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText(newFileName + FILE_EXT)
|
||||
const url = page.url()
|
||||
expect(url).toContain(newFileName)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
await expect(projectMenuButton).not.toContainText('main.kcl')
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
expect(url).not.toContain('main.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('circle(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'A file you do not have open',
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const newFileName = 'newFileName'
|
||||
const checkUnRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', 'fileToRename.kcl')
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const checkRenamedFS = () => {
|
||||
const filePath = join(dir, 'Test Project', `${newFileName}.kcl`)
|
||||
return fs.existsSync(filePath)
|
||||
}
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToRename = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToRename.kcl' }) })
|
||||
const renamedFile = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: newFileName + FILE_EXT }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const renameInput = page.getByPlaceholder('fileToRename.kcl')
|
||||
const codeLocator = page.locator('.cm-content')
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(fileToRename).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeTruthy()
|
||||
expect(checkRenamedFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the file', async () => {
|
||||
await fileToRename.click({ button: 'right' })
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFileName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the file is renamed', async () => {
|
||||
await expect(fileToRename).not.toBeAttached()
|
||||
await expect(renamedFile).toBeVisible()
|
||||
expect(checkUnRenamedFS()).toBeFalsy()
|
||||
expect(checkRenamedFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await test.step('Verify we have not navigated', async () => {
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(projectMenuButton).not.toContainText(
|
||||
newFileName + FILE_EXT
|
||||
)
|
||||
await expect(projectMenuButton).not.toContainText('fileToRename.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain(newFileName)
|
||||
expect(url).not.toContain('fileToRename.kcl')
|
||||
|
||||
await u.openKclCodePanel()
|
||||
await expect(codeLocator).toContainText('fillet(')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you're not inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and file pane', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and no navigation occurred', async () => {
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
`A folder you are inside`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browser: _ }, testInfo) => {
|
||||
const { electronApp, page, dir } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
await fsp.mkdir(join(dir, 'Test Project'), { recursive: true })
|
||||
await fsp.mkdir(join(dir, 'Test Project', 'folderToRename'), {
|
||||
recursive: true,
|
||||
})
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(dir, 'Test Project', 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(dir, 'Test Project', 'folderToRename', 'someFileWithin.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectLink = page.getByText('Test Project')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const folderToRename = page.getByRole('button', {
|
||||
name: 'folderToRename',
|
||||
})
|
||||
const renamedFolder = page.getByRole('button', { name: 'newFolderName' })
|
||||
const fileWithinFolder = page.getByRole('listitem').filter({
|
||||
has: page.getByRole('button', { name: 'someFileWithin.kcl' }),
|
||||
})
|
||||
const renameMenuItem = page.getByRole('button', { name: 'Rename' })
|
||||
const originalFolderName = 'folderToRename'
|
||||
const renameInput = page.getByPlaceholder(originalFolderName)
|
||||
const newFolderName = 'newFolderName'
|
||||
const checkUnRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', originalFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
const checkRenamedFolderFS = () => {
|
||||
const folderPath = join(dir, 'Test Project', newFolderName)
|
||||
return fs.existsSync(folderPath)
|
||||
}
|
||||
|
||||
await test.step('Open project and navigate into folder', async () => {
|
||||
await expect(projectLink).toBeVisible()
|
||||
await projectLink.click()
|
||||
await expect(projectMenuButton).toBeVisible()
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
|
||||
const url = page.url()
|
||||
expect(url).toContain('main.kcl')
|
||||
expect(url).not.toContain('folderToRename')
|
||||
|
||||
await u.openFilePanel()
|
||||
await expect(folderToRename).toBeVisible()
|
||||
await folderToRename.click()
|
||||
await expect(fileWithinFolder).toBeVisible()
|
||||
await fileWithinFolder.click()
|
||||
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
const newUrl = page.url()
|
||||
expect(newUrl).toContain('folderToRename')
|
||||
expect(newUrl).toContain('someFileWithin.kcl')
|
||||
expect(newUrl).not.toContain('main.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeTruthy()
|
||||
expect(checkRenamedFolderFS()).toBeFalsy()
|
||||
})
|
||||
|
||||
await test.step('Rename the folder', async () => {
|
||||
await page.waitForTimeout(2000)
|
||||
await folderToRename.click({ button: 'right' })
|
||||
await expect(renameMenuItem).toBeVisible()
|
||||
await renameMenuItem.click()
|
||||
await expect(renameInput).toBeVisible()
|
||||
await renameInput.fill(newFolderName)
|
||||
await page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await test.step('Verify the folder is renamed, and navigated to new path', async () => {
|
||||
const urlSnippet = encodeURIComponent(
|
||||
join(newFolderName, 'someFileWithin.kcl')
|
||||
)
|
||||
await page.waitForURL(new RegExp(urlSnippet))
|
||||
await expect(projectMenuButton).toContainText('someFileWithin.kcl')
|
||||
await expect(renamedFolder).toBeVisible()
|
||||
await expect(folderToRename).not.toBeAttached()
|
||||
|
||||
// URL is synchronous, so we check the other stuff first
|
||||
const url = page.url()
|
||||
expect(url).not.toContain('main.kcl')
|
||||
expect(url).toContain(newFolderName)
|
||||
expect(url).toContain('someFileWithin.kcl')
|
||||
expect(checkUnRenamedFolderFS()).toBeFalsy()
|
||||
expect(checkRenamedFolderFS()).toBeTruthy()
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Deleting files from the file pane', () => {
|
||||
test(
|
||||
`when main.kcl exists, navigate to main.kcl`,
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const testDir = join(dir, 'testProject')
|
||||
await fsp.mkdir(testDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
join(testDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||
join(testDir, 'fileToDelete.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
page.on('console', console.log)
|
||||
|
||||
// Constants and locators
|
||||
const projectCard = page.getByText('testProject')
|
||||
const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
const fileToDelete = page
|
||||
.getByRole('listitem')
|
||||
.filter({ has: page.getByRole('button', { name: 'fileToDelete.kcl' }) })
|
||||
const deleteMenuItem = page.getByRole('button', { name: 'Delete' })
|
||||
const deleteConfirmation = page.getByTestId('delete-confirmation')
|
||||
|
||||
await test.step('Open project and navigate to fileToDelete.kcl', async () => {
|
||||
await projectCard.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openFilePanel()
|
||||
|
||||
await fileToDelete.click()
|
||||
await u.waitForPageLoad()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||
await u.closeKclCodePanel()
|
||||
})
|
||||
|
||||
await test.step('Delete fileToDelete.kcl', async () => {
|
||||
await fileToDelete.click({ button: 'right' })
|
||||
await expect(deleteMenuItem).toBeVisible()
|
||||
await deleteMenuItem.click()
|
||||
await expect(deleteConfirmation).toBeVisible()
|
||||
await deleteConfirmation.click()
|
||||
})
|
||||
|
||||
await test.step('Check deletion and navigation', async () => {
|
||||
await u.waitForPageLoad()
|
||||
await expect(fileToDelete).not.toBeVisible()
|
||||
await u.closeFilePanel()
|
||||
await u.openKclCodePanel()
|
||||
await expect(u.codeLocator).toContainText('circle(')
|
||||
await expect(projectMenuButton).toContainText('main.kcl')
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test.fixme('TODO - when main.kcl does not exist', async () => {})
|
||||
})
|
||||
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
|
@ -346,10 +346,7 @@ const sketch001 = startSketchAt([-0, -0])
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
await expect(errorToastMessage).toBeVisible()
|
||||
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
await expect(engineErrorToastMessage).toBeVisible()
|
||||
|
@ -40,7 +40,7 @@ test.describe('Sketch tests', () => {
|
||||
const screwRadius = 3
|
||||
const wireRadius = 2
|
||||
const wireOffset = 0.5
|
||||
|
||||
|
||||
const screwHole = startSketchOn('XY')
|
||||
${startProfileAt1}
|
||||
|> arc({
|
||||
@ -48,7 +48,7 @@ test.describe('Sketch tests', () => {
|
||||
angle_start: 0,
|
||||
angle_end: 360
|
||||
}, %)
|
||||
|
||||
|
||||
const part001 = startSketchOn('XY')
|
||||
${startProfileAt2}
|
||||
|> xLine(width * .5, %)
|
||||
@ -57,7 +57,7 @@ test.describe('Sketch tests', () => {
|
||||
|> close(%)
|
||||
|> hole(screwHole, %)
|
||||
|> extrude(thickness, %)
|
||||
|
||||
|
||||
const part002 = startSketchOn('-XZ')
|
||||
${startProfileAt3}
|
||||
|> xLine(width / 4, %)
|
||||
@ -618,19 +618,19 @@ test.describe('Sketch tests', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> startProfileAt([1.53, 0], %)`
|
||||
codeStr += ` |> startProfileAt([2.03, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(30, 0)
|
||||
codeStr += ` |> line([1.53, 0], %)`
|
||||
codeStr += ` |> line([2.04, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(0, 30)
|
||||
codeStr += ` |> line([0, -1.53], %)`
|
||||
codeStr += ` |> line([0, -2.03], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(-30, 0)
|
||||
codeStr += ` |> line([-1.53, 0], %)`
|
||||
codeStr += ` |> line([-2.04, 0], %)`
|
||||
await expect(u.codeLocator).toHaveText(codeStr)
|
||||
|
||||
await click00r(undefined, undefined)
|
||||
@ -954,4 +954,68 @@ const sketch002 = startSketchOn(extrude001, 'END')
|
||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||
).toBeLessThan(3)
|
||||
})
|
||||
|
||||
test('Can attempt to sketch on revolved face', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
test.skip(
|
||||
browserName === 'webkit',
|
||||
'Skip on Safari until `window.tearDown` is working there'
|
||||
)
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`const lugHeadLength = 0.25
|
||||
const lugDiameter = 0.5
|
||||
const lugLength = 2
|
||||
|
||||
fn lug = (origin, length, diameter, plane) => {
|
||||
const lugSketch = startSketchOn(plane)
|
||||
|> startProfileAt([origin[0] + lugDiameter / 2, origin[1]], %)
|
||||
|> angledLineOfYLength({ angle: 60, length: lugHeadLength }, %)
|
||||
|> xLineTo(0 + .001, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close(%)
|
||||
|> revolve({ axis: "Y" }, %)
|
||||
|
||||
return lugSketch
|
||||
}
|
||||
|
||||
lug([0, 0], 10, .5, "XY")`
|
||||
)
|
||||
})
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
/***
|
||||
* Test Plan
|
||||
* Start the sketch mode
|
||||
* Click the middle of the screen which should click the top face that is revolved
|
||||
* Wait till you see the line tool be enabled
|
||||
* Wait till you see the exit sketch enabled
|
||||
*
|
||||
* This is supposed to test that you are allowed to go into sketch mode to sketch on a revolved face
|
||||
*/
|
||||
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await expect(async () => {
|
||||
await page.mouse.click(600, 250)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).toHaveAttribute('aria-pressed', 'true')
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
@ -441,6 +441,34 @@ export async function getUtils(page: Page, test_?: typeof test) {
|
||||
}
|
||||
return maxDiff
|
||||
},
|
||||
getPixelRGBs: async (
|
||||
coords: { x: number; y: number },
|
||||
radius: number
|
||||
): Promise<[number, number, number][]> => {
|
||||
const buffer = await page.screenshot({
|
||||
fullPage: true,
|
||||
})
|
||||
const screenshot = await PNG.sync.read(buffer)
|
||||
const pixMultiplier: number = await page.evaluate(
|
||||
'window.devicePixelRatio'
|
||||
)
|
||||
const allCords: [number, number][] = [[coords.x, coords.y]]
|
||||
for (let i = 1; i < radius; i++) {
|
||||
allCords.push([coords.x + i, coords.y])
|
||||
allCords.push([coords.x - i, coords.y])
|
||||
allCords.push([coords.x, coords.y + i])
|
||||
allCords.push([coords.x, coords.y - i])
|
||||
}
|
||||
return allCords.map(([x, y]) => {
|
||||
const index =
|
||||
(screenshot.width * y * pixMultiplier + x * pixMultiplier) * 4 // rbga is 4 channels
|
||||
return [
|
||||
screenshot.data[index],
|
||||
screenshot.data[index + 1],
|
||||
screenshot.data[index + 2],
|
||||
]
|
||||
})
|
||||
},
|
||||
doAndWaitForImageDiff: (fn: () => Promise<unknown>, diffCount = 200) =>
|
||||
new Promise<boolean>((resolve) => {
|
||||
;(async () => {
|
||||
|
@ -31,6 +31,8 @@ test.describe('Testing selections', () => {
|
||||
|
||||
const xAxisClick = () =>
|
||||
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
||||
const xAxisClickAfterExitingSketch = () =>
|
||||
page.mouse.click(639, 278).then(() => page.waitForTimeout(100))
|
||||
const emptySpaceHover = () =>
|
||||
test.step('Hover over empty space', async () => {
|
||||
await page.mouse.move(700, 143, { steps: 5 })
|
||||
@ -44,9 +46,13 @@ test.describe('Testing selections', () => {
|
||||
)
|
||||
})
|
||||
const topHorzSegmentClick = () =>
|
||||
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
|
||||
page.mouse
|
||||
.click(startXPx, 500 - PUR * 20)
|
||||
.then(() => page.waitForTimeout(100))
|
||||
const bottomHorzSegmentClick = () =>
|
||||
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
|
||||
page.mouse
|
||||
.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
.then(() => page.waitForTimeout(100))
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await expect(
|
||||
@ -196,6 +202,8 @@ test.describe('Testing selections', () => {
|
||||
|
||||
// select a line, this verifies that sketches in the scene can be selected outside of sketch mode
|
||||
await topHorzSegmentClick()
|
||||
await xAxisClickAfterExitingSketch()
|
||||
await page.waitForTimeout(100)
|
||||
await emptySpaceHover()
|
||||
|
||||
// enter sketch again
|
||||
@ -425,7 +433,7 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
|> line([0, 20.03], %)
|
||||
|> line([62.61, 0], %, $seg03)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
|> close(%)
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
@ -528,11 +536,22 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
await page.waitForTimeout(100)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const extrusionTop: Coords2d = [800, 240]
|
||||
const extrusionTopCap: Coords2d = [800, 240]
|
||||
const flatExtrusionFace: Coords2d = [960, 160]
|
||||
const arc: Coords2d = [840, 160]
|
||||
const tangentialArcTo: Coords2d = [840, 160]
|
||||
const close: Coords2d = [720, 200]
|
||||
const nothing: Coords2d = [600, 200]
|
||||
const closeEdge: Coords2d = [744, 233]
|
||||
const closeAdjacentEdge: Coords2d = [688, 123]
|
||||
const closeOppositeEdge: Coords2d = [687, 169]
|
||||
|
||||
const tangentialArcEdge: Coords2d = [811, 142]
|
||||
const tangentialArcOppositeEdge: Coords2d = [820, 180]
|
||||
const tangentialArcAdjacentEdge: Coords2d = [893, 165]
|
||||
|
||||
const straightSegmentEdge: Coords2d = [819, 369]
|
||||
const straightSegmentOppositeEdge: Coords2d = [635, 394]
|
||||
const straightSegmentAdjacentEdge: Coords2d = [679, 329]
|
||||
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await page.mouse.click(nothing[0], nothing[1])
|
||||
@ -540,26 +559,141 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
await page.mouse.move(extrusionTop[0], extrusionTop[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
const checkCodeAtHoverPosition = async (
|
||||
name = '',
|
||||
coord: Coords2d,
|
||||
highlightCode: string,
|
||||
activeLine = highlightCode
|
||||
) => {
|
||||
await test.step(`test selection for: ${name}`, async () => {
|
||||
const highlightedLocator = page.getByTestId('hover-highlight')
|
||||
const activeLineLocator = page.locator('.cm-activeLine')
|
||||
|
||||
await page.mouse.move(arc[0], arc[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
await test.step(`hover should highlight correct code`, async () => {
|
||||
await page.mouse.move(coord[0], coord[1])
|
||||
await expect(highlightedLocator.first()).toBeVisible()
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const textContents = await highlightedLocator.allTextContents()
|
||||
return textContents.join('').replace(/\s+/g, '')
|
||||
})
|
||||
.toBe(highlightCode)
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
})
|
||||
await test.step(`click should put the cursor in the right place`, async () => {
|
||||
await expect(highlightedLocator.first()).not.toBeVisible()
|
||||
await page.mouse.click(coord[0], coord[1])
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const activeLines = await activeLineLocator.allInnerTexts()
|
||||
return activeLines.join('')
|
||||
})
|
||||
.toContain(activeLine)
|
||||
// check pixels near the click location are yellow
|
||||
})
|
||||
await test.step(`check the engine agrees with selections`, async () => {
|
||||
// ultimately the only way we know if the engine agrees with the selection from the FE
|
||||
// perspective is if it highlights the pixels near where we clicked yellow.
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const RGBs = await u.getPixelRGBs({ x: coord[0], y: coord[1] }, 3)
|
||||
for (const rgb of RGBs) {
|
||||
const [r, g, b] = rgb
|
||||
const RGAverage = (r + g) / 2
|
||||
const isRedGreenSameIsh = Math.abs(r - g) < 3
|
||||
const isBlueLessThanRG = RGAverage - b > 45
|
||||
const isYellowy = isRedGreenSameIsh && isBlueLessThanRG
|
||||
if (isYellowy) return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
.toBeTruthy()
|
||||
await page.mouse.click(nothing[0], nothing[1])
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
await page.mouse.move(close[0], close[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
await checkCodeAtHoverPosition(
|
||||
'extrusionTopCap',
|
||||
extrusionTopCap,
|
||||
'startProfileAt([20,0],%)',
|
||||
'startProfileAt([20, 0], %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'flatExtrusionFace',
|
||||
flatExtrusionFace,
|
||||
`angledLineThatIntersects({angle:3.14,intersectTag:a,offset:0},%)extrude(5+7,%)`,
|
||||
'}, %)'
|
||||
)
|
||||
|
||||
await page.mouse.move(flatExtrusionFace[0], flatExtrusionFace[1])
|
||||
await expect(page.getByTestId('hover-highlight')).toHaveCount(6) // multiple lines
|
||||
await page.mouse.move(nothing[0], nothing[1])
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.getByTestId('hover-highlight').first()).not.toBeVisible()
|
||||
await checkCodeAtHoverPosition(
|
||||
'tangentialArcTo',
|
||||
tangentialArcTo,
|
||||
'tangentialArcTo([13.14+0,13.14],%)extrude(5+7,%)',
|
||||
'tangentialArcTo([13.14 + 0, 13.14], %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'tangentialArcEdge',
|
||||
tangentialArcEdge,
|
||||
`tangentialArcTo([13.14+0,13.14],%)`,
|
||||
'tangentialArcTo([13.14 + 0, 13.14], %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'tangentialArcOppositeEdge',
|
||||
tangentialArcOppositeEdge,
|
||||
`tangentialArcTo([13.14+0,13.14],%)`,
|
||||
'tangentialArcTo([13.14 + 0, 13.14], %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'tangentialArcAdjacentEdge',
|
||||
tangentialArcAdjacentEdge,
|
||||
`tangentialArcTo([13.14+0,13.14],%)`,
|
||||
'tangentialArcTo([13.14 + 0, 13.14], %)'
|
||||
)
|
||||
|
||||
await checkCodeAtHoverPosition(
|
||||
'close',
|
||||
close,
|
||||
'close(%)extrude(5+7,%)',
|
||||
'close(%)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'closeEdge',
|
||||
closeEdge,
|
||||
`close(%)`,
|
||||
'close(%)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'closeAdjacentEdge',
|
||||
closeAdjacentEdge,
|
||||
`close(%)`,
|
||||
'close(%)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'closeOppositeEdge',
|
||||
closeOppositeEdge,
|
||||
`close(%)`,
|
||||
'close(%)'
|
||||
)
|
||||
|
||||
await checkCodeAtHoverPosition(
|
||||
'straightSegmentEdge',
|
||||
straightSegmentEdge,
|
||||
`angledLineToY({angle:30,to:11.14},%)`,
|
||||
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'straightSegmentOppositeEdge',
|
||||
straightSegmentOppositeEdge,
|
||||
`angledLineToY({angle:30,to:11.14},%)`,
|
||||
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
||||
)
|
||||
await checkCodeAtHoverPosition(
|
||||
'straightSegmentAdjancentEdge',
|
||||
straightSegmentAdjacentEdge,
|
||||
`angledLineToY({angle:30,to:11.14},%)`,
|
||||
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
||||
)
|
||||
})
|
||||
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({
|
||||
page,
|
||||
@ -888,7 +1022,7 @@ const extrude001 = extrude(50, sketch001)
|
||||
|> line([4.95, -8], %)
|
||||
|> line([-20.38, -10.12], %)
|
||||
|> line([-15.79, 17.08], %)
|
||||
|
||||
|
||||
fn yohey = (pos) => {
|
||||
const sketch004 = startSketchOn('XZ')
|
||||
${extrudeAndEditBlockedInFunction}
|
||||
@ -898,7 +1032,7 @@ const extrude001 = extrude(50, sketch001)
|
||||
|> line([-15.79, 17.08], %)
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
yohey([15.79, -34.6])
|
||||
`
|
||||
)
|
||||
|
@ -69,12 +69,15 @@ test.describe('Testing settings', () => {
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await u.waitForAuthSkipAppStart()
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
.waitFor({ state: 'visible' })
|
||||
})
|
||||
|
||||
// Selectors and constants
|
||||
const paneButtonLocator = page.getByTestId('debug-pane-button')
|
||||
const headingLocator = page.getByRole('heading', {
|
||||
name: 'Settings',
|
||||
@ -82,11 +85,23 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
||||
|
||||
// Open the settings modal with the browser keyboard shortcut
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
||||
await page.keyboard.press('ControlOrMeta+Shift+,')
|
||||
await expect(headingLocator).toBeVisible()
|
||||
|
||||
await expect(headingLocator).toBeVisible()
|
||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
/** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */
|
||||
await test.step(`Confirm that this dialog has a solid background`, async () => {
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), {
|
||||
timeout: 1000,
|
||||
message:
|
||||
'Checking for solid background, should not see default plane colors',
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await page.locator('#showDebugPanel').getByText('OffOn').click()
|
||||
})
|
||||
|
||||
// Close it and open again with keyboard shortcut, while KCL editor is focused
|
||||
// Put the cursor in the editor
|
||||
@ -262,8 +277,6 @@ test.describe('Testing settings', () => {
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
// Selectors and constants
|
||||
const userThemeColor = '120'
|
||||
const projectThemeColor = '50'
|
||||
@ -277,7 +290,6 @@ test.describe('Testing settings', () => {
|
||||
const projectLink = page.getByText('bracket')
|
||||
const logoLink = page.getByTestId('app-logo')
|
||||
|
||||
// Open the app and set the user theme color
|
||||
await test.step('Set user theme color on home', async () => {
|
||||
await expect(settingsOpenButton).toBeVisible()
|
||||
await settingsOpenButton.click()
|
||||
@ -296,13 +308,15 @@ test.describe('Testing settings', () => {
|
||||
await expect(projectSettingsTab).toBeChecked()
|
||||
await themeColorSetting.fill(projectThemeColor)
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await test.step('Refresh the application and see project setting applied', async () => {
|
||||
// Make sure we're done navigating before we reload
|
||||
await expect(settingsCloseButton).not.toBeVisible()
|
||||
await page.reload({ waitUntil: 'domcontentloaded' })
|
||||
|
||||
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
|
||||
await settingsCloseButton.click()
|
||||
})
|
||||
|
||||
await test.step(`Navigate back to the home view and see user setting applied`, async () => {
|
||||
|
@ -183,7 +183,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.4.2",
|
||||
"vite": "^5.4.3",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
|
@ -124,7 +124,7 @@ export function Toolbar({
|
||||
}, [currentMode, disableAllButtons, configCallbackProps])
|
||||
|
||||
return (
|
||||
<menu className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-20 dark:border-chalkboard-80 border-t-0 shadow-sm">
|
||||
<menu className="max-w-full whitespace-nowrap rounded-b px-2 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 relative border border-chalkboard-30 dark:border-chalkboard-80 border-t-0 shadow-sm">
|
||||
<ul
|
||||
{...props}
|
||||
ref={toolbarButtonsRef}
|
||||
|
@ -31,6 +31,7 @@ import { reportRejection } from 'lib/trap'
|
||||
|
||||
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
||||
const FRAMES_TO_ANIMATE_IN = 30
|
||||
const ORTHOGRAPHIC_MAGIC_FOV = 4
|
||||
|
||||
const tempQuaternion = new Quaternion() // just used for maths
|
||||
|
||||
@ -84,7 +85,7 @@ export class CameraControls {
|
||||
pendingPan: Vector2 | null = null
|
||||
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
||||
isFovAnimationInProgress = false
|
||||
fovBeforeOrtho = 45
|
||||
perspectiveFovBeforeOrtho = 45
|
||||
get isPerspective() {
|
||||
return this.camera instanceof PerspectiveCamera
|
||||
}
|
||||
@ -398,7 +399,7 @@ export class CameraControls {
|
||||
const zoomFudgeFactor = 2280
|
||||
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
||||
}
|
||||
const panSpeed = (distance / 1000 / 45) * this.fovBeforeOrtho
|
||||
const panSpeed = (distance / 1000 / 45) * this.perspectiveFovBeforeOrtho
|
||||
this.pendingPan.x += -deltaMove.x * panSpeed
|
||||
this.pendingPan.y += deltaMove.y * panSpeed
|
||||
}
|
||||
@ -443,8 +444,19 @@ export class CameraControls {
|
||||
}
|
||||
|
||||
onMouseWheel = (event: WheelEvent) => {
|
||||
const interaction = this.getInteractionType(event)
|
||||
if (interaction === 'none') return
|
||||
event.preventDefault()
|
||||
|
||||
if (this.syncDirection === 'engineToClient') {
|
||||
this.zoomDataFromLastFrame = event.deltaY
|
||||
if (interaction === 'zoom') {
|
||||
this.zoomDataFromLastFrame = event.deltaY
|
||||
} else {
|
||||
// This case will get handled when we add pan and rotate using Apple trackpad.
|
||||
console.error(
|
||||
`Unexpected interaction type for engineToClient wheel event: ${interaction}`
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -454,8 +466,16 @@ export class CameraControls {
|
||||
// zoom commands to engine. This means dropping some zoom
|
||||
// commands too.
|
||||
// From onMouseMove zoom handling which seems to be really smooth
|
||||
|
||||
this.handleStart()
|
||||
this.pendingZoom = 1 + (event.deltaY / window.devicePixelRatio) * 0.001
|
||||
if (interaction === 'zoom') {
|
||||
this.pendingZoom = 1 + (event.deltaY / window.devicePixelRatio) * 0.001
|
||||
} else {
|
||||
// This case will get handled when we add pan and rotate using Apple trackpad.
|
||||
console.error(
|
||||
`Unexpected interaction type for wheel event: ${interaction}`
|
||||
)
|
||||
}
|
||||
this.handleEnd()
|
||||
}
|
||||
|
||||
@ -516,19 +536,15 @@ export class CameraControls {
|
||||
_usePerspectiveCamera = () => {
|
||||
const { x: px, y: py, z: pz } = this.camera.position
|
||||
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
||||
const zoom = this.camera.zoom
|
||||
this.camera = this.createPerspectiveCamera()
|
||||
|
||||
this.camera.position.set(px, py, pz)
|
||||
this.camera.quaternion.set(qx, qy, qz, qw)
|
||||
const zoomFudgeFactor = 2280
|
||||
const distance = zoomFudgeFactor / (zoom * this.lastPerspectiveFov)
|
||||
const direction = new Vector3().subVectors(
|
||||
this.camera.position,
|
||||
this.target
|
||||
)
|
||||
direction.normalize()
|
||||
this.camera.position.copy(this.target).addScaledVector(direction, distance)
|
||||
}
|
||||
usePerspectiveCamera = async (forceSend = false) => {
|
||||
this._usePerspectiveCamera()
|
||||
@ -980,9 +996,9 @@ export class CameraControls {
|
||||
)
|
||||
this.isFovAnimationInProgress = true
|
||||
let currentFov = this.lastPerspectiveFov
|
||||
this.fovBeforeOrtho = currentFov
|
||||
this.perspectiveFovBeforeOrtho = currentFov
|
||||
|
||||
const targetFov = 4
|
||||
const targetFov = ORTHOGRAPHIC_MAGIC_FOV
|
||||
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
|
||||
let frameWaitOnFinish = 10
|
||||
|
||||
@ -1018,9 +1034,9 @@ export class CameraControls {
|
||||
)
|
||||
}
|
||||
this.isFovAnimationInProgress = true
|
||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = 4
|
||||
let currentFov = 4
|
||||
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = ORTHOGRAPHIC_MAGIC_FOV
|
||||
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
|
||||
const initialCameraUp = this.camera.up.clone()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.usePerspectiveCamera()
|
||||
@ -1056,9 +1072,8 @@ export class CameraControls {
|
||||
)
|
||||
}
|
||||
this.isFovAnimationInProgress = true
|
||||
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
|
||||
this.lastPerspectiveFov = 4
|
||||
let currentFov = 4
|
||||
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
|
||||
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
|
||||
const initialCameraUp = this.camera.up.clone()
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.usePerspectiveCamera()
|
||||
@ -1127,7 +1142,7 @@ export class CameraControls {
|
||||
this.deferReactUpdate(this.reactCameraProperties)
|
||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||
}
|
||||
getInteractionType = (event: any) =>
|
||||
getInteractionType = (event: MouseEvent) =>
|
||||
_getInteractionType(
|
||||
this.interactionGuards,
|
||||
event,
|
||||
@ -1235,16 +1250,21 @@ function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
||||
|
||||
function _getInteractionType(
|
||||
interactionGuards: MouseGuard,
|
||||
event: any,
|
||||
event: MouseEvent | WheelEvent,
|
||||
enablePan: boolean,
|
||||
enableRotate: boolean,
|
||||
enableZoom: boolean
|
||||
): interactionType | 'none' {
|
||||
let state: interactionType | 'none' = 'none'
|
||||
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
||||
if (enableRotate && interactionGuards.rotate.callback(event)) return 'rotate'
|
||||
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
||||
return state
|
||||
if (event instanceof WheelEvent) {
|
||||
if (enableZoom && interactionGuards.zoom.scrollCallback(event))
|
||||
return 'zoom'
|
||||
} else {
|
||||
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
||||
if (enableRotate && interactionGuards.rotate.callback(event))
|
||||
return 'rotate'
|
||||
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
||||
}
|
||||
return 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,8 +29,8 @@ export const ActionIcon = ({
|
||||
size = 'md',
|
||||
children,
|
||||
}: ActionIconProps) => {
|
||||
const computedIconClassName = `h-auto text-inherit dark:text-current !group-disabled:text-chalkboard-60 !group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 !group-disabled:bg-chalkboard-30 !dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||
const computedIconClassName = `h-auto text-inherit dark:text-current group-disabled:text-chalkboard-60 group-disabled:text-chalkboard-60 ${iconClassName}`
|
||||
const computedBgClassName = `bg-chalkboard-20 dark:bg-chalkboard-80 group-disabled:bg-chalkboard-30 dark:group-disabled:bg-chalkboard-80 ${bgClassName}`
|
||||
|
||||
return (
|
||||
<div
|
||||
|
@ -681,6 +681,21 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
logs: (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-label="logs"
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M6.5 15C6.5 14.1716 5.82843 13.5 5 13.5C4.17157 13.5 3.5 14.1716 3.5 15C3.5 15.8284 4.17157 16.5 5 16.5C5.82843 16.5 6.5 15.8284 6.5 15ZM6.5 10C6.5 9.17157 5.82843 8.5 5 8.5C4.17157 8.5 3.5 9.17157 3.5 10C3.5 10.8284 4.17157 11.5 5 11.5C5.82843 11.5 6.5 10.8284 6.5 10ZM5 3.5C5.82843 3.5 6.5 4.17157 6.5 5C6.5 5.82843 5.82843 6.5 5 6.5C4.17157 6.5 3.5 5.82843 3.5 5C3.5 4.17157 4.17157 3.5 5 3.5ZM8.5 5.5H16.5V4.5H8.5V5.5ZM8.5 10.5H16.5V9.5H8.5V10.5ZM16.5 15.5H8.5V14.5H16.5V15.5Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
'make-variable': (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -38,7 +38,7 @@ import {
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import {
|
||||
Selections,
|
||||
canExtrudeSelection,
|
||||
canSweepSelection,
|
||||
handleSelectionBatch,
|
||||
isSelectionLastLine,
|
||||
isRangeInbetweenCharacters,
|
||||
@ -62,8 +62,8 @@ import {
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, parse, recast } from 'lang/wasm'
|
||||
import {
|
||||
doesSceneHaveSweepableSketch,
|
||||
getNodePathFromSourceRange,
|
||||
hasExtrudableGeometry,
|
||||
isSingleCursorInPipe,
|
||||
} from 'lang/queryAst'
|
||||
import { exportFromEngine } from 'lib/exportFromEngine'
|
||||
@ -415,20 +415,9 @@ export const ModelingMachineProvider = ({
|
||||
selection: { type: 'default_scene' },
|
||||
}
|
||||
|
||||
// Artificially delay the export in playwright tests
|
||||
toast
|
||||
.promise(
|
||||
exportFromEngine({
|
||||
format: format,
|
||||
}),
|
||||
|
||||
{
|
||||
loading: 'Starting print...',
|
||||
success: 'Started print successfully',
|
||||
error: 'Error while starting print',
|
||||
}
|
||||
)
|
||||
.catch(reportRejection)
|
||||
exportFromEngine({
|
||||
format: format,
|
||||
}).catch(reportRejection)
|
||||
},
|
||||
'Engine export': ({ event }) => {
|
||||
if (event.type !== 'Export') return
|
||||
@ -482,18 +471,9 @@ export const ModelingMachineProvider = ({
|
||||
format.selection = { type: 'default_scene' }
|
||||
}
|
||||
|
||||
toast
|
||||
.promise(
|
||||
exportFromEngine({
|
||||
format: format as Models['OutputFormat_type'],
|
||||
}),
|
||||
{
|
||||
loading: 'Exporting...',
|
||||
success: 'Exported successfully',
|
||||
error: 'Error while exporting',
|
||||
}
|
||||
)
|
||||
.catch(reportRejection)
|
||||
exportFromEngine({
|
||||
format: format as Models['OutputFormat_type'],
|
||||
}).catch(reportRejection)
|
||||
},
|
||||
'Submit to Text-to-CAD API': ({ event }) => {
|
||||
if (event.type !== 'Text-to-CAD') return
|
||||
@ -528,12 +508,32 @@ export const ModelingMachineProvider = ({
|
||||
// they have no selection, we should enable the button
|
||||
// so they can select the face through the cmdbar
|
||||
// BUT only if there's extrudable geometry
|
||||
if (hasExtrudableGeometry(kclManager.ast)) return true
|
||||
if (doesSceneHaveSweepableSketch(kclManager.ast)) return true
|
||||
return false
|
||||
}
|
||||
if (!isPipe) return false
|
||||
|
||||
return canExtrudeSelection(selectionRanges)
|
||||
return canSweepSelection(selectionRanges)
|
||||
},
|
||||
'has valid revolve selection': ({ context: { selectionRanges } }) => {
|
||||
// A user can begin extruding if they either have 1+ faces selected or nothing selected
|
||||
// TODO: I believe this guard only allows for extruding a single face at a time
|
||||
const isPipe = isSketchPipe(selectionRanges)
|
||||
|
||||
if (
|
||||
selectionRanges.codeBasedSelections.length === 0 ||
|
||||
isRangeInbetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
) {
|
||||
// they have no selection, we should enable the button
|
||||
// so they can select the face through the cmdbar
|
||||
// BUT only if there's extrudable geometry
|
||||
if (doesSceneHaveSweepableSketch(kclManager.ast)) return true
|
||||
return false
|
||||
}
|
||||
if (!isPipe) return false
|
||||
|
||||
return canSweepSelection(selectionRanges)
|
||||
},
|
||||
'has valid selection for deletion': ({
|
||||
context: { selectionRanges },
|
||||
@ -571,7 +571,9 @@ export const ModelingMachineProvider = ({
|
||||
else if (kclManager.ast.body.length === 0)
|
||||
errorMessage += 'due to Empty Scene'
|
||||
console.error(errorMessage)
|
||||
toast.error(errorMessage)
|
||||
toast.error(errorMessage, {
|
||||
id: kclManager.engineCommandManager.pendingExport?.toastId,
|
||||
})
|
||||
return false
|
||||
}
|
||||
},
|
||||
|
@ -2,7 +2,7 @@
|
||||
@apply relative z-0 rounded-r max-w-full flex-auto;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
@apply bg-chalkboard-10/50 focus-within:bg-chalkboard-10/90 backdrop-blur-sm border border-chalkboard-20;
|
||||
@apply bg-chalkboard-10/50 focus-within:bg-chalkboard-10/90 backdrop-blur-sm border border-chalkboard-30;
|
||||
scroll-margin-block-start: 41px;
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
@apply z-10 relative rounded-tr;
|
||||
@apply flex h-[41px] items-center justify-between gap-2 px-2;
|
||||
@apply font-mono text-xs font-bold select-none text-chalkboard-90;
|
||||
@apply bg-chalkboard-10 border-b border-chalkboard-20;
|
||||
@apply bg-chalkboard-10 border-b border-chalkboard-30;
|
||||
}
|
||||
|
||||
:global(.dark) .header {
|
||||
|
@ -1,10 +1,4 @@
|
||||
import {
|
||||
IconDefinition,
|
||||
faBugSlash,
|
||||
faCode,
|
||||
faCodeCommit,
|
||||
faSquareRootVariable,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import { IconDefinition, faBugSlash } from '@fortawesome/free-solid-svg-icons'
|
||||
import { KclEditorMenu } from 'components/ModelingSidebar/ModelingPanes/KclEditorMenu'
|
||||
import { CustomIconName } from 'components/CustomIcon'
|
||||
import { KclEditorPane } from 'components/ModelingSidebar/ModelingPanes/KclEditorPane'
|
||||
@ -68,7 +62,7 @@ export const sidebarPanes: SidebarPane[] = [
|
||||
{
|
||||
id: 'code',
|
||||
title: 'KCL Code',
|
||||
icon: faCode,
|
||||
icon: 'code',
|
||||
Content: KclEditorPane,
|
||||
keybinding: 'Shift + C',
|
||||
Menu: KclEditorMenu,
|
||||
@ -94,7 +88,7 @@ export const sidebarPanes: SidebarPane[] = [
|
||||
{
|
||||
id: 'variables',
|
||||
title: 'Variables',
|
||||
icon: faSquareRootVariable,
|
||||
icon: 'make-variable',
|
||||
Content: MemoryPane,
|
||||
Menu: MemoryPaneMenu,
|
||||
keybinding: 'Shift + V',
|
||||
@ -102,7 +96,7 @@ export const sidebarPanes: SidebarPane[] = [
|
||||
{
|
||||
id: 'logs',
|
||||
title: 'Logs',
|
||||
icon: faCodeCommit,
|
||||
icon: 'logs',
|
||||
Content: LogsPane,
|
||||
keybinding: 'Shift + L',
|
||||
},
|
||||
|
@ -55,7 +55,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
id: 'export',
|
||||
title: 'Export part',
|
||||
icon: 'floppyDiskArrow',
|
||||
iconClassName: '!p-0',
|
||||
keybinding: 'Ctrl + Shift + E',
|
||||
action: () =>
|
||||
commandBarSend({
|
||||
@ -67,7 +66,6 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
id: 'make',
|
||||
title: 'Make part',
|
||||
icon: 'printer3d',
|
||||
iconClassName: '!p-0',
|
||||
keybinding: 'Ctrl + Shift + M',
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
action: async () => {
|
||||
@ -181,7 +179,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
className={
|
||||
(context.store?.openPanes.length === 0 ? 'rounded-r ' : '') +
|
||||
'relative z-[2] pointer-events-auto p-0 col-start-1 col-span-1 h-fit w-fit flex flex-col ' +
|
||||
'bg-chalkboard-10 border border-solid border-chalkboard-20 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 '
|
||||
'bg-chalkboard-10 border border-solid border-chalkboard-30 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 shadow-sm '
|
||||
}
|
||||
>
|
||||
<ul
|
||||
@ -204,7 +202,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||
</ul>
|
||||
{filteredActions.length > 0 && (
|
||||
<>
|
||||
<hr className="w-full border-chalkboard-20 dark:border-chalkboard-80" />
|
||||
<hr className="w-full border-chalkboard-30 dark:border-chalkboard-80" />
|
||||
<ul
|
||||
id="sidebar-actions"
|
||||
className="w-fit p-2 flex flex-col gap-2"
|
||||
@ -292,7 +290,7 @@ function ModelingPaneButton({
|
||||
return (
|
||||
<div id={paneConfig.id + '-button-holder'}>
|
||||
<button
|
||||
className="pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||
className="group pointer-events-auto flex items-center justify-center border-transparent dark:border-transparent disabled:!border-transparent p-0 m-0 rounded-sm !outline-0 focus-visible:border-primary"
|
||||
onClick={onClick}
|
||||
name={paneConfig.title}
|
||||
data-testid={paneConfig.id + '-pane-button'}
|
||||
@ -302,13 +300,9 @@ function ModelingPaneButton({
|
||||
>
|
||||
<ActionIcon
|
||||
icon={paneConfig.icon}
|
||||
className={'p-1 ' + paneConfig.iconClassName || ''}
|
||||
size={paneConfig.iconSize || 'sm'}
|
||||
iconClassName={
|
||||
paneIsOpen
|
||||
? ' !text-chalkboard-10'
|
||||
: '!text-chalkboard-80 dark:!text-chalkboard-30'
|
||||
}
|
||||
className={paneConfig.iconClassName || ''}
|
||||
size={paneConfig.iconSize || 'md'}
|
||||
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
|
||||
bgClassName={
|
||||
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
|
||||
}
|
||||
|
@ -260,7 +260,7 @@ export const Stream = () => {
|
||||
if (state.matches('Sketch')) return
|
||||
if (state.matches({ idle: 'showPlanes' })) return
|
||||
|
||||
if (btnName(e).left) {
|
||||
if (btnName(e.nativeEvent).left) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
sendSelectEventToEngine(e, videoRef.current)
|
||||
}
|
||||
|
@ -9,8 +9,8 @@ import { useModelingContext } from './useModelingContext'
|
||||
import { getEventForSelectWithPoint } from 'lib/selections'
|
||||
import {
|
||||
getCapCodeRef,
|
||||
getExtrudeEdgeCodeRef,
|
||||
getExtrusionFromSuspectedExtrudeSurface,
|
||||
getSweepEdgeCodeRef,
|
||||
getSweepFromSuspectedSweepSurface,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
} from 'lang/std/artifactGraph'
|
||||
@ -47,7 +47,7 @@ export function useEngineConnectionSubscriptions() {
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else if (artifact?.type === 'wall') {
|
||||
const extrusion = getExtrusionFromSuspectedExtrudeSurface(
|
||||
const extrusion = getSweepFromSuspectedSweepSurface(
|
||||
data.entity_id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
@ -61,8 +61,8 @@ export function useEngineConnectionSubscriptions() {
|
||||
? [codeRef.range]
|
||||
: [codeRef.range, extrusion.codeRef.range]
|
||||
)
|
||||
} else if (artifact?.type === 'extrudeEdge') {
|
||||
const codeRef = getExtrudeEdgeCodeRef(
|
||||
} else if (artifact?.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
@ -172,7 +172,7 @@ export function useEngineConnectionSubscriptions() {
|
||||
}
|
||||
const faceId = planeOrFaceId
|
||||
const artifact = engineCommandManager.artifactGraph.get(faceId)
|
||||
const extrusion = getExtrusionFromSuspectedExtrudeSurface(
|
||||
const extrusion = getSweepFromSuspectedSweepSurface(
|
||||
faceId,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
|
@ -410,6 +410,11 @@ describe('testing math operators', () => {
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myVar')?.value).toBe(5)
|
||||
})
|
||||
it('can do power of math', async () => {
|
||||
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
|
||||
const mem = await exe(code)
|
||||
expect(mem.get('myNeg2')?.value).toBe(-2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing Errors', () => {
|
||||
|
@ -251,7 +251,7 @@ export function extrudeSketch(
|
||||
node: Program,
|
||||
pathToNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
distance = createLiteral(4) as Expr
|
||||
distance: Expr = createLiteral(4)
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
@ -259,7 +259,7 @@ export function extrudeSketch(
|
||||
pathToExtrudeArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const _node = { ...node }
|
||||
const _node = structuredClone(node)
|
||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: sketchExpression } = _node1
|
||||
@ -342,6 +342,102 @@ export function extrudeSketch(
|
||||
}
|
||||
}
|
||||
|
||||
export function revolveSketch(
|
||||
node: Program,
|
||||
pathToNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
angle: Expr = createLiteral(4)
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
pathToRevolveArg: PathToNode
|
||||
}
|
||||
| Error {
|
||||
const _node = structuredClone(node)
|
||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: sketchExpression } = _node1
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const _node2 = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(_node2)) return _node2
|
||||
const { node: pipeExpression } = _node2
|
||||
|
||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
||||
|
||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node3)) return _node3
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
|
||||
|
||||
const revolveCall = createCallExpressionStdLib('revolve', [
|
||||
createObjectExpression({
|
||||
angle: angle,
|
||||
// TODO: hard coded 'X' axis for revolve MVP, should be changed.
|
||||
axis: createLiteral('X'),
|
||||
}),
|
||||
createIdentifier(variableDeclarator.id.name),
|
||||
])
|
||||
|
||||
if (shouldPipe) {
|
||||
const pipeChain = createPipeExpression(
|
||||
isInPipeExpression
|
||||
? [...pipeExpression.body, revolveCall]
|
||||
: [sketchExpression as any, revolveCall]
|
||||
)
|
||||
|
||||
variableDeclarator.init = pipeChain
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
...pathToDecleration,
|
||||
['init', 'VariableDeclarator'],
|
||||
['body', ''],
|
||||
[pipeChain.body.length - 1, 'index'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode,
|
||||
pathToRevolveArg,
|
||||
}
|
||||
}
|
||||
|
||||
// We're not creating a pipe expression,
|
||||
// but rather a separate constant for the extrusion
|
||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
|
||||
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
|
||||
const sketchIndexInPathToNode =
|
||||
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
|
||||
const sketchIndexInBody = pathToDecleration[sketchIndexInPathToNode][0]
|
||||
if (typeof sketchIndexInBody !== 'number')
|
||||
return new Error('expected sketchIndexInBody to be a number')
|
||||
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
|
||||
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']],
|
||||
pathToRevolveArg,
|
||||
}
|
||||
}
|
||||
|
||||
export function sketchOnExtrudedFace(
|
||||
node: Program,
|
||||
sketchPathToNode: PathToNode,
|
||||
|
@ -35,7 +35,7 @@ import { Selections, canFilletSelection } from 'lib/selections'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
import {
|
||||
ArtifactGraph,
|
||||
getExtrusionFromSuspectedPath,
|
||||
getSweepFromSuspectedPath,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
|
||||
|
||||
@ -152,7 +152,7 @@ export function getPathToExtrudeForSegmentSelection(
|
||||
)
|
||||
if (trap(sketchGroup)) return sketchGroup
|
||||
|
||||
const extrusion = getExtrusionFromSuspectedPath(sketchGroup.id, artifactGraph)
|
||||
const extrusion = getSweepFromSuspectedPath(sketchGroup.id, artifactGraph)
|
||||
if (err(extrusion)) return extrusion
|
||||
|
||||
const pathToExtrudeNode = getNodePathFromSourceRange(
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
hasExtrudeSketchGroup,
|
||||
findUsesOfTagInPipe,
|
||||
hasSketchPipeBeenExtruded,
|
||||
hasExtrudableGeometry,
|
||||
doesSceneHaveSweepableSketch,
|
||||
traverse,
|
||||
} from './queryAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
@ -488,7 +488,7 @@ const sketch002 = startSketchOn(extrude001, $seg01)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing hasExtrudableGeometry', () => {
|
||||
describe('Testing doesSceneHaveSweepableSketch', () => {
|
||||
it('finds sketch001 pipe to be extruded', async () => {
|
||||
const exampleCode = `const sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([3.29, 7.86], %)
|
||||
@ -506,7 +506,7 @@ const sketch002 = startSketchOn(extrude001, $seg01)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = hasExtrudableGeometry(ast)
|
||||
const extrudable = doesSceneHaveSweepableSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
it('find sketch002 NOT pipe to be extruded', async () => {
|
||||
@ -520,7 +520,7 @@ const extrude001 = extrude(10, sketch001)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = hasExtrudableGeometry(ast)
|
||||
const extrudable = doesSceneHaveSweepableSketch(ast)
|
||||
expect(extrudable).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
@ -880,7 +880,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||
if (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.type === 'Identifier' &&
|
||||
node.callee.name === 'extrude' &&
|
||||
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
|
||||
node.arguments?.[1]?.type === 'Identifier' &&
|
||||
node.arguments[1].name === varDec.id.name
|
||||
) {
|
||||
@ -892,7 +892,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||
}
|
||||
|
||||
/** File must contain at least one sketch that has not been extruded already */
|
||||
export function hasExtrudableGeometry(ast: Program) {
|
||||
export function doesSceneHaveSweepableSketch(ast: Program) {
|
||||
const theMap: any = {}
|
||||
traverse(ast as any, {
|
||||
enter(node) {
|
||||
@ -925,7 +925,7 @@ export function hasExtrudableGeometry(ast: Program) {
|
||||
}
|
||||
} else if (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.name === 'extrude' &&
|
||||
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
|
||||
node.arguments[1]?.type === 'Identifier' &&
|
||||
theMap?.[node?.arguments?.[1]?.name]
|
||||
) {
|
||||
|
@ -33,7 +33,6 @@ Map {
|
||||
70,
|
||||
],
|
||||
},
|
||||
"extrusionId": "UUID",
|
||||
"planeId": "UUID",
|
||||
"segIds": [
|
||||
"UUID",
|
||||
@ -43,6 +42,7 @@ Map {
|
||||
"UUID",
|
||||
],
|
||||
"solid2dId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "path",
|
||||
},
|
||||
"UUID-2" => {
|
||||
@ -175,6 +175,7 @@ Map {
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"subType": "extrusion",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
@ -183,99 +184,99 @@ Map {
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"type": "extrusion",
|
||||
"type": "sweep",
|
||||
},
|
||||
"UUID-9" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-10" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [
|
||||
"UUID",
|
||||
],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-11" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-12" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-13" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "start",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-14" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "end",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-15" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-16" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-17" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-18" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-19" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-20" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-21" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-22" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-23" => {
|
||||
"codeRef": {
|
||||
@ -308,7 +309,6 @@ Map {
|
||||
395,
|
||||
],
|
||||
},
|
||||
"extrusionId": "UUID",
|
||||
"planeId": "UUID",
|
||||
"segIds": [
|
||||
"UUID",
|
||||
@ -317,6 +317,7 @@ Map {
|
||||
"UUID",
|
||||
],
|
||||
"solid2dId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "path",
|
||||
},
|
||||
"UUID-25" => {
|
||||
@ -425,6 +426,7 @@ Map {
|
||||
"UUID",
|
||||
],
|
||||
"pathId": "UUID",
|
||||
"subType": "extrusion",
|
||||
"surfaceIds": [
|
||||
"UUID",
|
||||
"UUID",
|
||||
@ -432,78 +434,78 @@ Map {
|
||||
"UUID",
|
||||
"UUID",
|
||||
],
|
||||
"type": "extrusion",
|
||||
"type": "sweep",
|
||||
},
|
||||
"UUID-31" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-32" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-33" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"segId": "UUID",
|
||||
"sweepId": "UUID",
|
||||
"type": "wall",
|
||||
},
|
||||
"UUID-34" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "start",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-35" => {
|
||||
"edgeCutEdgeIds": [],
|
||||
"extrusionId": "UUID",
|
||||
"pathIds": [],
|
||||
"subType": "end",
|
||||
"sweepId": "UUID",
|
||||
"type": "cap",
|
||||
},
|
||||
"UUID-36" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-37" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-38" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-39" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-40" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "opposite",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
"UUID-41" => {
|
||||
"extrusionId": "UUID",
|
||||
"segId": "UUID",
|
||||
"subType": "adjacent",
|
||||
"type": "extrudeEdge",
|
||||
"sweepId": "UUID",
|
||||
"type": "sweepEdge",
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
filterArtifacts,
|
||||
expandPlane,
|
||||
expandPath,
|
||||
expandExtrusion,
|
||||
expandSweep,
|
||||
ArtifactGraph,
|
||||
expandSegment,
|
||||
getArtifactsToUpdate,
|
||||
@ -194,13 +194,13 @@ describe('testing createArtifactGraph', () => {
|
||||
})
|
||||
|
||||
it('there should be two extrusions, for the original and the sketchOnFace, the first extrusion should have 6 sides of the cube', () => {
|
||||
const extrusions = [
|
||||
...filterArtifacts({ types: ['extrusion'] }, theMap),
|
||||
].map((extrusion) => expandExtrusion(extrusion[1], theMap))
|
||||
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
|
||||
(extrusion) => expandSweep(extrusion[1], theMap)
|
||||
)
|
||||
expect(extrusions).toHaveLength(2)
|
||||
extrusions.forEach((extrusion, index) => {
|
||||
if (err(extrusion)) throw extrusion
|
||||
expect(extrusion.type).toBe('extrusion')
|
||||
expect(extrusion.type).toBe('sweep')
|
||||
const firstExtrusionIsACubeIE6Sides = 6
|
||||
const secondExtrusionIsATriangularPrismIE5Sides = 5
|
||||
expect(extrusion.surfaces.length).toBe(
|
||||
@ -535,7 +535,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'path',
|
||||
segIds: [],
|
||||
planeId: 'UUID-1',
|
||||
extrusionId: '',
|
||||
sweepId: '',
|
||||
codeRef: {
|
||||
pathToNode: [['body', '']],
|
||||
range: [43, 70],
|
||||
@ -544,7 +544,8 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
])
|
||||
expect(getUpdateObjects('extrude')).toEqual([
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
@ -557,7 +558,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'path',
|
||||
segIds: expect.any(Array),
|
||||
planeId: expect.any(String),
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
codeRef: {
|
||||
range: [43, 70],
|
||||
pathToNode: [['body', '']],
|
||||
@ -580,7 +581,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'path',
|
||||
segIds: expect.any(Array),
|
||||
planeId: expect.any(String),
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
codeRef: {
|
||||
range: [43, 70],
|
||||
pathToNode: [['body', '']],
|
||||
@ -617,7 +618,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'wall',
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
@ -631,7 +632,8 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
@ -644,7 +646,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'wall',
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
@ -658,7 +660,8 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
@ -671,7 +674,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'wall',
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
@ -686,7 +689,8 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
edgeCutId: expect.any(String),
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
@ -699,7 +703,7 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'wall',
|
||||
segId: expect.any(String),
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
@ -713,7 +717,8 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
@ -726,11 +731,12 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'cap',
|
||||
subType: 'start',
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
@ -743,11 +749,12 @@ describe('testing getArtifactsToUpdate', () => {
|
||||
type: 'cap',
|
||||
subType: 'end',
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: expect.any(String),
|
||||
sweepId: expect.any(String),
|
||||
pathIds: [],
|
||||
},
|
||||
{
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
pathId: expect.any(String),
|
||||
surfaceIds: expect.any(Array),
|
||||
edgeIds: expect.any(Array),
|
||||
|
@ -25,7 +25,7 @@ export interface PathArtifact {
|
||||
type: 'path'
|
||||
planeId: ArtifactId
|
||||
segIds: Array<ArtifactId>
|
||||
extrusionId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
solid2dId?: ArtifactId
|
||||
codeRef: CommonCommandProperties
|
||||
}
|
||||
@ -38,7 +38,7 @@ export interface PathArtifactRich {
|
||||
type: 'path'
|
||||
plane: PlaneArtifact | WallArtifact
|
||||
segments: Array<SegmentArtifact>
|
||||
extrusion: ExtrusionArtifact
|
||||
sweep: SweepArtifact
|
||||
codeRef: CommonCommandProperties
|
||||
}
|
||||
|
||||
@ -54,23 +54,26 @@ interface SegmentArtifactRich {
|
||||
type: 'segment'
|
||||
path: PathArtifact
|
||||
surf: WallArtifact
|
||||
edges: Array<ExtrudeEdge>
|
||||
edges: Array<SweepEdge>
|
||||
edgeCut?: EdgeCut
|
||||
codeRef: CommonCommandProperties
|
||||
}
|
||||
|
||||
interface ExtrusionArtifact {
|
||||
type: 'extrusion'
|
||||
pathId: ArtifactId
|
||||
surfaceIds: Array<ArtifactId>
|
||||
edgeIds: Array<ArtifactId>
|
||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||
interface SweepArtifact {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve'
|
||||
pathId: string
|
||||
surfaceIds: Array<string>
|
||||
edgeIds: Array<string>
|
||||
codeRef: CommonCommandProperties
|
||||
}
|
||||
interface ExtrusionArtifactRich {
|
||||
type: 'extrusion'
|
||||
interface SweepArtifactRich {
|
||||
type: 'sweep'
|
||||
subType: 'extrusion' | 'revolve'
|
||||
path: PathArtifact
|
||||
surfaces: Array<WallArtifact | CapArtifact>
|
||||
edges: Array<ExtrudeEdge>
|
||||
edges: Array<SweepEdge>
|
||||
codeRef: CommonCommandProperties
|
||||
}
|
||||
|
||||
@ -78,21 +81,21 @@ interface WallArtifact {
|
||||
type: 'wall'
|
||||
segId: ArtifactId
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
extrusionId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
interface CapArtifact {
|
||||
type: 'cap'
|
||||
subType: 'start' | 'end'
|
||||
edgeCutEdgeIds: Array<ArtifactId>
|
||||
extrusionId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
pathIds: Array<ArtifactId>
|
||||
}
|
||||
|
||||
interface ExtrudeEdge {
|
||||
type: 'extrudeEdge'
|
||||
interface SweepEdge {
|
||||
type: 'sweepEdge'
|
||||
segId: ArtifactId
|
||||
extrusionId: ArtifactId
|
||||
sweepId: ArtifactId
|
||||
subType: 'opposite' | 'adjacent'
|
||||
}
|
||||
|
||||
@ -116,10 +119,10 @@ export type Artifact =
|
||||
| PlaneArtifact
|
||||
| PathArtifact
|
||||
| SegmentArtifact
|
||||
| ExtrusionArtifact
|
||||
| SweepArtifact
|
||||
| WallArtifact
|
||||
| CapArtifact
|
||||
| ExtrudeEdge
|
||||
| SweepEdge
|
||||
| EdgeCut
|
||||
| EdgeCutEdge
|
||||
| solid2D
|
||||
@ -257,7 +260,7 @@ export function getArtifactsToUpdate({
|
||||
type: 'wall',
|
||||
segId: existingPlane.segId,
|
||||
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
|
||||
extrusionId: existingPlane.extrusionId,
|
||||
sweepId: existingPlane.sweepId,
|
||||
pathIds: existingPlane.pathIds,
|
||||
},
|
||||
},
|
||||
@ -274,7 +277,7 @@ export function getArtifactsToUpdate({
|
||||
type: 'path',
|
||||
segIds: [],
|
||||
planeId: currentPlaneId,
|
||||
extrusionId: '',
|
||||
sweepId: '',
|
||||
codeRef: { range, pathToNode },
|
||||
},
|
||||
})
|
||||
@ -294,7 +297,7 @@ export function getArtifactsToUpdate({
|
||||
type: 'wall',
|
||||
segId: plane.segId,
|
||||
edgeCutEdgeIds: plane.edgeCutEdgeIds,
|
||||
extrusionId: plane.extrusionId,
|
||||
sweepId: plane.sweepId,
|
||||
pathIds: [id],
|
||||
},
|
||||
})
|
||||
@ -337,11 +340,13 @@ export function getArtifactsToUpdate({
|
||||
})
|
||||
}
|
||||
return returnArr
|
||||
} else if (cmd.type === 'extrude') {
|
||||
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') {
|
||||
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
||||
returnArr.push({
|
||||
id,
|
||||
artifact: {
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: subType,
|
||||
pathId: cmd.target,
|
||||
surfaceIds: [],
|
||||
edgeIds: [],
|
||||
@ -352,7 +357,7 @@ export function getArtifactsToUpdate({
|
||||
if (path?.type === 'path')
|
||||
returnArr.push({
|
||||
id: cmd.target,
|
||||
artifact: { ...path, extrusionId: id },
|
||||
artifact: { ...path, sweepId: id },
|
||||
})
|
||||
return returnArr
|
||||
} else if (
|
||||
@ -375,7 +380,7 @@ export function getArtifactsToUpdate({
|
||||
type: 'wall',
|
||||
segId: curve_id,
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: path.extrusionId,
|
||||
sweepId: path.sweepId,
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
@ -383,12 +388,12 @@ export function getArtifactsToUpdate({
|
||||
id: curve_id,
|
||||
artifact: { ...seg, surfaceId: face_id },
|
||||
})
|
||||
const extrusion = getArtifact(path.extrusionId)
|
||||
if (extrusion?.type === 'extrusion') {
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type === 'sweep') {
|
||||
returnArr.push({
|
||||
id: path.extrusionId,
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...extrusion,
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
@ -407,16 +412,16 @@ export function getArtifactsToUpdate({
|
||||
type: 'cap',
|
||||
subType: cap === 'bottom' ? 'start' : 'end',
|
||||
edgeCutEdgeIds: [],
|
||||
extrusionId: path.extrusionId,
|
||||
sweepId: path.sweepId,
|
||||
pathIds: [],
|
||||
},
|
||||
})
|
||||
const extrusion = getArtifact(path.extrusionId)
|
||||
if (extrusion?.type !== 'extrusion') return
|
||||
const sweep = getArtifact(path.sweepId)
|
||||
if (sweep?.type !== 'sweep') return
|
||||
returnArr.push({
|
||||
id: path.extrusionId,
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...extrusion,
|
||||
...sweep,
|
||||
surfaceIds: [face_id],
|
||||
},
|
||||
})
|
||||
@ -439,9 +444,9 @@ export function getArtifactsToUpdate({
|
||||
) {
|
||||
const wall = getArtifact(cmd.face_id)
|
||||
if (wall?.type !== 'wall') return returnArr
|
||||
const extrusion = getArtifact(wall.extrusionId)
|
||||
if (extrusion?.type !== 'extrusion') return returnArr
|
||||
const path = getArtifact(extrusion.pathId)
|
||||
const sweep = getArtifact(wall.sweepId)
|
||||
if (sweep?.type !== 'sweep') return returnArr
|
||||
const path = getArtifact(sweep.pathId)
|
||||
if (path?.type !== 'path') return returnArr
|
||||
const segment = getArtifact(cmd.edge_id)
|
||||
if (segment?.type !== 'segment') return returnArr
|
||||
@ -450,13 +455,13 @@ export function getArtifactsToUpdate({
|
||||
{
|
||||
id: response.data.modeling_response.data.edge,
|
||||
artifact: {
|
||||
type: 'extrudeEdge',
|
||||
type: 'sweepEdge',
|
||||
subType:
|
||||
cmd.type === 'solid3d_get_prev_adjacent_edge'
|
||||
? 'adjacent'
|
||||
: 'opposite',
|
||||
segId: cmd.edge_id,
|
||||
extrusionId: path.extrusionId,
|
||||
sweepId: path.sweepId,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -467,9 +472,9 @@ export function getArtifactsToUpdate({
|
||||
},
|
||||
},
|
||||
{
|
||||
id: path.extrusionId,
|
||||
id: path.sweepId,
|
||||
artifact: {
|
||||
...extrusion,
|
||||
...sweep,
|
||||
edgeIds: [response.data.modeling_response.data.edge],
|
||||
},
|
||||
},
|
||||
@ -582,10 +587,10 @@ export function expandPath(
|
||||
{ keys: path.segIds, types: ['segment'] },
|
||||
artifactGraph
|
||||
)
|
||||
const extrusion = getArtifactOfTypes(
|
||||
const sweep = getArtifactOfTypes(
|
||||
{
|
||||
key: path.extrusionId,
|
||||
types: ['extrusion'],
|
||||
key: path.sweepId,
|
||||
types: ['sweep'],
|
||||
},
|
||||
artifactGraph
|
||||
)
|
||||
@ -593,40 +598,41 @@ export function expandPath(
|
||||
{ key: path.planeId, types: ['plane', 'wall'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(extrusion)) return extrusion
|
||||
if (err(sweep)) return sweep
|
||||
if (err(plane)) return plane
|
||||
return {
|
||||
type: 'path',
|
||||
segments: Array.from(segs.values()),
|
||||
extrusion,
|
||||
sweep,
|
||||
plane,
|
||||
codeRef: path.codeRef,
|
||||
}
|
||||
}
|
||||
|
||||
export function expandExtrusion(
|
||||
extrusion: ExtrusionArtifact,
|
||||
export function expandSweep(
|
||||
sweep: SweepArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): ExtrusionArtifactRich | Error {
|
||||
): SweepArtifactRich | Error {
|
||||
const surfs = getArtifactsOfTypes(
|
||||
{ keys: extrusion.surfaceIds, types: ['wall', 'cap'] },
|
||||
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
|
||||
artifactGraph
|
||||
)
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: extrusion.edgeIds, types: ['extrudeEdge'] },
|
||||
{ keys: sweep.edgeIds, types: ['sweepEdge'] },
|
||||
artifactGraph
|
||||
)
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: extrusion.pathId, types: ['path'] },
|
||||
{ key: sweep.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(path)) return path
|
||||
return {
|
||||
type: 'extrusion',
|
||||
type: 'sweep',
|
||||
subType: 'extrusion',
|
||||
surfaces: Array.from(surfs.values()),
|
||||
edges: Array.from(edges.values()),
|
||||
path,
|
||||
codeRef: extrusion.codeRef,
|
||||
codeRef: sweep.codeRef,
|
||||
}
|
||||
}
|
||||
|
||||
@ -643,7 +649,7 @@ export function expandSegment(
|
||||
artifactGraph
|
||||
)
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: segment.edgeIds, types: ['extrudeEdge'] },
|
||||
{ keys: segment.edgeIds, types: ['sweepEdge'] },
|
||||
artifactGraph
|
||||
)
|
||||
const edgeCut = segment.edgeCutId
|
||||
@ -670,13 +676,13 @@ export function getCapCodeRef(
|
||||
cap: CapArtifact,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
const extrusion = getArtifactOfTypes(
|
||||
{ key: cap.extrusionId, types: ['extrusion'] },
|
||||
const sweep = getArtifactOfTypes(
|
||||
{ key: cap.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(extrusion)) return extrusion
|
||||
if (err(sweep)) return sweep
|
||||
const path = getArtifactOfTypes(
|
||||
{ key: extrusion.pathId, types: ['path'] },
|
||||
{ key: sweep.pathId, types: ['path'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(path)) return path
|
||||
@ -707,8 +713,8 @@ export function getWallCodeRef(
|
||||
return seg.codeRef
|
||||
}
|
||||
|
||||
export function getExtrudeEdgeCodeRef(
|
||||
edge: ExtrudeEdge,
|
||||
export function getSweepEdgeCodeRef(
|
||||
edge: SweepEdge,
|
||||
artifactGraph: ArtifactGraph
|
||||
): CommonCommandProperties | Error {
|
||||
const seg = getArtifactOfTypes(
|
||||
@ -719,29 +725,29 @@ export function getExtrudeEdgeCodeRef(
|
||||
return seg.codeRef
|
||||
}
|
||||
|
||||
export function getExtrusionFromSuspectedExtrudeSurface(
|
||||
export function getSweepFromSuspectedSweepSurface(
|
||||
id: ArtifactId,
|
||||
artifactGraph: ArtifactGraph
|
||||
): ExtrusionArtifact | Error {
|
||||
): SweepArtifact | Error {
|
||||
const artifact = getArtifactOfTypes(
|
||||
{ key: id, types: ['wall', 'cap'] },
|
||||
artifactGraph
|
||||
)
|
||||
if (err(artifact)) return artifact
|
||||
return getArtifactOfTypes(
|
||||
{ key: artifact.extrusionId, types: ['extrusion'] },
|
||||
{ key: artifact.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
)
|
||||
}
|
||||
|
||||
export function getExtrusionFromSuspectedPath(
|
||||
export function getSweepFromSuspectedPath(
|
||||
id: ArtifactId,
|
||||
artifactGraph: ArtifactGraph
|
||||
): ExtrusionArtifact | Error {
|
||||
): SweepArtifact | Error {
|
||||
const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph)
|
||||
if (err(path)) return path
|
||||
return getArtifactOfTypes(
|
||||
{ key: path.extrusionId, types: ['extrusion'] },
|
||||
{ key: path.sweepId, types: ['sweep'] },
|
||||
artifactGraph
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 380 KiB After Width: | Height: | Size: 378 KiB |
Before Width: | Height: | Size: 617 KiB After Width: | Height: | Size: 613 KiB |
@ -16,7 +16,11 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { exportMake } from 'lib/exportMake'
|
||||
import toast from 'react-hot-toast'
|
||||
import { SettingsViaQueryString } from 'lib/settings/settingsTypes'
|
||||
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
import {
|
||||
EXECUTE_AST_INTERRUPT_ERROR_MESSAGE,
|
||||
EXPORT_TOAST_MESSAGES,
|
||||
MAKE_TOAST_MESSAGES,
|
||||
} from 'lib/constants'
|
||||
import { KclManager } from 'lang/KclSingleton'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
||||
@ -959,7 +963,9 @@ class EngineConnection extends EventTarget {
|
||||
) {
|
||||
// Reject the promise with the error.
|
||||
this.engineCommandManager.pendingExport.reject(errorsString)
|
||||
toast.error(errorsString)
|
||||
toast.error(errorsString, {
|
||||
id: this.engineCommandManager.pendingExport.toastId,
|
||||
})
|
||||
this.engineCommandManager.pendingExport = undefined
|
||||
}
|
||||
} else {
|
||||
@ -1327,8 +1333,13 @@ export class EngineCommandManager extends EventTarget {
|
||||
defaultPlanes: DefaultPlanes | null = null
|
||||
commandLogs: CommandLog[] = []
|
||||
pendingExport?: {
|
||||
/** The id of the shared loading/success/error toast for export */
|
||||
toastId: string
|
||||
/** An on-success callback */
|
||||
resolve: (a: null) => void
|
||||
/** An on-error callback */
|
||||
reject: (reason: string) => void
|
||||
/** The engine command uuid */
|
||||
commandId: string
|
||||
}
|
||||
settings: SettingsViaQueryString
|
||||
@ -1590,7 +1601,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
// because in all other cases we send JSON strings. But in the case of
|
||||
// export we send a binary blob.
|
||||
// Pass this to our export function.
|
||||
if (this.exportIntent === null) {
|
||||
if (this.exportIntent === null || this.pendingExport === undefined) {
|
||||
toast.error(
|
||||
'Export intent was not set, but export data was received'
|
||||
)
|
||||
@ -1602,19 +1613,22 @@ export class EngineCommandManager extends EventTarget {
|
||||
|
||||
switch (this.exportIntent) {
|
||||
case ExportIntent.Save: {
|
||||
exportSave(event.data).then(() => {
|
||||
exportSave(event.data, this.pendingExport.toastId).then(() => {
|
||||
this.pendingExport?.resolve(null)
|
||||
}, this.pendingExport?.reject)
|
||||
break
|
||||
}
|
||||
case ExportIntent.Make: {
|
||||
exportMake(event.data).then((result) => {
|
||||
if (result) {
|
||||
this.pendingExport?.resolve(null)
|
||||
} else {
|
||||
this.pendingExport?.reject('Failed to make export')
|
||||
}
|
||||
}, this.pendingExport?.reject)
|
||||
exportMake(event.data, this.pendingExport.toastId).then(
|
||||
(result) => {
|
||||
if (result) {
|
||||
this.pendingExport?.resolve(null)
|
||||
} else {
|
||||
this.pendingExport?.reject('Failed to make export')
|
||||
}
|
||||
},
|
||||
this.pendingExport?.reject
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -1929,7 +1943,20 @@ export class EngineCommandManager extends EventTarget {
|
||||
return Promise.resolve(null)
|
||||
} else if (cmd.type === 'export') {
|
||||
const promise = new Promise<null>((resolve, reject) => {
|
||||
if (this.exportIntent === null) {
|
||||
if (this.exportIntent === null) {
|
||||
toast.error('Export intent was not set, but export is being sent')
|
||||
console.error('Export intent was not set, but export is being sent')
|
||||
return
|
||||
}
|
||||
}
|
||||
const toastId = toast.loading(
|
||||
this.exportIntent === ExportIntent.Save
|
||||
? EXPORT_TOAST_MESSAGES.START
|
||||
: MAKE_TOAST_MESSAGES.START
|
||||
)
|
||||
this.pendingExport = {
|
||||
toastId,
|
||||
resolve: (passThrough) => {
|
||||
this.addCommandLog({
|
||||
type: 'export-done',
|
||||
|
@ -1,8 +1,16 @@
|
||||
/// The method below uses the File System Access API when it's supported and
|
||||
// else falls back to the classic approach. In both cases the function saves
|
||||
// the file, but in case of where the File System Access API is supported, the
|
||||
|
||||
import toast from 'react-hot-toast'
|
||||
import { EXPORT_TOAST_MESSAGES } from './constants'
|
||||
|
||||
// user will get a file save dialog where they can choose where the file should be saved.
|
||||
export const browserSaveFile = async (blob: Blob, suggestedName: string) => {
|
||||
export const browserSaveFile = async (
|
||||
blob: Blob,
|
||||
suggestedName: string,
|
||||
toastId: string
|
||||
) => {
|
||||
// Feature detection. The API needs to be supported
|
||||
// and the app not run in an iframe.
|
||||
const supportsFileSystemAccess =
|
||||
@ -29,11 +37,15 @@ export const browserSaveFile = async (blob: Blob, suggestedName: string) => {
|
||||
const writable = await handle.createWritable()
|
||||
await writable.write(blob)
|
||||
await writable.close()
|
||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||
return
|
||||
} catch (err: any) {
|
||||
// Fail silently if the user has simply canceled the dialog.
|
||||
if (err.name !== 'AbortError') {
|
||||
if (err.name === 'AbortError') {
|
||||
toast.dismiss(toastId)
|
||||
} else {
|
||||
console.error(err.name, err.message)
|
||||
toast.error(EXPORT_TOAST_MESSAGES.FAILED, { id: toastId })
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -54,4 +66,5 @@ export const browserSaveFile = async (blob: Blob, suggestedName: string) => {
|
||||
URL.revokeObjectURL(blobURL)
|
||||
a.remove()
|
||||
}, 1000)
|
||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ const META =
|
||||
PLATFORM === 'macos' ? 'Cmd' : PLATFORM === 'windows' ? 'Win' : 'Super'
|
||||
const ALT = PLATFORM === 'macos' ? 'Option' : 'Alt'
|
||||
|
||||
const noModifiersPressed = (e: React.MouseEvent) =>
|
||||
const noModifiersPressed = (e: MouseEvent) =>
|
||||
!e.ctrlKey && !e.shiftKey && !e.altKey && !e.metaKey
|
||||
|
||||
export type CameraSystem =
|
||||
@ -53,14 +53,14 @@ export function mouseControlsToCameraSystem(
|
||||
|
||||
interface MouseGuardHandler {
|
||||
description: string
|
||||
callback: (e: React.MouseEvent) => boolean
|
||||
callback: (e: MouseEvent) => boolean
|
||||
lenientDragStartButton?: number
|
||||
}
|
||||
|
||||
interface MouseGuardZoomHandler {
|
||||
description: string
|
||||
dragCallback: (e: React.MouseEvent) => boolean
|
||||
scrollCallback: (e: React.MouseEvent) => boolean
|
||||
dragCallback: (e: MouseEvent) => boolean
|
||||
scrollCallback: (e: WheelEvent) => boolean
|
||||
lenientDragStartButton?: number
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ export interface MouseGuard {
|
||||
rotate: MouseGuardHandler
|
||||
}
|
||||
|
||||
export const btnName = (e: React.MouseEvent) => ({
|
||||
export const btnName = (e: MouseEvent) => ({
|
||||
middle: !!(e.buttons & 4) || e.button === 1,
|
||||
right: !!(e.buttons & 2) || e.button === 2,
|
||||
left: !!(e.buttons & 1) || e.button === 0,
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
|
||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { machineManager } from 'lib/machineManager'
|
||||
@ -32,6 +32,10 @@ export type ModelingCommandSchema = {
|
||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Revolve: {
|
||||
selection: Selections
|
||||
angle: KclCommandValue
|
||||
}
|
||||
Fillet: {
|
||||
// todo
|
||||
selection: Selections
|
||||
@ -209,6 +213,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
// TODO: These are products of an extrude
|
||||
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
multiple: false, // TODO: multiple selection
|
||||
required: true,
|
||||
@ -232,6 +237,26 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
||||
Revolve: {
|
||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||
icon: 'revolve',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['extrude-wall', 'start-cap', 'end-cap'],
|
||||
multiple: false, // TODO: multiple selection
|
||||
required: true,
|
||||
skip: true,
|
||||
},
|
||||
angle: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_DEGREE,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Fillet: {
|
||||
// todo
|
||||
description: 'Fillet edge',
|
||||
|
@ -53,9 +53,14 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
SKETCH: 'sketch',
|
||||
EXTRUDE: 'extrude',
|
||||
SEGMENT: 'seg',
|
||||
REVOLVE: 'revolve',
|
||||
} as const
|
||||
/** The default KCL length expression */
|
||||
export const KCL_DEFAULT_LENGTH = `5`
|
||||
|
||||
/** The default KCL degree expression */
|
||||
export const KCL_DEFAULT_DEGREE = `360`
|
||||
|
||||
/** localStorage key for the playwright test-specific app settings file */
|
||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
||||
|
||||
@ -72,3 +77,21 @@ export const PLAYWRIGHT_KEY = 'playwright'
|
||||
* allows us to match if the execution of executeAst was interrupted */
|
||||
export const EXECUTE_AST_INTERRUPT_ERROR_MESSAGE =
|
||||
'Force interrupt, executionIsStale, new AST requested'
|
||||
|
||||
/** The messages that appear for exporting toasts */
|
||||
export const EXPORT_TOAST_MESSAGES = {
|
||||
START: 'Exporting...',
|
||||
SUCCESS: 'Exported successfully',
|
||||
FAILED: 'Export failed',
|
||||
}
|
||||
|
||||
/** The messages that appear for "make" command toasts */
|
||||
export const MAKE_TOAST_MESSAGES = {
|
||||
START: 'Starting print...',
|
||||
NO_MACHINES: 'No machines available',
|
||||
NO_MACHINE_API_IP: 'No machine api ip available',
|
||||
NO_CURRENT_MACHINE: 'No current machine available',
|
||||
NO_MACHINE_ID: 'No machine id available',
|
||||
ERROR_STARTING_PRINT: 'Error while starting print',
|
||||
SUCCESS: 'Started print successfully',
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ export function createMachineCommand<
|
||||
| Command<T, typeof type, S[typeof type]>[]
|
||||
| null {
|
||||
const commandConfig = commandBarConfig && commandBarConfig[type]
|
||||
|
||||
// There may be no command config for this event type,
|
||||
// or there may be multiple commands to create.
|
||||
if (!commandConfig) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { type Models } from '@kittycad/lib'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||
|
||||
// Isolating a function to call the engine to export the current scene.
|
||||
// Because it has given us trouble in automated testing environments.
|
||||
@ -23,11 +22,5 @@ export async function exportFromEngine({
|
||||
cmd_id: uuidv4(),
|
||||
})
|
||||
|
||||
// If we are in playwright slow down the export.
|
||||
const inPlaywright = window.localStorage.getItem(IS_PLAYWRIGHT_KEY)
|
||||
if (inPlaywright === 'true') {
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000))
|
||||
}
|
||||
|
||||
return exportPromise
|
||||
}
|
||||
|
@ -3,33 +3,37 @@ import { machineManager } from './machineManager'
|
||||
import toast from 'react-hot-toast'
|
||||
import { components } from './machine-api'
|
||||
import ModelingAppFile from './modelingAppFile'
|
||||
import { MAKE_TOAST_MESSAGES } from './constants'
|
||||
|
||||
// Make files locally from an export call.
|
||||
export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
|
||||
export async function exportMake(
|
||||
data: ArrayBuffer,
|
||||
toastId: string
|
||||
): Promise<Response | null> {
|
||||
if (machineManager.machineCount() === 0) {
|
||||
console.error('No machines available')
|
||||
toast.error('No machines available')
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_MACHINES)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINES, { id: toastId })
|
||||
return null
|
||||
}
|
||||
|
||||
const machineApiIp = machineManager.machineApiIp
|
||||
if (!machineApiIp) {
|
||||
console.error('No machine api ip available')
|
||||
toast.error('No machine api ip available')
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_MACHINE_API_IP)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINE_API_IP, { id: toastId })
|
||||
return null
|
||||
}
|
||||
|
||||
const currentMachine = machineManager.currentMachine
|
||||
if (!currentMachine) {
|
||||
console.error('No current machine available')
|
||||
toast.error('No current machine available')
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_CURRENT_MACHINE)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_CURRENT_MACHINE, { id: toastId })
|
||||
return null
|
||||
}
|
||||
|
||||
let machineId = currentMachine?.id
|
||||
if (!machineId) {
|
||||
console.error('No machine id available', currentMachine)
|
||||
toast.error('No machine id available')
|
||||
console.error(MAKE_TOAST_MESSAGES.NO_MACHINE_ID, currentMachine)
|
||||
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINE_ID, { id: toastId })
|
||||
return null
|
||||
}
|
||||
|
||||
@ -58,16 +62,22 @@ export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
|
||||
console.log('response', response)
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('Error exporting', response)
|
||||
console.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, response)
|
||||
const text = await response.text()
|
||||
toast.error('Error exporting: ' + response.statusText + ' ' + text)
|
||||
toast.error(
|
||||
'Error while starting print: ' + response.statusText + ' ' + text,
|
||||
{
|
||||
id: toastId,
|
||||
}
|
||||
)
|
||||
return null
|
||||
}
|
||||
|
||||
toast.success(MAKE_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||
return response
|
||||
} catch (error) {
|
||||
console.error('Error exporting', error)
|
||||
toast.error('Error exporting')
|
||||
console.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, error)
|
||||
toast.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, { id: toastId })
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,10 @@ import { browserSaveFile } from './browserSaveFile'
|
||||
|
||||
import JSZip from 'jszip'
|
||||
import ModelingAppFile from './modelingAppFile'
|
||||
import toast from 'react-hot-toast'
|
||||
import { EXPORT_TOAST_MESSAGES } from './constants'
|
||||
|
||||
const save_ = async (file: ModelingAppFile) => {
|
||||
const save_ = async (file: ModelingAppFile, toastId: string) => {
|
||||
try {
|
||||
if (isDesktop()) {
|
||||
const extension = file.name.split('.').pop() || null
|
||||
@ -20,6 +22,7 @@ const save_ = async (file: ModelingAppFile) => {
|
||||
file.name,
|
||||
new Uint8Array(file.contents)
|
||||
)
|
||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||
return
|
||||
}
|
||||
|
||||
@ -36,13 +39,17 @@ const save_ = async (file: ModelingAppFile) => {
|
||||
|
||||
// The user canceled the save.
|
||||
// Return early.
|
||||
if (filePathMeta.canceled) return
|
||||
if (filePathMeta.canceled) {
|
||||
toast.dismiss(toastId)
|
||||
return
|
||||
}
|
||||
|
||||
// Write the file.
|
||||
await window.electron.writeFile(
|
||||
filePathMeta.filePath,
|
||||
new Uint8Array(file.contents)
|
||||
)
|
||||
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
|
||||
} else {
|
||||
// Download the file to the user's computer.
|
||||
// Now we need to download the files to the user's downloads folder.
|
||||
@ -51,16 +58,17 @@ const save_ = async (file: ModelingAppFile) => {
|
||||
// Create a new blob.
|
||||
const blob = new Blob([new Uint8Array(file.contents)])
|
||||
// Save the file.
|
||||
await browserSaveFile(blob, file.name)
|
||||
await browserSaveFile(blob, file.name, toastId)
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
console.error('export error', e)
|
||||
toast.error(EXPORT_TOAST_MESSAGES.FAILED, { id: toastId })
|
||||
}
|
||||
}
|
||||
|
||||
// Saves files locally from an export call.
|
||||
export async function exportSave(data: ArrayBuffer) {
|
||||
export async function exportSave(data: ArrayBuffer, toastId: string) {
|
||||
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
|
||||
let uintArray = new Uint8Array(data)
|
||||
|
||||
@ -72,9 +80,9 @@ export async function exportSave(data: ArrayBuffer) {
|
||||
zip.file(file.name, new Uint8Array(file.contents), { binary: true })
|
||||
}
|
||||
return zip.generateAsync({ type: 'array' }).then((contents) => {
|
||||
return save_({ name: 'output.zip', contents })
|
||||
return save_({ name: 'output.zip', contents }, toastId)
|
||||
})
|
||||
} else {
|
||||
return save_(files[0])
|
||||
return save_(files[0], toastId)
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ import {
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getCapCodeRef,
|
||||
getExtrudeEdgeCodeRef,
|
||||
getSweepEdgeCodeRef,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
} from 'lang/std/artifactGraph'
|
||||
@ -52,6 +52,7 @@ export type Selection = {
|
||||
| 'end-cap'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'adjacent-edge'
|
||||
| 'line'
|
||||
| 'arc'
|
||||
| 'all'
|
||||
@ -140,12 +141,21 @@ export async function getEventForSelectWithPoint({
|
||||
},
|
||||
}
|
||||
}
|
||||
if (_artifact.type === 'extrudeEdge') {
|
||||
const codeRef = getExtrudeEdgeCodeRef(
|
||||
if (_artifact.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
selectionType: 'singleCodeCursor',
|
||||
selection: { range: codeRef.range, type: 'adjacent-edge' },
|
||||
},
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'Set selection',
|
||||
data: {
|
||||
@ -385,10 +395,16 @@ function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
|
||||
}
|
||||
|
||||
function nodeHasExtrude(node: CommonASTNode) {
|
||||
return doesPipeHaveCallExp({
|
||||
calleeName: 'extrude',
|
||||
...node,
|
||||
})
|
||||
return (
|
||||
doesPipeHaveCallExp({
|
||||
calleeName: 'extrude',
|
||||
...node,
|
||||
}) ||
|
||||
doesPipeHaveCallExp({
|
||||
calleeName: 'revolve',
|
||||
...node,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
function nodeHasClose(node: CommonASTNode) {
|
||||
@ -398,7 +414,7 @@ function nodeHasClose(node: CommonASTNode) {
|
||||
})
|
||||
}
|
||||
|
||||
export function canExtrudeSelection(selection: Selections) {
|
||||
export function canSweepSelection(selection: Selections) {
|
||||
const commonNodes = selection.codeBasedSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
)
|
||||
@ -422,10 +438,14 @@ export function canFilletSelection(selection: Selections) {
|
||||
}
|
||||
|
||||
function canExtrudeSelectionItem(selection: Selections, i: number) {
|
||||
const isolatedSelection = {
|
||||
...selection,
|
||||
codeBasedSelections: [selection.codeBasedSelections[i]],
|
||||
}
|
||||
const commonNode = buildCommonNodeFromSelection(selection, i)
|
||||
|
||||
return (
|
||||
!!isSketchPipe(selection) &&
|
||||
!!isSketchPipe(isolatedSelection) &&
|
||||
nodeHasClose(commonNode) &&
|
||||
!nodeHasExtrude(commonNode)
|
||||
)
|
||||
@ -444,25 +464,17 @@ export type ResolvedSelectionType = [Selection['type'] | 'other', number]
|
||||
export function getSelectionType(
|
||||
selection: Selections
|
||||
): ResolvedSelectionType[] {
|
||||
return selection.codeBasedSelections
|
||||
.map((s, i) => {
|
||||
if (canExtrudeSelectionItem(selection, i)) {
|
||||
return ['extrude-wall', 1] as ResolvedSelectionType // This is implicitly determining what a face is, which is bad
|
||||
} else {
|
||||
return ['other', 1] as ResolvedSelectionType
|
||||
}
|
||||
})
|
||||
.reduce((acc, [type, count]) => {
|
||||
const foundIndex = acc.findIndex((item) => item && item[0] === type)
|
||||
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
|
||||
const singleSelection = {
|
||||
...selection,
|
||||
codeBasedSelections: [selection.codeBasedSelections[i]],
|
||||
}
|
||||
return canExtrudeSelectionItem(singleSelection, 0)
|
||||
}).length
|
||||
|
||||
if (foundIndex === -1) {
|
||||
return [...acc, [type, count]]
|
||||
} else {
|
||||
const temp = [...acc]
|
||||
temp[foundIndex][1] += count
|
||||
return temp
|
||||
}
|
||||
}, [] as ResolvedSelectionType[])
|
||||
return extrudableCount === selection.codeBasedSelections.length
|
||||
? [['extrude-wall', extrudableCount]]
|
||||
: [['other', selection.codeBasedSelections.length]]
|
||||
}
|
||||
|
||||
export function getSelectionTypeDisplayText(
|
||||
@ -557,14 +569,43 @@ function codeToIdSelections(
|
||||
}
|
||||
return
|
||||
}
|
||||
if (type === 'edge' && entry.artifact.type === 'segment') {
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const edge = [...edges].find(([_, edge]) => edge.type === 'sweepEdge')
|
||||
if (!edge) return
|
||||
bestCandidate = {
|
||||
artifact: edge[1],
|
||||
selection: { type, range, ...rest },
|
||||
id: edge[0],
|
||||
}
|
||||
}
|
||||
if (type === 'adjacent-edge' && entry.artifact.type === 'segment') {
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const edge = [...edges].find(
|
||||
([_, edge]) =>
|
||||
edge.type === 'sweepEdge' && edge.subType === 'adjacent'
|
||||
)
|
||||
if (!edge) return
|
||||
bestCandidate = {
|
||||
artifact: edge[1],
|
||||
selection: { type, range, ...rest },
|
||||
id: edge[0],
|
||||
}
|
||||
}
|
||||
if (
|
||||
(type === 'end-cap' || type === 'start-cap') &&
|
||||
entry.artifact.type === 'path'
|
||||
) {
|
||||
const extrusion = getArtifactOfTypes(
|
||||
{
|
||||
key: entry.artifact.extrusionId,
|
||||
types: ['extrusion'],
|
||||
key: entry.artifact.sweepId,
|
||||
types: ['sweep'],
|
||||
},
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
|
@ -94,9 +94,16 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
},
|
||||
{
|
||||
id: 'revolve',
|
||||
onClick: () => console.error('Revolve not yet implemented'),
|
||||
onClick: ({ commandBarSend }) =>
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Revolve', groupId: 'modeling' },
|
||||
}),
|
||||
// TODO: disabled
|
||||
// Who's state is this?
|
||||
disabled: (state) => !state.can({ type: 'Revolve' }),
|
||||
icon: 'revolve',
|
||||
status: 'kcl-only',
|
||||
status: DEV ? 'available' : 'kcl-only',
|
||||
title: 'Revolve',
|
||||
hotkey: 'R',
|
||||
description:
|
||||
|
@ -33,7 +33,11 @@ import {
|
||||
applyConstraintEqualLength,
|
||||
setEqualLengthInfo,
|
||||
} from 'components/Toolbar/EqualLength'
|
||||
import { deleteFromSelection, extrudeSketch } from 'lang/modifyAst'
|
||||
import {
|
||||
deleteFromSelection,
|
||||
extrudeSketch,
|
||||
revolveSketch,
|
||||
} from 'lang/modifyAst'
|
||||
import { applyFilletToSelection } from 'lang/modifyAst/addFillet'
|
||||
import { getNodeFromPath } from '../lang/queryAst'
|
||||
import {
|
||||
@ -202,6 +206,7 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Export'; data: ModelingCommandSchema['Export'] }
|
||||
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
|
||||
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
||||
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
|
||||
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
||||
| { type: 'Text-to-CAD'; data: ModelingCommandSchema['Text-to-CAD'] }
|
||||
| {
|
||||
@ -310,6 +315,7 @@ export const modelingMachine = setup({
|
||||
guards: {
|
||||
'Selection is on face': () => false,
|
||||
'has valid extrude selection': () => false,
|
||||
'has valid revolve selection': () => false,
|
||||
'has valid fillet selection': () => false,
|
||||
'Has exportable geometry': () => false,
|
||||
'has valid selection for deletion': () => false,
|
||||
@ -566,6 +572,53 @@ export const modelingMachine = setup({
|
||||
}
|
||||
})().catch(reportRejection)
|
||||
},
|
||||
'AST revolve': ({ context: { store }, event }) => {
|
||||
if (event.type !== 'Revolve') return
|
||||
;(async () => {
|
||||
if (!event.data) return
|
||||
const { selection, angle } = event.data
|
||||
let ast = kclManager.ast
|
||||
if (
|
||||
'variableName' in angle &&
|
||||
angle.variableName &&
|
||||
angle.insertIndex !== undefined
|
||||
) {
|
||||
const newBody = [...ast.body]
|
||||
newBody.splice(angle.insertIndex, 0, angle.variableDeclarationAst)
|
||||
ast.body = newBody
|
||||
}
|
||||
const pathToNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selection.codeBasedSelections[0].range
|
||||
)
|
||||
const revolveSketchRes = revolveSketch(
|
||||
ast,
|
||||
pathToNode,
|
||||
false,
|
||||
'variableName' in angle ? angle.variableIdentifierAst : angle.valueAst
|
||||
)
|
||||
if (trap(revolveSketchRes)) return
|
||||
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
|
||||
|
||||
store.videoElement?.pause()
|
||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||
focusPath: pathToRevolveArg,
|
||||
zoomToFit: true,
|
||||
zoomOnRangeAndType: {
|
||||
range: selection.codeBasedSelections[0].range,
|
||||
type: 'path',
|
||||
},
|
||||
})
|
||||
if (!engineCommandManager.engineConnection?.idleMode) {
|
||||
store.videoElement?.play().catch((e) => {
|
||||
console.warn('Video playing was prevented', e)
|
||||
})
|
||||
}
|
||||
if (updatedAst?.selections) {
|
||||
editorManager.selectRange(updatedAst?.selections)
|
||||
}
|
||||
})().catch(reportRejection)
|
||||
},
|
||||
'AST delete selection': ({ context: { selectionRanges } }) => {
|
||||
;(async () => {
|
||||
let ast = kclManager.ast
|
||||
@ -1238,6 +1291,13 @@ export const modelingMachine = setup({
|
||||
reenter: false,
|
||||
},
|
||||
|
||||
Revolve: {
|
||||
target: 'idle',
|
||||
guard: 'has valid revolve selection',
|
||||
actions: ['AST revolve'],
|
||||
reenter: false,
|
||||
},
|
||||
|
||||
Fillet: {
|
||||
target: 'idle',
|
||||
guard: 'has valid fillet selection', // TODO: fix selections
|
||||
|
279
src/wasm-lib/Cargo.lock
generated
@ -78,20 +78,26 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.88"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
|
||||
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94"
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -201,7 +207,7 @@ dependencies = [
|
||||
"libm",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
"serde",
|
||||
]
|
||||
|
||||
@ -249,19 +255,19 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bson"
|
||||
version = "2.12.0"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80cf6f7806607bd58ad490bab34bf60e25455ea4aaf995f897a13324d41ea580"
|
||||
checksum = "068208f2b6fcfa27a7f1ee37488d2bb8ba2640f68f5475d08e1d9130696aba59"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"base64 0.13.1",
|
||||
"bitvec",
|
||||
"chrono",
|
||||
"hex",
|
||||
"indexmap 2.2.5",
|
||||
"indexmap 2.5.0",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
@ -326,6 +332,18 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.16.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c"
|
||||
dependencies = [
|
||||
"approx 0.1.1",
|
||||
"mint",
|
||||
"num-traits 0.1.43",
|
||||
"rand 0.4.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.38"
|
||||
@ -335,7 +353,7 @@ dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-targets 0.52.4",
|
||||
@ -496,7 +514,7 @@ dependencies = [
|
||||
"futures",
|
||||
"is-terminal",
|
||||
"itertools 0.10.5",
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
"once_cell",
|
||||
"oorandom",
|
||||
"plotters",
|
||||
@ -672,7 +690,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "derive-docs"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -754,6 +772,26 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c280b9e6b3ae19e152d8e31cf47f18389781e119d4013a2a2bb0180e5facc635"
|
||||
dependencies = [
|
||||
"enum-iterator-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "enum-iterator-derive"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1ab991c1362ac86c61ab6f556cff143daa22e5a15e4e189df818b2fd19fe65b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@ -770,6 +808,16 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "euler"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f19d11568a4a46aee488bdab3a2963e5e2c3cfd6091aa0abceaddcea82c0bc1"
|
||||
dependencies = [
|
||||
"approx 0.1.1",
|
||||
"cgmath",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "expectorate"
|
||||
version = "1.1.0"
|
||||
@ -834,6 +882,12 @@ dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuchsia-cprng"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
@ -1013,7 +1067,7 @@ dependencies = [
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"indexmap 2.2.5",
|
||||
"indexmap 2.5.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@ -1211,7 +1265,7 @@ checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder-lite",
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
"png",
|
||||
]
|
||||
|
||||
@ -1239,12 +1293,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.5"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1345,10 +1400,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.14"
|
||||
version = "0.2.16"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx",
|
||||
"approx 0.5.1",
|
||||
"async-recursion",
|
||||
"async-trait",
|
||||
"base64 0.22.1",
|
||||
@ -1368,10 +1423,12 @@ dependencies = [
|
||||
"http 0.2.12",
|
||||
"iai",
|
||||
"image",
|
||||
"indexmap 2.5.0",
|
||||
"insta",
|
||||
"itertools 0.13.0",
|
||||
"js-sys",
|
||||
"kittycad",
|
||||
"kittycad-modeling-cmds",
|
||||
"lazy_static",
|
||||
"measurements",
|
||||
"mime_guess",
|
||||
@ -1417,7 +1474,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.10"
|
||||
version = "0.1.11"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper",
|
||||
@ -1449,7 +1506,7 @@ dependencies = [
|
||||
"mime_guess",
|
||||
"parse-display",
|
||||
"phonenumber",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"reqwest-conditional-middleware",
|
||||
"reqwest-middleware",
|
||||
@ -1467,6 +1524,54 @@ dependencies = [
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.59"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee900033a5804ca2354f0760478e851a0ab04d32b38a9117d0bd4f87a8867110"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"data-encoding",
|
||||
"enum-iterator",
|
||||
"enum-iterator-derive",
|
||||
"euler",
|
||||
"http 0.2.12",
|
||||
"kittycad-modeling-cmds-macros",
|
||||
"kittycad-unit-conversion-derive",
|
||||
"measurements",
|
||||
"parse-display",
|
||||
"parse-display-derive",
|
||||
"schemars",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds-macros"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0cdc505a33bfffb87c317435ec41ced8f73474217cf30db685e479bf289757e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-unit-conversion-derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7001c46a92c1edce6722a3900539b198230980799035f02d92b4e7df3fc08738"
|
||||
dependencies = [
|
||||
"inflections",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
@ -1600,6 +1705,12 @@ dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mint"
|
||||
version = "0.5.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e53debba6bda7a793e5f99b8dacf19e626084f525f7829104ba9898f367d85ff"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.1"
|
||||
@ -1639,7 +1750,7 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1654,7 +1765,16 @@ version = "0.1.46"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||
dependencies = [
|
||||
"num-traits 0.2.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1677,9 +1797,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
version = "1.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe"
|
||||
|
||||
[[package]]
|
||||
name = "oncemutex"
|
||||
@ -1714,7 +1834,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"percent-encoding",
|
||||
"pin-project",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@ -1873,7 +1993,7 @@ version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"num-traits 0.2.18",
|
||||
"plotters-backend",
|
||||
"plotters-svg",
|
||||
"wasm-bindgen",
|
||||
@ -1928,9 +2048,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.4.0"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
|
||||
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
|
||||
dependencies = [
|
||||
"diff",
|
||||
"yansi",
|
||||
@ -1971,9 +2091,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3"
|
||||
version = "0.22.2"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433"
|
||||
checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"indoc",
|
||||
@ -1989,9 +2109,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-build-config"
|
||||
version = "0.22.2"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8"
|
||||
checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"target-lexicon",
|
||||
@ -1999,9 +2119,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-ffi"
|
||||
version = "0.22.2"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6"
|
||||
checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pyo3-build-config",
|
||||
@ -2009,9 +2129,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros"
|
||||
version = "0.22.2"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206"
|
||||
checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"pyo3-macros-backend",
|
||||
@ -2021,9 +2141,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pyo3-macros-backend"
|
||||
version = "0.22.2"
|
||||
version = "0.22.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372"
|
||||
checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1"
|
||||
dependencies = [
|
||||
"heck 0.5.0",
|
||||
"proc-macro2",
|
||||
@ -2056,6 +2176,19 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||
dependencies = [
|
||||
"fuchsia-cprng",
|
||||
"libc",
|
||||
"rand_core 0.3.1",
|
||||
"rdrand",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@ -2064,7 +2197,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2074,9 +2207,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||
dependencies = [
|
||||
"rand_core 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
@ -2106,6 +2254,15 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rdrand"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||
dependencies = [
|
||||
"rand_core 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
@ -2291,7 +2448,7 @@ checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2378,9 +2535,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.7.0"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
|
||||
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile 2.1.1",
|
||||
@ -2410,9 +2567,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.4.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da"
|
||||
checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
@ -2550,9 +2707,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.14"
|
||||
version = "0.11.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
|
||||
checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
@ -2585,7 +2742,7 @@ version = "1.0.128"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
|
||||
dependencies = [
|
||||
"indexmap 2.2.5",
|
||||
"indexmap 2.5.0",
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
@ -3058,9 +3215,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.23.1"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd"
|
||||
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"log",
|
||||
@ -3113,7 +3270,7 @@ version = "0.22.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
|
||||
dependencies = [
|
||||
"indexmap 2.2.5",
|
||||
"indexmap 2.5.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@ -3270,11 +3427,12 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs"
|
||||
version = "9.0.1"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b44017f9f875786e543595076374b9ef7d13465a518dd93d6ccdbf5b432dde8c"
|
||||
checksum = "3a2f31991cee3dce1ca4f929a8a04fdd11fd8801aac0f2030b0fa8a0a3fef6b9"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"lazy_static",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"ts-rs-macros",
|
||||
@ -3284,9 +3442,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ts-rs-macros"
|
||||
version = "9.0.1"
|
||||
version = "10.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c88cc88fd23b5a04528f3a8436024f20010a16ec18eb23c164b1242f65860130"
|
||||
checksum = "0ea0b99e8ec44abd6f94a18f28f7934437809dd062820797c52401298116f70e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3296,9 +3454,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tungstenite"
|
||||
version = "0.23.0"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
|
||||
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
@ -3306,7 +3464,7 @@ dependencies = [
|
||||
"http 1.1.0",
|
||||
"httparse",
|
||||
"log",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.23.7",
|
||||
"rustls-pki-types",
|
||||
"sha1",
|
||||
@ -3570,6 +3728,7 @@ dependencies = [
|
||||
"js-sys",
|
||||
"kcl-lib",
|
||||
"kittycad",
|
||||
"kittycad-modeling-cmds",
|
||||
"pretty_assertions",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
@ -3839,9 +3998,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
@ -3879,6 +4038,6 @@ dependencies = [
|
||||
"crc32fast",
|
||||
"crossbeam-utils",
|
||||
"displaydoc",
|
||||
"indexmap 2.2.5",
|
||||
"indexmap 2.5.0",
|
||||
"thiserror",
|
||||
]
|
||||
|
@ -10,7 +10,7 @@ rust-version = "1.73"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
bson = { version = "2.12.0", features = ["uuid-1", "chrono"] }
|
||||
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
|
||||
data-encoding = "2.6.0"
|
||||
gloo-utils = "0.2.0"
|
||||
kcl-lib = { path = "kcl" }
|
||||
@ -27,7 +27,8 @@ anyhow = "1"
|
||||
hyper = { version = "0.14.29", features = ["server", "http1"] }
|
||||
image = { version = "0.25.1", default-features = false, features = ["png"] }
|
||||
kittycad = { workspace = true, default-features = true }
|
||||
pretty_assertions = "1.4.0"
|
||||
kittycad-modeling-cmds = { workspace = true }
|
||||
pretty_assertions = "1.4.1"
|
||||
reqwest = { version = "0.11.26", default-features = false }
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
twenty-twenty = "0.8"
|
||||
@ -72,6 +73,7 @@ members = [
|
||||
http = "0.2.12"
|
||||
kittycad = { version = "0.3.20", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-session = "0.1.4"
|
||||
kittycad-modeling-cmds = { version = "0.2.59", features = ["websocket"] }
|
||||
|
||||
[[test]]
|
||||
name = "executor"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.26"
|
||||
version = "0.1.27"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
@ -14,7 +14,7 @@ proc-macro = true
|
||||
[dependencies]
|
||||
Inflector = "0.11.4"
|
||||
convert_case = "0.6.0"
|
||||
once_cell = "1.19.0"
|
||||
once_cell = "1.20.0"
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
regex = "1.10"
|
||||
@ -23,7 +23,7 @@ serde_tokenstream = "0.2"
|
||||
syn = { version = "2.0.77", features = ["full"] }
|
||||
|
||||
[dev-dependencies]
|
||||
anyhow = "1.0.88"
|
||||
anyhow = "1.0.89"
|
||||
expectorate = "1.1.0"
|
||||
pretty_assertions = "1.4.0"
|
||||
pretty_assertions = "1.4.1"
|
||||
rustfmt-wrapper = "0.2.1"
|
||||
|
@ -269,7 +269,7 @@ fn do_stdlib_inner(
|
||||
let ty_string = rust_type_to_openapi_type(&ty_string);
|
||||
let required = !ty_ident.to_string().starts_with("Option <");
|
||||
|
||||
if ty_string != "Args" {
|
||||
if ty_string != "ExecState" && ty_string != "Args" {
|
||||
let schema = if ty_ident.to_string().starts_with("Vec < ")
|
||||
|| ty_ident.to_string().starts_with("Option <")
|
||||
|| ty_ident.to_string().starts_with('[')
|
||||
@ -387,11 +387,12 @@ fn do_stdlib_inner(
|
||||
#const_struct
|
||||
|
||||
fn #boxed_fn_name_ident(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>> + Send>,
|
||||
Box<dyn std::future::Future<Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>> + Send + '_>,
|
||||
> {
|
||||
Box::pin(#fn_name_ident(args))
|
||||
Box::pin(#fn_name_ident(exec_state, args))
|
||||
}
|
||||
|
||||
impl #docs_crate::StdLibFn for #name_ident
|
||||
@ -662,6 +663,9 @@ fn clean_ty_string(t: &str) -> (String, proc_macro2::TokenStream) {
|
||||
.replace("mut", "")
|
||||
.replace("< 'a >", "")
|
||||
.replace(' ', "");
|
||||
if ty_string.starts_with("ExecState") {
|
||||
ty_string = "ExecState".to_string();
|
||||
}
|
||||
if ty_string.starts_with("Args") {
|
||||
ty_string = "Args".to_string();
|
||||
}
|
||||
|
@ -85,6 +85,32 @@ fn test_args_with_lifetime() {
|
||||
expectorate::assert_contents("tests/args_with_lifetime.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_args_with_exec_state() {
|
||||
let (item, mut errors) = do_stdlib(
|
||||
quote! {
|
||||
name = "someFunction",
|
||||
},
|
||||
quote! {
|
||||
/// Docs
|
||||
/// ```
|
||||
/// someFunction()
|
||||
/// ```
|
||||
fn inner_some_function<'a>(
|
||||
exec_state: &mut ExecState,
|
||||
args: &Args,
|
||||
) -> i32 {
|
||||
3
|
||||
}
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
if let Some(e) = errors.pop() {
|
||||
panic!("{e}");
|
||||
}
|
||||
expectorate::assert_contents("tests/test_args_with_exec_state.gen", &get_text_fmt(&item).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_stdlib_line_to() {
|
||||
let (item, errors) = do_stdlib(
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(someFn(args))
|
||||
Box::pin(someFn(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for SomeFn {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(someFn(args))
|
||||
Box::pin(someFn(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for SomeFn {
|
||||
|
@ -77,15 +77,17 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(show(args))
|
||||
Box::pin(show(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Show {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(show(args))
|
||||
Box::pin(show(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Show {
|
||||
|
@ -78,15 +78,17 @@ pub(crate) struct MyFunc {}
|
||||
#[doc = "Std lib function: myFunc\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const MyFunc: MyFunc = MyFunc {};
|
||||
fn boxed_my_func(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(my_func(args))
|
||||
Box::pin(my_func(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for MyFunc {
|
||||
|
@ -78,15 +78,17 @@ pub(crate) struct LineTo {}
|
||||
#[doc = "Std lib function: lineTo\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const LineTo: LineTo = LineTo {};
|
||||
fn boxed_line_to(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(line_to(args))
|
||||
Box::pin(line_to(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for LineTo {
|
||||
|
@ -77,15 +77,17 @@ pub(crate) struct Min {}
|
||||
#[doc = "Std lib function: min\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Min: Min = Min {};
|
||||
fn boxed_min(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(min(args))
|
||||
Box::pin(min(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Min {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(show(args))
|
||||
Box::pin(show(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Show {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(import(args))
|
||||
Box::pin(import(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Import {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(import(args))
|
||||
Box::pin(import(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Import {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(import(args))
|
||||
Box::pin(import(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Import {
|
||||
|
@ -44,15 +44,17 @@ pub(crate) struct Show {}
|
||||
#[doc = "Std lib function: show\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Show: Show = Show {};
|
||||
fn boxed_show(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(show(args))
|
||||
Box::pin(show(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for Show {
|
||||
|
134
src/wasm-lib/derive-docs/tests/test_args_with_exec_state.gen
Normal file
@ -0,0 +1,134 @@
|
||||
#[cfg(test)]
|
||||
mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_some_function0() {
|
||||
let tokens = crate::token::lexer("someFunction()").unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
.unwrap(),
|
||||
)),
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
is_mock: true,
|
||||
};
|
||||
ctx.run(&program, None).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_some_function0() {
|
||||
let code = "someFunction()";
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_some_function0"),
|
||||
&result,
|
||||
0.99,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
#[doc = "Std lib function: someFunction\nDocs"]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
|
||||
#[ts(export)]
|
||||
pub(crate) struct SomeFunction {}
|
||||
|
||||
#[allow(non_upper_case_globals, missing_docs)]
|
||||
#[doc = "Std lib function: someFunction\nDocs"]
|
||||
pub(crate) const SomeFunction: SomeFunction = SomeFunction {};
|
||||
fn boxed_some_function(
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
> {
|
||||
Box::pin(some_function(exec_state, args))
|
||||
}
|
||||
|
||||
impl crate::docs::StdLibFn for SomeFunction {
|
||||
fn name(&self) -> String {
|
||||
"someFunction".to_string()
|
||||
}
|
||||
|
||||
fn summary(&self) -> String {
|
||||
"Docs".to_string()
|
||||
}
|
||||
|
||||
fn description(&self) -> String {
|
||||
"".to_string()
|
||||
}
|
||||
|
||||
fn tags(&self) -> Vec<String> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
|
||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||
settings.inline_subschemas = true;
|
||||
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
|
||||
let mut settings = schemars::gen::SchemaSettings::openapi3();
|
||||
settings.inline_subschemas = true;
|
||||
let mut generator = schemars::gen::SchemaGenerator::new(settings);
|
||||
Some(crate::docs::StdLibFnArg {
|
||||
name: "".to_string(),
|
||||
type_: "i32".to_string(),
|
||||
schema: <i32>::json_schema(&mut generator),
|
||||
required: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn unpublished(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn deprecated(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<String> {
|
||||
let code_blocks = vec!["someFunction()"];
|
||||
code_blocks
|
||||
.iter()
|
||||
.map(|cb| {
|
||||
let tokens = crate::token::lexer(cb).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
fn std_lib_fn(&self) -> crate::std::StdFn {
|
||||
boxed_some_function
|
||||
}
|
||||
|
||||
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc = r" Docs"]
|
||||
#[doc = r" ```"]
|
||||
#[doc = r" someFunction()"]
|
||||
#[doc = r" ```"]
|
||||
fn inner_some_function<'a>(exec_state: &mut ExecState, args: &Args) -> i32 {
|
||||
3
|
||||
}
|