Compare commits

..

1 Commits

Author SHA1 Message Date
8c6e62cfc4 bump and release kcl-lib 2024-12-03 15:21:34 -05:00
478 changed files with 112623 additions and 117434 deletions

View File

@ -362,17 +362,6 @@ jobs:
- name: List artifacts - name: List artifacts
run: "ls -R out" run: "ls -R out"
- 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,
# see https://github.com/actions/checkout/issues/1471
git fetch --prune --unshallow --tags
export TAG="nightly-${VERSION}"
export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
export NOTES=$(./scripts/get-nightly-changelog.sh)
yarn files:set-notes
- name: Authenticate to Google Cloud - name: Authenticate to Google Cloud
if: ${{ env.IS_NIGHTLY == 'true' }} if: ${{ env.IS_NIGHTLY == 'true' }}
uses: 'google-github-actions/auth@v2.1.7' uses: 'google-github-actions/auth@v2.1.7'
@ -393,18 +382,3 @@ jobs:
glob: '*' glob: '*'
parent: false parent: false
destination: 'dl.kittycad.io/releases/modeling-app/nightly' 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
with:
script: |
const { VERSION } = process.env
const { owner, repo } = context.repo
const { sha } = context
const ref = `refs/tags/nightly-${VERSION}`
github.rest.git.createRef({ owner, repo, sha, ref })

View File

@ -126,7 +126,11 @@ jobs:
destination: 'dl.kittycad.io/releases/modeling-app' destination: 'dl.kittycad.io/releases/modeling-app'
- name: Invalidate bucket cache on latest*.yml and last_download.json files - 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 - name: Upload release files to Github
if: ${{ github.event_name == 'release' }} if: ${{ github.event_name == 'release' }}

1
.gitignore vendored
View File

@ -61,7 +61,6 @@ Mac_App_Distribution.provisionprofile
*.tsbuildinfo *.tsbuildinfo
src/wasm-lib/pkg src/wasm-lib/pkg
.eslintcache
venv venv
.vite/ .vite/

2
.nvmrc
View File

@ -1 +1 @@
v22.12.0 v21.7.3

View File

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

View File

@ -99,7 +99,7 @@ yarn tron:start
This will start the application and hot-reload on changes. This will start the application and hot-reload on changes.
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows). Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
To build, run `yarn tron:package`. To build, run `yarn tron:package`.

File diff suppressed because one or more lines are too long

View File

@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
```js ```js
r = 10 // radius r = 10 // radius
// Call `map`, using an anonymous function instead of a named one. // Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], fn(id) { circles = map([1..3], (id) {
return startSketchOn("XY") return startSketchOn("XY")
|> circle({ center = [id * 2 * r, 0], radius = r }, %) |> circle({ center = [id * 2 * r, 0], radius = r }, %)
}) })

View File

@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// an anonymous `add` function as its parameter, instead of declaring a // an anonymous `add` function as its parameter, instead of declaring a
// named function outside. // named function outside.
arr = [1, 2, 3] arr = [1, 2, 3]
sum = reduce(arr, 0, fn(i, result_so_far) { sum = reduce(arr, 0, (i, result_so_far) {
return i + result_so_far return i + result_so_far
}) })
@ -84,7 +84,7 @@ fn decagon(radius) {
// Use a `reduce` to draw the remaining decagon sides. // Use a `reduce` to draw the remaining decagon sides.
// For each number in the array 1..10, run the given function, // For each number in the array 1..10, run the given function,
// which takes a partially-sketched decagon and adds one more edge to it. // which takes a partially-sketched decagon and adds one more edge to it.
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) { fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
// Draw one edge of the decagon. // Draw one edge of the decagon.
x = cos(stepAngle * i) * radius x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius y = sin(stepAngle * i) * radius

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
/* add the following code to the editor ($ error is not a valid line) /* add the following code to the editor ($ error is not a valid line)
$ error $ error
topAng = 30 const topAng = 30
bottomAng = 25 const bottomAng = 25
*/ */
await u.codeLocator.click() await u.codeLocator.click()
await page.keyboard.type('$ error') await page.keyboard.type('$ error')
@ -474,14 +474,12 @@ test.describe('Editor tests', () => {
await page.keyboard.type('bottomAng = 25') await page.keyboard.type('bottomAng = 25')
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// error in gutter // error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible() await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
// error text on hover // error text on hover
await page.hover('.cm-lint-marker-error') await page.hover('.cm-lint-marker-error')
await expect( await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
page.getByText('Tag names must not be empty').first()
).toBeVisible()
// select the line that's causing the error and delete it // select the line that's causing the error and delete it
await page.getByText('$ error').click() await page.getByText('$ error').click()
@ -520,10 +518,7 @@ test.describe('Editor tests', () => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible() await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
}) })
// TODO currently multiple source ranges are not supported test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
page,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(

View File

@ -6,8 +6,6 @@ export class ToolbarFixture {
public page: Page public page: Page
extrudeButton!: Locator extrudeButton!: Locator
loftButton!: Locator
shellButton!: Locator
offsetPlaneButton!: Locator offsetPlaneButton!: Locator
startSketchBtn!: Locator startSketchBtn!: Locator
lineBtn!: Locator lineBtn!: Locator
@ -28,8 +26,6 @@ export class ToolbarFixture {
reConstruct = (page: Page) => { reConstruct = (page: Page) => {
this.page = page this.page = page
this.extrudeButton = page.getByTestId('extrude') this.extrudeButton = page.getByTestId('extrude')
this.loftButton = page.getByTestId('loft')
this.shellButton = page.getByTestId('shell')
this.offsetPlaneButton = page.getByTestId('plane-offset') this.offsetPlaneButton = page.getByTestId('plane-offset')
this.startSketchBtn = page.getByTestId('sketch') this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line') this.lineBtn = page.getByTestId('line')

View File

@ -677,259 +677,3 @@ test(`Offset plane point-and-click`, async ({
await scene.expectPixelColor([74, 74, 74], testPoint, 15) await scene.expectPixelColor([74, 74, 74], testPoint, 15)
}) })
}) })
const loftPointAndClickCases = [
{ shouldPreselect: true },
{ shouldPreselect: false },
]
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
app,
page,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 30 }, %)
plane001 = offsetPlane('XZ', 50)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 20 }, %)
`
await app.initialise(initialCode)
// One dumb hardcoded screen pixel value
const testPoint = { x: 575, y: 200 }
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
const [clickOnSketch2] = scene.makeMouseHelpers(
testPoint.x,
testPoint.y + 80
)
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
await test.step(`Look for the white of the sketch001 shape`, async () => {
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
async function selectSketches() {
await clickOnSketch1()
await page.keyboard.down('Shift')
await clickOnSketch2()
await app.page.waitForTimeout(500)
await page.keyboard.up('Shift')
}
if (!shouldPreselect) {
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.expectState({
stage: 'arguments',
currentArgKey: 'selection',
currentArgValue: '',
headerArguments: { Selection: '' },
highlightedHeaderArg: 'selection',
commandName: 'Loft',
})
await selectSketches()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
} else {
await test.step(`Preselect the two sketches`, async () => {
await selectSketches()
})
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
await toolbar.loftButton.click()
await cmdBar.progressCmdBar()
await cmdBar.expectState({
stage: 'review',
headerArguments: { Selection: '2 faces' },
commandName: 'Loft',
})
await cmdBar.progressCmdBar()
})
}
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await editor.expectEditor.toContain(loftDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: [loftDeclaration],
highlightedCode: '',
})
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
})
})
})
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 app.page.waitForTimeout(500)
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 app.page.waitForTimeout(500)
})
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)
})
})

View File

@ -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( test(
'when code with error first loads you get errors in console', 'when code with error first loads you get errors in console',
{ tag: '@electron' }, { tag: '@electron' },

View File

@ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0])
const u = await getUtils(page) const u = await getUtils(page)
// Constants and locators // Constants and locators
const planeColor: [number, number, number] = [161, 220, 155] const planeColor: [number, number, number] = [170, 220, 170]
const bgColor: [number, number, number] = [27, 27, 27] const bgColor: [number, number, number] = [27, 27, 27]
const middlePixelIsColor = async (color: [number, number, number]) => { const middlePixelIsColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff({ x: 600, y: 250 }, color) return u.getGreatestPixDiff({ x: 600, y: 250 }, color)

View File

@ -7,8 +7,6 @@ try {
.split('\n') .split('\n')
.filter((line) => line && line.length > 1) .filter((line) => line && line.length > 1)
.forEach((line) => { .forEach((line) => {
// Allow line comments.
if (line.trimStart().startsWith('#')) return
const [key, value] = line.split('=') const [key, value] = line.split('=')
// prefer env vars over secrets file // prefer env vars over secrets file
secrets[key] = process.env[key] || (value as any).replaceAll('"', '') secrets[key] = process.env[key] || (value as any).replaceAll('"', '')

View File

@ -943,110 +943,6 @@ sketch002 = startSketchOn(extrude001, 'END')
`.replace(/\s/g, '') `.replace(/\s/g, '')
) )
}) })
/* TODO: once we fix bug turn on.
test('empty-scene default-planes act as expected when spaces in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})
test('empty-scene default-planes act as expected when only code comments in file', async ({
page,
browserName,
}) => {
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const XYPlanePoint = { x: 774, y: 116 } as const
const unHoveredColor: [number, number, number] = [47, 47, 93]
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await u.openAndClearDebugPanel()
// Fill with spaces
await u.codeLocator.fill(`// this is a code comments ya nerds
`)
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
await page.waitForTimeout(200)
// color should not change for having been hovered
expect(
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
).toBeLessThan(8)
})*/
test('empty-scene default-planes act as expected', async ({ test('empty-scene default-planes act as expected', async ({
page, page,
browserName, browserName,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@ -26,17 +26,7 @@ test.describe('Testing constraints', () => {
}) })
const u = await getUtils(page) const u = await getUtils(page)
// constants and locators const PUR = 400 / 37.5 //pixeltoUnitRatio
const lengthValue = {
old: '20',
new: '25',
}
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart() await u.waitForAuthSkipAppStart()
@ -46,26 +36,26 @@ test.describe('Testing constraints', () => {
await u.closeDebugPanel() await u.closeDebugPanel()
// Click the line of code for line. // Click the line of code for line.
// TODO remove this and reinstate `await topHorzSegmentClick()` await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// enter sketch again // enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click() await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(500) // wait for animation await page.waitForTimeout(500) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.down('Shift')
await page.mouse.click(834, 244)
await page.keyboard.up('Shift')
await page await page
.getByRole('button', { name: 'dimension Length', exact: true }) .getByRole('button', { name: 'dimension Length', exact: true })
.click() .click()
await expect(cmdBarKclInput).toHaveText('20') await page.getByText('Add constraining value').click()
await cmdBarKclInput.fill(lengthValue.new)
await expect(
page.getByText(`Can't calculate`),
`Something went wrong with the KCL expression evaluation`
).not.toBeVisible()
await cmdBarSubmitButton.click()
await expect(page.locator('.cm-content')).toHaveText( await expect(page.locator('.cm-content')).toHaveText(
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)` `length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
) )
// Make sure we didn't pop out of sketch mode. // Make sure we didn't pop out of sketch mode.
@ -76,6 +66,7 @@ test.describe('Testing constraints', () => {
await page.waitForTimeout(500) // wait for animation await page.waitForTimeout(500) // wait for animation
// Exit sketch // Exit sketch
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect( await expect(
page.getByRole('button', { name: 'Exit Sketch' }) page.getByRole('button', { name: 'Exit Sketch' })
@ -533,7 +524,7 @@ part002 = startSketchOn('XZ')
}) })
} }
}) })
test.describe('Test Angle constraint single selection', () => { test.describe('Test Angle/Length constraint single selection', () => {
const cases = [ const cases = [
{ {
testName: 'Angle - Add variable', testName: 'Angle - Add variable',
@ -547,6 +538,18 @@ part002 = startSketchOn('XZ')
constraint: 'angle', constraint: 'angle',
value: '83, 78.33', value: '83, 78.33',
}, },
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const ] as const
for (const { testName, addVariable, value, constraint } of cases) { for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => { test(`${testName}`, async ({ page }) => {
@ -605,90 +608,6 @@ part002 = startSketchOn('XZ')
}) })
} }
}) })
test.describe('Test Length constraint single selection', () => {
const cases = [
{
testName: 'Length - Add variable',
addVariable: true,
constraint: 'length',
value: '83, length001',
},
{
testName: 'Length - No variable',
addVariable: false,
constraint: 'length',
value: '83, 78.33',
},
] as const
for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ page }) => {
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarKclVariableNameInput =
page.getByPlaceholder('Variable name')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`yo = 5
part001 = startSketchOn('XZ')
|> startProfileAt([-7.54, -26.74], %)
|> line([74.36, 130.4], %)
|> line([78.92, -120.11], %)
|> line([9.16, 77.79], %)
|> line([51.19, 48.97], %)
part002 = startSketchOn('XZ')
|> startProfileAt([299.05, 231.45], %)
|> xLine(-425.34, %, $seg_what)
|> yLine(-264.06, %)
|> xLine(segLen(seg_what), %)
|> lineTo([profileStartX(%), profileStartY(%)], %)`
)
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await u.waitForAuthSkipAppStart()
await page.getByText('line([74.36, 130.4], %)').click()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
const line3 = await u.getSegmentBodyCoords(
`[data-overlay-index="${2}"]`
)
await page.mouse.click(line3.x, line3.y)
await page
.getByRole('button', {
name: 'Length: open menu',
})
.click()
await page.getByTestId('dropdown-constraint-' + constraint).click()
if (!addVariable) {
await test.step(`Clear the variable input`, async () => {
await cmdBarKclVariableNameInput.clear()
await cmdBarKclVariableNameInput.press('Backspace')
})
}
await expect(cmdBarKclInput).toHaveText('78.33')
await cmdBarSubmitButton.click()
const changedCode = `|> angledLine([${value}], %)`
await expect(page.locator('.cm-content')).toContainText(changedCode)
// checking active assures the cursor is where it should be
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
})
}
})
test.describe('Many segments - no modal constraints', () => { test.describe('Many segments - no modal constraints', () => {
const cases = [ const cases = [
{ {
@ -949,15 +868,6 @@ part002 = startSketchOn('XZ')
|> line([3.13, -2.4], %)` |> line([3.13, -2.4], %)`
) )
}) })
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
.getByRole('textbox')
const cmdBarSubmitButton = page.getByRole('button', {
name: 'arrow right Continue',
})
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
@ -1018,8 +928,8 @@ part002 = startSketchOn('XZ')
// await page.getByRole('button', { name: 'length', exact: true }).click() // await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByTestId('dropdown-constraint-length').click() await page.getByTestId('dropdown-constraint-length').click()
await cmdBarKclInput.fill('10') await page.getByLabel('length Value').fill('10')
await cmdBarSubmitButton.click() await page.getByRole('button', { name: 'Add constraining value' }).click()
activeLinesContent = await page.locator('.cm-activeLine').all() activeLinesContent = await page.locator('.cm-activeLine').all()
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`) await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)

View File

@ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => {
y: screenHeight * 0.4, y: screenHeight * 0.4,
} }
const backgroundColor: [number, number, number] = [29, 29, 29] const backgroundColor: [number, number, number] = [29, 29, 29]
const xzPlaneColor: [number, number, number] = [82, 55, 96] const xzPlaneColor: [number, number, number] = [50, 50, 99]
const locationToHaveColor = async (color: [number, number, number]) => { const locationToHaveColor = async (color: [number, number, number]) => {
return u.getGreatestPixDiff(checkedScreenLocation, color) return u.getGreatestPixDiff(checkedScreenLocation, color)
} }

View File

@ -91,14 +91,7 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count() await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0) ).toBeGreaterThan(0)
await unconstrainedLocator.click() await unconstrainedLocator.click()
await expect( await page.getByText('Add variable').click()
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText(expectFinal) await expect(page.locator('.cm-content')).toContainText(expectFinal)
} }
@ -158,14 +151,7 @@ test.describe('Testing segment overlays', () => {
await page.getByTestId('constraint-symbol-popover').count() await page.getByTestId('constraint-symbol-popover').count()
).toBeGreaterThan(0) ).toBeGreaterThan(0)
await unconstrainedLocator.click() await unconstrainedLocator.click()
await expect( await page.getByText('Add variable').click()
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
).toBeFocused()
await page
.getByRole('button', {
name: 'arrow right Continue',
})
.click()
await expect(page.locator('.cm-content')).toContainText( await expect(page.locator('.cm-content')).toContainText(
expectAfterUnconstrained expectAfterUnconstrained
) )

View File

@ -81,7 +81,6 @@
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000", "simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "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:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:stop": "kill-port 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages", "fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages", "fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh", "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-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.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", "postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"", "xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
"make:dev": "make dev", "make:dev": "make dev",
@ -161,7 +158,6 @@
"@electron/rebuild": "^3.6.0", "@electron/rebuild": "^3.6.0",
"@iarna/toml": "^2.2.5", "@iarna/toml": "^2.2.5",
"@lezer/generator": "^1.7.1", "@lezer/generator": "^1.7.1",
"@nabla/vite-plugin-eslint": "^2.0.5",
"@playwright/test": "^1.46.1", "@playwright/test": "^1.46.1",
"@testing-library/jest-dom": "^5.14.1", "@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2", "@testing-library/react": "^15.0.2",
@ -174,7 +170,7 @@
"@types/pixelmatch": "^5.2.6", "@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4", "@types/pngjs": "^6.0.4",
"@types/react": "^18.3.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/react-modal": "^3.16.3",
"@types/three": "^0.163.0", "@types/three": "^0.163.0",
"@types/ua-parser-js": "^0.7.39", "@types/ua-parser-js": "^0.7.39",
@ -211,11 +207,12 @@
"ts-node": "^10.0.0", "ts-node": "^10.0.0",
"typescript": "^5.7.2", "typescript": "^5.7.2",
"vite": "^5.4.6", "vite": "^5.4.6",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-package-version": "^1.1.0", "vite-plugin-package-version": "^1.1.0",
"vite-tsconfig-paths": "^4.3.2", "vite-tsconfig-paths": "^4.3.2",
"vitest": "^1.6.0", "vitest": "^1.6.0",
"vitest-webgl-canvas-mock": "^1.1.0", "vitest-webgl-canvas-mock": "^1.1.0",
"wasm-pack": "^0.13.1", "wasm-pack": "^0.13.0",
"ws": "^8.17.0", "ws": "^8.17.0",
"yarn": "^1.22.22" "yarn": "^1.22.22"
} }

View File

@ -149,6 +149,11 @@
"title": "Tire", "title": "Tire",
"description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities." "description": "A tire is a critical component of a vehicle that provides the necessary traction and grip between the car and the road. It supports the vehicle's weight and absorbs shocks from road irregularities."
}, },
{
"file": "wall-e.kcl",
"title": "WALL-E",
"description": "An inquisitive trash compacting robot imagined by Disney Pixar Animation Studios"
},
{ {
"file": "washer.kcl", "file": "washer.kcl",
"title": "Washer", "title": "Washer",

View File

@ -1,5 +0,0 @@
#!/bin/bash
echo "## What's Changed"
git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
echo ""
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}"

View File

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

View File

@ -200,10 +200,7 @@ function CoreDump() {
() => new CoreDumpManager(engineCommandManager, codeManager, token), () => new CoreDumpManager(engineCommandManager, codeManager, token),
[] []
) )
// TODO: revisit once progress is made on upstream issue useHotkeyWrapper(['mod + shift + .'], () => {
// https://github.com/JohannesKlauss/react-hotkeys-hook/issues/1064
// const hotkey = process.platform !== 'linux' ? 'mod + shift + .' : 'mod + shift + >'
useHotkeyWrapper(['mod + shift + .', 'mod + shift + >'], () => {
toast toast
.promise( .promise(
coreDump(coreDumpManager, true), coreDump(coreDumpManager, true),

View File

@ -155,6 +155,7 @@ export class CameraControls {
this.camera.zoom = camProps.zoom || 1 this.camera.zoom = camProps.zoom || 1
} }
this.camera.updateProjectionMatrix() this.camera.updateProjectionMatrix()
console.log('doing this thing', camProps)
this.update(true) this.update(true)
} }
@ -272,26 +273,14 @@ export class CameraControls {
camSettings.center.y, camSettings.center.y,
camSettings.center.z camSettings.center.z
) )
const orientation = new Quaternion( const quat = new Quaternion(
camSettings.orientation.x, camSettings.orientation.x,
camSettings.orientation.y, camSettings.orientation.y,
camSettings.orientation.z, camSettings.orientation.z,
camSettings.orientation.w camSettings.orientation.w
).invert() ).invert()
const newUp = new Vector3( this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
camSettings.up.x,
camSettings.up.y,
camSettings.up.z
)
this.camera.quaternion.set(
orientation.x,
orientation.y,
orientation.z,
orientation.w
)
this.camera.up.copy(newUp)
this.camera.updateProjectionMatrix()
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) { if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera() this.useOrthographicCamera()
} }
@ -1175,7 +1164,7 @@ export class CameraControls {
this.camera.updateProjectionMatrix() this.camera.updateProjectionMatrix()
} }
if (this.syncDirection === 'clientToEngine' || forceUpdate) { if (this.syncDirection === 'clientToEngine' || forceUpdate)
this.throttledUpdateEngineCamera({ this.throttledUpdateEngineCamera({
quaternion: this.camera.quaternion, quaternion: this.camera.quaternion,
position: this.camera.position, position: this.camera.position,
@ -1183,7 +1172,6 @@ export class CameraControls {
isPerspective: this.isPerspective, isPerspective: this.isPerspective,
target: this.target, target: this.target,
}) })
}
this.deferReactUpdate(this.reactCameraProperties) this.deferReactUpdate(this.reactCameraProperties)
Object.values(this._camChangeCallbacks).forEach((cb) => cb()) Object.values(this._camChangeCallbacks).forEach((cb) => cb())
} }

View File

@ -29,9 +29,6 @@ import {
Expr, Expr,
parse, parse,
recast, recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
} from 'lang/wasm' } from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon' import { CustomIcon, CustomIconName } from 'components/CustomIcon'
import { ConstrainInfo } from 'lang/std/stdTypes' import { ConstrainInfo } from 'lang/std/stdTypes'
@ -415,15 +412,14 @@ export async function deleteSegment({
if (err(modifiedAst)) return Promise.reject(modifiedAst) if (err(modifiedAst)) return Promise.reject(modifiedAst)
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
const pResult = parse(newCode) modifiedAst = parse(newCode)
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (err(modifiedAst)) return Promise.reject(modifiedAst)
modifiedAst = pResult.program
const testExecute = await executeAst({ const testExecute = await executeAst({
ast: modifiedAst, ast: modifiedAst,
idGenerator: kclManager.execState.idGenerator,
useFakeExecutor: true,
engineCommandManager: engineCommandManager, engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
}) })
if (testExecute.errors.length) { if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.') toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -505,8 +501,7 @@ const ConstraintSymbol = ({
constrainInfo: ConstrainInfo constrainInfo: ConstrainInfo
verticalPosition: 'top' | 'bottom' verticalPosition: 'top' | 'bottom'
}) => { }) => {
const { commandBarSend } = useCommandsContext() const { context, send } = useModelingContext()
const { context } = useModelingContext()
const varNameMap: { const varNameMap: {
[key in ConstrainInfo['type']]: { [key in ConstrainInfo['type']]: {
varName: string varName: string
@ -595,9 +590,7 @@ const ConstraintSymbol = ({
if (err(_node)) return if (err(_node)) return
const node = _node.node const node = _node.node
const range: SourceRange = node const range: SourceRange = node ? [node.start, node.end] : [0, 0]
? [node.start, node.end, true]
: defaultSourceRange()
if (_type === 'intersectionTag') return null if (_type === 'intersectionTag') return null
@ -619,34 +612,25 @@ const ConstraintSymbol = ({
editorManager.setHighlightRange([range]) editorManager.setHighlightRange([range])
}} }}
onMouseLeave={() => { onMouseLeave={() => {
editorManager.setHighlightRange([defaultSourceRange()]) editorManager.setHighlightRange([[0, 0]])
}} }}
// disabled={isConstrained || !convertToVarEnabled} // disabled={isConstrained || !convertToVarEnabled}
// disabled={implicitDesc} TODO why does this change styles that are hard to override? // disabled={implicitDesc} TODO why does this change styles that are hard to override?
onClick={toSync(async () => { onClick={toSync(async () => {
if (!isConstrained) { if (!isConstrained) {
commandBarSend({ send({
type: 'Find and select command', type: 'Convert to variable',
data: { data: {
name: 'Constrain with named value', pathToNode,
groupId: 'modeling', variableName: varName,
argDefaultValues: {
currentValue: {
pathToNode,
variableName: varName,
valueText: value,
},
},
}, },
}) })
} else if (isConstrained) { } else if (isConstrained) {
try { try {
const pResult = parse(recast(kclManager.ast)) const parsed = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(parsed)) return Promise.reject(parsed)
return Promise.reject(pResult)
const _node1 = getNodeFromPath<CallExpression>( const _node1 = getNodeFromPath<CallExpression>(
pResult.program!, parsed,
pathToNode, pathToNode,
'CallExpression', 'CallExpression',
true true

View File

@ -48,9 +48,6 @@ import {
VariableDeclarator, VariableDeclarator,
sketchFromKclValue, sketchFromKclValue,
sketchFromKclValueOptional, sketchFromKclValueOptional,
defaultSourceRange,
sourceRangeFromRust,
resultIsOk,
} from 'lang/wasm' } from 'lang/wasm'
import { import {
engineCommandManager, engineCommandManager,
@ -498,9 +495,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
const sketch = sketchFromPathToNode({ const sketch = sketchFromPathToNode({
@ -532,7 +530,7 @@ export class SceneEntities {
const segPathToNode = getNodePathFromSourceRange( const segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange) sketch.start.__geoMeta.sourceRange
) )
if (sketch?.paths?.[0]?.type !== 'Circle') { if (sketch?.paths?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({ const _profileStart = createProfileStartHandle({
@ -554,7 +552,7 @@ export class SceneEntities {
sketch.paths.forEach((segment, index) => { sketch.paths.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange( let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange) segment.__geoMeta.sourceRange
) )
if ( if (
draftExpressionsIndices && draftExpressionsIndices &&
@ -563,12 +561,12 @@ export class SceneEntities {
const previousSegment = sketch.paths[index - 1] || sketch.start const previousSegment = sketch.paths[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange( const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst, maybeModdedAst,
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange) previousSegment.__geoMeta.sourceRange
) )
const bodyIndex = previousSegmentPathToNode[1][0] const bodyIndex = previousSegmentPathToNode[1][0]
segPathToNode = getNodePathFromSourceRange( segPathToNode = getNodePathFromSourceRange(
truncatedAst, truncatedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange) segment.__geoMeta.sourceRange
) )
segPathToNode[1][0] = bodyIndex segPathToNode[1][0] = bodyIndex
} }
@ -577,10 +575,7 @@ export class SceneEntities {
index <= draftExpressionsIndices.end && index <= draftExpressionsIndices.end &&
index >= draftExpressionsIndices.start index >= draftExpressionsIndices.start
const isSelected = selectionRanges?.graphSelections.some((selection) => const isSelected = selectionRanges?.graphSelections.some((selection) =>
isOverlap( isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
selection?.codeRef?.range,
sourceRangeFromRust(segment.__geoMeta.sourceRange)
)
) )
let seg: Group let seg: Group
@ -662,11 +657,13 @@ export class SceneEntities {
} }
updateAstAndRejigSketch = async ( updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode, sketchPathToNode: PathToNode,
modifiedAst: Node<Program>, modifiedAst: Node<Program> | Error,
forward: [number, number, number], forward: [number, number, number],
up: [number, number, number], up: [number, number, number],
origin: [number, number, number] origin: [number, number, number]
) => { ) => {
if (err(modifiedAst)) return modifiedAst
const nextAst = await kclManager.updateAst(modifiedAst, false) const nextAst = await kclManager.updateAst(modifiedAst, false)
await this.tearDownSketch({ removeAxis: false }) await this.tearDownSketch({ removeAxis: false })
sceneInfra.resetMouseListeners() sceneInfra.resetMouseListeners()
@ -701,7 +698,8 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) 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( const sg = sketchFromKclValue(
kclManager.programMemory.get(variableDeclarationName), kclManager.programMemory.get(variableDeclarationName),
@ -723,9 +721,8 @@ export class SceneEntities {
pathToNode: sketchPathToNode, pathToNode: sketchPathToNode,
}) })
if (trap(mod)) return Promise.reject(mod) if (trap(mod)) return Promise.reject(mod)
const pResult = parse(recast(mod.modifiedAst)) const modifiedAst = parse(recast(mod.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(modifiedAst)) return Promise.reject(modifiedAst)
const modifiedAst = pResult.program
const draftExpressionsIndices = { start: index, end: index } const draftExpressionsIndices = { start: index, end: index }
@ -901,9 +898,10 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = _node1.node?.declaration.id?.name || '' const variableDeclarationName =
const startSketchOn = _node1.node?.declaration _node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOnInit = startSketchOn?.init const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [ const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentA'),
@ -911,14 +909,14 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'), findUniqueName(_ast, 'rectangleSegmentC'),
] ]
startSketchOn.init = createPipeExpression([ startSketchOn[0].init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags), ...getRectangleCallExpressions(rectangleOrigin, tags),
]) ])
const pResult = parse(recast(_ast)) let _recastAst = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = pResult.program _ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({ const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode, sketchPathToNode,
@ -941,7 +939,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return Promise.reject(_node) 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 x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -952,9 +950,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -990,7 +989,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declaration.init const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type !== 'PipeExpression') { if (sketchInit.type !== 'PipeExpression') {
return return
@ -999,10 +998,9 @@ export class SceneEntities {
updateRectangleSketch(sketchInit, x, y, tags[0]) updateRectangleSketch(sketchInit, x, y, tags[0])
const newCode = recast(_ast) const newCode = recast(_ast)
const pResult = parse(newCode) let _recastAst = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult)) if (trap(_recastAst)) return
return Promise.reject(pResult) _ast = _recastAst
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool // Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
@ -1015,9 +1013,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
@ -1056,9 +1055,10 @@ export class SceneEntities {
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
// startSketchOn already exists // startSketchOn already exists
const variableDeclarationName = _node1.node?.declaration.id?.name || '' const variableDeclarationName =
const startSketchOn = _node1.node?.declaration _node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOnInit = startSketchOn?.init const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
const tags: [string, string, string] = [ const tags: [string, string, string] = [
findUniqueName(_ast, 'rectangleSegmentA'), findUniqueName(_ast, 'rectangleSegmentA'),
@ -1066,14 +1066,14 @@ export class SceneEntities {
findUniqueName(_ast, 'rectangleSegmentC'), findUniqueName(_ast, 'rectangleSegmentC'),
] ]
startSketchOn.init = createPipeExpression([ startSketchOn[0].init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
...getRectangleCallExpressions(rectangleOrigin, tags), ...getRectangleCallExpressions(rectangleOrigin, tags),
]) ])
const pResult = parse(recast(_ast)) let _recastAst = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = pResult.program _ast = _recastAst
const { programMemoryOverride, truncatedAst } = await this.setupSketch({ const { programMemoryOverride, truncatedAst } = await this.setupSketch({
sketchPathToNode, sketchPathToNode,
@ -1096,7 +1096,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return Promise.reject(_node) 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 x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1] const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
@ -1114,9 +1114,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -1152,7 +1153,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declaration.init const sketchInit = _node.node?.declarations?.[0]?.init
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
updateCenterRectangleSketch( updateCenterRectangleSketch(
@ -1164,10 +1165,9 @@ export class SceneEntities {
rectangleOrigin[1] rectangleOrigin[1]
) )
const pResult = parse(recast(_ast)) let _recastAst = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(_recastAst)) return
return Promise.reject(pResult) _ast = _recastAst
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool // Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
@ -1180,9 +1180,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
@ -1221,11 +1222,12 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node1)) return Promise.reject(_node1) if (trap(_node1)) return Promise.reject(_node1)
const variableDeclarationName = _node1.node?.declaration.id?.name || '' const variableDeclarationName =
const startSketchOn = _node1.node?.declaration _node1.node?.declarations?.[0]?.id?.name || ''
const startSketchOnInit = startSketchOn?.init const startSketchOn = _node1.node?.declarations
const startSketchOnInit = startSketchOn?.[0]?.init
startSketchOn.init = createPipeExpression([ startSketchOn[0].init = createPipeExpression([
startSketchOnInit, startSketchOnInit,
createCallExpressionStdLib('circle', [ createCallExpressionStdLib('circle', [
createObjectExpression({ createObjectExpression({
@ -1239,9 +1241,9 @@ export class SceneEntities {
]), ]),
]) ])
const pResult = parse(recast(_ast)) let _recastAst = parse(recast(_ast))
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult) if (trap(_recastAst)) return Promise.reject(_recastAst)
_ast = pResult.program _ast = _recastAst
// do a quick mock execution to get the program memory up-to-date // do a quick mock execution to get the program memory up-to-date
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
@ -1267,7 +1269,7 @@ export class SceneEntities {
) )
let modded = structuredClone(truncatedAst) let modded = structuredClone(truncatedAst)
if (trap(_node)) return 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 x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1] const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
@ -1297,9 +1299,10 @@ export class SceneEntities {
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: modded, ast: modded,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -1335,7 +1338,7 @@ export class SceneEntities {
'VariableDeclaration' 'VariableDeclaration'
) )
if (trap(_node)) return if (trap(_node)) return
const sketchInit = _node.node?.declaration.init const sketchInit = _node.node?.declarations?.[0]?.init
let modded = structuredClone(_ast) let modded = structuredClone(_ast)
if (sketchInit.type === 'PipeExpression') { if (sketchInit.type === 'PipeExpression') {
@ -1362,10 +1365,9 @@ export class SceneEntities {
const newCode = recast(modded) const newCode = recast(modded)
if (err(newCode)) return if (err(newCode)) return
const pResult = parse(newCode) let _recastAst = parse(newCode)
if (trap(pResult) || !resultIsOk(pResult)) if (trap(_recastAst)) return Promise.reject(_recastAst)
return Promise.reject(pResult) _ast = _recastAst
_ast = pResult.program
// Update the primary AST and unequip the rectangle tool // Update the primary AST and unequip the rectangle tool
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
@ -1658,7 +1660,7 @@ export class SceneEntities {
kclManager.programMemory, kclManager.programMemory,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: [node.start, node.end, true], sourceRange: [node.start, node.end],
}, },
getChangeSketchInput() getChangeSketchInput()
) )
@ -1681,9 +1683,10 @@ export class SceneEntities {
codeManager.updateCodeEditor(code) codeManager.updateCodeEditor(code)
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: truncatedAst, ast: truncatedAst,
useFakeExecutor: true,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride, programMemoryOverride,
idGenerator: kclManager.execState.idGenerator,
}) })
const programMemory = execState.memory const programMemory = execState.memory
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
@ -1747,7 +1750,7 @@ export class SceneEntities {
): (() => SegmentOverlayPayload | null) => { ): (() => SegmentOverlayPayload | null) => {
const segPathToNode = getNodePathFromSourceRange( const segPathToNode = getNodePathFromSourceRange(
modifiedAst, modifiedAst,
sourceRangeFromRust(segment.__geoMeta.sourceRange) segment.__geoMeta.sourceRange
) )
const sgPaths = sketch.paths const sgPaths = sketch.paths
const originalPathToNodeStr = JSON.stringify(segPathToNode) const originalPathToNodeStr = JSON.stringify(segPathToNode)
@ -1898,10 +1901,8 @@ export class SceneEntities {
SEGMENT_BODIES_PLUS_PROFILE_START SEGMENT_BODIES_PLUS_PROFILE_START
) )
if (parent?.userData?.pathToNode) { if (parent?.userData?.pathToNode) {
const pResult = parse(recast(kclManager.ast)) const updatedAst = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult)) if (trap(updatedAst)) return
return Promise.reject(pResult)
const updatedAst = pResult.program
const _node = getNodeFromPath<Node<CallExpression>>( const _node = getNodeFromPath<Node<CallExpression>>(
updatedAst, updatedAst,
parent.userData.pathToNode, parent.userData.pathToNode,
@ -1909,7 +1910,7 @@ export class SceneEntities {
) )
if (trap(_node, { suppress: true })) return if (trap(_node, { suppress: true })) return
const node = _node.node const node = _node.node
editorManager.setHighlightRange([[node.start, node.end, true]]) editorManager.setHighlightRange([[node.start, node.end]])
const yellow = 0xffff00 const yellow = 0xffff00
colorSegment(selected, yellow) colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE) const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -1954,10 +1955,10 @@ export class SceneEntities {
}) })
return return
} }
editorManager.setHighlightRange([defaultSourceRange()]) editorManager.setHighlightRange([[0, 0]])
}, },
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => { onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([defaultSourceRange()]) editorManager.setHighlightRange([[0, 0]])
const parent = getParentGroup( const parent = getParentGroup(
selected, selected,
SEGMENT_BODIES_PLUS_PROFILE_START SEGMENT_BODIES_PLUS_PROFILE_START
@ -2056,7 +2057,7 @@ function prepareTruncatedMemoryAndAst(
'VariableDeclaration' 'VariableDeclaration'
) )
if (err(_node)) return _node if (err(_node)) return _node
const variableDeclarationName = _node.node?.declaration.id?.name || '' const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
programMemory.get(variableDeclarationName), programMemory.get(variableDeclarationName),
variableDeclarationName variableDeclarationName
@ -2081,30 +2082,28 @@ function prepareTruncatedMemoryAndAst(
]) ])
} }
;( ;(
(_ast.body[bodyIndex] as VariableDeclaration).declaration (_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
.init as PipeExpression .init as PipeExpression
).body.push(newSegment) ).body.push(newSegment)
// update source ranges to section we just added. // update source ranges to section we just added.
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments // hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
if (trap(pResult) || !resultIsOk(pResult)) if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
return Error('Unexpected compilation error')
const updatedSrcRangeAst = pResult.program
const lastPipeItem = ( const lastPipeItem = (
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration (updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
.init as PipeExpression .declarations[0].init as PipeExpression
).body.slice(-1)[0] ).body.slice(-1)[0]
;( ;(
(_ast.body[bodyIndex] as VariableDeclaration).declaration (_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
.init as PipeExpression .init as PipeExpression
).body.slice(-1)[0].start = lastPipeItem.start ).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end _ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration> const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
varDec.end = lastPipeItem.end varDec.end = lastPipeItem.end
const declarator = varDec.declaration const declarator = varDec.declarations[0]
declarator.end = lastPipeItem.end declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression> const init = declarator.init as Node<PipeExpression>
init.end = lastPipeItem.end init.end = lastPipeItem.end
@ -2141,7 +2140,7 @@ function prepareTruncatedMemoryAndAst(
if (node.type !== 'VariableDeclaration') { if (node.type !== 'VariableDeclaration') {
continue continue
} }
const name = node.declaration.id.name const name = node.declarations[0].id.name
const memoryItem = programMemory.get(name) const memoryItem = programMemory.get(name)
if (!memoryItem) { if (!memoryItem) {
continue continue

View File

@ -5,7 +5,6 @@ import { useEffect, useRef, useState } from 'react'
import { trap } from 'lib/trap' import { trap } from 'lib/trap'
import { codeToIdSelections } from 'lib/selections' import { codeToIdSelections } from 'lib/selections'
import { codeRefFromRange } from 'lang/std/artifactGraph' import { codeRefFromRange } from 'lang/std/artifactGraph'
import { defaultSourceRange } from 'lang/wasm'
export function AstExplorer() { export function AstExplorer() {
const { context } = useModelingContext() const { context } = useModelingContext()
@ -47,7 +46,7 @@ export function AstExplorer() {
<div <div
className="h-full relative" className="h-full relative"
onMouseLeave={(e) => { onMouseLeave={(e) => {
editorManager.setHighlightRange([defaultSourceRange()]) editorManager.setHighlightRange([[0, 0]])
}} }}
> >
<pre className="text-xs"> <pre className="text-xs">
@ -116,19 +115,15 @@ function DisplayObj({
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : '' hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`} }`}
onMouseEnter={(e) => { onMouseEnter={(e) => {
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]]) editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
e.stopPropagation() e.stopPropagation()
}} }}
onMouseMove={(e) => { onMouseMove={(e) => {
e.stopPropagation() e.stopPropagation()
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]]) editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
}} }}
onClick={(e) => { onClick={(e) => {
const range: [number, number, boolean] = [ const range: [number, number] = [obj?.start || 0, obj.end || 0]
obj?.start || 0,
obj.end || 0,
true,
]
const idInfo = codeToIdSelections([ const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) }, { codeRef: codeRefFromRange(range, kclManager.ast) },
])[0] ])[0]

View File

@ -1,11 +1,5 @@
import { useEffect, useState, useRef } from 'react' import { useEffect, useState, useRef } from 'react'
import { import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import { import {
createIdentifier, createIdentifier,
createLiteral, createLiteral,
@ -147,9 +141,8 @@ export function useCalc({
useEffect(() => { useEffect(() => {
try { try {
const code = `const __result__ = ${value}` const code = `const __result__ = ${value}`
const pResult = parse(code) const ast = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return if (trap(ast)) return
const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty() const _programMem: ProgramMemory = ProgramMemory.empty()
for (const { key, value } of availableVarInfo.variables) { for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, { const error = _programMem.set(key, {
@ -163,17 +156,18 @@ export function useCalc({
executeAst({ executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. useFakeExecutor: true,
programMemoryOverride: kclManager.programMemory.clone(), programMemoryOverride: kclManager.programMemory.clone(),
idGenerator: kclManager.execState.idGenerator,
}).then(({ execState }) => { }).then(({ execState }) => {
const resultDeclaration = ast.body.find( const resultDeclaration = ast.body.find(
(a) => (a) =>
a.type === 'VariableDeclaration' && a.type === 'VariableDeclaration' &&
a.declaration.id?.name === '__result__' a.declarations?.[0]?.id?.name === '__result__'
) )
const init = const init =
resultDeclaration?.type === 'VariableDeclaration' && resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init resultDeclaration?.declarations?.[0]?.init
const result = execState.memory?.get('__result__')?.value const result = execState.memory?.get('__result__')?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN') setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init) init && setValueNode(init)

View File

@ -8,16 +8,11 @@ import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression' import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
import { roundOff } from 'lib/utils' import { roundOff } from 'lib/utils'
import { varMentions } from 'lib/varCompletionExtension' import { varMentions } from 'lib/varCompletionExtension'
import { useEffect, useMemo, useRef, useState } from 'react' import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import styles from './CommandBarKclInput.module.css' import styles from './CommandBarKclInput.module.css'
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst' import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor' import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
const machineContextSelector = (snapshot?: {
context: Record<string, unknown>
}) => snapshot?.context
function CommandBarKclInput({ function CommandBarKclInput({
arg, arg,
@ -36,44 +31,12 @@ function CommandBarKclInput({
arg.name arg.name
] as KclCommandValue | undefined ] as KclCommandValue | undefined
const { settings } = useSettingsAuthContext() const { settings } = useSettingsAuthContext()
const argMachineContext = useSelector( const defaultValue = (arg.defaultValue as string) || ''
arg.machineActor,
machineContextSelector
)
const defaultValue = useMemo(
() =>
arg.defaultValue
? arg.defaultValue instanceof Function
? arg.defaultValue(commandBarState.context, argMachineContext)
: arg.defaultValue
: '',
[arg.defaultValue, commandBarState.context, argMachineContext]
)
const initialVariableName = useMemo(() => {
// Use the configured variable name if it exists
if (arg.variableName !== undefined) {
return arg.variableName instanceof Function
? arg.variableName(commandBarState.context, argMachineContext)
: arg.variableName
}
// or derive it from the previously set value or the argument name
return previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name
}, [
arg.variableName,
commandBarState.context,
argMachineContext,
arg.name,
previouslySetValue,
])
const [value, setValue] = useState( const [value, setValue] = useState(
previouslySetValue?.valueText || defaultValue || '' previouslySetValue?.valueText || defaultValue || ''
) )
const [createNewVariable, setCreateNewVariable] = useState( const [createNewVariable, setCreateNewVariable] = useState(
(previouslySetValue && 'variableName' in previouslySetValue) || previouslySetValue && 'variableName' in previouslySetValue
arg.createVariableByDefault ||
false
) )
const [canSubmit, setCanSubmit] = useState(true) const [canSubmit, setCanSubmit] = useState(true)
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' })) useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
@ -89,7 +52,10 @@ function CommandBarKclInput({
isNewVariableNameUnique, isNewVariableNameUnique,
} = useCalculateKclExpression({ } = useCalculateKclExpression({
value, value,
initialVariableName, initialVariableName:
previouslySetValue && 'variableName' in previouslySetValue
? previouslySetValue.variableName
: arg.name,
}) })
const varMentionData: Completion[] = prevVariables.map((v) => ({ const varMentionData: Completion[] = prevVariables.map((v) => ({
label: v.key, label: v.key,

View File

@ -266,7 +266,6 @@ const FileTreeItem = ({
// Let the lsp servers know we closed a file. // Let the lsp servers know we closed a file.
onFileClose(currentFile?.path || null, project?.path || null) onFileClose(currentFile?.path || null, project?.path || null)
onFileOpen(fileOrDir.path, project?.path || null) onFileOpen(fileOrDir.path, project?.path || null)
kclManager.switchedFiles = true
// Open kcl files // Open kcl files
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`) navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)

