Compare commits

..

40 Commits

Author SHA1 Message Date
f6b71043d3 Merge branch 'main' into ryanrosello-og/playwright-test-coverage 2024-09-16 13:33:51 -04:00
95c9a4629c Merge branch 'main' into ryanrosello-og/playwright-test-coverage 2024-09-16 11:57:27 -04:00
a0b7510887 Merge branch 'main' into ryanrosello-og/playwright-test-coverage 2024-09-16 11:19:50 -04:00
331f5fd810 Include the damn hidden files... 2024-09-16 10:15:34 -04:00
77a339bffe try again 2024-09-16 09:21:14 -04:00
a5c34ff667 Try with quotes 2024-09-16 07:57:56 -04:00
24b9aa3a8f tweak again 2024-09-16 12:07:13 +10:00
91c4018314 tweak upload 2024-09-16 11:48:22 +10:00
6259954527 debug 2024-09-16 11:34:05 +10:00
50ebd6bd60 turn on coverage always 2024-09-16 11:11:31 +10:00
2ec268cdd1 quick fix 2024-09-16 09:52:02 +10:00
7f6d992df1 one more attempt 2024-09-16 09:48:10 +10:00
b5e19bc066 some more debug 2024-09-16 09:15:50 +10:00
df10fd303c debug artifacts 2024-09-16 08:08:59 +10:00
0c1135f706 Merge remote-tracking branch 'origin' into ryanrosello-og/playwright-test-coverage 2024-09-16 08:04:25 +10:00
f75701900d tweak pattern 2024-09-16 07:12:07 +10:00
c0c47665b4 make sure coverage dir exists 2024-09-16 06:20:37 +10:00
9ffd971c33 remove electron artifact upload as well 2024-09-15 08:39:38 +10:00
6f28abd0b4 tweak again 2024-09-15 07:53:05 +10:00
8eff9709ee turn off coverage on electron tests 2024-09-15 07:26:46 +10:00
be107ec1ab dependency 2024-09-15 06:43:09 +10:00
bfacd89ad6 Merge remote-tracking branch 'origin' into ryanrosello-og/playwright-test-coverage 2024-09-14 20:49:51 +10:00
92da01f515 yarn again 2024-08-22 20:58:49 +10:00
19a001a08a update yarn.lock 2024-08-22 20:57:08 +10:00
0839f5e95c Merge remote-tracking branch 'origin' into ryanrosello-og/playwright-test-coverage 2024-08-22 20:32:51 +10:00
3340069459 remove vite-plugin-istanbul 2024-08-17 21:46:50 +10:00
5d3a0b9b52 update job dependancy name 2024-08-17 21:32:29 +10:00
a95473fb87 Merge branch 'main' into ryanrosello-og/playwright-test-coverage 2024-08-17 21:30:19 +10:00
23e7b5b6f9 revert on push back to main 2024-08-13 19:46:28 +10:00
7236fb0add fix for Error: ver] ✘ [ERROR] Failed to resolve "vite-plugin-istanbul". This package is ESM only but it was tried to load by require. See https://vitejs.dev/guide/troubleshooting.html#this-package-is-esm-only for more details. [plugin externalize-deps] 2024-08-13 19:17:09 +10:00
459053dce9 re-instate vite.config.ts 2024-08-13 19:03:44 +10:00
85e7719ca3 only report coverage based on Google chrome exec 2024-08-13 19:02:09 +10:00
fd4edcb0f0 Merge branch 'main' into ryanrosello-og/playwright-test-coverage 2024-08-13 18:55:42 +10:00
0654bcbe5a test on GH workflow 2024-08-11 20:30:17 +10:00
d85bfa39e1 don't use base fixture 2024-08-11 20:15:19 +10:00
235f39717e add pw coverage report to gh 2024-08-11 11:29:42 +10:00
11cfb54487 use baseFixture for all spec files 2024-08-11 08:50:48 +10:00
d97a4d27b2 enable vite istanbul coverage 2024-08-11 07:46:29 +10:00
f0f0778ee6 ignore nyc_output folder 2024-08-11 07:44:58 +10:00
9a85bd06bd add custom base test fixture 2024-08-11 07:44:40 +10:00
158 changed files with 6341 additions and 11661 deletions

View File

@ -237,6 +237,7 @@ jobs:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
GENERATE_PLAYWRIGHT_COVERAGE: true
- name: send to axiom
if: always()
shell: bash
@ -256,6 +257,18 @@ jobs:
path: playwright-report/
retention-days: 30
overwrite: true
- name: Debug artifact name
if: ${{ !cancelled() && (success() || failure()) }}
run: |
echo "Artifact name: playwright-coverage-${{ runner.os }}-${{ matrix.shardIndex }}-${{ github.sha }}"
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() && (success() || failure()) }}
with:
name: playwright-coverage-${{ runner.os }}-${{ matrix.shardIndex }}-${{ github.sha }}
path: "./.nyc_output/*.json"
retention-days: 30
overwrite: true
include-hidden-files: true
playwright-electron:
@ -416,6 +429,8 @@ jobs:
VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
VITE_KC_SKIP_AUTH: true
IS_UBUNTU: ${{ startsWith(matrix.os, 'ubuntu') && 'true' || 'false' }}
# TODO set to true, see: https://github.com/KittyCAD/modeling-app/issues/3885
GENERATE_PLAYWRIGHT_COVERAGE: false
#DEBUG: 'pw:browser*'
- name: send to axiom
if: ${{ !cancelled() && (success() || failure()) && !startsWith(matrix.os, 'windows') }}
@ -436,3 +451,59 @@ jobs:
path: playwright-report/
retention-days: 30
overwrite: true
# TODO uncomment the following, see: https://github.com/KittyCAD/modeling-app/issues/3885
# - uses: actions/upload-artifact@v4
# if: ${{ always() }}
# with:
# name: playwright-coverage-${{ runner.os }}-${{ github.sha }}
# path: .nyc_output/
# retention-days: 30
# overwrite: true
# only run this job after all shards above have completed
# TBC: do we want to separate coverage reports by OS?
# the Job below combines both chrome and webkit coverage reports
merge-coverage-reports:
# Merge reports after playwright-tests, even if some shards have failed
if: ${{ !cancelled() }}
needs: [playwright-chrome]
# only report on ubuntu (Google chrome) for now
# needs: [playwright-ubuntu, playwright-macos]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- name: Install dependencies
run: yarn
- name: Ensure .all_coverage_reports directory exists
run: mkdir -p .all_coverage_reports
- name: List all artifacts
run: |
echo "Available artifacts:"
gh api -H "Accept: application/vnd.github+json" /repos/${{ github.repository }}/actions/artifacts --jq '.artifacts[].name'
env:
GH_TOKEN: ${{ github.token }}
- name: Download coverage reports from GitHub Actions Artifacts
uses: actions/download-artifact@v4
with:
path: .all_coverage_reports
pattern: playwright-coverage-*
merge-multiple: true
- name: Merge all coverage reports from all shards into a single json report
run: npx nyc merge .all_coverage_reports ./.nyc_output/coverage.json
- name: Generate HTML coverage report
run: npx nyc report --reporter=html || true
- name: Upload Convertage HTML report
uses: actions/upload-artifact@v4
with:
name: coverage-report-${{ github.sha }}
path: coverage
retention-days: 14

2
.gitignore vendored
View File

