10
.eslintrc
10
.eslintrc
@ -4,7 +4,8 @@
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"plugins": [
|
||||
"css-modules"
|
||||
"css-modules",
|
||||
"suggest-no-throw",
|
||||
],
|
||||
"extends": [
|
||||
"react-app",
|
||||
@ -17,6 +18,7 @@
|
||||
"never"
|
||||
],
|
||||
"react-hooks/exhaustive-deps": "off",
|
||||
"suggest-no-throw/suggest-no-throw": "warn",
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
@ -25,6 +27,12 @@
|
||||
"@typescript-eslint/no-floating-promises": "warn",
|
||||
"testing-library/prefer-screen-queries": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["src/**/*.test.ts"],
|
||||
"rules": {
|
||||
"suggest-no-throw/suggest-no-throw": "off",
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
88
.github/workflows/playwright.yml
vendored
88
.github/workflows/playwright.yml
vendored
@ -95,21 +95,13 @@ jobs:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
- name: check for changes
|
||||
id: git-check
|
||||
run: |
|
||||
git add .
|
||||
if git status | grep -q "Changes to be committed"
|
||||
then
|
||||
echo "::set-output name=modified::true"
|
||||
else
|
||||
echo "::set-output name=modified::false"
|
||||
then echo "modified=true" >> $GITHUB_OUTPUT
|
||||
else echo "modified=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Commit changes, if any
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
@ -125,17 +117,53 @@ jobs:
|
||||
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" || true
|
||||
git push
|
||||
git push origin ${{ github.head_ref }}
|
||||
# only upload artifacts if there's actually changes
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: steps.git-check.outputs.modified == 'true'
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
# if have previous run results, use them
|
||||
- uses: actions/download-artifact@v4
|
||||
if: always()
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
- name: Run ubuntu/chrome flow retry failures
|
||||
id: retry
|
||||
if: always()
|
||||
run: |
|
||||
ls -1 "test-results"
|
||||
if [[ $(ls -1 "test-results" | wc -l) == "0" ]];
|
||||
then echo "retried=false" >> $GITHUB_OUTPUT; exit 0;
|
||||
else echo "retried=true" >> $GITHUB_OUTPUT;
|
||||
fi;
|
||||
yarn playwright test --project="Google Chrome" --last-failed e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: Run ubuntu/chrome flow
|
||||
if: steps.retry.outputs.retried == 'false'
|
||||
run: yarn playwright test --project="Google Chrome" e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-ubuntu
|
||||
name: test-results
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
playwright-macos:
|
||||
timeout-minutes: 60
|
||||
@ -194,16 +222,46 @@ jobs:
|
||||
run: yarn build:wasm
|
||||
- name: build web
|
||||
run: yarn build:local
|
||||
# if have previous run results, use them
|
||||
- uses: actions/download-artifact@v4
|
||||
if: ${{ always() }}
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: test-results
|
||||
path: test-results/
|
||||
- name: Run macos/safari flow retry failures
|
||||
id: retry
|
||||
continue-on-error: true
|
||||
if: ${{ success() }}
|
||||
run: |
|
||||
if [ -d "test-results" ];
|
||||
then echo "retried=true" >> $GITHUB_OUTPUT;
|
||||
else echo "retried=false" >> $GITHUB_OUTPUT;
|
||||
fi;
|
||||
yarn playwright test --project="webkit" --last-failed e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- name: Run macos/safari flow
|
||||
if: ${{ steps.retry.outputs.retried != 'true' }}
|
||||
# webkit doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues)
|
||||
# TODO remove this and the matrix and run all tests on ubuntu when this is fixed
|
||||
run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts
|
||||
env:
|
||||
CI: true
|
||||
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: playwright-report-macos
|
||||
name: test-results
|
||||
path: test-results/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ always() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
overwrite: true
|
||||
|
||||
|
11
Makefile
11
Makefile
@ -1,14 +1,17 @@
|
||||
.PHONY: dev
|
||||
|
||||
WASM_LIB_FILES := $(wildcard src/wasm-lib/**/*.rs)
|
||||
TS_SRC := $(wildcard src/**/*.tsx) $(wildcard src/**/*.ts)
|
||||
XSTATE_TYPEGENS := $(wildcard src/machines/*.typegen.ts)
|
||||
|
||||
dev: node_modules public/wasm_lib_bg.wasm
|
||||
dev: node_modules public/wasm_lib_bg.wasm $(XSTATE_TYPEGENS)
|
||||
yarn start
|
||||
|
||||
$(XSTATE_TYPEGENS): $(TS_SRC)
|
||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||
|
||||
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
|
||||
yarn build:wasm-dev
|
||||
|
||||
node_modules: package.json
|
||||
|
||||
package.json:
|
||||
node_modules: package.json yarn.lock
|
||||
yarn install
|
||||
|
54
README.md
54
README.md
@ -89,25 +89,6 @@ enable third-party cookies. You can enable third-party cookies by clicking on
|
||||
the eye with a slash through it in the URL bar, and clicking on "Enable
|
||||
Third-Party Cookies".
|
||||
|
||||
## Running tests
|
||||
|
||||
First, start the dev server following "Running a development build" above.
|
||||
|
||||
Then in another terminal tab, run:
|
||||
|
||||
```
|
||||
yarn test
|
||||
```
|
||||
|
||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||
|
||||
For running the rust (not tauri rust though) only, you can
|
||||
|
||||
```bash
|
||||
cd src/wasm-lib
|
||||
cargo test
|
||||
```
|
||||
|
||||
## Tauri
|
||||
|
||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm-dev` need to have been done before hand then
|
||||
@ -195,7 +176,9 @@ $ cargo +nightly fuzz run parser
|
||||
For more information on fuzzing you can check out
|
||||
[this guide](https://rust-fuzz.github.io/book/cargo-fuzz.html).
|
||||
|
||||
### Playwright
|
||||
## Tests
|
||||
|
||||
### Playwright tests
|
||||
|
||||
For a portable way to run Playwright you'll need Docker.
|
||||
|
||||
@ -284,6 +267,37 @@ Where `./store` should look like this
|
||||
|
||||
However because much of our tests involve clicking in the stream at specific locations, it's code-gen looks `await page.locator('video').click();` when really we need to use a pixel coord, so I think it's of limited use.
|
||||
|
||||
### Unit and component tests
|
||||
|
||||
If you already haven't, run the following:
|
||||
|
||||
```
|
||||
yarn
|
||||
yarn build:wasm
|
||||
yarn start
|
||||
```
|
||||
|
||||
and finally:
|
||||
|
||||
```
|
||||
yarn test:nowatch
|
||||
```
|
||||
|
||||
For individual testing:
|
||||
|
||||
```
|
||||
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
||||
```
|
||||
|
||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||
|
||||
### Rust tests
|
||||
|
||||
```bash
|
||||
cd src/wasm-lib
|
||||
cargo test
|
||||
```
|
||||
|
||||
#### Some notes on CI
|
||||
|
||||
The tests are broken into snapshot tests and non-snapshot tests, and they run in that order, they automatically commit new snap shots, so if you see an image commit check it was an intended change. If we have non-determinism in the snapshots such that they are always committing new images, hopefully this annoyance makes us fix them asap, if you notice this happening let Kurt know. But for the odd occasion `git reset --hard HEAD~ && git push -f` is your friend.
|
||||
|
@ -630,6 +630,7 @@ test('if you use the format keyboard binding it formats your code', async ({
|
||||
|> line([-20, 0], %)
|
||||
|> close(%)`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
await page.setViewportSize({ width: 1000, height: 500 })
|
||||
const lspStartPromise = page.waitForEvent('console', async (message) => {
|
||||
@ -2114,19 +2115,29 @@ test.describe('Command bar tests', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// First try opening the command bar and closing it
|
||||
await page
|
||||
.getByRole('button', { name: 'Commands', exact: false })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
|
||||
let cmdSearchBar = await page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await page.keyboard.press('Escape')
|
||||
cmdSearchBar = await page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).not.toBeVisible()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
if (process.platform !== 'linux') {
|
||||
await page.keyboard.press('Meta+K')
|
||||
} else {
|
||||
await page.locator('html').press('Control+C')
|
||||
}
|
||||
cmdSearchBar = await page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
@ -2165,13 +2176,21 @@ test.describe('Command bar tests', () => {
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/', { waitUntil: 'domcontentloaded' })
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// Put the cursor in the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
if (process.platform !== 'linux') {
|
||||
await page.keyboard.press('Meta+K')
|
||||
} else {
|
||||
await page.locator('.cm-content').press('Control+C')
|
||||
}
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
@ -2234,7 +2253,11 @@ test.describe('Command bar tests', () => {
|
||||
await page.getByRole('button', { name: 'Extrude' }).isEnabled()
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
if (process.platform !== 'linux') {
|
||||
await page.keyboard.press('Meta+K')
|
||||
} else {
|
||||
await page.locator('html').press('Control+C')
|
||||
}
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
@ -2889,7 +2912,7 @@ test('Can edit a sketch that has been extruded in the same pipe', async ({
|
||||
|
||||
const startPX = [665, 458]
|
||||
|
||||
const dragPX = 30
|
||||
const dragPX = 80
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
|
||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
|
||||
@ -2897,16 +2920,14 @@ test('Can edit a sketch that has been extruded in the same pipe', async ({
|
||||
await page.waitForTimeout(400)
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
const step5 = { steps: 5 }
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfieAt handle
|
||||
await page.mouse.move(startPX[0], startPX[1])
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0], y: startPX[1] },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] - dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
@ -2914,20 +2935,23 @@ test('Can edit a sketch that has been extruded in the same pipe', async ({
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.mouse.move(lineEnd.x - 5, lineEnd.y)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(lineEnd.x + dragPX, lineEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.waitForTimeout(100)
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y - dragPX },
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag tangentialArcTo handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.mouse.move(tangentEnd.x, tangentEnd.y - 5)
|
||||
await page.mouse.down()
|
||||
await page.mouse.move(tangentEnd.x + dragPX, tangentEnd.y - dragPX, step5)
|
||||
await page.mouse.up()
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y - dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
@ -3404,11 +3428,8 @@ const part002 = startSketchOn('XZ')
|
||||
|
||||
const line3 = await u.getSegmentBodyCoords(`[data-overlay-index="${2}"]`)
|
||||
|
||||
// await page.mouse.click(line1.x, line1.y)
|
||||
// await page.keyboard.down('Shift')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.waitForTimeout(100) // this wait is needed for webkit - not sure why
|
||||
// await page.keyboard.up('Shift')
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Constraints',
|
||||
@ -3418,6 +3439,7 @@ const part002 = startSketchOn('XZ')
|
||||
.getByRole('button', { name: 'remove constraints', exact: true })
|
||||
.click()
|
||||
|
||||
await page.getByText('line([39.13, 68.63], %)').click()
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(1)
|
||||
await expect(activeLinesContent[0]).toHaveText('|> line([39.13, 68.63], %)')
|
||||
@ -3495,15 +3517,17 @@ const part002 = startSketchOn('XZ')
|
||||
.getByRole('button', { name: 'Add constraining value' })
|
||||
.click()
|
||||
|
||||
// Wait for the codemod to take effect
|
||||
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
`offset: ${offset},`
|
||||
)
|
||||
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(
|
||||
`|> line([74.36, 130.4], %, 'seg01')`
|
||||
)
|
||||
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
|
||||
await expect(page.locator('.cm-content')).toContainText(`angle: -57,`)
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
`offset: ${offset},`
|
||||
)
|
||||
|
||||
// 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)
|
||||
@ -3978,6 +4002,11 @@ const part002 = startSketchOn('XZ')
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page.mouse.click(line4.x, line4.y)
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
// check actives lines
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
||||
|
||||
const constraintMenuButton = page.getByRole('button', {
|
||||
name: 'Constraints',
|
||||
})
|
||||
@ -3989,11 +4018,8 @@ const part002 = startSketchOn('XZ')
|
||||
|
||||
// apply the constraint
|
||||
await constraintMenuButton.click()
|
||||
await constraintButton.click()
|
||||
await constraintButton.click({ delay: 200 })
|
||||
|
||||
// check actives lines
|
||||
const activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent).toHaveLength(codeAfter.length)
|
||||
// check there are still 3 cursors (they should stay on the same lines as before constraint was applied)
|
||||
await expect(page.locator('.cm-cursor')).toHaveCount(codeAfter.length)
|
||||
|
||||
@ -4257,6 +4283,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal,
|
||||
ang = 45,
|
||||
steps = 10,
|
||||
locator,
|
||||
}: {
|
||||
hoverPos: { x: number; y: number }
|
||||
constraintType:
|
||||
@ -4269,6 +4296,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal: string
|
||||
ang?: number
|
||||
steps?: number
|
||||
locator?: string
|
||||
}) => {
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
|
||||
@ -4279,7 +4307,7 @@ test.describe('Testing segment overlays', () => {
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectBeforeUnconstrained
|
||||
@ -4302,7 +4330,7 @@ test.describe('Testing segment overlays', () => {
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
|
||||
const unconstrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="false"]`
|
||||
@ -4336,6 +4364,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal,
|
||||
ang = 45,
|
||||
steps = 5,
|
||||
locator,
|
||||
}: {
|
||||
hoverPos: { x: number; y: number }
|
||||
constraintType:
|
||||
@ -4348,6 +4377,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal: string
|
||||
ang?: number
|
||||
steps?: number
|
||||
locator?: string
|
||||
}) => {
|
||||
await page.mouse.move(0, 0)
|
||||
await page.waitForTimeout(1000)
|
||||
@ -4356,7 +4386,7 @@ test.describe('Testing segment overlays', () => {
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
@ -4382,7 +4412,7 @@ test.describe('Testing segment overlays', () => {
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
|
||||
const constrainedLocator = page.locator(
|
||||
`[data-constraint-type="${constraintType}"][data-is-constrained="true"]`
|
||||
@ -4478,6 +4508,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectAfterUnconstrained: '|> line([0.5, -14], %)',
|
||||
expectFinal: '|> line([0.5, yRel001], %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="0"]',
|
||||
})
|
||||
console.log('line2')
|
||||
await clickUnconstrained({
|
||||
@ -4487,6 +4518,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectAfterUnconstrained: 'line([xRel001, yRel001], %)',
|
||||
expectFinal: '|> line([0.5, yRel001], %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-index="0"]',
|
||||
})
|
||||
|
||||
const angledLine = await u.getBoundingBox(`[data-overlay-index="1"]`)
|
||||
@ -4500,6 +4532,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectAfterUnconstrained: 'angledLine({ angle: 3, length: 32 + 0 }, %)',
|
||||
expectFinal: 'angledLine({ angle: angle001, length: 32 + 0 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
console.log('angledLine2')
|
||||
await clickConstrained({
|
||||
@ -4511,10 +4544,10 @@ test.describe('Testing segment overlays', () => {
|
||||
'angledLine({ angle: angle001, length: 32 }, %)',
|
||||
expectFinal: 'angledLine({ angle: angle001, length: len001 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
|
||||
await page.mouse.move(700, 250)
|
||||
await page.mouse.wheel(0, 25)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
let lineTo = await u.getBoundingBox(`[data-overlay-index="2"]`)
|
||||
@ -4528,6 +4561,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal: 'lineTo([5 + 33, yAbs001], %)',
|
||||
steps: 8,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="2"]',
|
||||
})
|
||||
console.log('lineTo2')
|
||||
await clickConstrained({
|
||||
@ -4538,6 +4572,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal: 'lineTo([xAbs001, yAbs001], %)',
|
||||
steps: 8,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="2"]',
|
||||
})
|
||||
|
||||
const xLineTo = await u.getBoundingBox(`[data-overlay-index="3"]`)
|
||||
@ -4551,6 +4586,7 @@ test.describe('Testing segment overlays', () => {
|
||||
expectFinal: 'xLineTo(xAbs002, %)',
|
||||
ang: ang + 180,
|
||||
steps: 8,
|
||||
locator: '[data-overlay-toolbar-index="3"]',
|
||||
})
|
||||
})
|
||||
test('for segments [yLineTo, xLine]', async ({ page }) => {
|
||||
@ -4597,7 +4633,6 @@ const part001 = startSketchOn('XZ')
|
||||
const clickUnconstrained = _clickUnconstrained(page)
|
||||
|
||||
await page.mouse.move(700, 250)
|
||||
await page.mouse.wheel(0, 25)
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
let ang = 0
|
||||
@ -4612,6 +4647,7 @@ const part001 = startSketchOn('XZ')
|
||||
expectAfterUnconstrained: "yLineTo(yAbs002, %, 'a')",
|
||||
expectFinal: "yLineTo(-10.77, %, 'a')",
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="4"]',
|
||||
})
|
||||
|
||||
const xLine = await u.getBoundingBox(`[data-overlay-index="5"]`)
|
||||
@ -4625,6 +4661,7 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'xLine(26.04, %)',
|
||||
steps: 10,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="5"]',
|
||||
})
|
||||
})
|
||||
test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({
|
||||
@ -4654,6 +4691,7 @@ const part001 = startSketchOn('XZ')
|
||||
|> tangentialArcTo([3.14 + 13, 3.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -4682,12 +4720,13 @@ const part001 = startSketchOn('XZ')
|
||||
ang = await u.getAngle(`[data-overlay-index="6"]`)
|
||||
console.log('yline1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: yLine.x, y: yLine.y + 20 },
|
||||
hoverPos: { x: yLine.x, y: yLine.y },
|
||||
constraintType: 'yRelative',
|
||||
expectBeforeUnconstrained: 'yLine(21.14 + 0, %)',
|
||||
expectAfterUnconstrained: 'yLine(21.14, %)',
|
||||
expectFinal: 'yLine(yRel001, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="6"]',
|
||||
})
|
||||
|
||||
const angledLineOfXLength = await u.getBoundingBox(
|
||||
@ -4696,7 +4735,7 @@ const part001 = startSketchOn('XZ')
|
||||
ang = await u.getAngle(`[data-overlay-index="7"]`)
|
||||
console.log('angledLineOfXLength1')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLineOfXLength.x + 20, y: angledLineOfXLength.y },
|
||||
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
||||
constraintType: 'angle',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineOfXLength({ angle: 181 + 0, length: 23.14 }, %)',
|
||||
@ -4705,10 +4744,11 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal:
|
||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="7"]',
|
||||
})
|
||||
console.log('angledLineOfXLength2')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: angledLineOfXLength.x + 25, y: angledLineOfXLength.y },
|
||||
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
||||
constraintType: 'xRelative',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
||||
@ -4718,6 +4758,7 @@ const part001 = startSketchOn('XZ')
|
||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
||||
steps: 7,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="7"]',
|
||||
})
|
||||
|
||||
const angledLineOfYLength = await u.getBoundingBox(
|
||||
@ -4726,7 +4767,7 @@ const part001 = startSketchOn('XZ')
|
||||
ang = await u.getAngle(`[data-overlay-index="8"]`)
|
||||
console.log('angledLineOfYLength1')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y - 20 },
|
||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
||||
constraintType: 'angle',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
||||
@ -4735,10 +4776,11 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
||||
ang: ang + 180,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="8"]',
|
||||
})
|
||||
console.log('angledLineOfYLength2')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y - 20 },
|
||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
||||
constraintType: 'yRelative',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
||||
@ -4747,6 +4789,7 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'angledLineOfYLength({ angle: -91, length: yRel002 }, %)',
|
||||
ang: ang + 180,
|
||||
steps: 7,
|
||||
locator: '[data-overlay-toolbar-index="8"]',
|
||||
})
|
||||
})
|
||||
test('for segments [angledLineToX, angledLineToY, angledLineThatIntersects]', async ({
|
||||
@ -4776,6 +4819,7 @@ const part001 = startSketchOn('XZ')
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -4809,10 +4853,11 @@ const part001 = startSketchOn('XZ')
|
||||
expectAfterUnconstrained: 'angledLineToX({ angle: 3, to: 26 }, %)',
|
||||
expectFinal: 'angledLineToX({ angle: angle001, to: 26 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="9"]',
|
||||
})
|
||||
console.log('angledLineToX2')
|
||||
await clickUnconstrained({
|
||||
hoverPos: { x: angledLineToX.x - 20, y: angledLineToX.y },
|
||||
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
||||
constraintType: 'xAbsolute',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineToX({ angle: angle001, to: 26 }, %)',
|
||||
@ -4820,6 +4865,7 @@ const part001 = startSketchOn('XZ')
|
||||
'angledLineToX({ angle: angle001, to: xAbs001 }, %)',
|
||||
expectFinal: 'angledLineToX({ angle: angle001, to: 26 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="9"]',
|
||||
})
|
||||
|
||||
const angledLineToY = await u.getBoundingBox(`[data-overlay-index="10"]`)
|
||||
@ -4835,16 +4881,18 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
||||
steps: process.platform === 'darwin' ? 8 : 9,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="10"]',
|
||||
})
|
||||
console.log('angledLineToY2')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: angledLineToY.x, y: angledLineToY.y + 20 },
|
||||
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
||||
constraintType: 'yAbsolute',
|
||||
expectBeforeUnconstrained:
|
||||
'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
||||
expectAfterUnconstrained: 'angledLineToY({ angle: 89, to: 9.14 }, %)',
|
||||
expectFinal: 'angledLineToY({ angle: 89, to: yAbs001 }, %)',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="10"]',
|
||||
})
|
||||
|
||||
const angledLineThatIntersects = await u.getBoundingBox(
|
||||
@ -4854,7 +4902,7 @@ const part001 = startSketchOn('XZ')
|
||||
console.log('angledLineThatIntersects')
|
||||
await clickUnconstrained({
|
||||
hoverPos: {
|
||||
x: angledLineThatIntersects.x + 20,
|
||||
x: angledLineThatIntersects.x,
|
||||
y: angledLineThatIntersects.y,
|
||||
},
|
||||
constraintType: 'angle',
|
||||
@ -4874,11 +4922,12 @@ const part001 = startSketchOn('XZ')
|
||||
intersectTag: 'a'
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="11"]',
|
||||
})
|
||||
console.log('angledLineThatIntersects2')
|
||||
await clickUnconstrained({
|
||||
hoverPos: {
|
||||
x: angledLineThatIntersects.x + 20,
|
||||
x: angledLineThatIntersects.x,
|
||||
y: angledLineThatIntersects.y,
|
||||
},
|
||||
constraintType: 'intersectionOffset',
|
||||
@ -4898,6 +4947,7 @@ const part001 = startSketchOn('XZ')
|
||||
intersectTag: 'a'
|
||||
}, %)`,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="11"]',
|
||||
})
|
||||
})
|
||||
test('for segment [tangentialArcTo]', async ({ page }) => {
|
||||
@ -4925,6 +4975,7 @@ const part001 = startSketchOn('XZ')
|
||||
|> tangentialArcTo([3.14 + 13, -3.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -4947,9 +4998,9 @@ const part001 = startSketchOn('XZ')
|
||||
const clickConstrained = _clickConstrained(page)
|
||||
|
||||
const tangentialArcTo = await u.getBoundingBox(
|
||||
`[data-overlay-index="12"]`
|
||||
'[data-overlay-index="12"]'
|
||||
)
|
||||
let ang = await u.getAngle(`[data-overlay-index="12"]`)
|
||||
let ang = await u.getAngle('[data-overlay-index="12"]')
|
||||
console.log('tangentialArcTo')
|
||||
await clickConstrained({
|
||||
hoverPos: { x: tangentialArcTo.x, y: tangentialArcTo.y },
|
||||
@ -4959,6 +5010,7 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'tangentialArcTo([xAbs001, -3.14], %)',
|
||||
ang: ang + 180,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="12"]',
|
||||
})
|
||||
console.log('tangentialArcTo2')
|
||||
await clickUnconstrained({
|
||||
@ -4969,6 +5021,7 @@ const part001 = startSketchOn('XZ')
|
||||
expectFinal: 'tangentialArcTo([xAbs001, -3.14], %)',
|
||||
ang: ang + 180,
|
||||
steps: 10,
|
||||
locator: '[data-overlay-toolbar-index="12"]',
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -4981,21 +5034,26 @@ const part001 = startSketchOn('XZ')
|
||||
stdLibFnName,
|
||||
ang = 45,
|
||||
steps = 6,
|
||||
locator,
|
||||
}: {
|
||||
hoverPos: { x: number; y: number }
|
||||
codeToBeDeleted: string
|
||||
stdLibFnName: string
|
||||
ang?: number
|
||||
steps?: number
|
||||
locator?: string
|
||||
}) => {
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
const [x, y] = [
|
||||
Math.cos((ang * Math.PI) / 180) * 45,
|
||||
Math.sin((ang * Math.PI) / 180) * 45,
|
||||
]
|
||||
|
||||
await page.mouse.move(hoverPos.x + x, hoverPos.y + y)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y, { steps })
|
||||
await page.mouse.move(0, 0)
|
||||
await page.waitForTimeout(1000)
|
||||
let x = 0,
|
||||
y = 0
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(page, x, y, 20, 30, ang, 10, 5, locator)
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted)
|
||||
|
||||
await page.locator(`[data-stdlib-fn-name="${stdLibFnName}"]`).click()
|
||||
@ -5030,6 +5088,7 @@ const part001 = startSketchOn('XZ')
|
||||
|> tangentialArcTo([3.14 + 13, 1.14], %)
|
||||
`
|
||||
)
|
||||
localStorage.setItem('disableAxis', 'true')
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
@ -5054,96 +5113,137 @@ const part001 = startSketchOn('XZ')
|
||||
const getOverlayByIndex = (index: number) =>
|
||||
u.getBoundingBox(`[data-overlay-index="${index}"]`)
|
||||
segmentToDelete = await getOverlayByIndex(12)
|
||||
let ang = await u.getAngle(`[data-overlay-index="${12}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x - 10, y: segmentToDelete.y + 20 },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'tangentialArcTo([3.14 + 13, 1.14], %)',
|
||||
stdLibFnName: 'tangentialArcTo',
|
||||
ang: -45,
|
||||
ang: ang + 180,
|
||||
steps: 6,
|
||||
locator: '[data-overlay-toolbar-index="12"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(11)
|
||||
ang = await u.getAngle(`[data-overlay-index="${11}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x + 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: `angledLineThatIntersects({
|
||||
angle: 4.14,
|
||||
intersectTag: 'a',
|
||||
offset: 9
|
||||
}, %)`,
|
||||
stdLibFnName: 'angledLineThatIntersects',
|
||||
ang: -45,
|
||||
ang: ang + 180,
|
||||
steps: 7,
|
||||
locator: '[data-overlay-toolbar-index="11"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(10)
|
||||
ang = await u.getAngle(`[data-overlay-index="${10}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x + 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
||||
stdLibFnName: 'angledLineToY',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="10"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(9)
|
||||
ang = await u.getAngle(`[data-overlay-index="${9}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x - 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLineToX({ angle: 3 + 0, to: 26 }, %)',
|
||||
stdLibFnName: 'angledLineToX',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="9"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(8)
|
||||
ang = await u.getAngle(`[data-overlay-index="${8}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y - 10 },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted:
|
||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
||||
stdLibFnName: 'angledLineOfYLength',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="8"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(7)
|
||||
ang = await u.getAngle(`[data-overlay-index="${7}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x + 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted:
|
||||
'angledLineOfXLength({ angle: 181 + 0, length: 23.14 }, %)',
|
||||
stdLibFnName: 'angledLineOfXLength',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="7"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(6)
|
||||
ang = await u.getAngle(`[data-overlay-index="${6}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y + 10 },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'yLine(21.14 + 0, %)',
|
||||
stdLibFnName: 'yLine',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="6"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(5)
|
||||
ang = await u.getAngle(`[data-overlay-index="${5}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x - 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'xLine(26.04, %)',
|
||||
stdLibFnName: 'xLine',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="5"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(4)
|
||||
ang = await u.getAngle(`[data-overlay-index="${4}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y - 10 },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: "yLineTo(-10.77, %, 'a')",
|
||||
stdLibFnName: 'yLineTo',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="4"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(3)
|
||||
ang = await u.getAngle(`[data-overlay-index="${3}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x + 10, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'xLineTo(9 - 5, %)',
|
||||
stdLibFnName: 'xLineTo',
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="3"]',
|
||||
})
|
||||
|
||||
// Not sure why this is diff. from the others - Kurt, ideas?
|
||||
segmentToDelete = await getOverlayByIndex(2)
|
||||
const hoverPos = { x: segmentToDelete.x - 10, y: segmentToDelete.y + 10 }
|
||||
ang = await u.getAngle(`[data-overlay-index="${2}"]`)
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
const [x, y] = [
|
||||
Math.cos((45 * Math.PI) / 180) * 45,
|
||||
Math.sin((45 * Math.PI) / 180) * 45,
|
||||
]
|
||||
|
||||
await page.mouse.move(hoverPos.x + x, hoverPos.y + y)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 })
|
||||
const hoverPos = { x: segmentToDelete.x, y: segmentToDelete.y }
|
||||
await page.mouse.move(0, 0)
|
||||
await page.waitForTimeout(1000)
|
||||
let x = 0,
|
||||
y = 0
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y)
|
||||
await wiggleMove(
|
||||
page,
|
||||
hoverPos.x,
|
||||
hoverPos.y,
|
||||
20,
|
||||
30,
|
||||
ang,
|
||||
10,
|
||||
5,
|
||||
'[data-overlay-toolbar-index="2"]'
|
||||
)
|
||||
|
||||
const codeToBeDeleted = 'lineTo([33, 11.5 + 0], %)'
|
||||
await expect(page.locator('.cm-content')).toContainText(codeToBeDeleted)
|
||||
|
||||
@ -5155,19 +5255,22 @@ const part001 = startSketchOn('XZ')
|
||||
)
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(1)
|
||||
ang = await u.getAngle(`[data-overlay-index="${1}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x - 20, y: segmentToDelete.y },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'angledLine({ angle: 3 + 0, length: 32 + 0 }, %)',
|
||||
stdLibFnName: 'angledLine',
|
||||
ang: 135,
|
||||
ang: ang + 180,
|
||||
locator: '[data-overlay-toolbar-index="1"]',
|
||||
})
|
||||
|
||||
segmentToDelete = await getOverlayByIndex(0)
|
||||
ang = await u.getAngle(`[data-overlay-index="${0}"]`)
|
||||
await deleteSegmentSequence({
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y - 20 },
|
||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||
codeToBeDeleted: 'line([0.5, -14 + 0], %)',
|
||||
stdLibFnName: 'line',
|
||||
ang: -45,
|
||||
ang: ang + 180,
|
||||
})
|
||||
|
||||
await page.waitForTimeout(200)
|
||||
@ -5381,24 +5484,30 @@ ${extraLine ? "const myVar = segLen('seg01', part001)" : ''}`
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
|
||||
const segmentToDelete = await u.getBoundingBox(
|
||||
`[data-overlay-index="0"]`
|
||||
)
|
||||
|
||||
const isYLine = before.toLowerCase().includes('yline')
|
||||
const hoverPos = {
|
||||
x: segmentToDelete.x + (isYLine ? 0 : -20),
|
||||
y: segmentToDelete.y + (isYLine ? -20 : 0),
|
||||
}
|
||||
await expect(page.getByText('Added variable')).not.toBeVisible()
|
||||
const ang = isYLine ? 45 : -45
|
||||
const [x, y] = [
|
||||
Math.cos((ang * Math.PI) / 180) * 45,
|
||||
Math.sin((ang * Math.PI) / 180) * 45,
|
||||
]
|
||||
|
||||
await page.mouse.move(hoverPos.x + x, hoverPos.y + y)
|
||||
await page.mouse.move(hoverPos.x, hoverPos.y, { steps: 5 })
|
||||
const hoverPos = await u.getBoundingBox(`[data-overlay-index="0"]`)
|
||||
let ang = await u.getAngle(`[data-overlay-index="${0}"]`)
|
||||
ang += 180
|
||||
|
||||
await page.mouse.move(0, 0)
|
||||
await page.waitForTimeout(1000)
|
||||
let x = 0,
|
||||
y = 0
|
||||
x = hoverPos.x + Math.cos(ang * deg) * 32
|
||||
y = hoverPos.y - Math.sin(ang * deg) * 32
|
||||
await page.mouse.move(x, y)
|
||||
await wiggleMove(
|
||||
page,
|
||||
x,
|
||||
y,
|
||||
20,
|
||||
30,
|
||||
ang,
|
||||
10,
|
||||
5,
|
||||
'[data-overlay-toolbar-index="0"]'
|
||||
)
|
||||
|
||||
await expect(page.locator('.cm-content')).toContainText(before)
|
||||
|
||||
|
@ -114,19 +114,45 @@ export const wiggleMove = async (
|
||||
dist: number,
|
||||
ang: number,
|
||||
amplitude: number,
|
||||
freq: number
|
||||
freq: number,
|
||||
locator?: string
|
||||
) => {
|
||||
const tau = Math.PI * 2
|
||||
const deg = tau / 360
|
||||
const step = dist / steps
|
||||
for (let i = 0, j = 0; i < dist; i += step, j += 1) {
|
||||
if (locator) {
|
||||
const isElVis = await page.locator(locator).isVisible()
|
||||
if (isElVis) return
|
||||
}
|
||||
const [x1, y1] = [0, Math.sin((tau / steps) * j * freq) * amplitude]
|
||||
const [x2, y2] = [
|
||||
Math.cos(-ang * deg) * i - Math.sin(-ang * deg) * y1,
|
||||
Math.sin(-ang * deg) * i + Math.cos(-ang * deg) * y1,
|
||||
]
|
||||
const [xr, yr] = [x2, y2]
|
||||
await page.mouse.move(x + xr, y + yr, { steps: 2 })
|
||||
await page.mouse.move(x + xr, y + yr, { steps: 5 })
|
||||
}
|
||||
}
|
||||
|
||||
export const circleMove = async (
|
||||
page: any,
|
||||
x: number,
|
||||
y: number,
|
||||
steps: number,
|
||||
diameter: number,
|
||||
locator?: string
|
||||
) => {
|
||||
const tau = Math.PI * 2
|
||||
const step = tau / steps
|
||||
for (let i = 0; i < tau; i += step) {
|
||||
if (locator) {
|
||||
const isElVis = await page.locator(locator).isVisible()
|
||||
if (isElVis) return
|
||||
}
|
||||
const [x1, y1] = [Math.cos(i) * diameter, Math.sin(i) * diameter]
|
||||
const [xr, yr] = [x1, y1]
|
||||
await page.mouse.move(x + xr, y + yr, { steps: 5 })
|
||||
}
|
||||
}
|
||||
|
||||
@ -151,11 +177,11 @@ export const getMovementUtils = (opts: any) => {
|
||||
|
||||
// Make it easier to click around from center ("click [from] zero zero")
|
||||
const click00 = (x: number, y: number) =>
|
||||
opts.page.mouse.click(opts.center.x + x, opts.center.y + y)
|
||||
opts.page.mouse.click(opts.center.x + x, opts.center.y + y, { delay: 100 })
|
||||
|
||||
// Relative clicker, must keep state
|
||||
let last = { x: 0, y: 0 }
|
||||
const click00r = (x?: number, y?: number) => {
|
||||
const click00r = async (x?: number, y?: number) => {
|
||||
// reset relative coordinates when anything is undefined
|
||||
if (x === undefined || y === undefined) {
|
||||
last.x = 0
|
||||
@ -163,12 +189,19 @@ export const getMovementUtils = (opts: any) => {
|
||||
return
|
||||
}
|
||||
|
||||
const ret = click00(last.x + x, last.y + y)
|
||||
await circleMove(
|
||||
opts.page,
|
||||
opts.center.x + last.x + x,
|
||||
opts.center.y + last.y + y,
|
||||
10,
|
||||
10
|
||||
)
|
||||
await click00(last.x + x, last.y + y)
|
||||
last.x += x
|
||||
last.y += y
|
||||
|
||||
// Returns the new absolute coordinate if you need it.
|
||||
return ret.then(() => [last.x, last.y])
|
||||
return [last.x, last.y]
|
||||
}
|
||||
|
||||
return { toSU, click00r }
|
||||
|
@ -37,6 +37,7 @@
|
||||
"crypto-js": "^4.2.0",
|
||||
"debounce-promise": "^3.1.2",
|
||||
"decamelize": "^6.0.0",
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"formik": "^2.4.6",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.4.3",
|
||||
@ -120,7 +121,7 @@
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.24.3",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@playwright/test": "^1.43.1",
|
||||
"@playwright/test": "^1.44.1",
|
||||
"@tauri-apps/cli": "^2.0.0-beta.13",
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/debounce-promise": "^3.1.9",
|
||||
|
@ -18,7 +18,7 @@ export default defineConfig({
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 3 : 0,
|
||||
/* Different amount of parallelism on CI and local. */
|
||||
workers: process.env.CI ? 1 : 1,
|
||||
workers: process.env.CI ? 1 : 4,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
|
@ -44,6 +44,7 @@ import {
|
||||
removeSingleConstraintInfo,
|
||||
} from 'lang/modifyAst'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||
const [isCamMoving, setIsCamMoving] = useState(false)
|
||||
@ -184,11 +185,14 @@ const Overlay = ({
|
||||
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
|
||||
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
|
||||
|
||||
const callExpression = getNodeFromPath<CallExpression>(
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
kclManager.ast,
|
||||
overlay.pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (err(_node1)) return
|
||||
const callExpression = _node1.node
|
||||
|
||||
const constraints = getConstraintInfo(
|
||||
callExpression,
|
||||
codeManager.code,
|
||||
@ -219,6 +223,7 @@ const Overlay = ({
|
||||
data-testid="segment-overlay"
|
||||
data-path-to-node={pathToNodeString}
|
||||
data-overlay-index={overlayIndex}
|
||||
data-overlay-visible={shouldShow}
|
||||
data-overlay-angle={overlay.angle}
|
||||
className="pointer-events-auto absolute w-0 h-0"
|
||||
style={{
|
||||
@ -227,6 +232,7 @@ const Overlay = ({
|
||||
></div>
|
||||
{shouldShow && (
|
||||
<div
|
||||
data-overlay-toolbar-index={overlayIndex}
|
||||
className={`px-0 pointer-events-auto absolute flex gap-1`}
|
||||
style={{
|
||||
transform: `translate3d(calc(${
|
||||
@ -352,7 +358,7 @@ export async function deleteSegment({
|
||||
pathToNode: PathToNode
|
||||
sketchDetails: SketchDetails | null
|
||||
}) {
|
||||
let modifiedAst: Program = kclManager.ast
|
||||
let modifiedAst: Program | Error = kclManager.ast
|
||||
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
|
||||
|
||||
const shouldContinueSegDelete = dependentRanges.length
|
||||
@ -363,6 +369,7 @@ export async function deleteSegment({
|
||||
: true
|
||||
|
||||
if (!shouldContinueSegDelete) return
|
||||
|
||||
modifiedAst = deleteSegmentFromPipeExpression(
|
||||
dependentRanges,
|
||||
modifiedAst,
|
||||
@ -370,9 +377,12 @@ export async function deleteSegment({
|
||||
codeManager.code,
|
||||
pathToNode
|
||||
)
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
modifiedAst = parse(newCode)
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
useFakeExecutor: true,
|
||||
@ -384,13 +394,15 @@ export async function deleteSegment({
|
||||
}
|
||||
|
||||
if (!sketchDetails) return
|
||||
sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
sketchDetails.sketchPathToNode,
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
pathToNode,
|
||||
modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
|
||||
// Now 'Set sketchDetails' is called with the modified pathToNode
|
||||
}
|
||||
|
||||
const SegmentMenu = ({
|
||||
@ -535,10 +547,13 @@ const ConstraintSymbol = ({
|
||||
const implicitDesc =
|
||||
varNameMap[_type as LineInputsType]?.implicitConstraintDesc
|
||||
|
||||
const node = useMemo(
|
||||
() => getNodeFromPath<Value>(kclManager.ast, pathToNode).node,
|
||||
const _node = useMemo(
|
||||
() => getNodeFromPath<Value>(kclManager.ast, pathToNode),
|
||||
[kclManager.ast, pathToNode]
|
||||
)
|
||||
if (err(_node)) return
|
||||
const node = _node.node
|
||||
|
||||
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
|
||||
|
||||
if (_type === 'intersectionTag') return null
|
||||
@ -576,12 +591,17 @@ const ConstraintSymbol = ({
|
||||
})
|
||||
} else if (isConstrained) {
|
||||
try {
|
||||
const shallowPath = getNodeFromPath<CallExpression>(
|
||||
parse(recast(kclManager.ast)),
|
||||
const parsed = parse(recast(kclManager.ast))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
parsed,
|
||||
pathToNode,
|
||||
'CallExpression',
|
||||
true
|
||||
).shallowPath
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const shallowPath = _node1.shallowPath
|
||||
|
||||
const input = makeRemoveSingleConstraintInput(
|
||||
argPosition,
|
||||
shallowPath
|
||||
|
@ -101,6 +101,7 @@ import {
|
||||
updateRectangleSketch,
|
||||
} from 'lib/rectangleTool'
|
||||
import { getThemeColorForThreeJs } from 'lib/theme'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||
|
||||
@ -318,8 +319,14 @@ export class SceneEntities {
|
||||
}> {
|
||||
this.createIntersectionPlane()
|
||||
|
||||
const prepared = this.prepareTruncatedMemoryAndAst(
|
||||
sketchPathToNode || [],
|
||||
maybeModdedAst
|
||||
)
|
||||
if (err(prepared)) return Promise.reject(prepared)
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
this.prepareTruncatedMemoryAndAst(sketchPathToNode || [], maybeModdedAst)
|
||||
prepared
|
||||
|
||||
const { programMemory } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
@ -331,6 +338,8 @@ export class SceneEntities {
|
||||
ast: maybeModdedAst,
|
||||
programMemory,
|
||||
})
|
||||
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
||||
|
||||
if (!Array.isArray(sketchGroup?.value))
|
||||
return {
|
||||
truncatedAst,
|
||||
@ -406,11 +415,14 @@ export class SceneEntities {
|
||||
)
|
||||
|
||||
let seg
|
||||
const callExpName = getNodeFromPath<CallExpression>(
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
maybeModdedAst,
|
||||
segPathToNode,
|
||||
'CallExpression'
|
||||
)?.node?.callee?.name
|
||||
)
|
||||
if (err(_node1)) return
|
||||
const callExpName = _node1.node?.callee?.name
|
||||
|
||||
if (segment.type === 'TangentialArcTo') {
|
||||
seg = tangentialArcToSegment({
|
||||
prevSegment: sketchGroup.value[index - 1],
|
||||
@ -489,12 +501,14 @@ export class SceneEntities {
|
||||
}
|
||||
updateAstAndRejigSketch = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
modifiedAst: Program,
|
||||
modifiedAst: Program | Error,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number]
|
||||
) => {
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
if (err(modifiedAst)) return modifiedAst
|
||||
|
||||
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
sceneInfra.resetMouseListeners()
|
||||
await this.setupSketch({
|
||||
@ -502,7 +516,7 @@ export class SceneEntities {
|
||||
forward,
|
||||
up,
|
||||
position: origin,
|
||||
maybeModdedAst: kclManager.ast,
|
||||
maybeModdedAst: nextAst.newAst,
|
||||
})
|
||||
this.setupSketchIdleCallbacks({
|
||||
forward,
|
||||
@ -510,6 +524,7 @@ export class SceneEntities {
|
||||
position: origin,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
return nextAst
|
||||
}
|
||||
setUpDraftSegment = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
@ -521,12 +536,15 @@ export class SceneEntities {
|
||||
) => {
|
||||
const _ast = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
|
||||
const variableDeclarationName =
|
||||
getNodeFromPath<VariableDeclaration>(
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.id?.name || ''
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
|
||||
const sg = kclManager.programMemory.root[
|
||||
variableDeclarationName
|
||||
] as SketchGroup
|
||||
@ -542,7 +560,9 @@ export class SceneEntities {
|
||||
fnName: segmentName,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
if (trap(mod)) return Promise.reject(mod)
|
||||
const modifiedAst = parse(recast(mod.modifiedAst))
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
|
||||
const draftExpressionsIndices = { start: index, end: index }
|
||||
|
||||
@ -593,14 +613,16 @@ export class SceneEntities {
|
||||
),
|
||||
],
|
||||
})
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
modifiedAst = addCloseToPipe({
|
||||
node: modifiedAst,
|
||||
programMemory: kclManager.programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
} else if (intersection2d) {
|
||||
const lastSegment = sketchGroup.value.slice(-1)[0]
|
||||
modifiedAst = addNewSketchLn({
|
||||
const tmp = addNewSketchLn({
|
||||
node: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
to: [intersection2d.x, intersection2d.y],
|
||||
@ -610,7 +632,10 @@ export class SceneEntities {
|
||||
? 'tangentialArcTo'
|
||||
: 'line',
|
||||
pathToNode: sketchPathToNode,
|
||||
}).modifiedAst
|
||||
})
|
||||
if (trap(tmp)) return Promise.reject(tmp)
|
||||
modifiedAst = tmp.modifiedAst
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
} else {
|
||||
// return early as we didn't modify the ast
|
||||
return
|
||||
@ -669,12 +694,14 @@ export class SceneEntities {
|
||||
) => {
|
||||
let _ast = JSON.parse(JSON.stringify(kclManager.ast))
|
||||
|
||||
const variableDeclarationName =
|
||||
getNodeFromPath<VariableDeclaration>(
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.id?.name || ''
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -682,11 +709,13 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
const startSketchOn = getNodeFromPath<VariableDeclaration>(
|
||||
const _node2 = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations
|
||||
)
|
||||
if (trap(_node2)) return Promise.reject(_node2)
|
||||
const startSketchOn = _node2.node?.declarations
|
||||
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
@ -711,11 +740,13 @@ export class SceneEntities {
|
||||
const pathToNodeTwo = JSON.parse(JSON.stringify(sketchPathToNode))
|
||||
pathToNodeTwo[1][0] = 0
|
||||
|
||||
const sketchInit = getNodeFromPath<VariableDeclaration>(
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
truncatedAst,
|
||||
pathToNodeTwo || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.init
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -757,11 +788,13 @@ export class SceneEntities {
|
||||
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
||||
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
||||
|
||||
const sketchInit = getNodeFromPath<VariableDeclaration>(
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.init
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||
@ -857,6 +890,7 @@ export class SceneEntities {
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (trap(sketchGroup)) return
|
||||
|
||||
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
|
||||
if (addingNewSegmentStatus === 'nothing') {
|
||||
@ -874,6 +908,8 @@ export class SceneEntities {
|
||||
spliceBetween: true,
|
||||
})
|
||||
addingNewSegmentStatus = 'pending'
|
||||
if (trap(mod)) return
|
||||
|
||||
await kclManager.executeAstMock(mod.modifiedAst)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
this.setupSketch({
|
||||
@ -982,17 +1018,22 @@ export class SceneEntities {
|
||||
const to: [number, number] = [intersection2d.x, intersection2d.y]
|
||||
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
|
||||
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
const _node = getNodeFromPath<CallExpression>(
|
||||
modifiedAst,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const node = _node.node
|
||||
|
||||
if (node.type !== 'CallExpression') return
|
||||
|
||||
let modded: {
|
||||
let modded:
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
| Error
|
||||
if (group.name === PROFILE_START) {
|
||||
modded = updateStartProfileAtArgs({
|
||||
node: modifiedAst,
|
||||
@ -1010,14 +1051,18 @@ export class SceneEntities {
|
||||
from
|
||||
)
|
||||
}
|
||||
if (trap(modded)) return
|
||||
|
||||
modifiedAst = modded.modifiedAst
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
draftInfo
|
||||
const info = draftInfo
|
||||
? draftInfo
|
||||
: this.prepareTruncatedMemoryAndAst(sketchPathToNode || [])
|
||||
: this.prepareTruncatedMemoryAndAst(pathToNode || [])
|
||||
if (trap(info, { suppress: true })) return
|
||||
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
|
||||
info
|
||||
;(async () => {
|
||||
const code = recast(modifiedAst)
|
||||
if (trap(code)) return
|
||||
if (!draftInfo)
|
||||
// don't want to mod the user's code yet as they have't committed to the change yet
|
||||
// plus this would be the truncated ast being recast, it would be wrong
|
||||
@ -1544,11 +1589,14 @@ export class SceneEntities {
|
||||
])
|
||||
if (parent?.userData?.pathToNode) {
|
||||
const updatedAst = parse(recast(kclManager.ast))
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
if (trap(updatedAst)) return
|
||||
const _node = getNodeFromPath<CallExpression>(
|
||||
updatedAst,
|
||||
parent.userData.pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (trap(_node, { suppress: true })) return
|
||||
const node = _node.node
|
||||
editorManager.setHighlightRange([node.start, node.end])
|
||||
const yellow = 0xffff00
|
||||
colorSegment(selected, yellow)
|
||||
@ -1663,20 +1711,23 @@ function prepareTruncatedMemoryAndAst(
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory,
|
||||
draftSegment?: DraftSegment
|
||||
): {
|
||||
):
|
||||
| {
|
||||
truncatedAst: Program
|
||||
programMemoryOverride: ProgramMemory
|
||||
variableDeclarationName: string
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
|
||||
const _ast = JSON.parse(JSON.stringify(ast))
|
||||
|
||||
const variableDeclarationName =
|
||||
getNodeFromPath<VariableDeclaration>(
|
||||
const _node = getNodeFromPath<VariableDeclaration>(
|
||||
_ast,
|
||||
sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.id?.name || ''
|
||||
)
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||
const lastSeg = (
|
||||
programMemory.root[variableDeclarationName] as SketchGroup
|
||||
).value.slice(-1)[0]
|
||||
@ -1704,6 +1755,8 @@ function prepareTruncatedMemoryAndAst(
|
||||
// update source ranges to section we just added.
|
||||
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketchGroup segments
|
||||
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
|
||||
|
||||
const lastPipeItem = (
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
||||
.declarations[0].init as PipeExpression
|
||||
@ -1727,7 +1780,9 @@ function prepareTruncatedMemoryAndAst(
|
||||
..._ast,
|
||||
body: [JSON.parse(JSON.stringify(_ast.body[bodyIndex]))],
|
||||
}
|
||||
const programMemoryOverride: ProgramMemory = programMemoryInit()
|
||||
const programMemoryOverride = programMemoryInit()
|
||||
if (err(programMemoryOverride)) return programMemoryOverride
|
||||
|
||||
for (let i = 0; i < bodyIndex; i++) {
|
||||
const node = _ast.body[i]
|
||||
if (node.type !== 'VariableDeclaration') {
|
||||
@ -1768,12 +1823,14 @@ export function sketchGroupFromPathToNode({
|
||||
pathToNode: PathToNode
|
||||
ast: Program
|
||||
programMemory: ProgramMemory
|
||||
}): SketchGroup {
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
}): SketchGroup | Error {
|
||||
const _varDec = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
)
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const result = programMemory.root[varDec?.id?.name || '']
|
||||
if (result?.type === 'ExtrudeGroup') {
|
||||
return result.sketchGroup
|
||||
@ -1808,13 +1865,15 @@ function colorSegment(object: any, color: number) {
|
||||
export function getSketchQuaternion(
|
||||
sketchPathToNode: PathToNode,
|
||||
sketchNormalBackUp: [number, number, number] | null
|
||||
): Quaternion {
|
||||
): Quaternion | Error {
|
||||
const sketchGroup = sketchGroupFromPathToNode({
|
||||
pathToNode: sketchPathToNode,
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(sketchGroup)) return sketchGroup
|
||||
const zAxis = sketchGroup?.on.zAxis || sketchNormalBackUp
|
||||
|
||||
return getQuaternionFromZAxis(massageFormats(zAxis))
|
||||
}
|
||||
export async function getSketchOrientationDetails(
|
||||
@ -1828,6 +1887,8 @@ export async function getSketchOrientationDetails(
|
||||
ast: kclManager.ast,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(sketchGroup)) return Promise.reject(sketchGroup)
|
||||
|
||||
if (sketchGroup.on.type === 'plane') {
|
||||
const zAxis = sketchGroup?.on.zAxis
|
||||
return {
|
||||
@ -1845,11 +1906,12 @@ export async function getSketchOrientationDetails(
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (sketchGroup.on.type === 'face') {
|
||||
const faceInfo = await getFaceDetails(sketchGroup.on.id)
|
||||
|
||||
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
|
||||
throw new Error('faceInfo')
|
||||
return Promise.reject('face info')
|
||||
const { z_axis, y_axis, origin } = faceInfo
|
||||
const quaternion = quaternionFromUpNForward(
|
||||
new Vector3(y_axis.x, y_axis.y, y_axis.z),
|
||||
@ -1866,7 +1928,7 @@ export async function getSketchOrientationDetails(
|
||||
},
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
return Promise.reject(
|
||||
'sketchGroup.on.type not recognized, has a new type been added?'
|
||||
)
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { editorManager, kclManager } from 'lib/singletons'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
|
||||
export function AstExplorer() {
|
||||
const { context } = useModelingContext()
|
||||
@ -10,9 +11,12 @@ export function AstExplorer() {
|
||||
kclManager.ast,
|
||||
context.selectionRanges.codeBasedSelections?.[0]?.range
|
||||
)
|
||||
const node = getNodeFromPath(kclManager.ast, pathToNode).node
|
||||
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
||||
|
||||
const _node = getNodeFromPath(kclManager.ast, pathToNode)
|
||||
if (trap(_node)) return
|
||||
const node = _node
|
||||
|
||||
return (
|
||||
<div id="ast-explorer" className="relative">
|
||||
<div className="">
|
||||
|
@ -11,6 +11,7 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import { executeAst } from 'useStore'
|
||||
import { trap } from 'lib/trap'
|
||||
|
||||
export const AvailableVars = ({
|
||||
onVarClick,
|
||||
@ -141,6 +142,7 @@ export function useCalc({
|
||||
try {
|
||||
const code = `const __result__ = ${value}`
|
||||
const ast = parse(code)
|
||||
if (trap(ast)) return
|
||||
const _programMem: any = { root: {}, return: null }
|
||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||
|
@ -24,7 +24,7 @@ export const CommandBar = () => {
|
||||
}, [pathname])
|
||||
|
||||
// Hook up keyboard shortcuts
|
||||
useHotkeyWrapper(['mod+k', 'mod+/'], () => {
|
||||
useHotkeyWrapper(['mod+k', 'ctrl+c'], () => {
|
||||
if (commandBarState.context.commands.length === 0) return
|
||||
if (commandBarState.matches('Closed')) {
|
||||
commandBarSend({ type: 'Open' })
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { LanguageServerClient } from 'editor/plugins/lsp'
|
||||
import type * as LSP from 'vscode-languageserver-protocol'
|
||||
import React, { createContext, useMemo, useEffect, useContext } from 'react'
|
||||
import React, {
|
||||
createContext,
|
||||
useMemo,
|
||||
useEffect,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { FromServer, IntoServer } from 'editor/plugins/lsp/codec'
|
||||
import Client from '../editor/plugins/lsp/client'
|
||||
import { TEST, VITE_KC_API_BASE_URL } from 'env'
|
||||
@ -24,6 +30,7 @@ import { wasmUrl } from 'lang/wasm'
|
||||
import { PROJECT_ENTRYPOINT } from 'lib/constants'
|
||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
|
||||
return []
|
||||
@ -76,6 +83,8 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
setIsCopilotLspServerReady: s.setIsCopilotLspServerReady,
|
||||
isStreamReady: s.isStreamReady,
|
||||
}))
|
||||
const [isLspReady, setIsLspReady] = useState(false)
|
||||
const [isCopilotReady, setIsCopilotReady] = useState(false)
|
||||
|
||||
const {
|
||||
auth,
|
||||
@ -111,14 +120,17 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
eventData: initEvent,
|
||||
})
|
||||
lspWorker.onmessage = function (e) {
|
||||
if (err(fromServer)) return
|
||||
fromServer.add(e.data)
|
||||
}
|
||||
|
||||
const intoServer: IntoServer = new IntoServer(LspWorker.Kcl, lspWorker)
|
||||
const fromServer: FromServer = FromServer.create()
|
||||
const fromServer: FromServer | Error = FromServer.create()
|
||||
if (err(fromServer)) return { lspClient: null }
|
||||
|
||||
const client = new Client(fromServer, intoServer)
|
||||
|
||||
setIsKclLspServerReady(true)
|
||||
setIsLspReady(true)
|
||||
|
||||
const lspClient = new LanguageServerClient({ client, name: LspWorker.Kcl })
|
||||
return { lspClient }
|
||||
@ -185,14 +197,17 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
eventData: initEvent,
|
||||
})
|
||||
lspWorker.onmessage = function (e) {
|
||||
if (err(fromServer)) return
|
||||
fromServer.add(e.data)
|
||||
}
|
||||
|
||||
const intoServer: IntoServer = new IntoServer(LspWorker.Copilot, lspWorker)
|
||||
const fromServer: FromServer = FromServer.create()
|
||||
const fromServer: FromServer | Error = FromServer.create()
|
||||
if (err(fromServer)) return { lspClient: null }
|
||||
|
||||
const client = new Client(fromServer, intoServer)
|
||||
|
||||
setIsCopilotLspServerReady(true)
|
||||
setIsCopilotReady(true)
|
||||
|
||||
const lspClient = new LanguageServerClient({
|
||||
client,
|
||||
@ -230,6 +245,13 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
lspClients.push(copilotLspClient)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setIsKclLspServerReady(isLspReady)
|
||||
}, [isLspReady])
|
||||
useEffect(() => {
|
||||
setIsCopilotLspServerReady(isCopilotReady)
|
||||
}, [isCopilotReady])
|
||||
|
||||
const onProjectClose = (
|
||||
file: FileEntry | null,
|
||||
projectPath: string | null,
|
||||
|
@ -29,7 +29,6 @@ import {
|
||||
applyConstraintAngleBetween,
|
||||
} from './Toolbar/SetAngleBetween'
|
||||
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||
import { pathMapToSelections } from 'lang/util'
|
||||
import { useStore } from 'useStore'
|
||||
import {
|
||||
Selections,
|
||||
@ -37,6 +36,7 @@ import {
|
||||
handleSelectionBatch,
|
||||
isSelectionLastLine,
|
||||
isSketchPipe,
|
||||
updateSelections,
|
||||
} from 'lib/selections'
|
||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||
@ -77,6 +77,7 @@ import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||
import { getVarNameModal } from 'hooks/useToolbarGuards'
|
||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { err, trap } from 'lib/trap'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -459,12 +460,17 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
return canExtrudeSelection(selectionRanges)
|
||||
},
|
||||
'Sketch is empty': ({ sketchDetails }) =>
|
||||
getNodeFromPath<VariableDeclaration>(
|
||||
'Sketch is empty': ({ sketchDetails }) => {
|
||||
const node = getNodeFromPath<VariableDeclaration>(
|
||||
kclManager.ast,
|
||||
sketchDetails?.sketchPathToNode || [],
|
||||
'VariableDeclaration'
|
||||
)?.node?.declarations?.[0]?.init.type !== 'PipeExpression',
|
||||
)
|
||||
// This should not be returning false, and it should be caught
|
||||
// but we need to simulate old behavior to move on.
|
||||
if (err(node)) return false
|
||||
return node.node?.declarations?.[0]?.init.type !== 'PipeExpression'
|
||||
},
|
||||
'Selection is on face': ({ selectionRanges }, { data }) => {
|
||||
if (data?.forceNewSketch) return false
|
||||
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
|
||||
@ -507,14 +513,16 @@ export const ModelingMachineProvider = ({
|
||||
},
|
||||
'animate-to-face': async (_, { data }) => {
|
||||
if (data.type === 'extrudeFace') {
|
||||
const { modifiedAst, pathToNode: pathToNewSketchNode } =
|
||||
sketchOnExtrudedFace(
|
||||
const sketched = sketchOnExtrudedFace(
|
||||
kclManager.ast,
|
||||
data.sketchPathToNode,
|
||||
data.extrudePathToNode,
|
||||
kclManager.programMemory,
|
||||
data.cap
|
||||
)
|
||||
if (trap(sketched)) return Promise.reject(sketched)
|
||||
const { modifiedAst, pathToNode: pathToNewSketchNode } = sketched
|
||||
|
||||
await kclManager.executeAstMock(modifiedAst)
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
@ -535,10 +543,12 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
await kclManager.updateAst(modifiedAst, false)
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
engineCommandManager,
|
||||
data.planeId
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
zAxis: data.zAxis,
|
||||
@ -576,25 +586,29 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -608,25 +622,29 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -634,9 +652,11 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
sketchDetails,
|
||||
}): Promise<SetSelections> => {
|
||||
const { modifiedAst, pathToNodeMap } = await (angleBetweenInfo({
|
||||
const info = angleBetweenInfo({
|
||||
selectionRanges,
|
||||
}).enabled
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const { modifiedAst, pathToNodeMap } = await (info.enabled
|
||||
? applyConstraintAngleBetween({
|
||||
selectionRanges,
|
||||
})
|
||||
@ -645,25 +665,31 @@ export const ModelingMachineProvider = ({
|
||||
angleOrLength: 'setAngle',
|
||||
}))
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
|
||||
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
_modifiedAst,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -674,25 +700,29 @@ export const ModelingMachineProvider = ({
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
await applyConstraintAngleLength({ selectionRanges })
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -706,25 +736,29 @@ export const ModelingMachineProvider = ({
|
||||
}
|
||||
)
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -738,25 +772,29 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
@ -770,48 +808,77 @@ export const ModelingMachineProvider = ({
|
||||
selectionRanges,
|
||||
})
|
||||
const _modifiedAst = parse(recast(modifiedAst))
|
||||
if (!sketchDetails) throw new Error('No sketch details')
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const updatedPathToNode = updatePathToNodeFromMap(
|
||||
sketchDetails.sketchPathToNode,
|
||||
pathToNodeMap
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
updatedPathToNode,
|
||||
_modifiedAst,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
pathToNodeMap,
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection: pathMapToSelections(
|
||||
kclManager.ast,
|
||||
selectionRanges,
|
||||
pathToNodeMap
|
||||
),
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
}
|
||||
},
|
||||
'Get convert to variable info': async ({ sketchDetails }, { data }) => {
|
||||
if (!sketchDetails) return []
|
||||
'Get convert to variable info': async (
|
||||
{ sketchDetails, selectionRanges },
|
||||
{ data }
|
||||
): Promise<SetSelections> => {
|
||||
if (!sketchDetails)
|
||||
return Promise.reject(new Error('No sketch details'))
|
||||
const { variableName } = await getVarNameModal({
|
||||
valueName: data.variableName || 'var',
|
||||
})
|
||||
let parsed = parse(recast(kclManager.ast))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
parsed = parsed as Program
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||
moveValueIntoNewVariablePath(
|
||||
parse(recast(kclManager.ast)),
|
||||
parsed,
|
||||
kclManager.programMemory,
|
||||
data.pathToNode,
|
||||
variableName
|
||||
)
|
||||
await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
parsed = parse(recast(_modifiedAst))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
parsed = parsed as Program
|
||||
if (!pathToReplacedNode)
|
||||
return Promise.reject(new Error('No path to replaced node'))
|
||||
|
||||
const updatedAst = await sceneEntitiesManager.updateAstAndRejigSketch(
|
||||
pathToReplacedNode || [],
|
||||
parse(recast(_modifiedAst)),
|
||||
parsed,
|
||||
sketchDetails.zAxis,
|
||||
sketchDetails.yAxis,
|
||||
sketchDetails.origin
|
||||
)
|
||||
return pathToReplacedNode || sketchDetails.sketchPathToNode
|
||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||
const selection = updateSelections(
|
||||
{ 0: pathToReplacedNode },
|
||||
selectionRanges,
|
||||
updatedAst.newAst
|
||||
)
|
||||
if (err(selection)) return Promise.reject(selection)
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode: pathToReplacedNode,
|
||||
}
|
||||
},
|
||||
},
|
||||
devTools: true,
|
||||
|
@ -1,10 +1,11 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import ReactJson from 'react-json-view'
|
||||
import { useMemo } from 'react'
|
||||
import { ProgramMemory, Path, ExtrudeSurface } from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import toast from 'react-hot-toast'
|
||||
import { trap } from 'lib/trap'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
@ -13,12 +14,12 @@ export const MemoryPaneMenu = () => {
|
||||
|
||||
function copyProgramMemoryToClipboard() {
|
||||
if (globalThis && 'navigator' in globalThis) {
|
||||
try {
|
||||
navigator.clipboard.writeText(JSON.stringify(programMemory))
|
||||
toast.success('Program memory copied to clipboard')
|
||||
} catch (e) {
|
||||
toast.error('Failed to copy program memory to clipboard')
|
||||
}
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify(programMemory))
|
||||
.then(() => toast.success('Program memory copied to clipboard'))
|
||||
.catch((e) =>
|
||||
trap(new Error('Failed to copy program memory to clipboard'))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,28 +10,46 @@ import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function equalAngleInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const varDecs = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const _varDecs = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)?.node
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err2 = _varDecs.find(err)
|
||||
if (err(_err2)) return _err2
|
||||
const varDecs = _varDecs as VariableDeclarator[]
|
||||
|
||||
const primaryLine = varDecs[0]
|
||||
const secondaryVarDecs = varDecs.slice(1)
|
||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||
@ -51,6 +69,7 @@ export function equalAngleInfo({
|
||||
kclManager.ast,
|
||||
'equalAngle'
|
||||
)
|
||||
if (err(transforms)) return transforms
|
||||
|
||||
const enabled =
|
||||
!!secondaryVarDecs.length &&
|
||||
@ -64,16 +83,24 @@ export function applyConstraintEqualAngle({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const { transforms } = equalAngleInfo({ selectionRanges })
|
||||
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
|
||||
}
|
||||
| Error {
|
||||
const info = equalAngleInfo({ selectionRanges })
|
||||
if (err(info)) return info
|
||||
const { transforms } = info
|
||||
|
||||
const transform = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(transform)) return transform
|
||||
const { modifiedAst, pathToNodeMap } = transform
|
||||
|
||||
return { modifiedAst, pathToNodeMap }
|
||||
}
|
||||
|
@ -10,28 +10,46 @@ import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function setEqualLengthInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const varDecs = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const _varDecs = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)?.node
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err2 = _varDecs.find(err)
|
||||
if (err(_err2)) return _err2
|
||||
const varDecs = _varDecs as VariableDeclarator[]
|
||||
|
||||
const primaryLine = varDecs[0]
|
||||
const secondaryVarDecs = varDecs.slice(1)
|
||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||
@ -51,6 +69,7 @@ export function setEqualLengthInfo({
|
||||
kclManager.ast,
|
||||
'equalLength'
|
||||
)
|
||||
if (err(transforms)) return transforms
|
||||
|
||||
const enabled =
|
||||
!!secondaryVarDecs.length &&
|
||||
@ -65,16 +84,24 @@ export function applyConstraintEqualLength({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const { transforms } = setEqualLengthInfo({ selectionRanges })
|
||||
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
|
||||
}
|
||||
| Error {
|
||||
const info = setEqualLengthInfo({ selectionRanges })
|
||||
if (err(info)) return info
|
||||
const { transforms } = info
|
||||
|
||||
const transform = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(transform)) return transform
|
||||
const { modifiedAst, pathToNodeMap } = transform
|
||||
|
||||
return { modifiedAst, pathToNodeMap }
|
||||
}
|
||||
|
@ -9,19 +9,32 @@ import {
|
||||
PathToNodeMap,
|
||||
getTransformInfos,
|
||||
transformAstSketchLines,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function horzVertInfo(
|
||||
selectionRanges: Selections,
|
||||
horOrVert: 'vertical' | 'horizontal'
|
||||
) {
|
||||
):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const isAllTooltips = nodes.every(
|
||||
(node) =>
|
||||
node?.type === 'CallExpression' &&
|
||||
@ -33,6 +46,8 @@ export function horzVertInfo(
|
||||
kclManager.ast,
|
||||
horOrVert
|
||||
)
|
||||
if (err(theTransforms)) return theTransforms
|
||||
|
||||
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
|
||||
return { enabled: _enableHorz, transforms: theTransforms }
|
||||
}
|
||||
@ -42,11 +57,16 @@ export function applyConstraintHorzVert(
|
||||
horOrVert: 'vertical' | 'horizontal',
|
||||
ast: Program,
|
||||
programMemory: ProgramMemory
|
||||
): {
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const transformInfos = horzVertInfo(selectionRanges, horOrVert).transforms
|
||||
}
|
||||
| Error {
|
||||
const info = horzVertInfo(selectionRanges, horOrVert)
|
||||
if (err(info)) return info
|
||||
const transformInfos = info.transforms
|
||||
|
||||
return transformAstSketchLines({
|
||||
ast,
|
||||
selectionRanges,
|
||||
|
@ -11,11 +11,13 @@ import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
@ -23,7 +25,13 @@ export function intersectInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
forcedSelectionRanges: Selections
|
||||
}
|
||||
| Error {
|
||||
if (selectionRanges.codeBasedSelections.length < 2) {
|
||||
return {
|
||||
enabled: false,
|
||||
@ -40,6 +48,8 @@ export function intersectInfo({
|
||||
selectionRanges.codeBasedSelections[0],
|
||||
selectionRanges.codeBasedSelections[1]
|
||||
)
|
||||
if (err(previousSegment)) return previousSegment
|
||||
|
||||
const shouldUsePreviousSegment =
|
||||
selectionRanges.codeBasedSelections?.[1]?.type !== 'line-end' &&
|
||||
previousSegment &&
|
||||
@ -61,17 +71,28 @@ export function intersectInfo({
|
||||
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const varDecs = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const _varDecs = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)?.node
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err2 = _varDecs.find(err)
|
||||
if (err(_err2)) return _err2
|
||||
const varDecs = _varDecs as VariableDeclarator[]
|
||||
|
||||
const primaryLine = varDecs[0]
|
||||
const secondaryVarDecs = varDecs.slice(1)
|
||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||
@ -117,16 +138,22 @@ export async function applyConstraintIntersect({
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
}> {
|
||||
const { transforms, forcedSelectionRanges } = intersectInfo({
|
||||
const info = intersectInfo({
|
||||
selectionRanges,
|
||||
})
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const { transforms, forcedSelectionRanges } = info
|
||||
|
||||
const transform1 = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges: forcedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(transform1)) return Promise.reject(transform1)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transform1
|
||||
|
||||
const {
|
||||
segName,
|
||||
value,
|
||||
@ -156,8 +183,7 @@ export async function applyConstraintIntersect({
|
||||
sign,
|
||||
variableName
|
||||
)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
const transform2 = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: forcedSelectionRanges,
|
||||
transformInfos: transforms,
|
||||
@ -165,6 +191,10 @@ export async function applyConstraintIntersect({
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(transform2)) return Promise.reject(transform2)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transform2
|
||||
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
|
@ -9,8 +9,10 @@ import {
|
||||
PathToNodeMap,
|
||||
getRemoveConstraintsTransforms,
|
||||
transformAstSketchLines,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function removeConstrainingValuesInfo({
|
||||
selectionRanges,
|
||||
@ -18,15 +20,27 @@ export function removeConstrainingValuesInfo({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
pathToNodes?: Array<PathToNode>
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
updatedSelectionRanges: Selections
|
||||
}
|
||||
| Error {
|
||||
const paths =
|
||||
pathToNodes ||
|
||||
selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const updatedSelectionRanges = pathToNodes
|
||||
? {
|
||||
otherSelections: [],
|
||||
@ -44,19 +58,15 @@ export function removeConstrainingValuesInfo({
|
||||
toolTips.includes(node.callee.name as any)
|
||||
)
|
||||
|
||||
try {
|
||||
const transforms = getRemoveConstraintsTransforms(
|
||||
updatedSelectionRanges,
|
||||
kclManager.ast,
|
||||
'removeConstrainingValues'
|
||||
)
|
||||
if (err(transforms)) return transforms
|
||||
|
||||
const enabled = isAllTooltips && transforms.every(Boolean)
|
||||
return { enabled, transforms, updatedSelectionRanges }
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
return { enabled: false, transforms: [], updatedSelectionRanges }
|
||||
}
|
||||
}
|
||||
|
||||
export function applyRemoveConstrainingValues({
|
||||
@ -65,14 +75,19 @@ export function applyRemoveConstrainingValues({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
pathToNodes?: Array<PathToNode>
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const { transforms, updatedSelectionRanges } = removeConstrainingValuesInfo({
|
||||
}
|
||||
| Error {
|
||||
const constraint = removeConstrainingValuesInfo({
|
||||
selectionRanges,
|
||||
pathToNodes,
|
||||
})
|
||||
if (err(constraint)) return constraint
|
||||
const { transforms, updatedSelectionRanges } = constraint
|
||||
|
||||
return transformAstSketchLines({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges: updatedSelectionRanges,
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
getTransformInfos,
|
||||
transformAstSketchLines,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import {
|
||||
SetAngleLengthModal,
|
||||
@ -20,6 +21,7 @@ import {
|
||||
} from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||
|
||||
@ -31,7 +33,12 @@ export function absDistanceInfo({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: Constraint
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const disType =
|
||||
constraint === 'xAbs' || constraint === 'yAbs'
|
||||
? constraint
|
||||
@ -41,10 +48,19 @@ export function absDistanceInfo({
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const isAllTooltips = nodes.every(
|
||||
(node) =>
|
||||
node?.type === 'CallExpression' &&
|
||||
@ -52,6 +68,7 @@ export function absDistanceInfo({
|
||||
)
|
||||
|
||||
const transforms = getTransformInfos(selectionRanges, kclManager.ast, disType)
|
||||
if (err(transforms)) return transforms
|
||||
|
||||
const enableY =
|
||||
disType === 'yAbs' &&
|
||||
@ -81,17 +98,23 @@ export async function applyConstraintAbsDistance({
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
}> {
|
||||
const transformInfos = absDistanceInfo({
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}).transforms
|
||||
const { valueUsedInTransform } = transformAstSketchLines({
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const transformInfos = info.transforms
|
||||
|
||||
const transform1 = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges: selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(transform1)) return Promise.reject(transform1)
|
||||
const { valueUsedInTransform } = transform1
|
||||
|
||||
let forceVal = valueUsedInTransform || 0
|
||||
const { valueNode, variableName, newVariableInsertIndex, sign } =
|
||||
await getModalInfo({
|
||||
@ -104,7 +127,7 @@ export async function applyConstraintAbsDistance({
|
||||
variableName
|
||||
)
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformAstSketchLines({
|
||||
const transform2 = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges: selectionRanges,
|
||||
transformInfos,
|
||||
@ -112,6 +135,9 @@ export async function applyConstraintAbsDistance({
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(transform2)) return Promise.reject(transform2)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
|
||||
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -134,14 +160,18 @@ export function applyConstraintAxisAlign({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: 'snapToYAxis' | 'snapToXAxis'
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const transformInfos = absDistanceInfo({
|
||||
}
|
||||
| Error {
|
||||
const info = absDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}).transforms
|
||||
})
|
||||
if (err(info)) return info
|
||||
const transformInfos = info.transforms
|
||||
|
||||
let finalValue = createIdentifier('ZERO')
|
||||
|
||||
|
@ -10,11 +10,13 @@ import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
@ -22,22 +24,38 @@ export function angleBetweenInfo({
|
||||
selectionRanges,
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const varDecs = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err1 = _nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const _varDecs = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)?.node
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err2 = _varDecs.find(err)
|
||||
if (err(_err2)) return _err2
|
||||
const varDecs = _varDecs as VariableDeclarator[]
|
||||
|
||||
const primaryLine = varDecs[0]
|
||||
const secondaryVarDecs = varDecs.slice(1)
|
||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||
@ -77,14 +95,20 @@ export async function applyConstraintAngleBetween({
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
}> {
|
||||
const transformInfos = angleBetweenInfo({ selectionRanges }).transforms
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
const info = angleBetweenInfo({ selectionRanges })
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const transformInfos = info.transforms
|
||||
|
||||
const transformed1 = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(transformed1)) return Promise.reject(transformed1)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transformed1
|
||||
|
||||
const {
|
||||
segName,
|
||||
value,
|
||||
@ -115,8 +139,7 @@ export async function applyConstraintAngleBetween({
|
||||
variableName
|
||||
)
|
||||
// transform again but forcing certain values
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
const transformed2 = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
@ -124,6 +147,10 @@ export async function applyConstraintAngleBetween({
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(transformed2)) return Promise.reject(transformed2)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||
transformed2
|
||||
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
|
@ -9,12 +9,14 @@ import {
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
getTransformInfos,
|
||||
PathToNodeMap,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import { GetInfoModal, createInfoModal } from '../SetHorVertDistanceModal'
|
||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { cleanErrs, err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createInfoModal(GetInfoModal)
|
||||
|
||||
@ -24,21 +26,38 @@ export function horzVertDistanceInfo({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||
)
|
||||
const varDecs = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<VariableDeclarator>(
|
||||
const _nodes = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<Value>(kclManager.ast, pathToNode)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const [hasErr, , nodesWErrs] = cleanErrs(_nodes)
|
||||
|
||||
if (hasErr) return nodesWErrs[0]
|
||||
const nodes = _nodes as Value[]
|
||||
|
||||
const _varDecs = paths.map((pathToNode) => {
|
||||
const tmp = getNodeFromPath<VariableDeclarator>(
|
||||
kclManager.ast,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)?.node
|
||||
)
|
||||
if (err(tmp)) return tmp
|
||||
return tmp.node
|
||||
})
|
||||
const _err2 = _varDecs.find(err)
|
||||
if (err(_err2)) return _err2
|
||||
const varDecs = _varDecs as VariableDeclarator[]
|
||||
|
||||
const primaryLine = varDecs[0]
|
||||
const secondaryVarDecs = varDecs.slice(1)
|
||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||
@ -82,17 +101,21 @@ export async function applyConstraintHorzVertDistance({
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
}> {
|
||||
const transformInfos = horzVertDistanceInfo({
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}).transforms
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
})
|
||||
if (err(info)) return Promise.reject(info)
|
||||
const transformInfos = info.transforms
|
||||
const transformed = transformSecondarySketchLinesTagFirst({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
})
|
||||
if (err(transformed)) return Promise.reject(transformed)
|
||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||
transformed
|
||||
const {
|
||||
segName,
|
||||
value,
|
||||
@ -120,8 +143,7 @@ export async function applyConstraintHorzVertDistance({
|
||||
? createLiteral(0)
|
||||
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
|
||||
// transform again but forcing certain values
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||
transformSecondarySketchLinesTagFirst({
|
||||
const transformed = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
@ -129,6 +151,9 @@ export async function applyConstraintHorzVertDistance({
|
||||
forceSegName: segName,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
|
||||
if (err(transformed)) return Promise.reject(transformed)
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -155,22 +180,28 @@ export function applyConstraintHorzVertAlign({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
const transformInfos = horzVertDistanceInfo({
|
||||
}
|
||||
| Error {
|
||||
const info = horzVertDistanceInfo({
|
||||
selectionRanges,
|
||||
constraint,
|
||||
}).transforms
|
||||
})
|
||||
if (err(info)) return info
|
||||
const transformInfos = info.transforms
|
||||
let finalValue = createLiteral(0)
|
||||
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
|
||||
const retval = transformSecondarySketchLinesTagFirst({
|
||||
ast: kclManager.ast,
|
||||
selectionRanges,
|
||||
transformInfos,
|
||||
programMemory: kclManager.programMemory,
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(retval)) return retval
|
||||
const { modifiedAst, pathToNodeMap } = retval
|
||||
return {
|
||||
modifiedAst: modifiedAst,
|
||||
pathToNodeMap,
|
||||
|
@ -9,6 +9,7 @@ import {
|
||||
PathToNodeMap,
|
||||
getTransformInfos,
|
||||
transformAstSketchLines,
|
||||
TransformInfo,
|
||||
} from '../../lang/std/sketchcombos'
|
||||
import {
|
||||
SetAngleLengthModal,
|
||||
@ -22,6 +23,7 @@ import {
|
||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||
import { normaliseAngle } from '../../lib/utils'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const getModalInfo = createSetAngleLengthModal(SetAngleLengthModal)
|
||||
|
||||
@ -31,19 +33,29 @@ export function angleLengthInfo({
|
||||
}: {
|
||||
selectionRanges: Selections
|
||||
angleOrLength?: 'setLength' | 'setAngle'
|
||||
}) {
|
||||
}):
|
||||
| {
|
||||
transforms: TransformInfo[]
|
||||
enabled: boolean
|
||||
}
|
||||
| Error {
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(kclManager.ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
|
||||
|
||||
const nodes = paths.map((pathToNode) =>
|
||||
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression')
|
||||
)
|
||||
const isAllTooltips = nodes.every(
|
||||
(node) =>
|
||||
node?.type === 'CallExpression' &&
|
||||
toolTips.includes(node.callee.name as any)
|
||||
const _err1 = nodes.find(err)
|
||||
if (err(_err1)) return _err1
|
||||
|
||||
const isAllTooltips = nodes.every((meta) => {
|
||||
if (err(meta)) return false
|
||||
return (
|
||||
meta.node?.type === 'CallExpression' &&
|
||||
toolTips.includes(meta.node.callee.name as any)
|
||||
)
|
||||
})
|
||||
|
||||
const transforms = getTransformInfos(
|
||||
selectionRanges,
|
||||
@ -67,15 +79,20 @@ export async function applyConstraintAngleLength({
|
||||
modifiedAst: Program
|
||||
pathToNodeMap: PathToNodeMap
|
||||
}> {
|
||||
const { transforms } = angleLengthInfo({ selectionRanges, angleOrLength })
|
||||
const { valueUsedInTransform } = transformAstSketchLines({
|
||||
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
|
||||
if (err(angleLength)) return Promise.reject(angleLength)
|
||||
|
||||
const { transforms } = angleLength
|
||||
const sketched = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
programMemory: kclManager.programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
try {
|
||||
if (err(sketched)) return Promise.reject(sketched)
|
||||
const { valueUsedInTransform } = sketched
|
||||
|
||||
const isReferencingYAxis =
|
||||
selectionRanges.otherSelections.length === 1 &&
|
||||
selectionRanges.otherSelections[0] === 'y-axis'
|
||||
@ -121,8 +138,7 @@ export async function applyConstraintAngleLength({
|
||||
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
|
||||
}
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||
transformAstSketchLines({
|
||||
const retval = transformAstSketchLines({
|
||||
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||
selectionRanges,
|
||||
transformInfos: transforms,
|
||||
@ -130,6 +146,9 @@ export async function applyConstraintAngleLength({
|
||||
referenceSegName: '',
|
||||
forceValueUsedInTransform: finalValue,
|
||||
})
|
||||
if (err(retval)) return Promise.reject(retval)
|
||||
|
||||
const { modifiedAst: _modifiedAst, pathToNodeMap } = retval
|
||||
if (variableName) {
|
||||
const newBody = [..._modifiedAst.body]
|
||||
newBody.splice(
|
||||
@ -147,8 +166,4 @@ export async function applyConstraintAngleLength({
|
||||
modifiedAst: _modifiedAst,
|
||||
pathToNodeMap,
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('error', e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
unregisterServerCapability,
|
||||
} from './server-capability-registration'
|
||||
import { Codec, FromServer, IntoServer } from './codec'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const client_capabilities: LSP.ClientCapabilities = {
|
||||
textDocument: {
|
||||
@ -119,10 +120,12 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
||||
// Register a server capability.
|
||||
params.registrations.forEach(
|
||||
(capabilityRegistration: LSP.Registration) => {
|
||||
this.serverCapabilities = registerServerCapability(
|
||||
const caps = registerServerCapability(
|
||||
this.serverCapabilities,
|
||||
capabilityRegistration
|
||||
)
|
||||
if (err(caps)) return (this.serverCapabilities = {})
|
||||
this.serverCapabilities = caps
|
||||
}
|
||||
)
|
||||
})
|
||||
@ -132,10 +135,12 @@ export default class Client extends jsrpc.JSONRPCServerAndClient {
|
||||
// Unregister a server capability.
|
||||
params.unregisterations.forEach(
|
||||
(capabilityUnregistration: LSP.Unregistration) => {
|
||||
this.serverCapabilities = unregisterServerCapability(
|
||||
const caps = unregisterServerCapability(
|
||||
this.serverCapabilities,
|
||||
capabilityUnregistration
|
||||
)
|
||||
if (err(caps)) return (this.serverCapabilities = {})
|
||||
this.serverCapabilities = caps
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -67,7 +67,13 @@ export interface FromServer extends WritableStream<Uint8Array> {
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
export namespace FromServer {
|
||||
export function create(): FromServer {
|
||||
export function create(): FromServer | Error {
|
||||
// Calls private method .start() which can throw.
|
||||
// This is an odd one of the bunch but try/catch seems most suitable here.
|
||||
try {
|
||||
return new StreamDemuxer()
|
||||
} catch (e: any) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,9 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
||||
|
||||
// try to parse the content-length from the headers
|
||||
const length = parseInt(match[1])
|
||||
if (isNaN(length)) throw new Error('invalid content length')
|
||||
|
||||
if (isNaN(length))
|
||||
return Promise.reject(new Error('invalid content length'))
|
||||
|
||||
// slice the headers since we now have the content length
|
||||
buffer = buffer.slice(match[0].length)
|
||||
|
@ -176,7 +176,7 @@ export class LanguageServerClient {
|
||||
},
|
||||
})
|
||||
|
||||
this.semanticTokens = deserializeTokens(
|
||||
this.semanticTokens = await deserializeTokens(
|
||||
result.data,
|
||||
this.getServerCapabilities().semanticTokensProvider
|
||||
)
|
||||
|
@ -22,16 +22,16 @@ export class SemanticToken {
|
||||
}
|
||||
}
|
||||
|
||||
export function deserializeTokens(
|
||||
export async function deserializeTokens(
|
||||
data: number[],
|
||||
semanticTokensProvider?: LSP.SemanticTokensOptions
|
||||
): SemanticToken[] {
|
||||
): Promise<SemanticToken[]> {
|
||||
if (!semanticTokensProvider) {
|
||||
return []
|
||||
}
|
||||
// Check if data length is divisible by 5
|
||||
if (data.length % 5 !== 0) {
|
||||
throw new Error('Length is not divisible by 5')
|
||||
return Promise.reject(new Error('Length is not divisible by 5'))
|
||||
}
|
||||
|
||||
const tokens = []
|
||||
|
@ -41,7 +41,7 @@ const ServerCapabilitiesProviders: IMethodServerCapabilityProviderDictionary = {
|
||||
function registerServerCapability(
|
||||
serverCapabilities: ServerCapabilities,
|
||||
registration: Registration
|
||||
): ServerCapabilities {
|
||||
): ServerCapabilities | Error {
|
||||
const serverCapabilitiesCopy = JSON.parse(
|
||||
JSON.stringify(serverCapabilities)
|
||||
) as IFlexibleServerCapabilities
|
||||
@ -58,7 +58,7 @@ function registerServerCapability(
|
||||
)
|
||||
}
|
||||
} else {
|
||||
throw new Error('Could not register server capability.')
|
||||
return new Error('Could not register server capability.')
|
||||
}
|
||||
|
||||
return serverCapabilitiesCopy
|
||||
|
@ -14,9 +14,10 @@ import {
|
||||
CopilotWorkerOptions,
|
||||
} from 'editor/plugins/lsp/types'
|
||||
import { EngineCommandManager } from 'lang/std/engineConnection'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const intoServer: IntoServer = new IntoServer()
|
||||
const fromServer: FromServer = FromServer.create()
|
||||
const fromServer: FromServer | Error = FromServer.create()
|
||||
|
||||
// Initialise the wasm module.
|
||||
const initialise = async (wasmUrl: string) => {
|
||||
@ -56,6 +57,7 @@ export async function kclLspRun(
|
||||
}
|
||||
|
||||
onmessage = function (event) {
|
||||
if (err(fromServer)) return
|
||||
const { worker, eventType, eventData }: LspWorkerEvent = event.data
|
||||
|
||||
switch (eventType) {
|
||||
@ -111,6 +113,7 @@ onmessage = function (event) {
|
||||
}
|
||||
|
||||
new Promise<void>(async (resolve) => {
|
||||
if (err(fromServer)) return
|
||||
for await (const requests of fromServer.requests) {
|
||||
const encoded = Codec.encode(requests as jsrpc.JSONRPCRequest)
|
||||
postMessage(encoded)
|
||||
@ -118,6 +121,7 @@ new Promise<void>(async (resolve) => {
|
||||
})
|
||||
|
||||
new Promise<void>(async (resolve) => {
|
||||
if (err(fromServer)) return
|
||||
for await (const notification of fromServer.notifications) {
|
||||
const encoded = Codec.encode(notification as jsrpc.JSONRPCRequest)
|
||||
postMessage(encoded)
|
||||
|
@ -15,6 +15,8 @@ export function useRefreshSettings(routeId: string = paths.INDEX) {
|
||||
const routeData = useRouteLoaderData(routeId) as typeof settings
|
||||
|
||||
if (!ctx) {
|
||||
// Intended to stop the world
|
||||
// eslint-disable-next-line
|
||||
throw new Error(
|
||||
'useRefreshSettings must be used within a SettingsAuthProvider'
|
||||
)
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
createSetVarNameModal,
|
||||
} from 'components/SetVarNameModal'
|
||||
import { editorManager, kclManager } from 'lib/singletons'
|
||||
import { trap } from 'lib/trap'
|
||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -22,10 +23,16 @@ export function useConvertToVariable(range?: SourceRange) {
|
||||
}, [enable])
|
||||
|
||||
useEffect(() => {
|
||||
const { isSafe, value } = isNodeSafeToReplace(
|
||||
parse(recast(ast)),
|
||||
const parsed = parse(recast(ast))
|
||||
if (trap(parsed)) return
|
||||
|
||||
const meta = isNodeSafeToReplace(
|
||||
parsed,
|
||||
range || context.selectionRanges.codeBasedSelections?.[0]?.range || []
|
||||
)
|
||||
if (trap(meta)) return
|
||||
|
||||
const { isSafe, value } = meta
|
||||
const canReplace = isSafe && value.type !== 'Identifier'
|
||||
const isOnlyOneSelection =
|
||||
!!range || context.selectionRanges.codeBasedSelections.length === 1
|
||||
|
@ -3,6 +3,7 @@ import { Selections } from 'lib/selections'
|
||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
||||
import { uuidv4 } from 'lib/utils'
|
||||
import { EngineCommandManager } from './std/engineConnection'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
import { deferExecution } from 'lib/utils'
|
||||
import {
|
||||
@ -161,19 +162,18 @@ export class KclManager {
|
||||
}
|
||||
|
||||
safeParse(code: string): Program | null {
|
||||
try {
|
||||
const ast = parse(code)
|
||||
this.kclErrors = []
|
||||
return ast
|
||||
} catch (e) {
|
||||
console.error('error parsing code', e)
|
||||
if (e instanceof KCLError) {
|
||||
this.kclErrors = [e]
|
||||
if (e.msg === 'file is empty') this.engineCommandManager?.endSession()
|
||||
}
|
||||
if (!err(ast)) return ast
|
||||
const kclerror: KCLError = ast as KCLError
|
||||
|
||||
console.error('error parsing code', kclerror)
|
||||
this.kclErrors = [kclerror]
|
||||
// TODO: re-eval if session should end?
|
||||
if (kclerror.msg === 'file is empty')
|
||||
this.engineCommandManager?.endSession()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async ensureWasmInit() {
|
||||
try {
|
||||
@ -256,6 +256,10 @@ export class KclManager {
|
||||
await this.ensureWasmInit()
|
||||
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) {
|
||||
console.error(newCode)
|
||||
return
|
||||
}
|
||||
const newAst = this.safeParse(newCode)
|
||||
if (!newAst) return
|
||||
codeManager.updateCodeEditor(newCode)
|
||||
@ -281,11 +285,13 @@ export class KclManager {
|
||||
Object.entries(this.engineCommandManager.artifactMap).forEach(
|
||||
([commandId, artifact]) => {
|
||||
if (!artifact.pathToNode) return
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
this.ast,
|
||||
artifact.pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (err(_node1)) return
|
||||
const { node } = _node1
|
||||
if (node.type !== 'CallExpression') return
|
||||
const [oldStart, oldEnd] = artifact.range
|
||||
if (oldStart === 0 && oldEnd === 0) return
|
||||
@ -322,6 +328,10 @@ export class KclManager {
|
||||
return
|
||||
}
|
||||
const code = recast(ast)
|
||||
if (err(code)) {
|
||||
console.error(code)
|
||||
return
|
||||
}
|
||||
if (originalCode === code) return
|
||||
|
||||
// Update the code state and the editor.
|
||||
@ -339,19 +349,31 @@ export class KclManager {
|
||||
optionalParams?: {
|
||||
focusPath?: PathToNode
|
||||
}
|
||||
): Promise<Selections | null> {
|
||||
): Promise<{
|
||||
newAst: Program
|
||||
selections?: Selections
|
||||
}> {
|
||||
const newCode = recast(ast)
|
||||
if (err(newCode)) return Promise.reject(newCode)
|
||||
|
||||
const astWithUpdatedSource = this.safeParse(newCode)
|
||||
if (!astWithUpdatedSource) return null
|
||||
let returnVal: Selections | null = null
|
||||
if (!astWithUpdatedSource) return Promise.reject(new Error('bad ast'))
|
||||
let returnVal: Selections | undefined = undefined
|
||||
|
||||
if (optionalParams?.focusPath) {
|
||||
const { node } = getNodeFromPath<any>(
|
||||
const _node1 = getNodeFromPath<any>(
|
||||
astWithUpdatedSource,
|
||||
optionalParams?.focusPath
|
||||
)
|
||||
if (err(_node1)) return Promise.reject(_node1)
|
||||
const { node } = _node1
|
||||
|
||||
const { start, end } = node
|
||||
if (!start || !end) return null
|
||||
if (!start || !end)
|
||||
return {
|
||||
selections: undefined,
|
||||
newAst: astWithUpdatedSource,
|
||||
}
|
||||
returnVal = {
|
||||
codeBasedSelections: [
|
||||
{
|
||||
@ -377,7 +399,8 @@ export class KclManager {
|
||||
// Execute ast mock will update the code state and editor.
|
||||
await this.executeAstMock(astWithUpdatedSource)
|
||||
}
|
||||
return returnVal
|
||||
|
||||
return { selections: returnVal, newAst: astWithUpdatedSource }
|
||||
}
|
||||
|
||||
get defaultPlanes() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { KCLError } from './errors'
|
||||
import { initPromise, parse } from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -8,6 +9,7 @@ beforeAll(async () => {
|
||||
describe('testing AST', () => {
|
||||
test('5 + 6', () => {
|
||||
const result = parse('5 +6')
|
||||
if (err(result)) throw result
|
||||
delete (result as any).nonCodeMeta
|
||||
expect(result.body).toEqual([
|
||||
{
|
||||
@ -38,7 +40,9 @@ describe('testing AST', () => {
|
||||
])
|
||||
})
|
||||
test('const myVar = 5', () => {
|
||||
const { body } = parse('const myVar = 5')
|
||||
const ast = parse('const myVar = 5')
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -72,7 +76,9 @@ describe('testing AST', () => {
|
||||
const code = `const myVar = 5
|
||||
const newVar = myVar + 1
|
||||
`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -144,9 +150,11 @@ const newVar = myVar + 1
|
||||
|
||||
describe('testing function declaration', () => {
|
||||
test('fn funcN = (a, b) => {return a + b}', () => {
|
||||
const { body } = parse(
|
||||
const ast = parse(
|
||||
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
|
||||
)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
@ -229,7 +237,9 @@ describe('testing function declaration', () => {
|
||||
test('call expression assignment', () => {
|
||||
const code = `fn funcN = (a, b) => { return a + b }
|
||||
const myVar = funcN(1, 2)`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
@ -366,7 +376,9 @@ describe('testing pipe operator special', () => {
|
||||
|> lineTo([1, 1], %)
|
||||
|> rx(45, %)
|
||||
`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
delete (body[0] as any).declarations[0].init.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
@ -566,7 +578,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('pipe operator with binary expression', () => {
|
||||
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
delete (body as any)[0].declarations[0].init.nonCodeMeta
|
||||
expect(body).toEqual([
|
||||
{
|
||||
@ -645,7 +659,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('array expression', () => {
|
||||
let code = `const yo = [1, '2', three, 4 + 5]`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -720,7 +736,9 @@ describe('testing pipe operator special', () => {
|
||||
'const three = 3',
|
||||
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
|
||||
].join('\n')
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -864,7 +882,9 @@ describe('testing pipe operator special', () => {
|
||||
const code = `const yo = {key: {
|
||||
key2: 'value'
|
||||
}}`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -932,7 +952,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('object expression with array ast', () => {
|
||||
const code = `const yo = {key: [1, '2']}`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -996,7 +1018,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('object memberExpression simple', () => {
|
||||
const code = `const prop = yo.one.two`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -1051,7 +1075,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('object memberExpression with square braces', () => {
|
||||
const code = `const prop = yo.one["two"]`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -1107,7 +1133,9 @@ describe('testing pipe operator special', () => {
|
||||
})
|
||||
test('object memberExpression with two square braces literal and identifier', () => {
|
||||
const code = `const prop = yo["one"][two]`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body).toEqual([
|
||||
{
|
||||
type: 'VariableDeclaration',
|
||||
@ -1166,7 +1194,9 @@ describe('testing pipe operator special', () => {
|
||||
describe('nests binary expressions correctly', () => {
|
||||
it('works with the simple case', () => {
|
||||
const code = `const yo = 1 + 2`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body[0]).toEqual({
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
@ -1210,7 +1240,9 @@ describe('nests binary expressions correctly', () => {
|
||||
it('should nest according to precedence with multiply first', () => {
|
||||
// should be binExp { binExp { lit-1 * lit-2 } + lit}
|
||||
const code = `const yo = 1 * 2 + 3`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body[0]).toEqual({
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
@ -1267,7 +1299,9 @@ describe('nests binary expressions correctly', () => {
|
||||
it('should nest according to precedence with sum first', () => {
|
||||
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
|
||||
const code = `const yo = 1 + 2 * 3`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect(body[0]).toEqual({
|
||||
type: 'VariableDeclaration',
|
||||
start: 0,
|
||||
@ -1323,7 +1357,9 @@ describe('nests binary expressions correctly', () => {
|
||||
})
|
||||
it('should nest properly with two operators of equal precedence', () => {
|
||||
const code = `const yo = 1 + 2 - 3`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect((body[0] as any).declarations[0].init).toEqual({
|
||||
type: 'BinaryExpression',
|
||||
start: 11,
|
||||
@ -1360,7 +1396,9 @@ describe('nests binary expressions correctly', () => {
|
||||
})
|
||||
it('should nest properly with two operators of equal (but higher) precedence', () => {
|
||||
const code = `const yo = 1 * 2 / 3`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
expect((body[0] as any).declarations[0].init).toEqual({
|
||||
type: 'BinaryExpression',
|
||||
start: 11,
|
||||
@ -1397,7 +1435,9 @@ describe('nests binary expressions correctly', () => {
|
||||
})
|
||||
it('should nest properly with longer example', () => {
|
||||
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const init = (body[0] as any).declarations[0].init
|
||||
expect(init).toEqual({
|
||||
type: 'BinaryExpression',
|
||||
@ -1460,12 +1500,16 @@ const key = 'c'`
|
||||
value: 'this is a comment',
|
||||
},
|
||||
}
|
||||
const { nonCodeMeta } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { nonCodeMeta } = ast
|
||||
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
|
||||
|
||||
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
||||
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
||||
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
|
||||
const ast2 = parse(codeWithExtraStartWhitespace)
|
||||
if (err(ast2)) throw ast2
|
||||
const { nonCodeMeta: nonCodeMeta2 } = ast2
|
||||
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
|
||||
nonCodeMetaInstance.value
|
||||
)
|
||||
@ -1483,7 +1527,9 @@ const key = 'c'`
|
||||
|> close(%)
|
||||
`
|
||||
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const indexOfSecondLineToExpression = 2
|
||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||
.nonCodeNodes
|
||||
@ -1508,7 +1554,9 @@ const key = 'c'`
|
||||
' |> rx(90, %)',
|
||||
].join('\n')
|
||||
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||
.nonCodeNodes[3][0]
|
||||
expect(sketchNonCodeMeta).toEqual({
|
||||
@ -1527,7 +1575,9 @@ const key = 'c'`
|
||||
describe('test UnaryExpression', () => {
|
||||
it('should parse a unary expression in simple var dec situation', () => {
|
||||
const code = `const myVar = -min(4, 100)`
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||
expect(myVarInit).toEqual({
|
||||
type: 'UnaryExpression',
|
||||
@ -1552,7 +1602,9 @@ describe('test UnaryExpression', () => {
|
||||
describe('testing nested call expressions', () => {
|
||||
it('callExp in a binExp in a callExp', () => {
|
||||
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const myVarInit = (body?.[0] as any).declarations[0]?.init
|
||||
expect(myVarInit).toEqual({
|
||||
type: 'CallExpression',
|
||||
@ -1588,7 +1640,9 @@ describe('testing nested call expressions', () => {
|
||||
describe('should recognise callExpresions in binaryExpressions', () => {
|
||||
const code = "xLineTo(segEndX('seg02', %) + 1, %)"
|
||||
it('should recognise the callExp', () => {
|
||||
const { body } = parse(code)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const { body } = ast
|
||||
const callExpArgs = (body?.[0] as any).expression?.arguments
|
||||
expect(callExpArgs).toEqual([
|
||||
{
|
||||
@ -1623,16 +1677,12 @@ describe('should recognise callExpresions in binaryExpressions', () => {
|
||||
describe('parsing errors', () => {
|
||||
it('should return an error when there is a unexpected closed curly brace', async () => {
|
||||
const code = `const myVar = startSketchAt([}], %)`
|
||||
const result = parse(code)
|
||||
|
||||
let _theError
|
||||
try {
|
||||
let _ = expect(parse(code))
|
||||
} catch (e) {
|
||||
_theError = e
|
||||
}
|
||||
const theError = _theError as any
|
||||
expect(theError).toEqual(
|
||||
new KCLError('syntax', 'Unexpected token', [[27, 28]])
|
||||
)
|
||||
expect(result).toBeInstanceOf(KCLError)
|
||||
const error = result as KCLError
|
||||
expect(error.kind).toBe('syntax')
|
||||
expect(error.msg).toBe('Unexpected token')
|
||||
expect(error.sourceRanges).toEqual([[27, 28]])
|
||||
})
|
||||
})
|
||||
|
@ -4,6 +4,8 @@ describe('test kclErrToDiagnostic', () => {
|
||||
it('converts KCL errors to CodeMirror diagnostics', () => {
|
||||
const errors: KCLError[] = [
|
||||
{
|
||||
name: '',
|
||||
message: '',
|
||||
kind: 'semantic',
|
||||
msg: 'Semantic error',
|
||||
sourceRanges: [
|
||||
@ -12,6 +14,8 @@ describe('test kclErrToDiagnostic', () => {
|
||||
],
|
||||
},
|
||||
{
|
||||
name: '',
|
||||
message: '',
|
||||
kind: 'type',
|
||||
msg: 'Type error',
|
||||
sourceRanges: [
|
||||
|
@ -5,7 +5,7 @@ import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||
import { Text } from '@codemirror/state'
|
||||
|
||||
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
||||
export class KCLError {
|
||||
export class KCLError extends Error {
|
||||
kind: ExtractKind<RustKclError> | 'name'
|
||||
sourceRanges: [number, number][]
|
||||
msg: string
|
||||
@ -14,6 +14,7 @@ export class KCLError {
|
||||
msg: string,
|
||||
sourceRanges: [number, number][]
|
||||
) {
|
||||
super()
|
||||
this.kind = kind
|
||||
this.msg = msg
|
||||
this.sourceRanges = sourceRanges
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -22,8 +23,11 @@ const sk3 = startSketchAt([0, 0])
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const { node } = getNodeFromPath<any>(ast, nodePath)
|
||||
const _node = getNodeFromPath<any>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
const { node } = _node
|
||||
|
||||
expect([node.start, node.end]).toEqual(sourceRange)
|
||||
expect(node.type).toBe('CallExpression')
|
||||
@ -47,8 +51,11 @@ const b1 = cube([0,0], 10)`
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const node = getNodeFromPath<Parameter>(ast, nodePath).node
|
||||
const _node = getNodeFromPath<Parameter>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
const node = _node.node
|
||||
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
@ -81,8 +88,11 @@ const b1 = cube([0,0], 10)`
|
||||
]
|
||||
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const node = getNodeFromPath<Identifier>(ast, nodePath).node
|
||||
const _node = getNodeFromPath<Identifier>(ast, nodePath)
|
||||
if (err(_node)) throw _node
|
||||
const node = _node.node
|
||||
expect(nodePath).toEqual([
|
||||
['body', ''],
|
||||
[0, 'index'],
|
||||
|
@ -18,6 +18,7 @@ import {
|
||||
} from './modifyAst'
|
||||
import { enginelessExecutor } from '../lib/testHelpers'
|
||||
import { findUsesOfTagInPipe, getNodePathFromSourceRange } from './queryAst'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -140,10 +141,14 @@ function giveSketchFnCallTagTestHelper(
|
||||
// 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
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const start = code.indexOf(searchStr)
|
||||
const range: [number, number] = [start, start + searchStr.length]
|
||||
const { modifiedAst, tag, isTagExisting } = giveSketchFnCallTag(ast, range)
|
||||
const sketchRes = giveSketchFnCallTag(ast, range)
|
||||
if (err(sketchRes)) throw sketchRes
|
||||
const { modifiedAst, tag, isTagExisting } = sketchRes
|
||||
const newCode = recast(modifiedAst)
|
||||
if (err(newCode)) throw newCode
|
||||
return { tag, newCode, isTagExisting }
|
||||
}
|
||||
|
||||
@ -211,6 +216,7 @@ const part001 = startSketchOn('XY')
|
||||
const yo2 = hmm([identifierGuy + 5])`
|
||||
it('should move a binary expression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('100 + 100') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
@ -225,6 +231,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
})
|
||||
it('should move a value into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('2.8') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
@ -239,6 +246,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
})
|
||||
it('should move a callExpression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('def(')
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
@ -253,6 +261,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
})
|
||||
it('should move a binary expression with call expression into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('jkl(') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
@ -267,6 +276,7 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
})
|
||||
it('should move a identifier into a new variable', async () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const startIndex = code.indexOf('identifierGuy +') + 1
|
||||
const { modifiedAst } = moveValueIntoNewVariable(
|
||||
@ -290,6 +300,8 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
@ -304,12 +316,15 @@ describe('testing sketchOnExtrudedFace', () => {
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
@ -327,6 +342,7 @@ const sketch001 = startSketchOn(part001, 'seg01')`)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `close(%)`
|
||||
const segmentRange: [number, number] = [
|
||||
@ -341,12 +357,15 @@ const sketch001 = startSketchOn(part001, 'seg01')`)
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
@ -364,6 +383,7 @@ const sketch001 = startSketchOn(part001, 'seg01')`)
|
||||
|> close(%)
|
||||
|> extrude(5 + 7, %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||
const sketchRange: [number, number] = [
|
||||
@ -378,13 +398,16 @@ const sketch001 = startSketchOn(part001, 'seg01')`)
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
const extruded = sketchOnExtrudedFace(
|
||||
ast,
|
||||
sketchPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory,
|
||||
'end'
|
||||
)
|
||||
if (err(extruded)) throw extruded
|
||||
const { modifiedAst } = extruded
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([3.58, 2.06], %)
|
||||
@ -410,6 +433,7 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
|> close(%)
|
||||
const part001 = extrude(5 + 7, sketch001)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||
const segmentRange: [number, number] = [
|
||||
@ -424,13 +448,14 @@ const sketch001 = startSketchOn(part001, 'END')`)
|
||||
]
|
||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
|
||||
const { modifiedAst } = sketchOnExtrudedFace(
|
||||
const updatedAst = sketchOnExtrudedFace(
|
||||
ast,
|
||||
segmentPathToNode,
|
||||
extrudePathToNode,
|
||||
programMemory
|
||||
)
|
||||
const newCode = recast(modifiedAst)
|
||||
if (err(updatedAst)) throw updatedAst
|
||||
const newCode = recast(updatedAst.modifiedAst)
|
||||
expect(newCode).toContain(`const part001 = extrude(5 + 7, sketch001)
|
||||
const sketch002 = startSketchOn(part001, 'seg01')`)
|
||||
})
|
||||
@ -444,6 +469,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
||||
|> line([306.21, 198.85], %, 'a')
|
||||
|> line([306.21, 198.87], %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const lineOfInterest = "line([306.21, 198.85], %, 'a')"
|
||||
const range: [number, number] = [
|
||||
@ -458,6 +484,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
||||
code,
|
||||
pathToNode
|
||||
)
|
||||
if (err(modifiedAst)) throw modifiedAst
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toBe(`const part001 = startSketchOn('-XZ')
|
||||
|> startProfileAt([54.78, -95.91], %)
|
||||
@ -520,6 +547,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
||||
])(`%s`, async (_, line, [replace1, replace2]) => {
|
||||
const code = makeCode(line)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const lineOfInterest = line
|
||||
const range: [number, number] = [
|
||||
@ -535,6 +563,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
||||
code,
|
||||
pathToNode
|
||||
)
|
||||
if (err(modifiedAst)) throw modifiedAst
|
||||
const newCode = recast(modifiedAst)
|
||||
expect(newCode).toBe(makeCode(line, replace1, replace2))
|
||||
})
|
||||
@ -606,6 +635,8 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
['tangentialArcTo([3.14 + 0, 13.14], %)', 'arrayIndex', 1],
|
||||
])('stdlib fn: %s', async (expectedFinish, key, value) => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number] = [
|
||||
@ -621,7 +652,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
ast,
|
||||
programMemory
|
||||
)
|
||||
if (!mod) throw new Error('yo is undefined')
|
||||
if (!mod) return new Error('mod is undefined')
|
||||
const recastCode = recast(mod.modifiedAst)
|
||||
expect(recastCode).toContain(expectedFinish)
|
||||
})
|
||||
@ -642,6 +673,8 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
['angledLineToY([30, 10.14 + 0], %)', 'arrayIndex', 0],
|
||||
])('stdlib fn: %s', async (expectedFinish, key, value) => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||
const range: [number, number] = [
|
||||
@ -657,7 +690,7 @@ describe('Testing removeSingleConstraintInfo', () => {
|
||||
ast,
|
||||
programMemory
|
||||
)
|
||||
if (!mod) throw new Error('yo is undefined')
|
||||
if (!mod) return new Error('mod is undefined')
|
||||
const recastCode = recast(mod.modifiedAst)
|
||||
expect(recastCode).toContain(expectedFinish)
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Selection } from 'lib/selections'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import {
|
||||
Program,
|
||||
CallExpression,
|
||||
@ -73,14 +74,16 @@ export function addStartProfileAt(
|
||||
node: Program,
|
||||
pathToNode: PathToNode,
|
||||
at: [number, number]
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||
const variableDeclaration = getNodeFromPath<VariableDeclaration>(
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
|
||||
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||
node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
).node
|
||||
)
|
||||
if (err(_node1)) return _node1
|
||||
const variableDeclaration = _node1.node
|
||||
if (variableDeclaration.type !== 'VariableDeclaration') {
|
||||
throw new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
return new Error('variableDeclaration.init.type !== PipeExpression')
|
||||
}
|
||||
const _node = { ...node }
|
||||
const init = variableDeclaration.declarations[0].init
|
||||
@ -247,24 +250,36 @@ export function extrudeSketch(
|
||||
pathToNode: PathToNode,
|
||||
shouldPipe = false,
|
||||
distance = createLiteral(4) as Value
|
||||
): {
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
pathToExtrudeArg: PathToNode
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const _node = { ...node }
|
||||
const { node: sketchExpression } = getNodeFromPath(_node, pathToNode)
|
||||
const _node1 = getNodeFromPath(_node, pathToNode)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: sketchExpression } = _node1
|
||||
|
||||
// determine if sketchExpression is in a pipeExpression or not
|
||||
const { node: pipeExpression } = getNodeFromPath<PipeExpression>(
|
||||
const _node2 = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(_node2)) return _node2
|
||||
const { node: pipeExpression } = _node2
|
||||
|
||||
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
|
||||
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } =
|
||||
getNodeFromPath<VariableDeclarator>(_node, pathToNode, 'VariableDeclarator')
|
||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node3)) return _node3
|
||||
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
|
||||
|
||||
const extrudeCall = createCallExpressionStdLib('extrude', [
|
||||
distance,
|
||||
@ -331,34 +346,42 @@ export function sketchOnExtrudedFace(
|
||||
extrudePathToNode: PathToNode,
|
||||
programMemory: ProgramMemory,
|
||||
cap: 'none' | 'start' | 'end' = 'none'
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
|
||||
let _node = { ...node }
|
||||
const newSketchName = findUniqueName(
|
||||
node,
|
||||
KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH
|
||||
)
|
||||
const { node: oldSketchNode } = getNodeFromPath<VariableDeclarator>(
|
||||
const _node1 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
sketchPathToNode,
|
||||
'VariableDeclarator',
|
||||
true
|
||||
)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: oldSketchNode } = _node1
|
||||
|
||||
const oldSketchName = oldSketchNode.id.name
|
||||
const { node: expression } = getNodeFromPath<CallExpression>(
|
||||
const _node2 = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
sketchPathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
const { node: extrudeVarDec } = getNodeFromPath<VariableDeclarator>(
|
||||
if (err(_node2)) return _node2
|
||||
const { node: expression } = _node2
|
||||
|
||||
const _node3 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
extrudePathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node3)) return _node3
|
||||
const { node: extrudeVarDec } = _node3
|
||||
const extrudeName = extrudeVarDec.id?.name
|
||||
|
||||
let _tag = ''
|
||||
if (cap === 'none') {
|
||||
const { modifiedAst, tag } = addTagForSketchOnFace(
|
||||
const __tag = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode: sketchPathToNode,
|
||||
@ -366,6 +389,8 @@ export function sketchOnExtrudedFace(
|
||||
},
|
||||
expression.callee.name
|
||||
)
|
||||
if (err(__tag)) return __tag
|
||||
const { modifiedAst, tag } = __tag
|
||||
_tag = tag
|
||||
_node = modifiedAst
|
||||
} else {
|
||||
@ -616,18 +641,19 @@ export function giveSketchFnCallTag(
|
||||
ast: Program,
|
||||
range: Selection['range'],
|
||||
tag?: string
|
||||
): {
|
||||
):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
tag: string
|
||||
isTagExisting: boolean
|
||||
pathToNode: PathToNode
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const path = getNodePathFromSourceRange(ast, range)
|
||||
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
path,
|
||||
'CallExpression'
|
||||
)
|
||||
const _node1 = getNodeFromPath<CallExpression>(ast, path, 'CallExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: primaryCallExp } = _node1
|
||||
|
||||
// Tag is always 3rd expression now, using arg index feels brittle
|
||||
// but we can come up with a better way to identify tag later.
|
||||
const thirdArg = primaryCallExp.arguments?.[2]
|
||||
@ -646,7 +672,7 @@ export function giveSketchFnCallTag(
|
||||
pathToNode: path,
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unable to assign tag without value')
|
||||
return new Error('Unable to assign tag without value')
|
||||
}
|
||||
}
|
||||
|
||||
@ -659,7 +685,10 @@ export function moveValueIntoNewVariablePath(
|
||||
modifiedAst: Program
|
||||
pathToReplacedNode?: PathToNode
|
||||
} {
|
||||
const { isSafe, value, replacer } = isNodeSafeToReplacePath(ast, pathToNode)
|
||||
const meta = isNodeSafeToReplacePath(ast, pathToNode)
|
||||
if (trap(meta)) return { modifiedAst: ast }
|
||||
const { isSafe, value, replacer } = meta
|
||||
|
||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||
|
||||
const { insertIndex } = findAllPreviousVariablesPath(
|
||||
@ -669,6 +698,8 @@ export function moveValueIntoNewVariablePath(
|
||||
)
|
||||
let _node = JSON.parse(JSON.stringify(ast))
|
||||
const boop = replacer(_node, variableName)
|
||||
if (trap(boop)) return { modifiedAst: ast }
|
||||
|
||||
_node = boop.modifiedAst
|
||||
_node.body.splice(
|
||||
insertIndex,
|
||||
@ -687,7 +718,9 @@ export function moveValueIntoNewVariable(
|
||||
modifiedAst: Program
|
||||
pathToReplacedNode?: PathToNode
|
||||
} {
|
||||
const { isSafe, value, replacer } = isNodeSafeToReplace(ast, sourceRange)
|
||||
const meta = isNodeSafeToReplace(ast, sourceRange)
|
||||
if (trap(meta)) return { modifiedAst: ast }
|
||||
const { isSafe, value, replacer } = meta
|
||||
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
|
||||
|
||||
const { insertIndex } = findAllPreviousVariables(
|
||||
@ -696,7 +729,10 @@ export function moveValueIntoNewVariable(
|
||||
sourceRange
|
||||
)
|
||||
let _node = JSON.parse(JSON.stringify(ast))
|
||||
const { modifiedAst, pathToReplaced } = replacer(_node, variableName)
|
||||
const replaced = replacer(_node, variableName)
|
||||
if (trap(replaced)) return { modifiedAst: ast }
|
||||
|
||||
const { modifiedAst, pathToReplaced } = replaced
|
||||
_node = modifiedAst
|
||||
_node.body.splice(
|
||||
insertIndex,
|
||||
@ -716,7 +752,7 @@ export function deleteSegmentFromPipeExpression(
|
||||
programMemory: ProgramMemory,
|
||||
code: string,
|
||||
pathToNode: PathToNode
|
||||
): Program {
|
||||
): Program | Error {
|
||||
let _modifiedAst: Program = JSON.parse(JSON.stringify(modifiedAst))
|
||||
|
||||
dependentRanges.forEach((range) => {
|
||||
@ -728,10 +764,13 @@ export function deleteSegmentFromPipeExpression(
|
||||
'CallExpression',
|
||||
true
|
||||
)
|
||||
if (err(callExp)) return callExp
|
||||
|
||||
const constraintInfo = getConstraintInfo(callExp.node, code, path).find(
|
||||
({ sourceRange }) => isOverlap(sourceRange, range)
|
||||
)
|
||||
if (!constraintInfo) return
|
||||
|
||||
const input = makeRemoveSingleConstraintInput(
|
||||
constraintInfo.argPosition,
|
||||
callExp.shallowPath
|
||||
@ -752,13 +791,17 @@ export function deleteSegmentFromPipeExpression(
|
||||
_modifiedAst,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
)
|
||||
if (err(pipeExpression)) return pipeExpression
|
||||
|
||||
const pipeInPathIndex = pathToNode.findIndex(
|
||||
([_, desc]) => desc === 'PipeExpression'
|
||||
)
|
||||
const segmentIndexInPipe = pathToNode[pipeInPathIndex + 1][0] as number
|
||||
pipeExpression.body.splice(segmentIndexInPipe, 1)
|
||||
const segmentIndexInPipe = pathToNode[pipeInPathIndex + 1]
|
||||
pipeExpression.node.body.splice(segmentIndexInPipe[0] as number, 1)
|
||||
|
||||
// Move up to the next segment.
|
||||
segmentIndexInPipe[0] = Math.max((segmentIndexInPipe[0] as number) - 1, 0)
|
||||
|
||||
return _modifiedAst
|
||||
}
|
||||
@ -809,11 +852,13 @@ export function removeSingleConstraintInfo(
|
||||
ast,
|
||||
})
|
||||
if (!transform) return false
|
||||
return transformAstSketchLines({
|
||||
const retval = transformAstSketchLines({
|
||||
ast,
|
||||
selectionRanges: [pathToCallExp],
|
||||
transformInfos: [transform],
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(retval)) return false
|
||||
return retval
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
createLiteral,
|
||||
createPipeSubstitution,
|
||||
} from './modifyAst'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -42,6 +43,7 @@ const variableBelowShouldNotBeIncluded = 3
|
||||
`
|
||||
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
|
||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||
@ -76,53 +78,65 @@ const yo = 5 + 6
|
||||
const yo2 = hmm([identifierGuy + 5])`
|
||||
it('find a safe binaryExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('100 + 100') + 2
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('100 + 100')
|
||||
const { modifiedAst } = result.replacer(
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const outCode = recast(modifiedAst)
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find a safe Identifier', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('abc')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('Identifier')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('abc')
|
||||
})
|
||||
it('find a safe CallExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('def')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe("def('yo')")
|
||||
const { modifiedAst } = result.replacer(
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const outCode = recast(modifiedAst)
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('ghi')
|
||||
const range: [number, number] = [rangeStart, rangeStart]
|
||||
const result = isNodeSafeToReplace(ast, range)
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('ghi(%)')
|
||||
})
|
||||
it('find an UNsafe Identifier, as it is a callee', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('ine([2.8,')
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(false)
|
||||
expect(result.value?.type).toBe('CallExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe(
|
||||
@ -131,47 +145,60 @@ const yo2 = hmm([identifierGuy + 5])`
|
||||
})
|
||||
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('5 + 6') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe('5 + 6')
|
||||
const { modifiedAst } = result.replacer(
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
const outCode = recast(modifiedAst)
|
||||
if (err(replaced)) throw replaced
|
||||
const outCode = recast(replaced.modifiedAst)
|
||||
expect(outCode).toContain(`const yo = replaceName`)
|
||||
})
|
||||
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
const rangeStart = code.indexOf('jkl') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe(
|
||||
"jkl('yo') + 2"
|
||||
)
|
||||
const { modifiedAst } = result.replacer(
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
if (err(replaced)) throw replaced
|
||||
const { modifiedAst } = replaced
|
||||
const outCode = recast(modifiedAst)
|
||||
expect(outCode).toContain(`angledLine([replaceName, 3.09], %)`)
|
||||
})
|
||||
it('find a safe BinaryExpression within a CallExpression', () => {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const rangeStart = code.indexOf('identifierGuy') + 1
|
||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart])
|
||||
if (err(result)) throw result
|
||||
|
||||
expect(result.isSafe).toBe(true)
|
||||
expect(result.value?.type).toBe('BinaryExpression')
|
||||
expect(code.slice(result.value.start, result.value.end)).toBe(
|
||||
'identifierGuy + 5'
|
||||
)
|
||||
const { modifiedAst } = result.replacer(
|
||||
const replaced = result.replacer(
|
||||
JSON.parse(JSON.stringify(ast)),
|
||||
'replaceName'
|
||||
)
|
||||
if (err(replaced)) throw replaced
|
||||
const { modifiedAst } = replaced
|
||||
const outCode = recast(modifiedAst)
|
||||
expect(outCode).toContain(`const yo2 = hmm([replaceName])`)
|
||||
})
|
||||
@ -209,6 +236,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const searchLn = `line([0.94, 2.61], %)`
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
expect(result).toEqual([
|
||||
['body', ''],
|
||||
@ -224,6 +253,8 @@ describe('testing getNodePathFromSourceRange', () => {
|
||||
const searchLn = `line([-0.21, -1.4], %)`
|
||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||
const ast = parse(code)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||
const expected = [
|
||||
['body', ''],
|
||||
@ -262,6 +293,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
|> close(%)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
@ -280,6 +313,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
|> extrude(1, %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'extrude',
|
||||
ast,
|
||||
@ -296,6 +331,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
|> angledLine([-175, segLen('seg01', %)], %)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
@ -306,6 +343,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
it('returns false if not a pipe', () => {
|
||||
const exampleCode = `const length001 = 2`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const result = doesPipeHaveCallExp({
|
||||
calleeName: 'close',
|
||||
ast,
|
||||
@ -324,6 +363,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
|> line([-3.22, -7.36], %)
|
||||
|> angledLine([-175, segLen('seg01', %)], %)`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketchGroup({
|
||||
ast,
|
||||
@ -341,6 +382,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
|> angledLine([-175, segLen('seg01', %)], %)
|
||||
|> extrude(1, %)`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketchGroup({
|
||||
ast,
|
||||
@ -352,6 +395,8 @@ const part001 = startSketchAt([-1.41, 3.46])
|
||||
it('finds nothing', async () => {
|
||||
const exampleCode = `const length001 = 2`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const result = hasExtrudeSketchGroup({
|
||||
ast,
|
||||
@ -372,6 +417,8 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
|> angledLine([65, segLen('seg01', %)], %)`
|
||||
it('finds the current segment', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const lineOfInterest = `198.85], %, 'seg01'`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
@ -387,6 +434,8 @@ describe('Testing findUsesOfTagInPipe', () => {
|
||||
})
|
||||
it('find no tag if line has no tag', () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
|
||||
const lineOfInterest = `line([306.21, 198.82], %)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
@ -423,6 +472,7 @@ const sketch002 = startSketchOn(extrude001, 'seg01')
|
||||
`
|
||||
it('finds sketch001 pipe to be extruded', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const lineOfInterest = `line([4.99, -0.46], %, 'seg01')`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
@ -437,6 +487,7 @@ const sketch002 = startSketchOn(extrude001, 'seg01')
|
||||
})
|
||||
it('find sketch002 NOT pipe to be extruded', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const lineOfInterest = `line([2.45, -0.2], %)`
|
||||
const characterIndex =
|
||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||
@ -468,6 +519,7 @@ const sketch002 = startSketchOn(extrude001, 'seg01')
|
||||
|> close(%)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = hasExtrudableGeometry(ast)
|
||||
expect(extrudable).toBeTruthy()
|
||||
})
|
||||
@ -481,6 +533,7 @@ const sketch002 = startSketchOn(extrude001, 'seg01')
|
||||
const extrude001 = extrude(10, sketch001)
|
||||
`
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const extrudable = hasExtrudableGeometry(ast)
|
||||
expect(extrudable).toBeFalsy()
|
||||
})
|
||||
|
@ -25,6 +25,7 @@ import {
|
||||
getConstraintLevelFromSourceRange,
|
||||
getConstraintType,
|
||||
} from './std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
/**
|
||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||
@ -38,19 +39,20 @@ export function getNodeFromPath<T>(
|
||||
path: PathToNode,
|
||||
stopAt?: SyntaxType | SyntaxType[],
|
||||
returnEarly = false
|
||||
): {
|
||||
):
|
||||
| {
|
||||
node: T
|
||||
shallowPath: PathToNode
|
||||
deepPath: PathToNode
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
let currentNode = node as any
|
||||
let stopAtNode = null
|
||||
let successfulPaths: PathToNode = []
|
||||
let pathsExplored: PathToNode = []
|
||||
for (const pathItem of path) {
|
||||
try {
|
||||
if (typeof currentNode[pathItem[0]] !== 'object')
|
||||
throw new Error('not an object')
|
||||
return new Error('not an object')
|
||||
currentNode = currentNode?.[pathItem[0]]
|
||||
successfulPaths.push(pathItem)
|
||||
if (!stopAtNode) {
|
||||
@ -73,15 +75,6 @@ export function getNodeFromPath<T>(
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(
|
||||
// `Could not find path ${pathItem} in node ${JSON.stringify(
|
||||
// currentNode,
|
||||
// null,
|
||||
// 2
|
||||
// )}, successful path was ${successfulPaths}`
|
||||
// )
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: stopAtNode || currentNode,
|
||||
@ -99,17 +92,16 @@ export function getNodeFromPathCurry(
|
||||
): <T>(
|
||||
stopAt?: SyntaxType | SyntaxType[],
|
||||
returnEarly?: boolean
|
||||
) => {
|
||||
) =>
|
||||
| {
|
||||
node: T
|
||||
path: PathToNode
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
return <T>(stopAt?: SyntaxType | SyntaxType[], returnEarly = false) => {
|
||||
const { node: _node, shallowPath } = getNodeFromPath<T>(
|
||||
node,
|
||||
path,
|
||||
stopAt,
|
||||
returnEarly
|
||||
)
|
||||
const _node1 = getNodeFromPath<T>(node, path, stopAt, returnEarly)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: _node, shallowPath } = _node1
|
||||
return {
|
||||
node: _node,
|
||||
path: shallowPath,
|
||||
@ -374,17 +366,31 @@ export function findAllPreviousVariablesPath(
|
||||
bodyPath: PathToNode
|
||||
insertIndex: number
|
||||
} {
|
||||
const { shallowPath: pathToDec, node } = getNodeFromPath(
|
||||
ast,
|
||||
path,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
const _node1 = getNodeFromPath(ast, path, 'VariableDeclaration')
|
||||
if (err(_node1)) {
|
||||
console.error(_node1)
|
||||
return {
|
||||
variables: [],
|
||||
bodyPath: [],
|
||||
insertIndex: 0,
|
||||
}
|
||||
}
|
||||
const { shallowPath: pathToDec, node } = _node1
|
||||
|
||||
const startRange = (node as any).start
|
||||
|
||||
const { index: insertIndex, path: bodyPath } = splitPathAtLastIndex(pathToDec)
|
||||
|
||||
const { node: bodyItems } = getNodeFromPath<Program['body']>(ast, bodyPath)
|
||||
const _node2 = getNodeFromPath<Program['body']>(ast, bodyPath)
|
||||
if (err(_node2)) {
|
||||
console.error(_node2)
|
||||
return {
|
||||
variables: [],
|
||||
bodyPath: [],
|
||||
insertIndex: 0,
|
||||
}
|
||||
}
|
||||
const { node: bodyItems } = _node2
|
||||
|
||||
const variables: PrevVariable<any>[] = []
|
||||
bodyItems?.forEach?.((item) => {
|
||||
@ -422,16 +428,18 @@ export function findAllPreviousVariables(
|
||||
type ReplacerFn = (
|
||||
_ast: Program,
|
||||
varName: string
|
||||
) => { modifiedAst: Program; pathToReplaced: PathToNode }
|
||||
) => { modifiedAst: Program; pathToReplaced: PathToNode } | Error
|
||||
|
||||
export function isNodeSafeToReplacePath(
|
||||
ast: Program,
|
||||
path: PathToNode
|
||||
): {
|
||||
):
|
||||
| {
|
||||
isSafe: boolean
|
||||
value: Value
|
||||
replacer: ReplacerFn
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
if (path[path.length - 1][0] === 'callee') {
|
||||
path = path.slice(0, -1)
|
||||
}
|
||||
@ -442,16 +450,14 @@ export function isNodeSafeToReplacePath(
|
||||
'Literal',
|
||||
'UnaryExpression',
|
||||
]
|
||||
const { node: value, deepPath: outPath } = getNodeFromPath(
|
||||
ast,
|
||||
path,
|
||||
acceptedNodeTypes
|
||||
)
|
||||
const { node: binValue, shallowPath: outBinPath } = getNodeFromPath(
|
||||
ast,
|
||||
path,
|
||||
'BinaryExpression'
|
||||
)
|
||||
const _node1 = getNodeFromPath(ast, path, acceptedNodeTypes)
|
||||
if (err(_node1)) return _node1
|
||||
const { node: value, deepPath: outPath } = _node1
|
||||
|
||||
const _node2 = getNodeFromPath(ast, path, 'BinaryExpression')
|
||||
if (err(_node2)) return _node2
|
||||
const { node: binValue, shallowPath: outBinPath } = _node2
|
||||
|
||||
// binaryExpression should take precedence
|
||||
const [finVal, finPath] =
|
||||
(binValue as Value)?.type === 'BinaryExpression'
|
||||
@ -464,7 +470,9 @@ export function isNodeSafeToReplacePath(
|
||||
const pathToReplaced = JSON.parse(JSON.stringify(finPath))
|
||||
pathToReplaced[1][0] = pathToReplaced[1][0] + 1
|
||||
const startPath = finPath.slice(0, -1)
|
||||
const nodeToReplace = getNodeFromPath(_ast, startPath).node as any
|
||||
const _nodeToReplace = getNodeFromPath(_ast, startPath)
|
||||
if (err(_nodeToReplace)) return _nodeToReplace
|
||||
const nodeToReplace = _nodeToReplace.node as any
|
||||
nodeToReplace[last[0]] = identifier
|
||||
return { modifiedAst: _ast, pathToReplaced }
|
||||
}
|
||||
@ -485,11 +493,13 @@ export function isNodeSafeToReplacePath(
|
||||
export function isNodeSafeToReplace(
|
||||
ast: Program,
|
||||
sourceRange: [number, number]
|
||||
): {
|
||||
):
|
||||
| {
|
||||
isSafe: boolean
|
||||
value: Value
|
||||
replacer: ReplacerFn
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
let path = getNodePathFromSourceRange(ast, sourceRange)
|
||||
return isNodeSafeToReplacePath(ast, path)
|
||||
}
|
||||
@ -546,28 +556,38 @@ export function isLinesParallelAndConstrained(
|
||||
programMemory: ProgramMemory,
|
||||
primaryLine: Selection,
|
||||
secondaryLine: Selection
|
||||
): {
|
||||
):
|
||||
| {
|
||||
isParallelAndConstrained: boolean
|
||||
sourceRange: SourceRange
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
try {
|
||||
const EPSILON = 0.005
|
||||
const primaryPath = getNodePathFromSourceRange(ast, primaryLine.range)
|
||||
const secondaryPath = getNodePathFromSourceRange(ast, secondaryLine.range)
|
||||
const secondaryNode = getNodeFromPath<CallExpression>(
|
||||
const _secondaryNode = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
secondaryPath,
|
||||
'CallExpression'
|
||||
).node
|
||||
const varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration').node
|
||||
)
|
||||
if (err(_secondaryNode)) return _secondaryNode
|
||||
const secondaryNode = _secondaryNode.node
|
||||
const _varDec = getNodeFromPath(ast, primaryPath, 'VariableDeclaration')
|
||||
if (err(_varDec)) return _varDec
|
||||
const varDec = _varDec.node
|
||||
const varName = (varDec as VariableDeclaration)?.declarations[0]?.id?.name
|
||||
const path = programMemory?.root[varName] as SketchGroup
|
||||
const primarySegment = getSketchSegmentFromSourceRange(
|
||||
const _primarySegment = getSketchSegmentFromSourceRange(
|
||||
path,
|
||||
primaryLine.range
|
||||
).segment
|
||||
const { segment: secondarySegment, index: secondaryIndex } =
|
||||
getSketchSegmentFromSourceRange(path, secondaryLine.range)
|
||||
)
|
||||
if (err(_primarySegment)) return _primarySegment
|
||||
const primarySegment = _primarySegment.segment
|
||||
|
||||
const _segment = getSketchSegmentFromSourceRange(path, secondaryLine.range)
|
||||
if (err(_segment)) return _segment
|
||||
const { segment: secondarySegment, index: secondaryIndex } = _segment
|
||||
const primaryAngle = getAngle(primarySegment.from, primarySegment.to)
|
||||
const secondaryAngle = getAngle(secondarySegment.from, secondarySegment.to)
|
||||
const secondaryAngleAlt = getAngle(
|
||||
@ -580,14 +600,26 @@ export function isLinesParallelAndConstrained(
|
||||
|
||||
// is secordary line fully constrain, or has constrain type of 'angle'
|
||||
const secondaryFirstArg = getFirstArg(secondaryNode)
|
||||
if (err(secondaryFirstArg)) return secondaryFirstArg
|
||||
|
||||
const constraintType = getConstraintType(
|
||||
secondaryFirstArg.val,
|
||||
secondaryNode.callee.name as ToolTip
|
||||
)
|
||||
const constraintLevel = getConstraintLevelFromSourceRange(
|
||||
|
||||
const constraintLevelMeta = getConstraintLevelFromSourceRange(
|
||||
secondaryLine.range,
|
||||
ast
|
||||
).level
|
||||
)
|
||||
if (err(constraintLevelMeta)) {
|
||||
console.error(constraintLevelMeta)
|
||||
return {
|
||||
isParallelAndConstrained: false,
|
||||
sourceRange: [0, 0],
|
||||
}
|
||||
}
|
||||
const constraintLevel = constraintLevelMeta.level
|
||||
|
||||
const isConstrained =
|
||||
constraintType === 'angle' || constraintLevel === 'full'
|
||||
|
||||
@ -622,11 +654,16 @@ export function doesPipeHaveCallExp({
|
||||
selection: Selection
|
||||
}): boolean {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||
const pipeExpressionMeta = getNodeFromPath<PipeExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
)
|
||||
if (err(pipeExpressionMeta)) {
|
||||
console.error(pipeExpressionMeta)
|
||||
return false
|
||||
}
|
||||
const pipeExpression = pipeExpressionMeta.node
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
return pipeExpression.body.some(
|
||||
(expression) =>
|
||||
@ -645,11 +682,16 @@ export function hasExtrudeSketchGroup({
|
||||
programMemory: ProgramMemory
|
||||
}): boolean {
|
||||
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||
const varDecMeta = getNodeFromPath<VariableDeclaration>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
).node
|
||||
)
|
||||
if (err(varDecMeta)) {
|
||||
console.error(varDecMeta)
|
||||
return false
|
||||
}
|
||||
const varDec = varDecMeta.node
|
||||
if (varDec.type !== 'VariableDeclaration') return false
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varValue = programMemory?.root[varName]
|
||||
@ -679,11 +721,16 @@ export function findUsesOfTagInPipe(
|
||||
'segEndY',
|
||||
'segLen',
|
||||
]
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (err(nodeMeta)) {
|
||||
console.error(nodeMeta)
|
||||
return []
|
||||
}
|
||||
const node = nodeMeta.node
|
||||
if (node.type !== 'CallExpression') return []
|
||||
const tagIndex = node.callee.name === 'close' ? 1 : 2
|
||||
const thirdParam = node.arguments[tagIndex]
|
||||
@ -694,10 +741,14 @@ export function findUsesOfTagInPipe(
|
||||
ast,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
).node
|
||||
)
|
||||
if (err(varDec)) {
|
||||
console.error(varDec)
|
||||
return []
|
||||
}
|
||||
const dependentRanges: SourceRange[] = []
|
||||
|
||||
traverse(varDec, {
|
||||
traverse(varDec.node, {
|
||||
enter: (node) => {
|
||||
if (
|
||||
node.type !== 'CallExpression' ||
|
||||
@ -715,17 +766,17 @@ export function findUsesOfTagInPipe(
|
||||
|
||||
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||
const path = getNodePathFromSourceRange(ast, selection.range)
|
||||
const { node: pipeExpression } = getNodeFromPath<PipeExpression>(
|
||||
ast,
|
||||
path,
|
||||
'PipeExpression'
|
||||
)
|
||||
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression')
|
||||
if (err(_node)) return false
|
||||
const { node: pipeExpression } = _node
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||
const _varDec = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
path,
|
||||
'VariableDeclarator'
|
||||
).node
|
||||
)
|
||||
if (err(_varDec)) return false
|
||||
const varDec = _varDec.node
|
||||
let extruded = false
|
||||
traverse(ast as any, {
|
||||
enter(node) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { parse, Program, recast, initPromise } from './wasm'
|
||||
import fs from 'node:fs'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -10,22 +11,27 @@ describe('recast', () => {
|
||||
const code = '1 + 2'
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('variable declaration', () => {
|
||||
const code = 'const myVar = 5'
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it("variable declaration that's binary with string", () => {
|
||||
const code = "const myVar = 5 + 'yo'"
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
|
||||
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
|
||||
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
|
||||
const recastRetVal = recast(ast2)
|
||||
if (err(recastRetVal)) throw recastRetVal
|
||||
expect(recastRetVal.trim()).toBe(codeWithOtherQuotes)
|
||||
})
|
||||
it('test assigning two variables, the second summing with the first', () => {
|
||||
const code = `const myVar = 5
|
||||
@ -33,6 +39,7 @@ const newVar = myVar + 1
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('test assigning a var by cont concatenating two strings string', () => {
|
||||
@ -42,6 +49,7 @@ const newVar = myVar + 1
|
||||
)
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('test with function call', () => {
|
||||
@ -50,6 +58,7 @@ log(5, myVar)
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('function declaration with call', () => {
|
||||
@ -62,6 +71,7 @@ log(5, myVar)
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('recast sketch declaration', () => {
|
||||
@ -73,6 +83,7 @@ log(5, myVar)
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('sketch piped into callExpression', () => {
|
||||
@ -85,6 +96,7 @@ log(5, myVar)
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast BinaryExpression piped into CallExpression', () => {
|
||||
@ -97,36 +109,42 @@ log(5, myVar)
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('recast nested binary expression', () => {
|
||||
const code = ['const myVar = 1 + 2 * 5'].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast nested binary expression with parans', () => {
|
||||
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('unnecessary paran wrap will be remove', () => {
|
||||
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
|
||||
})
|
||||
it('complex nested binary expression', () => {
|
||||
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('multiplied paren expressions', () => {
|
||||
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast array declaration', () => {
|
||||
@ -135,6 +153,7 @@ log(5, myVar)
|
||||
)
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast long array declaration', () => {
|
||||
@ -150,6 +169,7 @@ log(5, myVar)
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code.trim())
|
||||
})
|
||||
it('recast long object execution', () => {
|
||||
@ -163,6 +183,7 @@ const yo = {
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('recast short object execution', () => {
|
||||
@ -170,6 +191,7 @@ const yo = {
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('recast object execution with member expression', () => {
|
||||
@ -181,6 +203,7 @@ const myVar2 = yo['a'][key2].c
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
})
|
||||
@ -194,6 +217,7 @@ const key = 'c'
|
||||
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
@ -207,6 +231,7 @@ const yo = 'bing'
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('comments at the start and end', () => {
|
||||
@ -218,6 +243,7 @@ const key = 'c'
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('comments in a fn block', () => {
|
||||
@ -233,6 +259,7 @@ const key = 'c'
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('comments in a pipe expression', () => {
|
||||
@ -246,6 +273,7 @@ const key = 'c'
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('comments sprinkled in all over the place', () => {
|
||||
@ -272,6 +300,7 @@ one more for good measure
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(`/* comment at start */
|
||||
|
||||
const mySk1 = startSketchAt([0, 0])
|
||||
@ -295,18 +324,21 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
|
||||
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('nested callExpression in unaryExpression', () => {
|
||||
const code = 'const myVar = -min(100, legLen(5, 3))'
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('with unaryExpression in callExpression', () => {
|
||||
const code = 'const myVar = min(5, -legLen(5, 4))'
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
it('with unaryExpression in sketch situation', () => {
|
||||
@ -316,6 +348,7 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
|
||||
].join('\n')
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted.trim()).toBe(code)
|
||||
})
|
||||
})
|
||||
@ -334,6 +367,7 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
it('recasts wrapped object expressions NOT in pipe body correctly', () => {
|
||||
@ -345,6 +379,7 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
|
||||
`
|
||||
const { ast } = code2ast(code)
|
||||
const recasted = recast(ast)
|
||||
if (err(recasted)) throw recasted
|
||||
expect(recasted).toBe(code)
|
||||
})
|
||||
})
|
||||
@ -362,5 +397,7 @@ describe('it recasts binary expression using brackets where needed', () => {
|
||||
|
||||
function code2ast(code: string): { ast: Program } {
|
||||
const ast = parse(code)
|
||||
// eslint-ignore-next-line
|
||||
if (err(ast)) throw ast
|
||||
return { ast }
|
||||
}
|
||||
|
@ -1822,7 +1822,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
)
|
||||
}
|
||||
}
|
||||
throw Error('shouldnt reach here')
|
||||
return Promise.reject(new Error('Expected unreachable reached'))
|
||||
}
|
||||
handlePendingSceneCommand(
|
||||
id: string,
|
||||
@ -1930,7 +1930,9 @@ export class EngineCommandManager extends EventTarget {
|
||||
)
|
||||
|
||||
if (!idToRangeMap) {
|
||||
throw new Error('idToRangeMap is required for batch commands')
|
||||
return Promise.reject(
|
||||
new Error('idToRangeMap is required for batch commands')
|
||||
)
|
||||
}
|
||||
|
||||
// Add the overall batch command to the artifact map just so we can track all of the
|
||||
@ -1954,7 +1956,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
)
|
||||
return promise
|
||||
}
|
||||
sendModelingCommandFromWasm(
|
||||
async sendModelingCommandFromWasm(
|
||||
id: string,
|
||||
rangeStr: string,
|
||||
commandStr: string,
|
||||
@ -1967,13 +1969,13 @@ export class EngineCommandManager extends EventTarget {
|
||||
return Promise.resolve()
|
||||
}
|
||||
if (id === undefined) {
|
||||
throw new Error('id is undefined')
|
||||
return Promise.reject(new Error('id is undefined'))
|
||||
}
|
||||
if (rangeStr === undefined) {
|
||||
throw new Error('rangeStr is undefined')
|
||||
return Promise.reject(new Error('rangeStr is undefined'))
|
||||
}
|
||||
if (commandStr === undefined) {
|
||||
throw new Error('commandStr is undefined')
|
||||
return Promise.reject(new Error('commandStr is undefined'))
|
||||
}
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
const idToRangeMap: { [key: string]: SourceRange } =
|
||||
@ -1990,17 +1992,19 @@ export class EngineCommandManager extends EventTarget {
|
||||
idToRangeMap,
|
||||
}).then((resp) => {
|
||||
if (!resp) {
|
||||
throw new Error(
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
'returning modeling cmd response to the rust side is undefined or null'
|
||||
)
|
||||
)
|
||||
}
|
||||
return JSON.stringify(resp.raw)
|
||||
})
|
||||
}
|
||||
commandResult(id: string): Promise<any> {
|
||||
async commandResult(id: string): Promise<any> {
|
||||
const command = this.artifactMap[id]
|
||||
if (!command) {
|
||||
throw new Error('No command found')
|
||||
return Promise.reject(new Error('No command found'))
|
||||
}
|
||||
if (command.type === 'result') {
|
||||
return command.data
|
||||
|
@ -10,69 +10,64 @@ class FileSystemManager {
|
||||
private _dir: string | null = null
|
||||
|
||||
get dir() {
|
||||
if (this._dir === null) {
|
||||
throw new Error('current project dir is not set')
|
||||
}
|
||||
|
||||
return this._dir
|
||||
return this._dir ?? ''
|
||||
}
|
||||
|
||||
set dir(dir: string) {
|
||||
this._dir = dir
|
||||
}
|
||||
|
||||
readFile(path: string): Promise<Uint8Array | void> {
|
||||
async readFile(path: string): Promise<Uint8Array | void> {
|
||||
// Using local file system only works from Tauri.
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'This function can only be called from a Tauri application'
|
||||
return Promise.reject(
|
||||
new Error('This function can only be called from a Tauri application')
|
||||
)
|
||||
}
|
||||
|
||||
return join(this.dir, path)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error reading file: ${error}`)
|
||||
return Promise.reject(new Error(`Error reading file: ${error}`))
|
||||
})
|
||||
.then((file) => {
|
||||
return readFile(file)
|
||||
})
|
||||
}
|
||||
|
||||
exists(path: string): Promise<boolean | void> {
|
||||
async exists(path: string): Promise<boolean | void> {
|
||||
// Using local file system only works from Tauri.
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'This function can only be called from a Tauri application'
|
||||
return Promise.reject(
|
||||
new Error('This function can only be called from a Tauri application')
|
||||
)
|
||||
}
|
||||
|
||||
return join(this.dir, path)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error checking file exists: ${error}`)
|
||||
return Promise.reject(new Error(`Error checking file exists: ${error}`))
|
||||
})
|
||||
.then((file) => {
|
||||
return tauriExists(file)
|
||||
})
|
||||
}
|
||||
|
||||
getAllFiles(path: string): Promise<string[] | void> {
|
||||
async getAllFiles(path: string): Promise<string[] | void> {
|
||||
// Using local file system only works from Tauri.
|
||||
if (!isTauri()) {
|
||||
throw new Error(
|
||||
'This function can only be called from a Tauri application'
|
||||
return Promise.reject(
|
||||
new Error('This function can only be called from a Tauri application')
|
||||
)
|
||||
}
|
||||
|
||||
return join(this.dir, path)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error joining dir: ${error}`)
|
||||
return Promise.reject(new Error(`Error joining dir: ${error}`))
|
||||
})
|
||||
.then((p) => {
|
||||
readDirRecursive(p)
|
||||
.catch((error) => {
|
||||
throw new Error(`Error reading dir: ${error}`)
|
||||
return Promise.reject(new Error(`Error reading dir: ${error}`))
|
||||
})
|
||||
|
||||
.then((files) => {
|
||||
return files.map((file) => file.path)
|
||||
})
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
} from '../wasm'
|
||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const eachQuad: [number, [number, number]][] = [
|
||||
[-315, [1, 1]],
|
||||
@ -114,16 +115,19 @@ describe('testing changeSketchArguments', () => {
|
||||
const code = genCode(lineToChange)
|
||||
const expectedCode = genCode(lineAfterChange)
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
const { modifiedAst } = changeSketchArguments(
|
||||
const changeSketchArgsRetVal = changeSketchArguments(
|
||||
ast,
|
||||
programMemory,
|
||||
[sourceStart, sourceStart + lineToChange.length],
|
||||
[2, 3],
|
||||
[0, 0]
|
||||
)
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
if (err(changeSketchArgsRetVal)) return changeSketchArgsRetVal
|
||||
expect(recast(changeSketchArgsRetVal.modifiedAst)).toBe(expectedCode)
|
||||
})
|
||||
})
|
||||
|
||||
@ -138,10 +142,12 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> lineTo([-1.59, -1.54], %)
|
||||
|> lineTo([0.46, -5.82], %)`
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const sourceStart = code.indexOf(lineToChange)
|
||||
expect(sourceStart).toBe(95)
|
||||
let { modifiedAst } = addNewSketchLn({
|
||||
const newSketchLnRetVal = addNewSketchLn({
|
||||
node: ast,
|
||||
programMemory,
|
||||
to: [2, 3],
|
||||
@ -155,6 +161,8 @@ const mySketch001 = startSketchOn('XY')
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
if (err(newSketchLnRetVal)) return newSketchLnRetVal
|
||||
|
||||
// Enable rotations #152
|
||||
let expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
@ -163,9 +171,11 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> lineTo([2, 3], %)
|
||||
`
|
||||
|
||||
const { modifiedAst } = newSketchLnRetVal
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
|
||||
modifiedAst = addCloseToPipe({
|
||||
const modifiedAst2 = addCloseToPipe({
|
||||
node: ast,
|
||||
programMemory,
|
||||
pathToNode: [
|
||||
@ -176,6 +186,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
['init', 'VariableDeclarator'],
|
||||
],
|
||||
})
|
||||
if (err(modifiedAst2)) return modifiedAst2
|
||||
|
||||
expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([0, 0], %)
|
||||
@ -184,7 +195,7 @@ const mySketch001 = startSketchOn('XY')
|
||||
|> lineTo([0.46, -5.82], %)
|
||||
|> close(%)
|
||||
`
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
expect(recast(modifiedAst2)).toBe(expectedCode)
|
||||
})
|
||||
})
|
||||
|
||||
@ -206,8 +217,9 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
sourceStart,
|
||||
sourceStart + originalLine.length,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const { modifiedAst } = addTagForSketchOnFace(
|
||||
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
||||
{
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode,
|
||||
@ -215,6 +227,9 @@ describe('testing addTagForSketchOnFace', () => {
|
||||
},
|
||||
'lineTo'
|
||||
)
|
||||
if (err(sketchOnFaceRetVal)) return sketchOnFaceRetVal
|
||||
|
||||
const { modifiedAst } = sketchOnFaceRetVal
|
||||
const expectedCode = genCode("lineTo([-1.59, -1.54], %, 'seg01')")
|
||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||
})
|
||||
@ -583,13 +598,15 @@ describe('testing getConstraintInfo', () => {
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
const result = getConstraintInfo(callExp, code, pathToNode)
|
||||
)
|
||||
if (err(callExp)) return callExp
|
||||
const result = getConstraintInfo(callExp.node, code, pathToNode)
|
||||
expect(result).toEqual(expected)
|
||||
})
|
||||
})
|
||||
@ -735,13 +752,15 @@ describe('testing getConstraintInfo', () => {
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
const result = getConstraintInfo(callExp, code, pathToNode)
|
||||
)
|
||||
if (err(callExp)) return callExp
|
||||
const result = getConstraintInfo(callExp.node, code, pathToNode)
|
||||
expect(result).toEqual(expected)
|
||||
})
|
||||
})
|
||||
@ -1089,13 +1108,16 @@ describe('testing getConstraintInfo', () => {
|
||||
code.indexOf(functionName),
|
||||
code.indexOf(functionName) + functionName.length,
|
||||
]
|
||||
if (err(ast)) return ast
|
||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||
const callExp = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
const result = getConstraintInfo(callExp, code, pathToNode)
|
||||
)
|
||||
if (err(callExp)) return callExp
|
||||
|
||||
const result = getConstraintInfo(callExp.node, code, pathToNode)
|
||||
expect(result).toEqual(expected)
|
||||
})
|
||||
})
|
||||
|
@ -49,6 +49,7 @@ import {
|
||||
findUniqueName,
|
||||
} from 'lang/modifyAst'
|
||||
import { roundOff, getLength, getAngle } from 'lib/utils'
|
||||
import { err } from 'lib/trap'
|
||||
import { perpendicularDistance } from 'sketch-helpers'
|
||||
|
||||
export type Coords2d = [number, number]
|
||||
@ -69,7 +70,7 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
|
||||
export function createFirstArg(
|
||||
sketchFn: ToolTip,
|
||||
val: Value | [Value, Value] | [Value, Value, Value]
|
||||
): Value {
|
||||
): Value | Error {
|
||||
if (Array.isArray(val)) {
|
||||
if (
|
||||
[
|
||||
@ -97,7 +98,7 @@ export function createFirstArg(
|
||||
)
|
||||
return val
|
||||
}
|
||||
throw new Error('all sketch line types should have been covered')
|
||||
return new Error('Missing sketch line type')
|
||||
}
|
||||
|
||||
type AbbreviatedInput =
|
||||
@ -314,11 +315,13 @@ export const lineTo: SketchLineHelper = {
|
||||
referencedSegment,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: pipe } = nodeMeta
|
||||
|
||||
const newVals: [Value, Value] = [
|
||||
createLiteral(roundOff(to[0], 2)),
|
||||
@ -355,10 +358,9 @@ export const lineTo: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
|
||||
const toArrExp = createArrayExpression([
|
||||
createLiteral(to[0]),
|
||||
@ -396,16 +398,20 @@ export const line: SketchLineHelper = {
|
||||
spliceBetween,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression | CallExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression | CallExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: pipe } = nodeMeta
|
||||
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
const { node: varDec } = nodeMeta2
|
||||
|
||||
const newXVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
const newYVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
@ -420,8 +426,7 @@ export const line: SketchLineHelper = {
|
||||
)
|
||||
const pipeIndex = pathToNode[pathToNodeIndex + 1][0]
|
||||
if (typeof pipeIndex === 'undefined' || typeof pipeIndex === 'string') {
|
||||
throw new Error('pipeIndex is undefined')
|
||||
// return
|
||||
return new Error('pipeIndex is undefined')
|
||||
}
|
||||
pipe.body = [
|
||||
...pipe.body.slice(0, pipeIndex),
|
||||
@ -447,11 +452,7 @@ export const line: SketchLineHelper = {
|
||||
pipe.body[callIndex] = callExp
|
||||
return {
|
||||
modifiedAst: _node,
|
||||
pathToNode: [
|
||||
...pathToNode,
|
||||
['body', 'PipeExpression'],
|
||||
[callIndex, 'CallExpression'],
|
||||
],
|
||||
pathToNode: [...pathToNode],
|
||||
valueUsedInTransform,
|
||||
}
|
||||
}
|
||||
@ -480,10 +481,9 @@ export const line: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
|
||||
const toArrExp = createArrayExpression([
|
||||
createLiteral(roundOff(to[0] - from[0], 2)),
|
||||
@ -515,7 +515,9 @@ export const xLineTo: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
|
||||
const newVal = createLiteral(roundOff(to[0], 2))
|
||||
|
||||
@ -544,10 +546,9 @@ export const xLineTo: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const newX = createLiteral(roundOff(to[0], 2))
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newX
|
||||
@ -574,7 +575,9 @@ export const yLineTo: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, to, replaceExisting, createCallback }) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
|
||||
const newVal = createLiteral(roundOff(to[1], 2))
|
||||
|
||||
@ -603,10 +606,9 @@ export const yLineTo: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const newY = createLiteral(roundOff(to[1], 2))
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newY
|
||||
@ -633,7 +635,9 @@ export const xLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
|
||||
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
const firstArg = newVal
|
||||
@ -661,10 +665,9 @@ export const xLine: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const newX = createLiteral(roundOff(to[0] - from[0], 2))
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newX
|
||||
@ -691,7 +694,9 @@ export const yLine: SketchLineHelper = {
|
||||
add: ({ node, pathToNode, to, from, replaceExisting, createCallback }) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
if (replaceExisting && createCallback) {
|
||||
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
|
||||
@ -716,10 +721,9 @@ export const yLine: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const newY = createLiteral(roundOff(to[1] - from[1], 2))
|
||||
if (isLiteralArrayOrStatic(callExpression.arguments?.[0])) {
|
||||
callExpression.arguments[0] = newY
|
||||
@ -753,14 +757,16 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression | CallExpression>(
|
||||
'PipeExpression'
|
||||
)
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
const _node1 = getNode<PipeExpression | CallExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
const _node2 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(_node2)) return _node2
|
||||
const { node: varDec } = _node2
|
||||
|
||||
const toX = createLiteral(roundOff(to[0], 2))
|
||||
const toY = createLiteral(roundOff(to[1], 2))
|
||||
@ -806,10 +812,9 @@ export const tangentialArcTo: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const x = createLiteral(roundOff(to[0], 2))
|
||||
const y = createLiteral(roundOff(to[1], 2))
|
||||
|
||||
@ -883,7 +888,9 @@ export const angledLine: SketchLineHelper = {
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const getNode = getNodeFromPathCurry(_node, pathToNode)
|
||||
const { node: pipe } = getNode<PipeExpression>('PipeExpression')
|
||||
const _node1 = getNode<PipeExpression>('PipeExpression')
|
||||
if (err(_node1)) return _node1
|
||||
const { node: pipe } = _node1
|
||||
|
||||
const newAngleVal = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const newLengthVal = createLiteral(roundOff(getLength(from, to), 2))
|
||||
@ -918,10 +925,9 @@ export const angledLine: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
const lineLength = roundOff(getLength(from, to), 2)
|
||||
|
||||
@ -964,19 +970,26 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
replaceExisting,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: pipe } = nodeMeta
|
||||
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
const { node: varDec } = nodeMeta2
|
||||
|
||||
const variableName = varDec.id.name
|
||||
const sketch = previousProgramMemory?.root?.[variableName]
|
||||
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||
if (sketch.type !== 'SketchGroup') {
|
||||
return new Error('not a SketchGroup')
|
||||
}
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const xLength = createLiteral(roundOff(Math.abs(from[0] - to[0]), 2) || 0.1)
|
||||
const newLine = createCallback
|
||||
@ -1004,10 +1017,9 @@ export const angledLineOfXLength: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
const xLength = roundOff(Math.abs(to[0] - from[0]), 2)
|
||||
|
||||
@ -1054,19 +1066,25 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
replaceExisting,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: pipe } = nodeMeta
|
||||
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclarator'
|
||||
)
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
const { node: varDec } = nodeMeta2
|
||||
const variableName = varDec.id.name
|
||||
const sketch = previousProgramMemory?.root?.[variableName]
|
||||
if (sketch.type !== 'SketchGroup') throw new Error('not a SketchGroup')
|
||||
if (sketch.type !== 'SketchGroup') {
|
||||
return new Error('not a SketchGroup')
|
||||
}
|
||||
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const yLength = createLiteral(roundOff(Math.abs(from[1] - to[1]), 2) || 0.1)
|
||||
@ -1095,10 +1113,9 @@ export const angledLineOfYLength: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
const yLength = roundOff(to[1] - from[1], 2)
|
||||
|
||||
@ -1145,11 +1162,14 @@ export const angledLineToX: SketchLineHelper = {
|
||||
referencedSegment,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: pipe } = nodeMeta
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const xArg = createLiteral(roundOff(to[0], 2))
|
||||
if (replaceExisting && createCallback) {
|
||||
@ -1182,10 +1202,10 @@ export const angledLineToX: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
const xLength = roundOff(to[0], 2)
|
||||
|
||||
@ -1229,11 +1249,15 @@ export const angledLineToY: SketchLineHelper = {
|
||||
referencedSegment,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: pipe } = nodeMeta
|
||||
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
const yArg = createLiteral(roundOff(to[1], 2))
|
||||
|
||||
@ -1267,10 +1291,10 @@ export const angledLineToY: SketchLineHelper = {
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
const xLength = roundOff(to[1], 2)
|
||||
|
||||
@ -1314,14 +1338,20 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
referencedSegment,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: pipe } = getNodeFromPath<PipeExpression>(
|
||||
const nodeMeta = getNodeFromPath<PipeExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: pipe } = nodeMeta
|
||||
|
||||
const angle = createLiteral(roundOff(getAngle(from, to), 0))
|
||||
if (!referencedSegment)
|
||||
throw new Error('referencedSegment must be provided')
|
||||
if (!referencedSegment) {
|
||||
return new Error('referencedSegment must be provided')
|
||||
}
|
||||
|
||||
const offset = createLiteral(
|
||||
roundOff(
|
||||
perpendicularDistance(
|
||||
@ -1359,14 +1389,14 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
valueUsedInTransform,
|
||||
}
|
||||
}
|
||||
throw new Error('not implemented')
|
||||
return new Error('not implemented')
|
||||
},
|
||||
updateArgs: ({ node, pathToNode, to, from, previousProgramMemory }) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: callExpression } = nodeMeta
|
||||
const angle = roundOff(getAngle(from, to), 0)
|
||||
|
||||
const firstArg = callExpression.arguments?.[0]
|
||||
@ -1377,12 +1407,14 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
||||
: createLiteral('')
|
||||
const intersectTagName =
|
||||
intersectTag.type === 'Literal' ? intersectTag.value : ''
|
||||
const { node: varDec } = getNodeFromPath<VariableDeclaration>(
|
||||
const nodeMeta2 = getNodeFromPath<VariableDeclaration>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(nodeMeta2)) return nodeMeta2
|
||||
|
||||
const { node: varDec } = nodeMeta2
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const sketchGroup = previousProgramMemory.root[varName] as SketchGroup
|
||||
const intersectPath = sketchGroup.value.find(
|
||||
@ -1493,11 +1525,24 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
|
||||
to,
|
||||
}) => {
|
||||
const _node = { ...node }
|
||||
const { node: callExpression } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
||||
if (err(nodeMeta)) {
|
||||
console.error(nodeMeta)
|
||||
return {
|
||||
modifiedAst: {
|
||||
start: 0,
|
||||
end: 0,
|
||||
body: [],
|
||||
nonCodeMeta: {
|
||||
start: [],
|
||||
nonCodeNodes: [],
|
||||
},
|
||||
},
|
||||
pathToNode,
|
||||
}
|
||||
}
|
||||
|
||||
const { node: callExpression } = nodeMeta
|
||||
const toArrExp = createArrayExpression([
|
||||
createLiteral(roundOff(to[0])),
|
||||
createLiteral(roundOff(to[1])),
|
||||
@ -1533,17 +1578,20 @@ export function changeSketchArguments(
|
||||
sourceRange: SourceRange,
|
||||
args: [number, number],
|
||||
from: [number, number]
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } {
|
||||
): { modifiedAst: Program; pathToNode: PathToNode } | Error {
|
||||
const _node = { ...node }
|
||||
const thePath = getNodePathFromSourceRange(_node, sourceRange)
|
||||
const { node: callExpression, shallowPath } = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
thePath
|
||||
)
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, thePath)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: callExpression, shallowPath } = nodeMeta
|
||||
|
||||
if (callExpression?.callee?.name in sketchLineHelperMap) {
|
||||
const { updateArgs } = sketchLineHelperMap[callExpression.callee.name]
|
||||
if (!updateArgs) throw new Error('not a sketch line helper')
|
||||
if (!updateArgs) {
|
||||
return new Error('not a sketch line helper')
|
||||
}
|
||||
|
||||
return updateArgs({
|
||||
node: _node,
|
||||
previousProgramMemory: programMemory,
|
||||
@ -1553,7 +1601,7 @@ export function changeSketchArguments(
|
||||
})
|
||||
}
|
||||
|
||||
throw new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
||||
return new Error(`not a sketch line helper: ${callExpression?.callee?.name}`)
|
||||
}
|
||||
|
||||
export function getConstraintInfo(
|
||||
@ -1612,13 +1660,18 @@ export function addNewSketchLn({
|
||||
pathToNode,
|
||||
from,
|
||||
spliceBetween = false,
|
||||
}: CreateLineFnCallArgs): {
|
||||
}: CreateLineFnCallArgs):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const node = JSON.parse(JSON.stringify(_node))
|
||||
const { add, updateArgs } = sketchLineHelperMap?.[fnName] || {}
|
||||
if (!add || !updateArgs) throw new Error('not a sketch line helper')
|
||||
if (!add || !updateArgs) {
|
||||
return new Error('not a sketch line helper')
|
||||
}
|
||||
|
||||
getNodeFromPath<VariableDeclarator>(node, pathToNode, 'VariableDeclarator')
|
||||
getNodeFromPath<PipeExpression | CallExpression>(
|
||||
node,
|
||||
@ -1651,10 +1704,13 @@ export function addCallExpressionsToPipe({
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
if (pipeExpression.type !== 'PipeExpression')
|
||||
throw new Error('not a pipe expression')
|
||||
pipeExpression.body = [...pipeExpression.body, ...expressions]
|
||||
)
|
||||
if (err(pipeExpression)) return pipeExpression
|
||||
|
||||
if (pipeExpression.node.type !== 'PipeExpression') {
|
||||
return new Error('not a pipe expression')
|
||||
}
|
||||
pipeExpression.node.body = [...pipeExpression.node.body, ...expressions]
|
||||
return _node
|
||||
}
|
||||
|
||||
@ -1674,10 +1730,13 @@ export function addCloseToPipe({
|
||||
_node,
|
||||
pathToNode,
|
||||
'PipeExpression'
|
||||
).node
|
||||
if (pipeExpression.type !== 'PipeExpression')
|
||||
throw new Error('not a pipe expression')
|
||||
pipeExpression.body = [...pipeExpression.body, closeExpression]
|
||||
)
|
||||
if (err(pipeExpression)) return pipeExpression
|
||||
|
||||
if (pipeExpression.node.type !== 'PipeExpression') {
|
||||
return new Error('not a pipe expression')
|
||||
}
|
||||
pipeExpression.node.body = [...pipeExpression.node.body, closeExpression]
|
||||
return _node
|
||||
}
|
||||
|
||||
@ -1699,17 +1758,20 @@ export function replaceSketchLine({
|
||||
from: [number, number]
|
||||
createCallback: TransformCallback
|
||||
referencedSegment?: Path
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
valueUsedInTransform?: number
|
||||
pathToNode: PathToNode
|
||||
} {
|
||||
if (![...toolTips, 'intersect'].includes(fnName))
|
||||
throw new Error('not a tooltip')
|
||||
}
|
||||
| Error {
|
||||
if (![...toolTips, 'intersect'].includes(fnName)) {
|
||||
return new Error('not a tooltip')
|
||||
}
|
||||
const _node = { ...node }
|
||||
|
||||
const { add } = sketchLineHelperMap[fnName]
|
||||
const { modifiedAst, valueUsedInTransform, pathToNode } = add({
|
||||
const addRetVal = add({
|
||||
node: _node,
|
||||
previousProgramMemory: programMemory,
|
||||
pathToNode: _pathToNode,
|
||||
@ -1719,6 +1781,9 @@ export function replaceSketchLine({
|
||||
replaceExisting: true,
|
||||
createCallback,
|
||||
})
|
||||
if (err(addRetVal)) return addRetVal
|
||||
|
||||
const { modifiedAst, valueUsedInTransform, pathToNode } = addRetVal
|
||||
return { modifiedAst, valueUsedInTransform, pathToNode }
|
||||
}
|
||||
|
||||
@ -1733,7 +1798,7 @@ export function addTagForSketchOnFace(
|
||||
const { addTag } = sketchLineHelperMap[expressionName]
|
||||
return addTag(a)
|
||||
}
|
||||
throw new Error(`"${expressionName}" is not a sketch line helper`)
|
||||
return new Error(`"${expressionName}" is not a sketch line helper`)
|
||||
}
|
||||
|
||||
function isAngleLiteral(lineArugement: Value): boolean {
|
||||
@ -1746,16 +1811,22 @@ function isAngleLiteral(lineArugement: Value): boolean {
|
||||
: false
|
||||
}
|
||||
|
||||
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
|
||||
type addTagFn = (
|
||||
a: ModifyAstBase
|
||||
) => { modifiedAst: Program; tag: string } | Error
|
||||
|
||||
function addTag(tagIndex = 2): addTagFn {
|
||||
return ({ node, pathToNode }) => {
|
||||
const _node = { ...node }
|
||||
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
|
||||
const callExpr = getNodeFromPath<CallExpression>(
|
||||
_node,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(callExpr)) return callExpr
|
||||
|
||||
const { node: primaryCallExp } = callExpr
|
||||
|
||||
// Tag is always 3rd expression now, using arg index feels brittle
|
||||
// but we can come up with a better way to identify tag later.
|
||||
const thirdArg = primaryCallExp.arguments?.[tagIndex]
|
||||
@ -1772,7 +1843,7 @@ function addTag(tagIndex = 2): addTagFn {
|
||||
tag: String(tagLiteral.value),
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unable to assign tag without value')
|
||||
return new Error('Unable to assign tag without value')
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1797,10 +1868,12 @@ export function getXComponent(
|
||||
return [sign * xComponent, sign * yComponent]
|
||||
}
|
||||
|
||||
function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
||||
function getFirstArgValuesForXYFns(callExpression: CallExpression):
|
||||
| {
|
||||
val: [Value, Value]
|
||||
tag?: Value
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
// used for lineTo, line
|
||||
const firstArg = callExpression.arguments[0]
|
||||
if (firstArg.type === 'ArrayExpression') {
|
||||
@ -1814,13 +1887,15 @@ function getFirstArgValuesForXYFns(callExpression: CallExpression): {
|
||||
return { val: [x, y], tag }
|
||||
}
|
||||
}
|
||||
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||
return new Error('expected ArrayExpression or ObjectExpression')
|
||||
}
|
||||
|
||||
function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
||||
function getFirstArgValuesForAngleFns(callExpression: CallExpression):
|
||||
| {
|
||||
val: [Value, Value]
|
||||
tag?: Value
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
// used for angledLine, angledLineOfXLength, angledLineToX, angledLineOfYLength, angledLineToY
|
||||
const firstArg = callExpression.arguments[0]
|
||||
if (firstArg.type === 'ArrayExpression') {
|
||||
@ -1841,7 +1916,7 @@ function getFirstArgValuesForAngleFns(callExpression: CallExpression): {
|
||||
return { val: [angle, length], tag }
|
||||
}
|
||||
}
|
||||
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||
return new Error('expected ArrayExpression or ObjectExpression')
|
||||
}
|
||||
|
||||
function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
|
||||
@ -1874,10 +1949,12 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
|
||||
|
||||
const getAngledLineThatIntersects = (
|
||||
callExp: CallExpression
|
||||
): {
|
||||
):
|
||||
| {
|
||||
val: [Value, Value, Value]
|
||||
tag?: Value
|
||||
} => {
|
||||
}
|
||||
| Error => {
|
||||
const firstArg = callExp.arguments[0]
|
||||
if (firstArg.type === 'ObjectExpression') {
|
||||
const tag = firstArg.properties.find((p) => p.key.name === 'tag')?.value
|
||||
@ -1892,13 +1969,15 @@ const getAngledLineThatIntersects = (
|
||||
return { val: [angle, offset, intersectTag], tag }
|
||||
}
|
||||
}
|
||||
throw new Error('expected ArrayExpression or ObjectExpression')
|
||||
return new Error('expected ArrayExpression or ObjectExpression')
|
||||
}
|
||||
|
||||
export function getFirstArg(callExp: CallExpression): {
|
||||
export function getFirstArg(callExp: CallExpression):
|
||||
| {
|
||||
val: Value | [Value, Value] | [Value, Value, Value]
|
||||
tag?: Value
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const name = callExp?.callee?.name
|
||||
if (['lineTo', 'line'].includes(name)) {
|
||||
return getFirstArgValuesForXYFns(callExp)
|
||||
@ -1927,5 +2006,5 @@ export function getFirstArg(callExp: CallExpression): {
|
||||
// TODO probably needs it's own implementation
|
||||
return getFirstArgValuesForXYFns(callExp)
|
||||
}
|
||||
throw new Error('unexpected call expression: ' + name)
|
||||
return new Error('unexpected call expression: ' + name)
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ import {
|
||||
import { getSketchSegmentFromSourceRange } from './sketchConstraints'
|
||||
import { Selection } from 'lib/selections'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -31,6 +32,8 @@ async function testingSwapSketchFnCall({
|
||||
range: [startIndex, startIndex + callToSwap.length],
|
||||
}
|
||||
const ast = parse(inputCode)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const programMemory = await enginelessExecutor(ast)
|
||||
const selections = {
|
||||
codeBasedSelections: [range],
|
||||
@ -38,16 +41,22 @@ async function testingSwapSketchFnCall({
|
||||
}
|
||||
const transformInfos = getTransformInfos(selections, ast, constraintType)
|
||||
|
||||
if (!transformInfos) throw new Error('nope')
|
||||
const { modifiedAst } = transformAstSketchLines({
|
||||
if (!transformInfos)
|
||||
return Promise.reject(new Error('transformInfos undefined'))
|
||||
const ast2 = transformAstSketchLines({
|
||||
ast,
|
||||
programMemory,
|
||||
selectionRanges: selections,
|
||||
transformInfos,
|
||||
referenceSegName: '',
|
||||
})
|
||||
if (err(ast2)) return Promise.reject(ast2)
|
||||
|
||||
const newCode = recast(ast2.modifiedAst)
|
||||
if (err(newCode)) return Promise.reject(newCode)
|
||||
|
||||
return {
|
||||
newCode: recast(modifiedAst),
|
||||
newCode,
|
||||
originalRange: range.range,
|
||||
}
|
||||
}
|
||||
@ -355,10 +364,12 @@ const part001 = startSketchOn('XY')
|
||||
it('normal case works', async () => {
|
||||
const programMemory = await enginelessExecutor(parse(code))
|
||||
const index = code.indexOf('// normal-segment') - 7
|
||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
programMemory.root['part001'] as SketchGroup,
|
||||
[index, index]
|
||||
).segment
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
expect(segment).toEqual({
|
||||
type: 'ToPoint',
|
||||
to: [5.62, 1.79],
|
||||
@ -369,10 +380,12 @@ const part001 = startSketchOn('XY')
|
||||
it('verify it works when the segment is in the `start` property', async () => {
|
||||
const programMemory = await enginelessExecutor(parse(code))
|
||||
const index = code.indexOf('// segment-in-start') - 7
|
||||
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
programMemory.root['part001'] as SketchGroup,
|
||||
[index, index]
|
||||
).segment
|
||||
)
|
||||
if (err(_segment)) throw _segment
|
||||
const { __geoMeta, ...segment } = _segment.segment
|
||||
expect(segment).toEqual({
|
||||
to: [0, 0.04],
|
||||
from: [0, 0.04],
|
||||
|
@ -10,31 +10,39 @@ import {
|
||||
PathToNode,
|
||||
Value,
|
||||
} from '../wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function getSketchSegmentFromPathToNode(
|
||||
sketchGroup: SketchGroup,
|
||||
ast: Program,
|
||||
pathToNode: PathToNode
|
||||
): {
|
||||
):
|
||||
| {
|
||||
segment: SketchGroup['value'][number]
|
||||
index: number
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
// TODO: once pathTodNode is stored on program memory as part of execution,
|
||||
// we can check if the pathToNode matches the pathToNode of the sketchGroup.
|
||||
// For now we fall back to the sourceRange
|
||||
const node = getNodeFromPath<Value>(ast, pathToNode).node
|
||||
const nodeMeta = getNodeFromPath<Value>(ast, pathToNode)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const node = nodeMeta.node
|
||||
if (!node || typeof node.start !== 'number' || !node.end)
|
||||
throw new Error('no node found')
|
||||
return new Error('no node found')
|
||||
const sourceRange: SourceRange = [node.start, node.end]
|
||||
return getSketchSegmentFromSourceRange(sketchGroup, sourceRange)
|
||||
}
|
||||
export function getSketchSegmentFromSourceRange(
|
||||
sketchGroup: SketchGroup,
|
||||
[rangeStart, rangeEnd]: SourceRange
|
||||
): {
|
||||
):
|
||||
| {
|
||||
segment: SketchGroup['value'][number]
|
||||
index: number
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
const startSourceRange = sketchGroup.start?.__geoMeta.sourceRange
|
||||
if (
|
||||
startSourceRange &&
|
||||
@ -49,7 +57,7 @@ export function getSketchSegmentFromSourceRange(
|
||||
sourceRange[0] <= rangeStart && sourceRange[1] >= rangeEnd
|
||||
)
|
||||
const line = sketchGroup.value[lineIndex]
|
||||
if (!line) throw new Error('could not find matching line')
|
||||
if (!line) return new Error('could not find matching line')
|
||||
return {
|
||||
segment: line,
|
||||
index: lineIndex,
|
||||
|
@ -5,10 +5,12 @@ import {
|
||||
transformAstSketchLines,
|
||||
transformSecondarySketchLinesTagFirst,
|
||||
ConstraintType,
|
||||
ConstraintLevel,
|
||||
getConstraintLevelFromSourceRange,
|
||||
} from './sketchcombos'
|
||||
import { ToolTip } from '../../useStore'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { err } from 'lib/trap'
|
||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||
|
||||
beforeAll(async () => {
|
||||
@ -62,8 +64,10 @@ describe('testing getConstraintType', () => {
|
||||
|
||||
function getConstraintTypeFromSourceHelper(
|
||||
code: string
|
||||
): ReturnType<typeof getConstraintType> {
|
||||
): ReturnType<typeof getConstraintType> | Error {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
|
||||
const args = (ast.body[0] as any).expression.arguments[0].elements as [
|
||||
Value,
|
||||
Value
|
||||
@ -73,8 +77,10 @@ function getConstraintTypeFromSourceHelper(
|
||||
}
|
||||
function getConstraintTypeFromSourceHelper2(
|
||||
code: string
|
||||
): ReturnType<typeof getConstraintType> {
|
||||
): ReturnType<typeof getConstraintType> | Error {
|
||||
const ast = parse(code)
|
||||
if (err(ast)) return ast
|
||||
|
||||
const arg = (ast.body[0] as any).expression.arguments[0] as Value
|
||||
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
|
||||
return getConstraintType(arg, fnName)
|
||||
@ -200,6 +206,8 @@ const part001 = startSketchOn('XY')
|
||||
`
|
||||
it('should transform the ast', async () => {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('//'))
|
||||
@ -224,8 +232,10 @@ const part001 = startSketchOn('XY')
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})?.modifiedAst
|
||||
const newCode = recast(newAst)
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
||||
const newCode = recast(newAst.modifiedAst)
|
||||
expect(newCode).toBe(expectModifiedScript)
|
||||
})
|
||||
})
|
||||
@ -287,6 +297,8 @@ const part001 = startSketchOn('XY')
|
||||
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
|
||||
`
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('// select for horizontal constraint'))
|
||||
@ -312,8 +324,10 @@ const part001 = startSketchOn('XY')
|
||||
transformInfos,
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})?.modifiedAst
|
||||
const newCode = recast(newAst)
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
||||
const newCode = recast(newAst.modifiedAst)
|
||||
expect(newCode).toBe(expectModifiedScript)
|
||||
})
|
||||
it('should transform vertical lines the ast', async () => {
|
||||
@ -345,6 +359,8 @@ const part001 = startSketchOn('XY')
|
||||
|> yLineTo(myVar, %) // select for vertical constraint 10
|
||||
`
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) => ln.includes('// select for vertical constraint'))
|
||||
@ -370,8 +386,10 @@ const part001 = startSketchOn('XY')
|
||||
transformInfos,
|
||||
programMemory,
|
||||
referenceSegName: '',
|
||||
})?.modifiedAst
|
||||
const newCode = recast(newAst)
|
||||
})
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
|
||||
const newCode = recast(newAst.modifiedAst)
|
||||
expect(newCode).toBe(expectModifiedScript)
|
||||
})
|
||||
})
|
||||
@ -436,6 +454,8 @@ async function helperThing(
|
||||
constraint: ConstraintType
|
||||
): Promise<string> {
|
||||
const ast = parse(inputScript)
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
|
||||
const selectionRanges: Selections['codeBasedSelections'] = inputScript
|
||||
.split('\n')
|
||||
.filter((ln) =>
|
||||
@ -462,8 +482,13 @@ async function helperThing(
|
||||
selectionRanges: makeSelections(selectionRanges),
|
||||
transformInfos,
|
||||
programMemory,
|
||||
})?.modifiedAst
|
||||
return recast(newAst)
|
||||
})
|
||||
|
||||
if (err(newAst)) return Promise.reject(newAst)
|
||||
const recasted = recast(newAst.modifiedAst)
|
||||
|
||||
if (err(recasted)) return Promise.reject(recasted)
|
||||
return recasted
|
||||
}
|
||||
|
||||
describe('testing getConstraintLevelFromSourceRange', () => {
|
||||
@ -498,9 +523,7 @@ const part001 = startSketchOn('XY')
|
||||
|> xLine(-3.43 + 0, %) // full
|
||||
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
|
||||
const ast = parse(code)
|
||||
const constraintLevels: ReturnType<
|
||||
typeof getConstraintLevelFromSourceRange
|
||||
>['level'][] = ['full', 'partial', 'free']
|
||||
const constraintLevels: ConstraintLevel[] = ['full', 'partial', 'free']
|
||||
constraintLevels.forEach((constraintLevel) => {
|
||||
const recursivelySeachCommentsAndCheckConstraintLevel = (
|
||||
str: string,
|
||||
@ -514,8 +537,11 @@ const part001 = startSketchOn('XY')
|
||||
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
||||
[offsetIndex, offsetIndex],
|
||||
ast
|
||||
).level
|
||||
expect(expectedConstraintLevel).toBe(constraintLevel)
|
||||
)
|
||||
if (err(expectedConstraintLevel)) {
|
||||
throw expectedConstraintLevel
|
||||
}
|
||||
expect(expectedConstraintLevel.level).toBe(constraintLevel)
|
||||
return recursivelySeachCommentsAndCheckConstraintLevel(
|
||||
str,
|
||||
index + constraintLevel.length
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { TransformCallback, VarValues } from './stdTypes'
|
||||
import { toolTips, ToolTip } from '../../useStore'
|
||||
import { Selections, Selection } from 'lib/selections'
|
||||
import { cleanErrs, err } from 'lib/trap'
|
||||
import {
|
||||
CallExpression,
|
||||
Program,
|
||||
@ -75,8 +76,18 @@ function createCallWrapper(
|
||||
if (tag) {
|
||||
args.push(tag)
|
||||
}
|
||||
|
||||
const [hasErr, argsWOutErr] = cleanErrs(args)
|
||||
if (hasErr) {
|
||||
console.error(args)
|
||||
return {
|
||||
callExp: createCallExpression(a, args),
|
||||
callExp: createCallExpression('', []),
|
||||
valueUsedInTransform: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
callExp: createCallExpression(a, argsWOutErr),
|
||||
valueUsedInTransform,
|
||||
}
|
||||
}
|
||||
@ -1173,6 +1184,11 @@ export function getRemoveConstraintsTransform(
|
||||
|
||||
// check if the function is locked down and so can't be transformed
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
if (err(firstArg)) {
|
||||
console.error(firstArg)
|
||||
return false
|
||||
}
|
||||
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
return transformInfo
|
||||
}
|
||||
@ -1213,11 +1229,18 @@ export function removeSingleConstraint({
|
||||
ast,
|
||||
pathToCallExp,
|
||||
'CallExpression'
|
||||
).node
|
||||
if (callExp.type !== 'CallExpression') throw new Error('Invalid node type')
|
||||
)
|
||||
if (err(callExp)) {
|
||||
console.error(callExp)
|
||||
return false
|
||||
}
|
||||
if (callExp.node.type !== 'CallExpression') {
|
||||
console.error(new Error('Invalid node type'))
|
||||
return false
|
||||
}
|
||||
|
||||
const transform: TransformInfo = {
|
||||
tooltip: callExp.callee.name as any,
|
||||
tooltip: callExp.node.callee.name as any,
|
||||
createNode:
|
||||
({ tag, referenceSegName, varValues }) =>
|
||||
(_, rawValues) => {
|
||||
@ -1241,7 +1264,7 @@ export function removeSingleConstraint({
|
||||
})
|
||||
const objExp = createObjectExpression(expression)
|
||||
return createStdlibCallExpression(
|
||||
callExp.callee.name as any,
|
||||
callExp.node.callee.name as any,
|
||||
objExp,
|
||||
tag
|
||||
)
|
||||
@ -1266,7 +1289,7 @@ export function removeSingleConstraint({
|
||||
return varValue.value
|
||||
})
|
||||
return createStdlibCallExpression(
|
||||
callExp.callee.name as any,
|
||||
callExp.node.callee.name as any,
|
||||
createArrayExpression(values),
|
||||
tag
|
||||
)
|
||||
@ -1275,7 +1298,7 @@ export function removeSingleConstraint({
|
||||
// if (typeof arrayIndex !== 'number' || !objectProperty) must be single value input xLine, yLineTo etc
|
||||
|
||||
return createCallWrapper(
|
||||
callExp.callee.name as any,
|
||||
callExp.node.callee.name as any,
|
||||
rawValues[0].value,
|
||||
tag
|
||||
)
|
||||
@ -1301,6 +1324,11 @@ function getTransformMapPath(
|
||||
|
||||
// check if the function is locked down and so can't be transformed
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
if (err(firstArg)) {
|
||||
console.error(firstArg)
|
||||
return false
|
||||
}
|
||||
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
return false
|
||||
}
|
||||
@ -1387,13 +1415,18 @@ export function getTransformInfos(
|
||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||
getNodePathFromSourceRange(ast, range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) =>
|
||||
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
|
||||
const nodes = paths.map((pathToNode) =>
|
||||
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression')
|
||||
)
|
||||
|
||||
try {
|
||||
const theTransforms = nodes.map((node) => {
|
||||
const theTransforms = nodes.map((nodeMeta) => {
|
||||
if (err(nodeMeta)) {
|
||||
console.error(nodeMeta)
|
||||
return false
|
||||
}
|
||||
|
||||
const node = nodeMeta.node
|
||||
if (node?.type === 'CallExpression')
|
||||
return getTransformInfo(node, constraintType)
|
||||
|
||||
@ -1410,16 +1443,24 @@ export function getRemoveConstraintsTransforms(
|
||||
selectionRanges: Selections,
|
||||
ast: Program,
|
||||
constraintType: ConstraintType
|
||||
): TransformInfo[] {
|
||||
): TransformInfo[] | Error {
|
||||
// return ()
|
||||
const paths = selectionRanges.codeBasedSelections.map((selectionRange) =>
|
||||
getNodePathFromSourceRange(ast, selectionRange.range)
|
||||
)
|
||||
const nodes = paths.map(
|
||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
||||
const nodes = paths.map((pathToNode) =>
|
||||
getNodeFromPath<Value>(ast, pathToNode)
|
||||
)
|
||||
|
||||
const theTransforms = nodes.map((node) => {
|
||||
const theTransforms = nodes.map((nodeMeta) => {
|
||||
// Typescript is not smart enough to know node will never be Error
|
||||
// here, but we'll place a condition anyway
|
||||
if (err(nodeMeta)) {
|
||||
console.error(nodeMeta)
|
||||
return false
|
||||
}
|
||||
|
||||
const node = nodeMeta.node
|
||||
if (node?.type === 'CallExpression')
|
||||
return getRemoveConstraintsTransform(node, constraintType)
|
||||
|
||||
@ -1444,7 +1485,8 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
programMemory: ProgramMemory
|
||||
forceSegName?: string
|
||||
forceValueUsedInTransform?: Value
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
valueUsedInTransform?: number
|
||||
pathToNodeMap: PathToNodeMap
|
||||
@ -1452,15 +1494,14 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
tag: string
|
||||
isTagExisting: boolean
|
||||
}
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
// let node = JSON.parse(JSON.stringify(ast))
|
||||
const primarySelection = selectionRanges.codeBasedSelections[0].range
|
||||
|
||||
const { modifiedAst, tag, isTagExisting, pathToNode } = giveSketchFnCallTag(
|
||||
ast,
|
||||
primarySelection,
|
||||
forceSegName
|
||||
)
|
||||
const _tag = giveSketchFnCallTag(ast, primarySelection, forceSegName)
|
||||
if (err(_tag)) return _tag
|
||||
const { modifiedAst, tag, isTagExisting, pathToNode } = _tag
|
||||
|
||||
const result = transformAstSketchLines({
|
||||
ast: modifiedAst,
|
||||
@ -1474,6 +1515,8 @@ export function transformSecondarySketchLinesTagFirst({
|
||||
referenceSegName: tag,
|
||||
forceValueUsedInTransform,
|
||||
})
|
||||
if (err(result)) return result
|
||||
|
||||
const updatedPathToNodeMap = incrementPathToNodeMap(result.pathToNodeMap)
|
||||
updatedPathToNodeMap[0] = pathToNode
|
||||
|
||||
@ -1514,11 +1557,13 @@ export function transformAstSketchLines({
|
||||
referenceSegName: string
|
||||
forceValueUsedInTransform?: Value
|
||||
referencedSegmentRange?: Selection['range']
|
||||
}): {
|
||||
}):
|
||||
| {
|
||||
modifiedAst: Program
|
||||
valueUsedInTransform?: number
|
||||
pathToNodeMap: PathToNodeMap
|
||||
} {
|
||||
}
|
||||
| Error {
|
||||
// deep clone since we are mutating in a loop, of which any could fail
|
||||
let node = JSON.parse(JSON.stringify(ast))
|
||||
let _valueUsedInTransform // TODO should this be an array?
|
||||
@ -1528,18 +1573,21 @@ export function transformAstSketchLines({
|
||||
const callBack = transformInfos?.[index].createNode
|
||||
const transformTo = transformInfos?.[index].tooltip
|
||||
|
||||
if (!callBack || !transformTo) throw new Error('no callback helper')
|
||||
if (!callBack || !transformTo) return new Error('no callback helper')
|
||||
|
||||
const getNode = getNodeFromPathCurry(node, _pathToNode)
|
||||
|
||||
const callExp = getNode<CallExpression>('CallExpression')?.node
|
||||
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
|
||||
const callExp = getNode<CallExpression>('CallExpression')
|
||||
if (err(callExp)) return callExp
|
||||
const varDec = getNode<VariableDeclarator>('VariableDeclarator')
|
||||
if (err(varDec)) return varDec
|
||||
|
||||
const { val } = getFirstArg(callExp)
|
||||
const callBackTag = callExp.arguments[2]
|
||||
const firstArg = getFirstArg(callExp.node)
|
||||
if (err(firstArg)) return firstArg
|
||||
const callBackTag = callExp.node.arguments[2]
|
||||
const _referencedSegmentNameVal =
|
||||
callExp.arguments[0]?.type === 'ObjectExpression' &&
|
||||
callExp.arguments[0].properties?.find(
|
||||
callExp.node.arguments[0]?.type === 'ObjectExpression' &&
|
||||
callExp.node.arguments[0].properties?.find(
|
||||
(prop) => prop.key.name === 'intersectTag'
|
||||
)?.value
|
||||
const _referencedSegmentName =
|
||||
@ -1548,64 +1596,80 @@ export function transformAstSketchLines({
|
||||
_referencedSegmentNameVal.type === 'Literal' &&
|
||||
String(_referencedSegmentNameVal.value)) ||
|
||||
''
|
||||
const { val } = firstArg
|
||||
const [varValA, varValB] = Array.isArray(val) ? val : [val, val]
|
||||
|
||||
const varValues: VarValues = []
|
||||
|
||||
getConstraintInfo(callExp, '', _pathToNode).forEach((a) => {
|
||||
getConstraintInfo(callExp.node, '', _pathToNode).forEach((a) => {
|
||||
if (
|
||||
a.type === 'tangentialWithPrevious' ||
|
||||
a.type === 'horizontal' ||
|
||||
a.type === 'vertical'
|
||||
)
|
||||
return
|
||||
|
||||
const nodeMeta = getNodeFromPath<Value>(ast, a.pathToNode)
|
||||
if (err(nodeMeta)) return
|
||||
|
||||
if (a?.argPosition?.type === 'arrayItem') {
|
||||
varValues.push({
|
||||
type: 'arrayItem',
|
||||
index: a.argPosition.index,
|
||||
value: getNodeFromPath<Value>(ast, a.pathToNode).node,
|
||||
value: nodeMeta.node,
|
||||
argType: a.type,
|
||||
})
|
||||
} else if (a?.argPosition?.type === 'objectProperty') {
|
||||
varValues.push({
|
||||
type: 'objectProperty',
|
||||
key: a.argPosition.key,
|
||||
value: getNodeFromPath<Value>(ast, a.pathToNode).node,
|
||||
value: nodeMeta.node,
|
||||
argType: a.type,
|
||||
})
|
||||
} else if (a?.argPosition?.type === 'singleValue') {
|
||||
varValues.push({
|
||||
type: 'singleValue',
|
||||
argType: a.type,
|
||||
value: getNodeFromPath<Value>(ast, a.pathToNode).node,
|
||||
value: nodeMeta.node,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const varName = varDec.id.name
|
||||
const varName = varDec.node.id.name
|
||||
let sketchGroup = programMemory.root?.[varName]
|
||||
if (sketchGroup.type === 'ExtrudeGroup') {
|
||||
sketchGroup = sketchGroup.sketchGroup
|
||||
}
|
||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup')
|
||||
throw new Error('not a sketch group')
|
||||
const seg = getSketchSegmentFromPathToNode(
|
||||
return new Error('not a sketch group')
|
||||
const segMeta = getSketchSegmentFromPathToNode(
|
||||
sketchGroup,
|
||||
ast,
|
||||
_pathToNode
|
||||
).segment
|
||||
const referencedSegment = referencedSegmentRange
|
||||
? getSketchSegmentFromSourceRange(sketchGroup, referencedSegmentRange)
|
||||
.segment
|
||||
: sketchGroup.value.find((path) => path.name === _referencedSegmentName)
|
||||
)
|
||||
if (err(segMeta)) return segMeta
|
||||
|
||||
const seg = segMeta.segment
|
||||
let referencedSegment
|
||||
if (referencedSegmentRange) {
|
||||
const _segment = getSketchSegmentFromSourceRange(
|
||||
sketchGroup,
|
||||
referencedSegmentRange
|
||||
)
|
||||
if (err(_segment)) return _segment
|
||||
referencedSegment = _segment.segment
|
||||
} else {
|
||||
referencedSegment = sketchGroup.value.find(
|
||||
(path) => path.name === _referencedSegmentName
|
||||
)
|
||||
}
|
||||
const { to, from } = seg
|
||||
const { modifiedAst, valueUsedInTransform, pathToNode } = replaceSketchLine(
|
||||
{
|
||||
const replacedSketchLine = replaceSketchLine({
|
||||
node: node,
|
||||
programMemory,
|
||||
pathToNode: _pathToNode,
|
||||
referencedSegment,
|
||||
fnName: transformTo || (callExp.callee.name as ToolTip),
|
||||
fnName: transformTo || (callExp.node.callee.name as ToolTip),
|
||||
to,
|
||||
from,
|
||||
createCallback: callBack({
|
||||
@ -1616,9 +1680,10 @@ export function transformAstSketchLines({
|
||||
tag: callBackTag,
|
||||
forceValueUsedInTransform,
|
||||
}),
|
||||
}
|
||||
)
|
||||
})
|
||||
if (err(replacedSketchLine)) return replacedSketchLine
|
||||
|
||||
const { modifiedAst, valueUsedInTransform, pathToNode } = replacedSketchLine
|
||||
node = modifiedAst
|
||||
pathToNodeMap[index] = pathToNode
|
||||
if (typeof valueUsedInTransform === 'number') {
|
||||
@ -1627,11 +1692,17 @@ export function transformAstSketchLines({
|
||||
}
|
||||
|
||||
if ('codeBasedSelections' in selectionRanges) {
|
||||
selectionRanges.codeBasedSelections.forEach(({ range }, index) =>
|
||||
// If the processing of any of the selections failed, return the first error
|
||||
const maybeProcessErrors = selectionRanges.codeBasedSelections
|
||||
.map(({ range }, index) =>
|
||||
processSelection(getNodePathFromSourceRange(node, range), index)
|
||||
)
|
||||
.filter(err)
|
||||
|
||||
if (maybeProcessErrors.length) return maybeProcessErrors[0]
|
||||
} else {
|
||||
selectionRanges.forEach(processSelection)
|
||||
const maybeProcessErrors = selectionRanges.map(processSelection).filter(err)
|
||||
if (maybeProcessErrors.length) return maybeProcessErrors[0]
|
||||
}
|
||||
|
||||
return {
|
||||
@ -1672,20 +1743,27 @@ function getArgLiteralVal(arg: Value): number {
|
||||
return arg?.type === 'Literal' ? Number(arg.value) : 0
|
||||
}
|
||||
|
||||
export type ConstraintLevel = 'free' | 'partial' | 'full'
|
||||
|
||||
export function getConstraintLevelFromSourceRange(
|
||||
cursorRange: Selection['range'],
|
||||
ast: Program
|
||||
): { range: [number, number]; level: 'free' | 'partial' | 'full' } {
|
||||
const { node: sketchFnExp } = getNodeFromPath<CallExpression>(
|
||||
ast: Program | Error
|
||||
): Error | { range: [number, number]; level: ConstraintLevel } {
|
||||
if (err(ast)) return ast
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
ast,
|
||||
getNodePathFromSourceRange(ast, cursorRange),
|
||||
'CallExpression'
|
||||
)
|
||||
if (err(nodeMeta)) return nodeMeta
|
||||
|
||||
const { node: sketchFnExp } = nodeMeta
|
||||
const name = sketchFnExp?.callee?.name as ToolTip
|
||||
const range: [number, number] = [sketchFnExp.start, sketchFnExp.end]
|
||||
if (!toolTips.includes(name)) return { level: 'free', range: range }
|
||||
|
||||
const firstArg = getFirstArg(sketchFnExp)
|
||||
if (err(firstArg)) return firstArg
|
||||
|
||||
// check if the function is fully constrained
|
||||
if (isNotLiteralArrayOrStatic(firstArg.val)) {
|
||||
|
@ -114,19 +114,25 @@ export interface ConstrainInfo {
|
||||
}
|
||||
|
||||
export interface SketchLineHelper {
|
||||
add: (a: addCall) => {
|
||||
add: (a: addCall) =>
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
valueUsedInTransform?: number
|
||||
}
|
||||
updateArgs: (a: updateArgs) => {
|
||||
| Error
|
||||
updateArgs: (a: updateArgs) =>
|
||||
| {
|
||||
modifiedAst: Program
|
||||
pathToNode: PathToNode
|
||||
}
|
||||
addTag: (a: ModifyAstBase) => {
|
||||
| Error
|
||||
addTag: (a: ModifyAstBase) =>
|
||||
| {
|
||||
modifiedAst: Program
|
||||
tag: string
|
||||
}
|
||||
| Error
|
||||
getConstraintInfo: (
|
||||
callExp: CallExpression,
|
||||
code: string,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { lexer, initPromise } from './wasm'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
beforeAll(async () => {
|
||||
await initPromise
|
||||
@ -369,10 +370,13 @@ const ya = 6 */' from 14 to 50`,
|
||||
|
||||
// helpers
|
||||
|
||||
const stringSummaryLexer = (input: string) =>
|
||||
lexer(input).map(
|
||||
const stringSummaryLexer = (input: string) => {
|
||||
const tokens = lexer(input)
|
||||
if (err(tokens)) return []
|
||||
return tokens.map(
|
||||
({ type, value, start, end }) =>
|
||||
`${type.padEnd(12, ' ')} ${`'${value}'`.padEnd(10, ' ')} from ${String(
|
||||
start
|
||||
).padEnd(3, ' ')} to ${end}`
|
||||
)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { Program, PathToNode } from './wasm'
|
||||
import { getNodeFromPath } from './queryAst'
|
||||
import { ArtifactMap } from './std/engineConnection'
|
||||
import { isOverlap } from 'lib/utils'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export function pathMapToSelections(
|
||||
ast: Program,
|
||||
@ -14,7 +15,9 @@ export function pathMapToSelections(
|
||||
codeBasedSelections: [],
|
||||
}
|
||||
Object.entries(pathToNodeMap).forEach(([index, path]) => {
|
||||
const node = getNodeFromPath(ast, path).node as any
|
||||
const nodeMeta = getNodeFromPath<any>(ast, path)
|
||||
if (err(nodeMeta)) return
|
||||
const node = nodeMeta.node as any
|
||||
const type = prevSelections.codeBasedSelections[Number(index)].type
|
||||
if (node) {
|
||||
newSelections.codeBasedSelections.push({
|
||||
|
117
src/lang/wasm.ts
117
src/lang/wasm.ts
@ -33,6 +33,7 @@ import { TEST } from 'env'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||
import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||
@ -108,7 +109,7 @@ const initialise = async () => {
|
||||
return await init(buffer)
|
||||
} catch (e) {
|
||||
console.log('Error initialising WASM', e)
|
||||
throw e
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,20 +118,19 @@ export const initPromise = initialise()
|
||||
export const rangeTypeFix = (ranges: number[][]): [number, number][] =>
|
||||
ranges.map(([start, end]) => [start, end])
|
||||
|
||||
export const parse = (code: string): Program => {
|
||||
export const parse = (code: string | Error): Program | Error => {
|
||||
if (err(code)) return code
|
||||
|
||||
try {
|
||||
const program: Program = parse_wasm(code)
|
||||
return program
|
||||
} catch (e: any) {
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
throw kclError
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,10 +147,12 @@ export interface ProgramMemory {
|
||||
|
||||
export const executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {}, return: null },
|
||||
programMemory: ProgramMemory | Error = { root: {}, return: null },
|
||||
engineCommandManager: EngineCommandManager,
|
||||
isMock: boolean = false
|
||||
): Promise<ProgramMemory> => {
|
||||
if (err(programMemory)) return Promise.reject(programMemory)
|
||||
|
||||
engineCommandManager.startNewSession()
|
||||
const _programMemory = await _executor(
|
||||
node,
|
||||
@ -166,10 +168,12 @@ export const executor = async (
|
||||
|
||||
export const _executor = async (
|
||||
node: Program,
|
||||
programMemory: ProgramMemory = { root: {}, return: null },
|
||||
programMemory: ProgramMemory | Error = { root: {}, return: null },
|
||||
engineCommandManager: EngineCommandManager,
|
||||
isMock: boolean
|
||||
): Promise<ProgramMemory> => {
|
||||
if (err(programMemory)) return Promise.reject(programMemory)
|
||||
|
||||
try {
|
||||
let baseUnit = 'mm'
|
||||
if (!TEST) {
|
||||
@ -197,20 +201,12 @@ export const _executor = async (
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
throw kclError
|
||||
return Promise.reject(kclError)
|
||||
}
|
||||
}
|
||||
|
||||
export const recast = (ast: Program): string => {
|
||||
try {
|
||||
const s: string = recast_wasm(JSON.stringify(ast))
|
||||
return s
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
console.log('recast error', e)
|
||||
throw e
|
||||
}
|
||||
export const recast = (ast: Program): string | Error => {
|
||||
return recast_wasm(JSON.stringify(ast))
|
||||
}
|
||||
|
||||
export const makeDefaultPlanes = async (
|
||||
@ -224,19 +220,12 @@ export const makeDefaultPlanes = async (
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
console.log('make default planes error', e)
|
||||
throw e
|
||||
return Promise.reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
export function lexer(str: string): Token[] {
|
||||
try {
|
||||
const tokens: Token[] = lexer_wasm(str)
|
||||
return tokens
|
||||
} catch (e) {
|
||||
// TODO: do something real with the error.
|
||||
console.log('lexer error', e)
|
||||
throw e
|
||||
}
|
||||
export function lexer(str: string): Token[] | Error {
|
||||
return lexer_wasm(str)
|
||||
}
|
||||
|
||||
export const modifyAstForSketch = async (
|
||||
@ -265,7 +254,7 @@ export const modifyAstForSketch = async (
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
throw kclError
|
||||
return Promise.reject(kclError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,21 +301,18 @@ export function getTangentialArcToInfo({
|
||||
}
|
||||
}
|
||||
|
||||
export function programMemoryInit(): ProgramMemory {
|
||||
export function programMemoryInit(): ProgramMemory | Error {
|
||||
try {
|
||||
const memory: ProgramMemory = program_memory_init()
|
||||
return memory
|
||||
} catch (e: any) {
|
||||
console.log(e)
|
||||
const parsed: RustKclError = JSON.parse(e.toString())
|
||||
const kclError = new KCLError(
|
||||
return new KCLError(
|
||||
parsed.kind,
|
||||
parsed.msg,
|
||||
rangeTypeFix(parsed.sourceRanges)
|
||||
)
|
||||
|
||||
console.log(kclError)
|
||||
throw kclError
|
||||
}
|
||||
}
|
||||
|
||||
@ -354,66 +340,35 @@ export async function coreDump(
|
||||
return dump
|
||||
} catch (e: any) {
|
||||
console.error('CoreDump: error', e)
|
||||
throw new Error(`Error getting core dump: ${e}`)
|
||||
return Promise.reject(new Error(`Error getting core dump: ${e}`))
|
||||
}
|
||||
}
|
||||
|
||||
export function tomlStringify(toml: any): string {
|
||||
try {
|
||||
const s: string = toml_stringify(JSON.stringify(toml))
|
||||
return s
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error stringifying toml: ${e}`)
|
||||
}
|
||||
export function tomlStringify(toml: any): string | Error {
|
||||
return toml_stringify(JSON.stringify(toml))
|
||||
}
|
||||
|
||||
export function defaultAppSettings(): Configuration {
|
||||
try {
|
||||
const settings: Configuration = default_app_settings()
|
||||
return settings
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error getting default app settings: ${e}`)
|
||||
}
|
||||
export function defaultAppSettings(): Configuration | Error {
|
||||
return default_app_settings()
|
||||
}
|
||||
|
||||
export function parseAppSettings(toml: string): Configuration {
|
||||
try {
|
||||
const settings: Configuration = parse_app_settings(toml)
|
||||
return settings
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error parsing app settings: ${e}`)
|
||||
}
|
||||
export function parseAppSettings(toml: string): Configuration | Error {
|
||||
return parse_app_settings(toml)
|
||||
}
|
||||
|
||||
export function defaultProjectSettings(): ProjectConfiguration {
|
||||
try {
|
||||
const settings: ProjectConfiguration = default_project_settings()
|
||||
return settings
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error getting default project settings: ${e}`)
|
||||
}
|
||||
export function defaultProjectSettings(): ProjectConfiguration | Error {
|
||||
return default_project_settings()
|
||||
}
|
||||
|
||||
export function parseProjectSettings(toml: string): ProjectConfiguration {
|
||||
try {
|
||||
const settings: ProjectConfiguration = parse_project_settings(toml)
|
||||
return settings
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error parsing project settings: ${e}`)
|
||||
}
|
||||
export function parseProjectSettings(
|
||||
toml: string
|
||||
): ProjectConfiguration | Error {
|
||||
return parse_project_settings(toml)
|
||||
}
|
||||
|
||||
export function parseProjectRoute(
|
||||
configuration: Configuration,
|
||||
route_str: string
|
||||
): ProjectRoute {
|
||||
try {
|
||||
const route: ProjectRoute = parse_project_route(
|
||||
JSON.stringify(configuration),
|
||||
route_str
|
||||
)
|
||||
return route
|
||||
} catch (e: any) {
|
||||
throw new Error(`Error parsing project route: ${e}`)
|
||||
}
|
||||
): ProjectRoute | Error {
|
||||
return parse_project_route(JSON.stringify(configuration), route_str)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import { ProjectRoute } from 'wasm-lib/kcl/bindings/ProjectRoute'
|
||||
import { parseProjectRoute, readAppSettingsFile } from './tauri'
|
||||
import { parseProjectRoute as parseProjectRouteWasm } from 'lang/wasm'
|
||||
import { readLocalStorageAppSettingsFile } from './settings/settingsUtils'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
const prependRoutes =
|
||||
(routesObject: Record<string, string>) => (prepend: string) => {
|
||||
@ -33,21 +34,25 @@ export const BROWSER_PATH = `%2F${BROWSER_PROJECT_NAME}%2F${BROWSER_FILE_NAME}${
|
||||
|
||||
export async function getProjectMetaByRouteId(
|
||||
id?: string,
|
||||
configuration?: Configuration
|
||||
configuration?: Configuration | Error
|
||||
): Promise<ProjectRoute | undefined> {
|
||||
if (!id) return undefined
|
||||
|
||||
const inTauri = isTauri()
|
||||
|
||||
if (!configuration) {
|
||||
if (configuration === undefined) {
|
||||
configuration = inTauri
|
||||
? await readAppSettingsFile()
|
||||
: readLocalStorageAppSettingsFile()
|
||||
}
|
||||
|
||||
if (err(configuration)) return Promise.reject(configuration)
|
||||
|
||||
const route = inTauri
|
||||
? await parseProjectRoute(configuration, id)
|
||||
: parseProjectRouteWasm(configuration, id)
|
||||
|
||||
if (err(route)) return Promise.reject(route)
|
||||
|
||||
return route
|
||||
}
|
||||
|
@ -6,16 +6,16 @@ export default async function screenshot(
|
||||
htmlRef: React.RefObject<HTMLDivElement> | null
|
||||
): Promise<string> {
|
||||
if (htmlRef === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
return Promise.reject(new Error('htmlRef is null'))
|
||||
}
|
||||
if (htmlRef.current === null) {
|
||||
throw new Error('htmlRef is null')
|
||||
return Promise.reject(new Error('htmlRef is null'))
|
||||
}
|
||||
return html2canvas(htmlRef.current)
|
||||
.then((canvas) => {
|
||||
return canvas.toDataURL()
|
||||
})
|
||||
.catch((error) => {
|
||||
throw error
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
|
@ -29,6 +29,7 @@ import {
|
||||
import { Mesh, Object3D, Object3DEventMap } from 'three'
|
||||
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
|
||||
import { PathToNodeMap } from 'lang/std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
@ -168,11 +169,16 @@ export function getEventForSegmentSelection(
|
||||
// So we want to make sure we have and updated ast with
|
||||
// accurate source ranges
|
||||
const updatedAst = parse(codeManager.code)
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
if (err(updatedAst)) return null
|
||||
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
updatedAst,
|
||||
pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (err(nodeMeta)) return null
|
||||
|
||||
const node = nodeMeta.node
|
||||
const range: SourceRange = [node.start, node.end]
|
||||
return {
|
||||
type: 'Set selection',
|
||||
@ -278,14 +284,10 @@ export function processCodeMirrorRanges({
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
let updated: Program
|
||||
try {
|
||||
updated = parse(recast(kclManager.ast))
|
||||
} catch (e) {
|
||||
console.error('error parsing code in processCodeMirrorRanges', e)
|
||||
return
|
||||
}
|
||||
function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
const updated = parse(recast(kclManager.ast))
|
||||
if (err(updated)) return
|
||||
|
||||
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
|
||||
if (
|
||||
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes(
|
||||
@ -293,11 +295,13 @@ export function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
||||
)
|
||||
)
|
||||
return
|
||||
const node = getNodeFromPath<CallExpression>(
|
||||
const nodeMeta = getNodeFromPath<CallExpression>(
|
||||
updated,
|
||||
segmentGroup.userData.pathToNode,
|
||||
'CallExpression'
|
||||
).node
|
||||
)
|
||||
if (err(nodeMeta)) return
|
||||
const node = nodeMeta.node
|
||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||
return isOverlap(selection.range, [node.start, node.end])
|
||||
})
|
||||
@ -571,18 +575,27 @@ export async function sendSelectEventToEngine(
|
||||
export function updateSelections(
|
||||
pathToNodeMap: PathToNodeMap,
|
||||
prevSelectionRanges: Selections,
|
||||
ast: Program
|
||||
): Selections {
|
||||
return {
|
||||
...prevSelectionRanges,
|
||||
codeBasedSelections: Object.entries(pathToNodeMap).map(
|
||||
([index, pathToNode]): Selection => {
|
||||
const node = getNodeFromPath<Value>(ast, pathToNode).node
|
||||
ast: Program | Error
|
||||
): Selections | Error {
|
||||
if (err(ast)) return ast
|
||||
|
||||
const newSelections = Object.entries(pathToNodeMap)
|
||||
.map(([index, pathToNode]): Selection | undefined => {
|
||||
const nodeMeta = getNodeFromPath<Value>(ast, pathToNode)
|
||||
if (err(nodeMeta)) return undefined
|
||||
const node = nodeMeta.node
|
||||
return {
|
||||
range: [node.start, node.end],
|
||||
type: prevSelectionRanges.codeBasedSelections[Number(index)]?.type,
|
||||
}
|
||||
}
|
||||
),
|
||||
})
|
||||
.filter((x?: Selection) => x !== undefined) as Selection[]
|
||||
|
||||
return {
|
||||
codeBasedSelections:
|
||||
newSelections.length > 0
|
||||
? newSelections
|
||||
: prevSelectionRanges.codeBasedSelections,
|
||||
otherSelections: prevSelectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Setting, createSettings, settings } from 'lib/settings/initialSettings'
|
||||
import { SaveSettingsPayload, SettingsLevel } from './settingsTypes'
|
||||
import { isTauri } from 'lib/isTauri'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
defaultAppSettings,
|
||||
defaultProjectSettings,
|
||||
@ -101,7 +102,7 @@ function localStorageProjectSettingsPath() {
|
||||
return '/' + BROWSER_PROJECT_NAME + '/project.toml'
|
||||
}
|
||||
|
||||
export function readLocalStorageAppSettingsFile(): Configuration {
|
||||
export function readLocalStorageAppSettingsFile(): Configuration | Error {
|
||||
// TODO: Remove backwards compatibility after a few releases.
|
||||
let stored =
|
||||
localStorage.getItem(localStorageAppSettingsPath()) ??
|
||||
@ -116,12 +117,16 @@ export function readLocalStorageAppSettingsFile(): Configuration {
|
||||
return parseAppSettings(stored)
|
||||
} catch (e) {
|
||||
const settings = defaultAppSettings()
|
||||
localStorage.setItem(localStorageAppSettingsPath(), tomlStringify(settings))
|
||||
if (err(settings)) return settings
|
||||
const tomlStr = tomlStringify(settings)
|
||||
if (err(tomlStr)) return tomlStr
|
||||
|
||||
localStorage.setItem(localStorageAppSettingsPath(), tomlStr)
|
||||
return settings
|
||||
}
|
||||
}
|
||||
|
||||
function readLocalStorageProjectSettingsFile(): ProjectConfiguration {
|
||||
function readLocalStorageProjectSettingsFile(): ProjectConfiguration | Error {
|
||||
// TODO: Remove backwards compatibility after a few releases.
|
||||
let stored = localStorage.getItem(localStorageProjectSettingsPath()) ?? ''
|
||||
|
||||
@ -129,15 +134,16 @@ function readLocalStorageProjectSettingsFile(): ProjectConfiguration {
|
||||
return defaultProjectSettings()
|
||||
}
|
||||
|
||||
try {
|
||||
return parseProjectSettings(stored)
|
||||
} catch (e) {
|
||||
const projectSettings = parseProjectSettings(stored)
|
||||
if (err(projectSettings)) {
|
||||
const settings = defaultProjectSettings()
|
||||
localStorage.setItem(
|
||||
localStorageProjectSettingsPath(),
|
||||
tomlStringify(settings)
|
||||
)
|
||||
const tomlStr = tomlStringify(settings)
|
||||
if (err(tomlStr)) return tomlStr
|
||||
|
||||
localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
|
||||
return settings
|
||||
} else {
|
||||
return projectSettings
|
||||
}
|
||||
}
|
||||
|
||||
@ -161,6 +167,9 @@ export async function loadAndValidateSettings(
|
||||
const appSettings = inTauri
|
||||
? await readAppSettingsFile()
|
||||
: readLocalStorageAppSettingsFile()
|
||||
|
||||
if (err(appSettings)) return Promise.reject(appSettings)
|
||||
|
||||
// Convert the app settings to the JS settings format.
|
||||
const appSettingsPayload = configurationToSettingsPayload(appSettings)
|
||||
setSettingsAtLevel(settings, 'user', appSettingsPayload)
|
||||
@ -171,6 +180,9 @@ export async function loadAndValidateSettings(
|
||||
? await readProjectSettingsFile(projectPath)
|
||||
: readLocalStorageProjectSettingsFile()
|
||||
|
||||
if (err(projectSettings))
|
||||
return Promise.reject(new Error('Invalid project settings'))
|
||||
|
||||
const projectSettingsPayload =
|
||||
projectConfigurationToSettingsPayload(projectSettings)
|
||||
setSettingsAtLevel(settings, 'project', projectSettingsPayload)
|
||||
@ -191,17 +203,20 @@ export async function saveSettings(
|
||||
// Get the user settings.
|
||||
const jsAppSettings = getChangedSettingsAtLevel(allSettings, 'user')
|
||||
const tomlString = tomlStringify({ settings: jsAppSettings })
|
||||
if (err(tomlString)) return
|
||||
|
||||
// Parse this as a Configuration.
|
||||
const appSettings = parseAppSettings(tomlString)
|
||||
if (err(appSettings)) return
|
||||
|
||||
const tomlString2 = tomlStringify(appSettings)
|
||||
if (err(tomlString2)) return
|
||||
|
||||
// Write the app settings.
|
||||
if (inTauri) {
|
||||
await writeAppSettingsFile(appSettings)
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
localStorageAppSettingsPath(),
|
||||
tomlStringify(appSettings)
|
||||
)
|
||||
localStorage.setItem(localStorageAppSettingsPath(), tomlString2)
|
||||
}
|
||||
|
||||
if (!projectPath) {
|
||||
@ -212,17 +227,21 @@ export async function saveSettings(
|
||||
// Get the project settings.
|
||||
const jsProjectSettings = getChangedSettingsAtLevel(allSettings, 'project')
|
||||
const projectTomlString = tomlStringify({ settings: jsProjectSettings })
|
||||
if (err(projectTomlString)) return
|
||||
|
||||
// Parse this as a Configuration.
|
||||
const projectSettings = parseProjectSettings(projectTomlString)
|
||||
if (err(projectSettings)) return
|
||||
|
||||
const tomlStr = tomlStringify(projectSettings)
|
||||
|
||||
if (err(tomlStr)) return
|
||||
|
||||
// Write the project settings.
|
||||
if (inTauri) {
|
||||
await writeProjectSettingsFile(projectPath, projectSettings)
|
||||
} else {
|
||||
localStorage.setItem(
|
||||
localStorageProjectSettingsPath(),
|
||||
tomlStringify(projectSettings)
|
||||
)
|
||||
localStorage.setItem(localStorageProjectSettingsPath(), tomlStr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
import { Models } from '@kittycad/lib'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { err } from 'lib/trap'
|
||||
|
||||
type WebSocketResponse = Models['WebSocketResponse_type']
|
||||
|
||||
@ -56,13 +57,13 @@ class MockEngineCommandManager {
|
||||
commandStr: string
|
||||
): Promise<any> {
|
||||
if (id === undefined) {
|
||||
throw new Error('id is undefined')
|
||||
return Promise.reject(new Error('id is undefined'))
|
||||
}
|
||||
if (rangeStr === undefined) {
|
||||
throw new Error('rangeStr is undefined')
|
||||
return Promise.reject(new Error('rangeStr is undefined'))
|
||||
}
|
||||
if (commandStr === undefined) {
|
||||
throw new Error('commandStr is undefined')
|
||||
return Promise.reject(new Error('commandStr is undefined'))
|
||||
}
|
||||
const command: EngineCommand = JSON.parse(commandStr)
|
||||
const range: SourceRange = JSON.parse(rangeStr)
|
||||
@ -73,9 +74,12 @@ class MockEngineCommandManager {
|
||||
}
|
||||
|
||||
export async function enginelessExecutor(
|
||||
ast: Program,
|
||||
pm: ProgramMemory = { root: {}, return: null }
|
||||
ast: Program | Error,
|
||||
pm: ProgramMemory | Error = { root: {}, return: null }
|
||||
): Promise<ProgramMemory> {
|
||||
if (err(ast)) return Promise.reject(ast)
|
||||
if (err(pm)) return Promise.reject(pm)
|
||||
|
||||
const mockEngineCommandManager = new MockEngineCommandManager({
|
||||
setIsStreamReady: () => {},
|
||||
setMediaStream: () => {},
|
||||
|
48
src/lib/trap.ts
Normal file
48
src/lib/trap.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
type ExcludeErr<T> = Exclude<T, Error>
|
||||
|
||||
// Used to bubble errors up
|
||||
export function err<T>(value: ExcludeErr<T> | Error): value is Error {
|
||||
if (!(value instanceof Error)) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/** Takes array of maybe error and types narrows them into
|
||||
* @returns [hasErr, arrayWithoutErr, arrayWithErr]
|
||||
*/
|
||||
export function cleanErrs<T>(
|
||||
value: Array<ExcludeErr<T> | Error>
|
||||
): [boolean, Array<ExcludeErr<T>>, Array<Error>] {
|
||||
const argsWOutErr: Array<ExcludeErr<T>> = []
|
||||
const argsWErr: Array<Error> = []
|
||||
for (const v of value) {
|
||||
if (err(v)) {
|
||||
argsWErr.push(v)
|
||||
} else {
|
||||
argsWOutErr.push(v)
|
||||
}
|
||||
}
|
||||
return [argsWOutErr.length !== value.length, argsWOutErr, argsWErr]
|
||||
}
|
||||
|
||||
// Used to report errors to user at a certain point in execution
|
||||
export function trap<T>(
|
||||
value: ExcludeErr<T> | Error,
|
||||
opts?: {
|
||||
altErr?: Error
|
||||
suppress?: boolean
|
||||
}
|
||||
): value is Error {
|
||||
if (!err(value)) {
|
||||
return false
|
||||
}
|
||||
|
||||
console.error(value)
|
||||
opts?.suppress ||
|
||||
toast.error((opts?.altErr ?? value ?? new Error('Unknown')).toString())
|
||||
return true
|
||||
}
|
@ -6,6 +6,7 @@ import { PrevVariable, findAllPreviousVariables } from 'lang/queryAst'
|
||||
import { Value, parse } from 'lang/wasm'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { executeAst } from 'useStore'
|
||||
import { trap } from 'lib/trap'
|
||||
|
||||
const isValidVariableName = (name: string) =>
|
||||
/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)
|
||||
@ -85,6 +86,8 @@ export function useCalculateKclExpression({
|
||||
const execAstAndSetResult = async () => {
|
||||
const _code = `const __result__ = ${value}`
|
||||
const ast = parse(_code)
|
||||
if (trap(ast, { suppress: true })) return
|
||||
|
||||
const _programMem: any = { root: {}, return: null }
|
||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||
|
@ -123,7 +123,7 @@ async function getUser(context: UserContext) {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
if (!token && isTauri()) throw new Error('No token found')
|
||||
if (!token && isTauri()) return Promise.reject(new Error('No token found'))
|
||||
if (token) headers['Authorization'] = `Bearer ${context.token}`
|
||||
|
||||
if (SKIP_AUTH)
|
||||
@ -144,7 +144,7 @@ async function getUser(context: UserContext) {
|
||||
|
||||
const user = await userPromise
|
||||
|
||||
if ('error_code' in user) throw new Error(user.message)
|
||||
if ('error_code' in user) return Promise.reject(new Error(user.message))
|
||||
|
||||
return {
|
||||
user: user as Models['User_type'],
|
||||
|
@ -501,7 +501,7 @@ export const commandBarMachine = createMachine(
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error validating argument', context, e)
|
||||
throw e
|
||||
return reject(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user