View File

@ -1,4 +1,4 @@
import { APP_VERSION, RELEASE_URL } from 'routes/Settings' import { APP_VERSION } from 'routes/Settings'
import { CustomIcon } from 'components/CustomIcon' import { CustomIcon } from 'components/CustomIcon'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
@ -72,8 +72,10 @@ export function LowerRightControls({
<menu className="flex items-center justify-end gap-3 pointer-events-auto"> <menu className="flex items-center justify-end gap-3 pointer-events-auto">
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />} {!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
<a <a
onClick={openExternalBrowserIfDesktop(RELEASE_URL)} onClick={openExternalBrowserIfDesktop(
href={RELEASE_URL} `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" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className={'!no-underline font-mono text-xs ' + linkOverrideClassName} className={'!no-underline font-mono text-xs ' + linkOverrideClassName}

View File

@ -41,10 +41,7 @@ import {
angleBetweenInfo, angleBetweenInfo,
applyConstraintAngleBetween, applyConstraintAngleBetween,
} from './Toolbar/SetAngleBetween' } from './Toolbar/SetAngleBetween'
import { import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
applyConstraintAngleLength,
applyConstraintLength,
} from './Toolbar/setAngleLength'
import { import {
canSweepSelection, canSweepSelection,
handleSelectionBatch, handleSelectionBatch,
@ -53,9 +50,6 @@ import {
isSketchPipe, isSketchPipe,
Selections, Selections,
updateSelections, updateSelections,
canLoftSelection,
canRevolveSelection,
canShellSelection,
} from 'lib/selections' } from 'lib/selections'
import { applyConstraintIntersect } from './Toolbar/Intersect' import { applyConstraintIntersect } from './Toolbar/Intersect'
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance' import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
@ -67,15 +61,13 @@ import {
getSketchOrientationDetails, getSketchOrientationDetails,
} from 'clientSideScene/sceneEntities' } from 'clientSideScene/sceneEntities'
import { import {
insertNamedConstant, moveValueIntoNewVariablePath,
replaceValueAtNodePath,
sketchOnExtrudedFace, sketchOnExtrudedFace,
sketchOnOffsetPlane, sketchOnOffsetPlane,
startSketchOnDefault, startSketchOnDefault,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm' import { Program, parse, recast } from 'lang/wasm'
import { import {
doesSceneHaveExtrudedSketch,
doesSceneHaveSweepableSketch, doesSceneHaveSweepableSketch,
getNodePathFromSourceRange, getNodePathFromSourceRange,
isSingleCursorInPipe, isSingleCursorInPipe,
@ -86,10 +78,11 @@ import toast from 'react-hot-toast'
import { EditorSelection, Transaction } from '@codemirror/state' import { EditorSelection, Transaction } from '@codemirror/state'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom' import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls' import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { getVarNameModal } from 'hooks/useToolbarGuards'
import { err, reportRejection, trap } from 'lib/trap' import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { modelingMachineEvent } from 'editor/manager' import { modelingMachineEvent } from 'editor/manager'
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment' import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addFillet'
import { import {
ExportIntent, ExportIntent,
EngineConnectionStateType, EngineConnectionStateType,
@ -576,59 +569,6 @@ export const ModelingMachineProvider = ({
if (err(canSweep)) return false if (err(canSweep)) return false
return canSweep return canSweep
}, },
'has valid revolve selection': ({ context: { selectionRanges } }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
// they have no selection, we should enable the button
// so they can select the face through the cmdbar
// BUT only if there's extrudable geometry
return doesSceneHaveSweepableSketch(kclManager.ast)
}
if (!isSketchPipe(selectionRanges)) return false
const canSweep = canRevolveSelection(selectionRanges)
if (err(canSweep)) return false
return canSweep
},
'has valid loft selection': ({ context: { selectionRanges } }) => {
const hasNoSelection =
selectionRanges.graphSelections.length === 0 ||
isRangeBetweenCharacters(selectionRanges) ||
isSelectionLastLine(selectionRanges, codeManager.code)
if (hasNoSelection) {
const count = 2
return doesSceneHaveSweepableSketch(kclManager.ast, count)
}
const canLoft = canLoftSelection(selectionRanges)
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': ({ 'has valid selection for deletion': ({
context: { selectionRanges }, context: { selectionRanges },
}) => { }) => {
@ -656,11 +596,15 @@ export const ModelingMachineProvider = ({
) )
}, },
'Has exportable geometry': () => { 'Has exportable geometry': () => {
if (!kclManager.hasErrors() && kclManager.ast.body.length > 0) if (
kclManager.kclErrors.length === 0 &&
kclManager.ast.body.length > 0
)
return true return true
else { else {
let errorMessage = 'Unable to Export ' let errorMessage = 'Unable to Export '
if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors' if (kclManager.kclErrors.length > 0)
errorMessage += 'due to KCL Errors'
else if (kclManager.ast.body.length === 0) else if (kclManager.ast.body.length === 0)
errorMessage += 'due to Empty Scene' errorMessage += 'due to Empty Scene'
console.error(errorMessage) console.error(errorMessage)
@ -778,11 +722,7 @@ export const ModelingMachineProvider = ({
constraint: 'setHorzDistance', constraint: 'setHorzDistance',
selectionRanges, selectionRanges,
}) })
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -823,10 +763,7 @@ export const ModelingMachineProvider = ({
constraint: 'setVertDistance', constraint: 'setVertDistance',
selectionRanges, selectionRanges,
}) })
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -874,10 +811,7 @@ export const ModelingMachineProvider = ({
selectionRanges, selectionRanges,
angleOrLength: 'setAngle', angleOrLength: 'setAngle',
})) }))
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (err(_modifiedAst)) return Promise.reject(_modifiedAst) if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
if (!sketchDetails) if (!sketchDetails)
@ -913,22 +847,13 @@ export const ModelingMachineProvider = ({
} }
} }
), ),
astConstrainLength: fromPromise( 'Get length info': fromPromise(
async ({ async ({ input: { selectionRanges, sketchDetails } }) => {
input: { selectionRanges, sketchDetails, lengthValue }, const { modifiedAst, pathToNodeMap } =
}) => { await applyConstraintAngleLength({
if (!lengthValue) selectionRanges,
return Promise.reject(new Error('No length value')) })
const constraintResult = await applyConstraintLength({ const _modifiedAst = parse(recast(modifiedAst))
selectionRanges,
length: lengthValue,
})
if (err(constraintResult)) return Promise.reject(constraintResult)
const { modifiedAst, pathToNodeMap } = constraintResult
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -968,10 +893,7 @@ export const ModelingMachineProvider = ({
await applyConstraintIntersect({ await applyConstraintIntersect({
selectionRanges, selectionRanges,
}) })
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -1012,10 +934,7 @@ export const ModelingMachineProvider = ({
constraint: 'xAbs', constraint: 'xAbs',
selectionRanges, selectionRanges,
}) })
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -1056,10 +975,7 @@ export const ModelingMachineProvider = ({
constraint: 'yAbs', constraint: 'yAbs',
selectionRanges, selectionRanges,
}) })
const pResult = parse(recast(modifiedAst)) const _modifiedAst = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails) if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap( const updatedPathToNode = updatePathToNodeFromMap(
@ -1093,88 +1009,33 @@ export const ModelingMachineProvider = ({
} }
} }
), ),
'Apply named value constraint': fromPromise( 'Get convert to variable info': fromPromise(
async ({ input: { selectionRanges, sketchDetails, data } }) => { async ({ input: { selectionRanges, sketchDetails, data } }) => {
if (!sketchDetails) { if (!sketchDetails)
return Promise.reject(new Error('No sketch details')) return Promise.reject(new Error('No sketch details'))
} const { variableName } = await getVarNameModal({
if (!data) { valueName: data?.variableName || 'var',
return Promise.reject(new Error('No data from command flow')) })
} let parsed = parse(recast(kclManager.ast))
let pResult = parse(recast(kclManager.ast))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
let parsed = pResult.program
let result: {
modifiedAst: Node<Program>
pathToReplaced: PathToNode | null
} = {
modifiedAst: parsed,
pathToReplaced: null,
}
// If the user provided a constant name,
// we need to insert the named constant
// and then replace the node with the constant's name.
if ('variableName' in data.namedValue) {
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.variableName,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
const parseResultAfterInsertion = parse(
recast(
insertNamedConstant({
node: astAfterReplacement.modifiedAst,
newExpression: data.namedValue,
})
)
)
if (
trap(parseResultAfterInsertion) ||
!resultIsOk(parseResultAfterInsertion)
)
return Promise.reject(parseResultAfterInsertion)
result = {
modifiedAst: parseResultAfterInsertion.program,
pathToReplaced: astAfterReplacement.pathToReplaced,
}
} else if ('valueText' in data.namedValue) {
// If they didn't provide a constant name,
// just replace the node with the value.
const astAfterReplacement = replaceValueAtNodePath({
ast: parsed,
pathToNode: data.currentValue.pathToNode,
newExpressionString: data.namedValue.valueText,
})
if (trap(astAfterReplacement)) {
return Promise.reject(astAfterReplacement)
}
// The `replacer` function returns a pathToNode that assumes
// an identifier is also being inserted into the AST, creating an off-by-one error.
// This corrects that error, but TODO we should fix this upstream
// to avoid this kind of error in the future.
astAfterReplacement.pathToReplaced[1][0] =
(astAfterReplacement.pathToReplaced[1][0] as number) - 1
result = astAfterReplacement
}
pResult = parse(recast(result.modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
parsed = pResult.program
if (trap(parsed)) return Promise.reject(parsed) if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program> parsed = parsed as Node<Program>
if (!result.pathToReplaced)
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariablePath(
parsed,
kclManager.programMemory,
data?.pathToNode || [],
variableName
)
parsed = parse(recast(_modifiedAst))
if (trap(parsed)) return Promise.reject(parsed)
parsed = parsed as Node<Program>
if (!pathToReplacedNode)
return Promise.reject(new Error('No path to replaced node')) return Promise.reject(new Error('No path to replaced node'))
const updatedAst = const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch( await sceneEntitiesManager.updateAstAndRejigSketch(
result.pathToReplaced || [], pathToReplacedNode || [],
parsed, parsed,
sketchDetails.zAxis, sketchDetails.zAxis,
sketchDetails.yAxis, sketchDetails.yAxis,
@ -1187,7 +1048,7 @@ export const ModelingMachineProvider = ({
) )
const selection = updateSelections( const selection = updateSelections(
{ 0: result.pathToReplaced }, { 0: pathToReplacedNode },
selectionRanges, selectionRanges,
updatedAst.newAst updatedAst.newAst
) )
@ -1195,7 +1056,7 @@ export const ModelingMachineProvider = ({
return { return {
selectionType: 'completeSelection', selectionType: 'completeSelection',
selection, selection,
updatedPathToNode: result.pathToReplaced, updatedPathToNode: pathToReplacedNode,
} }
} }
), ),

View File

@ -76,7 +76,7 @@ export const ModelingPane = ({
return ( return (
<section <section
{...props} {...props}
aria-label={title && typeof title === 'string' ? title : ''} title={title && typeof title === 'string' ? title : ''}
data-testid={detailsTestId} data-testid={detailsTestId}
id={id} id={id}
className={ className={

View File

@ -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.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> <Menu.Item>
<button <button
onClick={() => { onClick={() => kclManager.format()}
kclManager.format().catch(reportRejection)
}}
className={styles.button} className={styles.button}
> >
<span>Format code</span> <span>Format code</span>

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane' import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers' import { enginelessExecutor } from '../../../lib/testHelpers'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm' import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
beforeAll(async () => { beforeAll(async () => {
await initPromise await initPromise
@ -28,16 +28,12 @@ describe('processMemory', () => {
|> lineTo([0.98, 5.16], %) |> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %) |> lineTo([2.15, 4.32], %)
// |> rx(90, %)` // |> rx(90, %)`
const ast = assertParse(code) const ast = parse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty()) const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(execState.memory) const output = processMemory(execState.memory)
expect(output.myVar).toEqual(5) expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3) expect(output.otherVar).toEqual(3)
expect(output).toEqual({ expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5, myVar: 5,
myFn: '__function(a)__', myFn: '__function(a)__',
otherVar: 3, otherVar: 3,

View File

@ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [
keybinding: 'Shift + C', keybinding: 'Shift + C',
showBadge: { showBadge: {
value: ({ kclContext }) => { value: ({ kclContext }) => {
return kclContext.diagnostics.length return kclContext.errors.length
}, },
onClick: (e) => { onClick: (e) => {
e.preventDefault() e.preventDefault()

View File

@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
settings: settings.context, settings: settings.context,
platform: getPlatformString(), platform: getPlatformString(),
}), }),
[kclContext.diagnostics, settings.context] [kclContext.errors, settings.context]
) )
const sidebarActions: SidebarAction[] = [ const sidebarActions: SidebarAction[] = [

View File

@ -10,7 +10,7 @@ import { APP_NAME } from 'lib/constants'
import { useCommandsContext } from 'hooks/useCommandsContext' import { useCommandsContext } from 'hooks/useCommandsContext'
import { CustomIcon } from './CustomIcon' import { CustomIcon } from './CustomIcon'
import { useLspContext } from './LspProvider' import { useLspContext } from './LspProvider'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager } from 'lib/singletons'
import { MachineManagerContext } from 'components/MachineManagerProvider' import { MachineManagerContext } from 'components/MachineManagerProvider'
import usePlatform from 'hooks/usePlatform' import usePlatform from 'hooks/usePlatform'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath' import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
@ -68,7 +68,8 @@ function AppLogoLink({
data-testid="app-logo" data-testid="app-logo"
onClick={() => { onClick={() => {
onProjectClose(file || null, project?.path || null, false) onProjectClose(file || null, project?.path || null, false)
kclManager.switchedFiles = true // Clear the scene and end the session.
engineCommandManager.endSession()
}} }}
to={PATHS.HOME} to={PATHS.HOME}
className={wrapperClassName + ' hover:before:brightness-110'} className={wrapperClassName + ' hover:before:brightness-110'}
@ -189,7 +190,8 @@ function ProjectMenuPopover({
className: !isDesktop() ? 'hidden' : '', className: !isDesktop() ? 'hidden' : '',
onClick: () => { onClick: () => {
onProjectClose(file || null, project?.path || null, true) onProjectClose(file || null, project?.path || null, true)
kclManager.switchedFiles = true // Clear the scene and end the session.
engineCommandManager.endSession()
}, },
}, },
].filter( ].filter(

View File

@ -10,7 +10,7 @@ interface AllKeybindingsFieldsProps {}
export const AllKeybindingsFields = forwardRef( export const AllKeybindingsFields = forwardRef(
( (
_props: AllKeybindingsFieldsProps, props: AllKeybindingsFieldsProps,
scrollRef: ForwardedRef<HTMLDivElement> scrollRef: ForwardedRef<HTMLDivElement>
) => { ) => {
// This is how we will get the interaction map from the context // This is how we will get the interaction map from the context
@ -25,7 +25,7 @@ export const AllKeybindingsFields = forwardRef(
.map(([category, categoryItems]) => ( .map(([category, categoryItems]) => (
<div className="flex flex-col gap-4 px-2 pr-4"> <div className="flex flex-col gap-4 px-2 pr-4">
<h2 <h2
id={`category-${category.replaceAll(/\s/g, '-')}`} id={`category-${category}`}
className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold" className="text-xl mt-6 first-of-type:mt-0 capitalize font-bold"
> >
{category} {category}

View File

@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { SettingsFieldInput } from './SettingsFieldInput' import { SettingsFieldInput } from './SettingsFieldInput'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { APP_VERSION, IS_NIGHTLY, RELEASE_URL } from 'routes/Settings' import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
import { PATHS } from 'lib/paths' import { PATHS } from 'lib/paths'
import { import {
createAndOpenNewTutorialProject, createAndOpenNewTutorialProject,
@ -246,8 +246,10 @@ export const AllSettingsFields = forwardRef(
to inject the version from package.json */} to inject the version from package.json */}
App version {APP_VERSION}.{' '} App version {APP_VERSION}.{' '}
<a <a
onClick={openExternalBrowserIfDesktop(RELEASE_URL)} onClick={openExternalBrowserIfDesktop(
href={RELEASE_URL} `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" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
@ -269,7 +271,7 @@ export const AllSettingsFields = forwardRef(
, and start a discussion if you don't see it! Your feedback will , and start a discussion if you don't see it! Your feedback will
help us prioritize what to build next. help us prioritize what to build next.
</p> </p>
{!IS_NIGHTLY && ( {PACKAGE_NAME.indexOf('-nightly') === -1 && (
<p className="max-w-2xl mt-6"> <p className="max-w-2xl mt-6">
Want to experience the latest and (hopefully) greatest from our Want to experience the latest and (hopefully) greatest from our
main development branch?{' '} main development branch?{' '}

View File

@ -19,7 +19,7 @@ export function KeybindingsSectionsList({
key={category} key={category}
onClick={() => onClick={() =>
scrollRef.current scrollRef.current
?.querySelector(`#category-${category.replaceAll(/\s/g, '-')}`) ?.querySelector(`#category-${category}`)
?.scrollIntoView({ ?.scrollIntoView({
block: 'center', block: 'center',
behavior: 'smooth', behavior: 'smooth',

View File

@ -40,10 +40,7 @@ export function removeConstrainingValuesInfo({
otherSelections: [], otherSelections: [],
graphSelections: nodes.map( graphSelections: nodes.map(
(node): Selection => ({ (node): Selection => ({
codeRef: codeRefFromRange( codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
[node.start, node.end, true],
kclManager.ast
),
}) })
), ),
} }

View File

@ -22,7 +22,6 @@ import { removeDoubleNegatives } from '../AvailableVarsHelpers'
import { normaliseAngle } from '../../lib/utils' import { normaliseAngle } from '../../lib/utils'
import { kclManager } from 'lib/singletons' import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { KclCommandValue } from 'lib/commandTypes'
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal) const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
@ -64,57 +63,6 @@ export function angleLengthInfo({
return { enabled, transforms } return { enabled, transforms }
} }
export async function applyConstraintLength({
length,
selectionRanges,
}: {
length: KclCommandValue
selectionRanges: Selections
}) {
const ast = kclManager.ast
const angleLength = angleLengthInfo({ selectionRanges })
if (err(angleLength)) return angleLength
const { transforms } = angleLength
let distanceExpression: Expr = length.valueAst
/**
* To be "constrained", the value must be a binary expression, a named value, or a function call.
* If it has a variable name, we need to insert a variable declaration at the correct index.
*/
if (
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
) {
const newBody = [...ast.body]
newBody.splice(length.insertIndex, 0, length.variableDeclarationAst)
ast.body = newBody
distanceExpression = createIdentifier(length.variableName)
}
if (!isExprBinaryPart(distanceExpression)) {
return new Error('Invalid valueNode, is not a BinaryPart')
}
const retval = transformAstSketchLines({
ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
if (err(retval)) return Promise.reject(retval)
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
}
}
export async function applyConstraintAngleLength({ export async function applyConstraintAngleLength({
selectionRanges, selectionRanges,
angleOrLength = 'setLength', angleOrLength = 'setLength',

View File

@ -139,9 +139,7 @@ export default class EditorManager {
} }
setHighlightRange(range: Array<Selection['codeRef']['range']>): void { setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
this._highlightRange = range.map((s): [number, number] => { this._highlightRange = range
return [s[0], s[1]]
})
const selectionsWithSafeEnds = range.map((s): [number, number] => { const selectionsWithSafeEnds = range.map((s): [number, number] => {
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1]) const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])

View File

@ -18,7 +18,7 @@ import {
import { err, reportRejection } from 'lib/trap' import { err, reportRejection } from 'lib/trap'
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities' import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { CallExpression, defaultSourceRange } from 'lang/wasm' import { CallExpression } from 'lang/wasm'
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine' import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
export function useEngineConnectionSubscriptions() { export function useEngineConnectionSubscriptions() {
@ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() {
(editorManager.highlightRange[0][0] !== 0 && (editorManager.highlightRange[0][0] !== 0 &&
editorManager.highlightRange[0][1] !== 0) editorManager.highlightRange[0][1] !== 0)
) { ) {
editorManager.setHighlightRange([defaultSourceRange()]) editorManager.setHighlightRange([[0, 0]])
} }
}, },
}) })
@ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() {
const { z_axis, y_axis, origin } = faceInfo const { z_axis, y_axis, origin } = faceInfo
const sketchPathToNode = getNodePathFromSourceRange( const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast, kclManager.ast,
err(codeRef) ? defaultSourceRange() : codeRef.range err(codeRef) ? [0, 0] : codeRef.range
) )
const getEdgeCutMeta = (): null | EdgeCutInfo => { const getEdgeCutMeta = (): null | EdgeCutInfo => {

View File

@ -24,8 +24,6 @@ export function useConvertToVariable(range?: SourceRange) {
}, [enable]) }, [enable])
useEffect(() => { useEffect(() => {
// Return early if there are no selection ranges for whatever reason
if (!context.selectionRanges) return
const parsed = ast const parsed = ast
const meta = isNodeSafeToReplace( const meta = isNodeSafeToReplace(

View File

@ -1,15 +1,15 @@
import { KCLError } from './errors'
import { createContext, useContext, useEffect, useState } from 'react' import { createContext, useContext, useEffect, useState } from 'react'
import { type IndexLoaderData } from 'lib/types' import { type IndexLoaderData } from 'lib/types'
import { useLoaderData } from 'react-router-dom' import { useLoaderData } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons' import { codeManager, kclManager } from 'lib/singletons'
import { Diagnostic } from '@codemirror/lint'
const KclContext = createContext({ const KclContext = createContext({
code: codeManager?.code || '', code: codeManager?.code || '',
programMemory: kclManager?.programMemory, programMemory: kclManager?.programMemory,
ast: kclManager?.ast, ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting, isExecuting: kclManager?.isExecuting,
diagnostics: kclManager?.diagnostics, errors: kclManager?.kclErrors,
logs: kclManager?.logs, logs: kclManager?.logs,
wasmInitFailed: kclManager?.wasmInitFailed, wasmInitFailed: kclManager?.wasmInitFailed,
}) })
@ -32,7 +32,7 @@ export function KclContextProvider({
const [programMemory, setProgramMemory] = useState(kclManager.programMemory) const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast) const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false) const [isExecuting, setIsExecuting] = useState(false)
const [diagnostics, setErrors] = useState<Diagnostic[]>([]) const [errors, setErrors] = useState<KCLError[]>([])
const [logs, setLogs] = useState<string[]>([]) const [logs, setLogs] = useState<string[]>([])
const [wasmInitFailed, setWasmInitFailed] = useState(false) const [wasmInitFailed, setWasmInitFailed] = useState(false)
@ -57,7 +57,7 @@ export function KclContextProvider({
programMemory, programMemory,
ast, ast,
isExecuting, isExecuting,
diagnostics, errors,
logs, logs,
wasmInitFailed, wasmInitFailed,
}} }}

View File

@ -1,10 +1,6 @@
import { executeAst, lintAst } from 'lang/langHelpers' import { executeAst, lintAst } from 'lang/langHelpers'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { import { KCLError, kclErrorsToDiagnostics } from './errors'
KCLError,
complilationErrorsToDiagnostics,
kclErrorsToDiagnostics,
} from './errors'
import { uuidv4 } from 'lib/utils' import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection' import { EngineCommandManager } from './std/engineConnection'
import { err } from 'lib/trap' import { err } from 'lib/trap'
@ -12,7 +8,6 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
import { import {
CallExpression, CallExpression,
clearSceneAndBustCache,
emptyExecState, emptyExecState,
ExecState, ExecState,
initPromise, initPromise,
@ -56,12 +51,11 @@ export class KclManager {
private _programMemory: ProgramMemory = ProgramMemory.empty() private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty() lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _logs: string[] = [] private _logs: string[] = []
private _diagnostics: Diagnostic[] = [] private _lints: Diagnostic[] = []
private _kclErrors: KCLError[] = []
private _isExecuting = false private _isExecuting = false
private _executeIsStale: ExecuteArgs | null = null private _executeIsStale: ExecuteArgs | null = null
private _wasmInitFailed = true private _wasmInitFailed = true
private _hasErrors = false
private _switchedFiles = false
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
@ -69,7 +63,7 @@ export class KclManager {
private _astCallBack: (arg: Node<Program>) => void = () => {} private _astCallBack: (arg: Node<Program>) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {} private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {} private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {} private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
private _wasmInitFailedCallback: (arg: boolean) => void = () => {} private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
private _executeCallback: () => void = () => {} private _executeCallback: () => void = () => {}
@ -81,10 +75,6 @@ export class KclManager {
this._astCallBack(ast) this._astCallBack(ast)
} }
set switchedFiles(switchedFiles: boolean) {
this._switchedFiles = switchedFiles
}
get programMemory() { get programMemory() {
return this._programMemory return this._programMemory
} }
@ -94,7 +84,7 @@ export class KclManager {
this._programMemoryCallBack(programMemory) this._programMemoryCallBack(programMemory)
} }
private set execState(execState) { set execState(execState) {
this._execState = execState this._execState = execState
this.programMemory = execState.memory this.programMemory = execState.memory
} }
@ -111,28 +101,38 @@ export class KclManager {
this._logsCallBack(logs) this._logsCallBack(logs)
} }
get diagnostics() { get lints() {
return this._diagnostics return this._lints
} }
set diagnostics(ds) { set lints(lints) {
if (ds === this._diagnostics) return if (lints === this._lints) return
this._diagnostics = ds this._lints = lints
// Run the lints through the diagnostics.
this.kclErrors = this._kclErrors
}
get kclErrors() {
return this._kclErrors
}
set kclErrors(kclErrors) {
if (kclErrors === this._kclErrors && this.lints.length === 0) return
this._kclErrors = kclErrors
this.setDiagnosticsForCurrentErrors() this.setDiagnosticsForCurrentErrors()
} this._kclErrorsCallBack(kclErrors)
addDiagnostics(ds: Diagnostic[]) {
if (ds.length === 0) return
this.diagnostics = this.diagnostics.concat(ds)
}
hasErrors(): boolean {
return this._hasErrors
} }
setDiagnosticsForCurrentErrors() { setDiagnosticsForCurrentErrors() {
editorManager?.setDiagnostics(this.diagnostics) let diagnostics = kclErrorsToDiagnostics(this.kclErrors)
this._kclErrorsCallBack(this.diagnostics) if (this.lints.length > 0) {
diagnostics = diagnostics.concat(this.lints)
}
editorManager?.setDiagnostics(diagnostics)
}
addKclErrors(kclErrors: KCLError[]) {
if (kclErrors.length === 0) return
this.kclErrors = this.kclErrors.concat(kclErrors)
} }
get isExecuting() { get isExecuting() {
@ -172,12 +172,8 @@ export class KclManager {
this.engineCommandManager = engineCommandManager this.engineCommandManager = engineCommandManager
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
this.ensureWasmInit().then(async () => { this.ensureWasmInit().then(() => {
await this.safeParse(codeManager.code).then((ast) => { this.ast = this.safeParse(codeManager.code) || this.ast
if (ast) {
this.ast = ast
}
})
}) })
} }
@ -192,7 +188,7 @@ export class KclManager {
setProgramMemory: (arg: ProgramMemory) => void setProgramMemory: (arg: ProgramMemory) => void
setAst: (arg: Node<Program>) => void setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void setLogs: (arg: string[]) => void
setKclErrors: (errors: Diagnostic[]) => void setKclErrors: (arg: KCLError[]) => void
setIsExecuting: (arg: boolean) => void setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void setWasmInitFailed: (arg: boolean) => void
}) { }) {
@ -221,48 +217,18 @@ export class KclManager {
} }
} }
// (jess) I'm not in love with this, but it ensures we clear the scene and safeParse(code: string): Node<Program> | null {
// bust the cache on const ast = parse(code)
// errors from parsing when opening new files. this.lints = []
// Why not just clear the cache on all parse errors, you ask? well its actually this.kclErrors = []
// really nice to keep the cache on parse errors within the same file, and if (!err(ast)) return ast
// only bust on engine errors esp if they take a long time to execute and const kclerror: KCLError = ast as KCLError
// 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> { this.addKclErrors([kclerror])
const result = parse(code) // TODO: re-eval if session should end?
this.diagnostics = [] if (kclerror.msg === 'file is empty')
this._hasErrors = false this.engineCommandManager?.endSession()
return null
if (err(result)) {
const kclerror: KCLError = result as KCLError
this.diagnostics = kclErrorsToDiagnostics([kclerror])
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
if (result.errors.length > 0) {
this._hasErrors = true
await this.checkIfSwitchedFilesShouldClear()
return null
}
return result.program
} }
async ensureWasmInit() { async ensureWasmInit() {
@ -301,16 +267,19 @@ export class KclManager {
this._cancelTokens.set(currentExecutionId, false) this._cancelTokens.set(currentExecutionId, false)
this.isExecuting = true this.isExecuting = true
// Make sure we clear before starting again. End session will do this.
this.engineCommandManager?.endSession()
await this.ensureWasmInit() await this.ensureWasmInit()
const { logs, errors, execState, isInterrupted } = await executeAst({ const { logs, errors, execState, isInterrupted } = await executeAst({
ast, ast,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
}) })
// Program was not interrupted, setup the scene // Program was not interrupted, setup the scene
// Do not send send scene commands if the program was interrupted, go to clean up // Do not send send scene commands if the program was interrupted, go to clean up
if (!isInterrupted) { if (!isInterrupted) {
this.addDiagnostics(await lintAst({ ast: ast })) this.lints = await lintAst({ ast: ast })
sceneInfra.modelingSend({ type: 'code edit during sketch' }) sceneInfra.modelingSend({ type: 'code edit during sketch' })
setSelectionFilterToDefault(execState.memory, this.engineCommandManager) setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
@ -352,7 +321,9 @@ export class KclManager {
this.logs = logs this.logs = logs
// Do not add the errors since the program was interrupted and the error is not a real KCL error // Do not add the errors since the program was interrupted and the error is not a real KCL error
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors)) this.addKclErrors(isInterrupted ? [] : errors)
// Reset the next ID index so that we reuse the previous IDs next time.
execState.idGenerator.nextId = 0
this.execState = execState this.execState = execState
if (!errors.length) { if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory this.lastSuccessfulProgramMemory = execState.memory
@ -384,7 +355,7 @@ export class KclManager {
console.error(newCode) console.error(newCode)
return return
} }
const newAst = await this.safeParse(newCode) const newAst = this.safeParse(newCode)
if (!newAst) { if (!newAst) {
this.clearAst() this.clearAst()
return return
@ -393,13 +364,13 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({ const { logs, errors, execState } = await executeAst({
ast: newAst, ast: newAst,
idGenerator: this.execState.idGenerator,
engineCommandManager: this.engineCommandManager, engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode. useFakeExecutor: true,
programMemoryOverride: ProgramMemory.empty(),
}) })
this._logs = logs this._logs = logs
this.addDiagnostics(kclErrorsToDiagnostics(errors)) this._kclErrors = errors
this._execState = execState this._execState = execState
this._programMemory = execState.memory this._programMemory = execState.memory
if (!errors.length) { if (!errors.length) {
@ -427,7 +398,7 @@ export class KclManager {
...artifact, ...artifact,
codeRef: { codeRef: {
...artifact.codeRef, ...artifact.codeRef,
range: [node.start, node.end, true], range: [node.start, node.end],
}, },
}) })
} }
@ -439,7 +410,7 @@ export class KclManager {
}) })
} }
async executeCode(zoomToFit?: boolean): Promise<void> { async executeCode(zoomToFit?: boolean): Promise<void> {
const ast = await this.safeParse(codeManager.code) const ast = this.safeParse(codeManager.code)
if (!ast) { if (!ast) {
this.clearAst() this.clearAst()
return return
@ -447,9 +418,9 @@ export class KclManager {
this.ast = { ...ast } this.ast = { ...ast }
return this.executeAst({ zoomToFit }) return this.executeAst({ zoomToFit })
} }
async format() { format() {
const originalCode = codeManager.code const originalCode = codeManager.code
const ast = await this.safeParse(originalCode) const ast = this.safeParse(originalCode)
if (!ast) { if (!ast) {
this.clearAst() this.clearAst()
return return
@ -489,7 +460,7 @@ export class KclManager {
const newCode = recast(ast) const newCode = recast(ast)
if (err(newCode)) return Promise.reject(newCode) 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')) if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
let returnVal: Selections | undefined = undefined let returnVal: Selections | undefined = undefined
@ -519,7 +490,7 @@ export class KclManager {
if (start && end) { if (start && end) {
returnVal.graphSelections.push({ returnVal.graphSelections.push({
codeRef: { codeRef: {
range: [start, end, true], range: [start, end],
pathToNode: path, pathToNode: path,
}, },
}) })

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise } from './wasm' import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(async () => { beforeAll(async () => {
@ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %) |> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
// |> rx(45, %)` // |> rx(45, %)`
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(parse(code))
// @ts-ignore // @ts-ignore
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
@ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
// |> rx(45, %) // |> rx(45, %)
|> extrude(2, %)` |> extrude(2, %)`
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(parse(code))
// @ts-ignore // @ts-ignore
const sketch001 = execState.memory.get('mySketch001') const sketch001 = execState.memory.get('mySketch001')
expect(sketch001).toEqual({ expect(sketch001).toEqual({
@ -147,7 +147,7 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %) |> extrude(2, %)
` `
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(parse(code))
const programMemory = execState.memory const programMemory = execState.memory
// @ts-ignore // @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')] const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]

View File

@ -8,14 +8,20 @@ describe('test kclErrToDiagnostic', () => {
message: '', message: '',
kind: 'semantic', kind: 'semantic',
msg: 'Semantic error', msg: 'Semantic error',
sourceRange: [0, 1, true], sourceRanges: [
[0, 1, 0],
[2, 3, 0],
],
}, },
{ {
name: '', name: '',
message: '', message: '',
kind: 'type', kind: 'type',
msg: 'Type error', msg: 'Type error',
sourceRange: [4, 5, true], sourceRanges: [
[4, 5, 0],
[6, 7, 0],
],
}, },
] ]
const diagnostics = kclErrorsToDiagnostics(errors) const diagnostics = kclErrorsToDiagnostics(errors)
@ -26,12 +32,24 @@ describe('test kclErrToDiagnostic', () => {
message: 'Semantic error', message: 'Semantic error',
severity: 'error', severity: 'error',
}, },
{
from: 2,
to: 3,
message: 'Semantic error',
severity: 'error',
},
{ {
from: 4, from: 4,
to: 5, to: 5,
message: 'Type error', message: 'Type error',
severity: 'error', severity: 'error',
}, },
{
from: 6,
to: 7,
message: 'Type error',
severity: 'error',
},
]) ])
}) })
}) })

View File

@ -1,90 +1,88 @@
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint' import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
import { posToOffset } from '@kittycad/codemirror-lsp-client' import { posToOffset } from '@kittycad/codemirror-lsp-client'
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol' import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
import { Text } from '@codemirror/state' import { Text } from '@codemirror/state'
import { EditorView } from 'codemirror'
import { SourceRange } from 'lang/wasm' const TOP_LEVEL_MODULE_ID = 0
type ExtractKind<T> = T extends { kind: infer K } ? K : never type ExtractKind<T> = T extends { kind: infer K } ? K : never
export class KCLError extends Error { export class KCLError extends Error {
kind: ExtractKind<RustKclError> | 'name' kind: ExtractKind<RustKclError> | 'name'
sourceRange: SourceRange sourceRanges: [number, number, number][]
msg: string msg: string
constructor( constructor(
kind: ExtractKind<RustKclError> | 'name', kind: ExtractKind<RustKclError> | 'name',
msg: string, msg: string,
sourceRange: SourceRange sourceRanges: [number, number, number][]
) { ) {
super() super()
this.kind = kind this.kind = kind
this.msg = msg this.msg = msg
this.sourceRange = sourceRange this.sourceRanges = sourceRanges
Object.setPrototypeOf(this, KCLError.prototype) Object.setPrototypeOf(this, KCLError.prototype)
} }
} }
export class KCLLexicalError extends KCLError { export class KCLLexicalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('lexical', msg, sourceRange) super('lexical', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLInternalError extends KCLError { export class KCLInternalError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('internal', msg, sourceRange) super('internal', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLSyntaxError extends KCLError { export class KCLSyntaxError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('syntax', msg, sourceRange) super('syntax', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSyntaxError.prototype) Object.setPrototypeOf(this, KCLSyntaxError.prototype)
} }
} }
export class KCLSemanticError extends KCLError { export class KCLSemanticError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('semantic', msg, sourceRange) super('semantic', msg, sourceRanges)
Object.setPrototypeOf(this, KCLSemanticError.prototype) Object.setPrototypeOf(this, KCLSemanticError.prototype)
} }
} }
export class KCLTypeError extends KCLError { export class KCLTypeError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('type', msg, sourceRange) super('type', msg, sourceRanges)
Object.setPrototypeOf(this, KCLTypeError.prototype) Object.setPrototypeOf(this, KCLTypeError.prototype)
} }
} }
export class KCLUnimplementedError extends KCLError { export class KCLUnimplementedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unimplemented', msg, sourceRange) super('unimplemented', msg, sourceRanges)
Object.setPrototypeOf(this, KCLUnimplementedError.prototype) Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
} }
} }
export class KCLUnexpectedError extends KCLError { export class KCLUnexpectedError extends KCLError {
constructor(msg: string, sourceRange: SourceRange) { constructor(msg: string, sourceRanges: [number, number, number][]) {
super('unexpected', msg, sourceRange) super('unexpected', msg, sourceRanges)
Object.setPrototypeOf(this, KCLUnexpectedError.prototype) Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
} }
} }
export class KCLValueAlreadyDefined extends KCLError { export class KCLValueAlreadyDefined extends KCLError {
constructor(key: string, sourceRange: SourceRange) { constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} was already defined elsewhere`, sourceRange) super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype) Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
} }
} }
export class KCLUndefinedValueError extends KCLError { export class KCLUndefinedValueError extends KCLError {
constructor(key: string, sourceRange: SourceRange) { constructor(key: string, sourceRanges: [number, number, number][]) {
super('name', `Key ${key} has not been defined`, sourceRange) super('name', `Key ${key} has not been defined`, sourceRanges)
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype) Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
} }
} }
@ -101,14 +99,27 @@ export function lspDiagnosticsToKclErrors(
.flatMap( .flatMap(
({ range, message }) => ({ range, message }) =>
new KCLError('unexpected', message, [ new KCLError('unexpected', message, [
posToOffset(doc, range.start)!, [
posToOffset(doc, range.end)!, posToOffset(doc, range.start)!,
true, posToOffset(doc, range.end)!,
TOP_LEVEL_MODULE_ID,
],
]) ])
) )
.filter(({ sourceRanges }) => {
const [from, to, moduleId] = sourceRanges[0]
return (
from !== null &&
to !== null &&
from !== undefined &&
to !== undefined &&
// Filter out errors that are not from the top-level module.
moduleId === TOP_LEVEL_MODULE_ID
)
})
.sort((a, b) => { .sort((a, b) => {
const c = a.sourceRange[0] const c = a.sourceRanges[0][0]
const d = b.sourceRange[0] const d = b.sourceRanges[0][0]
switch (true) { switch (true) {
case c < d: case c < d:
return -1 return -1
@ -126,48 +137,17 @@ export function lspDiagnosticsToKclErrors(
export function kclErrorsToDiagnostics( export function kclErrorsToDiagnostics(
errors: KCLError[] errors: KCLError[]
): CodeMirrorDiagnostic[] { ): CodeMirrorDiagnostic[] {
return errors return errors?.flatMap((err) => {
?.filter((err) => err.sourceRange[2]) const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
.map((err) => { // Filter out errors that are not from the top-level module.
return { .filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
from: err.sourceRange[0], .map(([from, to]) => {
to: err.sourceRange[1], return { from, to, message: err.msg, severity: 'error' }
message: err.msg, })
severity: 'error', // Make sure we didn't filter out all the source ranges.
} if (sourceRanges.length === 0) {
}) sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
} }
return sourceRanges
export function complilationErrorsToDiagnostics( })
errors: CompilationError[]
): CodeMirrorDiagnostic[] {
return errors
?.filter((err) => err.sourceRange[2] === 0)
.map((err) => {
let severity: any = 'error'
if (err.severity === 'Warning') {
severity = 'warning'
}
let actions
const suggestion = err.suggestion
if (suggestion) {
actions = [
{
name: suggestion.title,
apply: (view: EditorView, from: number, to: number) => {
view.dispatch({
changes: { from, to, insert: suggestion.insert },
})
},
},
]
}
return {
from: err.sourceRange[0],
to: err.sourceRange[1],
message: err.message,
severity,
actions,
}
})
} }

View File

@ -1,7 +1,7 @@
import fs from 'node:fs' import fs from 'node:fs'
import { import {
assertParse, parse,
ProgramMemory, ProgramMemory,
Sketch, Sketch,
initPromise, initPromise,
@ -472,7 +472,7 @@ describe('Testing Errors', () => {
const theExtrude = startSketchOn('XY') const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([-2.4, 5], %) |> line([-2.4, 5], %)
|> line(myVarZ, %) |> line([-0.76], myVarZ, %)
|> line([5,5], %) |> line([5,5], %)
|> close(%) |> close(%)
|> extrude(4, %)` |> extrude(4, %)`
@ -480,7 +480,7 @@ const theExtrude = startSketchOn('XY')
new KCLError( new KCLError(
'undefined_value', 'undefined_value',
'memory item key `myVarZ` is not defined', 'memory item key `myVarZ` is not defined',
[129, 135, true] [[129, 135, 0]]
) )
) )
}) })
@ -492,7 +492,7 @@ async function exe(
code: string, code: string,
programMemory: ProgramMemory = ProgramMemory.empty() programMemory: ProgramMemory = ProgramMemory.empty()
) { ) {
const ast = assertParse(code) const ast = parse(code)
const execState = await enginelessExecutor(ast, programMemory) const execState = await enginelessExecutor(ast, programMemory)
return execState.memory return execState.memory

View File

@ -1,5 +1,5 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst' import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { Identifier, assertParse, initPromise, Parameter } from './wasm' import { Identifier, parse, initPromise, Parameter } from './wasm'
import { err } from 'lib/trap' import { err } from 'lib/trap'
beforeAll(async () => { beforeAll(async () => {
@ -17,19 +17,19 @@ const sk3 = startSketchAt([0, 0])
` `
const subStr = 'lineTo([3, 4], %, $yo)' const subStr = 'lineTo([3, 4], %, $yo)'
const lineToSubstringIndex = code.indexOf(subStr) const lineToSubstringIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [ const sourceRange: [number, number] = [
lineToSubstringIndex, lineToSubstringIndex,
lineToSubstringIndex + subStr.length, lineToSubstringIndex + subStr.length,
true,
] ]
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange) const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<any>(ast, nodePath) const _node = getNodeFromPath<any>(ast, nodePath)
if (err(_node)) throw _node if (err(_node)) throw _node
const { node } = _node const { node } = _node
expect([node.start, node.end, true]).toEqual(sourceRange) expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression') expect(node.type).toBe('CallExpression')
}) })
it('gets path right for function definition params', () => { it('gets path right for function definition params', () => {
@ -45,13 +45,13 @@ const sk3 = startSketchAt([0, 0])
const b1 = cube([0,0], 10)` const b1 = cube([0,0], 10)`
const subStr = 'pos, scale' const subStr = 'pos, scale'
const subStrIndex = code.indexOf(subStr) const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [ const sourceRange: [number, number] = [
subStrIndex, subStrIndex,
subStrIndex + 'pos'.length, subStrIndex + 'pos'.length,
true,
] ]
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange) const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Parameter>(ast, nodePath) const _node = getNodeFromPath<Parameter>(ast, nodePath)
if (err(_node)) throw _node if (err(_node)) throw _node
@ -60,7 +60,8 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([ expect(nodePath).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['params', 'FunctionExpression'], ['params', 'FunctionExpression'],
[0, 'index'], [0, 'index'],
@ -81,13 +82,13 @@ const b1 = cube([0,0], 10)`
const b1 = cube([0,0], 10)` const b1 = cube([0,0], 10)`
const subStr = 'scale, 0' const subStr = 'scale, 0'
const subStrIndex = code.indexOf(subStr) const subStrIndex = code.indexOf(subStr)
const sourceRange: [number, number, boolean] = [ const sourceRange: [number, number] = [
subStrIndex, subStrIndex,
subStrIndex + 'scale'.length, subStrIndex + 'scale'.length,
true,
] ]
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const nodePath = getNodePathFromSourceRange(ast, sourceRange) const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const _node = getNodeFromPath<Identifier>(ast, nodePath) const _node = getNodeFromPath<Identifier>(ast, nodePath)
if (err(_node)) throw _node if (err(_node)) throw _node
@ -95,12 +96,14 @@ const b1 = cube([0,0], 10)`
expect(nodePath).toEqual([ expect(nodePath).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'FunctionExpression'], ['body', 'FunctionExpression'],
['body', 'FunctionExpression'], ['body', 'FunctionExpression'],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[2, 'index'], [2, 'index'],

View File

@ -1,5 +1,6 @@
import { assertParse, initPromise, programMemoryInit } from './wasm' import { parse, initPromise, programMemoryInit } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { assert } from 'vitest'
// These unit tests makes web requests to a public github repository. // These unit tests makes web requests to a public github repository.
interface KclSampleFile { interface KclSampleFile {
@ -57,7 +58,8 @@ describe('Test KCL Samples from public Github repository', () => {
files.forEach((file: KclSampleFile) => { files.forEach((file: KclSampleFile) => {
it(`should parse ${file.filename} without errors`, async () => { it(`should parse ${file.filename} without errors`, async () => {
const code = await getKclSampleCodeFromGithub(file.filename) const code = await getKclSampleCodeFromGithub(file.filename)
assertParse(code) const parsed = parse(code)
assert(!(parsed instanceof Error))
}, 1000) }, 1000)
}) })
}) })
@ -69,8 +71,9 @@ describe('Test KCL Samples from public Github repository', () => {
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
const file: KclSampleFile = files[i] const file: KclSampleFile = files[i]
const code = await getKclSampleCodeFromGithub(file.filename) const code = await getKclSampleCodeFromGithub(file.filename)
const ast = assertParse(code) const parsed = parse(code)
await enginelessExecutor(ast, programMemoryInit()) assert(!(parsed instanceof Error))
await enginelessExecutor(parsed, programMemoryInit())
} }
}, },
files.length * 1000 files.length * 1000

View File

@ -2,6 +2,7 @@ import {
Program, Program,
_executor, _executor,
ProgramMemory, ProgramMemory,
programMemoryInit,
kclLint, kclLint,
emptyExecState, emptyExecState,
ExecState, ExecState,
@ -10,6 +11,7 @@ import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection' import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors' import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint' import { Diagnostic } from '@codemirror/lint'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
export type ToolTip = export type ToolTip =
@ -47,13 +49,15 @@ export const toolTips: Array<ToolTip> = [
export async function executeAst({ export async function executeAst({
ast, ast,
engineCommandManager, engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that useFakeExecutor = false,
// is the only way to go about it.
programMemoryOverride, programMemoryOverride,
idGenerator,
}: { }: {
ast: Node<Program> ast: Node<Program>
engineCommandManager: EngineCommandManager engineCommandManager: EngineCommandManager
useFakeExecutor?: boolean
programMemoryOverride?: ProgramMemory programMemoryOverride?: ProgramMemory
idGenerator?: IdGenerator
isInterrupted?: boolean isInterrupted?: boolean
}): Promise<{ }): Promise<{
logs: string[] logs: string[]
@ -62,14 +66,22 @@ export async function executeAst({
isInterrupted: boolean isInterrupted: boolean
}> { }> {
try { try {
const execState = await (programMemoryOverride if (!useFakeExecutor) {
? enginelessExecutor(ast, programMemoryOverride) engineCommandManager.endSession()
: _executor(ast, engineCommandManager)) // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession()
await engineCommandManager.waitForAllCommands( }
programMemoryOverride !== undefined const execState = await (useFakeExecutor
) ? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
: _executor(
ast,
programMemoryInit(),
idGenerator,
engineCommandManager,
false
))
await engineCommandManager.waitForAllCommands(useFakeExecutor)
return { return {
logs: [], logs: [],
errors: [], errors: [],

View File

@ -1,4 +1,4 @@
import { assertParse, recast, initPromise, Identifier } from './wasm' import { parse, recast, initPromise, Identifier } from './wasm'
import { import {
createLiteral, createLiteral,
createIdentifier, createIdentifier,
@ -82,11 +82,11 @@ describe('Testing createVariableDeclaration', () => {
it('should create a variable declaration', () => { it('should create a variable declaration', () => {
const result = createVariableDeclaration('myVar', createLiteral(5)) const result = createVariableDeclaration('myVar', createLiteral(5))
expect(result.type).toBe('VariableDeclaration') expect(result.type).toBe('VariableDeclaration')
expect(result.declaration.type).toBe('VariableDeclarator') expect(result.declarations[0].type).toBe('VariableDeclarator')
expect(result.declaration.id.type).toBe('Identifier') expect(result.declarations[0].id.type).toBe('Identifier')
expect(result.declaration.id.name).toBe('myVar') expect(result.declarations[0].id.name).toBe('myVar')
expect(result.declaration.init.type).toBe('Literal') expect(result.declarations[0].init.type).toBe('Literal')
expect((result.declaration.init as any).value).toBe(5) expect((result.declarations[0].init as any).value).toBe(5)
}) })
}) })
describe('Testing createPipeExpression', () => { describe('Testing createPipeExpression', () => {
@ -146,13 +146,10 @@ function giveSketchFnCallTagTestHelper(
// giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing // giveSketchFnCallTag inputs and outputs an ast, which is very verbose for testing
// this wrapper changes the input and output to code // this wrapper changes the input and output to code
// making it more of an integration test, but easier to read the test intention is the goal // making it more of an integration test, but easier to read the test intention is the goal
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const start = code.indexOf(searchStr) const start = code.indexOf(searchStr)
const range: [number, number, boolean] = [ const range: [number, number] = [start, start + searchStr.length]
start,
start + searchStr.length,
true,
]
const sketchRes = giveSketchFnCallTag(ast, range) const sketchRes = giveSketchFnCallTag(ast, range)
if (err(sketchRes)) throw sketchRes if (err(sketchRes)) throw sketchRes
const { modifiedAst, tag, isTagExisting } = sketchRes const { modifiedAst, tag, isTagExisting } = sketchRes
@ -224,13 +221,14 @@ part001 = startSketchOn('XY')
|> angledLine([jkl(yo) + 2, 3.09], %) |> angledLine([jkl(yo) + 2, 3.09], %)
yo2 = hmm([identifierGuy + 5])` yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => { it('should move a binary expression into a new variable', async () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('100 + 100') + 1 const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.memory,
[startIndex, startIndex, true], [startIndex, startIndex],
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
@ -238,13 +236,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`) expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
}) })
it('should move a value into a new variable', async () => { it('should move a value into a new variable', async () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('2.8') + 1 const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.memory,
[startIndex, startIndex, true], [startIndex, startIndex],
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
@ -252,13 +251,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`line([newVar, 0], %)`) expect(newCode).toContain(`line([newVar, 0], %)`)
}) })
it('should move a callExpression into a new variable', async () => { it('should move a callExpression into a new variable', async () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('def(') const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.memory,
[startIndex, startIndex, true], [startIndex, startIndex],
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
@ -266,13 +266,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`) expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
}) })
it('should move a binary expression with call expression into a new variable', async () => { it('should move a binary expression with call expression into a new variable', async () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('jkl(') + 1 const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.memory,
[startIndex, startIndex, true], [startIndex, startIndex],
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
@ -280,13 +281,14 @@ yo2 = hmm([identifierGuy + 5])`
expect(newCode).toContain(`angledLine([newVar, 3.09], %)`) expect(newCode).toContain(`angledLine([newVar, 3.09], %)`)
}) })
it('should move a identifier into a new variable', async () => { it('should move a identifier into a new variable', async () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const startIndex = code.indexOf('identifierGuy +') + 1 const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable( const { modifiedAst } = moveValueIntoNewVariable(
ast, ast,
execState.memory, execState.memory,
[startIndex, startIndex, true], [startIndex, startIndex],
'newVar' 'newVar'
) )
const newCode = recast(modifiedAst) const newCode = recast(modifiedAst)
@ -303,20 +305,19 @@ describe('testing sketchOnExtrudedFace', () => {
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `line([9.7, 9.19], %)` const segmentSnippet = `line([9.7, 9.19], %)`
const segmentRange: [number, number, boolean] = [ const segmentRange: [number, number] = [
code.indexOf(segmentSnippet), code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
] ]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange) const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)` const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [ const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet), code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length, code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
] ]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange) const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -344,19 +345,18 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `close(%)` const segmentSnippet = `close(%)`
const segmentRange: [number, number, boolean] = [ const segmentRange: [number, number] = [
code.indexOf(segmentSnippet), code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
] ]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange) const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, %)` const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [ const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet), code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length, code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
] ]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange) const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -384,19 +384,18 @@ sketch001 = startSketchOn(part001, seg01)`)
|> line([8.62, -9.57], %) |> line([8.62, -9.57], %)
|> close(%) |> close(%)
|> extrude(5 + 7, %)` |> extrude(5 + 7, %)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const sketchSnippet = `startProfileAt([3.58, 2.06], %)` const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
const sketchRange: [number, number, boolean] = [ const sketchRange: [number, number] = [
code.indexOf(sketchSnippet), code.indexOf(sketchSnippet),
code.indexOf(sketchSnippet) + sketchSnippet.length, code.indexOf(sketchSnippet) + sketchSnippet.length,
true,
] ]
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange) const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const extrudeSnippet = `extrude(5 + 7, %)` const extrudeSnippet = `extrude(5 + 7, %)`
const extrudeRange: [number, number, boolean] = [ const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet), code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length, code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
] ]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange) const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -433,19 +432,18 @@ sketch001 = startSketchOn(part001, 'END')`)
|> line([-17.67, 0.85], %) |> line([-17.67, 0.85], %)
|> close(%) |> close(%)
part001 = extrude(5 + 7, sketch001)` part001 = extrude(5 + 7, sketch001)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const segmentSnippet = `line([4.99, -0.46], %)` const segmentSnippet = `line([4.99, -0.46], %)`
const segmentRange: [number, number, boolean] = [ const segmentRange: [number, number] = [
code.indexOf(segmentSnippet), code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
] ]
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange) const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
const extrudeSnippet = `extrude(5 + 7, sketch001)` const extrudeSnippet = `extrude(5 + 7, sketch001)`
const extrudeRange: [number, number, boolean] = [ const extrudeRange: [number, number] = [
code.indexOf(extrudeSnippet), code.indexOf(extrudeSnippet),
code.indexOf(extrudeSnippet) + extrudeSnippet.length, code.indexOf(extrudeSnippet) + extrudeSnippet.length,
true,
] ]
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange) const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
@ -468,13 +466,13 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|> line([306.21, 198.82], %) |> line([306.21, 198.82], %)
|> line([306.21, 198.85], %, $a) |> line([306.21, 198.85], %, $a)
|> line([306.21, 198.87], %)` |> line([306.21, 198.87], %)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = 'line([306.21, 198.85], %, $a)' const lineOfInterest = 'line([306.21, 198.85], %, $a)'
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
const modifiedAst = deleteSegmentFromPipeExpression( const modifiedAst = deleteSegmentFromPipeExpression(
@ -546,13 +544,13 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
], ],
])(`%s`, async (_, line, [replace1, replace2]) => { ])(`%s`, async (_, line, [replace1, replace2]) => {
const code = makeCode(line) const code = makeCode(line)
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = line const lineOfInterest = line
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode) const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
@ -634,14 +632,14 @@ describe('Testing removeSingleConstraintInfo', () => {
], ],
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1], ['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
] as const)('stdlib fn: %s', async (expectedFinish, key, value) => { ] as const)('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
let argPosition: SimplifiedArgDetails let argPosition: SimplifiedArgDetails
@ -688,14 +686,14 @@ describe('Testing removeSingleConstraintInfo', () => {
['angledLineToX([12.14 + 0, 12], %)', 'arrayIndex', 1], ['angledLineToX([12.14 + 0, 12], %)', 'arrayIndex', 1],
['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0], ['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0],
])('stdlib fn: %s', async (expectedFinish, key, value) => { ])('stdlib fn: %s', async (expectedFinish, key, value) => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const lineOfInterest = expectedFinish.split('(')[0] + '(' const lineOfInterest = expectedFinish.split('(')[0] + '('
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest) + 1, code.indexOf(lineOfInterest) + 1,
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
let argPosition: SimplifiedArgDetails let argPosition: SimplifiedArgDetails
if (key === 'arrayIndex' && typeof value === 'number') { if (key === 'arrayIndex' && typeof value === 'number') {
@ -885,14 +883,14 @@ sketch002 = startSketchOn({
'%s', '%s',
async (name, { codeBefore, codeAfter, lineOfInterest, type }) => { async (name, { codeBefore, codeAfter, lineOfInterest, type }) => {
// const lineOfInterest = 'line([-2.94, 2.7], %)' // const lineOfInterest = 'line([-2.94, 2.7], %)'
const ast = assertParse(codeBefore) const ast = parse(codeBefore)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
// deleteFromSelection // deleteFromSelection
const range: [number, number, boolean] = [ const range: [number, number] = [
codeBefore.indexOf(lineOfInterest), codeBefore.indexOf(lineOfInterest),
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length, codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const artifact = { type } as Artifact const artifact = { type } as Artifact
const newAst = await deleteFromSelection( const newAst = await deleteFromSelection(

View File

@ -45,7 +45,6 @@ import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { ExtrudeFacePlane } from 'machines/modelingMachine' import { ExtrudeFacePlane } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { KclExpressionWithVariable } from 'lib/commandTypes'
export function startSketchOnDefault( export function startSketchOnDefault(
node: Node<Program>, node: Node<Program>,
@ -67,7 +66,8 @@ export function startSketchOnDefault(
let pathToNode: PathToNode = [ let pathToNode: PathToNode = [
['body', ''], ['body', ''],
[sketchIndex, 'index'], [sketchIndex, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
@ -94,7 +94,7 @@ export function addStartProfileAt(
return new Error('variableDeclaration.init.type !== PipeExpression') return new Error('variableDeclaration.init.type !== PipeExpression')
} }
const _node = { ...node } const _node = { ...node }
const init = variableDeclaration.declaration.init const init = variableDeclaration.declarations[0].init
const startProfileAt = createCallExpressionStdLib('startProfileAt', [ const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([ createArrayExpression([
createLiteral(roundOff(at[0])), createLiteral(roundOff(at[0])),
@ -105,7 +105,7 @@ export function addStartProfileAt(
if (init.type === 'PipeExpression') { if (init.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt) init.body.splice(1, 0, startProfileAt)
} else { } else {
variableDeclaration.declaration.init = createPipeExpression([ variableDeclaration.declarations[0].init = createPipeExpression([
init, init,
startProfileAt, startProfileAt,
]) ])
@ -149,7 +149,8 @@ export function addSketchTo(
let pathToNode: PathToNode = [ let pathToNode: PathToNode = [
['body', ''], ['body', ''],
[sketchIndex, 'index'], [sketchIndex, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
if (axis !== 'xy') { if (axis !== 'xy') {
@ -332,7 +333,8 @@ export function extrudeSketch(
const pathToExtrudeArg: PathToNode = [ const pathToExtrudeArg: PathToNode = [
['body', ''], ['body', ''],
[sketchIndexInBody + 1, 'index'], [sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -344,36 +346,6 @@ export function extrudeSketch(
} }
} }
export function loftSketches(
node: Node<Program>,
declarators: VariableDeclarator[]
): {
modifiedAst: Node<Program>
pathToNode: PathToNode
} {
const modifiedAst = structuredClone(node)
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.LOFT)
const elements = declarators.map((d) => createIdentifier(d.id.name))
const loft = createCallExpressionStdLib('loft', [
createArrayExpression(elements),
])
const declaration = createVariableDeclaration(name, loft)
modifiedAst.body.push(declaration)
const pathToNode: PathToNode = [
['body', ''],
[modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}
export function revolveSketch( export function revolveSketch(
node: Node<Program>, node: Node<Program>,
pathToNode: PathToNode, pathToNode: PathToNode,
@ -457,7 +429,8 @@ export function revolveSketch(
const pathToRevolveArg: PathToNode = [ const pathToRevolveArg: PathToNode = [
['body', ''], ['body', ''],
[sketchIndexInBody + 1, 'index'], [sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -543,7 +516,8 @@ export function sketchOnExtrudedFace(
const newpathToNode: PathToNode = [ const newpathToNode: PathToNode = [
['body', ''], ['body', ''],
[expressionIndex + 1, 'index'], [expressionIndex + 1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
] ]
@ -580,7 +554,8 @@ export function addOffsetPlane({
const pathToNode: PathToNode = [ const pathToNode: PathToNode = [
['body', ''], ['body', ''],
[modifiedAst.body.length - 1, 'index'], [modifiedAst.body.length - 1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
['0', 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
['arguments', 'CallExpression'], ['arguments', 'CallExpression'],
[0, 'index'], [0, 'index'],
@ -591,25 +566,6 @@ export function addOffsetPlane({
} }
} }
/**
* Return a modified clone of an AST with a named constant inserted into the body
*/
export function insertNamedConstant({
node,
newExpression,
}: {
node: Node<Program>
newExpression: KclExpressionWithVariable
}): Node<Program> {
const ast = structuredClone(node)
ast.body.splice(
newExpression.insertIndex,
0,
newExpression.variableDeclarationAst
)
return ast
}
/** /**
* Modify the AST to create a new sketch using the variable declaration * Modify the AST to create a new sketch using the variable declaration
* of an offset plane. The new sketch just has to come after the offset * of an offset plane. The new sketch just has to come after the offset
@ -836,15 +792,17 @@ export function createVariableDeclaration(
end: 0, end: 0,
moduleId: 0, moduleId: 0,
declaration: { declarations: [
type: 'VariableDeclarator', {
start: 0, type: 'VariableDeclarator',
end: 0, start: 0,
moduleId: 0, end: 0,
moduleId: 0,
id: createIdentifier(varName), id: createIdentifier(varName),
init, init,
}, },
],
visibility, visibility,
kind, kind,
} }
@ -953,31 +911,6 @@ export function giveSketchFnCallTag(
} }
} }
/**
* Replace a
*/
export function replaceValueAtNodePath({
ast,
pathToNode,
newExpressionString,
}: {
ast: Node<Program>
pathToNode: PathToNode
newExpressionString: string
}) {
const replaceCheckResult = isNodeSafeToReplacePath(ast, pathToNode)
if (err(replaceCheckResult)) {
return replaceCheckResult
}
const { isSafe, value, replacer } = replaceCheckResult
if (!isSafe || value.type === 'Identifier') {
return new Error('Not safe to replace')
}
return replacer(ast, newExpressionString)
}
export function moveValueIntoNewVariablePath( export function moveValueIntoNewVariablePath(
ast: Node<Program>, ast: Node<Program>,
programMemory: ProgramMemory, programMemory: ProgramMemory,
@ -1156,7 +1089,7 @@ export async function deleteFromSelection(
traverse(astClone, { traverse(astClone, {
enter: (node, path) => { enter: (node, path) => {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
const dec = node.declaration const dec = node.declarations[0]
if ( if (
dec.init.type === 'CallExpression' && dec.init.type === 'CallExpression' &&
(dec.init.callee.name === 'extrude' || (dec.init.callee.name === 'extrude' ||
@ -1191,7 +1124,7 @@ export async function deleteFromSelection(
enter: (node, path) => { enter: (node, path) => {
;(async () => { ;(async () => {
if (node.type === 'VariableDeclaration') { if (node.type === 'VariableDeclaration') {
currentVariableName = node.declaration.id.name currentVariableName = node.declarations[0].id.name
} }
if ( if (
// match startSketchOn(${extrudeNameToDelete}) // match startSketchOn(${extrudeNameToDelete})

View File

@ -1,5 +1,5 @@
import { import {
assertParse, parse,
recast, recast,
initPromise, initPromise,
PathToNode, PathToNode,
@ -18,11 +18,11 @@ import {
FilletParameters, FilletParameters,
ChamferParameters, ChamferParameters,
EdgeTreatmentParameters, EdgeTreatmentParameters,
} from './addEdgeTreatment' } from './addFillet'
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst' import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
import { createLiteral } from 'lang/modifyAst' import { createLiteral } from 'lang/modifyAst'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Selection, Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { engineCommandManager, kclManager } from 'lib/singletons' import { engineCommandManager, kclManager } from 'lib/singletons'
import { VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_DEV_TOKEN } from 'env'
import { isOverlap } from 'lib/utils' import { isOverlap } from 'lib/utils'
@ -78,10 +78,9 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code: string, code: string,
expectedExtrudeSnippet: string expectedExtrudeSnippet: string
): CallExpression | PipeExpression | Error { ): CallExpression | PipeExpression | Error {
const extrudeRange: [number, number, boolean] = [ const extrudeRange: [number, number] = [
code.indexOf(expectedExtrudeSnippet), code.indexOf(expectedExtrudeSnippet),
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length, code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
true,
] ]
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange) const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expectedExtrudeNodeResult = getNodeFromPath< const expectedExtrudeNodeResult = getNodeFromPath<
@ -110,16 +109,22 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
} }
// ast // ast
const ast = assertParse(code) const astOrError = parse(code)
if (err(astOrError)) return new Error('AST not found')
const ast = astOrError
// selection // selection
const segmentRange: [number, number, boolean] = [ const segmentRange: [number, number] = [
code.indexOf(selectedSegmentSnippet), code.indexOf(selectedSegmentSnippet),
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length, code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
true,
] ]
const selection: Selection = { const selection: Selections = {
codeRef: codeRefFromRange(segmentRange, ast), graphSelections: [
{
codeRef: codeRefFromRange(segmentRange, ast),
},
],
otherSelections: [],
} }
// executeAst and artifactGraph // executeAst and artifactGraph
@ -258,14 +263,17 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
expectedCode: string expectedCode: string
) => { ) => {
// ast // ast
const ast = assertParse(code) const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
// selection // selection
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map( const segmentRanges: Array<[number, number]> = selectionSnippets.map(
(selectionSnippet) => [ (selectionSnippet) => [
code.indexOf(selectionSnippet), code.indexOf(selectionSnippet),
code.indexOf(selectionSnippet) + selectionSnippet.length, code.indexOf(selectionSnippet) + selectionSnippet.length,
true,
] ]
) )
@ -595,12 +603,12 @@ extrude001 = extrude(-5, sketch001)
}, %) }, %)
` `
it('should correctly identify getOppositeEdge and baseEdge edges', () => { it('should correctly identify getOppositeEdge and baseEdge edges', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([7.11, 3.48], %, $seg01)` const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return if (err(pathToNode)) return
@ -614,12 +622,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getOppositeEdge', 'baseEdge']) expect(edges).toEqual(['getOppositeEdge', 'baseEdge'])
}) })
it('should correctly identify getPreviousAdjacentEdge edges', () => { it('should correctly identify getPreviousAdjacentEdge edges', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)` const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return if (err(pathToNode)) return
@ -633,12 +641,12 @@ extrude001 = extrude(-5, sketch001)
expect(edges).toEqual(['getPreviousAdjacentEdge']) expect(edges).toEqual(['getPreviousAdjacentEdge'])
}) })
it('should correctly identify no edges', () => { it('should correctly identify no edges', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return
const lineOfInterest = `line([-3.29, -13.85], %)` const lineOfInterest = `line([-3.29, -13.85], %)`
const range: [number, number, boolean] = [ const range: [number, number] = [
code.indexOf(lineOfInterest), code.indexOf(lineOfInterest),
code.indexOf(lineOfInterest) + lineOfInterest.length, code.indexOf(lineOfInterest) + lineOfInterest.length,
true,
] ]
const pathToNode = getNodePathFromSourceRange(ast, range) const pathToNode = getNodePathFromSourceRange(ast, range)
if (err(pathToNode)) return if (err(pathToNode)) return
@ -659,15 +667,19 @@ describe('Testing button states', () => {
segmentSnippet: string, segmentSnippet: string,
expectedState: boolean expectedState: boolean
) => { ) => {
const ast = assertParse(code) // ast
const astOrError = parse(code)
if (err(astOrError)) {
return new Error('AST not found')
}
const ast = astOrError
const range: [number, number, boolean] = segmentSnippet const range: [number, number] = segmentSnippet
? [ ? [
code.indexOf(segmentSnippet), code.indexOf(segmentSnippet),
code.indexOf(segmentSnippet) + segmentSnippet.length, code.indexOf(segmentSnippet) + segmentSnippet.length,
true,
] ]
: [ast.end, ast.end, true] // empty line in the end of the code : [ast.end, ast.end] // empty line in the end of the code
const selectionRanges: Selections = { const selectionRanges: Selections = {
graphSelections: [ graphSelections: [

View File

@ -29,7 +29,7 @@ import {
sketchLineHelperMap, sketchLineHelperMap,
} from '../std/sketch' } from '../std/sketch'
import { err, trap } from 'lib/trap' import { err, trap } from 'lib/trap'
import { Selection, Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { KclCommandValue } from 'lib/commandTypes' import { KclCommandValue } from 'lib/commandTypes'
import { import {
Artifact, Artifact,
@ -99,9 +99,14 @@ export function modifyAstWithEdgeTreatmentAndTag(
const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison const lookupMap: Map<string, PathToNode> = new Map() // work around for Map key comparison
for (const selection of selections.graphSelections) { for (const selection of selections.graphSelections) {
const singleSelection = {
graphSelections: [selection],
otherSelections: [],
}
const result = getPathToExtrudeForSegmentSelection( const result = getPathToExtrudeForSegmentSelection(
clonedAstForGetExtrude, clonedAstForGetExtrude,
selection, singleSelection,
artifactGraph artifactGraph
) )
if (err(result)) return result if (err(result)) return result
@ -254,12 +259,12 @@ function insertParametersIntoAst(
export function getPathToExtrudeForSegmentSelection( export function getPathToExtrudeForSegmentSelection(
ast: Program, ast: Program,
selection: Selection, selection: Selections,
artifactGraph: ArtifactGraph artifactGraph: ArtifactGraph
): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error { ): { pathToSegmentNode: PathToNode; pathToExtrudeNode: PathToNode } | Error {
const pathToSegmentNode = getNodePathFromSourceRange( const pathToSegmentNode = getNodePathFromSourceRange(
ast, ast,
selection.codeRef?.range selection.graphSelections[0]?.codeRef?.range
) )
const varDecNode = getNodeFromPath<VariableDeclaration>( const varDecNode = getNodeFromPath<VariableDeclaration>(
@ -268,7 +273,7 @@ export function getPathToExtrudeForSegmentSelection(
'VariableDeclaration' 'VariableDeclaration'
) )
if (err(varDecNode)) return varDecNode if (err(varDecNode)) return varDecNode
const sketchVar = varDecNode.node.declaration.id.name const sketchVar = varDecNode.node.declarations[0].id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
kclManager.programMemory.get(sketchVar), kclManager.programMemory.get(sketchVar),
@ -303,7 +308,7 @@ async function updateAstAndFocus(
} }
} }
export function mutateAstWithTagForSketchSegment( function mutateAstWithTagForSketchSegment(
astClone: Node<Program>, astClone: Node<Program>,
pathToSegmentNode: PathToNode pathToSegmentNode: PathToNode
): { modifiedAst: Program; tag: string } | Error { ): { modifiedAst: Program; tag: string } | Error {
@ -335,7 +340,7 @@ export function mutateAstWithTagForSketchSegment(
return { modifiedAst: astClone, tag } return { modifiedAst: astClone, tag }
} }
export function getEdgeTagCall( function getEdgeTagCall(
tag: string, tag: string,
artifact: Artifact artifact: Artifact
): Node<Identifier | CallExpression> { ): Node<Identifier | CallExpression> {
@ -362,7 +367,7 @@ function locateExtrudeDeclarator(
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = nodeOfExtrudeCall const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declaration const extrudeDeclarator = extrudeVarDecl.declarations[0]
if (!extrudeDeclarator) { if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.') return new Error('Extrude Declarator not found.')
} }

View File

@ -1,154 +0,0 @@
import { err } from 'lib/trap'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import {
Program,
PathToNode,
Expr,
CallExpression,
PipeExpression,
VariableDeclarator,
} from 'lang/wasm'
import { Selections } from 'lib/selections'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
createLiteral,
createCallExpressionStdLib,
createObjectExpression,
createIdentifier,
createPipeExpression,
findUniqueName,
createVariableDeclaration,
} from 'lang/modifyAst'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import {
mutateAstWithTagForSketchSegment,
getEdgeTagCall,
} from 'lang/modifyAst/addEdgeTreatment'
export function revolveSketch(
ast: Node<Program>,
pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4),
axis: Selections
):
| {
modifiedAst: Node<Program>
pathToSketchNode: PathToNode
pathToRevolveArg: PathToNode
}
| Error {
const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode
// testing code
const pathToAxisSelection = getNodePathFromSourceRange(
clonedAst,
axis.graphSelections[0]?.codeRef.range
)
const lineNode = getNodeFromPath<CallExpression>(
clonedAst,
pathToAxisSelection,
'CallExpression'
)
if (err(lineNode)) return lineNode
// TODO Kevin: What if |> close(%)?
// TODO Kevin: What if opposite edge
// TODO Kevin: What if the edge isn't planar to the sketch?
// TODO Kevin: add a tag.
const tagResult = mutateAstWithTagForSketchSegment(
clonedAst,
pathToAxisSelection
)
// Have the tag whether it is already created or a new one is generated
if (err(tagResult)) return tagResult
const { tag } = tagResult
/* Original Code */
const { node: sketchExpression } = sketchNode
// determine if sketchExpression is in a pipeExpression or not
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
clonedAst,
pathToSketchNode,
'PipeExpression'
)
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
const { node: sketchPipeExpression } = sketchPipeExpressionNode
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst,
pathToSketchNode,
'VariableDeclarator'
)
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const {
node: sketchVariableDeclarator,
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
const axisSelection = axis?.graphSelections[0]?.artifact
if (!axisSelection) return new Error('Axis selection is missing.')
const revolveCall = createCallExpressionStdLib('revolve', [
createObjectExpression({
angle: angle,
axis: getEdgeTagCall(tag, axisSelection),
}),
createIdentifier(sketchVariableDeclarator.id.name),
])
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...sketchPipeExpression.body, revolveCall]
: [sketchExpression as any, revolveCall]
)
sketchVariableDeclarator.init = pipeChain
const pathToRevolveArg: PathToNode = [
...sketchPathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode,
pathToRevolveArg,
}
}
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
const sketchIndexInPathToNode =
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
if (typeof sketchIndexInBody !== 'number')
return new Error('expected sketchIndexInBody to be a number')
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToRevolveArg: PathToNode = [
['body', ''],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode: [...pathToSketchNode.slice(0, -1), [-1, 'index']],
pathToRevolveArg,
}
}

View File

@ -1,123 +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'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst,
pathToNode,
}
}

View File

@ -1,10 +1,4 @@
import { import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
assertParse,
recast,
initPromise,
PathToNode,
Identifier,
} from './wasm'
import { import {
findAllPreviousVariables, findAllPreviousVariables,
isNodeSafeToReplace, isNodeSafeToReplace,
@ -17,7 +11,6 @@ import {
doesSceneHaveSweepableSketch, doesSceneHaveSweepableSketch,
traverse, traverse,
getNodeFromPath, getNodeFromPath,
doesSceneHaveExtrudedSketch,
} from './queryAst' } from './queryAst'
import { enginelessExecutor } from '../lib/testHelpers' import { enginelessExecutor } from '../lib/testHelpers'
import { import {
@ -52,13 +45,14 @@ part001 = startSketchOn('XY')
variableBelowShouldNotBeIncluded = 3 variableBelowShouldNotBeIncluded = 3
` `
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7 const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const { variables, bodyPath, insertIndex } = findAllPreviousVariables( const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast, ast,
execState.memory, execState.memory,
[rangeStart, rangeStart, true] [rangeStart, rangeStart]
) )
expect(variables).toEqual([ expect(variables).toEqual([
{ key: 'baseThick', value: 1 }, { key: 'baseThick', value: 1 },
@ -86,9 +80,10 @@ describe('testing argIsNotIdentifier', () => {
yo = 5 + 6 yo = 5 + 6
yo2 = hmm([identifierGuy + 5])` yo2 = hmm([identifierGuy + 5])`
it('find a safe binaryExpression', () => { it('find a safe binaryExpression', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('100 + 100') + 2 const rangeStart = code.indexOf('100 + 100') + 2
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression') expect(result.value?.type).toBe('BinaryExpression')
@ -99,18 +94,20 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
}) })
it('find a safe Identifier', () => { it('find a safe Identifier', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('abc') const rangeStart = code.indexOf('abc')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('Identifier') expect(result.value?.type).toBe('Identifier')
expect(code.slice(result.value.start, result.value.end)).toBe('abc') expect(code.slice(result.value.start, result.value.end)).toBe('abc')
}) })
it('find a safe CallExpression', () => { it('find a safe CallExpression', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('def') const rangeStart = code.indexOf('def')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('CallExpression') expect(result.value?.type).toBe('CallExpression')
@ -121,9 +118,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
}) })
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => { it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('ghi') const rangeStart = code.indexOf('ghi')
const range: [number, number, boolean] = [rangeStart, rangeStart, true] const range: [number, number] = [rangeStart, rangeStart]
const result = isNodeSafeToReplace(ast, range) const result = isNodeSafeToReplace(ast, range)
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(false) expect(result.isSafe).toBe(false)
@ -131,9 +129,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)') expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
}) })
it('find an UNsafe Identifier, as it is a callee', () => { it('find an UNsafe Identifier, as it is a callee', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('ine([2.8,') const rangeStart = code.indexOf('ine([2.8,')
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(false) expect(result.isSafe).toBe(false)
expect(result.value?.type).toBe('CallExpression') expect(result.value?.type).toBe('CallExpression')
@ -142,9 +141,10 @@ yo2 = hmm([identifierGuy + 5])`
) )
}) })
it("find a safe BinaryExpression that's assigned to a variable", () => { it("find a safe BinaryExpression that's assigned to a variable", () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('5 + 6') + 1 const rangeStart = code.indexOf('5 + 6') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression') expect(result.value?.type).toBe('BinaryExpression')
@ -155,9 +155,10 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`yo = replaceName`) expect(outCode).toContain(`yo = replaceName`)
}) })
it('find a safe BinaryExpression that has a CallExpression within', () => { it('find a safe BinaryExpression that has a CallExpression within', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('jkl') + 1 const rangeStart = code.indexOf('jkl') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
expect(result.value?.type).toBe('BinaryExpression') expect(result.value?.type).toBe('BinaryExpression')
@ -171,10 +172,11 @@ yo2 = hmm([identifierGuy + 5])`
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`) expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
}) })
it('find a safe BinaryExpression within a CallExpression', () => { it('find a safe BinaryExpression within a CallExpression', () => {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const rangeStart = code.indexOf('identifierGuy') + 1 const rangeStart = code.indexOf('identifierGuy') + 1
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true]) const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
if (err(result)) throw result if (err(result)) throw result
expect(result.isSafe).toBe(true) expect(result.isSafe).toBe(true)
@ -221,17 +223,15 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the second line when cursor is put at the end', () => { it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)` const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [ const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[2, 'index'], [2, 'index'],
@ -240,17 +240,15 @@ describe('testing getNodePathFromSourceRange', () => {
it('finds the last line when cursor is put at the end', () => { it('finds the last line when cursor is put at the end', () => {
const searchLn = `line([-0.21, -1.4], %)` const searchLn = `line([-0.21, -1.4], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length const sourceIndex = code.indexOf(searchLn) + searchLn.length
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [ const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
sourceIndex,
sourceIndex,
true,
])
const expected = [ const expected = [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['body', 'PipeExpression'], ['body', 'PipeExpression'],
[3, 'index'], [3, 'index'],
@ -261,14 +259,12 @@ describe('testing getNodePathFromSourceRange', () => {
const startResult = getNodePathFromSourceRange(ast, [ const startResult = getNodePathFromSourceRange(ast, [
startSourceIndex, startSourceIndex,
startSourceIndex, startSourceIndex,
true,
]) ])
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']]) expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
// expect similar result when whole line is selected // expect similar result when whole line is selected
const selectWholeThing = getNodePathFromSourceRange(ast, [ const selectWholeThing = getNodePathFromSourceRange(ast, [
startSourceIndex, startSourceIndex,
sourceIndex, sourceIndex,
true,
]) ])
expect(selectWholeThing).toEqual(expected) expect(selectWholeThing).toEqual(expected)
}) })
@ -282,17 +278,15 @@ describe('testing getNodePathFromSourceRange', () => {
}` }`
const searchLn = `x > y` const searchLn = `x > y`
const sourceIndex = code.indexOf(searchLn) const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [ const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[1, 'index'], [1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['cond', 'IfExpression'], ['cond', 'IfExpression'],
['left', 'BinaryExpression'], ['left', 'BinaryExpression'],
@ -312,17 +306,15 @@ describe('testing getNodePathFromSourceRange', () => {
}` }`
const searchLn = `x + 1` const searchLn = `x + 1`
const sourceIndex = code.indexOf(searchLn) const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [ const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[1, 'index'], [1, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', ''], ['init', ''],
['then_val', 'IfExpression'], ['then_val', 'IfExpression'],
['body', 'IfExpression'], ['body', 'IfExpression'],
@ -340,18 +332,14 @@ describe('testing getNodePathFromSourceRange', () => {
const code = `import foo, bar as baz from 'thing.kcl'` const code = `import foo, bar as baz from 'thing.kcl'`
const searchLn = `bar` const searchLn = `bar`
const sourceIndex = code.indexOf(searchLn) const sourceIndex = code.indexOf(searchLn)
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
const result = getNodePathFromSourceRange(ast, [ const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
sourceIndex,
sourceIndex,
true,
])
expect(result).toEqual([ expect(result).toEqual([
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['selector', 'ImportStatement'], ['items', 'ImportStatement'],
['items', 'ImportSelector'],
[1, 'index'], [1, 'index'],
['name', 'ImportItem'], ['name', 'ImportItem'],
]) ])
@ -372,13 +360,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-175, segLen(seg01)], %) |> angledLine([-175, segLen(seg01)], %)
|> close(%) |> close(%)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({ const result = doesPipeHaveCallExp({
calleeName: 'close', calleeName: 'close',
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([100, 101, true], ast), codeRef: codeRefFromRange([100, 101], ast),
}, },
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
@ -393,13 +382,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> close(%) |> close(%)
|> extrude(1, %) |> extrude(1, %)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({ const result = doesPipeHaveCallExp({
calleeName: 'extrude', calleeName: 'extrude',
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([100, 101, true], ast), codeRef: codeRefFromRange([100, 101], ast),
}, },
}) })
expect(result).toEqual(true) expect(result).toEqual(true)
@ -412,26 +402,28 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %) |> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %) |> angledLine([-175, segLen(seg01)], %)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({ const result = doesPipeHaveCallExp({
calleeName: 'close', calleeName: 'close',
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([100, 101, true], ast), codeRef: codeRefFromRange([100, 101], ast),
}, },
}) })
expect(result).toEqual(false) expect(result).toEqual(false)
}) })
it('returns false if not a pipe', () => { it('returns false if not a pipe', () => {
const exampleCode = `length001 = 2` const exampleCode = `length001 = 2`
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const result = doesPipeHaveCallExp({ const result = doesPipeHaveCallExp({
calleeName: 'close', calleeName: 'close',
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([9, 10, true], ast), codeRef: codeRefFromRange([9, 10], ast),
}, },
}) })
expect(result).toEqual(false) expect(result).toEqual(false)
@ -446,13 +438,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> angledLine([-35, length001], %) |> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %) |> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %)` |> angledLine([-175, segLen(seg01)], %)`
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([100, 101, true], ast), codeRef: codeRefFromRange([100, 101], ast),
}, },
programMemory: execState.memory, programMemory: execState.memory,
}) })
@ -466,13 +459,14 @@ part001 = startSketchAt([-1.41, 3.46])
|> line([-3.22, -7.36], %) |> line([-3.22, -7.36], %)
|> angledLine([-175, segLen(seg01)], %) |> angledLine([-175, segLen(seg01)], %)
|> extrude(1, %)` |> extrude(1, %)`
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([100, 101, true], ast), codeRef: codeRefFromRange([100, 101], ast),
}, },
programMemory: execState.memory, programMemory: execState.memory,
}) })
@ -480,13 +474,14 @@ part001 = startSketchAt([-1.41, 3.46])
}) })
it('finds nothing', async () => { it('finds nothing', async () => {
const exampleCode = `length001 = 2` const exampleCode = `length001 = 2`
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const result = hasExtrudeSketch({ const result = hasExtrudeSketch({
ast, ast,
selection: { selection: {
codeRef: codeRefFromRange([10, 11, true], ast), codeRef: codeRefFromRange([10, 11], ast),
}, },
programMemory: execState.memory, programMemory: execState.memory,
}) })
@ -503,7 +498,8 @@ describe('Testing findUsesOfTagInPipe', () => {
|> line([306.21, 198.87], %) |> line([306.21, 198.87], %)
|> angledLine([65, segLen(seg01)], %)` |> angledLine([65, segLen(seg01)], %)`
it('finds the current segment', async () => { it('finds the current segment', async () => {
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `198.85], %, $seg01` const lineOfInterest = `198.85], %, $seg01`
const characterIndex = const characterIndex =
@ -511,7 +507,6 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [ const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex, characterIndex,
characterIndex, characterIndex,
true,
]) ])
const result = findUsesOfTagInPipe(ast, pathToNode) const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(2) expect(result).toHaveLength(2)
@ -520,7 +515,8 @@ describe('Testing findUsesOfTagInPipe', () => {
}) })
}) })
it('find no tag if line has no tag', () => { it('find no tag if line has no tag', () => {
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([306.21, 198.82], %)` const lineOfInterest = `line([306.21, 198.82], %)`
const characterIndex = const characterIndex =
@ -528,7 +524,6 @@ describe('Testing findUsesOfTagInPipe', () => {
const pathToNode = getNodePathFromSourceRange(ast, [ const pathToNode = getNodePathFromSourceRange(ast, [
characterIndex, characterIndex,
characterIndex, characterIndex,
true,
]) ])
const result = findUsesOfTagInPipe(ast, pathToNode) const result = findUsesOfTagInPipe(ast, pathToNode)
expect(result).toHaveLength(0) expect(result).toHaveLength(0)
@ -569,39 +564,42 @@ sketch003 = startSketchOn(extrude001, 'END')
|> extrude(3.14, %) |> extrude(3.14, %)
` `
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => { it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([4.99, -0.46], %, $seg01)` const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
const characterIndex = const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded( const extruded = hasSketchPipeBeenExtruded(
{ {
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast), codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
}, },
ast ast
) )
expect(extruded).toBeTruthy() expect(extruded).toBeTruthy()
}) })
it('identifies sketch002 pipe as not extruded', async () => { it('identifies sketch002 pipe as not extruded', async () => {
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `line([2.45, -0.2], %)` const lineOfInterest = `line([2.45, -0.2], %)`
const characterIndex = const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded( const extruded = hasSketchPipeBeenExtruded(
{ {
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast), codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
}, },
ast ast
) )
expect(extruded).toBeFalsy() expect(extruded).toBeFalsy()
}) })
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => { it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `|> line([3.12, 1.74], %)` const lineOfInterest = `|> line([3.12, 1.74], %)`
const characterIndex = const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded( const extruded = hasSketchPipeBeenExtruded(
{ {
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast), codeRef: codeRefFromRange([characterIndex, characterIndex], ast),
}, },
ast ast
) )
@ -625,21 +623,11 @@ sketch002 = startSketchOn(extrude001, $seg01)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveSweepableSketch(ast) const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeTruthy() expect(extrudable).toBeTruthy()
}) })
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
plane001 = offsetPlane('XZ', 2)
sketch002 = startSketchOn(plane001)
|> circle({ center = [0, 0], radius = 3 }, %)
`
const ast = assertParse(exampleCode)
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
expect(extrudable).toBeTruthy()
})
it('find sketch002 NOT pipe to be extruded', async () => { it('find sketch002 NOT pipe to be extruded', async () => {
const exampleCode = `sketch001 = startSketchOn('XZ') const exampleCode = `sketch001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %) |> startProfileAt([3.29, 7.86], %)
@ -649,44 +637,13 @@ sketch002 = startSketchOn(plane001)
|> close(%) |> close(%)
extrude001 = extrude(10, sketch001) extrude001 = extrude(10, sketch001)
` `
const ast = assertParse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveSweepableSketch(ast) const extrudable = doesSceneHaveSweepableSketch(ast)
expect(extrudable).toBeFalsy() expect(extrudable).toBeFalsy()
}) })
}) })
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 = assertParse(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 = assertParse(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 = assertParse(exampleCode)
if (err(ast)) throw ast
const extrudable = doesSceneHaveExtrudedSketch(ast)
expect(extrudable).toBeFalsy()
})
})
describe('Testing traverse and pathToNode', () => { describe('Testing traverse and pathToNode', () => {
it.each([ it.each([
['basic', '2.73'], ['basic', '2.73'],
@ -709,7 +666,8 @@ myNestedVar = [
} }
] ]
` `
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) throw ast
let pathToNode: PathToNode = [] let pathToNode: PathToNode = []
traverse(ast, { traverse(ast, {
enter: (node, path) => { enter: (node, path) => {
@ -731,7 +689,6 @@ myNestedVar = [
const pathToNode2 = getNodePathFromSourceRange(ast, [ const pathToNode2 = getNodePathFromSourceRange(ast, [
literalIndex + 2, literalIndex + 2,
literalIndex + 2, literalIndex + 2,
true,
]) ])
expect(pathToNode).toEqual(pathToNode2) expect(pathToNode).toEqual(pathToNode2)
}) })

View File

@ -16,7 +16,6 @@ import {
sketchFromKclValue, sketchFromKclValue,
sketchFromKclValueOptional, sketchFromKclValueOptional,
SourceRange, SourceRange,
sourceRangeFromRust,
SyntaxType, SyntaxType,
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
@ -174,30 +173,6 @@ function moreNodePathFromSourceRange(
} }
return path return path
} }
if (_node.type === 'CallExpressionKw' && isInRange) {
const { callee, arguments: args } = _node
if (
callee.type === 'Identifier' &&
callee.start <= start &&
callee.end >= end
) {
path.push(['callee', 'CallExpressionKw'])
return path
}
if (args.length > 0) {
for (let argIndex = 0; argIndex < args.length; argIndex++) {
const arg = args[argIndex].arg
if (arg.start <= start && arg.end >= end) {
path.push(['arguments', 'CallExpressionKw'])
path.push([argIndex, 'index'])
return moreNodePathFromSourceRange(arg, sourceRange, path)
}
}
}
return path
}
if (_node.type === 'BinaryExpression' && isInRange) { if (_node.type === 'BinaryExpression' && isInRange) {
const { left, right } = _node const { left, right } = _node
if (left.start <= start && left.end >= end) { if (left.start <= start && left.end >= end) {
@ -259,26 +234,34 @@ function moreNodePathFromSourceRange(
return moreNodePathFromSourceRange(expression, sourceRange, path) return moreNodePathFromSourceRange(expression, sourceRange, path)
} }
if (_node.type === 'VariableDeclaration' && isInRange) { if (_node.type === 'VariableDeclaration' && isInRange) {
const declaration = _node.declaration const declarations = _node.declarations
if (declaration.start <= start && declaration.end >= end) { for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
path.push(['declaration', 'VariableDeclaration']) const declaration = declarations[decIndex]
const init = declaration.init if (declaration.start <= start && declaration.end >= end) {
if (init.start <= start && init.end >= end) { path.push(['declarations', 'VariableDeclaration'])
path.push(['init', '']) path.push([decIndex, 'index'])
return moreNodePathFromSourceRange(init, sourceRange, path) const init = declaration.init
if (init.start <= start && init.end >= end) {
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
} }
} }
} }
if (_node.type === 'VariableDeclaration' && isInRange) { if (_node.type === 'VariableDeclaration' && isInRange) {
const declaration = _node.declaration const declarations = _node.declarations
if (declaration.start <= start && declaration.end >= end) { for (let decIndex = 0; decIndex < declarations.length; decIndex++) {
const init = declaration.init const declaration = declarations[decIndex]
if (init.start <= start && init.end >= end) { if (declaration.start <= start && declaration.end >= end) {
path.push(['declaration', 'VariableDeclaration']) const init = declaration.init
path.push(['init', '']) if (init.start <= start && init.end >= end) {
return moreNodePathFromSourceRange(init, sourceRange, path) path.push(['declarations', 'VariableDeclaration'])
path.push([decIndex, 'index'])
path.push(['init', ''])
return moreNodePathFromSourceRange(init, sourceRange, path)
}
} }
} }
return path return path
@ -372,31 +355,24 @@ function moreNodePathFromSourceRange(
} }
if (_node.type === 'ImportStatement' && isInRange) { if (_node.type === 'ImportStatement' && isInRange) {
if (_node.selector && _node.selector.type === 'List') { const { items } = _node
path.push(['selector', 'ImportStatement']) for (let i = 0; i < items.length; i++) {
const { items } = _node.selector const item = items[i]
for (let i = 0; i < items.length; i++) { if (item.start <= start && item.end >= end) {
const item = items[i] path.push(['items', 'ImportStatement'])
if (item.start <= start && item.end >= end) { path.push([i, 'index'])
path.push(['items', 'ImportSelector']) if (item.name.start <= start && item.name.end >= end) {
path.push([i, 'index']) path.push(['name', 'ImportItem'])
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
} }
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) console.error('not implemented: ' + node.type)
@ -450,10 +426,13 @@ export function traverse(
traverse(node, option, pathToNode) traverse(node, option, pathToNode)
if (_node.type === 'VariableDeclaration') { if (_node.type === 'VariableDeclaration') {
_traverse(_node.declaration, [ _node.declarations.forEach((declaration, index) =>
...pathToNode, _traverse(declaration, [
['declaration', 'VariableDeclaration'], ...pathToNode,
]) ['declarations', 'VariableDeclaration'],
[index, 'index'],
])
)
} else if (_node.type === 'VariableDeclarator') { } else if (_node.type === 'VariableDeclarator') {
_traverse(_node.init, [...pathToNode, ['init', '']]) _traverse(_node.init, [...pathToNode, ['init', '']])
} else if (_node.type === 'PipeExpression') { } else if (_node.type === 'PipeExpression') {
@ -563,7 +542,7 @@ export function findAllPreviousVariablesPath(
const variables: PrevVariable<any>[] = [] const variables: PrevVariable<any>[] = []
bodyItems?.forEach?.((item) => { bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return 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) const varValue = programMemory?.get(varName)
if (!varValue || typeof varValue?.value !== type) return if (!varValue || typeof varValue?.value !== type) return
variables.push({ variables.push({
@ -666,7 +645,7 @@ export function isNodeSafeToReplacePath(
export function isNodeSafeToReplace( export function isNodeSafeToReplace(
ast: Node<Program>, ast: Node<Program>,
sourceRange: SourceRange sourceRange: [number, number]
): ):
| { | {
isSafe: boolean isSafe: boolean
@ -757,7 +736,7 @@ export function isLinesParallelAndConstrained(
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration') const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
if (err(_varDec)) return _varDec if (err(_varDec)) return _varDec
const varDec = _varDec.node 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) const sg = sketchFromKclValue(programMemory?.get(varName), varName)
if (err(sg)) return sg if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange( const _primarySegment = getSketchSegmentFromSourceRange(
@ -818,7 +797,7 @@ export function isLinesParallelAndConstrained(
return { return {
isParallelAndConstrained, isParallelAndConstrained,
selection: { selection: {
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast), codeRef: codeRefFromRange(prevSourceRange, ast),
artifact: artifactGraph.get(prevSegment.__geoMeta.id), artifact: artifactGraph.get(prevSegment.__geoMeta.id),
}, },
} }
@ -877,7 +856,7 @@ export function hasExtrudeSketch({
} }
const varDec = varDecMeta.node const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declaration.id.name const varName = varDec.declarations[0].id.name
const varValue = programMemory?.get(varName) const varValue = programMemory?.get(varName)
return ( return (
varValue?.type === 'Solid' || varValue?.type === 'Solid' ||
@ -954,8 +933,7 @@ export function findUsesOfTagInPipe(
return return
const tagArgValue = const tagArgValue =
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
if (tagArgValue === tag) if (tagArgValue === tag) dependentRanges.push([node.start, node.end])
dependentRanges.push([node.start, node.end, true])
}, },
}) })
return dependentRanges return dependentRanges
@ -997,9 +975,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
if ( if (
node.type === 'CallExpression' && node.type === 'CallExpression' &&
node.callee.type === 'Identifier' && node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' || (node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.callee.name === 'revolve' ||
node.callee.name === 'loft') &&
node.arguments?.[1]?.type === 'Identifier' && node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name node.arguments[1].name === varDec.id.name
) { ) {
@ -1012,7 +988,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
} }
/** File must contain at least one sketch that has not been extruded already */ /** File must contain at least one sketch that has not been extruded already */
export function doesSceneHaveSweepableSketch(ast: Node<Program>, count = 1) { export function doesSceneHaveSweepableSketch(ast: Node<Program>) {
const theMap: any = {} const theMap: any = {}
traverse(ast as any, { traverse(ast as any, {
enter(node) { enter(node) {
@ -1061,35 +1037,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 return Object.keys(theMap).length > 0
} }

View File

@ -1,4 +1,4 @@
import { assertParse, Program, recast, initPromise } from './wasm' import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs' import fs from 'node:fs'
import { err } from 'lib/trap' import { err } from 'lib/trap'
@ -394,6 +394,8 @@ describe('it recasts binary expression using brackets where needed', () => {
// helpers // helpers
function code2ast(code: string): { ast: Program } { function code2ast(code: string): { ast: Program } {
const ast = assertParse(code) const ast = parse(code)
// eslint-ignore-next-line
if (err(ast)) throw ast
return { ast } return { ast }
} }

View File

@ -11,8 +11,8 @@ Map {
], ],
], ],
"range": [ "range": [
12, 37,
31, 64,
0, 0,
], ],
}, },

View File

@ -1,4 +1,4 @@
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm' import { makeDefaultPlanes, parse, initPromise, Program } from 'lang/wasm'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { import {
OrderedCommand, OrderedCommand,
@ -148,7 +148,11 @@ beforeAll(async () => {
][] ][]
const cacheToWriteToFileTemp: Partial<CacheShape> = {} const cacheToWriteToFileTemp: Partial<CacheShape> = {}
for (const [codeKey, code] of cacheEntries) { for (const [codeKey, code] of cacheEntries) {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) {
console.error(ast)
return Promise.reject(ast)
}
await kclManager.executeAst({ ast }) await kclManager.executeAst({ ast })
cacheToWriteToFileTemp[codeKey] = { cacheToWriteToFileTemp[codeKey] = {
@ -399,7 +403,11 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
}) })
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } { function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } {
const ast = assertParse(codeKey) const ast = parse(codeKey)
if (err(ast)) {
console.error(ast)
throw ast
}
const file = fs.readFileSync(fullPath, 'utf-8') const file = fs.readFileSync(fullPath, 'utf-8')
const parsed: CacheShape = JSON.parse(file) const parsed: CacheShape = JSON.parse(file)
// these either already exist from the last run, or were created in // these either already exist from the last run, or were created in

View File

@ -871,15 +871,3 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
pathToNode: getNodePathFromSourceRange(ast, range), pathToNode: getNodePathFromSourceRange(ast, range),
} }
} }
export function isSolid2D(artifact: Artifact): artifact is solid2D {
return (artifact as solid2D).pathId !== undefined
}
export function isSegment(artifact: Artifact): artifact is SegmentArtifact {
return (artifact as SegmentArtifact).pathId !== undefined
}
export function isSweep(artifact: Artifact): artifact is SweepArtifact {
return (artifact as SweepArtifact).pathId !== undefined
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 KiB

After

Width:  |  Height:  |  Size: 613 KiB

View File

@ -1,4 +1,4 @@
import { defaultSourceRange, SourceRange } from 'lang/wasm' import { SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env' import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave' import { exportSave } from 'lib/exportSave'
@ -1879,6 +1879,17 @@ export class EngineCommandManager extends EventTarget {
} }
return JSON.stringify(this.defaultPlanes) return JSON.stringify(this.defaultPlanes)
} }
endSession() {
const deleteCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'scene_clear_all',
},
}
this.clearDefaultPlanes()
this.engineConnection?.send(deleteCmd)
}
addCommandLog(message: CommandLog) { addCommandLog(message: CommandLog) {
if (this.commandLogs.length > 500) { if (this.commandLogs.length > 500) {
this.commandLogs.shift() this.commandLogs.shift()
@ -2003,7 +2014,7 @@ export class EngineCommandManager extends EventTarget {
{ {
command, command,
idToRangeMap: {}, idToRangeMap: {},
range: defaultSourceRange(), range: [0, 0],
}, },
true // isSceneCommand true // isSceneCommand
) )

View File

@ -8,7 +8,7 @@ import {
getConstraintInfo, getConstraintInfo,
} from './sketch' } from './sketch'
import { import {
assertParse, parse,
recast, recast,
initPromise, initPromise,
SourceRange, SourceRange,
@ -115,7 +115,8 @@ describe('testing changeSketchArguments', () => {
` `
const code = genCode(lineToChange) const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange) const expectedCode = genCode(lineAfterChange)
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
@ -124,7 +125,7 @@ describe('testing changeSketchArguments', () => {
execState.memory, execState.memory,
{ {
type: 'sourceRange', type: 'sourceRange',
sourceRange: [sourceStart, sourceStart + lineToChange.length, true], sourceRange: [sourceStart, sourceStart + lineToChange.length],
}, },
{ {
type: 'straight-segment', type: 'straight-segment',
@ -147,7 +148,8 @@ mySketch001 = startSketchOn('XY')
// |> rx(45, %) // |> rx(45, %)
|> lineTo([-1.59, -1.54], %) |> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)` |> lineTo([0.46, -5.82], %)`
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return ast
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange) const sourceStart = code.indexOf(lineToChange)
@ -164,7 +166,8 @@ mySketch001 = startSketchOn('XY')
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
], ],
}) })
@ -188,7 +191,8 @@ mySketch001 = startSketchOn('XY')
pathToNode: [ pathToNode: [
['body', ''], ['body', ''],
[0, 'index'], [0, 'index'],
['declaration', 'VariableDeclaration'], ['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'], ['init', 'VariableDeclarator'],
], ],
}) })
@ -216,13 +220,12 @@ describe('testing addTagForSketchOnFace', () => {
|> lineTo([0.46, -5.82], %) |> lineTo([0.46, -5.82], %)
` `
const code = genCode(originalLine) const code = genCode(originalLine)
const ast = assertParse(code) const ast = parse(code)
await enginelessExecutor(ast) await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine) const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number, boolean] = [ const sourceRange: [number, number] = [
sourceStart, sourceStart,
sourceStart + originalLine.length, sourceStart + originalLine.length,
true,
] ]
if (err(ast)) return ast if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -288,14 +291,13 @@ extrude001 = extrude(100, sketch001)
${insertCode} ${insertCode}
` `
const code = genCode(originalChamfer) const code = genCode(originalChamfer)
const ast = assertParse(code) const ast = parse(code)
await enginelessExecutor(ast) await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalChamfer) const sourceStart = code.indexOf(originalChamfer)
const extraChars = originalChamfer.indexOf('chamfer') const extraChars = originalChamfer.indexOf('chamfer')
const sourceRange: [number, number, boolean] = [ const sourceRange: [number, number] = [
sourceStart + extraChars, sourceStart + extraChars,
sourceStart + originalChamfer.length - extraChars, sourceStart + originalChamfer.length - extraChars,
true,
] ]
if (err(ast)) throw ast if (err(ast)) throw ast
@ -333,7 +335,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %) |> lineTo([6.14, 3.14], %)
|> xLineTo(8, %) |> xLineTo(8, %)
|> yLineTo(5, %) |> yLineTo(5, %)
|> yLine(3.14, %, $a) |> yLine(3.14, %, 'a')
|> xLine(3.14, %) |> xLine(3.14, %)
|> angledLineOfXLength({ |> angledLineOfXLength({
angle = 3.14, angle = 3.14,
@ -353,11 +355,11 @@ describe('testing getConstraintInfo', () => {
}, %) }, %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = 3.14, angle = 3.14,
intersectTag = a, intersectTag = 'a',
offset = 0 offset = 0
}, %) }, %)
|> tangentialArcTo([3.14, 13.14], %)` |> tangentialArcTo([3.14, 13.14], %)`
const ast = assertParse(code) const ast = parse(code)
test.each([ test.each([
[ [
'line', 'line',
@ -366,7 +368,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: false, isConstrained: false,
value: '3', value: '3',
sourceRange: [78, 79, true], sourceRange: [78, 79],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'line', stdLibFnName: 'line',
@ -375,7 +377,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: false, isConstrained: false,
value: '4', value: '4',
sourceRange: [81, 82, true], sourceRange: [81, 82],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'line', stdLibFnName: 'line',
@ -389,7 +391,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [118, 122, true], sourceRange: [117, 121],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -398,7 +400,7 @@ describe('testing getConstraintInfo', () => {
type: 'length', type: 'length',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [137, 141, true], sourceRange: [135, 139],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -412,7 +414,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: false, isConstrained: false,
value: '6.14', value: '6.14',
sourceRange: [164, 168, true], sourceRange: [162, 166],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'lineTo', stdLibFnName: 'lineTo',
@ -421,7 +423,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [170, 174, true], sourceRange: [168, 172],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'lineTo', stdLibFnName: 'lineTo',
@ -435,7 +437,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal', type: 'horizontal',
isConstrained: true, isConstrained: true,
value: 'xLineTo', value: 'xLineTo',
sourceRange: [185, 192, true], sourceRange: [183, 190],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo', stdLibFnName: 'xLineTo',
@ -444,7 +446,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: false, isConstrained: false,
value: '8', value: '8',
sourceRange: [193, 194, true], sourceRange: [191, 192],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo', stdLibFnName: 'xLineTo',
@ -458,7 +460,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical', type: 'vertical',
isConstrained: true, isConstrained: true,
value: 'yLineTo', value: 'yLineTo',
sourceRange: [204, 211, true], sourceRange: [202, 209],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo', stdLibFnName: 'yLineTo',
@ -467,7 +469,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: false, isConstrained: false,
value: '5', value: '5',
sourceRange: [212, 213, true], sourceRange: [210, 211],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo', stdLibFnName: 'yLineTo',
@ -481,7 +483,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical', type: 'vertical',
isConstrained: true, isConstrained: true,
value: 'yLine', value: 'yLine',
sourceRange: [223, 228, true], sourceRange: [221, 226],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLine', stdLibFnName: 'yLine',
@ -490,7 +492,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [229, 233, true], sourceRange: [227, 231],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLine', stdLibFnName: 'yLine',
@ -504,7 +506,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal', type: 'horizontal',
isConstrained: true, isConstrained: true,
value: 'xLine', value: 'xLine',
sourceRange: [247, 252, true], sourceRange: [246, 251],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLine', stdLibFnName: 'xLine',
@ -513,7 +515,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [253, 257, true], sourceRange: [252, 256],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLine', stdLibFnName: 'xLine',
@ -527,7 +529,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [301, 305, true], sourceRange: [299, 303],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -536,7 +538,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [320, 324, true], sourceRange: [317, 321],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -550,7 +552,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '30', value: '30',
sourceRange: [373, 375, true], sourceRange: [369, 371],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -559,7 +561,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: false, isConstrained: false,
value: '3', value: '3',
sourceRange: [390, 391, true], sourceRange: [385, 386],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -573,7 +575,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '12.14', value: '12.14',
sourceRange: [434, 439, true], sourceRange: [428, 433],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -582,7 +584,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: false, isConstrained: false,
value: '12', value: '12',
sourceRange: [450, 452, true], sourceRange: [443, 445],
argPosition: { type: 'objectProperty', key: 'to' }, argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -596,7 +598,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '30', value: '30',
sourceRange: [495, 497, true], sourceRange: [487, 489],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -605,7 +607,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: false, isConstrained: false,
value: '10.14', value: '10.14',
sourceRange: [508, 513, true], sourceRange: [499, 504],
argPosition: { type: 'objectProperty', key: 'to' }, argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -619,7 +621,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [567, 571, true], sourceRange: [557, 561],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
@ -628,7 +630,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset', type: 'intersectionOffset',
isConstrained: false, isConstrained: false,
value: '0', value: '0',
sourceRange: [608, 609, true], sourceRange: [598, 599],
argPosition: { type: 'objectProperty', key: 'offset' }, argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
@ -636,8 +638,8 @@ describe('testing getConstraintInfo', () => {
{ {
type: 'intersectionTag', type: 'intersectionTag',
isConstrained: false, isConstrained: false,
value: 'a', value: "'a'",
sourceRange: [592, 593, true], sourceRange: [581, 584],
argPosition: { argPosition: {
key: 'intersectTag', key: 'intersectTag',
type: 'objectProperty', type: 'objectProperty',
@ -654,7 +656,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious', type: 'tangentialWithPrevious',
isConstrained: true, isConstrained: true,
value: 'tangentialArcTo', value: 'tangentialArcTo',
sourceRange: [623, 638, true], sourceRange: [613, 628],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -663,7 +665,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [640, 644, true], sourceRange: [630, 634],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -672,7 +674,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: false, isConstrained: false,
value: '13.14', value: '13.14',
sourceRange: [646, 651, true], sourceRange: [636, 641],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -683,7 +685,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [ const sourceRange: SourceRange = [
code.indexOf(functionName), code.indexOf(functionName),
code.indexOf(functionName) + functionName.length, code.indexOf(functionName) + functionName.length,
true,
] ]
if (err(ast)) return ast if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -705,7 +706,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14, 3.14], %) |> lineTo([6.14, 3.14], %)
|> xLineTo(8, %) |> xLineTo(8, %)
|> yLineTo(5, %) |> yLineTo(5, %)
|> yLine(3.14, %, $a) |> yLine(3.14, %, 'a')
|> xLine(3.14, %) |> xLine(3.14, %)
|> angledLineOfXLength([3.14, 3.14], %) |> angledLineOfXLength([3.14, 3.14], %)
|> angledLineOfYLength([30, 3], %) |> angledLineOfYLength([30, 3], %)
@ -713,11 +714,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY([30, 10], %) |> angledLineToY([30, 10], %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = 3.14, angle = 3.14,
intersectTag = a, intersectTag = 'a',
offset = 0 offset = 0
}, %) }, %)
|> tangentialArcTo([3.14, 13.14], %)` |> tangentialArcTo([3.14, 13.14], %)`
const ast = assertParse(code) const ast = parse(code)
test.each([ test.each([
[ [
`angledLine(`, `angledLine(`,
@ -726,7 +727,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [112, 116, true], sourceRange: [112, 116],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -735,7 +736,7 @@ describe('testing getConstraintInfo', () => {
type: 'length', type: 'length',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [118, 122, true], sourceRange: [118, 122],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -749,7 +750,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [277, 281, true], sourceRange: [278, 282],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -758,7 +759,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: false, isConstrained: false,
value: '3.14', value: '3.14',
sourceRange: [283, 287, true], sourceRange: [284, 288],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -772,7 +773,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '30', value: '30',
sourceRange: [321, 323, true], sourceRange: [322, 324],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -781,7 +782,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: false, isConstrained: false,
value: '3', value: '3',
sourceRange: [325, 326, true], sourceRange: [326, 327],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -795,7 +796,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '12', value: '12',
sourceRange: [354, 356, true], sourceRange: [355, 357],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -804,7 +805,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: false, isConstrained: false,
value: '12', value: '12',
sourceRange: [358, 360, true], sourceRange: [359, 361],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -818,7 +819,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: false, isConstrained: false,
value: '30', value: '30',
sourceRange: [388, 390, true], sourceRange: [389, 391],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -827,7 +828,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: false, isConstrained: false,
value: '10', value: '10',
sourceRange: [392, 394, true], sourceRange: [393, 395],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -838,7 +839,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [ const sourceRange: SourceRange = [
code.indexOf(functionName), code.indexOf(functionName),
code.indexOf(functionName) + functionName.length, code.indexOf(functionName) + functionName.length,
true,
] ]
if (err(ast)) return ast if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
@ -860,7 +860,7 @@ describe('testing getConstraintInfo', () => {
|> lineTo([6.14 + 0, 3.14 + 0], %) |> lineTo([6.14 + 0, 3.14 + 0], %)
|> xLineTo(8 + 0, %) |> xLineTo(8 + 0, %)
|> yLineTo(5 + 0, %) |> yLineTo(5 + 0, %)
|> yLine(3.14 + 0, %, $a) |> yLine(3.14 + 0, %, 'a')
|> xLine(3.14 + 0, %) |> xLine(3.14 + 0, %)
|> angledLineOfXLength({ angle = 3.14 + 0, length = 3.14 + 0 }, %) |> angledLineOfXLength({ angle = 3.14 + 0, length = 3.14 + 0 }, %)
|> angledLineOfYLength({ angle = 30 + 0, length = 3 + 0 }, %) |> angledLineOfYLength({ angle = 30 + 0, length = 3 + 0 }, %)
@ -868,11 +868,11 @@ describe('testing getConstraintInfo', () => {
|> angledLineToY({ angle = 30 + 0, to = 10.14 + 0 }, %) |> angledLineToY({ angle = 30 + 0, to = 10.14 + 0 }, %)
|> angledLineThatIntersects({ |> angledLineThatIntersects({
angle = 3.14 + 0, angle = 3.14 + 0,
intersectTag = a, intersectTag = 'a',
offset = 0 + 0 offset = 0 + 0
}, %) }, %)
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)` |> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
const ast = assertParse(code) const ast = parse(code)
test.each([ test.each([
[ [
'line', 'line',
@ -881,7 +881,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: true, isConstrained: true,
value: '3 + 0', value: '3 + 0',
sourceRange: [83, 88, true], sourceRange: [83, 88],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'line', stdLibFnName: 'line',
@ -890,7 +890,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: true, isConstrained: true,
value: '4 + 0', value: '4 + 0',
sourceRange: [90, 95, true], sourceRange: [90, 95],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'line', stdLibFnName: 'line',
@ -904,7 +904,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [129, 137, true], sourceRange: [128, 136],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -913,7 +913,7 @@ describe('testing getConstraintInfo', () => {
type: 'length', type: 'length',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [148, 156, true], sourceRange: [146, 154],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLine', stdLibFnName: 'angledLine',
@ -927,7 +927,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: true, isConstrained: true,
value: '6.14 + 0', value: '6.14 + 0',
sourceRange: [178, 186, true], sourceRange: [176, 184],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'lineTo', stdLibFnName: 'lineTo',
@ -936,7 +936,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [188, 196, true], sourceRange: [186, 194],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'lineTo', stdLibFnName: 'lineTo',
@ -950,7 +950,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal', type: 'horizontal',
isConstrained: true, isConstrained: true,
value: 'xLineTo', value: 'xLineTo',
sourceRange: [209, 216, true], sourceRange: [207, 214],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo', stdLibFnName: 'xLineTo',
@ -959,7 +959,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: true, isConstrained: true,
value: '8 + 0', value: '8 + 0',
sourceRange: [217, 222, true], sourceRange: [215, 220],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLineTo', stdLibFnName: 'xLineTo',
@ -973,7 +973,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical', type: 'vertical',
isConstrained: true, isConstrained: true,
value: 'yLineTo', value: 'yLineTo',
sourceRange: [234, 241, true], sourceRange: [232, 239],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo', stdLibFnName: 'yLineTo',
@ -982,7 +982,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: true, isConstrained: true,
value: '5 + 0', value: '5 + 0',
sourceRange: [242, 247, true], sourceRange: [240, 245],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLineTo', stdLibFnName: 'yLineTo',
@ -996,7 +996,7 @@ describe('testing getConstraintInfo', () => {
type: 'vertical', type: 'vertical',
isConstrained: true, isConstrained: true,
value: 'yLine', value: 'yLine',
sourceRange: [259, 264, true], sourceRange: [257, 262],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLine', stdLibFnName: 'yLine',
@ -1005,7 +1005,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [265, 273, true], sourceRange: [263, 271],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'yLine', stdLibFnName: 'yLine',
@ -1019,7 +1019,7 @@ describe('testing getConstraintInfo', () => {
type: 'horizontal', type: 'horizontal',
isConstrained: true, isConstrained: true,
value: 'xLine', value: 'xLine',
sourceRange: [289, 294, true], sourceRange: [288, 293],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLine', stdLibFnName: 'xLine',
@ -1028,7 +1028,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [295, 303, true], sourceRange: [294, 302],
argPosition: { type: 'singleValue' }, argPosition: { type: 'singleValue' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'xLine', stdLibFnName: 'xLine',
@ -1042,7 +1042,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [345, 353, true], sourceRange: [343, 351],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -1051,7 +1051,7 @@ describe('testing getConstraintInfo', () => {
type: 'xRelative', type: 'xRelative',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [364, 372, true], sourceRange: [361, 369],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfXLength', stdLibFnName: 'angledLineOfXLength',
@ -1065,7 +1065,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '30 + 0', value: '30 + 0',
sourceRange: [416, 422, true], sourceRange: [412, 418],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -1074,7 +1074,7 @@ describe('testing getConstraintInfo', () => {
type: 'yRelative', type: 'yRelative',
isConstrained: true, isConstrained: true,
value: '3 + 0', value: '3 + 0',
sourceRange: [433, 438, true], sourceRange: [428, 433],
argPosition: { type: 'objectProperty', key: 'length' }, argPosition: { type: 'objectProperty', key: 'length' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineOfYLength', stdLibFnName: 'angledLineOfYLength',
@ -1088,7 +1088,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '12.14 + 0', value: '12.14 + 0',
sourceRange: [476, 485, true], sourceRange: [470, 479],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -1097,7 +1097,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: true, isConstrained: true,
value: '12 + 0', value: '12 + 0',
sourceRange: [492, 498, true], sourceRange: [485, 491],
argPosition: { type: 'objectProperty', key: 'to' }, argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToX', stdLibFnName: 'angledLineToX',
@ -1111,7 +1111,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '30 + 0', value: '30 + 0',
sourceRange: [536, 542, true], sourceRange: [528, 534],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -1120,7 +1120,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: true, isConstrained: true,
value: '10.14 + 0', value: '10.14 + 0',
sourceRange: [549, 558, true], sourceRange: [540, 549],
argPosition: { type: 'objectProperty', key: 'to' }, argPosition: { type: 'objectProperty', key: 'to' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineToY', stdLibFnName: 'angledLineToY',
@ -1134,7 +1134,7 @@ describe('testing getConstraintInfo', () => {
type: 'angle', type: 'angle',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [616, 624, true], sourceRange: [606, 614],
argPosition: { type: 'objectProperty', key: 'angle' }, argPosition: { type: 'objectProperty', key: 'angle' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
@ -1143,7 +1143,7 @@ describe('testing getConstraintInfo', () => {
type: 'intersectionOffset', type: 'intersectionOffset',
isConstrained: true, isConstrained: true,
value: '0 + 0', value: '0 + 0',
sourceRange: [671, 676, true], sourceRange: [661, 666],
argPosition: { type: 'objectProperty', key: 'offset' }, argPosition: { type: 'objectProperty', key: 'offset' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
@ -1151,8 +1151,8 @@ describe('testing getConstraintInfo', () => {
{ {
type: 'intersectionTag', type: 'intersectionTag',
isConstrained: false, isConstrained: false,
value: 'a', value: "'a'",
sourceRange: [650, 651, true], sourceRange: [639, 642],
argPosition: { key: 'intersectTag', type: 'objectProperty' }, argPosition: { key: 'intersectTag', type: 'objectProperty' },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'angledLineThatIntersects', stdLibFnName: 'angledLineThatIntersects',
@ -1166,7 +1166,7 @@ describe('testing getConstraintInfo', () => {
type: 'tangentialWithPrevious', type: 'tangentialWithPrevious',
isConstrained: true, isConstrained: true,
value: 'tangentialArcTo', value: 'tangentialArcTo',
sourceRange: [697, 712, true], sourceRange: [687, 702],
argPosition: undefined, argPosition: undefined,
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -1175,7 +1175,7 @@ describe('testing getConstraintInfo', () => {
type: 'xAbsolute', type: 'xAbsolute',
isConstrained: true, isConstrained: true,
value: '3.14 + 0', value: '3.14 + 0',
sourceRange: [714, 722, true], sourceRange: [704, 712],
argPosition: { type: 'arrayItem', index: 0 }, argPosition: { type: 'arrayItem', index: 0 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -1184,7 +1184,7 @@ describe('testing getConstraintInfo', () => {
type: 'yAbsolute', type: 'yAbsolute',
isConstrained: true, isConstrained: true,
value: '13.14 + 0', value: '13.14 + 0',
sourceRange: [724, 733, true], sourceRange: [714, 723],
argPosition: { type: 'arrayItem', index: 1 }, argPosition: { type: 'arrayItem', index: 1 },
pathToNode: expect.any(Array), pathToNode: expect.any(Array),
stdLibFnName: 'tangentialArcTo', stdLibFnName: 'tangentialArcTo',
@ -1195,7 +1195,6 @@ describe('testing getConstraintInfo', () => {
const sourceRange: SourceRange = [ const sourceRange: SourceRange = [
code.indexOf(functionName), code.indexOf(functionName),
code.indexOf(functionName) + functionName.length, code.indexOf(functionName) + functionName.length,
true,
] ]
if (err(ast)) return ast if (err(ast)) return ast
const pathToNode = getNodePathFromSourceRange(ast, sourceRange) const pathToNode = getNodePathFromSourceRange(ast, sourceRange)

View File

@ -222,7 +222,7 @@ const commonConstraintInfoHelper = (
code.slice(input1.start, input1.end), code.slice(input1.start, input1.end),
stdLibFnName, stdLibFnName,
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput, isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
[input1.start, input1.end, true], [input1.start, input1.end],
pathToFirstArg pathToFirstArg
) )
) )
@ -234,7 +234,7 @@ const commonConstraintInfoHelper = (
code.slice(input2.start, input2.end), code.slice(input2.start, input2.end),
stdLibFnName, stdLibFnName,
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput, isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
[input2.start, input2.end, true], [input2.start, input2.end],
pathToSecondArg pathToSecondArg
) )
) )
@ -266,7 +266,7 @@ const horzVertConstraintInfoHelper = (
callee.name, callee.name,
stdLibFnName, stdLibFnName,
undefined, undefined,
[callee.start, callee.end, true], [callee.start, callee.end],
pathToCallee pathToCallee
), ),
constrainInfo( constrainInfo(
@ -275,7 +275,7 @@ const horzVertConstraintInfoHelper = (
code.slice(firstArg.start, firstArg.end), code.slice(firstArg.start, firstArg.end),
stdLibFnName, stdLibFnName,
abbreviatedInput, abbreviatedInput,
[firstArg.start, firstArg.end, true], [firstArg.start, firstArg.end],
pathToFirstArg pathToFirstArg
), ),
] ]
@ -905,7 +905,7 @@ export const tangentialArcTo: SketchLineHelper = {
callee.name, callee.name,
'tangentialArcTo', 'tangentialArcTo',
undefined, undefined,
[callee.start, callee.end, true], [callee.start, callee.end],
pathToCallee pathToCallee
), ),
constrainInfo( constrainInfo(
@ -914,7 +914,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[0].start, firstArg.elements[0].end), code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
'tangentialArcTo', 'tangentialArcTo',
0, 0,
[firstArg.elements[0].start, firstArg.elements[0].end, true], [firstArg.elements[0].start, firstArg.elements[0].end],
pathToFirstArg pathToFirstArg
), ),
constrainInfo( constrainInfo(
@ -923,7 +923,7 @@ export const tangentialArcTo: SketchLineHelper = {
code.slice(firstArg.elements[1].start, firstArg.elements[1].end), code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
'tangentialArcTo', 'tangentialArcTo',
1, 1,
[firstArg.elements[1].start, firstArg.elements[1].end, true], [firstArg.elements[1].start, firstArg.elements[1].end],
pathToSecondArg pathToSecondArg
), ),
] ]
@ -1052,7 +1052,7 @@ export const circle: SketchLineHelper = {
code.slice(radiusDetails.expr.start, radiusDetails.expr.end), code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
'circle', 'circle',
'radius', 'radius',
[radiusDetails.expr.start, radiusDetails.expr.end, true], [radiusDetails.expr.start, radiusDetails.expr.end],
pathToRadiusLiteral pathToRadiusLiteral
), ),
{ {
@ -1064,7 +1064,6 @@ export const circle: SketchLineHelper = {
sourceRange: [ sourceRange: [
centerDetails.expr.elements[0].start, centerDetails.expr.elements[0].start,
centerDetails.expr.elements[0].end, centerDetails.expr.elements[0].end,
true,
], ],
pathToNode: pathToXArg, pathToNode: pathToXArg,
value: code.slice( value: code.slice(
@ -1086,7 +1085,6 @@ export const circle: SketchLineHelper = {
sourceRange: [ sourceRange: [
centerDetails.expr.elements[1].start, centerDetails.expr.elements[1].start,
centerDetails.expr.elements[1].end, centerDetails.expr.elements[1].end,
true,
], ],
pathToNode: pathToYArg, pathToNode: pathToYArg,
value: code.slice( value: code.slice(
@ -1701,7 +1699,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2 if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2 const { node: varDec } = nodeMeta2
const varName = varDec.declaration.id.name const varName = varDec.declarations[0].id.name
const sketch = sketchFromKclValue( const sketch = sketchFromKclValue(
previousProgramMemory.get(varName), previousProgramMemory.get(varName),
varName varName
@ -1763,7 +1761,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(angle.start, angle.end), code.slice(angle.start, angle.end),
'angledLineThatIntersects', 'angledLineThatIntersects',
'angle', 'angle',
[angle.start, angle.end, true], [angle.start, angle.end],
pathToAngleProp pathToAngleProp
) )
) )
@ -1782,7 +1780,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(offset.start, offset.end), code.slice(offset.start, offset.end),
'angledLineThatIntersects', 'angledLineThatIntersects',
'offset', 'offset',
[offset.start, offset.end, true], [offset.start, offset.end],
pathToOffsetProp pathToOffsetProp
) )
) )
@ -1801,7 +1799,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
code.slice(tag.start, tag.end), code.slice(tag.start, tag.end),
'angledLineThatIntersects', 'angledLineThatIntersects',
'intersectTag', 'intersectTag',
[tag.start, tag.end, true], [tag.start, tag.end],
pathToTagProp pathToTagProp
) )
returnVal.push(info) returnVal.push(info)

View File

@ -1,5 +1,5 @@
import { import {
assertParse, parse,
Sketch, Sketch,
recast, recast,
initPromise, initPromise,
@ -31,11 +31,12 @@ async function testingSwapSketchFnCall({
constraintType: ConstraintType constraintType: ConstraintType
}): Promise<{ }): Promise<{
newCode: string newCode: string
originalRange: [number, number, boolean] originalRange: [number, number]
}> { }> {
const startIndex = inputCode.indexOf(callToSwap) const startIndex = inputCode.indexOf(callToSwap)
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true] const range: SourceRange = [startIndex, startIndex + callToSwap.length]
const ast = assertParse(inputCode) const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const selections = { const selections = {
@ -369,13 +370,13 @@ part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment |> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)` |> xLine(3.54, %)`
it('normal case works', async () => { it('normal case works', async () => {
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7 const index = code.indexOf('// normal-segment') - 7
const sg = sketchFromKclValue( const sg = sketchFromKclValue(
execState.memory.get('part001'), execState.memory.get('part001'),
'part001' 'part001'
) as Sketch ) as Sketch
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true]) const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
if (err(_segment)) throw _segment if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment const { __geoMeta, ...segment } = _segment.segment
expect(segment).toEqual({ expect(segment).toEqual({
@ -386,11 +387,11 @@ part001 = startSketchOn('XY')
}) })
}) })
it('verify it works when the segment is in the `start` property', async () => { it('verify it works when the segment is in the `start` property', async () => {
const execState = await enginelessExecutor(assertParse(code)) const execState = await enginelessExecutor(parse(code))
const index = code.indexOf('// segment-in-start') - 7 const index = code.indexOf('// segment-in-start') - 7
const _segment = getSketchSegmentFromSourceRange( const _segment = getSketchSegmentFromSourceRange(
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch, sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
[index, index, true] [index, index]
) )
if (err(_segment)) throw _segment if (err(_segment)) throw _segment
const { __geoMeta, ...segment } = _segment.segment const { __geoMeta, ...segment } = _segment.segment

View File

@ -31,7 +31,7 @@ export function getSketchSegmentFromPathToNode(
const node = nodeMeta.node const node = nodeMeta.node
if (!node || typeof node.start !== 'number' || !node.end) if (!node || typeof node.start !== 'number' || !node.end)
return new Error('no node found') return new Error('no node found')
const sourceRange: SourceRange = [node.start, node.end, true] const sourceRange: SourceRange = [node.start, node.end]
return getSketchSegmentFromSourceRange(sketch, sourceRange) return getSketchSegmentFromSourceRange(sketch, sourceRange)
} }
export function getSketchSegmentFromSourceRange( export function getSketchSegmentFromSourceRange(
@ -111,10 +111,12 @@ export function isSketchVariablesLinked(
let nextVarDec: VariableDeclarator | undefined let nextVarDec: VariableDeclarator | undefined
for (const node of ast.body) { for (const node of ast.body) {
if (node.type !== 'VariableDeclaration') continue if (node.type !== 'VariableDeclaration') continue
if (node.declaration.id.name === secondArg.name) { const found = node.declarations.find(
nextVarDec = node.declaration ({ id }) => id?.name === secondArg.name
break )
} if (!found) continue
nextVarDec = found
break
} }
if (!nextVarDec) return false if (!nextVarDec) return false
return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast) return isSketchVariablesLinked(nextVarDec, primaryVarDec, ast)

View File

@ -1,4 +1,4 @@
import { assertParse, Expr, recast, initPromise, Program } from '../wasm' import { parse, Expr, recast, initPromise, Program } from '../wasm'
import { import {
getConstraintType, getConstraintType,
getTransformInfos, getTransformInfos,
@ -66,7 +66,8 @@ describe('testing getConstraintType', () => {
function getConstraintTypeFromSourceHelper( function getConstraintTypeFromSourceHelper(
code: string code: string
): ReturnType<typeof getConstraintType> | Error { ): ReturnType<typeof getConstraintType> | Error {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return ast
const args = (ast.body[0] as any).expression.arguments[0].elements as [ const args = (ast.body[0] as any).expression.arguments[0].elements as [
Expr, Expr,
@ -78,7 +79,8 @@ function getConstraintTypeFromSourceHelper(
function getConstraintTypeFromSourceHelper2( function getConstraintTypeFromSourceHelper2(
code: string code: string
): ReturnType<typeof getConstraintType> | Error { ): ReturnType<typeof getConstraintType> | Error {
const ast = assertParse(code) const ast = parse(code)
if (err(ast)) return ast
const arg = (ast.body[0] as any).expression.arguments[0] as Expr const arg = (ast.body[0] as any).expression.arguments[0] as Expr
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
@ -125,7 +127,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
) )
} }
const start = codeBeforeLine + line.indexOf('|> ' + 5) const start = codeBeforeLine + line.indexOf('|> ' + 5)
const range: [number, number, boolean] = [start, start, true] const range: [number, number] = [start, start]
return { return {
codeRef: codeRefFromRange(range, ast), codeRef: codeRefFromRange(range, ast),
} }
@ -135,7 +137,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
inputCode: string, inputCode: string,
selectionRanges: Selections['graphSelections'] selectionRanges: Selections['graphSelections']
) { ) {
const ast = assertParse(inputCode) const ast = parse(inputCode)
if (err(ast)) return Promise.reject(ast)
const execState = await enginelessExecutor(ast) const execState = await enginelessExecutor(ast)
const transformInfos = getTransformInfos( const transformInfos = getTransformInfos(
makeSelections(selectionRanges.slice(1)), makeSelections(selectionRanges.slice(1)),
@ -158,7 +161,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
} }
it(`Should reorder when user selects first-to-last`, async () => { it(`Should reorder when user selects first-to-last`, async () => {
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [ const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 3, ast), selectLine(inputScript, 3, ast),
selectLine(inputScript, 4, ast), selectLine(inputScript, 4, ast),
@ -169,7 +173,8 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
}) })
it(`Should reorder when user selects last-to-first`, async () => { it(`Should reorder when user selects last-to-first`, async () => {
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = [ const selectionRanges: Selections['graphSelections'] = [
selectLine(inputScript, 4, ast), selectLine(inputScript, 4, ast),
selectLine(inputScript, 3, ast), selectLine(inputScript, 3, ast),
@ -288,7 +293,8 @@ part001 = startSketchOn('XY')
|> yLine(segLen(seg01), %) // ln-yLineTo-free should convert to yLine |> yLine(segLen(seg01), %) // ln-yLineTo-free should convert to yLine
` `
it('should transform the ast', async () => { it('should transform the ast', async () => {
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n') .split('\n')
@ -297,7 +303,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1] const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7 const start = inputScript.indexOf('//' + comment) - 7
return { return {
codeRef: codeRefFromRange([start, start, true], ast), codeRef: codeRefFromRange([start, start], ast),
} }
}) })
@ -377,7 +383,8 @@ part001 = startSketchOn('XY')
|> xLineTo(myVar3, %) // select for horizontal constraint 10 |> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10 |> angledLineToY([301, myVar], %) // select for vertical constraint 10
` `
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n') .split('\n')
@ -386,7 +393,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1] const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7 const start = inputScript.indexOf('//' + comment) - 7
return { return {
codeRef: codeRefFromRange([start, start, true], ast), codeRef: codeRefFromRange([start, start], ast),
} }
}) })
@ -437,7 +444,8 @@ part001 = startSketchOn('XY')
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10 |> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10 |> yLineTo(myVar, %) // select for vertical constraint 10
` `
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n') .split('\n')
@ -446,7 +454,7 @@ part001 = startSketchOn('XY')
const comment = ln.split('//')[1] const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7 const start = inputScript.indexOf('//' + comment) - 7
return { return {
codeRef: codeRefFromRange([start, start, true], ast), codeRef: codeRefFromRange([start, start], ast),
} }
}) })
@ -530,7 +538,8 @@ async function helperThing(
linesOfInterest: string[], linesOfInterest: string[],
constraint: ConstraintType constraint: ConstraintType
): Promise<string> { ): Promise<string> {
const ast = assertParse(inputScript) const ast = parse(inputScript)
if (err(ast)) return Promise.reject(ast)
const selectionRanges: Selections['graphSelections'] = inputScript const selectionRanges: Selections['graphSelections'] = inputScript
.split('\n') .split('\n')
@ -541,7 +550,7 @@ async function helperThing(
const comment = ln.split('//')[1] const comment = ln.split('//')[1]
const start = inputScript.indexOf('//' + comment) - 7 const start = inputScript.indexOf('//' + comment) - 7
return { return {
codeRef: codeRefFromRange([start, start, true], ast), codeRef: codeRefFromRange([start, start], ast),
} }
}) })
@ -597,7 +606,7 @@ part001 = startSketchOn('XY')
|> line([-1.49, 1.06], %) // free |> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full |> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full` |> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = assertParse(code) const ast = parse(code)
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free'] const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
constraintLevels.forEach((constraintLevel) => { constraintLevels.forEach((constraintLevel) => {
const recursivelySearchCommentsAndCheckConstraintLevel = ( const recursivelySearchCommentsAndCheckConstraintLevel = (
@ -610,7 +619,7 @@ part001 = startSketchOn('XY')
} }
const offsetIndex = index - 7 const offsetIndex = index - 7
const expectedConstraintLevel = getConstraintLevelFromSourceRange( const expectedConstraintLevel = getConstraintLevelFromSourceRange(
[offsetIndex, offsetIndex, true], [offsetIndex, offsetIndex],
ast ast
) )
if (err(expectedConstraintLevel)) { if (err(expectedConstraintLevel)) {

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise } from '../wasm' import { parse, initPromise } from '../wasm'
import { enginelessExecutor } from '../../lib/testHelpers' import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => { beforeAll(async () => {
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset}, offset: ${offset},
}, %, $yo2) }, %, $yo2)
intersect = segEndX(yo2)` intersect = segEndX(yo2)`
const execState = await enginelessExecutor(assertParse(code('-1'))) const execState = await enginelessExecutor(parse(code('-1')))
expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2)) expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
const noOffset = await enginelessExecutor(assertParse(code('0'))) const noOffset = await enginelessExecutor(parse(code('0')))
expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1) expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
}) })
}) })

View File

@ -1,22 +1,13 @@
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { initPromise, parse, ParseResult } from './wasm' import { parse } from './wasm'
import { enginelessExecutor } from 'lib/testHelpers' import { enginelessExecutor } from 'lib/testHelpers'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Program } from '../wasm-lib/kcl/bindings/Program'
beforeEach(async () => {
await initPromise
})
it('can execute parsed AST', async () => { it('can execute parsed AST', async () => {
const code = `x = 1 const code = `x = 1
// A comment.` // A comment.`
const result = parse(code) const ast = parse(code)
expect(err(result)).toEqual(false) expect(err(ast)).toEqual(false)
const pResult = result as ParseResult const execState = await enginelessExecutor(ast)
expect(pResult.errors.length).toEqual(0) expect(err(ast)).toEqual(false)
expect(pResult.program).not.toEqual(null)
const execState = await enginelessExecutor(pResult.program as Node<Program>)
expect(err(execState)).toEqual(false)
expect(execState.memory.get('x')?.value).toEqual(1) expect(execState.memory.get('x')?.value).toEqual(1)
}) })

View File

@ -16,7 +16,6 @@ import init, {
parse_project_settings, parse_project_settings,
default_project_settings, default_project_settings,
base64_decode, base64_decode,
clear_scene_and_bust_cache,
} from '../wasm-lib/pkg/wasm_lib' } from '../wasm-lib/pkg/wasm_lib'
import { KCLError } from './errors' import { KCLError } from './errors'
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError' import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
@ -36,13 +35,12 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch' import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState' import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory' import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef' import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
import { Environment } from '../wasm-lib/kcl/bindings/Environment' import { Environment } from '../wasm-lib/kcl/bindings/Environment'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
export type { Program } from '../wasm-lib/kcl/bindings/Program' export type { Program } from '../wasm-lib/kcl/bindings/Program'
export type { Expr } from '../wasm-lib/kcl/bindings/Expr' export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
@ -86,22 +84,13 @@ export type SyntaxType =
| 'NonCodeNode' | 'NonCodeNode'
| 'UnaryExpression' | 'UnaryExpression'
export type { SourceRange } from '../wasm-lib/kcl/bindings/SourceRange'
export type { Path } from '../wasm-lib/kcl/bindings/Path' export type { Path } from '../wasm-lib/kcl/bindings/Path'
export type { Sketch } from '../wasm-lib/kcl/bindings/Sketch' export type { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
export type { Solid } from '../wasm-lib/kcl/bindings/Solid' export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue' export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface' export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
export type SourceRange = [number, number, boolean]
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
return [s[0], s[1], s[2] === 0]
}
export function defaultSourceRange(): SourceRange {
return [0, 0, true]
}
export const wasmUrl = () => { export const wasmUrl = () => {
// For when we're in electron (file based) or web server (network based) // For when we're in electron (file based) or web server (network based)
// For some reason relative paths don't work as expected. Otherwise we would // For some reason relative paths don't work as expected. Otherwise we would
@ -131,81 +120,26 @@ const initialise = async () => {
export const initPromise = initialise() export const initPromise = initialise()
const splitErrors = ( export const rangeTypeFix = (ranges: number[][]): [number, number, number][] =>
input: CompilationError[] ranges.map(([start, end, moduleId]) => [start, end, moduleId])
): { errors: CompilationError[]; warnings: CompilationError[] } => {
let errors = []
let warnings = []
for (const i of input) {
if (i.severity === 'Warning') {
warnings.push(i)
} else {
errors.push(i)
}
}
return { errors, warnings } export const parse = (code: string | Error): Node<Program> | Error => {
}
export class ParseResult {
program: Node<Program> | null
errors: CompilationError[]
warnings: CompilationError[]
constructor(
program: Node<Program> | null,
errors: CompilationError[],
warnings: CompilationError[]
) {
this.program = program
this.errors = errors
this.warnings = warnings
}
}
class SuccessParseResult extends ParseResult {
program: Node<Program>
constructor(
program: Node<Program>,
errors: CompilationError[],
warnings: CompilationError[]
) {
super(program, errors, warnings)
this.program = program
}
}
export function resultIsOk(result: ParseResult): result is SuccessParseResult {
return !!result.program && result.errors.length === 0
}
export const parse = (code: string | Error): ParseResult | Error => {
if (err(code)) return code if (err(code)) return code
try { try {
const parsed: [Node<Program>, CompilationError[]] = parse_wasm(code) const program: Node<Program> = parse_wasm(code)
let errs = splitErrors(parsed[1]) return program
return new ParseResult(parsed[0], errs.errors, errs.warnings)
} catch (e: any) { } catch (e: any) {
// throw e // throw e
const parsed: RustKclError = JSON.parse(e.toString()) const parsed: RustKclError = JSON.parse(e.toString())
return new KCLError( return new KCLError(
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]) rangeTypeFix(parsed.sourceRanges)
) )
} }
} }
// Parse and throw an exception if there are any errors (probably not suitable for use outside of testing).
export const assertParse = (code: string): Node<Program> => {
const result = parse(code)
// eslint-disable-next-line suggest-no-throw/suggest-no-throw
if (err(result) || !resultIsOk(result)) throw result
return result.program
}
export type PathToNode = [string | number, string][] export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = ( export const isPathToNodeNumber = (
@ -216,6 +150,7 @@ export const isPathToNodeNumber = (
export interface ExecState { export interface ExecState {
memory: ProgramMemory memory: ProgramMemory
idGenerator: IdGenerator
} }
/** /**
@ -225,12 +160,21 @@ export interface ExecState {
export function emptyExecState(): ExecState { export function emptyExecState(): ExecState {
return { return {
memory: ProgramMemory.empty(), memory: ProgramMemory.empty(),
idGenerator: defaultIdGenerator(),
} }
} }
function execStateFromRaw(raw: RawExecState): ExecState { function execStateFromRaw(raw: RawExecState): ExecState {
return { return {
memory: ProgramMemory.fromRaw(raw.memory), memory: ProgramMemory.fromRaw(raw.memory),
idGenerator: raw.idGenerator,
}
}
export function defaultIdGenerator(): IdGenerator {
return {
nextId: 0,
ids: [],
} }
} }
@ -244,19 +188,6 @@ function emptyEnvironment(): Environment {
return { bindings: {}, parent: null } return { bindings: {}, parent: null }
} }
function emptyRootEnvironment(): Environment {
return {
// This is dumb this is copied from rust.
bindings: {
ZERO: { type: 'Number', value: 0.0, __meta: [] },
QUARTER_TURN: { type: 'Number', value: 90.0, __meta: [] },
HALF_TURN: { type: 'Number', value: 180.0, __meta: [] },
THREE_QUARTER_TURN: { type: 'Number', value: 270.0, __meta: [] },
},
parent: null,
}
}
/** /**
* This duplicates logic in Rust. The hope is to keep ProgramMemory internals * This duplicates logic in Rust. The hope is to keep ProgramMemory internals
* isolated from the rest of the TypeScript code so that we can move it to Rust * isolated from the rest of the TypeScript code so that we can move it to Rust
@ -279,7 +210,7 @@ export class ProgramMemory {
} }
constructor( constructor(
environments: Environment[] = [emptyRootEnvironment()], environments: Environment[] = [emptyEnvironment()],
currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF, currentEnv: EnvironmentRef = ROOT_ENVIRONMENT_REF,
returnVal: KclValue | null = null returnVal: KclValue | null = null
) { ) {
@ -466,31 +397,36 @@ export function sketchFromKclValue(
export const executor = async ( export const executor = async (
node: Node<Program>, node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
programMemoryOverride: ProgramMemory | Error | null = null isMock: boolean = false
): Promise<ExecState> => { ): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride)) if (err(programMemory)) return Promise.reject(programMemory)
return Promise.reject(programMemoryOverride)
// eslint-disable-next-line @typescript-eslint/no-floating-promises // eslint-disable-next-line @typescript-eslint/no-floating-promises
engineCommandManager.startNewSession() engineCommandManager.startNewSession()
const _programMemory = await _executor( const _programMemory = await _executor(
node, node,
programMemory,
idGenerator,
engineCommandManager, engineCommandManager,
programMemoryOverride isMock
) )
await engineCommandManager.waitForAllCommands() await engineCommandManager.waitForAllCommands()
engineCommandManager.endSession()
return _programMemory return _programMemory
} }
export const _executor = async ( export const _executor = async (
node: Node<Program>, node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
idGenerator: IdGenerator = defaultIdGenerator(),
engineCommandManager: EngineCommandManager, engineCommandManager: EngineCommandManager,
programMemoryOverride: ProgramMemory | Error | null = null isMock: boolean
): Promise<ExecState> => { ): Promise<ExecState> => {
if (programMemoryOverride !== null && err(programMemoryOverride)) if (err(programMemory)) return Promise.reject(programMemory)
return Promise.reject(programMemoryOverride)
try { try {
let baseUnit = 'mm' let baseUnit = 'mm'
@ -503,10 +439,13 @@ export const _executor = async (
} }
const execState: RawExecState = await execute_wasm( const execState: RawExecState = await execute_wasm(
JSON.stringify(node), JSON.stringify(node),
JSON.stringify(programMemoryOverride?.toRaw() || null), JSON.stringify(programMemory.toRaw()),
JSON.stringify(idGenerator),
baseUnit, baseUnit,
engineCommandManager, engineCommandManager,
fileSystemManager fileSystemManager,
undefined,
isMock
) )
return execStateFromRaw(execState) return execStateFromRaw(execState)
} catch (e: any) { } catch (e: any) {
@ -515,7 +454,7 @@ export const _executor = async (
const kclError = new KCLError( const kclError = new KCLError(
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]) rangeTypeFix(parsed.sourceRanges)
) )
return Promise.reject(kclError) return Promise.reject(kclError)
@ -588,7 +527,7 @@ export const modifyAstForSketch = async (
const kclError = new KCLError( const kclError = new KCLError(
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]) rangeTypeFix(parsed.sourceRanges)
) )
console.log(kclError) console.log(kclError)
@ -656,7 +595,7 @@ export function programMemoryInit(): ProgramMemory | Error {
return new KCLError( return new KCLError(
parsed.kind, parsed.kind,
parsed.msg, parsed.msg,
sourceRangeFromRust(parsed.sourceRanges[0]) rangeTypeFix(parsed.sourceRanges)
) )
} }
} }
@ -699,21 +638,6 @@ export function defaultAppSettings(): DeepPartial<Configuration> | Error {
return default_app_settings() 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( export function parseAppSettings(
toml: string toml: string
): DeepPartial<Configuration> | Error { ): DeepPartial<Configuration> | Error {

View File

@ -32,14 +32,8 @@ export function mouseControlsToCameraSystem(
mouseControl: MouseControlType | undefined mouseControl: MouseControlType | undefined
): CameraSystem | undefined { ): CameraSystem | undefined {
switch (mouseControl) { switch (mouseControl) {
// TODO: understand why the values come back without underscores and fix the root cause
// @ts-ignore: TS2678
case 'kittycad':
case 'kitty_cad': case 'kitty_cad':
return 'KittyCAD' return 'KittyCAD'
// TODO: understand why the values come back without underscores and fix the root cause
// @ts-ignore: TS2678
case 'onshape':
case 'on_shape': case 'on_shape':
return 'OnShape' return 'OnShape'
case 'trackpad_friendly': case 'trackpad_friendly':
@ -50,9 +44,6 @@ export function mouseControlsToCameraSystem(
return 'NX' return 'NX'
case 'creo': case 'creo':
return 'Creo' return 'Creo'
// TODO: understand why the values come back without underscores and fix the root cause
// @ts-ignore: TS2678
case 'autocad':
case 'auto_cad': case 'auto_cad':
return 'AutoCAD' return 'AutoCAD'
default: default:

View File

@ -1,15 +1,9 @@
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
import { transformAstSketchLines } from 'lang/std/sketchcombos'
import { PathToNode } from 'lang/wasm'
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes' import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants' import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
import { components } from 'lib/machine-api' import { components } from 'lib/machine-api'
import { Selections } from 'lib/selections' import { Selections } from 'lib/selections'
import { kclManager } from 'lib/singletons'
import { err } from 'lib/trap'
import { modelingMachine, SketchTool } from 'machines/modelingMachine' import { modelingMachine, SketchTool } from 'machines/modelingMachine'
import { revolveAxisValidator } from './validators'
type OutputFormat = Models['OutputFormat_type'] type OutputFormat = Models['OutputFormat_type']
type OutputTypeKey = OutputFormat['type'] type OutputTypeKey = OutputFormat['type']
@ -37,17 +31,9 @@ export type ModelingCommandSchema = {
// result: (typeof EXTRUSION_RESULTS)[number] // result: (typeof EXTRUSION_RESULTS)[number]
distance: KclCommandValue distance: KclCommandValue
} }
Loft: {
selection: Selections
}
Shell: {
selection: Selections
thickness: KclCommandValue
}
Revolve: { Revolve: {
selection: Selections selection: Selections
angle: KclCommandValue angle: KclCommandValue
axis: Selections
} }
Fillet: { Fillet: {
// todo // todo
@ -61,18 +47,6 @@ export type ModelingCommandSchema = {
'change tool': { 'change tool': {
tool: SketchTool tool: SketchTool
} }
'Constrain length': {
selection: Selections
length: KclCommandValue
}
'Constrain with named value': {
currentValue: {
valueText: string
pathToNode: PathToNode
variableName: string
}
namedValue: KclCommandValue
}
'Text-to-CAD': { 'Text-to-CAD': {
prompt: string prompt: string
} }
@ -286,39 +260,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
Loft: {
description: 'Create a 3D body by blending between two or more sketches',
icon: 'loft',
needsReview: true,
args: {
selection: {
inputType: 'selection',
selectionTypes: ['solid2D'],
multiple: true,
required: true,
skip: false,
},
},
},
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 // TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
Revolve: { Revolve: {
description: 'Create a 3D body by rotating a sketch region about an axis.', description: 'Create a 3D body by rotating a sketch region about an axis.',
@ -332,13 +273,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
required: true, required: true,
skip: true, skip: true,
}, },
axis: {
required: true,
inputType: 'selection',
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
multiple: false,
validation: revolveAxisValidator,
},
angle: { angle: {
inputType: 'kcl', inputType: 'kcl',
defaultValue: KCL_DEFAULT_DEGREE, defaultValue: KCL_DEFAULT_DEGREE,
@ -386,88 +320,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
}, },
}, },
}, },
'Constrain length': {
description: 'Constrain the length of one or more segments.',
icon: 'dimension',
args: {
selection: {
inputType: 'selection',
selectionTypes: ['segment'],
multiple: false,
required: true,
skip: true,
},
length: {
inputType: 'kcl',
required: true,
createVariableByDefault: true,
defaultValue(_, machineContext) {
const selectionRanges = machineContext?.selectionRanges
if (!selectionRanges) return KCL_DEFAULT_LENGTH
const angleLength = angleLengthInfo({
selectionRanges,
angleOrLength: 'setLength',
})
if (err(angleLength)) return KCL_DEFAULT_LENGTH
const { transforms } = angleLength
// QUESTION: is it okay to reference kclManager here? will its state be up to date?
const sketched = transformAstSketchLines({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
referenceSegName: '',
})
if (err(sketched)) return KCL_DEFAULT_LENGTH
const { valueUsedInTransform } = sketched
return valueUsedInTransform?.toString() || KCL_DEFAULT_LENGTH
},
},
},
},
'Constrain with named value': {
description: 'Constrain a value by making it a named constant.',
icon: 'make-variable',
args: {
currentValue: {
description:
'Path to the node in the AST to constrain. This is never shown to the user.',
inputType: 'text',
required: false,
skip: true,
},
namedValue: {
inputType: 'kcl',
required: true,
createVariableByDefault: true,
variableName(commandBarContext, machineContext) {
const { currentValue } = commandBarContext.argumentsToSubmit
if (
!currentValue ||
!(currentValue instanceof Object) ||
!('variableName' in currentValue) ||
typeof currentValue.variableName !== 'string'
) {
return 'value'
}
return currentValue.variableName
},
defaultValue: (commandBarContext) => {
const { currentValue } = commandBarContext.argumentsToSubmit
if (
!currentValue ||
!(currentValue instanceof Object) ||
!('valueText' in currentValue) ||
typeof currentValue.valueText !== 'string'
) {
return KCL_DEFAULT_LENGTH
}
return currentValue.valueText
},
},
},
},
'Text-to-CAD': { 'Text-to-CAD': {
description: 'Use the Zoo Text-to-CAD API to generate part starters.', description: 'Use the Zoo Text-to-CAD API to generate part starters.',
icon: 'chat', icon: 'chat',

View File

@ -1,107 +0,0 @@
import { Models } from '@kittycad/lib'
import { engineCommandManager } from 'lib/singletons'
import { uuidv4 } from 'lib/utils'
import { CommandBarContext } from 'machines/commandBarMachine'
import { Selections } from 'lib/selections'
import { isSolid2D, isSegment, isSweep } from 'lang/std/artifactGraph'
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
for (let tries = 0; tries < numberOfRetries; tries++) {
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'disable_dry_run' },
})
// Exit out since the command was successful
return
} catch (e) {
console.error(e)
console.error('disable_dry_run failed. This is bad!')
}
}
}
// Takes a callback function and wraps it around enable_dry_run and disable_dry_run
export const dryRunWrapper = async (callback: () => Promise<any>) => {
// Gotcha: What about race conditions?
try {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'enable_dry_run' },
})
const result = await callback()
return result
} catch (e) {
console.error(e)
} finally {
await disableDryRunWithRetry(5)
}
}
function isSelections(selections: unknown): selections is Selections {
return (
(selections as Selections).graphSelections !== undefined &&
(selections as Selections).otherSelections !== undefined
)
}
export const revolveAxisValidator = async ({
data,
context,
}: {
data: { [key: string]: Selections }
context: CommandBarContext
}): Promise<boolean | string> => {
if (!isSelections(context.argumentsToSubmit.selection)) {
return 'Unable to revolve, selections are missing'
}
const artifact =
context.argumentsToSubmit.selection.graphSelections[0].artifact
if (!artifact) {
return 'Unable to revolve, sketch not found'
}
if (!(isSolid2D(artifact) || isSegment(artifact) || isSweep(artifact))) {
return 'Unable to revolve, sketch has no path'
}
const sketchSelection = artifact.pathId
let edgeSelection = data.axis.graphSelections[0].artifact?.id
if (!sketchSelection) {
return 'Unable to revolve, sketch is missing'
}
if (!edgeSelection) {
return 'Unable to revolve, edge is missing'
}
const angleInDegrees: Models['Angle_type'] = {
unit: 'degrees',
value: 360,
}
const revolveAboutEdgeCommand = async () => {
return await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'revolve_about_edge',
angle: angleInDegrees,
edge_id: edgeSelection,
target: sketchSelection,
tolerance: 0.0001,
},
})
}
const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
if (attemptRevolve?.success) {
return true
} else {
// return error message for the toast
return 'Unable to revolve with selected axis'
}
}

View File

@ -7,7 +7,7 @@ import { ReactNode } from 'react'
import { MachineManager } from 'components/MachineManagerProvider' import { MachineManager } from 'components/MachineManagerProvider'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
import { Artifact } from 'lang/std/artifactGraph' import { Artifact } from 'lang/std/artifactGraph'
import { CommandBarContext } from 'machines/commandBarMachine'
type Icon = CustomIconName type Icon = CustomIconName
const PLATFORMS = ['both', 'web', 'desktop'] as const const PLATFORMS = ['both', 'web', 'desktop'] as const
const INPUT_TYPES = [ const INPUT_TYPES = [
@ -147,30 +147,8 @@ export type CommandArgumentConfig<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
}
| {
inputType: 'kcl'
createVariableByDefault?: boolean
variableName?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: C
) => string)
defaultValue?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: C
) => string)
} }
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default values
| { | {
inputType: 'string' inputType: 'string'
defaultValue?: defaultValue?:
@ -243,30 +221,8 @@ export type CommandArgument<
inputType: 'selection' inputType: 'selection'
selectionTypes: Artifact['type'][] selectionTypes: Artifact['type'][]
multiple: boolean multiple: boolean
validation?: ({
data,
context,
}: {
data: any
context: CommandBarContext
}) => Promise<boolean | string>
}
| {
inputType: 'kcl'
createVariableByDefault?: boolean
variableName?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: ContextFrom<T>
) => string)
defaultValue?:
| string
| ((
commandBarContext: ContextFrom<typeof commandBarMachine>,
machineContext?: ContextFrom<T>
) => string)
} }
| { inputType: 'kcl'; defaultValue?: string } // KCL expression inputs have simple strings as default value
| { | {
inputType: 'string' inputType: 'string'
defaultValue?: defaultValue?:

View File

@ -52,8 +52,6 @@ export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
export const KCL_DEFAULT_CONSTANT_PREFIXES = { export const KCL_DEFAULT_CONSTANT_PREFIXES = {
SKETCH: 'sketch', SKETCH: 'sketch',
EXTRUDE: 'extrude', EXTRUDE: 'extrude',
LOFT: 'loft',
SHELL: 'shell',
SEGMENT: 'seg', SEGMENT: 'seg',
REVOLVE: 'revolve', REVOLVE: 'revolve',
PLANE: 'plane', PLANE: 'plane',
@ -111,10 +109,3 @@ export const KCL_SAMPLES_MANIFEST_URLS = {
/** Toast id for the app auto-updater toast */ /** Toast id for the app auto-updater toast */
export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast' export const AUTO_UPDATER_TOAST_ID = 'auto-updater-toast'
/** Local sketch axis values in KCL for operations, it could either be 'X' or 'Y' */
export const KCL_AXIS_X = 'X'
export const KCL_AXIS_Y = 'Y'
export const KCL_AXIS_NEG_X = '-X'
export const KCL_AXIS_NEG_Y = '-Y'
export const KCL_DEFAULT_AXIS = 'X'

View File

@ -155,8 +155,6 @@ export function buildCommandArgument<
context: ContextFrom<T>, context: ContextFrom<T>,
machineActor: Actor<T> machineActor: Actor<T>
): CommandArgument<O, T> & { inputType: typeof arg.inputType } { ): CommandArgument<O, T> & { inputType: typeof arg.inputType } {
// GOTCHA: modelingCommandConfig is not a 1:1 mapping to this baseCommandArgument
// You need to manually add key/value pairs here.
const baseCommandArgument = { const baseCommandArgument = {
description: arg.description, description: arg.description,
required: arg.required, required: arg.required,
@ -183,13 +181,10 @@ export function buildCommandArgument<
...baseCommandArgument, ...baseCommandArgument,
multiple: arg.multiple, multiple: arg.multiple,
selectionTypes: arg.selectionTypes, selectionTypes: arg.selectionTypes,
validation: arg.validation,
} satisfies CommandArgument<O, T> & { inputType: 'selection' } } satisfies CommandArgument<O, T> & { inputType: 'selection' }
} else if (arg.inputType === 'kcl') { } else if (arg.inputType === 'kcl') {
return { return {
inputType: arg.inputType, inputType: arg.inputType,
createVariableByDefault: arg.createVariableByDefault,
variableName: arg.variableName,
defaultValue: arg.defaultValue, defaultValue: arg.defaultValue,
...baseCommandArgument, ...baseCommandArgument,
} satisfies CommandArgument<O, T> & { inputType: 'kcl' } } satisfies CommandArgument<O, T> & { inputType: 'kcl' }

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