@ -62,7 +62,7 @@ Mac_App_Distribution.provisionprofile
src/wasm-lib/pkg
venv
.vite/
.nyc_output/*.vite/
# electron
out/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -13,16 +13,14 @@ arrays can hold objects and vice versa.
`true` or `false` work when defining values.
## Constant declaration
## Variable declaration
Constants are defined with the `let` keyword like so:
Variables 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

View File

@ -104,7 +104,7 @@ test(
},
{ timeout: 15_000 }
)
.toBe(482669)
.toBe(477481)
// clean up output.gltf
await fsp.rm('output.gltf')

View File

@ -1,15 +1,6 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import {
executorInputPath,
getUtils,
setup,
setupElectron,
tearDown,
} from './test-utils'
import { join } from 'path'
import { FILE_EXT } from 'lib/constants'
import { getUtils, setup, setupElectron, tearDown } from './test-utils'
test.beforeEach(async ({ context, page }, testInfo) => {
await setup(context, page, testInfo)
@ -286,584 +277,3 @@ 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 () => {})
})

View File

@ -38,7 +38,7 @@ test(
await expect(page.getByText(notFoundText).first()).not.toBeVisible()
// Find the make button
const makeButton = page.getByRole('button', { name: 'Make part' })
const makeButton = page.getByRole('button', { name: 'Make' })
// Make sure the button is visible but disabled
await expect(makeButton).toBeVisible()
await expect(makeButton).toBeDisabled()

View File

@ -12,6 +12,7 @@ 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)
@ -203,7 +204,7 @@ test.describe('Can export from electron app', () => {
},
{ timeout: 15_000 }
)
.toBe(482669)
.toBe(477481)
// clean up output.gltf
await fsp.rm('output.gltf')
@ -1370,6 +1371,455 @@ 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' },

View File

@ -346,7 +346,10 @@ 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()

View File

@ -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([2.03, 0], %)`
codeStr += ` |> startProfileAt([1.53, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(30, 0)
codeStr += ` |> line([2.04, 0], %)`
codeStr += ` |> line([1.53, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30)
codeStr += ` |> line([0, -2.03], %)`
codeStr += ` |> line([0, -1.53], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(-30, 0)
codeStr += ` |> line([-2.04, 0], %)`
codeStr += ` |> line([-1.53, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(undefined, undefined)
@ -954,68 +954,4 @@ 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] })
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@ -12,6 +12,7 @@ import { EngineCommand } from 'lang/std/artifactGraph'
import fsp from 'fs/promises'
import fsSync from 'fs'
import { join } from 'path'
import * as fs from 'fs'
import pixelMatch from 'pixelmatch'
import { PNG } from 'pngjs'
import { Protocol } from 'playwright-core/types/protocol'
@ -26,6 +27,7 @@ import {
import * as TOML from '@iarna/toml'
import { SaveSettingsPayload } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME } from 'lib/constants'
import { uuidv4 } from 'lib/utils'
import { isErrorWhitelisted } from './lib/console-error-whitelist'
import { isArray } from 'lib/utils'
import { reportRejection } from 'lib/trap'
@ -441,34 +443,6 @@ 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 () => {
@ -844,6 +818,16 @@ export async function tearDown(page: Page, testInfo: TestInfo) {
uploadThroughput: -1,
})
if (process.env.GENERATE_PLAYWRIGHT_COVERAGE) {
for (const activePage of page.context().pages()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
await activePage.evaluate(() =>
(window as any)?.collectIstanbulCoverage?.(
JSON.stringify((window as any).__coverage__)
)
)
}
}
// It seems it's best to give the browser about 3s to close things
// It's not super reliable but we have no real other choice for now
await page.waitForTimeout(3000)
@ -892,6 +876,30 @@ export async function setup(
},
])
if (process.env.GENERATE_PLAYWRIGHT_COVERAGE) {
await context.addInitScript(() =>
window.addEventListener('beforeunload', () =>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(window as any)?.collectIstanbulCoverage?.(
JSON.stringify((window as any).__coverage__)
)
)
)
const istanbulCLIOutput = join(process.cwd(), '.nyc_output')
await fsp.mkdir(istanbulCLIOutput, { recursive: true })
await context.exposeFunction(
'collectIstanbulCoverage',
(coverageJSON: string) => {
if (coverageJSON) {
fs.writeFileSync(
join(istanbulCLIOutput, `playwright_coverage_${uuidv4()}.json`),
coverageJSON
)
}
}
)
}
failOnConsoleErrors(page, testInfo)
// kill animations, speeds up tests and reduced flakiness
await page.emulateMedia({ reducedMotion: 'reduce' })

View File

@ -31,8 +31,6 @@ 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 })
@ -46,13 +44,9 @@ test.describe('Testing selections', () => {
)
})
const topHorzSegmentClick = () =>
page.mouse
.click(startXPx, 500 - PUR * 20)
.then(() => page.waitForTimeout(100))
page.mouse.click(709, 290).then(() => page.waitForTimeout(100))
const bottomHorzSegmentClick = () =>
page.mouse
.click(startXPx + PUR * 10, 500 - PUR * 10)
.then(() => page.waitForTimeout(100))
page.mouse.click(767, 396).then(() => page.waitForTimeout(100))
await u.clearCommandLogs()
await expect(
@ -202,8 +196,6 @@ 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
@ -433,7 +425,7 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
|> line([0, 20.03], %)
|> line([62.61, 0], %, $seg03)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> close(%)
`
)
}, KCL_DEFAULT_LENGTH)
@ -536,22 +528,11 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
await page.waitForTimeout(100)
await u.closeDebugPanel()
const extrusionTopCap: Coords2d = [800, 240]
const extrusionTop: Coords2d = [800, 240]
const flatExtrusionFace: Coords2d = [960, 160]
const tangentialArcTo: Coords2d = [840, 160]
const arc: 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])
@ -559,141 +540,26 @@ const sketch002 = startSketchOn(launderExtrudeThroughVar, seg02)
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(200)
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(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()
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(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 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(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(
'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 }, %)'
)
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()
})
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({
page,
@ -1022,7 +888,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}
@ -1032,7 +898,7 @@ const extrude001 = extrude(50, sketch001)
|> line([-15.79, 17.08], %)
return ''
}
yohey([15.79, -34.6])
`
)

View File

@ -69,15 +69,12 @@ test.describe('Testing settings', () => {
page,
}) => {
const u = await getUtils(page)
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' })
})
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',
@ -85,23 +82,11 @@ test.describe('Testing settings', () => {
})
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
await page.keyboard.press('ControlOrMeta+Shift+,')
await expect(headingLocator).toBeVisible()
// Open the settings modal with the browser keyboard shortcut
await page.keyboard.press('ControlOrMeta+Shift+,')
/** 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()
})
await expect(headingLocator).toBeVisible()
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

View File

@ -68,6 +68,9 @@
},
"scripts": {
"start": "vite",
"start:playwright-ci:unix": "GENERATE_PLAYWRIGHT_COVERAGE=true yarn start",
"start:playwright-ci:win": "set GENERATE_PLAYWRIGHT_COVERAGE=true && yarn start",
"start:playwright-ci": "sh -c 'if [ \"$OS\" = \"Windows_NT\" ]; then yarn start:playwright-ci:unix; else yarn start:playwright-ci:win; fi'",
"start:prod": "vite preview --port=3000",
"serve": "vite serve --port=3000",
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
@ -80,7 +83,7 @@
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt": "prettier --write ./src *.ts *.mts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
@ -183,8 +186,9 @@
"tailwindcss": "^3.4.1",
"ts-node": "^10.0.0",
"typescript": "^5.0.0",
"vite": "^5.4.3",
"vite": "^5.4.2",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-istanbul": "^6.0.2",
"vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0",

View File

@ -93,7 +93,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: 'yarn start',
command: 'yarn start:playwright-ci',
// url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},

View File

@ -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-30 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-20 dark:border-chalkboard-80 border-t-0 shadow-sm">
<ul
{...props}
ref={toolbarButtonsRef}

View File

@ -31,7 +31,6 @@ 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
@ -85,7 +84,7 @@ export class CameraControls {
pendingPan: Vector2 | null = null
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
isFovAnimationInProgress = false
perspectiveFovBeforeOrtho = 45
fovBeforeOrtho = 45
get isPerspective() {
return this.camera instanceof PerspectiveCamera
}
@ -399,7 +398,7 @@ export class CameraControls {
const zoomFudgeFactor = 2280
distance = zoomFudgeFactor / (this.camera.zoom * 45)
}
const panSpeed = (distance / 1000 / 45) * this.perspectiveFovBeforeOrtho
const panSpeed = (distance / 1000 / 45) * this.fovBeforeOrtho
this.pendingPan.x += -deltaMove.x * panSpeed
this.pendingPan.y += deltaMove.y * panSpeed
}
@ -444,19 +443,8 @@ export class CameraControls {
}
onMouseWheel = (event: WheelEvent) => {
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
event.preventDefault()
if (this.syncDirection === 'engineToClient') {
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}`
)
}
this.zoomDataFromLastFrame = event.deltaY
return
}
@ -466,16 +454,8 @@ 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()
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.pendingZoom = 1 + (event.deltaY / window.devicePixelRatio) * 0.001
this.handleEnd()
}
@ -536,15 +516,19 @@ 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()
@ -996,9 +980,9 @@ export class CameraControls {
)
this.isFovAnimationInProgress = true
let currentFov = this.lastPerspectiveFov
this.perspectiveFovBeforeOrtho = currentFov
this.fovBeforeOrtho = currentFov
const targetFov = ORTHOGRAPHIC_MAGIC_FOV
const targetFov = 4
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
let frameWaitOnFinish = 10
@ -1034,9 +1018,9 @@ export class CameraControls {
)
}
this.isFovAnimationInProgress = true
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
this.lastPerspectiveFov = ORTHOGRAPHIC_MAGIC_FOV
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
this.lastPerspectiveFov = 4
let currentFov = 4
const initialCameraUp = this.camera.up.clone()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.usePerspectiveCamera()
@ -1072,8 +1056,9 @@ export class CameraControls {
)
}
this.isFovAnimationInProgress = true
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
this.lastPerspectiveFov = 4
let currentFov = 4
const initialCameraUp = this.camera.up.clone()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.usePerspectiveCamera()
@ -1142,7 +1127,7 @@ export class CameraControls {
this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
}
getInteractionType = (event: MouseEvent) =>
getInteractionType = (event: any) =>
_getInteractionType(
this.interactionGuards,
event,
@ -1250,21 +1235,16 @@ function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
function _getInteractionType(
interactionGuards: MouseGuard,
event: MouseEvent | WheelEvent,
event: any,
enablePan: boolean,
enableRotate: boolean,
enableZoom: boolean
): interactionType | 'none' {
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'
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
}
/**

View File

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

View File

@ -681,21 +681,6 @@ 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"

View File

@ -38,7 +38,7 @@ import {
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
import {
Selections,
canSweepSelection,
canExtrudeSelection,
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,9 +415,20 @@ export const ModelingMachineProvider = ({
selection: { type: 'default_scene' },
}
exportFromEngine({
format: format,
}).catch(reportRejection)
// 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)
},
'Engine export': ({ event }) => {
if (event.type !== 'Export') return
@ -471,9 +482,18 @@ export const ModelingMachineProvider = ({
format.selection = { type: 'default_scene' }
}
exportFromEngine({
format: format as Models['OutputFormat_type'],
}).catch(reportRejection)
toast
.promise(
exportFromEngine({
format: format as Models['OutputFormat_type'],
}),
{
loading: 'Exporting...',
success: 'Exported successfully',
error: 'Error while exporting',
}
)
.catch(reportRejection)
},
'Submit to Text-to-CAD API': ({ event }) => {
if (event.type !== 'Text-to-CAD') return
@ -508,32 +528,12 @@ 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 (doesSceneHaveSweepableSketch(kclManager.ast)) return true
if (hasExtrudableGeometry(kclManager.ast)) return true
return false
}
if (!isPipe) return false
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)
return canExtrudeSelection(selectionRanges)
},
'has valid selection for deletion': ({
context: { selectionRanges },
@ -571,9 +571,7 @@ export const ModelingMachineProvider = ({
else if (kclManager.ast.body.length === 0)
errorMessage += 'due to Empty Scene'
console.error(errorMessage)
toast.error(errorMessage, {
id: kclManager.engineCommandManager.pendingExport?.toastId,
})
toast.error(errorMessage)
return false
}
},

View File

@ -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-30;
@apply bg-chalkboard-10/50 focus-within:bg-chalkboard-10/90 backdrop-blur-sm border border-chalkboard-20;
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-30;
@apply bg-chalkboard-10 border-b border-chalkboard-20;
}
:global(.dark) .header {

View File

@ -1,4 +1,10 @@
import { IconDefinition, faBugSlash } from '@fortawesome/free-solid-svg-icons'
import {
IconDefinition,
faBugSlash,
faCode,
faCodeCommit,
faSquareRootVariable,
} 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'
@ -62,7 +68,7 @@ export const sidebarPanes: SidebarPane[] = [
{
id: 'code',
title: 'KCL Code',
icon: 'code',
icon: faCode,
Content: KclEditorPane,
keybinding: 'Shift + C',
Menu: KclEditorMenu,
@ -88,7 +94,7 @@ export const sidebarPanes: SidebarPane[] = [
{
id: 'variables',
title: 'Variables',
icon: 'make-variable',
icon: faSquareRootVariable,
Content: MemoryPane,
Menu: MemoryPaneMenu,
keybinding: 'Shift + V',
@ -96,7 +102,7 @@ export const sidebarPanes: SidebarPane[] = [
{
id: 'logs',
title: 'Logs',
icon: 'logs',
icon: faCodeCommit,
Content: LogsPane,
keybinding: 'Shift + L',
},

View File

@ -55,6 +55,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
id: 'export',
title: 'Export part',
icon: 'floppyDiskArrow',
iconClassName: '!p-0',
keybinding: 'Ctrl + Shift + E',
action: () =>
commandBarSend({
@ -66,6 +67,7 @@ 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 () => {
@ -179,7 +181,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-30 dark:bg-chalkboard-90 dark:border-chalkboard-80 group-focus-within:border-primary dark:group-focus-within:border-chalkboard-50 shadow-sm '
'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 '
}
>
<ul
@ -202,7 +204,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
</ul>
{filteredActions.length > 0 && (
<>
<hr className="w-full border-chalkboard-30 dark:border-chalkboard-80" />
<hr className="w-full border-chalkboard-20 dark:border-chalkboard-80" />
<ul
id="sidebar-actions"
className="w-fit p-2 flex flex-col gap-2"
@ -290,7 +292,7 @@ function ModelingPaneButton({
return (
<div id={paneConfig.id + '-button-holder'}>
<button
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"
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"
onClick={onClick}
name={paneConfig.title}
data-testid={paneConfig.id + '-pane-button'}
@ -300,9 +302,13 @@ function ModelingPaneButton({
>
<ActionIcon
icon={paneConfig.icon}
className={paneConfig.iconClassName || ''}
size={paneConfig.iconSize || 'md'}
iconClassName={paneIsOpen ? ' !text-chalkboard-10' : ''}
className={'p-1 ' + paneConfig.iconClassName || ''}
size={paneConfig.iconSize || 'sm'}
iconClassName={
paneIsOpen
? ' !text-chalkboard-10'
: '!text-chalkboard-80 dark:!text-chalkboard-30'
}
bgClassName={
'rounded-sm ' + (paneIsOpen ? '!bg-primary' : '!bg-transparent')
}

View File

@ -238,7 +238,7 @@ export const AllSettingsFields = forwardRef(
</h2>
<div className="text-sm mb-12">
<p>
{/* This uses a Vite plugin, set in vite.config.ts
{/* This uses a Vite plugin, set in vite.config.mts
to inject the version from package.json */}
App version {APP_VERSION}.{' '}
<a

