Compare commits
10 Commits
pierremtb/
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
f70847c407 | |||
9510849a3a | |||
5b208356b4 | |||
84209e764e | |||
2b85e7abd6 | |||
9155a5efc8 | |||
2ccc27112a | |||
ee160b67f4 | |||
5117b6f5d6 | |||
bc8a7a364d |
6
.github/workflows/build-apps.yml
vendored
@ -365,7 +365,7 @@ jobs:
|
||||
- name: Set more complete nightly release notes
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: |
|
||||
# Note: preferred going this way instead of a full clone in the checkout step,
|
||||
# Note: prefered going this way instead of a full clone in the checkout step,
|
||||
# see https://github.com/actions/checkout/issues/1471
|
||||
git fetch --prune --unshallow --tags
|
||||
export TAG="nightly-${VERSION}"
|
||||
@ -394,10 +394,6 @@ jobs:
|
||||
parent: false
|
||||
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: yarn files:invalidate-bucket:nightly
|
||||
|
||||
- name: Tag nightly commit
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
|
6
.github/workflows/publish-apps-release.yml
vendored
@ -126,7 +126,11 @@ jobs:
|
||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
run: yarn files:invalidate-bucket
|
||||
run: |
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
|
1
.gitignore
vendored
@ -61,7 +61,6 @@ Mac_App_Distribution.provisionprofile
|
||||
*.tsbuildinfo
|
||||
src/wasm-lib/pkg
|
||||
|
||||
.eslintcache
|
||||
venv
|
||||
.vite/
|
||||
|
||||
|
43
INSTALL.md
@ -1,43 +0,0 @@
|
||||
# Setting Up Zoo Modeling App
|
||||
|
||||
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
|
||||
|
||||
## Windows
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
|
||||
|
||||
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
|
||||
|
||||
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
|
||||
|
||||
## macOS
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
|
||||
|
||||
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
||||
|
||||
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
|
||||
|
||||
|
||||
## Linux
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
|
||||
|
||||
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
||||
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install libfuse2
|
||||
```
|
||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||
|
||||
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||
```bash
|
||||
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
||||
|
||||
3. You can double-click on the AppImage to run it, or in a terminal with this command:
|
||||
```bash
|
||||
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
@ -78970,7 +78970,7 @@
|
||||
"model = import(\"tests/inputs/cube.gltf\")",
|
||||
"model = import(\"tests/inputs/cube.sldprt\")",
|
||||
"model = import(\"tests/inputs/cube.step\")",
|
||||
"import height, buildSketch from \"common.kcl\"\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])"
|
||||
"import height, buildSketch from 'common.kcl'\n\nplane = 'XZ'\nmargin = 2\ns1 = buildSketch(plane, [0, 0])\ns2 = buildSketch(plane, [0, height() + margin])"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
topAng = 30
|
||||
bottomAng = 25
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
@ -474,14 +474,12 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.type('bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in gutter
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(
|
||||
page.getByText('Tag names must not be empty').first()
|
||||
).toBeVisible()
|
||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
|
@ -7,7 +7,6 @@ export class ToolbarFixture {
|
||||
|
||||
extrudeButton!: Locator
|
||||
loftButton!: Locator
|
||||
shellButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
@ -29,7 +28,6 @@ export class ToolbarFixture {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.loftButton = page.getByTestId('loft')
|
||||
this.shellButton = page.getByTestId('shell')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
|
@ -768,166 +768,3 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const shellPointAndClickCapCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
]
|
||||
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||
app,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
extrude001 = extrude(30, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||
})
|
||||
|
||||
if (!shouldPreselect) {
|
||||
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
} else {
|
||||
await test.step(`Preselect the cap`, async () => {
|
||||
await clickOnCap()
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
}
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [shellDeclaration],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Shell point-and-click wall', async ({
|
||||
app,
|
||||
page,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-20, 20], %)
|
||||
|> xLine(40, %)
|
||||
|> yLine(-60, %)
|
||||
|> xLine(-40, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(40, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 580, y: 180 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||
const formattedOutLastLine = '}, extrude001)'
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await page.keyboard.down('Shift')
|
||||
await clickOnWall()
|
||||
await app.page.waitForTimeout(500)
|
||||
await page.keyboard.up('Shift')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap, 1 face',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(mutatedCode)
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [formattedOutLastLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
@ -136,335 +136,6 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const errorDir = join(dir, 'broken-code')
|
||||
await fsp.mkdir(errorDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('broken-code-test.kcl'),
|
||||
join(errorDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening broken code project should clear the scene and show the error', async () => {
|
||||
// Go back home.
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
|
||||
await page.getByText('broken-code').click()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
const crypticErrorText = `Expected a tag declarator`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
|
||||
// black pixel means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const emptyDir = join(dir, 'empty')
|
||||
await fsp.mkdir(emptyDir, { recursive: true })
|
||||
await fsp.writeFile(join(emptyDir, 'main.kcl'), '')
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('empty')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening empty code project should clear the scene', async () => {
|
||||
// Go back home.
|
||||
await expect(page.getByText('empty')).toBeVisible()
|
||||
|
||||
await page.getByText('empty').click()
|
||||
|
||||
// Ensure the code is empty.
|
||||
await expect(u.codeLocator).toContainText('')
|
||||
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
|
||||
|
||||
// planes colors means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open empty file, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
|
||||
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '')
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step('creating a empty file should clear the scene', async () => {
|
||||
// open the file pane.
|
||||
await page.getByTestId('files-pane-button').click()
|
||||
|
||||
// OPen the other file.
|
||||
const file = page.getByRole('button', { name: 'empty.kcl' })
|
||||
await expect(file).toBeVisible()
|
||||
|
||||
await file.click()
|
||||
|
||||
// planes colors means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
|
||||
// Ensure the code is empty.
|
||||
await expect(u.codeLocator).toContainText('')
|
||||
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('broken-code-test.kcl'),
|
||||
join(bracketDir, 'broken-code-test.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step('opening broken code file should clear the scene and show the error', async () => {
|
||||
// open the file pane.
|
||||
await page.getByTestId('files-pane-button').click()
|
||||
|
||||
// OPen the other file.
|
||||
const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
|
||||
await expect(file).toBeVisible()
|
||||
|
||||
await file.click()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
const crypticErrorText = `Expected a tag declarator`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
|
||||
// black pixel means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'when code with error first loads you get errors in console',
|
||||
{ tag: '@electron' },
|
||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@ -81,7 +81,6 @@
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:stop": "kill-port 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
@ -96,8 +95,6 @@
|
||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
@ -161,7 +158,6 @@
|
||||
"@electron/rebuild": "^3.6.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
@ -174,7 +170,7 @@
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
@ -211,6 +207,7 @@
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
|
@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
base_dir="/releases/modeling-app"
|
||||
if [[ $1 = "--nightly" ]]; then
|
||||
base_dir="/releases/modeling-app/nightly"
|
||||
fi
|
||||
|
||||
echo "Invalidating json and yml files at $base_dir in the download bucket"
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async
|
65
src/App.tsx
@ -3,7 +3,7 @@ import { useHotKeyListener } from './hooks/useHotKeyListener'
|
||||
import { Stream } from './components/Stream'
|
||||
import { AppHeader } from './components/AppHeader'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
||||
import { useLoaderData, useLocation, useNavigate } from 'react-router-dom'
|
||||
import { type IndexLoaderData } from 'lib/types'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
@ -22,7 +22,14 @@ import Gizmo from 'components/Gizmo'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import { UnitsMenu } from 'components/UnitsMenu'
|
||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
||||
import { homeDefaultStatusBarItems } from 'components/statusBar/homeDefaultStatusBarItems'
|
||||
import { StatusBar } from 'components/StatusBar'
|
||||
import { useModelStateStatus } from 'components/ModelStateIndicator'
|
||||
import { useNetworkHealthStatus } from 'components/NetworkHealthIndicator'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { xStateValueToString } from 'lib/xStateValueToString'
|
||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||
import { useNetworkMachineStatus } from 'components/NetworkMachineIndicator'
|
||||
maybeWriteToDisk()
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
@ -31,11 +38,10 @@ export function App() {
|
||||
const { project, file } = useLoaderData() as IndexLoaderData
|
||||
useRefreshSettings(PATHS.FILE + 'SETTINGS')
|
||||
const navigate = useNavigate()
|
||||
const location = useLocation()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
const { onProjectOpen } = useLspContext()
|
||||
// We need the ref for the outermost div so we can screenshot the app for
|
||||
// the coredump.
|
||||
const ref = useRef<HTMLDivElement>(null)
|
||||
const { state: modelingState, streamRef } = useModelingContext()
|
||||
|
||||
const projectName = project?.name || null
|
||||
const projectPath = project?.path || null
|
||||
@ -77,21 +83,44 @@ export function App() {
|
||||
useEngineConnectionSubscriptions()
|
||||
|
||||
return (
|
||||
<div className="relative h-full flex flex-col" ref={ref}>
|
||||
<AppHeader
|
||||
className={'transition-opacity transition-duration-75 ' + paneOpacity}
|
||||
project={{ project, file }}
|
||||
enableMenu={true}
|
||||
<div className="h-screen flex flex-col overflow-hidden select-none">
|
||||
<div className="relative flex flex-1 flex-col" ref={streamRef}>
|
||||
<AppHeader
|
||||
className={'transition-opacity transition-duration-75 ' + paneOpacity}
|
||||
project={{ project, file }}
|
||||
enableMenu={true}
|
||||
/>
|
||||
<ModalContainer />
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<Stream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<UnitsMenu />
|
||||
<Gizmo />
|
||||
<CameraProjectionToggle />
|
||||
</LowerRightControls>
|
||||
</div>
|
||||
<StatusBar
|
||||
globalItems={[
|
||||
useNetworkHealthStatus(),
|
||||
useNetworkMachineStatus(),
|
||||
...homeDefaultStatusBarItems({ coreDumpManager, location }),
|
||||
]}
|
||||
localItems={[
|
||||
{
|
||||
id: 'modeling-state',
|
||||
element: 'text',
|
||||
label:
|
||||
modelingState.value instanceof Object
|
||||
? xStateValueToString(modelingState.value) ?? ''
|
||||
: modelingState.value,
|
||||
toolTip: {
|
||||
children: 'The current state of the modeler',
|
||||
},
|
||||
},
|
||||
useModelStateStatus(),
|
||||
]}
|
||||
/>
|
||||
<ModalContainer />
|
||||
<ModelingSidebar paneOpacity={paneOpacity} />
|
||||
<Stream />
|
||||
{/* <CamToggle /> */}
|
||||
<LowerRightControls coreDumpManager={coreDumpManager}>
|
||||
<UnitsMenu />
|
||||
<Gizmo />
|
||||
<CameraProjectionToggle />
|
||||
</LowerRightControls>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -701,7 +701,8 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
|
||||
const sg = sketchFromKclValue(
|
||||
kclManager.programMemory.get(variableDeclarationName),
|
||||
@ -901,9 +902,10 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -911,7 +913,7 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
@ -941,7 +943,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -990,7 +992,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
if (sketchInit.type !== 'PipeExpression') {
|
||||
return
|
||||
@ -1056,9 +1058,10 @@ export class SceneEntities {
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
|
||||
// startSketchOn already exists
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -1066,7 +1069,7 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
@ -1096,7 +1099,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -1152,7 +1155,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateCenterRectangleSketch(
|
||||
@ -1221,11 +1224,12 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
createCallExpressionStdLib('circle', [
|
||||
createObjectExpression({
|
||||
@ -1267,7 +1271,7 @@ export class SceneEntities {
|
||||
)
|
||||
let modded = structuredClone(truncatedAst)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
||||
@ -1335,7 +1339,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
let modded = structuredClone(_ast)
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
@ -2056,7 +2060,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||
const sg = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
@ -2081,7 +2085,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
])
|
||||
}
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
.init as PipeExpression
|
||||
).body.push(newSegment)
|
||||
// update source ranges to section we just added.
|
||||
@ -2092,19 +2096,19 @@ function prepareTruncatedMemoryAndAst(
|
||||
const updatedSrcRangeAst = pResult.program
|
||||
|
||||
const lastPipeItem = (
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
||||
.declarations[0].init as PipeExpression
|
||||
).body.slice(-1)[0]
|
||||
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
.init as PipeExpression
|
||||
).body.slice(-1)[0].start = lastPipeItem.start
|
||||
|
||||
_ast.end = lastPipeItem.end
|
||||
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
|
||||
varDec.end = lastPipeItem.end
|
||||
const declarator = varDec.declaration
|
||||
const declarator = varDec.declarations[0]
|
||||
declarator.end = lastPipeItem.end
|
||||
const init = declarator.init as Node<PipeExpression>
|
||||
init.end = lastPipeItem.end
|
||||
@ -2141,7 +2145,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
if (node.type !== 'VariableDeclaration') {
|
||||
continue
|
||||
}
|
||||
const name = node.declaration.id.name
|
||||
const name = node.declarations[0].id.name
|
||||
const memoryItem = programMemory.get(name)
|
||||
if (!memoryItem) {
|
||||
continue
|
||||
|
@ -169,11 +169,11 @@ export function useCalc({
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declaration.id?.name === '__result__'
|
||||
a.declarations?.[0]?.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
resultDeclaration?.declarations?.[0]?.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
|
@ -636,6 +636,16 @@ const CustomIconMap = {
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
loading: (
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12.5001 6.25839C11.76 5.76392 10.89 5.5 10 5.5V4.5C11.0878 4.5 12.1512 4.82257 13.0556 5.42692C13.9601 6.03126 14.6651 6.89025 15.0813 7.89524C15.4976 8.90023 15.6065 10.0061 15.3943 11.073C15.1821 12.1399 14.6583 13.1199 13.8891 13.8891C13.1199 14.6583 12.1399 15.1821 11.073 15.3943C10.0061 15.6065 8.90023 15.4976 7.89524 15.0813C6.89025 14.6651 6.03126 13.9601 5.42692 13.0556C4.82257 12.1512 4.5 11.0878 4.5 10H5.5C5.5 10.89 5.76392 11.76 6.25839 12.5001C6.75285 13.2401 7.45566 13.8169 8.27792 14.1575C9.10019 14.4981 10.005 14.5872 10.8779 14.4135C11.7508 14.2399 12.5526 13.8113 13.182 13.182C13.8113 12.5526 14.2399 11.7508 14.4135 10.8779C14.5872 10.005 14.4981 9.10019 14.1575 8.27792C13.8169 7.45566 13.2401 6.75285 12.5001 6.25839Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
lockClosed: (
|
||||
<svg
|
||||
viewBox="0 0 20 20"
|
||||
|
@ -266,7 +266,6 @@ const FileTreeItem = ({
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
onFileOpen(fileOrDir.path, project?.path || null)
|
||||
kclManager.switchedFiles = true
|
||||
|
||||
// Open kcl files
|
||||
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||
|
@ -1,141 +1,14 @@
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
|
||||
import { HelpMenu } from './HelpMenu'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import toast from 'react-hot-toast'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
||||
import { ModelStateIndicator } from './ModelStateIndicator'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
||||
export function LowerRightControls({
|
||||
children,
|
||||
coreDumpManager,
|
||||
}: {
|
||||
children?: React.ReactNode
|
||||
coreDumpManager?: CoreDumpManager
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const filePath = useAbsoluteFilePath()
|
||||
|
||||
const linkOverrideClassName =
|
||||
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
|
||||
|
||||
function reportbug(event: {
|
||||
preventDefault: () => void
|
||||
stopPropagation: () => void
|
||||
}) {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
|
||||
if (!coreDumpManager) {
|
||||
// open default reporting option
|
||||
openWindow(
|
||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||
).catch(reportRejection)
|
||||
} else {
|
||||
toast
|
||||
.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Preparing bug report...',
|
||||
success: 'Bug report opened in new window',
|
||||
error: 'Unable to export a core dump. Using default reporting.',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err: Error) => {
|
||||
if (err) {
|
||||
openWindow(
|
||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||
).catch(reportRejection)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||
<section className="absolute bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||
{children}
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
||||
>
|
||||
v{APP_VERSION}
|
||||
</a>
|
||||
<a
|
||||
onClick={reportbug}
|
||||
href="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<CustomIcon
|
||||
name="bug"
|
||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
||||
/>
|
||||
<Tooltip position="top" contentClassName="text-xs">
|
||||
Report a bug
|
||||
</Tooltip>
|
||||
</a>
|
||||
<Link
|
||||
to={
|
||||
location.pathname.includes(PATHS.FILE)
|
||||
? filePath + PATHS.TELEMETRY + '?tab=project'
|
||||
: PATHS.HOME + PATHS.TELEMETRY
|
||||
}
|
||||
data-testid="telemetry-link"
|
||||
>
|
||||
<CustomIcon
|
||||
name="stopwatch"
|
||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
||||
/>
|
||||
<span className="sr-only">Telemetry</span>
|
||||
<Tooltip position="top" contentClassName="text-xs">
|
||||
Telemetry
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<Link
|
||||
to={
|
||||
location.pathname.includes(PATHS.FILE)
|
||||
? filePath + PATHS.SETTINGS + '?tab=project'
|
||||
: PATHS.HOME + PATHS.SETTINGS
|
||||
}
|
||||
data-testid="settings-link"
|
||||
>
|
||||
<CustomIcon
|
||||
name="settings"
|
||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
||||
/>
|
||||
<span className="sr-only">Settings</span>
|
||||
<Tooltip position="top" contentClassName="text-xs">
|
||||
Settings
|
||||
</Tooltip>
|
||||
</Link>
|
||||
<NetworkMachineIndicator className={linkOverrideClassName} />
|
||||
{!location.pathname.startsWith(PATHS.HOME) && (
|
||||
<NetworkHealthIndicator />
|
||||
)}
|
||||
<HelpMenu />
|
||||
</menu>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,39 @@
|
||||
import { useEngineCommands } from './EngineCommands'
|
||||
import { Spinner } from './Spinner'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||
|
||||
export const useModelStateStatus = (): StatusBarItemType => {
|
||||
const [commands] = useEngineCommands()
|
||||
const lastCommandType = commands[commands.length - 1]?.type
|
||||
|
||||
let icon: StatusBarItemType['icon'] = 'loading'
|
||||
const baseDataTestId = 'model-state-indicator'
|
||||
let dataTestId = baseDataTestId
|
||||
|
||||
if (lastCommandType === 'receive-reliable') {
|
||||
icon = 'checkmark'
|
||||
dataTestId = `${baseDataTestId}-receive-reliable`
|
||||
} else if (lastCommandType === 'execution-done') {
|
||||
icon = 'checkmark'
|
||||
dataTestId = `${baseDataTestId}-execution-done`
|
||||
} else if (lastCommandType === 'export-done') {
|
||||
icon = 'checkmark'
|
||||
dataTestId = `${baseDataTestId}-export-done`
|
||||
}
|
||||
|
||||
return {
|
||||
id: 'model-state-indicator',
|
||||
label: '',
|
||||
icon,
|
||||
toolTip: {
|
||||
children: 'Model state indicator',
|
||||
},
|
||||
element: 'button',
|
||||
onClick: () => {},
|
||||
'data-testid': dataTestId,
|
||||
}
|
||||
}
|
||||
|
||||
export const ModelStateIndicator = () => {
|
||||
const [commands] = useEngineCommands()
|
||||
|
@ -51,7 +51,6 @@ import {
|
||||
Selections,
|
||||
updateSelections,
|
||||
canLoftSelection,
|
||||
canShellSelection,
|
||||
} from 'lib/selections'
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
@ -70,7 +69,6 @@ import {
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||
import {
|
||||
doesSceneHaveExtrudedSketch,
|
||||
doesSceneHaveSweepableSketch,
|
||||
getNodePathFromSourceRange,
|
||||
isSingleCursorInPipe,
|
||||
@ -101,6 +99,7 @@ type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
context: ContextFrom<T>
|
||||
send: Prop<Actor<T>, 'send'>
|
||||
streamRef: React.RefObject<HTMLDivElement>
|
||||
}
|
||||
|
||||
export const ModelingMachineContext = createContext(
|
||||
@ -587,24 +586,6 @@ export const ModelingMachineProvider = ({
|
||||
if (err(canLoft)) return false
|
||||
return canLoft
|
||||
},
|
||||
'has valid shell selection': ({
|
||||
context: { selectionRanges },
|
||||
event,
|
||||
}) => {
|
||||
const hasNoSelection =
|
||||
selectionRanges.graphSelections.length === 0 ||
|
||||
isRangeBetweenCharacters(selectionRanges) ||
|
||||
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||
|
||||
if (hasNoSelection) {
|
||||
return doesSceneHaveExtrudedSketch(kclManager.ast)
|
||||
}
|
||||
|
||||
const canShell = canShellSelection(selectionRanges)
|
||||
console.log('canShellSelection', canShellSelection(selectionRanges))
|
||||
if (err(canShell)) return false
|
||||
return canShell
|
||||
},
|
||||
'has valid selection for deletion': ({
|
||||
context: { selectionRanges },
|
||||
}) => {
|
||||
@ -1225,13 +1206,10 @@ export const ModelingMachineProvider = ({
|
||||
state: modelingState,
|
||||
context: modelingState.context,
|
||||
send: modelingSend,
|
||||
streamRef,
|
||||
}}
|
||||
>
|
||||
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
|
||||
since realistically it won't ever have generic children that isn't app.tsx */}
|
||||
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
|
||||
{children}
|
||||
</div>
|
||||
{children}
|
||||
</ModelingMachineContext.Provider>
|
||||
)
|
||||
}
|
||||
|
@ -40,9 +40,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-100 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
||||
<Menu.Item>
|
||||
<button
|
||||
onClick={() => {
|
||||
kclManager.format().catch(reportRejection)
|
||||
}}
|
||||
onClick={() => kclManager.format()}
|
||||
className={styles.button}
|
||||
>
|
||||
<span>Format code</span>
|
||||
|
@ -6,6 +6,7 @@ import { useNetworkContext } from '../hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from '../hooks/useNetworkStatus'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||
|
||||
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
||||
[NetworkHealthState.Ok]: 'Connected',
|
||||
@ -64,14 +65,28 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
|
||||
},
|
||||
}
|
||||
|
||||
const overallConnectionStateIcon: Record<
|
||||
NetworkHealthState,
|
||||
ActionIconProps['icon']
|
||||
> = {
|
||||
const overallConnectionStateIcon = {
|
||||
[NetworkHealthState.Ok]: 'network',
|
||||
[NetworkHealthState.Weak]: 'network',
|
||||
[NetworkHealthState.Issue]: 'networkCrossedOut',
|
||||
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
|
||||
} as const
|
||||
|
||||
export const useNetworkHealthStatus = (): StatusBarItemType => {
|
||||
const { overallState } = useNetworkContext()
|
||||
|
||||
return {
|
||||
id: 'network-health',
|
||||
label: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
|
||||
hideLabel: true,
|
||||
element: 'popover',
|
||||
className: overallConnectionStateColor[overallState].icon,
|
||||
toolTip: {
|
||||
children: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
|
||||
},
|
||||
icon: overallConnectionStateIcon[overallState],
|
||||
popoverContent: <NetworkHealthPopoverContent />,
|
||||
}
|
||||
}
|
||||
|
||||
export const NetworkHealthIndicator = () => {
|
||||
@ -109,81 +124,95 @@ export const NetworkHealthIndicator = () => {
|
||||
Network health ({NETWORK_HEALTH_TEXT[overallState]})
|
||||
</Tooltip>
|
||||
</Popover.Button>
|
||||
<Popover.Panel
|
||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||
data-testid="network-popover"
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
|
||||
>
|
||||
<h2 className="text-sm font-sans font-normal">Network health</h2>
|
||||
<p
|
||||
data-testid="network"
|
||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||
>
|
||||
{NETWORK_HEALTH_TEXT[overallState]}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{Object.keys(steps).map((name) => (
|
||||
<li
|
||||
key={name}
|
||||
className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}
|
||||
>
|
||||
<div className="flex items-center text-left gap-1">
|
||||
<p className="flex-1">{name}</p>
|
||||
{internetConnected ? (
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
icon={
|
||||
hasIssueToIcon[
|
||||
String(issues[name as ConnectingTypeGroup])
|
||||
]
|
||||
}
|
||||
iconClassName={
|
||||
hasIssueToIconColors[
|
||||
String(issues[name as ConnectingTypeGroup])
|
||||
].icon
|
||||
}
|
||||
bgClassName={
|
||||
'rounded-sm ' +
|
||||
hasIssueToIconColors[
|
||||
String(issues[name as ConnectingTypeGroup])
|
||||
].bg
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ActionIcon
|
||||
icon={hasIssueToIcon.true}
|
||||
bgClassName={hasIssueToIconColors.true.bg}
|
||||
iconClassName={hasIssueToIconColors.true.icon}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{issues[name as ConnectingTypeGroup] && (
|
||||
<button
|
||||
onClick={toSync(async () => {
|
||||
await navigator.clipboard.writeText(
|
||||
JSON.stringify(error, null, 2) || ''
|
||||
)
|
||||
setHasCopied(true)
|
||||
setTimeout(() => setHasCopied(false), 5000)
|
||||
}, reportRejection)}
|
||||
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
|
||||
>
|
||||
{hasCopied ? 'Copied' : 'Copy Error'}
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
|
||||
iconClassName="text-inherit dark:text-inherit"
|
||||
bgClassName="!bg-transparent"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<Popover.Panel>
|
||||
<NetworkHealthPopoverContent />
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
const NetworkHealthPopoverContent = () => {
|
||||
const {
|
||||
hasIssues,
|
||||
overallState,
|
||||
internetConnected,
|
||||
steps,
|
||||
issues,
|
||||
error,
|
||||
setHasCopied,
|
||||
hasCopied,
|
||||
} = useNetworkContext()
|
||||
|
||||
return (
|
||||
<div
|
||||
className="absolute left-2 bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||
data-testid="network-popover"
|
||||
>
|
||||
<div
|
||||
className={`flex items-center justify-between p-2 rounded-t-sm ${overallConnectionStateColor[overallState].bg} ${overallConnectionStateColor[overallState].icon}`}
|
||||
>
|
||||
<h2 className="text-sm font-sans font-normal">Network health</h2>
|
||||
<p
|
||||
data-testid="network"
|
||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||
>
|
||||
{NETWORK_HEALTH_TEXT[overallState]}
|
||||
</p>
|
||||
</div>
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{Object.keys(steps).map((name) => (
|
||||
<li key={name} className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<div className="flex items-center text-left gap-1">
|
||||
<p className="flex-1">{name}</p>
|
||||
{internetConnected ? (
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
icon={
|
||||
hasIssueToIcon[String(issues[name as ConnectingTypeGroup])]
|
||||
}
|
||||
iconClassName={
|
||||
hasIssueToIconColors[
|
||||
String(issues[name as ConnectingTypeGroup])
|
||||
].icon
|
||||
}
|
||||
bgClassName={
|
||||
'rounded-sm ' +
|
||||
hasIssueToIconColors[
|
||||
String(issues[name as ConnectingTypeGroup])
|
||||
].bg
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<ActionIcon
|
||||
icon={hasIssueToIcon.true}
|
||||
bgClassName={hasIssueToIconColors.true.bg}
|
||||
iconClassName={hasIssueToIconColors.true.icon}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{issues[name as ConnectingTypeGroup] && (
|
||||
<button
|
||||
onClick={toSync(async () => {
|
||||
await navigator.clipboard.writeText(
|
||||
JSON.stringify(error, null, 2) || ''
|
||||
)
|
||||
setHasCopied(true)
|
||||
setTimeout(() => setHasCopied(false), 5000)
|
||||
}, reportRejection)}
|
||||
className="flex w-fit gap-2 items-center bg-transparent text-sm p-1 py-0 my-0 -mx-1 text-destroy-80 dark:text-destroy-10 hover:bg-transparent border-transparent dark:border-transparent hover:border-destroy-80 dark:hover:border-destroy-80 dark:hover:bg-destroy-80"
|
||||
>
|
||||
{hasCopied ? 'Copied' : 'Copy Error'}
|
||||
<ActionIcon
|
||||
size="lg"
|
||||
icon={hasCopied ? 'clipboardCheckmark' : 'clipboardPlus'}
|
||||
iconClassName="text-inherit dark:text-inherit"
|
||||
bgClassName="!bg-transparent"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { isDesktop } from 'lib/isDesktop'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||
|
||||
export const NetworkMachineIndicator = ({
|
||||
className,
|
||||
@ -27,12 +28,7 @@ export const NetworkMachineIndicator = ({
|
||||
}
|
||||
data-testid="network-machine-toggle"
|
||||
>
|
||||
<CustomIcon name="printer3d" className="w-5 h-5" />
|
||||
{machineCount > 0 && (
|
||||
<p aria-hidden className="flex items-center justify-center text-xs">
|
||||
{machineCount}
|
||||
</p>
|
||||
)}
|
||||
<NetworkMachinesIcon machineCount={machineCount} />
|
||||
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
||||
Network machines ({machineCount}) {reason && `: ${reason}`}
|
||||
</Tooltip>
|
||||
@ -41,50 +37,92 @@ export const NetworkMachineIndicator = ({
|
||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||
data-testid="network-popover"
|
||||
>
|
||||
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
|
||||
<h2 className="text-sm font-sans font-normal">Network machines</h2>
|
||||
<p
|
||||
data-testid="network"
|
||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||
>
|
||||
{machineCount}
|
||||
</p>
|
||||
</div>
|
||||
{machineCount > 0 && (
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{machines.map(
|
||||
(machine: components['schemas']['MachineInfoResponse']) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{`Status: ${machine.state.state
|
||||
.charAt(0)
|
||||
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ` (${machine.state.message})`
|
||||
: ''}
|
||||
{machine.state.state === 'running' && machine.progress
|
||||
? ` (${Math.round(machine.progress)}%)`
|
||||
: ''}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
<NetworkMachinesPopoverContent machines={machines} />
|
||||
</Popover.Panel>
|
||||
</Popover>
|
||||
) : null
|
||||
}
|
||||
|
||||
export const useNetworkMachineStatus = (): StatusBarItemType => {
|
||||
const {
|
||||
noMachinesReason,
|
||||
machines,
|
||||
machines: { length: machineCount },
|
||||
} = useContext(MachineManagerContext)
|
||||
const reason = noMachinesReason()
|
||||
|
||||
return {
|
||||
id: 'network-machines',
|
||||
label: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
|
||||
hideLabel: true,
|
||||
element: 'popover',
|
||||
toolTip: {
|
||||
children: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
|
||||
},
|
||||
icon: 'printer3d',
|
||||
popoverContent: <NetworkMachinesPopoverContent machines={machines} />,
|
||||
}
|
||||
}
|
||||
|
||||
function NetworkMachinesIcon({ machineCount }: { machineCount: number }) {
|
||||
return (
|
||||
<>
|
||||
<CustomIcon name="printer3d" className="w-5 h-5" />
|
||||
{machineCount > 0 && (
|
||||
<p aria-hidden className="flex items-center justify-center text-xs">
|
||||
{machineCount}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function NetworkMachinesPopoverContent({ machines }: { machines: components['schemas']['MachineInfoResponse'][] }) {
|
||||
return (
|
||||
<>
|
||||
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
|
||||
<h2 className="text-sm font-sans font-normal">Network machines</h2>
|
||||
<p
|
||||
data-testid="network"
|
||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||
>
|
||||
{machines.length}
|
||||
</p>
|
||||
</div>
|
||||
{machines.length > 0 && (
|
||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||
{machines.map(
|
||||
(machine: components['schemas']['MachineInfoResponse']) => {
|
||||
return (
|
||||
<li key={machine.id} className={'px-2 py-4 gap-1 last:mb-0 '}>
|
||||
<p className="">{machine.id.toUpperCase()}</p>
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{machine.make_model.model}
|
||||
</p>
|
||||
{machine.extra &&
|
||||
machine.extra.type === 'bambu' &&
|
||||
machine.extra.nozzle_diameter && (
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
Nozzle Diameter: {machine.extra.nozzle_diameter}
|
||||
</p>
|
||||
)}
|
||||
<p className="text-chalkboard-60 dark:text-chalkboard-50 text-xs">
|
||||
{`Status: ${machine.state.state
|
||||
.charAt(0)
|
||||
.toUpperCase()}${machine.state.state.slice(1)}`}
|
||||
{machine.state.state === 'failed' && machine.state.message
|
||||
? ` (${machine.state.message})`
|
||||
: ''}
|
||||
{machine.state.state === 'running' && machine.progress
|
||||
? ` (${Math.round(machine.progress)}%)`
|
||||
: ''}
|
||||
</p>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
|
||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { useLspContext } from './LspProvider'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { engineCommandManager } from 'lib/singletons'
|
||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||
import usePlatform from 'hooks/usePlatform'
|
||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||
@ -68,7 +68,8 @@ function AppLogoLink({
|
||||
data-testid="app-logo"
|
||||
onClick={() => {
|
||||
onProjectClose(file || null, project?.path || null, false)
|
||||
kclManager.switchedFiles = true
|
||||
// Clear the scene.
|
||||
engineCommandManager.clearScene()
|
||||
}}
|
||||
to={PATHS.HOME}
|
||||
className={wrapperClassName + ' hover:before:brightness-110'}
|
||||
@ -189,7 +190,8 @@ function ProjectMenuPopover({
|
||||
className: !isDesktop() ? 'hidden' : '',
|
||||
onClick: () => {
|
||||
onProjectClose(file || null, project?.path || null, true)
|
||||
kclManager.switchedFiles = true
|
||||
// Clear the scene.
|
||||
engineCommandManager.clearScene()
|
||||
},
|
||||
},
|
||||
].filter(
|
||||
|
@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { SettingsFieldInput } from './SettingsFieldInput'
|
||||
import toast from 'react-hot-toast'
|
||||
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
|
||||
import { APP_VERSION } from 'lib/appVersion'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import {
|
||||
createAndOpenNewTutorialProject,
|
||||
@ -25,6 +25,7 @@ import { useLspContext } from 'components/LspProvider'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { PACKAGE_NAME } from 'routes/Settings'
|
||||
|
||||
interface AllSettingsFieldsProps {
|
||||
searchParamTab: SettingsLevel
|
||||
|
148
src/components/StatusBar.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { useEffect } from 'react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||
import Tooltip, { TooltipProps } from './Tooltip'
|
||||
import { ActionIcon } from './ActionIcon'
|
||||
import { Popover } from '@headlessui/react'
|
||||
|
||||
export function StatusBar({
|
||||
globalItems,
|
||||
localItems,
|
||||
}: {
|
||||
globalItems: StatusBarItemType[]
|
||||
localItems: StatusBarItemType[]
|
||||
}) {
|
||||
return (
|
||||
<footer
|
||||
id="statusbar"
|
||||
className="relative z-10 flex justify-between items-center bg-chalkboard-20 dark:bg-chalkboard-90 text-chalkboard-80 dark:text-chalkboard-30 border-t border-t-chalkboard-30 dark:border-t-chalkboard-80"
|
||||
>
|
||||
<menu id="statusbar-globals" className="flex items-stretch">
|
||||
{globalItems.map((item) => (
|
||||
<StatusBarItem key={item.id} {...item} position="left" />
|
||||
))}
|
||||
</menu>
|
||||
<menu id="statusbar-locals" className="flex items-stretch">
|
||||
{localItems.map((item) => (
|
||||
<StatusBarItem key={item.id} {...item} position="right" />
|
||||
))}
|
||||
</menu>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
function StatusBarItem(
|
||||
props: StatusBarItemType & { position: 'left' | 'middle' | 'right' }
|
||||
) {
|
||||
const defaultClassNames = `px-2 py-1 text-xs text-chalkboard-80 dark:text-chalkboard-30 rounded-none border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-80 focus:bg-chalkboard-30 dark:focus:bg-chalkboard-80 hover:text-chalkboard-100 dark:hover:text-chalkboard-10 focustext-chalkboard-100 dark:focus:text-chalkboard-10 focus:outline-none focus-visible:ring-2 focus:ring-primary focus:ring-opacity-50`
|
||||
const tooltipPosition: TooltipProps['position'] =
|
||||
props.position === 'middle' ? 'top' : `top-${props.position}`
|
||||
|
||||
switch (props.element) {
|
||||
case 'button':
|
||||
return (
|
||||
<ActionButton
|
||||
Element="button"
|
||||
iconStart={
|
||||
props.icon && {
|
||||
icon: props.icon,
|
||||
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
|
||||
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||
}
|
||||
}
|
||||
className={defaultClassNames + ' ' + props.className}
|
||||
data-testid={props['data-testid']}
|
||||
>
|
||||
{props.label && (
|
||||
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||
{props.label}
|
||||
</span>
|
||||
)}
|
||||
{props.toolTip && (
|
||||
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||
)}
|
||||
</ActionButton>
|
||||
)
|
||||
case 'popover':
|
||||
return (
|
||||
<Popover className="relative">
|
||||
<Popover.Button
|
||||
as={ActionButton}
|
||||
Element="button"
|
||||
iconStart={
|
||||
props.icon && {
|
||||
icon: props.icon,
|
||||
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
|
||||
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||
}
|
||||
}
|
||||
className={defaultClassNames + ' ' + props.className}
|
||||
data-testid={props['data-testid']}
|
||||
>
|
||||
{props.label && (
|
||||
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||
{props.label}
|
||||
</span>
|
||||
)}
|
||||
{props.toolTip && (
|
||||
<Tooltip
|
||||
{...props.toolTip}
|
||||
wrapperClassName={`${
|
||||
props.toolTip?.wrapperClassName || ''
|
||||
} ui-open:hidden`}
|
||||
position={tooltipPosition}
|
||||
/>
|
||||
)}
|
||||
</Popover.Button>
|
||||
<Popover.Panel>{props.popoverContent}</Popover.Panel>
|
||||
</Popover>
|
||||
)
|
||||
case 'text':
|
||||
return (
|
||||
<div
|
||||
role="tooltip"
|
||||
className={defaultClassNames + ' ' + props.className}
|
||||
>
|
||||
{props.icon && (
|
||||
<ActionIcon
|
||||
icon={props.icon}
|
||||
iconClassName={props.icon === 'loading' ? 'animate-spin' : ''}
|
||||
bgClassName="bg-transparent dark:bg-transparent"
|
||||
/>
|
||||
)}
|
||||
{props.label && (
|
||||
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||
{props.label}
|
||||
</span>
|
||||
)}
|
||||
{props.toolTip && (
|
||||
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<ActionButton
|
||||
Element={props.element}
|
||||
to={props.href}
|
||||
iconStart={
|
||||
props.icon && {
|
||||
icon: props.icon,
|
||||
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||
}
|
||||
}
|
||||
className={defaultClassNames + ' ' + props.className}
|
||||
data-testid={props['data-testid']}
|
||||
>
|
||||
{props.label && (
|
||||
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||
{props.label}
|
||||
</span>
|
||||
)}
|
||||
{props.toolTip && (
|
||||
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||
)}
|
||||
</ActionButton>
|
||||
)
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ type LeftOrRight = 'left' | 'right'
|
||||
type Corner = `${TopOrBottom}-${LeftOrRight}`
|
||||
type TooltipPosition = TopOrBottom | LeftOrRight | Corner
|
||||
|
||||
interface TooltipProps extends React.PropsWithChildren {
|
||||
export interface TooltipProps extends React.PropsWithChildren {
|
||||
position?: TooltipPosition
|
||||
wrapperClassName?: string
|
||||
contentClassName?: string
|
||||
|
96
src/components/statusBar/homeDefaultStatusBarItems.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { StatusBarItemType } from './statusBarTypes'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { CoreDumpManager } from 'lib/coredump'
|
||||
import toast from 'react-hot-toast'
|
||||
import { coreDump } from 'lang/wasm'
|
||||
import { APP_VERSION } from 'lib/appVersion'
|
||||
import { Location } from 'react-router-dom'
|
||||
import { PATHS } from 'lib/paths'
|
||||
|
||||
export const homeDefaultStatusBarItems = ({
|
||||
coreDumpManager,
|
||||
location,
|
||||
}: {
|
||||
coreDumpManager?: CoreDumpManager
|
||||
location: Location
|
||||
}): StatusBarItemType[] => [
|
||||
{
|
||||
id: 'version',
|
||||
element: 'externalLink',
|
||||
label: `v${APP_VERSION}`,
|
||||
href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`,
|
||||
toolTip: {
|
||||
children: 'View the release notes on GitHub',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'report-bug',
|
||||
element: 'button',
|
||||
icon: 'bug',
|
||||
label: 'Report a bug',
|
||||
onClick: (event) => reportBug(event, { coreDumpManager }),
|
||||
toolTip: {
|
||||
children: 'Send your current app state to the developers for debugging',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'settings',
|
||||
element: 'link',
|
||||
icon: 'settings',
|
||||
href:
|
||||
'.' +
|
||||
PATHS.SETTINGS +
|
||||
(location.pathname.includes(PATHS.FILE) ? '?tab=project' : ''),
|
||||
'data-testid': 'settings-link',
|
||||
label: 'Settings',
|
||||
toolTip: {
|
||||
children: 'Settings',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
function reportBug(
|
||||
event: {
|
||||
preventDefault: () => void
|
||||
stopPropagation: () => void
|
||||
},
|
||||
dependencies: {
|
||||
coreDumpManager: CoreDumpManager | undefined
|
||||
}
|
||||
) {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
const { coreDumpManager } = dependencies
|
||||
|
||||
if (!coreDumpManager) {
|
||||
// open default reporting option
|
||||
openWindow(
|
||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||
).catch(reportRejection)
|
||||
} else {
|
||||
toast
|
||||
.promise(
|
||||
coreDump(coreDumpManager, true),
|
||||
{
|
||||
loading: 'Preparing bug report...',
|
||||
success: 'Bug report opened in new window',
|
||||
error: 'Unable to export a core dump. Using default reporting.',
|
||||
},
|
||||
{
|
||||
success: {
|
||||
// Note: this extended duration is especially important for Playwright e2e testing
|
||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||
duration: 6000,
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err: Error) => {
|
||||
if (err) {
|
||||
openWindow(
|
||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||
).catch(reportRejection)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
28
src/components/statusBar/statusBarTypes.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { CustomIconName } from 'components/CustomIcon'
|
||||
import { TooltipProps } from 'components/Tooltip'
|
||||
|
||||
export type StatusBarItemType = {
|
||||
id: string
|
||||
label: string
|
||||
icon?: CustomIconName
|
||||
hideLabel?: boolean
|
||||
toolTip?: Omit<TooltipProps, 'position'>
|
||||
className?: string
|
||||
['data-testid']?: string
|
||||
} & (
|
||||
| {
|
||||
element: 'button'
|
||||
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||
}
|
||||
| {
|
||||
element: 'popover'
|
||||
popoverContent: React.ReactNode
|
||||
}
|
||||
| {
|
||||
element: 'link' | 'externalLink'
|
||||
href: string
|
||||
}
|
||||
| {
|
||||
element: 'text'
|
||||
}
|
||||
)
|
@ -12,7 +12,6 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
|
||||
|
||||
import {
|
||||
CallExpression,
|
||||
clearSceneAndBustCache,
|
||||
emptyExecState,
|
||||
ExecState,
|
||||
initPromise,
|
||||
@ -61,7 +60,6 @@ export class KclManager {
|
||||
private _executeIsStale: ExecuteArgs | null = null
|
||||
private _wasmInitFailed = true
|
||||
private _hasErrors = false
|
||||
private _switchedFiles = false
|
||||
|
||||
engineCommandManager: EngineCommandManager
|
||||
|
||||
@ -81,10 +79,6 @@ export class KclManager {
|
||||
this._astCallBack(ast)
|
||||
}
|
||||
|
||||
set switchedFiles(switchedFiles: boolean) {
|
||||
this._switchedFiles = switchedFiles
|
||||
}
|
||||
|
||||
get programMemory() {
|
||||
return this._programMemory
|
||||
}
|
||||
@ -172,12 +166,8 @@ export class KclManager {
|
||||
this.engineCommandManager = engineCommandManager
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||
this.ensureWasmInit().then(async () => {
|
||||
await this.safeParse(codeManager.code).then((ast) => {
|
||||
if (ast) {
|
||||
this.ast = ast
|
||||
}
|
||||
})
|
||||
this.ensureWasmInit().then(() => {
|
||||
this.ast = this.safeParse(codeManager.code) || this.ast
|
||||
})
|
||||
}
|
||||
|
||||
@ -221,25 +211,7 @@ export class KclManager {
|
||||
}
|
||||
}
|
||||
|
||||
// (jess) I'm not in love with this, but it ensures we clear the scene and
|
||||
// bust the cache on
|
||||
// errors from parsing when opening new files.
|
||||
// Why not just clear the cache on all parse errors, you ask? well its actually
|
||||
// really nice to keep the cache on parse errors within the same file, and
|
||||
// only bust on engine errors esp if they take a long time to execute and
|
||||
// you hit the wrong key!
|
||||
private async checkIfSwitchedFilesShouldClear() {
|
||||
// If we were switching files and we hit an error on parse we need to bust
|
||||
// the cache and clear the scene.
|
||||
if (this._hasErrors && this._switchedFiles) {
|
||||
await clearSceneAndBustCache(this.engineCommandManager)
|
||||
} else if (this._switchedFiles) {
|
||||
// Reset the switched files boolean.
|
||||
this._switchedFiles = false
|
||||
}
|
||||
}
|
||||
|
||||
async safeParse(code: string): Promise<Node<Program> | null> {
|
||||
safeParse(code: string): Node<Program> | null {
|
||||
const result = parse(code)
|
||||
this.diagnostics = []
|
||||
this._hasErrors = false
|
||||
@ -248,8 +220,6 @@ export class KclManager {
|
||||
const kclerror: KCLError = result as KCLError
|
||||
this.diagnostics = kclErrorsToDiagnostics([kclerror])
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -258,7 +228,6 @@ export class KclManager {
|
||||
if (result.errors.length > 0) {
|
||||
this._hasErrors = true
|
||||
|
||||
await this.checkIfSwitchedFilesShouldClear()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -384,7 +353,7 @@ export class KclManager {
|
||||
console.error(newCode)
|
||||
return
|
||||
}
|
||||
const newAst = await this.safeParse(newCode)
|
||||
const newAst = this.safeParse(newCode)
|
||||
if (!newAst) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -439,7 +408,7 @@ export class KclManager {
|
||||
})
|
||||
}
|
||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||
const ast = await this.safeParse(codeManager.code)
|
||||
const ast = this.safeParse(codeManager.code)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -447,9 +416,9 @@ export class KclManager {
|
||||
this.ast = { ...ast }
|
||||
return this.executeAst({ zoomToFit })
|
||||
}
|
||||
async format() {
|
||||
format() {
|
||||
const originalCode = codeManager.code
|
||||
const ast = await this.safeParse(originalCode)
|
||||
const ast = this.safeParse(originalCode)
|
||||
if (!ast) {
|
||||
this.clearAst()
|
||||
return
|
||||
@ -489,7 +458,7 @@ export class KclManager {
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) return Promise.reject(newCode)
|
||||
|
||||
const astWithUpdatedSource = await this.safeParse(newCode)
|
||||
const astWithUpdatedSource = this.safeParse(newCode)
|
||||
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
|
||||
let returnVal: Selections | undefined = undefined
|
||||
|
||||
|
@ -60,7 +60,8 @@ const b1 = cube([0,0], 10)`
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['params', 'FunctionExpression'],
|
||||
[0, 'index'],
|
||||
@ -95,12 +96,14 @@ const b1 = cube([0,0], 10)`
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['body', 'FunctionExpression'],
|
||||
['body', 'FunctionExpression'],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[2, 'index'],
|
||||
|
@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
|
||||
it('should create a variable declaration', () => {
|
||||
const result = createVariableDeclaration('myVar', createLiteral(5))
|
||||
expect(result.type).toBe('VariableDeclaration')
|
||||
expect(result.declaration.type).toBe('VariableDeclarator')
|
||||
expect(result.declaration.id.type).toBe('Identifier')
|
||||
expect(result.declaration.id.name).toBe('myVar')
|
||||
expect(result.declaration.init.type).toBe('Literal')
|
||||
expect((result.declaration.init as any).value).toBe(5)
|
||||
expect(result.declarations[0].type).toBe('VariableDeclarator')
|
||||
expect(result.declarations[0].id.type).toBe('Identifier')
|
||||
expect(result.declarations[0].id.name).toBe('myVar')
|
||||
expect(result.declarations[0].init.type).toBe('Literal')
|
||||
expect((result.declarations[0].init as any).value).toBe(5)
|
||||
})
|
||||
})
|
||||
describe('Testing createPipeExpression', () => {
|
||||
|
@ -66,7 +66,8 @@ export function startSketchOnDefault(
|
||||
let pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndex, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
|
||||
@ -93,7 +94,7 @@ export function addStartProfileAt(
|
||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
}
|
||||
const _node = { ...node }
|
||||
const init = variableDeclaration.declaration.init
|
||||
const init = variableDeclaration.declarations[0].init
|
||||
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||
createArrayExpression([
|
||||
createLiteral(roundOff(at[0])),
|
||||
@ -104,7 +105,7 @@ export function addStartProfileAt(
|
||||
if (init.type === 'PipeExpression') {
|
||||
init.body.splice(1, 0, startProfileAt)
|
||||
} else {
|
||||
variableDeclaration.declaration.init = createPipeExpression([
|
||||
variableDeclaration.declarations[0].init = createPipeExpression([
|
||||
init,
|
||||
startProfileAt,
|
||||
])
|
||||
@ -148,7 +149,8 @@ export function addSketchTo(
|
||||
let pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndex, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
if (axis !== 'xy') {
|
||||
@ -331,7 +333,8 @@ export function extrudeSketch(
|
||||
const pathToExtrudeArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -361,7 +364,8 @@ export function loftSketches(
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -456,7 +460,8 @@ export function revolveSketch(
|
||||
const pathToRevolveArg: PathToNode = [
|
||||
['body', ''],
|
||||
[sketchIndexInBody + 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -542,7 +547,8 @@ export function sketchOnExtrudedFace(
|
||||
const newpathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[expressionIndex + 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
]
|
||||
|
||||
@ -579,7 +585,8 @@ export function addOffsetPlane({
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
@ -816,15 +823,17 @@ export function createVariableDeclaration(
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
|
||||
declaration: {
|
||||
type: 'VariableDeclarator',
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
declarations: [
|
||||
{
|
||||
type: 'VariableDeclarator',
|
||||
start: 0,
|
||||
end: 0,
|
||||
moduleId: 0,
|
||||
|
||||
id: createIdentifier(varName),
|
||||
init,
|
||||
},
|
||||
id: createIdentifier(varName),
|
||||
init,
|
||||
},
|
||||
],
|
||||
visibility,
|
||||
kind,
|
||||
}
|
||||
@ -1111,7 +1120,7 @@ export async function deleteFromSelection(
|
||||
traverse(astClone, {
|
||||
enter: (node, path) => {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
const dec = node.declaration
|
||||
const dec = node.declarations[0]
|
||||
if (
|
||||
dec.init.type === 'CallExpression' &&
|
||||
(dec.init.callee.name === 'extrude' ||
|
||||
@ -1146,7 +1155,7 @@ export async function deleteFromSelection(
|
||||
enter: (node, path) => {
|
||||
;(async () => {
|
||||
if (node.type === 'VariableDeclaration') {
|
||||
currentVariableName = node.declaration.id.name
|
||||
currentVariableName = node.declarations[0].id.name
|
||||
}
|
||||
if (
|
||||
// match startSketchOn(${extrudeNameToDelete})
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { createLiteral } from 'lang/modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
@ -118,8 +118,13 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
||||
true,
|
||||
]
|
||||
const selection: Selection = {
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
const selection: Selections = {
|
||||
graphSelections: [
|
||||
{
|
||||
codeRef: codeRefFromRange(segmentRange, ast),
|
||||
},
|
||||
],
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
// executeAst and artifactGraph
|
||||
|
@ -29,7 +29,7 @@ import {
|
||||
sketchLineHelperMap,
|
||||
} from '../std/sketch'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { Selection, Selections } from 'lib/selections'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { KclCommandValue } from 'lib/commandTypes'
|
||||
import {
|
||||
Artifact,
|
||||
@ -99,9 +99,14 @@ export function modifyAstWithEdgeTreatmentAndTag(
|
||||
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
|
||||
|
||||
for (const selection of selections.graphSelections) {
|
||||
const singleSelection = {
|
||||
graphSelections: [selection],
|
||||
otherSelections: [],
|
||||
}
|
||||
|
||||
const result = getPathToExtrudeForSegmentSelection(
|
||||
clonedAstForGetExtrude,
|
||||
selection,
|
||||
singleSelection,
|
||||
artifactGraph
|
||||
)
|
||||
if (err(result)) return result
|
||||
@ -254,12 +259,12 @@ function insertParametersIntoAst(
|
||||
|
||||
export function getPathToExtrudeForSegmentSelection(
|
||||
ast: Program,
|
||||
selection: Selection,
|
||||
selection: Selections,
|
||||
artifactGraph: ArtifactGraph
|
||||
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
|
||||
const pathToSegmentNode = getNodePathFromSourceRange(
|
||||
ast,
|
||||
selection.codeRef?.range
|
||||
selection.graphSelections[0]?.codeRef?.range
|
||||
)
|
||||
|
||||
const varDecNode = getNodeFromPath<VariableDeclaration>(
|
||||
@ -268,7 +273,7 @@ export function getPathToExtrudeForSegmentSelection(
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(varDecNode)) return varDecNode
|
||||
const sketchVar = varDecNode.node.declaration.id.name
|
||||
const sketchVar = varDecNode.node.declarations[0].id.name
|
||||
|
||||
const sketch = sketchFromKclValue(
|
||||
kclManager.programMemory.get(sketchVar),
|
||||
@ -303,7 +308,7 @@ async function updateAstAndFocus(
|
||||
}
|
||||
}
|
||||
|
||||
export function mutateAstWithTagForSketchSegment(
|
||||
function mutateAstWithTagForSketchSegment(
|
||||
astClone: Node<Program>,
|
||||
pathToSegmentNode: PathToNode
|
||||
): { modifiedAst: Program; tag: string } | Error {
|
||||
@ -362,7 +367,7 @@ function locateExtrudeDeclarator(
|
||||
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
|
||||
|
||||
const { node: extrudeVarDecl } = nodeOfExtrudeCall
|
||||
const extrudeDeclarator = extrudeVarDecl.declaration
|
||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||
if (!extrudeDeclarator) {
|
||||
return new Error('Extrude Declarator not found.')
|
||||
}
|
||||
|
@ -1,124 +0,0 @@
|
||||
import { ArtifactGraph } from 'lang/std/artifactGraph'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
||||
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||
import {
|
||||
getPathToExtrudeForSegmentSelection,
|
||||
mutateAstWithTagForSketchSegment,
|
||||
} from './addEdgeTreatment'
|
||||
import { getNodeFromPath } from 'lang/queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
createLiteral,
|
||||
createIdentifier,
|
||||
findUniqueName,
|
||||
createCallExpressionStdLib,
|
||||
createObjectExpression,
|
||||
createArrayExpression,
|
||||
createVariableDeclaration,
|
||||
} from 'lang/modifyAst'
|
||||
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
|
||||
|
||||
export function addShell({
|
||||
node,
|
||||
selection,
|
||||
artifactGraph,
|
||||
thickness,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
selection: Selections
|
||||
artifactGraph: ArtifactGraph
|
||||
thickness: Expr
|
||||
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||
const modifiedAst = structuredClone(node)
|
||||
|
||||
// Look up the corresponding extrude
|
||||
const clonedAstForGetExtrude = structuredClone(modifiedAst)
|
||||
|
||||
const expressions: Expr[] = []
|
||||
let pathToExtrudeNode: PathToNode | undefined = undefined
|
||||
for (const graphSelection of selection.graphSelections) {
|
||||
const extrudeLookupResult = getPathToExtrudeForSegmentSelection(
|
||||
clonedAstForGetExtrude,
|
||||
graphSelection,
|
||||
artifactGraph
|
||||
)
|
||||
if (err(extrudeLookupResult)) {
|
||||
return new Error("Couldn't find extrude")
|
||||
}
|
||||
|
||||
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
|
||||
// Get the sketch ref from the selection
|
||||
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
|
||||
// We must find a technique for these situations that is robust to intermediate declarations
|
||||
const sketchNode = getNodeFromPath<VariableDeclarator>(
|
||||
modifiedAst,
|
||||
graphSelection.codeRef.pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(sketchNode)) {
|
||||
return sketchNode
|
||||
}
|
||||
|
||||
const selectedArtifact = graphSelection.artifact
|
||||
if (!selectedArtifact) {
|
||||
return new Error('Bad artifact')
|
||||
}
|
||||
|
||||
// Check on the selection, and handle the wall vs cap casees
|
||||
let expr: Expr
|
||||
if (selectedArtifact.type === 'cap') {
|
||||
expr = createLiteral(selectedArtifact.subType)
|
||||
} else if (selectedArtifact.type === 'wall') {
|
||||
const tagResult = mutateAstWithTagForSketchSegment(
|
||||
modifiedAst,
|
||||
extrudeLookupResult.pathToSegmentNode
|
||||
)
|
||||
if (err(tagResult)) return tagResult
|
||||
const { tag } = tagResult
|
||||
expr = createIdentifier(tag)
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
expressions.push(expr)
|
||||
}
|
||||
|
||||
if (!pathToExtrudeNode) return new Error('No extrude found')
|
||||
|
||||
const extrudeNode = getNodeFromPath<VariableDeclarator>(
|
||||
modifiedAst,
|
||||
pathToExtrudeNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(extrudeNode)) {
|
||||
return extrudeNode
|
||||
}
|
||||
|
||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SHELL)
|
||||
const shell = createCallExpressionStdLib('shell', [
|
||||
createObjectExpression({
|
||||
faces: createArrayExpression(expressions),
|
||||
thickness,
|
||||
}),
|
||||
createIdentifier(extrudeNode.node.id.name),
|
||||
])
|
||||
const declaration = createVariableDeclaration(name, shell)
|
||||
|
||||
// TODO: check if we should append at the end like here or right after the extrude
|
||||
modifiedAst.body.push(declaration)
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
['0', 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpression'],
|
||||
[0, 'index'],
|
||||
]
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNode,
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@ import {
|
||||
doesSceneHaveSweepableSketch,
|
||||
traverse,
|
||||
getNodeFromPath,
|
||||
doesSceneHaveExtrudedSketch,
|
||||
} from './queryAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import {
|
||||
@ -231,7 +230,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[2, 'index'],
|
||||
@ -250,7 +250,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const expected = [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['body', 'PipeExpression'],
|
||||
[3, 'index'],
|
||||
@ -292,7 +293,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['cond', 'IfExpression'],
|
||||
['left', 'BinaryExpression'],
|
||||
@ -322,7 +324,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[1, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', ''],
|
||||
['then_val', 'IfExpression'],
|
||||
['body', 'IfExpression'],
|
||||
@ -350,8 +353,7 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['selector', 'ImportStatement'],
|
||||
['items', 'ImportSelector'],
|
||||
['items', 'ImportStatement'],
|
||||
[1, 'index'],
|
||||
['name', 'ImportItem'],
|
||||
])
|
||||
@ -655,38 +657,6 @@ extrude001 = extrude(10, sketch001)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing doesSceneHaveExtrudedSketch', () => {
|
||||
it('finds extruded sketch as variable', async () => {
|
||||
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
extrude001 = extrude(1, sketch001)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
it('finds extruded sketch in pipe', async () => {
|
||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
|> extrude(1, %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
it('finds no extrusion with sketch only', async () => {
|
||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = doesSceneHaveExtrudedSketch(ast)
|
||||
expect(extrudable).toBeFalsy()
|
||||
})
|
||||
})
|
||||
|
||||
describe('Testing traverse and pathToNode', () => {
|
||||
it.each([
|
||||
['basic', '2.73'],
|
||||
|
@ -259,26 +259,34 @@ function moreNodePathFromSourceRange(
|
||||
return moreNodePathFromSourceRange(expression, sourceRange, path)
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
const declarations = _node.declarations
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
|
||||
const declaration = declarations[decIndex]
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
path.push(['declarations', 'VariableDeclaration'])
|
||||
path.push([decIndex, 'index'])
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_node.type === 'VariableDeclaration' && isInRange) {
|
||||
const declaration = _node.declaration
|
||||
const declarations = _node.declarations
|
||||
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declaration', 'VariableDeclaration'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
|
||||
const declaration = declarations[decIndex]
|
||||
if (declaration.start <= start && declaration.end >= end) {
|
||||
const init = declaration.init
|
||||
if (init.start <= start && init.end >= end) {
|
||||
path.push(['declarations', 'VariableDeclaration'])
|
||||
path.push([decIndex, 'index'])
|
||||
path.push(['init', ''])
|
||||
return moreNodePathFromSourceRange(init, sourceRange, path)
|
||||
}
|
||||
}
|
||||
}
|
||||
return path
|
||||
@ -372,31 +380,24 @@ function moreNodePathFromSourceRange(
|
||||
}
|
||||
|
||||
if (_node.type === 'ImportStatement' && isInRange) {
|
||||
if (_node.selector && _node.selector.type === 'List') {
|
||||
path.push(['selector', 'ImportStatement'])
|
||||
const { items } = _node.selector
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportSelector'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
if (
|
||||
item.alias &&
|
||||
item.alias.start <= start &&
|
||||
item.alias.end >= end
|
||||
) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
const { items } = _node
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const item = items[i]
|
||||
if (item.start <= start && item.end >= end) {
|
||||
path.push(['items', 'ImportStatement'])
|
||||
path.push([i, 'index'])
|
||||
if (item.name.start <= start && item.name.end >= end) {
|
||||
path.push(['name', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
|
||||
path.push(['alias', 'ImportItem'])
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
console.error('not implemented: ' + node.type)
|
||||
@ -450,10 +451,13 @@ export function traverse(
|
||||
traverse(node, option, pathToNode)
|
||||
|
||||
if (_node.type === 'VariableDeclaration') {
|
||||
_traverse(_node.declaration, [
|
||||
...pathToNode,
|
||||
['declaration', 'VariableDeclaration'],
|
||||
])
|
||||
_node.declarations.forEach((declaration, index) =>
|
||||
_traverse(declaration, [
|
||||
...pathToNode,
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[index, 'index'],
|
||||
])
|
||||
)
|
||||
} else if (_node.type === 'VariableDeclarator') {
|
||||
_traverse(_node.init, [...pathToNode, ['init', '']])
|
||||
} else if (_node.type === 'PipeExpression') {
|
||||
@ -563,7 +567,7 @@ export function findAllPreviousVariablesPath(
|
||||
const variables: PrevVariable<any>[] = []
|
||||
bodyItems?.forEach?.((item) => {
|
||||
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
|
||||
const varName = item.declaration.id.name
|
||||
const varName = item.declarations[0].id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
if (!varValue || typeof varValue?.value !== type) return
|
||||
variables.push({
|
||||
@ -757,7 +761,7 @@ export function isLinesParallelAndConstrained(
|
||||
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
|
||||
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
||||
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
|
||||
if (err(sg)) return sg
|
||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||
@ -877,7 +881,7 @@ export function hasExtrudeSketch({
|
||||
}
|
||||
const varDec = varDecMeta.node
|
||||
if (varDec.type !== 'VariableDeclaration') return false
|
||||
const varName = varDec.declaration.id.name
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
return (
|
||||
varValue?.type === 'Solid' ||
|
||||
@ -1064,35 +1068,6 @@ export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) {
|
||||
return Object.keys(theMap).length >= count
|
||||
}
|
||||
|
||||
export function doesSceneHaveExtrudedSketch(ast: Node<Program>) {
|
||||
const theMap: any = {}
|
||||
traverse(ast as any, {
|
||||
enter(node) {
|
||||
if (
|
||||
node.type === 'VariableDeclarator' &&
|
||||
node.init?.type === 'PipeExpression'
|
||||
) {
|
||||
for (const pipe of node.init.body) {
|
||||
if (
|
||||
pipe.type === 'CallExpression' &&
|
||||
pipe.callee.name === 'extrude'
|
||||
) {
|
||||
theMap[node.id.name] = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
node.type === 'CallExpression' &&
|
||||
node.callee.name === 'extrude' &&
|
||||
node.arguments[1]?.type === 'Identifier'
|
||||
) {
|
||||
theMap[node.moduleId] = true
|
||||
}
|
||||
},
|
||||
})
|
||||
return Object.keys(theMap).length > 0
|
||||
}
|
||||
|
||||
export function getObjExprProperty(
|
||||
node: ObjectExpression,
|
||||
propName: string
|
||||
|
@ -1879,6 +1879,17 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
return JSON.stringify(this.defaultPlanes)
|
||||
}
|
||||
clearScene(): void {
|
||||
const deleteCmd: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'scene_clear_all',
|
||||
},
|
||||
}
|
||||
this.clearDefaultPlanes()
|
||||
this.engineConnection?.send(deleteCmd)
|
||||
}
|
||||
addCommandLog(message: CommandLog) {
|
||||
if (this.commandLogs.length > 500) {
|
||||
this.commandLogs.shift()
|
||||
|
@ -164,7 +164,8 @@ mySketch001 = startSketchOn('XY')
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
@ -188,7 +189,8 @@ mySketch001 = startSketchOn('XY')
|
||||
pathToNode: [
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['declarations', 'VariableDeclaration'],
|
||||
[0, 'index'],
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
|
@ -1701,7 +1701,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
|
||||
const { node: varDec } = nodeMeta2
|
||||
const varName = varDec.declaration.id.name
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const sketch = sketchFromKclValue(
|
||||
previousProgramMemory.get(varName),
|
||||
varName
|
||||
|
@ -111,10 +111,12 @@ export function isSketchVariablesLinked(
|
||||
let nextVarDec: VariableDeclarator | undefined
|
||||
for (const node of ast.body) {
|
||||
if (node.type !== 'VariableDeclaration') continue
|
||||
if (node.declaration.id.name === secondArg.name) {
|
||||
nextVarDec = node.declaration
|
||||
break
|
||||
}
|
||||
const found = node.declarations.find(
|
||||
({ id }) => id?.name === secondArg.name
|
||||
)
|
||||
if (!found) continue
|
||||
nextVarDec = found
|
||||
break
|
||||
}
|
||||
if (!nextVarDec) return false
|
||||
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)
|
||||
|
@ -16,7 +16,6 @@ import init, {
|
||||
parse_project_settings,
|
||||
default_project_settings,
|
||||
base64_decode,
|
||||
clear_scene_and_bust_cache,
|
||||
} from '../wasm-lib/pkg/wasm_lib'
|
||||
import { KCLError } from './errors'
|
||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||
@ -699,21 +698,6 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
|
||||
return default_app_settings()
|
||||
}
|
||||
|
||||
export async function clearSceneAndBustCache(
|
||||
engineCommandManager: EngineCommandManager
|
||||
): Promise<null | Error> {
|
||||
try {
|
||||
await clear_scene_and_bust_cache(engineCommandManager)
|
||||
} catch (e: any) {
|
||||
console.error('clear_scene_and_bust_cache: error', e)
|
||||
return Promise.reject(
|
||||
new Error(`Error on clear_scene_and_bust_cache: ${e}`)
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export function parseAppSettings(
|
||||
toml: string
|
||||
): DeepPartial<Configuration> | Error {
|
||||
|
12
src/lib/appVersion.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NODE_ENV } from 'env'
|
||||
import { isDesktop } from './isDesktop'
|
||||
import { isTestEnv } from './isTestEnv'
|
||||
|
||||
/** Version number of the app */
|
||||
export const APP_VERSION =
|
||||
isTestEnv && NODE_ENV === 'development'
|
||||
? '11.22.33'
|
||||
: isDesktop()
|
||||
? // @ts-ignore
|
||||
window.electron.packageJson.version
|
||||
: 'main'
|
@ -34,10 +34,6 @@ export type ModelingCommandSchema = {
|
||||
Loft: {
|
||||
selection: Selections
|
||||
}
|
||||
Shell: {
|
||||
selection: Selections
|
||||
thickness: KclCommandValue
|
||||
}
|
||||
Revolve: {
|
||||
selection: Selections
|
||||
angle: KclCommandValue
|
||||
@ -281,25 +277,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Shell: {
|
||||
description: 'Hollow out a 3D solid.',
|
||||
icon: 'shell',
|
||||
needsReview: true,
|
||||
args: {
|
||||
selection: {
|
||||
inputType: 'selection',
|
||||
selectionTypes: ['cap', 'wall'],
|
||||
multiple: true,
|
||||
required: true,
|
||||
skip: false,
|
||||
},
|
||||
thickness: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: KCL_DEFAULT_LENGTH,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
// 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.',
|
||||
|
@ -53,7 +53,6 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
||||
SKETCH: 'sketch',
|
||||
EXTRUDE: 'extrude',
|
||||
LOFT: 'loft',
|
||||
SHELL: 'shell',
|
||||
SEGMENT: 'seg',
|
||||
REVOLVE: 'revolve',
|
||||
PLANE: 'plane',
|
||||
|
@ -2,7 +2,7 @@ import { CommandLog, EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { WebrtcStats } from 'wasm-lib/kcl/bindings/WebrtcStats'
|
||||
import { OsInfo } from 'wasm-lib/kcl/bindings/OsInfo'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { APP_VERSION } from 'lib/appVersion'
|
||||
import { UAParser } from 'ua-parser-js'
|
||||
import screenshot from 'lib/screenshot'
|
||||
import { VITE_KC_API_BASE_URL } from 'env'
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
listProjects,
|
||||
readAppSettingsFile,
|
||||
} from './desktop'
|
||||
import { engineCommandManager } from './singletons'
|
||||
|
||||
export const isHidden = (fileOrDir: FileEntry) =>
|
||||
!!fileOrDir.name?.startsWith('.')
|
||||
@ -115,6 +116,9 @@ export async function createAndOpenNewTutorialProject({
|
||||
) => void
|
||||
navigate: (path: string) => void
|
||||
}) {
|
||||
// Clear the scene.
|
||||
engineCommandManager.clearScene()
|
||||
|
||||
// Create a new project with the onboarding project name
|
||||
const configuration = await readAppSettingsFile()
|
||||
const projects = await listProjects(configuration)
|
||||
|
@ -3,27 +3,27 @@ export const bracket = `// Shelf Bracket
|
||||
|
||||
|
||||
// Define constants
|
||||
sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||
width = 6 // inch
|
||||
p = 300 // Force on shelf - lbs
|
||||
factorOfSafety = 1.2 // FOS of 1.2
|
||||
shelfMountL = 5 // inches
|
||||
wallMountL = 2 // inches
|
||||
shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||
moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||
const sigmaAllow = 35000 // psi (6061-T6 aluminum)
|
||||
const width = 6 // inch
|
||||
const p = 300 // Force on shelf - lbs
|
||||
const factorOfSafety = 1.2 // FOS of 1.2
|
||||
const shelfMountL = 5 // inches
|
||||
const wallMountL = 2 // inches
|
||||
const shelfDepth = 12 // Shelf is 12 inches in depth from the wall
|
||||
const moment = shelfDepth * p // assume the force is applied at the end of the shelf to be conservative (lb-in)
|
||||
|
||||
|
||||
filletRadius = .375 // inches
|
||||
extFilletRadius = .25 // inches
|
||||
mountingHoleDiameter = 0.5 // inches
|
||||
const filletRadius = .375 // inches
|
||||
const extFilletRadius = .25 // inches
|
||||
const mountingHoleDiameter = 0.5 // inches
|
||||
|
||||
|
||||
// Calculate required thickness of bracket
|
||||
thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||
const thickness = sqrt(moment * factorOfSafety * 6 / (sigmaAllow * width)) // this is the calculation of two brackets holding up the shelf (inches)
|
||||
|
||||
|
||||
// Sketch the bracket body and fillet the inner and outer edges of the bend
|
||||
bracketLeg1Sketch = startSketchOn('XY')
|
||||
const bracketLeg1Sketch = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([shelfMountL - filletRadius, 0], %, $fillet1)
|
||||
|> line([0, width], %, $fillet2)
|
||||
@ -47,7 +47,7 @@ bracketLeg1Sketch = startSketchOn('XY')
|
||||
}, %), %)
|
||||
|
||||
// Extrude the leg 2 bracket sketch
|
||||
bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|
||||
const bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|
||||
|> fillet({
|
||||
radius = extFilletRadius,
|
||||
tags = [
|
||||
@ -57,7 +57,7 @@ bracketLeg1Extrude = extrude(thickness, bracketLeg1Sketch)
|
||||
}, %)
|
||||
|
||||
// Sketch the fillet arc
|
||||
filletSketch = startSketchOn('XZ')
|
||||
const filletSketch = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line([0, thickness], %)
|
||||
|> arc({
|
||||
@ -73,10 +73,10 @@ filletSketch = startSketchOn('XZ')
|
||||
}, %)
|
||||
|
||||
// Sketch the bend
|
||||
filletExtrude = extrude(-width, filletSketch)
|
||||
const filletExtrude = extrude(-width, filletSketch)
|
||||
|
||||
// Create a custom plane for the leg that sits on the wall
|
||||
customPlane = {
|
||||
const customPlane = {
|
||||
plane = {
|
||||
origin = { x = -filletRadius, y = 0, z = 0 },
|
||||
xAxis = { x = 0, y = 1, z = 0 },
|
||||
@ -86,7 +86,7 @@ customPlane = {
|
||||
}
|
||||
|
||||
// Create a sketch for the second leg
|
||||
bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
const bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
|> startProfileAt([0, -filletRadius], %)
|
||||
|> line([width, 0], %)
|
||||
|> line([0, -wallMountL], %, $fillet3)
|
||||
@ -102,7 +102,7 @@ bracketLeg2Sketch = startSketchOn(customPlane)
|
||||
}, %), %)
|
||||
|
||||
// Extrude the second leg
|
||||
bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|
||||
const bracketLeg2Extrude = extrude(-thickness, bracketLeg2Sketch)
|
||||
|> fillet({
|
||||
radius = extFilletRadius,
|
||||
tags = [
|
||||
@ -135,8 +135,8 @@ function findLineInExampleCode({
|
||||
}
|
||||
|
||||
export const bracketWidthConstantLine = findLineInExampleCode({
|
||||
searchText: 'width =',
|
||||
searchText: 'const width',
|
||||
})
|
||||
export const bracketThicknessCalculationLine = findLineInExampleCode({
|
||||
searchText: 'thickness =',
|
||||
searchText: 'const thickness',
|
||||
})
|
||||
|
4
src/lib/isTestEnv.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||
|
||||
export const isTestEnv =
|
||||
globalThis.window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
|
@ -5,7 +5,7 @@ import { isDesktop } from './isDesktop'
|
||||
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
|
||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
|
||||
import { parseProjectSettings } from 'lang/wasm'
|
||||
import { err, reportRejection } from './trap'
|
||||
import { err } from './trap'
|
||||
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
|
||||
|
||||
interface OnSubmitProps {
|
||||
@ -28,7 +28,7 @@ export function kclCommands(
|
||||
groupId: 'code',
|
||||
icon: 'code',
|
||||
onSubmit: () => {
|
||||
kclManager.format().catch(reportRejection)
|
||||
kclManager.format()
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -585,17 +585,6 @@ export function canLoftSelection(selection: Selections) {
|
||||
)
|
||||
}
|
||||
|
||||
export function canShellSelection(selection: Selections) {
|
||||
const commonNodes = selection.graphSelections.map((_, i) =>
|
||||
buildCommonNodeFromSelection(selection, i)
|
||||
)
|
||||
return commonNodes.every(
|
||||
(n) =>
|
||||
n.selection.artifact?.type === 'cap' ||
|
||||
n.selection.artifact?.type === 'wall'
|
||||
)
|
||||
}
|
||||
|
||||
// This accounts for non-geometry selections under "other"
|
||||
export type ResolvedSelectionType = Artifact['type'] | 'other'
|
||||
export type SelectionCountsByType = Map<ResolvedSelectionType, number>
|
||||
|
@ -190,15 +190,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
},
|
||||
{
|
||||
id: 'shell',
|
||||
onClick: ({ commandBarSend }) => {
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: { name: 'Shell', groupId: 'modeling' },
|
||||
})
|
||||
},
|
||||
disabled: (state) => !state.can({ type: 'Shell' }),
|
||||
onClick: () => console.error('Shell not yet implemented'),
|
||||
icon: 'shell',
|
||||
status: 'available',
|
||||
status: 'kcl-only',
|
||||
title: 'Shell',
|
||||
description: 'Hollow out a 3D solid.',
|
||||
links: [{ label: 'KCL docs', url: 'https://zoo.dev/docs/kcl/shell' }],
|
||||
|
@ -109,11 +109,11 @@ export function useCalculateKclExpression({
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declaration.id?.name === '__result__'
|
||||
a.declarations?.[0]?.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declaration.init
|
||||
resultDeclaration?.declarations?.[0]?.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
|
22
src/lib/xStateValueToString.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||
|
||||
/**
|
||||
* Convert an XState state value to a pretty string,
|
||||
* with nested states separated by slashes
|
||||
*/
|
||||
export function xStateValueToString(
|
||||
stateValue: StateFrom<AnyStateMachine>['value']
|
||||
) {
|
||||
const sep = ' / '
|
||||
let output = ''
|
||||
let remainingValues = stateValue
|
||||
let isFirstStep = true
|
||||
while (remainingValues instanceof Object) {
|
||||
const key: keyof typeof remainingValues = Object.keys(remainingValues)[0]
|
||||
output += (isFirstStep ? '' : sep) + key
|
||||
remainingValues = remainingValues[key]
|
||||
isFirstStep = false
|
||||
}
|
||||
if (typeof remainingValues === 'string' && remainingValues.trim().length)
|
||||
return output + sep + remainingValues.trim()
|
||||
}
|
@ -80,7 +80,6 @@ import { ToolbarModeName } from 'lib/toolbar'
|
||||
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
|
||||
import { Vector3 } from 'three'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
import { addShell } from 'lang/modifyAst/addShell'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
@ -261,7 +260,6 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Make'; data: ModelingCommandSchema['Make'] }
|
||||
| { type: 'Extrude'; data?: ModelingCommandSchema['Extrude'] }
|
||||
| { type: 'Loft'; data?: ModelingCommandSchema['Loft'] }
|
||||
| { type: 'Shell'; data?: ModelingCommandSchema['Shell'] }
|
||||
| { type: 'Revolve'; data?: ModelingCommandSchema['Revolve'] }
|
||||
| { type: 'Fillet'; data?: ModelingCommandSchema['Fillet'] }
|
||||
| { type: 'Offset plane'; data: ModelingCommandSchema['Offset plane'] }
|
||||
@ -394,7 +392,6 @@ export const modelingMachine = setup({
|
||||
'Selection is on face': () => false,
|
||||
'has valid sweep selection': () => false,
|
||||
'has valid loft selection': () => false,
|
||||
'has valid shell selection': () => false,
|
||||
'has valid edge treatment selection': () => false,
|
||||
'Has exportable geometry': () => false,
|
||||
'has valid selection for deletion': () => false,
|
||||
@ -1587,66 +1584,6 @@ export const modelingMachine = setup({
|
||||
updateAstResult.newAst
|
||||
)
|
||||
|
||||
if (updateAstResult?.selections) {
|
||||
editorManager.selectRange(updateAstResult?.selections)
|
||||
}
|
||||
}
|
||||
),
|
||||
shellAstMod: fromPromise(
|
||||
async ({
|
||||
input,
|
||||
}: {
|
||||
input: ModelingCommandSchema['Shell'] | undefined
|
||||
}) => {
|
||||
if (!input) {
|
||||
return new Error('No input provided')
|
||||
}
|
||||
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
const { selection, thickness } = input
|
||||
|
||||
// Insert the thickness variable if it exists
|
||||
if (
|
||||
'variableName' in thickness &&
|
||||
thickness.variableName &&
|
||||
thickness.insertIndex !== undefined
|
||||
) {
|
||||
const newBody = [...ast.body]
|
||||
newBody.splice(
|
||||
thickness.insertIndex,
|
||||
0,
|
||||
thickness.variableDeclarationAst
|
||||
)
|
||||
ast.body = newBody
|
||||
}
|
||||
|
||||
// Perform the shell op
|
||||
const shellResult = addShell({
|
||||
node: ast,
|
||||
selection,
|
||||
artifactGraph: engineCommandManager.artifactGraph,
|
||||
thickness:
|
||||
'variableName' in thickness
|
||||
? thickness.variableIdentifierAst
|
||||
: thickness.valueAst,
|
||||
})
|
||||
if (err(shellResult)) {
|
||||
return err(shellResult)
|
||||
}
|
||||
|
||||
const updateAstResult = await kclManager.updateAst(
|
||||
shellResult.modifiedAst,
|
||||
true,
|
||||
{
|
||||
focusPath: [shellResult.pathToNode],
|
||||
}
|
||||
)
|
||||
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||
updateAstResult.newAst
|
||||
)
|
||||
|
||||
if (updateAstResult?.selections) {
|
||||
editorManager.selectRange(updateAstResult?.selections)
|
||||
}
|
||||
@ -1690,13 +1627,6 @@ export const modelingMachine = setup({
|
||||
|
||||
Loft: {
|
||||
target: 'Applying loft',
|
||||
guard: 'has valid loft selection',
|
||||
reenter: true,
|
||||
},
|
||||
|
||||
Shell: {
|
||||
target: 'Applying shell',
|
||||
guard: 'has valid shell selection',
|
||||
reenter: true,
|
||||
},
|
||||
|
||||
@ -2461,19 +2391,6 @@ export const modelingMachine = setup({
|
||||
onError: ['idle'],
|
||||
},
|
||||
},
|
||||
|
||||
'Applying shell': {
|
||||
invoke: {
|
||||
src: 'shellAstMod',
|
||||
id: 'shellAstMod',
|
||||
input: ({ event }) => {
|
||||
if (event.type !== 'Shell') return undefined
|
||||
return event.data
|
||||
},
|
||||
onDone: ['idle'],
|
||||
onError: ['idle'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'idle',
|
||||
@ -2574,7 +2491,7 @@ export function canRectangleOrCircleTool({
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
return node.node?.declaration.init.type !== 'PipeExpression'
|
||||
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
|
||||
}
|
||||
|
||||
/** If the sketch contains `close` or `circle` stdlib functions it must be closed */
|
||||
@ -2591,8 +2508,8 @@ export function isClosedSketch({
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
if (node.node?.declaration.init.type !== 'PipeExpression') return false
|
||||
return node.node.declaration.init.body.some(
|
||||
if (node.node?.declarations?.[0]?.init.type !== 'PipeExpression') return false
|
||||
return node.node.declarations[0].init.body.some(
|
||||
(node) =>
|
||||
node.type === 'CallExpression' &&
|
||||
(node.callee.name === 'close' || node.callee.name === 'circle')
|
||||
|
@ -13,18 +13,6 @@ import { AllSettingsFields } from 'components/Settings/AllSettingsFields'
|
||||
import { AllKeybindingsFields } from 'components/Settings/AllKeybindingsFields'
|
||||
import { KeybindingsSectionsList } from 'components/Settings/KeybindingsSectionsList'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
import { IS_PLAYWRIGHT_KEY } from '../../e2e/playwright/storageStates'
|
||||
import { NODE_ENV } from 'env'
|
||||
|
||||
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
|
||||
|
||||
export const APP_VERSION =
|
||||
isTestEnv && NODE_ENV === 'development'
|
||||
? '11.22.33'
|
||||
: isDesktop()
|
||||
? // @ts-ignore
|
||||
window.electron.packageJson.version
|
||||
: 'main'
|
||||
|
||||
export const PACKAGE_NAME = isDesktop()
|
||||
? window.electron.packageJson.name
|
||||
|
@ -5,11 +5,11 @@ import { Themes, getSystemTheme } from '../lib/theme'
|
||||
import { PATHS } from 'lib/paths'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { APP_NAME } from 'lib/constants'
|
||||
import { APP_VERSION } from 'lib/appVersion'
|
||||
import { CSSProperties, useCallback, useState } from 'react'
|
||||
import { Logo } from 'components/Logo'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { APP_VERSION } from './Settings'
|
||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||
import { toSync } from 'lib/utils'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
|
@ -395,10 +395,10 @@ fn do_stdlib_inner(
|
||||
#const_struct
|
||||
|
||||
fn #boxed_fn_name_ident(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<dyn std::future::Future<Output = anyhow::Result<crate::execution::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))
|
||||
}
|
||||
@ -770,12 +770,12 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn #test_name_mock() {
|
||||
let program = crate::Program::parse_no_errs(#code_block).unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
|
||||
ctx.run(program.into(), &mut crate::ExecState::default()).await.unwrap();
|
||||
@ -785,7 +785,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
async fn #test_name() {
|
||||
let code = #code_block;
|
||||
// Note, `crate` must be kcl_lib
|
||||
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm, None).await.unwrap();
|
||||
let result = crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm).await.unwrap();
|
||||
twenty_twenty::assert_image(&format!("tests/outputs/{}.png", #output_test_name_str), &result, 0.99);
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::Program::parse_no_errs("someFn()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,7 +12,7 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -22,13 +22,10 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_someFn0() {
|
||||
let code = "someFn()";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&result,
|
||||
@ -47,12 +44,12 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -3,7 +3,7 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::Program::parse_no_errs("someFn()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,7 +12,7 @@ mod test_examples_someFn {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -22,13 +22,10 @@ mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_someFn0() {
|
||||
let code = "someFn()";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_someFn0"),
|
||||
&result,
|
||||
@ -47,12 +44,12 @@ pub(crate) struct SomeFn {}
|
||||
#[doc = "Std lib function: someFn\nDocs"]
|
||||
pub(crate) const SomeFn: SomeFn = SomeFn {};
|
||||
fn boxed_someFn(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_show {
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nshow";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -41,7 +38,7 @@ mod test_examples_show {
|
||||
async fn test_mock_example_show1() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -50,7 +47,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -60,13 +57,10 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show1() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show1"),
|
||||
&result,
|
||||
@ -85,12 +79,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_show {
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -5,7 +5,7 @@ mod test_examples_my_func {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmyFunc")
|
||||
.unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -14,7 +14,7 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -24,13 +24,10 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_my_func0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nmyFunc";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func0"),
|
||||
&result,
|
||||
@ -42,7 +39,7 @@ mod test_examples_my_func {
|
||||
async fn test_mock_example_my_func1() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -51,7 +48,7 @@ mod test_examples_my_func {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -61,13 +58,10 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_my_func1() {
|
||||
let code = "This is code.\nIt does other shit.\nmyFunc";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_my_func1"),
|
||||
&result,
|
||||
@ -86,12 +80,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -5,7 +5,7 @@ mod test_examples_line_to {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nlineTo")
|
||||
.unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -14,7 +14,7 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -24,13 +24,10 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_line_to0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nlineTo";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to0"),
|
||||
&result,
|
||||
@ -42,7 +39,7 @@ mod test_examples_line_to {
|
||||
async fn test_mock_example_line_to1() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -51,7 +48,7 @@ mod test_examples_line_to {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -61,13 +58,10 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_line_to1() {
|
||||
let code = "This is code.\nIt does other shit.\nlineTo";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_line_to1"),
|
||||
&result,
|
||||
@ -86,12 +80,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_min {
|
||||
async fn test_mock_example_min0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_min0() {
|
||||
let code = "This is another code block.\nyes sirrr.\nmin";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min0"),
|
||||
&result,
|
||||
@ -41,7 +38,7 @@ mod test_examples_min {
|
||||
async fn test_mock_example_min1() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nmin").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -50,7 +47,7 @@ mod test_examples_min {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -60,13 +57,10 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_min1() {
|
||||
let code = "This is code.\nIt does other shit.\nmin";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_min1"),
|
||||
&result,
|
||||
@ -85,12 +79,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_show {
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_import {
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_import {
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_import {
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_import {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_import0() {
|
||||
let code = "This is code.\nIt does other shit.\nimport";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_import0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ pub(crate) struct Import {}
|
||||
#[doc = "Std lib function: import\nThis is some function.\nIt does shit."]
|
||||
pub(crate) const Import: Import = Import {};
|
||||
fn boxed_import(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -4,7 +4,7 @@ mod test_examples_show {
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::Program::parse_no_errs("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -13,7 +13,7 @@ mod test_examples_show {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -23,13 +23,10 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_show0() {
|
||||
let code = "This is code.\nIt does other shit.\nshow";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_show0"),
|
||||
&result,
|
||||
@ -48,12 +45,12 @@ 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::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -3,7 +3,7 @@ mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_some_function0() {
|
||||
let program = crate::Program::parse_no_errs("someFunction()").unwrap();
|
||||
let ctx = crate::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
.await
|
||||
@ -12,7 +12,7 @@ mod test_examples_some_function {
|
||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: std::sync::Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
ctx.run(program.into(), &mut crate::ExecState::default())
|
||||
.await
|
||||
@ -22,13 +22,10 @@ mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||
async fn kcl_test_example_some_function0() {
|
||||
let code = "someFunction()";
|
||||
let result = crate::test_server::execute_and_snapshot(
|
||||
code,
|
||||
crate::settings::types::UnitLength::Mm,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let result =
|
||||
crate::test_server::execute_and_snapshot(code, crate::settings::types::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap();
|
||||
twenty_twenty::assert_image(
|
||||
&format!("tests/outputs/{}.png", "serial_test_example_some_function0"),
|
||||
&result,
|
||||
@ -47,12 +44,12 @@ pub(crate) struct SomeFunction {}
|
||||
#[doc = "Std lib function: someFunction\nDocs"]
|
||||
pub(crate) const SomeFunction: SomeFunction = SomeFunction {};
|
||||
fn boxed_some_function(
|
||||
exec_state: &mut crate::ExecState,
|
||||
exec_state: &mut crate::executor::ExecState,
|
||||
args: crate::std::Args,
|
||||
) -> std::pin::Pin<
|
||||
Box<
|
||||
dyn std::future::Future<
|
||||
Output = anyhow::Result<crate::execution::KclValue, crate::errors::KclError>,
|
||||
Output = anyhow::Result<crate::executor::KclValue, crate::errors::KclError>,
|
||||
> + Send
|
||||
+ '_,
|
||||
>,
|
||||
|
@ -15,6 +15,7 @@ redo-kcl-stdlib-docs:
|
||||
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
|
||||
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
|
||||
|
||||
|
||||
# Copy a test KCL file from executor tests into a new simulation test.
|
||||
copy-exec-test-into-sim-test test_name:
|
||||
mkdir -p kcl/tests/{{test_name}}
|
||||
|
@ -18,7 +18,7 @@ pub fn bench_execute(c: &mut Criterion) {
|
||||
let rt = Runtime::new().unwrap();
|
||||
// Spawn a future onto the runtime
|
||||
b.iter(|| {
|
||||
rt.block_on(test_server::execute_and_snapshot(s, Mm, None)).unwrap();
|
||||
rt.block_on(test_server::execute_and_snapshot(s, Mm)).unwrap();
|
||||
});
|
||||
});
|
||||
group.finish();
|
||||
@ -38,7 +38,7 @@ pub fn bench_lego(c: &mut Criterion) {
|
||||
let code = LEGO_PROGRAM.replace("{{N}}", &size.to_string());
|
||||
// Spawn a future onto the runtime
|
||||
b.iter(|| {
|
||||
rt.block_on(test_server::execute_and_snapshot(&code, Mm, None)).unwrap();
|
||||
rt.block_on(test_server::execute_and_snapshot(&code, Mm)).unwrap();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use iai::black_box;
|
||||
async fn execute_server_rack_heavy() {
|
||||
let code = SERVER_RACK_HEAVY_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
|
||||
async fn execute_server_rack_lite() {
|
||||
let code = SERVER_RACK_LITE_PROGRAM;
|
||||
black_box(
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm, None)
|
||||
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
|
3
src/wasm-lib/kcl/src/ast/mod.rs
Normal file
@ -0,0 +1,3 @@
|
||||
pub mod cache;
|
||||
pub mod modify;
|
||||
pub mod types;
|
@ -22,7 +22,7 @@ use super::ExecutionKind;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
@ -20,7 +20,7 @@ use kittycad_modeling_cmds::{self as kcmc};
|
||||
use super::ExecutionKind;
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
@ -11,7 +11,7 @@ use wasm_bindgen::prelude::*;
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{DefaultPlanes, IdGenerator},
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
@ -32,7 +32,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{DefaultPlanes, IdGenerator, Point3d},
|
||||
executor::{DefaultPlanes, IdGenerator, Point3d},
|
||||
SourceRange,
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
//! The executor for the AST.
|
||||
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use std::{collections::HashSet, sync::Arc};
|
||||
|
||||
use anyhow::Result;
|
||||
use async_recursion::async_recursion;
|
||||
@ -20,18 +20,14 @@ use serde::{Deserialize, Serialize};
|
||||
type Point2D = kcmc::shared::Point2d<f64>;
|
||||
type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
pub use function_param::FunctionParam;
|
||||
pub use kcl_value::{KclObjectFields, KclValue};
|
||||
|
||||
pub use crate::kcl_value::KclValue;
|
||||
use crate::{
|
||||
engine::{EngineManager, ExecutionKind},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::{FileManager, FileSystem},
|
||||
parsing::ast::{
|
||||
cache::{get_changed_program, CacheInformation},
|
||||
types::{
|
||||
BodyItem, Expr, FunctionExpression, ImportSelector, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode,
|
||||
},
|
||||
types::{BodyItem, Expr, FunctionExpression, ItemVisibility, Node, NodeRef, TagDeclarator, TagNode},
|
||||
},
|
||||
settings::types::UnitLength,
|
||||
source_range::{ModuleId, SourceRange},
|
||||
@ -39,10 +35,6 @@ use crate::{
|
||||
ExecError, Program,
|
||||
};
|
||||
|
||||
mod exec_ast;
|
||||
mod function_param;
|
||||
mod kcl_value;
|
||||
|
||||
/// State for executing a program.
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -58,7 +50,7 @@ pub struct ExecState {
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
/// Identifiers that have been exported from the current module.
|
||||
pub module_exports: Vec<String>,
|
||||
pub module_exports: HashSet<String>,
|
||||
/// The stack of import statements for detecting circular module imports.
|
||||
/// If this is empty, we're not currently executing an import statement.
|
||||
pub import_stack: Vec<std::path::PathBuf>,
|
||||
@ -69,7 +61,7 @@ pub struct ExecState {
|
||||
}
|
||||
|
||||
impl ExecState {
|
||||
fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId {
|
||||
pub fn add_module(&mut self, path: std::path::PathBuf) -> ModuleId {
|
||||
// Need to avoid borrowing self in the closure.
|
||||
let new_module_id = ModuleId::from_usize(self.path_to_source_id.len());
|
||||
let mut is_new = false;
|
||||
@ -1506,9 +1498,6 @@ pub struct ExecutorSettings {
|
||||
/// Should engine store this for replay?
|
||||
/// If so, under what name?
|
||||
pub replay: Option<String>,
|
||||
/// The directory of the current project. This is used for resolving import
|
||||
/// paths. If None is given, the current working directory is used.
|
||||
pub project_directory: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Default for ExecutorSettings {
|
||||
@ -1519,7 +1508,6 @@ impl Default for ExecutorSettings {
|
||||
enable_ssao: false,
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1532,7 +1520,6 @@ impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1545,7 +1532,6 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
|
||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||
show_grid: config.settings.modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1558,7 +1544,6 @@ impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
||||
enable_ssao: modeling.enable_ssao.into(),
|
||||
show_grid: modeling.show_scale_grid,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1789,7 +1774,6 @@ impl ExecutorContext {
|
||||
enable_ssao: false,
|
||||
show_grid: false,
|
||||
replay: None,
|
||||
project_directory: None,
|
||||
},
|
||||
None,
|
||||
engine_addr,
|
||||
@ -1801,7 +1785,7 @@ impl ExecutorContext {
|
||||
pub async fn reset_scene(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: crate::execution::SourceRange,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
self.engine
|
||||
.clear_scene(&mut exec_state.id_generator, source_range)
|
||||
@ -1866,7 +1850,7 @@ impl ExecutorContext {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.inner_execute(&cache_result.program, exec_state, crate::execution::BodyType::Root)
|
||||
self.inner_execute(&cache_result.program, exec_state, crate::executor::BodyType::Root)
|
||||
.await?;
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok(session_data)
|
||||
@ -1886,66 +1870,91 @@ impl ExecutorContext {
|
||||
match statement {
|
||||
BodyItem::ImportStatement(import_stmt) => {
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
let (module_memory, module_exports) =
|
||||
self.open_module(&import_stmt.path, exec_state, source_range).await?;
|
||||
let path = import_stmt.path.clone();
|
||||
// Empty path is used by the top-level module.
|
||||
if path.is_empty() {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "import path cannot be empty".to_owned(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
let resolved_path = std::path::PathBuf::from(&path);
|
||||
if exec_state.import_stack.contains(&resolved_path) {
|
||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
exec_state
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
resolved_path.to_string_lossy()
|
||||
),
|
||||
source_ranges: vec![import_stmt.into()],
|
||||
}));
|
||||
}
|
||||
let module_id = exec_state.add_module(resolved_path.clone());
|
||||
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
|
||||
// TODO handle parsing errors properly
|
||||
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
|
||||
let (module_memory, module_exports) = {
|
||||
exec_state.import_stack.push(resolved_path.clone());
|
||||
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
|
||||
let original_memory = std::mem::take(&mut exec_state.memory);
|
||||
let original_exports = std::mem::take(&mut exec_state.module_exports);
|
||||
let result = self
|
||||
.inner_execute(&program, exec_state, crate::executor::BodyType::Root)
|
||||
.await;
|
||||
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
|
||||
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
|
||||
self.engine.replace_execution_kind(original_execution);
|
||||
exec_state.import_stack.pop();
|
||||
|
||||
match &import_stmt.selector {
|
||||
ImportSelector::List { items } => {
|
||||
for import_item in items {
|
||||
// Extract the item from the module.
|
||||
let item =
|
||||
module_memory
|
||||
.get(&import_item.name.name, import_item.into())
|
||||
.map_err(|_err| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("{} is not defined in module", import_item.name.name),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
})
|
||||
})?;
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.memory.add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
|
||||
if let ItemVisibility::Export = import_stmt.visibility {
|
||||
exec_state.module_exports.push(import_item.identifier().to_owned());
|
||||
}
|
||||
result.map_err(|err| {
|
||||
if let KclError::ImportCycle(_) = err {
|
||||
// It was an import cycle. Keep the original message.
|
||||
err.override_source_ranges(vec![source_range])
|
||||
} else {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Error loading imported file. Open it to view more details. {path}: {}",
|
||||
err.message()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => {
|
||||
for name in module_exports.iter() {
|
||||
let item = module_memory.get(name, source_range).map_err(|_err| {
|
||||
KclError::Internal(KclErrorDetails {
|
||||
message: format!("{} is not defined in module (but was exported?)", name),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
})?;
|
||||
exec_state.memory.add(name, item.clone(), source_range)?;
|
||||
})?;
|
||||
|
||||
if let ItemVisibility::Export = import_stmt.visibility {
|
||||
exec_state.module_exports.push(name.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
ImportSelector::None(_) => {
|
||||
(module_memory, module_exports)
|
||||
};
|
||||
for import_item in &import_stmt.items {
|
||||
// Extract the item from the module.
|
||||
let item = module_memory
|
||||
.get(&import_item.name.name, import_item.into())
|
||||
.map_err(|_err| {
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("{} is not defined in module", import_item.name.name),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
})
|
||||
})?;
|
||||
// Check that the item is allowed to be imported.
|
||||
if !module_exports.contains(&import_item.name.name) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Importing whole module is not yet implemented, sorry.".to_owned(),
|
||||
source_ranges: vec![source_range],
|
||||
message: format!(
|
||||
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
|
||||
import_item.name.name
|
||||
),
|
||||
source_ranges: vec![SourceRange::from(&import_item.name)],
|
||||
}));
|
||||
}
|
||||
|
||||
// Add the item to the current module.
|
||||
exec_state.memory.add(
|
||||
import_item.identifier(),
|
||||
item.clone(),
|
||||
SourceRange::from(&import_item.name),
|
||||
)?;
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
@ -1962,23 +1971,34 @@ impl ExecutorContext {
|
||||
);
|
||||
}
|
||||
BodyItem::VariableDeclaration(variable_declaration) => {
|
||||
let var_name = variable_declaration.declaration.id.name.to_string();
|
||||
let source_range = SourceRange::from(&variable_declaration.declaration.init);
|
||||
let metadata = Metadata { source_range };
|
||||
for declaration in &variable_declaration.declarations {
|
||||
let var_name = declaration.id.name.to_string();
|
||||
let source_range = SourceRange::from(&declaration.init);
|
||||
let metadata = Metadata { source_range };
|
||||
|
||||
let memory_item = self
|
||||
.execute_expr(
|
||||
&variable_declaration.declaration.init,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
exec_state.module_exports.push(var_name);
|
||||
let memory_item = self
|
||||
.execute_expr(
|
||||
&declaration.init,
|
||||
exec_state,
|
||||
&metadata,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
let is_function = memory_item.is_function();
|
||||
exec_state.memory.add(&var_name, memory_item, source_range)?;
|
||||
// Track exports.
|
||||
match variable_declaration.visibility {
|
||||
ItemVisibility::Export => {
|
||||
if !is_function {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Only functions can be exported".to_owned(),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
exec_state.module_exports.insert(var_name);
|
||||
}
|
||||
ItemVisibility::Default => {}
|
||||
}
|
||||
}
|
||||
last_expr = None;
|
||||
}
|
||||
@ -2013,68 +2033,6 @@ impl ExecutorContext {
|
||||
Ok(last_expr)
|
||||
}
|
||||
|
||||
async fn open_module(
|
||||
&self,
|
||||
path: &str,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(ProgramMemory, Vec<String>), KclError> {
|
||||
let resolved_path = if let Some(project_dir) = &self.settings.project_directory {
|
||||
project_dir.join(path)
|
||||
} else {
|
||||
std::path::PathBuf::from(&path)
|
||||
};
|
||||
|
||||
if exec_state.import_stack.contains(&resolved_path) {
|
||||
return Err(KclError::ImportCycle(KclErrorDetails {
|
||||
message: format!(
|
||||
"circular import of modules is not allowed: {} -> {}",
|
||||
exec_state
|
||||
.import_stack
|
||||
.iter()
|
||||
.map(|p| p.as_path().to_string_lossy())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" -> "),
|
||||
resolved_path.to_string_lossy()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
}));
|
||||
}
|
||||
let module_id = exec_state.add_module(resolved_path.clone());
|
||||
let source = self.fs.read_to_string(&resolved_path, source_range).await?;
|
||||
// TODO handle parsing errors properly
|
||||
let program = crate::parsing::parse_str(&source, module_id).parse_errs_as_err()?;
|
||||
|
||||
exec_state.import_stack.push(resolved_path.clone());
|
||||
let original_execution = self.engine.replace_execution_kind(ExecutionKind::Isolated);
|
||||
let original_memory = std::mem::take(&mut exec_state.memory);
|
||||
let original_exports = std::mem::take(&mut exec_state.module_exports);
|
||||
let result = self
|
||||
.inner_execute(&program, exec_state, crate::execution::BodyType::Root)
|
||||
.await;
|
||||
let module_exports = std::mem::replace(&mut exec_state.module_exports, original_exports);
|
||||
let module_memory = std::mem::replace(&mut exec_state.memory, original_memory);
|
||||
self.engine.replace_execution_kind(original_execution);
|
||||
exec_state.import_stack.pop();
|
||||
|
||||
result.map_err(|err| {
|
||||
if let KclError::ImportCycle(_) = err {
|
||||
// It was an import cycle. Keep the original message.
|
||||
err.override_source_ranges(vec![source_range])
|
||||
} else {
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
"Error loading imported file. Open it to view more details. {path}: {}",
|
||||
err.message()
|
||||
),
|
||||
source_ranges: vec![source_range],
|
||||
})
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok((module_memory, module_exports))
|
||||
}
|
||||
|
||||
pub async fn execute_expr<'a>(
|
||||
&self,
|
||||
init: &Expr,
|
||||
@ -2162,7 +2120,7 @@ impl ExecutorContext {
|
||||
self.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::execution::SourceRange::default(),
|
||||
crate::executor::SourceRange::default(),
|
||||
ModelingCmd::from(mcmd::ZoomToFit {
|
||||
object_ids: Default::default(),
|
||||
animated: false,
|
||||
@ -2176,7 +2134,7 @@ impl ExecutorContext {
|
||||
.engine
|
||||
.send_modeling_cmd(
|
||||
uuid::Uuid::new_v4(),
|
||||
crate::execution::SourceRange::default(),
|
||||
crate::executor::SourceRange::default(),
|
||||
ModelingCmd::from(mcmd::TakeSnapshot {
|
||||
format: ImageFormat::Png,
|
||||
}),
|
@ -2,7 +2,7 @@ use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{
|
||||
executor::{
|
||||
call_user_defined_function, ExecState, ExecutorContext, KclValue, MemoryFunction, Metadata, ProgramMemory,
|
||||
},
|
||||
parsing::ast::types::FunctionExpression,
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
exec::{ProgramMemory, Sketch},
|
||||
execution::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||
executor::{Face, ImportedGeometry, MemoryFunction, Metadata, Plane, SketchSet, Solid, SolidSet, TagIdentifier},
|
||||
parsing::ast::types::{FunctionExpression, KclNone, LiteralValue, TagDeclarator, TagNode},
|
||||
std::{args::Arg, FnAsArg},
|
||||
ExecState, ExecutorContext, KclError, SourceRange,
|
||||
@ -262,6 +262,9 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
matches!(self, KclValue::Function { .. })
|
||||
}
|
||||
/// Put the number into a KCL value.
|
||||
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
|
||||
Self::Number { value: f, meta }
|
||||
@ -493,7 +496,7 @@ impl KclValue {
|
||||
)
|
||||
.await
|
||||
} else {
|
||||
crate::execution::call_user_defined_function(
|
||||
crate::executor::call_user_defined_function(
|
||||
args,
|
||||
closure_memory.as_ref(),
|
||||
expression.as_ref(),
|
@ -60,8 +60,10 @@ mod coredump;
|
||||
mod docs;
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod execution;
|
||||
mod executor;
|
||||
mod fs;
|
||||
mod function_param;
|
||||
mod kcl_value;
|
||||
pub mod lint;
|
||||
mod log;
|
||||
mod lsp;
|
||||
@ -82,7 +84,7 @@ mod wasm;
|
||||
pub use coredump::CoreDump;
|
||||
pub use engine::{EngineManager, ExecutionKind};
|
||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError};
|
||||
pub use execution::{ExecState, ExecutorContext, ExecutorSettings};
|
||||
pub use executor::{ExecState, ExecutorContext, ExecutorSettings};
|
||||
pub use lsp::{
|
||||
copilot::Backend as CopilotLspBackend,
|
||||
kcl::{Backend as KclLspBackend, Server as KclLspServerSubCommand},
|
||||
@ -98,7 +100,7 @@ pub use source_range::{ModuleId, SourceRange};
|
||||
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
|
||||
// Ideally we wouldn't export these things at all, they should only be used for testing.
|
||||
pub mod exec {
|
||||
pub use crate::execution::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
|
||||
pub use crate::executor::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
|
@ -60,7 +60,11 @@ pub fn lint_variables(decl: Node) -> Result<Vec<Discovered>> {
|
||||
return Ok(vec![]);
|
||||
};
|
||||
|
||||
lint_lower_camel_case_var(&decl.declaration)
|
||||
Ok(decl
|
||||
.declarations
|
||||
.iter()
|
||||
.flat_map(|v| lint_lower_camel_case_var(v).unwrap_or_default())
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn lint_object_properties(decl: Node) -> Result<Vec<Discovered>> {
|
||||
|
@ -19,7 +19,7 @@ impl Notification for AstUpdated {
|
||||
pub enum MemoryUpdated {}
|
||||
|
||||
impl Notification for MemoryUpdated {
|
||||
type Params = crate::execution::ProgramMemory;
|
||||
type Params = crate::executor::ProgramMemory;
|
||||
const METHOD: &'static str = "kcl/memoryUpdated";
|
||||
}
|
||||
|
||||
|
@ -115,7 +115,7 @@ pub struct Backend {
|
||||
/// information.
|
||||
pub last_successful_ast_state: Arc<RwLock<Option<OldAstState>>>,
|
||||
/// Memory maps.
|
||||
pub memory_map: DashMap<String, crate::execution::ProgramMemory>,
|
||||
pub memory_map: DashMap<String, crate::executor::ProgramMemory>,
|
||||
/// Current code.
|
||||
pub code_map: DashMap<String, Vec<u8>>,
|
||||
/// Diagnostics.
|
||||
@ -129,7 +129,7 @@ pub struct Backend {
|
||||
/// If we can send telemetry for this user.
|
||||
pub can_send_telemetry: bool,
|
||||
/// Optional executor context to use if we want to execute the code.
|
||||
pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
|
||||
pub executor_ctx: Arc<RwLock<Option<crate::executor::ExecutorContext>>>,
|
||||
/// If we are currently allowed to execute the ast.
|
||||
pub can_execute: Arc<RwLock<bool>>,
|
||||
|
||||
@ -140,7 +140,7 @@ impl Backend {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn new_wasm(
|
||||
client: Client,
|
||||
executor_ctx: Option<crate::execution::ExecutorContext>,
|
||||
executor_ctx: Option<crate::executor::ExecutorContext>,
|
||||
fs: crate::fs::wasm::FileSystemManager,
|
||||
zoo_client: kittycad::Client,
|
||||
can_send_telemetry: bool,
|
||||
@ -157,7 +157,7 @@ impl Backend {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn new(
|
||||
client: Client,
|
||||
executor_ctx: Option<crate::execution::ExecutorContext>,
|
||||
executor_ctx: Option<crate::executor::ExecutorContext>,
|
||||
zoo_client: kittycad::Client,
|
||||
can_send_telemetry: bool,
|
||||
) -> Result<Self, String> {
|
||||
@ -172,7 +172,7 @@ impl Backend {
|
||||
|
||||
fn with_file_manager(
|
||||
client: Client,
|
||||
executor_ctx: Option<crate::execution::ExecutorContext>,
|
||||
executor_ctx: Option<crate::executor::ExecutorContext>,
|
||||
fs: crate::fs::FileManager,
|
||||
zoo_client: kittycad::Client,
|
||||
can_send_telemetry: bool,
|
||||
@ -297,7 +297,7 @@ impl crate::lsp::backend::Backend for Backend {
|
||||
|
||||
// Try to get the memory for the current code.
|
||||
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
|
||||
*memory != crate::execution::ProgramMemory::default()
|
||||
*memory != crate::executor::ProgramMemory::default()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
@ -406,7 +406,7 @@ impl Backend {
|
||||
*self.can_execute.read().await
|
||||
}
|
||||
|
||||
pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
|
||||
pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::executor::ExecutorContext>> {
|
||||
self.executor_ctx.read().await
|
||||
}
|
||||
|
||||
@ -871,7 +871,7 @@ impl Backend {
|
||||
|
||||
// Try to get the memory for the current code.
|
||||
let has_memory = if let Some(memory) = self.memory_map.get(&filename) {
|
||||
*memory != crate::execution::ProgramMemory::default()
|
||||
*memory != crate::executor::ProgramMemory::default()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
@ -9,10 +9,10 @@ pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib)?;
|
||||
|
||||
let zoo_client = crate::execution::new_zoo_client(None, None)?;
|
||||
let zoo_client = crate::executor::new_zoo_client(None, None)?;
|
||||
|
||||
let executor_ctx = if execute {
|
||||
Some(crate::execution::ExecutorContext::new(&zoo_client, Default::default()).await?)
|
||||
Some(crate::executor::ExecutorContext::new(&zoo_client, Default::default()).await?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
@ -7,7 +7,7 @@ use tower_lsp::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
execution::ProgramMemory,
|
||||
executor::ProgramMemory,
|
||||
lsp::test_util::{copilot_lsp_server, kcl_lsp_server},
|
||||
parsing::ast::types::{Node, Program},
|
||||
};
|
||||
@ -645,7 +645,7 @@ async fn test_kcl_lsp_completions() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"thing= 1
|
||||
text: r#"const thing= 1
|
||||
st"#
|
||||
.to_string(),
|
||||
},
|
||||
@ -688,7 +688,7 @@ async fn test_kcl_lsp_completions_empty_in_comment() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"thing= 1 // st"#.to_string(),
|
||||
text: r#"const thing= 1 // st"#.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
@ -700,7 +700,7 @@ async fn test_kcl_lsp_completions_empty_in_comment() {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 13 },
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 19 },
|
||||
},
|
||||
context: None,
|
||||
partial_result_params: Default::default(),
|
||||
@ -723,7 +723,7 @@ async fn test_kcl_lsp_completions_tags() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([11.19, 28.35], %)
|
||||
|> line([28.67, -13.25], %, $here)
|
||||
|> line([-4.12, -22.81], %)
|
||||
@ -1058,7 +1058,7 @@ async fn test_kcl_lsp_semantic_tokens_with_modifiers() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
@ -1066,8 +1066,8 @@ async fn test_kcl_lsp_semantic_tokens_with_modifiers() {
|
||||
|> close(%)
|
||||
|> extrude(3.14, %)
|
||||
|
||||
thing = {blah: "foo"}
|
||||
bar = thing.blah
|
||||
const thing = {blah: "foo"}
|
||||
const bar = thing.blah
|
||||
|
||||
fn myFn = (param1) => {
|
||||
return param1
|
||||
@ -1230,7 +1230,7 @@ async fn test_kcl_lsp_semantic_tokens_multiple_comments() {
|
||||
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||
|
||||
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||
sphereDia = 0.5"#
|
||||
const sphereDia = 0.5"#
|
||||
.to_string(),
|
||||
},
|
||||
})
|
||||
@ -1251,7 +1251,7 @@ sphereDia = 0.5"#
|
||||
|
||||
// Check the semantic tokens.
|
||||
if let tower_lsp::lsp_types::SemanticTokensResult::Tokens(semantic_tokens) = semantic_tokens {
|
||||
assert_eq!(semantic_tokens.data.len(), 6);
|
||||
assert_eq!(semantic_tokens.data.len(), 7);
|
||||
assert_eq!(semantic_tokens.data[0].length, 15);
|
||||
assert_eq!(semantic_tokens.data[0].delta_start, 0);
|
||||
assert_eq!(semantic_tokens.data[0].delta_line, 0);
|
||||
@ -1279,27 +1279,36 @@ sphereDia = 0.5"#
|
||||
.get_semantic_token_type_index(&SemanticTokenType::COMMENT)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(semantic_tokens.data[3].length, 9);
|
||||
assert_eq!(semantic_tokens.data[3].length, 5);
|
||||
assert_eq!(semantic_tokens.data[3].delta_start, 0);
|
||||
assert_eq!(semantic_tokens.data[3].delta_line, 1);
|
||||
assert_eq!(
|
||||
semantic_tokens.data[3].token_type,
|
||||
server
|
||||
.get_semantic_token_type_index(&SemanticTokenType::KEYWORD)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(semantic_tokens.data[4].length, 9);
|
||||
assert_eq!(semantic_tokens.data[4].delta_start, 6);
|
||||
assert_eq!(semantic_tokens.data[4].delta_line, 0);
|
||||
assert_eq!(
|
||||
semantic_tokens.data[4].token_type,
|
||||
server
|
||||
.get_semantic_token_type_index(&SemanticTokenType::VARIABLE)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(semantic_tokens.data[4].length, 1);
|
||||
assert_eq!(semantic_tokens.data[4].delta_start, 10);
|
||||
assert_eq!(semantic_tokens.data[5].length, 1);
|
||||
assert_eq!(semantic_tokens.data[5].delta_start, 10);
|
||||
assert_eq!(
|
||||
semantic_tokens.data[4].token_type,
|
||||
semantic_tokens.data[5].token_type,
|
||||
server
|
||||
.get_semantic_token_type_index(&SemanticTokenType::OPERATOR)
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(semantic_tokens.data[5].length, 3);
|
||||
assert_eq!(semantic_tokens.data[5].delta_start, 2);
|
||||
assert_eq!(semantic_tokens.data[6].length, 3);
|
||||
assert_eq!(semantic_tokens.data[6].delta_start, 2);
|
||||
assert_eq!(
|
||||
semantic_tokens.data[5].token_type,
|
||||
semantic_tokens.data[6].token_type,
|
||||
server
|
||||
.get_semantic_token_type_index(&SemanticTokenType::NUMBER)
|
||||
.unwrap()
|
||||
@ -1320,7 +1329,7 @@ async fn test_kcl_lsp_document_symbol() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"myVar = 1
|
||||
text: r#"const myVar = 1
|
||||
startSketchOn('XY')"#
|
||||
.to_string(),
|
||||
},
|
||||
@ -1360,7 +1369,7 @@ async fn test_kcl_lsp_document_symbol_tag() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([11.19, 28.35], %)
|
||||
|> line([28.67, -13.25], %, $here)
|
||||
|> line([-4.12, -22.81], %)
|
||||
@ -1457,13 +1466,13 @@ async fn test_kcl_lsp_formatting_extra_parens() {
|
||||
// A ball bearing is a type of rolling-element bearing that uses balls to maintain the separation between the bearing races. The primary purpose of a ball bearing is to reduce rotational friction and support radial and axial loads.
|
||||
|
||||
// Define constants like ball diameter, inside diameter, overhange length, and thickness
|
||||
sphereDia = 0.5
|
||||
insideDia = 1
|
||||
thickness = 0.25
|
||||
overHangLength = .4
|
||||
const sphereDia = 0.5
|
||||
const insideDia = 1
|
||||
const thickness = 0.25
|
||||
const overHangLength = .4
|
||||
|
||||
// Sketch and revolve the inside bearing piece
|
||||
insideRevolve = startSketchOn('XZ')
|
||||
const insideRevolve = startSketchOn('XZ')
|
||||
|> startProfileAt([insideDia / 2, 0], %)
|
||||
|> line([0, thickness + sphereDia / 2], %)
|
||||
|> line([overHangLength, 0], %)
|
||||
@ -1477,7 +1486,7 @@ insideRevolve = startSketchOn('XZ')
|
||||
|> revolve({ axis: 'y' }, %)
|
||||
|
||||
// Sketch and revolve one of the balls and duplicate it using a circular pattern. (This is currently a workaround, we have a bug with rotating on a sketch that touches the rotation axis)
|
||||
sphere = startSketchOn('XZ')
|
||||
const sphere = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
0.05 + insideDia / 2 + thickness,
|
||||
0 - 0.05
|
||||
@ -1499,7 +1508,7 @@ sphere = startSketchOn('XZ')
|
||||
}, %)
|
||||
|
||||
// Sketch and revolve the outside bearing
|
||||
outsideRevolve = startSketchOn('XZ')
|
||||
const outsideRevolve = startSketchOn('XZ')
|
||||
|> startProfileAt([
|
||||
insideDia / 2 + thickness + sphereDia,
|
||||
0
|
||||
@ -1629,7 +1638,7 @@ async fn test_kcl_lsp_rename() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"thing= 1"#.to_string(),
|
||||
text: r#"const thing= 1"#.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
@ -1641,7 +1650,7 @@ async fn test_kcl_lsp_rename() {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 2 },
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 8 },
|
||||
},
|
||||
new_name: "newName".to_string(),
|
||||
work_done_progress_params: Default::default(),
|
||||
@ -1658,7 +1667,7 @@ async fn test_kcl_lsp_rename() {
|
||||
vec![tower_lsp::lsp_types::TextEdit {
|
||||
range: tower_lsp::lsp_types::Range {
|
||||
start: tower_lsp::lsp_types::Position { line: 0, character: 0 },
|
||||
end: tower_lsp::lsp_types::Position { line: 0, character: 7 }
|
||||
end: tower_lsp::lsp_types::Position { line: 0, character: 13 }
|
||||
},
|
||||
new_text: "newName = 1\n".to_string()
|
||||
}]
|
||||
@ -1764,7 +1773,7 @@ async fn test_kcl_lsp_diagnostic_has_lints() {
|
||||
uri: "file:///testlint.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"THING = 10"#.to_string(),
|
||||
text: r#"let THING = 10"#.to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
@ -1850,7 +1859,7 @@ async fn test_copilot_lsp_completions_raw() {
|
||||
let completions = server
|
||||
.get_completions(
|
||||
"kcl".to_string(),
|
||||
r#"bracket = startSketchOn('XY')
|
||||
r#"const bracket = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
"#
|
||||
.to_string(),
|
||||
@ -1869,7 +1878,7 @@ async fn test_copilot_lsp_completions_raw() {
|
||||
let completions_hit_cache = server
|
||||
.get_completions(
|
||||
"kcl".to_string(),
|
||||
r#"bracket = startSketchOn('XY')
|
||||
r#"const bracket = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
"#
|
||||
.to_string(),
|
||||
@ -1909,7 +1918,7 @@ async fn test_copilot_lsp_completions() {
|
||||
path: "file:///test.copilot".to_string(),
|
||||
position: crate::lsp::copilot::types::CopilotPosition { line: 3, character: 3 },
|
||||
relative_path: "test.copilot".to_string(),
|
||||
source: r#"bracket = startSketchOn('XY')
|
||||
source: r#"const bracket = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|
||||
|> close(%)
|
||||
@ -2057,7 +2066,7 @@ async fn test_lsp_initialized() {
|
||||
async fn test_kcl_lsp_on_change_update_ast() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
let same_text = r#"thing = 1"#.to_string();
|
||||
let same_text = r#"const thing = 1"#.to_string();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
@ -2093,7 +2102,7 @@ async fn test_kcl_lsp_on_change_update_ast() {
|
||||
assert_eq!(ast, server.ast_map.get("file:///test.kcl").unwrap().clone());
|
||||
|
||||
// Update the text.
|
||||
let new_text = r#"thing = 2"#.to_string();
|
||||
let new_text = r#"const thing = 2"#.to_string();
|
||||
// Send change file.
|
||||
server
|
||||
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
|
||||
@ -2119,7 +2128,7 @@ async fn test_kcl_lsp_on_change_update_ast() {
|
||||
async fn kcl_test_kcl_lsp_on_change_update_memory() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let same_text = r#"thing = 1"#.to_string();
|
||||
let same_text = r#"const thing = 1"#.to_string();
|
||||
|
||||
// Send open file.
|
||||
server
|
||||
@ -2155,7 +2164,7 @@ async fn kcl_test_kcl_lsp_on_change_update_memory() {
|
||||
assert_eq!(memory, server.memory_map.get("file:///test.kcl").unwrap().clone());
|
||||
|
||||
// Update the text.
|
||||
let new_text = r#"thing = 2"#.to_string();
|
||||
let new_text = r#"const thing = 2"#.to_string();
|
||||
// Send change file.
|
||||
server
|
||||
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
|
||||
@ -2179,7 +2188,7 @@ async fn kcl_test_kcl_lsp_update_units() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let same_text = r#"fn cube = (pos, scale) => {
|
||||
sg = startSketchOn('XY')
|
||||
const sg = startSketchOn('XY')
|
||||
|> startProfileAt(pos, %)
|
||||
|> line([0, scale], %)
|
||||
|> line([scale, 0], %)
|
||||
@ -2187,7 +2196,7 @@ async fn kcl_test_kcl_lsp_update_units() {
|
||||
|
||||
return sg
|
||||
}
|
||||
part001 = cube([0,0], 20)
|
||||
const part001 = cube([0,0], 20)
|
||||
|> close(%)
|
||||
|> extrude(20, %)"#
|
||||
.to_string();
|
||||
@ -2206,7 +2215,7 @@ part001 = cube([0,0], 20)
|
||||
|
||||
// Get the tokens.
|
||||
let tokens = server.token_map.get("file:///test.kcl").unwrap().clone();
|
||||
assert_eq!(tokens.len(), 120);
|
||||
assert_eq!(tokens.len(), 124);
|
||||
|
||||
// Get the ast.
|
||||
let ast = server.ast_map.get("file:///test.kcl").unwrap().clone();
|
||||
@ -2296,7 +2305,7 @@ async fn test_kcl_lsp_diagnostics_on_parse_error() {
|
||||
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
|
||||
|
||||
// Update the text.
|
||||
let new_text = r#"thing = 2"#.to_string();
|
||||
let new_text = r#"const thing = 2"#.to_string();
|
||||
// Send change file.
|
||||
server
|
||||
.did_change(tower_lsp::lsp_types::DidChangeTextDocumentParams {
|
||||
@ -2327,7 +2336,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2347,7 +2356,7 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
|
||||
assert_diagnostic_count(server.diagnostics_map.get("file:///test.kcl").as_deref(), 1);
|
||||
|
||||
// Update the text.
|
||||
let new_text = r#"part001 = startSketchOn('XY')
|
||||
let new_text = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2385,7 +2394,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2434,7 +2443,7 @@ async fn kcl_test_kcl_lsp_full_to_empty_file_updates_ast_and_memory() {
|
||||
async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2527,7 +2536,7 @@ async fn kcl_test_kcl_lsp_code_unchanged_but_has_diagnostics_reexecute() {
|
||||
async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2615,7 +2624,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_unchanged_but_has_diagnostics_reexecute()
|
||||
async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexecute_on_unit_change() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2706,7 +2715,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_diagnostics_reexe
|
||||
async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_on_unit_change() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2776,7 +2785,7 @@ async fn kcl_test_kcl_lsp_code_and_ast_units_unchanged_but_has_memory_reexecute_
|
||||
async fn kcl_test_kcl_lsp_cant_execute_set() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -2973,7 +2982,7 @@ async fn test_kcl_lsp_folding() {
|
||||
async fn kcl_test_kcl_lsp_code_with_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
let code = r#"part001 = startSketchOn('XY')
|
||||
let code = r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -3027,8 +3036,8 @@ async fn kcl_test_kcl_lsp_code_with_parse_error_and_ast_unchanged_but_has_diagno
|
||||
async fn kcl_test_kcl_lsp_code_with_lint_and_ast_unchanged_but_has_diagnostics_reparse() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -3081,8 +3090,8 @@ part001 = startSketchOn('XY')
|
||||
async fn kcl_test_kcl_lsp_code_with_lint_and_parse_error_and_ast_unchanged_but_has_diagnostics_reparse() {
|
||||
let server = kcl_lsp_server(false).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -3136,8 +3145,8 @@ part001 = startSketchOn('XY')
|
||||
async fn kcl_test_kcl_lsp_code_lint_and_ast_unchanged_but_has_diagnostics_reexecute() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
@ -3201,8 +3210,8 @@ part001 = startSketchOn('XY')
|
||||
async fn kcl_test_kcl_lsp_code_lint_reexecute_new_lint() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
@ -3244,14 +3253,14 @@ part001 = startSketchOn('XY')
|
||||
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
|> line([-20, 0], %, $seg01)
|
||||
|> close(%)
|
||||
|> extrude(3.14, %)
|
||||
NEW_LINT = 1"#
|
||||
const NEW_LINT = 1"#
|
||||
.to_string(),
|
||||
}],
|
||||
})
|
||||
@ -3274,8 +3283,8 @@ NEW_LINT = 1"#
|
||||
async fn kcl_test_kcl_lsp_code_lint_reexecute_new_ast_error() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
@ -3317,14 +3326,14 @@ part001 = startSketchOn('XY')
|
||||
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> ^^^^startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %, $seg01)
|
||||
|> line([-20, 0], %, $seg01)
|
||||
|> close(%)
|
||||
|> extrude(3.14, %)
|
||||
NEW_LINT = 1"#
|
||||
const NEW_LINT = 1"#
|
||||
.to_string(),
|
||||
}],
|
||||
})
|
||||
@ -3347,8 +3356,8 @@ NEW_LINT = 1"#
|
||||
async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_parse_error() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -3399,14 +3408,14 @@ part001 = startSketchOn('XY')
|
||||
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: r#"part001 = startSketchOn('XY')
|
||||
text: r#"const part001 = startSketchOn('XY')
|
||||
|> ^^^^startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)
|
||||
|> extrude(3.14, %)
|
||||
NEW_LINT = 1"#
|
||||
const NEW_LINT = 1"#
|
||||
.to_string(),
|
||||
}],
|
||||
})
|
||||
@ -3438,8 +3447,8 @@ NEW_LINT = 1"#
|
||||
async fn kcl_test_kcl_lsp_code_lint_reexecute_had_lint_new_execution_error() {
|
||||
let server = kcl_lsp_server(true).await.unwrap();
|
||||
|
||||
let code = r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
let code = r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %)
|
||||
|> line([0, 20], %)
|
||||
@ -3494,8 +3503,8 @@ part001 = startSketchOn('XY')
|
||||
content_changes: vec![tower_lsp::lsp_types::TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: r#"LINT = 1
|
||||
part001 = startSketchOn('XY')
|
||||
text: r#"const LINT = 1
|
||||
const part001 = startSketchOn('XY')
|
||||
|> startProfileAt([-10, -10], %)
|
||||
|> line([20, 0], %, $seg01)
|
||||
|> line([0, 20], %, $seg01)
|
||||
@ -3543,7 +3552,7 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
language_id: "kcl".to_string(),
|
||||
version: 1,
|
||||
text: "thing = 10".to_string(),
|
||||
text: "const thing = 10".to_string(),
|
||||
},
|
||||
})
|
||||
.await;
|
||||
@ -3554,7 +3563,7 @@ async fn kcl_test_kcl_lsp_completions_number_literal() {
|
||||
text_document: tower_lsp::lsp_types::TextDocumentIdentifier {
|
||||
uri: "file:///test.kcl".try_into().unwrap(),
|
||||
},
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 10 },
|
||||
position: tower_lsp::lsp_types::Position { line: 0, character: 15 },
|
||||
},
|
||||
context: None,
|
||||
partial_result_params: Default::default(),
|
||||
|
@ -4,7 +4,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
execution::ExecState,
|
||||
executor::ExecState,
|
||||
parsing::ast::types::{Node, Program},
|
||||
};
|
||||
|
||||
@ -27,7 +27,7 @@ pub struct OldAstState {
|
||||
/// The exec state.
|
||||
pub exec_state: ExecState,
|
||||
/// The last settings used for execution.
|
||||
pub settings: crate::execution::ExecutorSettings,
|
||||
pub settings: crate::executor::ExecutorSettings,
|
||||
}
|
||||
|
||||
impl From<crate::Program> for CacheInformation {
|
||||
@ -55,7 +55,7 @@ pub struct CacheResult {
|
||||
// the cache.
|
||||
pub fn get_changed_program(
|
||||
info: CacheInformation,
|
||||
new_settings: &crate::execution::ExecutorSettings,
|
||||
new_settings: &crate::executor::ExecutorSettings,
|
||||
) -> Option<CacheResult> {
|
||||
let Some(old) = info.old else {
|
||||
// We have no old info, we need to re-execute the whole thing.
|
||||
@ -109,14 +109,14 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
async fn execute(program: &crate::Program) -> Result<ExecState> {
|
||||
let ctx = crate::execution::ExecutorContext {
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: crate::execution::ContextType::Mock,
|
||||
context_type: crate::executor::ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = crate::execution::ExecState::default();
|
||||
let mut exec_state = crate::executor::ExecState::default();
|
||||
ctx.run(program.clone().into(), &mut exec_state).await?;
|
||||
|
||||
Ok(exec_state)
|
||||
|
@ -4,10 +4,9 @@ use super::types::{DefaultParamVal, ItemVisibility, VariableKind};
|
||||
use crate::parsing::ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CallExpressionKw,
|
||||
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgType, FunctionExpression, Identifier, IfExpression,
|
||||
ImportItem, ImportSelector, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject,
|
||||
NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression,
|
||||
PipeSubstitution, Program, ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
ImportItem, ImportStatement, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode,
|
||||
NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program,
|
||||
ReturnStatement, TagDeclarator, UnaryExpression, VariableDeclaration, VariableDeclarator,
|
||||
};
|
||||
|
||||
/// Position-independent digest of the AST node.
|
||||
@ -53,20 +52,9 @@ impl ImportItem {
|
||||
|
||||
impl ImportStatement {
|
||||
compute_digest!(|slf, hasher| {
|
||||
match &mut slf.selector {
|
||||
ImportSelector::List { items } => {
|
||||
for item in items {
|
||||
hasher.update(item.compute_digest());
|
||||
}
|
||||
}
|
||||
ImportSelector::Glob(_) => hasher.update(b"ImportSelector::Glob"),
|
||||
ImportSelector::None(None) => hasher.update(b"ImportSelector::None"),
|
||||
ImportSelector::None(Some(alias)) => {
|
||||
hasher.update(b"ImportSelector::None");
|
||||
hasher.update(alias.compute_digest());
|
||||
}
|
||||
for item in &mut slf.items {
|
||||
hasher.update(item.compute_digest());
|
||||
}
|
||||
hasher.update(slf.visibility.digestable_id());
|
||||
let path = slf.path.as_bytes();
|
||||
hasher.update(path.len().to_ne_bytes());
|
||||
hasher.update(path);
|
||||
@ -282,7 +270,10 @@ impl ExpressionStatement {
|
||||
|
||||
impl VariableDeclaration {
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.declaration.compute_digest());
|
||||
hasher.update(slf.declarations.len().to_ne_bytes());
|
||||
for declarator in &mut slf.declarations {
|
||||
hasher.update(declarator.compute_digest());
|
||||
}
|
||||
hasher.update(slf.visibility.digestable_id());
|
||||
hasher.update(slf.kind.digestable_id());
|
||||
});
|
||||
|
@ -4,19 +4,14 @@ use async_recursion::async_recursion;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
|
||||
},
|
||||
executor::{BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier},
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, CallExpression,
|
||||
CallExpressionKw, Expr, IfExpression, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Node,
|
||||
ObjectExpression, PipeExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
ObjectExpression, TagDeclarator, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::{
|
||||
args::{Arg, KwArgs},
|
||||
FunctionKind,
|
||||
},
|
||||
std::{args::Arg, FunctionKind},
|
||||
};
|
||||
|
||||
const FLOAT_TO_INT_MAX_DELTA: f64 = 0.01;
|
||||
@ -391,14 +386,7 @@ impl Node<CallExpressionKw> {
|
||||
None
|
||||
};
|
||||
|
||||
let args = crate::std::Args::new_kw(
|
||||
KwArgs {
|
||||
unlabeled,
|
||||
labeled: fn_args,
|
||||
},
|
||||
self.into(),
|
||||
ctx.clone(),
|
||||
);
|
||||
let args = crate::std::Args::new_kw(fn_args, unlabeled, self.into(), ctx.clone());
|
||||
match ctx.stdlib.get_either(fn_name) {
|
||||
FunctionKind::Core(func) => {
|
||||
// Attempt to call the function.
|
||||
@ -819,10 +807,3 @@ impl Property {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<PipeExpression> {
|
||||
#[async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
|
||||
}
|
||||
}
|
@ -1,76 +1,6 @@
|
||||
pub(crate) mod cache;
|
||||
pub(crate) mod digest;
|
||||
pub(crate) mod execute;
|
||||
pub mod modify;
|
||||
pub(crate) mod source_range;
|
||||
pub mod types;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
|
||||
source_range::ModuleId,
|
||||
};
|
||||
|
||||
impl BodyItem {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
BodyItem::ImportStatement(stmt) => stmt.module_id,
|
||||
BodyItem::ExpressionStatement(expression_statement) => expression_statement.module_id,
|
||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.module_id,
|
||||
BodyItem::ReturnStatement(return_statement) => return_statement.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Expr {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
Expr::Literal(literal) => literal.module_id,
|
||||
Expr::Identifier(identifier) => identifier.module_id,
|
||||
Expr::TagDeclarator(tag) => tag.module_id,
|
||||
Expr::BinaryExpression(binary_expression) => binary_expression.module_id,
|
||||
Expr::FunctionExpression(function_expression) => function_expression.module_id,
|
||||
Expr::CallExpression(call_expression) => call_expression.module_id,
|
||||
Expr::CallExpressionKw(call_expression) => call_expression.module_id,
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.module_id,
|
||||
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.module_id,
|
||||
Expr::ArrayExpression(array_expression) => array_expression.module_id,
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.module_id,
|
||||
Expr::ObjectExpression(object_expression) => object_expression.module_id,
|
||||
Expr::MemberExpression(member_expression) => member_expression.module_id,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.module_id,
|
||||
Expr::IfExpression(expr) => expr.module_id,
|
||||
Expr::None(none) => none.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
BinaryPart::Literal(literal) => literal.module_id,
|
||||
BinaryPart::Identifier(identifier) => identifier.module_id,
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.module_id,
|
||||
BinaryPart::CallExpression(call_expression) => call_expression.module_id,
|
||||
BinaryPart::CallExpressionKw(call_expression) => call_expression.module_id,
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.module_id,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.module_id,
|
||||
BinaryPart::IfExpression(e) => e.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
|
||||
MemberObject::Identifier(identifier) => identifier.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiteralIdentifier {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
LiteralIdentifier::Identifier(identifier) => identifier.module_id,
|
||||
LiteralIdentifier::Literal(literal) => literal.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::Point2d,
|
||||
executor::Point2d,
|
||||
parsing::ast::types::{
|
||||
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, Node, PipeExpression,
|
||||
PipeSubstitution, VariableDeclarator,
|
||||
@ -42,7 +42,7 @@ pub async fn modify_ast_for_sketch(
|
||||
// The name of the sketch.
|
||||
sketch_name: &str,
|
||||
// The type of plane the sketch is on. `XY` or `XZ`, etc
|
||||
plane: crate::execution::PlaneType,
|
||||
plane: crate::executor::PlaneType,
|
||||
// The ID of the parent sketch.
|
||||
sketch_id: uuid::Uuid,
|
||||
) -> Result<String, KclError> {
|
||||
@ -196,7 +196,7 @@ fn create_start_sketch_on(
|
||||
name: &str,
|
||||
start: [f64; 2],
|
||||
end: [f64; 2],
|
||||
plane: crate::execution::PlaneType,
|
||||
plane: crate::executor::PlaneType,
|
||||
additional_lines: Vec<[f64; 2]>,
|
||||
) -> Result<Node<VariableDeclarator>, KclError> {
|
||||
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;
|
||||
|