View File

@ -260,7 +260,7 @@ export const Stream = () => {
if (state.matches('Sketch')) return
if (state.matches({ idle: 'showPlanes' })) return
if (btnName(e.nativeEvent).left) {
if (btnName(e).left) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
sendSelectEventToEngine(e, videoRef.current)
}

View File

@ -9,8 +9,8 @@ import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
import {
getCapCodeRef,
getSweepEdgeCodeRef,
getSweepFromSuspectedSweepSurface,
getExtrudeEdgeCodeRef,
getExtrusionFromSuspectedExtrudeSurface,
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 = getSweepFromSuspectedSweepSurface(
const extrusion = getExtrusionFromSuspectedExtrudeSurface(
data.entity_id,
engineCommandManager.artifactGraph
)
@ -61,8 +61,8 @@ export function useEngineConnectionSubscriptions() {
? [codeRef.range]
: [codeRef.range, extrusion.codeRef.range]
)
} else if (artifact?.type === 'sweepEdge') {
const codeRef = getSweepEdgeCodeRef(
} else if (artifact?.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
artifact,
engineCommandManager.artifactGraph
)
@ -172,7 +172,7 @@ export function useEngineConnectionSubscriptions() {
}
const faceId = planeOrFaceId
const artifact = engineCommandManager.artifactGraph.get(faceId)
const extrusion = getSweepFromSuspectedSweepSurface(
const extrusion = getExtrusionFromSuspectedExtrudeSurface(
faceId,
engineCommandManager.artifactGraph
)

View File

@ -410,11 +410,6 @@ 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', () => {

View File

@ -251,7 +251,7 @@ export function extrudeSketch(
node: Program,
pathToNode: PathToNode,
shouldPipe = false,
distance: Expr = createLiteral(4)
distance = createLiteral(4) as Expr
):
| {
modifiedAst: Program
@ -259,7 +259,7 @@ export function extrudeSketch(
pathToExtrudeArg: PathToNode
}
| Error {
const _node = structuredClone(node)
const _node = { ...node }
const _node1 = getNodeFromPath(_node, pathToNode)
if (err(_node1)) return _node1
const { node: sketchExpression } = _node1
@ -342,102 +342,6 @@ 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,

View File

@ -35,7 +35,7 @@ import { Selections, canFilletSelection } from 'lib/selections'
import { KclCommandValue } from 'lib/commandTypes'
import {
ArtifactGraph,
getSweepFromSuspectedPath,
getExtrusionFromSuspectedPath,
} 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 = getSweepFromSuspectedPath(sketchGroup.id, artifactGraph)
const extrusion = getExtrusionFromSuspectedPath(sketchGroup.id, artifactGraph)
if (err(extrusion)) return extrusion
const pathToExtrudeNode = getNodePathFromSourceRange(

View File

@ -8,7 +8,7 @@ import {
hasExtrudeSketchGroup,
findUsesOfTagInPipe,
hasSketchPipeBeenExtruded,
doesSceneHaveSweepableSketch,
hasExtrudableGeometry,
traverse,
} from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers'
@ -488,7 +488,7 @@ const sketch002 = startSketchOn(extrude001, $seg01)
})
})
describe('Testing doesSceneHaveSweepableSketch', () => {
describe('Testing hasExtrudableGeometry', () => {
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 = doesSceneHaveSweepableSketch(ast)
const extrudable = hasExtrudableGeometry(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 = doesSceneHaveSweepableSketch(ast)
const extrudable = hasExtrudableGeometry(ast)
expect(extrudable).toBeFalsy()
})
})

View File

@ -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 === 'revolve') &&
node.callee.name === 'extrude' &&
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 doesSceneHaveSweepableSketch(ast: Program) {
export function hasExtrudableGeometry(ast: Program) {
const theMap: any = {}
traverse(ast as any, {
enter(node) {
@ -925,7 +925,7 @@ export function doesSceneHaveSweepableSketch(ast: Program) {
}
} else if (
node.type === 'CallExpression' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.callee.name === 'extrude' &&
node.arguments[1]?.type === 'Identifier' &&
theMap?.[node?.arguments?.[1]?.name]
) {

View File

@ -33,6 +33,7 @@ Map {
70,
],
},
"extrusionId": "UUID",
"planeId": "UUID",
"segIds": [
"UUID",
@ -42,7 +43,6 @@ Map {
"UUID",
],
"solid2dId": "UUID",
"sweepId": "UUID",
"type": "path",
},
"UUID-2" => {
@ -175,7 +175,6 @@ Map {
"UUID",
],
"pathId": "UUID",
"subType": "extrusion",
"surfaceIds": [
"UUID",
"UUID",
@ -184,99 +183,99 @@ Map {
"UUID",
"UUID",
],
"type": "sweep",
"type": "extrusion",
},
"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",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-16" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-17" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-18" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-19" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-20" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-21" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-22" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-23" => {
"codeRef": {
@ -309,6 +308,7 @@ Map {
395,
],
},
"extrusionId": "UUID",
"planeId": "UUID",
"segIds": [
"UUID",
@ -317,7 +317,6 @@ Map {
"UUID",
],
"solid2dId": "UUID",
"sweepId": "UUID",
"type": "path",
},
"UUID-25" => {
@ -426,7 +425,6 @@ Map {
"UUID",
],
"pathId": "UUID",
"subType": "extrusion",
"surfaceIds": [
"UUID",
"UUID",
@ -434,78 +432,78 @@ Map {
"UUID",
"UUID",
],
"type": "sweep",
"type": "extrusion",
},
"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",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-37" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-38" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-39" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-40" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "opposite",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
"UUID-41" => {
"extrusionId": "UUID",
"segId": "UUID",
"subType": "adjacent",
"sweepId": "UUID",
"type": "sweepEdge",
"type": "extrudeEdge",
},
}
`;

View File

@ -7,7 +7,7 @@ import {
filterArtifacts,
expandPlane,
expandPath,
expandSweep,
expandExtrusion,
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: ['sweep'] }, theMap)].map(
(extrusion) => expandSweep(extrusion[1], theMap)
)
const extrusions = [
...filterArtifacts({ types: ['extrusion'] }, theMap),
].map((extrusion) => expandExtrusion(extrusion[1], theMap))
expect(extrusions).toHaveLength(2)
extrusions.forEach((extrusion, index) => {
if (err(extrusion)) throw extrusion
expect(extrusion.type).toBe('sweep')
expect(extrusion.type).toBe('extrusion')
const firstExtrusionIsACubeIE6Sides = 6
const secondExtrusionIsATriangularPrismIE5Sides = 5
expect(extrusion.surfaces.length).toBe(
@ -535,7 +535,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'path',
segIds: [],
planeId: 'UUID-1',
sweepId: '',
extrusionId: '',
codeRef: {
pathToNode: [['body', '']],
range: [43, 70],
@ -544,8 +544,7 @@ describe('testing getArtifactsToUpdate', () => {
])
expect(getUpdateObjects('extrude')).toEqual([
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: [],
edgeIds: [],
@ -558,7 +557,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'path',
segIds: expect.any(Array),
planeId: expect.any(String),
sweepId: expect.any(String),
extrusionId: expect.any(String),
codeRef: {
range: [43, 70],
pathToNode: [['body', '']],
@ -581,7 +580,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'path',
segIds: expect.any(Array),
planeId: expect.any(String),
sweepId: expect.any(String),
extrusionId: expect.any(String),
codeRef: {
range: [43, 70],
pathToNode: [['body', '']],
@ -618,7 +617,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'wall',
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
@ -632,8 +631,7 @@ describe('testing getArtifactsToUpdate', () => {
},
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -646,7 +644,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'wall',
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
@ -660,8 +658,7 @@ describe('testing getArtifactsToUpdate', () => {
},
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -674,7 +671,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'wall',
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
@ -689,8 +686,7 @@ describe('testing getArtifactsToUpdate', () => {
edgeCutId: expect.any(String),
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -703,7 +699,7 @@ describe('testing getArtifactsToUpdate', () => {
type: 'wall',
segId: expect.any(String),
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
@ -717,8 +713,7 @@ describe('testing getArtifactsToUpdate', () => {
},
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -731,12 +726,11 @@ describe('testing getArtifactsToUpdate', () => {
type: 'cap',
subType: 'start',
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),
@ -749,12 +743,11 @@ describe('testing getArtifactsToUpdate', () => {
type: 'cap',
subType: 'end',
edgeCutEdgeIds: [],
sweepId: expect.any(String),
extrusionId: expect.any(String),
pathIds: [],
},
{
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
pathId: expect.any(String),
surfaceIds: expect.any(Array),
edgeIds: expect.any(Array),

View File

@ -25,7 +25,7 @@ export interface PathArtifact {
type: 'path'
planeId: ArtifactId
segIds: Array<ArtifactId>
sweepId: ArtifactId
extrusionId: ArtifactId
solid2dId?: ArtifactId
codeRef: CommonCommandProperties
}
@ -38,7 +38,7 @@ export interface PathArtifactRich {
type: 'path'
plane: PlaneArtifact | WallArtifact
segments: Array<SegmentArtifact>
sweep: SweepArtifact
extrusion: ExtrusionArtifact
codeRef: CommonCommandProperties
}
@ -54,26 +54,23 @@ interface SegmentArtifactRich {
type: 'segment'
path: PathArtifact
surf: WallArtifact
edges: Array<SweepEdge>
edges: Array<ExtrudeEdge>
edgeCut?: EdgeCut
codeRef: CommonCommandProperties
}
/** 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>
interface ExtrusionArtifact {
type: 'extrusion'
pathId: ArtifactId
surfaceIds: Array<ArtifactId>
edgeIds: Array<ArtifactId>
codeRef: CommonCommandProperties
}
interface SweepArtifactRich {
type: 'sweep'
subType: 'extrusion' | 'revolve'
interface ExtrusionArtifactRich {
type: 'extrusion'
path: PathArtifact
surfaces: Array<WallArtifact | CapArtifact>
edges: Array<SweepEdge>
edges: Array<ExtrudeEdge>
codeRef: CommonCommandProperties
}
@ -81,21 +78,21 @@ interface WallArtifact {
type: 'wall'
segId: ArtifactId
edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId
extrusionId: ArtifactId
pathIds: Array<ArtifactId>
}
interface CapArtifact {
type: 'cap'
subType: 'start' | 'end'
edgeCutEdgeIds: Array<ArtifactId>
sweepId: ArtifactId
extrusionId: ArtifactId
pathIds: Array<ArtifactId>
}
interface SweepEdge {
type: 'sweepEdge'
interface ExtrudeEdge {
type: 'extrudeEdge'
segId: ArtifactId
sweepId: ArtifactId
extrusionId: ArtifactId
subType: 'opposite' | 'adjacent'
}
@ -119,10 +116,10 @@ export type Artifact =
| PlaneArtifact
| PathArtifact
| SegmentArtifact
| SweepArtifact
| ExtrusionArtifact
| WallArtifact
| CapArtifact
| SweepEdge
| ExtrudeEdge
| EdgeCut
| EdgeCutEdge
| solid2D
@ -260,7 +257,7 @@ export function getArtifactsToUpdate({
type: 'wall',
segId: existingPlane.segId,
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
sweepId: existingPlane.sweepId,
extrusionId: existingPlane.extrusionId,
pathIds: existingPlane.pathIds,
},
},
@ -277,7 +274,7 @@ export function getArtifactsToUpdate({
type: 'path',
segIds: [],
planeId: currentPlaneId,
sweepId: '',
extrusionId: '',
codeRef: { range, pathToNode },
},
})
@ -297,7 +294,7 @@ export function getArtifactsToUpdate({
type: 'wall',
segId: plane.segId,
edgeCutEdgeIds: plane.edgeCutEdgeIds,
sweepId: plane.sweepId,
extrusionId: plane.extrusionId,
pathIds: [id],
},
})
@ -340,13 +337,11 @@ export function getArtifactsToUpdate({
})
}
return returnArr
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') {
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
} else if (cmd.type === 'extrude') {
returnArr.push({
id,
artifact: {
type: 'sweep',
subType: subType,
type: 'extrusion',
pathId: cmd.target,
surfaceIds: [],
edgeIds: [],
@ -357,7 +352,7 @@ export function getArtifactsToUpdate({
if (path?.type === 'path')
returnArr.push({
id: cmd.target,
artifact: { ...path, sweepId: id },
artifact: { ...path, extrusionId: id },
})
return returnArr
} else if (
@ -380,7 +375,7 @@ export function getArtifactsToUpdate({
type: 'wall',
segId: curve_id,
edgeCutEdgeIds: [],
sweepId: path.sweepId,
extrusionId: path.extrusionId,
pathIds: [],
},
})
@ -388,12 +383,12 @@ export function getArtifactsToUpdate({
id: curve_id,
artifact: { ...seg, surfaceId: face_id },
})
const sweep = getArtifact(path.sweepId)
if (sweep?.type === 'sweep') {
const extrusion = getArtifact(path.extrusionId)
if (extrusion?.type === 'extrusion') {
returnArr.push({
id: path.sweepId,
id: path.extrusionId,
artifact: {
...sweep,
...extrusion,
surfaceIds: [face_id],
},
})
@ -412,16 +407,16 @@ export function getArtifactsToUpdate({
type: 'cap',
subType: cap === 'bottom' ? 'start' : 'end',
edgeCutEdgeIds: [],
sweepId: path.sweepId,
extrusionId: path.extrusionId,
pathIds: [],
},
})
const sweep = getArtifact(path.sweepId)
if (sweep?.type !== 'sweep') return
const extrusion = getArtifact(path.extrusionId)
if (extrusion?.type !== 'extrusion') return
returnArr.push({
id: path.sweepId,
id: path.extrusionId,
artifact: {
...sweep,
...extrusion,
surfaceIds: [face_id],
},
})
@ -444,9 +439,9 @@ export function getArtifactsToUpdate({
) {
const wall = getArtifact(cmd.face_id)
if (wall?.type !== 'wall') return returnArr
const sweep = getArtifact(wall.sweepId)
if (sweep?.type !== 'sweep') return returnArr
const path = getArtifact(sweep.pathId)
const extrusion = getArtifact(wall.extrusionId)
if (extrusion?.type !== 'extrusion') return returnArr
const path = getArtifact(extrusion.pathId)
if (path?.type !== 'path') return returnArr
const segment = getArtifact(cmd.edge_id)
if (segment?.type !== 'segment') return returnArr
@ -455,13 +450,13 @@ export function getArtifactsToUpdate({
{
id: response.data.modeling_response.data.edge,
artifact: {
type: 'sweepEdge',
type: 'extrudeEdge',
subType:
cmd.type === 'solid3d_get_prev_adjacent_edge'
? 'adjacent'
: 'opposite',
segId: cmd.edge_id,
sweepId: path.sweepId,
extrusionId: path.extrusionId,
},
},
{
@ -472,9 +467,9 @@ export function getArtifactsToUpdate({
},
},
{
id: path.sweepId,
id: path.extrusionId,
artifact: {
...sweep,
...extrusion,
edgeIds: [response.data.modeling_response.data.edge],
},
},
@ -587,10 +582,10 @@ export function expandPath(
{ keys: path.segIds, types: ['segment'] },
artifactGraph
)
const sweep = getArtifactOfTypes(
const extrusion = getArtifactOfTypes(
{
key: path.sweepId,
types: ['sweep'],
key: path.extrusionId,
types: ['extrusion'],
},
artifactGraph
)
@ -598,41 +593,40 @@ export function expandPath(
{ key: path.planeId, types: ['plane', 'wall'] },
artifactGraph
)
if (err(sweep)) return sweep
if (err(extrusion)) return extrusion
if (err(plane)) return plane
return {
type: 'path',
segments: Array.from(segs.values()),
sweep,
extrusion,
plane,
codeRef: path.codeRef,
}
}
export function expandSweep(
sweep: SweepArtifact,
export function expandExtrusion(
extrusion: ExtrusionArtifact,
artifactGraph: ArtifactGraph
): SweepArtifactRich | Error {
): ExtrusionArtifactRich | Error {
const surfs = getArtifactsOfTypes(
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
{ keys: extrusion.surfaceIds, types: ['wall', 'cap'] },
artifactGraph
)
const edges = getArtifactsOfTypes(
{ keys: sweep.edgeIds, types: ['sweepEdge'] },
{ keys: extrusion.edgeIds, types: ['extrudeEdge'] },
artifactGraph
)
const path = getArtifactOfTypes(
{ key: sweep.pathId, types: ['path'] },
{ key: extrusion.pathId, types: ['path'] },
artifactGraph
)
if (err(path)) return path
return {
type: 'sweep',
subType: 'extrusion',
type: 'extrusion',
surfaces: Array.from(surfs.values()),
edges: Array.from(edges.values()),
path,
codeRef: sweep.codeRef,
codeRef: extrusion.codeRef,
}
}
@ -649,7 +643,7 @@ export function expandSegment(
artifactGraph
)
const edges = getArtifactsOfTypes(
{ keys: segment.edgeIds, types: ['sweepEdge'] },
{ keys: segment.edgeIds, types: ['extrudeEdge'] },
artifactGraph
)
const edgeCut = segment.edgeCutId
@ -676,13 +670,13 @@ export function getCapCodeRef(
cap: CapArtifact,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
const sweep = getArtifactOfTypes(
{ key: cap.sweepId, types: ['sweep'] },
const extrusion = getArtifactOfTypes(
{ key: cap.extrusionId, types: ['extrusion'] },
artifactGraph
)
if (err(sweep)) return sweep
if (err(extrusion)) return extrusion
const path = getArtifactOfTypes(
{ key: sweep.pathId, types: ['path'] },
{ key: extrusion.pathId, types: ['path'] },
artifactGraph
)
if (err(path)) return path
@ -713,8 +707,8 @@ export function getWallCodeRef(
return seg.codeRef
}
export function getSweepEdgeCodeRef(
edge: SweepEdge,
export function getExtrudeEdgeCodeRef(
edge: ExtrudeEdge,
artifactGraph: ArtifactGraph
): CommonCommandProperties | Error {
const seg = getArtifactOfTypes(
@ -725,29 +719,29 @@ export function getSweepEdgeCodeRef(
return seg.codeRef
}
export function getSweepFromSuspectedSweepSurface(
export function getExtrusionFromSuspectedExtrudeSurface(
id: ArtifactId,
artifactGraph: ArtifactGraph
): SweepArtifact | Error {
): ExtrusionArtifact | Error {
const artifact = getArtifactOfTypes(
{ key: id, types: ['wall', 'cap'] },
artifactGraph
)
if (err(artifact)) return artifact
return getArtifactOfTypes(
{ key: artifact.sweepId, types: ['sweep'] },
{ key: artifact.extrusionId, types: ['extrusion'] },
artifactGraph
)
}
export function getSweepFromSuspectedPath(
export function getExtrusionFromSuspectedPath(
id: ArtifactId,
artifactGraph: ArtifactGraph
): SweepArtifact | Error {
): ExtrusionArtifact | Error {
const path = getArtifactOfTypes({ key: id, types: ['path'] }, artifactGraph)
if (err(path)) return path
return getArtifactOfTypes(
{ key: path.sweepId, types: ['sweep'] },
{ key: path.extrusionId, types: ['extrusion'] },
artifactGraph
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 378 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 613 KiB

After

Width:  |  Height:  |  Size: 617 KiB

View File

@ -16,11 +16,7 @@ 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,
EXPORT_TOAST_MESSAGES,
MAKE_TOAST_MESSAGES,
} from 'lib/constants'
import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { KclManager } from 'lang/KclSingleton'
import { reportRejection } from 'lib/trap'
@ -963,9 +959,7 @@ class EngineConnection extends EventTarget {
) {
// Reject the promise with the error.
this.engineCommandManager.pendingExport.reject(errorsString)
toast.error(errorsString, {
id: this.engineCommandManager.pendingExport.toastId,
})
toast.error(errorsString)
this.engineCommandManager.pendingExport = undefined
}
} else {
@ -1333,13 +1327,8 @@ 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
@ -1601,7 +1590,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 || this.pendingExport === undefined) {
if (this.exportIntent === null) {
toast.error(
'Export intent was not set, but export data was received'
)
@ -1613,22 +1602,19 @@ export class EngineCommandManager extends EventTarget {
switch (this.exportIntent) {
case ExportIntent.Save: {
exportSave(event.data, this.pendingExport.toastId).then(() => {
exportSave(event.data).then(() => {
this.pendingExport?.resolve(null)
}, this.pendingExport?.reject)
break
}
case ExportIntent.Make: {
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
)
exportMake(event.data).then((result) => {
if (result) {
this.pendingExport?.resolve(null)
} else {
this.pendingExport?.reject('Failed to make export')
}
}, this.pendingExport?.reject)
break
}
}
@ -1943,20 +1929,7 @@ 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',

View File

@ -1,16 +1,8 @@
/// 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,
toastId: string
) => {
export const browserSaveFile = async (blob: Blob, suggestedName: string) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
@ -37,15 +29,11 @@ export const browserSaveFile = async (
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') {
toast.dismiss(toastId)
} else {
if (err.name !== 'AbortError') {
console.error(err.name, err.message)
toast.error(EXPORT_TOAST_MESSAGES.FAILED, { id: toastId })
}
return
}
@ -66,5 +54,4 @@ export const browserSaveFile = async (
URL.revokeObjectURL(blobURL)
a.remove()
}, 1000)
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
}

View File

@ -6,7 +6,7 @@ const META =
PLATFORM === 'macos' ? 'Cmd' : PLATFORM === 'windows' ? 'Win' : 'Super'
const ALT = PLATFORM === 'macos' ? 'Option' : 'Alt'
const noModifiersPressed = (e: MouseEvent) =>
const noModifiersPressed = (e: React.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: MouseEvent) => boolean
callback: (e: React.MouseEvent) => boolean
lenientDragStartButton?: number
}
interface MouseGuardZoomHandler {
description: string
dragCallback: (e: MouseEvent) => boolean
scrollCallback: (e: WheelEvent) => boolean
dragCallback: (e: React.MouseEvent) => boolean
scrollCallback: (e: React.MouseEvent) => boolean
lenientDragStartButton?: number
}
@ -70,7 +70,7 @@ export interface MouseGuard {
rotate: MouseGuardHandler
}
export const btnName = (e: MouseEvent) => ({
export const btnName = (e: React.MouseEvent) => ({
middle: !!(e.buttons & 4) || e.button === 1,
right: !!(e.buttons & 2) || e.button === 2,
left: !!(e.buttons & 1) || e.button === 0,

View File

@ -1,6 +1,6 @@
import { Models } from '@kittycad/lib'
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
import { KCL_DEFAULT_LENGTH } from 'lib/constants'
import { components } from 'lib/machine-api'
import { Selections } from 'lib/selections'
import { machineManager } from 'lib/machineManager'
@ -32,10 +32,6 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue
}
Revolve: {
selection: Selections
angle: KclCommandValue
}
Fillet: {
// todo
selection: Selections
@ -213,7 +209,6 @@ 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,
@ -237,26 +232,6 @@ 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',

View File

@ -53,14 +53,9 @@ 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'
@ -77,21 +72,3 @@ 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',
}

View File

@ -46,7 +46,6 @@ 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) {

View File

@ -1,6 +1,7 @@
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.
@ -22,5 +23,11 @@ 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
}

View File

@ -3,37 +3,33 @@ 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,
toastId: string
): Promise<Response | null> {
export async function exportMake(data: ArrayBuffer): Promise<Response | null> {
if (machineManager.machineCount() === 0) {
console.error(MAKE_TOAST_MESSAGES.NO_MACHINES)
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINES, { id: toastId })
console.error('No machines available')
toast.error('No machines available')
return null
}
const machineApiIp = machineManager.machineApiIp
if (!machineApiIp) {
console.error(MAKE_TOAST_MESSAGES.NO_MACHINE_API_IP)
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINE_API_IP, { id: toastId })
console.error('No machine api ip available')
toast.error('No machine api ip available')
return null
}
const currentMachine = machineManager.currentMachine
if (!currentMachine) {
console.error(MAKE_TOAST_MESSAGES.NO_CURRENT_MACHINE)
toast.error(MAKE_TOAST_MESSAGES.NO_CURRENT_MACHINE, { id: toastId })
console.error('No current machine available')
toast.error('No current machine available')
return null
}
let machineId = currentMachine?.id
if (!machineId) {
console.error(MAKE_TOAST_MESSAGES.NO_MACHINE_ID, currentMachine)
toast.error(MAKE_TOAST_MESSAGES.NO_MACHINE_ID, { id: toastId })
console.error('No machine id available', currentMachine)
toast.error('No machine id available')
return null
}
@ -62,22 +58,16 @@ export async function exportMake(
console.log('response', response)
if (!response.ok) {
console.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, response)
console.error('Error exporting', response)
const text = await response.text()
toast.error(
'Error while starting print: ' + response.statusText + ' ' + text,
{
id: toastId,
}
)
toast.error('Error exporting: ' + response.statusText + ' ' + text)
return null
}
toast.success(MAKE_TOAST_MESSAGES.SUCCESS, { id: toastId })
return response
} catch (error) {
console.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, error)
toast.error(MAKE_TOAST_MESSAGES.ERROR_STARTING_PRINT, { id: toastId })
console.error('Error exporting', error)
toast.error('Error exporting')
return null
}
}

View File

@ -4,10 +4,8 @@ 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, toastId: string) => {
const save_ = async (file: ModelingAppFile) => {
try {
if (isDesktop()) {
const extension = file.name.split('.').pop() || null
@ -22,7 +20,6 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
file.name,
new Uint8Array(file.contents)
)
toast.success(EXPORT_TOAST_MESSAGES.SUCCESS, { id: toastId })
return
}
@ -39,17 +36,13 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
// The user canceled the save.
// Return early.
if (filePathMeta.canceled) {
toast.dismiss(toastId)
return
}
if (filePathMeta.canceled) 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.
@ -58,17 +51,16 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
// Create a new blob.
const blob = new Blob([new Uint8Array(file.contents)])
// Save the file.
await browserSaveFile(blob, file.name, toastId)
await browserSaveFile(blob, file.name)
}
} 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, toastId: string) {
export async function exportSave(data: ArrayBuffer) {
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
let uintArray = new Uint8Array(data)
@ -80,9 +72,9 @@ export async function exportSave(data: ArrayBuffer, toastId: string) {
zip.file(file.name, new Uint8Array(file.contents), { binary: true })
}
return zip.generateAsync({ type: 'array' }).then((contents) => {
return save_({ name: 'output.zip', contents }, toastId)
return save_({ name: 'output.zip', contents })
})
} else {
return save_(files[0], toastId)
return save_(files[0])
}
}

View File

@ -31,7 +31,7 @@ import {
getArtifactOfTypes,
getArtifactsOfTypes,
getCapCodeRef,
getSweepEdgeCodeRef,
getExtrudeEdgeCodeRef,
getSolid2dCodeRef,
getWallCodeRef,
} from 'lang/std/artifactGraph'
@ -52,7 +52,6 @@ export type Selection = {
| 'end-cap'
| 'point'
| 'edge'
| 'adjacent-edge'
| 'line'
| 'arc'
| 'all'
@ -141,21 +140,12 @@ export async function getEventForSelectWithPoint({
},
}
}
if (_artifact.type === 'sweepEdge') {
const codeRef = getSweepEdgeCodeRef(
if (_artifact.type === 'extrudeEdge') {
const codeRef = getExtrudeEdgeCodeRef(
_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: {
@ -395,16 +385,10 @@ function buildCommonNodeFromSelection(selectionRanges: Selections, i: number) {
}
function nodeHasExtrude(node: CommonASTNode) {
return (
doesPipeHaveCallExp({
calleeName: 'extrude',
...node,
}) ||
doesPipeHaveCallExp({
calleeName: 'revolve',
...node,
})
)
return doesPipeHaveCallExp({
calleeName: 'extrude',
...node,
})
}
function nodeHasClose(node: CommonASTNode) {
@ -414,7 +398,7 @@ function nodeHasClose(node: CommonASTNode) {
})
}
export function canSweepSelection(selection: Selections) {
export function canExtrudeSelection(selection: Selections) {
const commonNodes = selection.codeBasedSelections.map((_, i) =>
buildCommonNodeFromSelection(selection, i)
)
@ -438,14 +422,10 @@ 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(isolatedSelection) &&
!!isSketchPipe(selection) &&
nodeHasClose(commonNode) &&
!nodeHasExtrude(commonNode)
)
@ -464,17 +444,25 @@ export type ResolvedSelectionType = [Selection['type'] | 'other', number]
export function getSelectionType(
selection: Selections
): ResolvedSelectionType[] {
const extrudableCount = selection.codeBasedSelections.filter((_, i) => {
const singleSelection = {
...selection,
codeBasedSelections: [selection.codeBasedSelections[i]],
}
return canExtrudeSelectionItem(singleSelection, 0)
}).length
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)
return extrudableCount === selection.codeBasedSelections.length
? [['extrude-wall', extrudableCount]]
: [['other', selection.codeBasedSelections.length]]
if (foundIndex === -1) {
return [...acc, [type, count]]
} else {
const temp = [...acc]
temp[foundIndex][1] += count
return temp
}
}, [] as ResolvedSelectionType[])
}
export function getSelectionTypeDisplayText(
@ -569,43 +557,14 @@ 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.sweepId,
types: ['sweep'],
key: entry.artifact.extrusionId,
types: ['extrusion'],
},
engineCommandManager.artifactGraph
)

View File

@ -94,16 +94,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
},
{
id: 'revolve',
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' }),
onClick: () => console.error('Revolve not yet implemented'),
icon: 'revolve',
status: DEV ? 'available' : 'kcl-only',
status: 'kcl-only',
title: 'Revolve',
hotkey: 'R',
description:

View File

@ -33,11 +33,7 @@ import {
applyConstraintEqualLength,
setEqualLengthInfo,
} from 'components/Toolbar/EqualLength'
import {
deleteFromSelection,
extrudeSketch,
revolveSketch,
} from 'lang/modifyAst'
import { deleteFromSelection, extrudeSketch } from 'lang/modifyAst'
import { applyFilletToSelection } from 'lang/modifyAst/addFillet'
import { getNodeFromPath } from '../lang/queryAst'
import {
@ -206,7 +202,6 @@ 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'] }
| {
@ -315,7 +310,6 @@ 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,
@ -572,53 +566,6 @@ 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
@ -1291,13 +1238,6 @@ 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

266
src/wasm-lib/Cargo.lock generated
View File

@ -78,26 +78,20 @@ checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anyhow"
version = "1.0.89"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
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 0.2.18",
"num-traits",
]
[[package]]
@ -207,7 +201,7 @@ dependencies = [
"libm",
"num-bigint",
"num-integer",
"num-traits 0.2.18",
"num-traits",
"serde",
]
@ -264,10 +258,10 @@ dependencies = [
"bitvec",
"chrono",
"hex",
"indexmap 2.5.0",
"indexmap 2.2.5",
"js-sys",
"once_cell",
"rand 0.8.5",
"rand",
"serde",
"serde_bytes",
"serde_json",
@ -332,18 +326,6 @@ 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"
@ -353,7 +335,7 @@ dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits 0.2.18",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.4",
@ -514,7 +496,7 @@ dependencies = [
"futures",
"is-terminal",
"itertools 0.10.5",
"num-traits 0.2.18",
"num-traits",
"once_cell",
"oorandom",
"plotters",
@ -690,7 +672,7 @@ dependencies = [
[[package]]
name = "derive-docs"
version = "0.1.27"
version = "0.1.26"
dependencies = [
"Inflector",
"anyhow",
@ -772,26 +754,6 @@ 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"
@ -808,16 +770,6 @@ 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"
@ -882,12 +834,6 @@ 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"
@ -1067,7 +1013,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http 0.2.12",
"indexmap 2.5.0",
"indexmap 2.2.5",
"slab",
"tokio",
"tokio-util",
@ -1265,7 +1211,7 @@ checksum = "99314c8a2152b8ddb211f924cdae532d8c5e4c8bb54728e12fff1b0cd5963a10"
dependencies = [
"bytemuck",
"byteorder-lite",
"num-traits 0.2.18",
"num-traits",
"png",
]
@ -1293,13 +1239,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.5.0"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown 0.14.3",
"serde",
]
[[package]]
@ -1400,10 +1345,10 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.16"
version = "0.2.14"
dependencies = [
"anyhow",
"approx 0.5.1",
"approx",
"async-recursion",
"async-trait",
"base64 0.22.1",
@ -1423,12 +1368,10 @@ 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",
@ -1474,7 +1417,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.11"
version = "0.1.10"
dependencies = [
"anyhow",
"hyper",
@ -1506,7 +1449,7 @@ dependencies = [
"mime_guess",
"parse-display",
"phonenumber",
"rand 0.8.5",
"rand",
"reqwest",
"reqwest-conditional-middleware",
"reqwest-middleware",
@ -1524,54 +1467,6 @@ 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"
@ -1705,12 +1600,6 @@ 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"
@ -1750,7 +1639,7 @@ checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
dependencies = [
"autocfg",
"num-integer",
"num-traits 0.2.18",
"num-traits",
]
[[package]]
@ -1765,16 +1654,7 @@ version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"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",
"num-traits",
]
[[package]]
@ -1797,9 +1677,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.20.0"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ea5043e58958ee56f3e15a90aee535795cd7dfd319846288d93c5b57d85cbe"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "oncemutex"
@ -1834,7 +1714,7 @@ dependencies = [
"lazy_static",
"percent-encoding",
"pin-project",
"rand 0.8.5",
"rand",
"thiserror",
]
@ -1993,7 +1873,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45"
dependencies = [
"num-traits 0.2.18",
"num-traits",
"plotters-backend",
"plotters-svg",
"wasm-bindgen",
@ -2048,9 +1928,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_assertions"
version = "1.4.1"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d"
checksum = "af7cee1a6c8a5b9208b3cb1061f10c0cb689087b3d8ce85fb9d2dd7a29b6ba66"
dependencies = [
"diff",
"yansi",
@ -2091,9 +1971,9 @@ dependencies = [
[[package]]
name = "pyo3"
version = "0.22.3"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15ee168e30649f7f234c3d49ef5a7a6cbf5134289bc46c29ff3155fa3221c225"
checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433"
dependencies = [
"cfg-if",
"indoc",
@ -2109,9 +1989,9 @@ dependencies = [
[[package]]
name = "pyo3-build-config"
version = "0.22.3"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e61cef80755fe9e46bb8a0b8f20752ca7676dcc07a5277d8b7768c6172e529b3"
checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8"
dependencies = [
"once_cell",
"target-lexicon",
@ -2119,9 +1999,9 @@ dependencies = [
[[package]]
name = "pyo3-ffi"
version = "0.22.3"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67ce096073ec5405f5ee2b8b31f03a68e02aa10d5d4f565eca04acc41931fa1c"
checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6"
dependencies = [
"libc",
"pyo3-build-config",
@ -2129,9 +2009,9 @@ dependencies = [
[[package]]
name = "pyo3-macros"
version = "0.22.3"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2440c6d12bc8f3ae39f1e775266fa5122fd0c8891ce7520fa6048e683ad3de28"
checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206"
dependencies = [
"proc-macro2",
"pyo3-macros-backend",
@ -2141,9 +2021,9 @@ dependencies = [
[[package]]
name = "pyo3-macros-backend"
version = "0.22.3"
version = "0.22.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be962f0e06da8f8465729ea2cb71a416d2257dff56cbe40a70d3e62a93ae5d1"
checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -2176,19 +2056,6 @@ 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"
@ -2197,7 +2064,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.4",
"rand_core",
]
[[package]]
@ -2207,24 +2074,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
"rand_core",
]
[[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"
@ -2254,15 +2106,6 @@ 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"
@ -2448,7 +2291,7 @@ checksum = "e09bbcb5003282bcb688f0bae741b278e9c7e8f378f561522c9806c58e075d9b"
dependencies = [
"anyhow",
"chrono",
"rand 0.8.5",
"rand",
]
[[package]]
@ -2535,9 +2378,9 @@ dependencies = [
[[package]]
name = "rustls-native-certs"
version = "0.8.0"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a"
checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792"
dependencies = [
"openssl-probe",
"rustls-pemfile 2.1.1",
@ -2567,9 +2410,9 @@ dependencies = [
[[package]]
name = "rustls-pki-types"
version = "1.8.0"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0"
checksum = "868e20fada228fefaf6b652e00cc73623d54f8171e7352c18bb281571f2d92da"
[[package]]
name = "rustls-webpki"
@ -2707,9 +2550,9 @@ dependencies = [
[[package]]
name = "serde_bytes"
version = "0.11.15"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
checksum = "8b8497c313fd43ab992087548117643f6fcd935cbf36f176ffda0aacf9591734"
dependencies = [
"serde",
]
@ -2742,7 +2585,7 @@ version = "1.0.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8"
dependencies = [
"indexmap 2.5.0",
"indexmap 2.2.5",
"itoa",
"memchr",
"ryu",
@ -3215,9 +3058,9 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.24.0"
version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd"
dependencies = [
"futures-util",
"log",
@ -3270,7 +3113,7 @@ version = "0.22.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
dependencies = [
"indexmap 2.5.0",
"indexmap 2.2.5",
"serde",
"serde_spanned",
"toml_datetime",
@ -3453,9 +3296,9 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.24.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8"
dependencies = [
"byteorder",
"bytes",
@ -3463,7 +3306,7 @@ dependencies = [
"http 1.1.0",
"httparse",
"log",
"rand 0.8.5",
"rand",
"rustls 0.23.7",
"rustls-pki-types",
"sha1",
@ -3727,7 +3570,6 @@ dependencies = [
"js-sys",
"kcl-lib",
"kittycad",
"kittycad-modeling-cmds",
"pretty_assertions",
"reqwest",
"serde_json",
@ -3997,9 +3839,9 @@ dependencies = [
[[package]]
name = "yansi"
version = "1.0.1"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]]
name = "zerocopy"
@ -4037,6 +3879,6 @@ dependencies = [
"crc32fast",
"crossbeam-utils",
"displaydoc",
"indexmap 2.5.0",
"indexmap 2.2.5",
"thiserror",
]

View File

@ -27,8 +27,7 @@ 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 }
kittycad-modeling-cmds = { workspace = true }
pretty_assertions = "1.4.1"
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.26", default-features = false }
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.8"
@ -73,7 +72,6 @@ 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"

View File

@ -1,7 +1,7 @@
[package]
name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.27"
version = "0.1.26"
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.20.0"
once_cell = "1.19.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.89"
anyhow = "1.0.88"
expectorate = "1.1.0"
pretty_assertions = "1.4.1"
pretty_assertions = "1.4.0"
rustfmt-wrapper = "0.2.1"

View File

@ -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 != "ExecState" && ty_string != "Args" {
if 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,12 +387,11 @@ 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(exec_state, args))
Box::pin(#fn_name_ident(args))
}
impl #docs_crate::StdLibFn for #name_ident
@ -663,9 +662,6 @@ 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();
}

View File

@ -85,32 +85,6 @@ 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(

View File

@ -44,17 +44,15 @@ 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(exec_state, args))
Box::pin(someFn(args))
}
impl crate::docs::StdLibFn for SomeFn {

View File

@ -44,17 +44,15 @@ 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(exec_state, args))
Box::pin(someFn(args))
}
impl crate::docs::StdLibFn for SomeFn {

View File

@ -77,17 +77,15 @@ 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(exec_state, args))
Box::pin(show(args))
}
impl crate::docs::StdLibFn for Show {

View File

@ -44,17 +44,15 @@ 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(exec_state, args))
Box::pin(show(args))
}
impl crate::docs::StdLibFn for Show {

View File

@ -78,17 +78,15 @@ 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(exec_state, args))
Box::pin(my_func(args))
}
impl crate::docs::StdLibFn for MyFunc {

View File

@ -78,17 +78,15 @@ 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(exec_state, args))
Box::pin(line_to(args))
}
impl crate::docs::StdLibFn for LineTo {

View File

@ -77,17 +77,15 @@ 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(exec_state, args))
Box::pin(min(args))
}
impl crate::docs::StdLibFn for Min {

View File

@ -44,17 +44,15 @@ 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(exec_state, args))
Box::pin(show(args))
}
impl crate::docs::StdLibFn for Show {

Some files were not shown because too many files have changed in this diff Show More