Compare commits

..

9 Commits

Author SHA1 Message Date
ae068b7ed6 closer
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 17:02:37 -07:00
5bdc899831 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:25:08 -07:00
e0e6acf231 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:08:11 -07:00
668bc863df updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:56 -07:00
59ce602d6e updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 16:06:44 -07:00
8ce819b28f updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:58:08 -07:00
ed5e276377 working
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:47:38 -07:00
8549bd9486 updates
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:41:03 -07:00
56f9078812 init
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-04-19 13:33:31 -07:00
303 changed files with 3897 additions and 7279 deletions

View File

@ -1,6 +1,5 @@
VITE_KC_API_WS_MODELING_URL=wss://api.dev.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_WASM_OVERRIDE_URL=""
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000

View File

@ -1,6 +1,5 @@
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_WASM_OVERRIDE_URL=""
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000

View File

@ -14,31 +14,9 @@ permissions:
pull-requests: write
jobs:
check-wasm-lib-changes:
runs-on: ubuntu-latest
outputs:
url: ${{ steps.set-output.outputs.url }}
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # Fetches all history for all branches and tags
- name: Check for changes in src/wasm-lib
id: set-output
run: |
if git diff --quiet origin/main...HEAD -- src/wasm-lib; then
echo "url=https://app.zoo.dev" >> $GITHUB_OUTPUT
echo "No changes detected in src/wasm-lib"
else
echo "Changes detected in src/wasm-lib"
echo "url=" >> $GITHUB_OUTPUT
fi
playwright-ubuntu:
timeout-minutes: 60
runs-on: ubuntu-latest-8-cores
needs: check-wasm-lib-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -50,19 +28,13 @@ jobs:
run: yarn
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Print WASM Lib Changes URL
run: |
echo "WASM Lib Changes URL: ${{ needs.check-wasm-lib-changes.outputs.url }}"
- name: Setup Rust
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: build wasm
if: ${{ needs.check-wasm-lib-changes.outputs.url }} == ''
run: yarn build:wasm
- name: build web
run: yarn build:local
@ -72,7 +44,6 @@ jobs:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:
@ -108,7 +79,6 @@ jobs:
env:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:
@ -119,7 +89,6 @@ jobs:
playwright-macos:
timeout-minutes: 60
runs-on: macos-14
needs: check-wasm-lib-changes
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
@ -131,15 +100,12 @@ jobs:
- name: Install Playwright Browsers
run: yarn playwright install --with-deps
- name: Setup Rust
if: needs.check-wasm-lib-changes.outputs.url == ''
uses: dtolnay/rust-toolchain@stable
- name: Cache wasm
if: needs.check-wasm-lib-changes.outputs.url == ''
uses: Swatinem/rust-cache@v2
with:
workspaces: './src/wasm-lib'
- name: build wasm
if: needs.check-wasm-lib-changes.outputs.url == ''
run: yarn build:wasm
- name: build web
run: yarn build:local
@ -150,7 +116,6 @@ jobs:
env:
CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
WASM_OVERRIDE: ${{ steps.check-wasm-lib-changes.outputs.url }}
- uses: actions/upload-artifact@v3
if: always()
with:

2
.nvmrc
View File

@ -1 +1 @@
v21.7.1
v20.5.0

View File

@ -1,3 +1,3 @@
module.exports = {
presets: ['@babel/preset-env'],
presets: ["@babel/preset-env"],
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -104,7 +104,6 @@ test('Basic sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -329,7 +328,9 @@ test('if you write invalid kcl you get inlined errors', async ({ page }) => {
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
})
test('if your kcl gets an error from the engine it is inlined', async ({
/* Ignore this test for now since its causing engine to crash
*
* test('if your kcl gets an error from the engine it is inlined', async ({
page,
}) => {
const u = getUtils(page)
@ -348,7 +349,7 @@ const sketch001 = startSketchOn(box, "revolveAxis")
|> startProfileAt([5, 10], %)
|> line([0, -10], %)
|> line([2, 0], %)
|> line([0, -10], %)
|> line([0, 10], %)
|> close(%)
|> revolve({
axis: getEdge('revolveAxis', box),
@ -363,7 +364,7 @@ angle: 90
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
@ -377,7 +378,7 @@ angle: 90
'sketch profile must lie entirely on one side of the revolution axis'
)
).toBeVisible()
})
})*/
test('executes on load', async ({ page }) => {
const u = getUtils(page)
@ -565,9 +566,7 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('Tab')
await page.keyboard.type('12')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.waitForTimeout(100)
await page.keyboard.press('Tab')
await page.keyboard.press('Tab')
await page.keyboard.press('Enter')
@ -737,7 +736,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.openDebugPanel()
const xAxisClick = () =>
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
page.mouse.click(700, 250).then(() => page.waitForTimeout(100))
const emptySpaceClick = () =>
page.mouse.click(728, 343).then(() => page.waitForTimeout(100))
const topHorzSegmentClick = () =>
@ -762,7 +761,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
@ -770,14 +768,12 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
@ -790,14 +786,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Line' }).click()
await u.closeDebugPanel()
const selectionSequence = async (isSecondTime = false) => {
const selectionSequence = async () => {
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.waitForTimeout(100)
await page.mouse.move(
startXPx + PUR * 15,
isSecondTime ? 430 : 500 - PUR * 10
)
await page.mouse.move(startXPx + PUR * 15, 500 - PUR * 10)
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// bg-yellow-200 is more brittle than hover-highlight, but is closer to the user experience
@ -807,10 +799,7 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// check mousing off, than mousing onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 15) // mouse off
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
await page.mouse.move(
startXPx + PUR * 10,
isSecondTime ? 295 : 500 - PUR * 20
) // mouse onto another line
await page.mouse.move(startXPx + PUR * 10, 500 - PUR * 20) // mouse onto another line
await expect(page.getByTestId('hover-highlight')).toBeVisible()
// now check clicking works including axis
@ -820,7 +809,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.keyboard.down('Shift')
const absYButton = page.getByRole('button', { name: 'ABS Y' })
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await absYButton.and(page.locator(':not([disabled])')).waitFor()
@ -829,12 +817,10 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
// clear selection by clicking on nothing
await emptySpaceClick()
await page.waitForTimeout(100)
// same selection but click the axis first
await xAxisClick()
await expect(absYButton).toBeDisabled()
await page.keyboard.down('Shift')
await page.waitForTimeout(100)
await topHorzSegmentClick()
await page.keyboard.up('Shift')
@ -847,7 +833,6 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await expect(absYButton).toBeDisabled()
await page.waitForTimeout(100)
await xAxisClick()
await page.keyboard.up('Shift')
await expect(absYButton).not.toBeDisabled()
@ -890,16 +875,11 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await page.waitForTimeout(100)
// enter sketch again
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings'
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(300) // wait for animation
// hover again and check it works
await selectionSequence(true)
await selectionSequence()
})
test.describe('Command bar tests', () => {
@ -1035,7 +1015,6 @@ const part001 = startSketchOn('-XZ')
})
test('Can add multiple sketches', async ({ page }) => {
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -1086,7 +1065,6 @@ test('Can add multiple sketches', async ({ page }) => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
@ -1102,33 +1080,24 @@ test('Can add multiple sketches', async ({ page }) => {
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.updateCamPosition([100, 100, 100])
await page.waitForTimeout(250)
await u.updateCamPosition([0, 100, 100])
// start a new sketch
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(400)
await page.mouse.click(650, 450)
await page.waitForTimeout(100)
await page.mouse.click(673, 384)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
await u.clearAndCloseDebugPanel()
// on mock os there are issues with getting the camera to update
// it should not be selecting the 'XZ' plane here if the camera updated
// properly, but if we just role with it we can still verify everything
// in the rest of the test
const plane = process.platform === 'darwin' ? 'XZ' : 'XY'
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 =
process.platform === 'darwin' ? '[9.75, -13.16]' : '[0.93, -1.25]'
const startAt2 = '[0.93,-1.25]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
@ -1137,12 +1106,12 @@ const part002 = startSketchOn('${plane}')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = process.platform === 'darwin' ? 9.84 : 0.94
const num2 = 0.94
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)`.replace(/\s/g, '')
)
@ -1152,29 +1121,21 @@ const part002 = startSketchOn('${plane}')
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
`${finalCodeFirstSketch}
const part002 = startSketchOn('${plane}')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${roundOff(
num2 + (process.platform === 'darwin' ? 0.01 : -0.01)
)}], %)
|> line([-${process.platform === 'darwin' ? 19.59 : 1.87}, 0], %)`.replace(
/\s/g,
''
)
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
)
})
@ -1378,12 +1339,10 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(700, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(750, 300)
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
@ -1408,16 +1367,16 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [665, 458]
const lineEndPX = [842, 458]
const arcEndPX = [971, 342]
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await page.waitForTimeout(100)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
@ -1427,7 +1386,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
@ -1455,9 +1414,9 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([6.44, -12.07], %)
|> line([14.04, 2.03], %)
|> tangentialArcTo([27.19, -4.2], %)`)
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
})
const doSnapAtDifferentScales = async (
@ -1576,46 +1535,38 @@ test('Sketch on face', async ({ page }) => {
).not.toBeDisabled()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(300)
let previousCodeContent = await page.locator('.cm-content').innerText()
await u.openAndClearDebugPanel()
await u.doAndWaitForCmd(
() => page.mouse.click(793, 133),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.mouse.click(793, 133)
const firstClickPosition = [612, 238]
const secondClickPosition = [661, 242]
const thirdClickPosition = [609, 267]
await page.waitForTimeout(300)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(secondClickPosition[0], secondClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(thirdClickPosition[0], thirdClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await page.waitForTimeout(100)
await page.mouse.click(firstClickPosition[0], firstClickPosition[1])
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([2.87, -0.23], %)
|> line([-3.05, -1.47], %)
|> startProfileAt([1.03, 1.03], %)
|> line([4.18, -0.35], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
await u.openAndClearDebugPanel()
@ -1625,14 +1576,9 @@ test('Sketch on face', async ({ page }) => {
await u.updateCamPosition([1049, 239, 686])
await u.closeDebugPanel()
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await u.doAndWaitForCmd(
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
'default_camera_get_settings',
true
)
await page.waitForTimeout(150)
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
@ -1652,11 +1598,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-3.05, -1.47], %)
|> line([-4.44, -2.13], %)
|> close(%)`)
// exit sketch
@ -1664,7 +1610,7 @@ test('Sketch on face', async ({ page }) => {
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await u.expectCmdLog('[data-message-type="execution-done"]')
await page.getByText('startProfileAt([-12.83, 6.7], %)').click()
await page.getByText('startProfileAt([1.03, 1.03], %)').click()
await expect(page.getByRole('button', { name: 'Extrude' })).not.toBeDisabled()
await page.getByRole('button', { name: 'Extrude' }).click()
@ -1678,11 +1624,11 @@ test('Sketch on face', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toContainText(`const part002 = startSketchOn(part001, 'seg01')
|> startProfileAt([-12.83, 6.7], %)
|> line([${process?.env?.CI ? 2.28 : 2.28}, -${
process?.env?.CI ? 0.07 : 0.07
|> startProfileAt([1.03, 1.03], %)
|> line([${process?.env?.CI ? 2.74 : 2.93}, -${
process?.env?.CI ? 0.24 : 0.2
}], %)
|> line([-3.05, -1.47], %)
|> line([-4.44, -2.13], %)
|> close(%)
|> extrude(5 + 7, %)`)
})
@ -1715,11 +1661,11 @@ test('Can code mod a line length', async ({ page }) => {
// enter sketch again
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(350) // wait for animation
await page.waitForTimeout(300) // wait for animation
const startXPx = 500
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
await page.mouse.click(615, 102)
await page.mouse.click(615, 133)
await page.getByRole('button', { name: 'length', exact: true }).click()
await page.getByText('Add constraining value').click()
@ -1727,42 +1673,3 @@ test('Can code mod a line length', async ({ page }) => {
`const length001 = 20const part001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> line([0, 20], %) |> xLine(-length001, %)`
)
})
test('Extrude from command bar selects extrude line after', async ({
page,
}) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('XY')
|> startProfileAt([-10, -10], %)
|> line([20, 0], %)
|> line([0, 20], %)
|> xLine(-20, %)
|> close(%)
`
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
// Click the line of code for xLine.
await page.getByText(`close(%)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Extrude' }).click()
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await page.keyboard.press('Enter')
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toHaveText(
` |> extrude(5 + 7, %)`
)
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -7,7 +7,6 @@ export const TEST_SETTINGS = {
theme: Themes.Dark,
onboardingStatus: 'dismissed',
projectDirectory: '',
enableSSAO: false,
},
modeling: {
defaultUnit: 'in',

View File

@ -57,7 +57,7 @@ echo "New version number without 'v': $new_version_number"
git checkout -b "cut-release-$new_version"
echo "$(jq --arg v "$new_version_number" '.version=$v' package.json --indent 2)" > package.json
echo "$(jq --arg v "$new_version_number" '.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
echo "$(jq --arg v "$new_version_number" '.package.version=$v' src-tauri/tauri.conf.json --indent 2)" > src-tauri/tauri.conf.json
git add package.json src-tauri/tauri.conf.json
git commit -m "Cut release $new_version"

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.18.1",
"version": "0.17.3",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",
@ -8,7 +8,7 @@
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/react": "^1.7.18",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.58",
"@lezer/javascript": "^1.4.9",
@ -84,8 +84,8 @@
"test:e2e:tauri": "E2E_TAURI_ENABLED=true TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' wdio run wdio.conf.ts",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e",
"fmt": "prettier --write ./src && prettier --write ./e2e",
"fmt-check": "prettier --check ./src && prettier --check ./e2e",
"build:wasm-dev": "(cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm": "(cd src/wasm-lib && wasm-pack build --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
"build:wasm-clean": "yarn wasm-prep && yarn build:wasm",
@ -132,7 +132,6 @@
"@types/wicg-file-system-access": "^2023.10.5",
"@types/ws": "^8.5.10",
"@vitejs/plugin-react": "^4.2.1",
"@vitest/web-worker": "^1.5.0",
"@wdio/cli": "^8.24.3",
"@wdio/globals": "^8.36.0",
"@wdio/local-runner": "^8.36.0",

View File

@ -18,7 +18,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 3 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 2 : 1,
workers: process.env.CI ? 1 : 1,
/* 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. */
@ -49,6 +49,8 @@ export default defineConfig({
// use: { ...devices['Desktop Chrome'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
@ -72,7 +74,7 @@ export default defineConfig({
/* Run your local dev server before starting the tests */
webServer: {
command: 'VITE_KC_WASM_OVERRIDE_URL=$WASM_OVERRIDE yarn serve',
command: 'yarn serve',
// url: 'http://127.0.0.1:3000',
reuseExistingServer: !process.env.CI,
},

44
src-tauri/Cargo.lock generated
View File

@ -1592,7 +1592,7 @@ version = "0.18.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro-crate 2.0.2",
"proc-macro-error",
"proc-macro2",
@ -1735,12 +1735,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -2205,9 +2199,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.3.0"
version = "0.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddc922f0da3abc22661bf49423c9bfcc02ce6ae92dae007ede6990874789545b"
checksum = "fc460442c165c8e707b1154551cefd08938d10bb80c78940e10cd9869487c325"
dependencies = [
"anyhow",
"async-trait",
@ -4328,7 +4322,7 @@ version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"rustversion",
@ -4417,7 +4411,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5fa6fb9ee296c0dc2df41a656ca7948546d061958115ddb0bcaae43ad0d17d2"
dependencies = [
"cfg-expr",
"heck 0.4.1",
"heck",
"pkg-config",
"toml 0.7.8",
"version-compare",
@ -4514,7 +4508,7 @@ dependencies = [
"getrandom 0.2.14",
"glob",
"gtk",
"heck 0.4.1",
"heck",
"http 1.1.0",
"jni",
"libc",
@ -4549,15 +4543,15 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-beta.13"
version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abcf98a9b4527567c3e5ca9723431d121e001c2145651b3fa044d22b5e025a7e"
checksum = "33de24aabe2b9c340d67005800cb6dd40aac5283126a42896fc8eec0b87cbe45"
dependencies = [
"anyhow",
"cargo_toml",
"dirs-next",
"glob",
"heck 0.5.0",
"heck",
"json-patch",
"schemars",
"semver",
@ -4602,7 +4596,7 @@ version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b096f63f2724a1280ae0f5a34d0731de18ca18305e2ef6e5e9a39bb2710e8a85"
dependencies = [
"heck 0.4.1",
"heck",
"proc-macro2",
"quote",
"syn 2.0.48",
@ -4629,9 +4623,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-dialog"
version = "2.0.0-beta.6"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87caf6f2b704b0d27b4c64ef1fdd1f6ef97e2f5293216e230ad1efe61de54131"
checksum = "db4476c824a1488a52f4672d2b419a71fbf3dc97249013ef3c2c08fae2a23b71"
dependencies = [
"dunce",
"log",
@ -4647,9 +4641,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs"
version = "2.0.0-beta.6"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "609f53d90f08808679ecdd81455d9a4d0053291b92780695569f7400fdba27d5"
checksum = "c138126392c350aa68554e3461529b02680062c9146ab7b41d3ef97a2deaf93b"
dependencies = [
"anyhow",
"glob",
@ -4666,9 +4660,9 @@ dependencies = [
[[package]]
name = "tauri-plugin-http"
version = "2.0.0-beta.6"
version = "2.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c32962a2e2141b3bc034e5c04f363635965e59435794b6bdcf97a027f0925a"
checksum = "f27b2c90ed5473e1c068f523927fa0024212bc3a3f3a47c2a9c0b10b4b2e3ee4"
dependencies = [
"data-url",
"http 1.1.0",
@ -4806,16 +4800,16 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-beta.13"
version = "2.0.0-beta.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4709765385f035338ecc330f3fba753b8ee283c659c235da9768949cdb25469"
checksum = "760ac613d7f0de95067bcbcbcea175fe1df88fc4ab59c7f0b2cc2d01dc16a199"
dependencies = [
"brotli",
"cargo_metadata",
"ctor",
"dunce",
"glob",
"heck 0.5.0",
"heck",
"html5ever",
"infer",
"json-patch",

View File

@ -12,18 +12,18 @@ rust-version = "1.70"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "2.0.0-beta.13", features = [] }
tauri-build = { version = "2.0.0-beta.12", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.3.0"
kittycad = "0.2.67"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tauri = { version = "2.0.0-beta.15", features = [ "devtools", "unstable"] }
tauri-plugin-dialog = { version = "2.0.0-beta.6" }
tauri-plugin-fs = { version = "2.0.0-beta.6" }
tauri-plugin-http = { version = "2.0.0-beta.6" }
tauri-plugin-dialog = { version = "2.0.0-beta.5" }
tauri-plugin-fs = { version = "2.0.0-beta.5" }
tauri-plugin-http = { version = "2.0.0-beta.5" }
tauri-plugin-os = { version = "2.0.0-beta.2" }
tauri-plugin-process = { version = "2.0.0-beta.2" }
tauri-plugin-shell = { version = "2.0.0-beta.2" }

View File

@ -55,5 +55,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.18.1"
"version": "0.17.3"
}

View File

@ -246,31 +246,13 @@ export class CameraControls {
camSettings.center.y,
camSettings.center.z
)
this.camera.up.set(camSettings.up.x, camSettings.up.y, camSettings.up.z)
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
this.useOrthographicCamera()
}
if (this.camera instanceof OrthographicCamera && !camSettings.ortho) {
this.usePerspectiveCamera()
}
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
this.camera.fov = camSettings.fov_y
} else if (
this.camera instanceof OrthographicCamera &&
camSettings.ortho_scale
) {
const distanceToTarget = new Vector3(
camSettings.pos.x,
camSettings.pos.y,
camSettings.pos.z
).distanceTo(
new Vector3(
camSettings.center.x,
camSettings.center.y,
camSettings.center.z
)
)
this.camera.zoom = (camSettings.ortho_scale * 40) / distanceToTarget
this.camera.zoom = camSettings.ortho_scale
}
this.onCameraChange()
}
@ -983,10 +965,10 @@ export class CameraControls {
// Pure function helpers
function calculateNearFarFromFOV(fov: number) {
// const nearFarRatio = (fov - 3) / (45 - 3)
const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far: 1000 }
const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far }
}
function convertThreeCamValuesToEngineCam({
@ -1061,62 +1043,3 @@ function _getInteractionType(
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
return state
}
/**
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
* to ensure the client-side camera is synchronized with the engine's camera state.
*
* @param engineCommandManager Our websocket singleton
* @param entityId - The ID of face or sketchPlane.
*/
export async function letEngineAnimateAndSyncCamAfter(
engineCommandManager: EngineCommandManager,
entityId: string
) {
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: !isReducedMotion(),
ortho: false,
entity_id: entityId,
},
})
// wait 600ms (animation takes 500, + 100 for safety)
await new Promise((resolve) =>
setTimeout(resolve, isReducedMotion() ? 100 : 600)
)
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: true,
animated: false,
ortho: true,
entity_id: entityId,
},
})
await new Promise((resolve) => setTimeout(resolve, 50))
await engineCommandManager.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
}

View File

@ -3,6 +3,7 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useStore } from 'useStore'
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import { throttle } from 'lib/utils'
@ -46,6 +47,10 @@ export const ClientSideScene = ({
const canvasRef = useRef<HTMLDivElement>(null)
const { state, send, context } = useModelingContext()
const { hideClient, hideServer } = useShouldHideScene()
const { setHighlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
// Listen for changes to the camera controls setting
// and update the client-side scene's controls accordingly.
@ -64,6 +69,7 @@ export const ClientSideScene = ({
const canvas = canvasRef.current
canvas.appendChild(sceneInfra.renderer.domElement)
sceneInfra.animate()
sceneInfra.setHighlightCallback(setHighlightRange)
canvas.addEventListener('mousemove', sceneInfra.onMouseMove, false)
canvas.addEventListener('mousedown', sceneInfra.onMouseDown, false)
canvas.addEventListener('mouseup', sceneInfra.onMouseUp, false)

View File

@ -57,7 +57,6 @@ import {
kclManager,
sceneInfra,
codeManager,
editorManager,
} from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst, useStore } from 'useStore'
@ -215,9 +214,8 @@ export class SceneEntities {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const axisPixelWidth = 1.6
const xAxisGeometry = new BoxGeometry(100000, axisPixelWidth, 0.01)
const yAxisGeometry = new BoxGeometry(axisPixelWidth, 100000, 0.01)
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
const yAxisGeometry = new BoxGeometry(0.3, 100000, 0.01)
const xAxisMaterial = new MeshBasicMaterial({
color: baseXColor,
depthTest: false,
@ -1325,31 +1323,30 @@ export class SceneEntities {
selected.material.color = defaultPlaneColor(type)
},
onClick: async (args) => {
const checkExtrudeFaceClick = async (): Promise<
['face' | 'plane' | 'other', string]
> => {
const checkExtrudeFaceClick = async (): Promise<boolean> => {
const { streamDimensions } = useStore.getState()
const { entity_id } = await sendSelectEventToEngine(
args?.mouseEvent,
document.getElementById('video-stream') as HTMLVideoElement,
streamDimensions
)
if (!entity_id) return ['other', '']
if (
engineCommandManager.defaultPlanes?.xy === entity_id ||
engineCommandManager.defaultPlanes?.xz === entity_id ||
engineCommandManager.defaultPlanes?.yz === entity_id
) {
return ['plane', entity_id]
}
if (!entity_id) return false
const artifact = this.engineCommandManager.artifactMap[entity_id]
if (artifact?.commandType !== 'solid3d_get_extrusion_face_info')
return ['other', entity_id]
const faceInfo = await getFaceDetails(entity_id)
return false
const faceInfo: Models['FaceIsPlanar_type'] = (
await this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: entity_id,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
return ['other', entity_id]
const { z_axis, y_axis, origin } = faceInfo
return false
const { z_axis, origin, y_axis } = faceInfo
const pathToNode = getNodePathFromSourceRange(
kclManager.ast,
artifact.range
@ -1369,15 +1366,12 @@ export class SceneEntities {
artifact?.additionalData?.type === 'cap'
? artifact.additionalData.info
: 'none',
faceId: entity_id,
},
})
return ['face', entity_id]
return true
}
const faceResult = await checkExtrudeFaceClick()
console.log('faceResult', faceResult)
if (faceResult[0] === 'face') return
if (await checkExtrudeFaceClick()) return
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
@ -1403,7 +1397,6 @@ export class SceneEntities {
plane: planeString,
zAxis,
yAxis,
planeId: faceResult[1],
},
})
},
@ -1430,7 +1423,7 @@ export class SceneEntities {
parent.userData.pathToNode,
'CallExpression'
).node
editorManager.setHighlightRange([node.start, node.end])
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(selected, yellow)
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
@ -1466,10 +1459,10 @@ export class SceneEntities {
}
return
}
editorManager.setHighlightRange([0, 0])
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
editorManager.setHighlightRange([0, 0])
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
@ -1687,7 +1680,7 @@ export async function getSketchOrientationDetails(
sketchPathToNode: PathToNode
): Promise<{
quat: Quaternion
sketchDetails: SketchDetails & { faceId?: string }
sketchDetails: SketchDetails
}> {
const sketchGroup = sketchGroupFromPathToNode({
pathToNode: sketchPathToNode,
@ -1703,13 +1696,20 @@ export async function getSketchOrientationDetails(
zAxis: [zAxis.x, zAxis.y, zAxis.z],
yAxis: [sketchGroup.yAxis.x, sketchGroup.yAxis.y, sketchGroup.yAxis.z],
origin: [0, 0, 0],
faceId: sketchGroup.on.id,
},
}
}
if (sketchGroup.on.type === 'face') {
const faceInfo = await getFaceDetails(sketchGroup.on.faceId)
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'face_is_planar',
object_id: sketchGroup.on.faceId,
},
})
)?.data?.data
if (!faceInfo?.origin || !faceInfo?.z_axis || !faceInfo?.y_axis)
throw new Error('faceInfo')
const { z_axis, y_axis, origin } = faceInfo
@ -1724,7 +1724,6 @@ export async function getSketchOrientationDetails(
zAxis: [z_axis.x, z_axis.y, z_axis.z],
yAxis: [y_axis.x, y_axis.y, y_axis.z],
origin: [origin.x, origin.y, origin.z],
faceId: sketchGroup.on.faceId,
},
}
}
@ -1733,46 +1732,6 @@ export async function getSketchOrientationDetails(
)
}
/**
* Retrieves orientation details for a given entity representing a face (brep face or default plane).
* This function asynchronously fetches and returns the origin, x-axis, y-axis, and z-axis details
* for a specified entity ID. It is primarily used to obtain the orientation of a face in the scene,
* which is essential for calculating the correct positioning and alignment of the client side sketch.
*
* @param entityId - The ID of the entity for which orientation details are being fetched.
* @returns A promise that resolves with the orientation details of the face.
*/
async function getFaceDetails(
entityId: string
): Promise<Models['FaceIsPlanar_type']> {
// TODO mode engine connection to allow batching returns and batch the following
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'enable_sketch_mode',
adjust_camera: false,
animated: false,
ortho: false,
entity_id: entityId,
},
})
// TODO change typing to get_sketch_mode_plane once lib is updated
const faceInfo: Models['FaceIsPlanar_type'] = (
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'get_sketch_mode_plane' },
})
)?.data?.data
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'sketch_mode_disable' },
})
return faceInfo
}
export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
const dummyCam = new PerspectiveCamera()
dummyCam.up.set(0, 0, 1)

View File

@ -24,6 +24,7 @@ import {
import { compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext'
import * as TWEEN from '@tweenjs/tween.js'
import { SourceRange } from 'lang/wasm'
import { Axis } from 'lib/selections'
import { type BaseUnit } from 'lib/settings/settingsTypes'
import { CameraControls } from './CameraControls'
@ -148,6 +149,10 @@ export class SceneInfra {
onMouseLeave: () => {},
})
}
highlightCallback: (a: SourceRange) => void = () => {}
setHighlightCallback(cb: (a: SourceRange) => void) {
this.highlightCallback = cb
}
modelingSend: SendType = (() => {}) as any
setSend(send: SendType) {

View File

@ -1,9 +1,11 @@
import { useModelingContext } from 'hooks/useModelingContext'
import { editorManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { useEffect, useRef, useState } from 'react'
import { useStore } from 'useStore'
export function AstExplorer() {
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { context } = useModelingContext()
const pathToNode = getNodePathFromSourceRange(
// TODO maybe need to have callback to make sure it stays in sync
@ -40,7 +42,7 @@ export function AstExplorer() {
<div
className="h-full relative"
onMouseLeave={(e) => {
editorManager.setHighlightRange([0, 0])
setHighlightRange([0, 0])
}}
>
<pre className="text-xs">
@ -86,6 +88,7 @@ function DisplayObj({
filterKeys: string[]
node: any
}) {
const setHighlightRange = useStore((s) => s.setHighlightRange)
const { send } = useModelingContext()
const ref = useRef<HTMLPreElement>(null)
const [hasCursor, setHasCursor] = useState(false)
@ -109,12 +112,12 @@ function DisplayObj({
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
}`}
onMouseEnter={(e) => {
editorManager.setHighlightRange([obj?.start || 0, obj.end])
setHighlightRange([obj?.start || 0, obj.end])
e.stopPropagation()
}}
onMouseMove={(e) => {
e.stopPropagation()
editorManager.setHighlightRange([obj?.start || 0, obj.end])
setHighlightRange([obj?.start || 0, obj.end])
}}
onClick={(e) => {
send({

View File

@ -137,10 +137,10 @@ export function useCalc({
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
useEffect(() => {
useEffect(async () => {
try {
const code = `const __result__ = ${value}`
const ast = parse(code)
parse(code).then((ast) => {
const _programMem: any = { root: {}, return: null }
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
@ -165,6 +165,7 @@ export function useCalc({
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})
})
} catch (e) {
setCalcResult('NAN')
setValueNode(null)

View File

@ -1,7 +1,6 @@
import { useMachine } from '@xstate/react'
import { editorManager } from 'lib/singletons'
import { commandBarMachine } from 'machines/commandBarMachine'
import { createContext, useEffect } from 'react'
import { createContext } from 'react'
import { EventFrom, StateFrom } from 'xstate'
type CommandsContextType = {
@ -31,10 +30,6 @@ export const CommandBarProvider = ({
},
})
useEffect(() => {
editorManager.setCommandBarSend(commandBarSend)
})
return (
<CommandsContext.Provider
value={{

View File

@ -3,12 +3,13 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
import { useKclContext } from 'lang/KclProvider'
import { CommandArgument } from 'lib/commandTypes'
import {
ResolvedSelectionType,
canSubmitSelectionArg,
getSelectionType,
getSelectionTypeDisplayText,
} from 'lib/selections'
import { modelingMachine } from 'machines/modelingMachine'
import { useCallback, useEffect, useRef, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useHotkeys } from 'react-hotkeys-hook'
import { StateFrom } from 'xstate'
@ -29,13 +30,13 @@ function CommandBarSelectionInput({
const { commandBarState, commandBarSend } = useCommandsContext()
const [hasSubmitted, setHasSubmitted] = useState(false)
const selection = useSelector(arg.machineActor, selectionSelector)
const initSelectionsByType = useCallback(() => {
const selectionRangeEnd = selection.codeBasedSelections[0]?.range[1]
return !selectionRangeEnd || selectionRangeEnd === code.length
const [selectionsByType, setSelectionsByType] = useState<
'none' | ResolvedSelectionType[]
>(
selection.codeBasedSelections[0]?.range[1] === code.length
? 'none'
: getSelectionType(selection)
}, [selection, code])
const selectionsByType = initSelectionsByType()
)
const [canSubmitSelection, setCanSubmitSelection] = useState<boolean>(
canSubmitSelectionArg(selectionsByType, arg)
)
@ -50,14 +51,17 @@ function CommandBarSelectionInput({
inputRef.current?.focus()
}, [selection, inputRef])
useEffect(() => {
setSelectionsByType(
selection.codeBasedSelections[0]?.range[1] === code.length
? 'none'
: getSelectionType(selection)
)
}, [selection])
// Fast-forward through this arg if it's marked as skippable
// and we have a valid selection already
useEffect(() => {
console.log('selection input effect', {
selectionsByType,
canSubmitSelection,
arg,
})
setCanSubmitSelection(canSubmitSelectionArg(selectionsByType, arg))
const argValue = commandBarState.context.argumentsToSubmit[arg.name]
if (canSubmitSelection && arg.skip && argValue === undefined) {

View File

@ -17,7 +17,6 @@ import {
sceneInfra,
engineCommandManager,
codeManager,
editorManager,
} from 'lib/singletons'
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
import {
@ -54,9 +53,10 @@ import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { EditorSelection } from '@uiw/react-codemirror'
import { Vector3 } from 'three'
import { quaternionFromUpNForward } from 'clientSideScene/helpers'
import { CoreDumpManager } from 'lib/coredump'
import { useHotkeys } from 'react-hotkeys-hook'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -77,7 +77,7 @@ export const ModelingMachineProvider = ({
auth,
settings: {
context: {
app: { theme, enableSSAO },
app: { theme },
modeling: { defaultUnit, highlightEdges },
},
},
@ -87,7 +87,6 @@ export const ModelingMachineProvider = ({
useSetupEngineManager(streamRef, token, {
theme: theme.current,
highlightEdges: highlightEdges.current,
enableSSAO: enableSSAO.current,
})
const { htmlRef } = useStore((s) => ({
htmlRef: s.htmlRef,
@ -99,6 +98,17 @@ export const ModelingMachineProvider = ({
)
useHotkeys('meta + shift + .', () => coreDump(coreDumpManager, true))
const {
isShiftDown,
editorView,
setLastCodeMirrorSelectionUpdatedFromScene,
} = useStore((s) => ({
isShiftDown: s.isShiftDown,
editorView: s.editorView,
setLastCodeMirrorSelectionUpdatedFromScene:
s.setLastCodeMirrorSelectionUpdatedFromScene,
}))
// Settings machine setup
// const retrievedSettings = useRef(
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
@ -125,33 +135,29 @@ export const ModelingMachineProvider = ({
'Set selection': assign(({ selectionRanges }, event) => {
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
const setSelections = event.data
if (!editorManager.editorView) return {}
if (!editorView) return {}
const dispatchSelection = (selection?: EditorSelection) => {
if (!selection) return // TODO less of hack for the below please
editorManager.lastSelectionEvent = Date.now()
setTimeout(() => {
if (editorManager.editorView) {
editorManager.editorView.dispatch({ selection })
}
})
setLastCodeMirrorSelectionUpdatedFromScene(Date.now())
setTimeout(() => editorView.dispatch({ selection }))
}
let selections: Selections = {
codeBasedSelections: [],
otherSelections: [],
}
if (setSelections.selectionType === 'singleCodeCursor') {
if (!setSelections.selection && editorManager.isShiftDown) {
} else if (!setSelections.selection && !editorManager.isShiftDown) {
if (!setSelections.selection && isShiftDown) {
} else if (!setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [],
otherSelections: [],
}
} else if (setSelections.selection && !editorManager.isShiftDown) {
} else if (setSelections.selection && !isShiftDown) {
selections = {
codeBasedSelections: [setSelections.selection],
otherSelections: [],
}
} else if (setSelections.selection && editorManager.isShiftDown) {
} else if (setSelections.selection && isShiftDown) {
selections = {
codeBasedSelections: [
...selectionRanges.codeBasedSelections,
@ -174,7 +180,6 @@ export const ModelingMachineProvider = ({
engineCommandManager.sendSceneCommand(event)
)
updateSceneObjectColors()
return {
selectionRanges: selections,
}
@ -187,7 +192,7 @@ export const ModelingMachineProvider = ({
}
if (setSelections.selectionType === 'otherSelection') {
if (editorManager.isShiftDown) {
if (isShiftDown) {
selections = {
codeBasedSelections: selectionRanges.codeBasedSelections,
otherSelections: [setSelections.selection],
@ -268,12 +273,10 @@ export const ModelingMachineProvider = ({
'has valid extrude selection': ({ selectionRanges }) => {
// A user can begin extruding if they either have 1+ faces selected or nothing selected
// TODO: I believe this guard only allows for extruding a single face at a time
if (selectionRanges.codeBasedSelections.length < 1) return false
const isPipe = isSketchPipe(selectionRanges)
if (
selectionRanges.codeBasedSelections.length === 0 ||
isSelectionLastLine(selectionRanges, codeManager.code)
)
if (isSelectionLastLine(selectionRanges, codeManager.code))
return true
if (!isPipe) return false
@ -321,9 +324,16 @@ export const ModelingMachineProvider = ({
)
await kclManager.executeAstMock(modifiedAst)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
data.faceId
const forward = new Vector3(...data.zAxis)
const up = new Vector3(...data.yAxis)
let target = new Vector3(...data.position).multiplyScalar(
sceneInfra._baseUnitMultiplier
)
const quaternion = quaternionFromUpNForward(up, forward)
await sceneInfra.camControls.tweenCameraToQuaternion(
quaternion,
target
)
return {
@ -338,7 +348,6 @@ export const ModelingMachineProvider = ({
data.plane
)
await kclManager.updateAst(modifiedAst, false)
sceneInfra.camControls.syncDirection = 'clientToEngine'
const quat = await getSketchQuaternion(pathToNode, data.zAxis)
await sceneInfra.camControls.tweenCameraToQuaternion(quat)
return {
@ -355,9 +364,9 @@ export const ModelingMachineProvider = ({
sourceRange
)
const info = await getSketchOrientationDetails(sketchPathToNode || [])
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
info?.sketchDetails?.faceId || ''
await sceneInfra.camControls.tweenCameraToQuaternion(
info.quat,
new Vector3(...info.sketchDetails.origin)
)
return {
sketchPathToNode: sketchPathToNode || [],
@ -507,19 +516,6 @@ export const ModelingMachineProvider = ({
})
}, [modelingSend])
// Give the state back to the editorManager.
useEffect(() => {
editorManager.modelingSend = modelingSend
}, [modelingSend])
useEffect(() => {
editorManager.modelingEvent = modelingState.event
}, [modelingState.event])
useEffect(() => {
editorManager.selectionRanges = modelingState.context.selectionRanges
}, [modelingState.context.selectionRanges])
useStateMachineCommands({
machineId: 'modeling',
state: modelingState,

View File

@ -1,8 +1,13 @@
import { undo, redo } from '@codemirror/commands'
import ReactCodeMirror from '@uiw/react-codemirror'
import { TEST } from 'env'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useConvertToVariable } from 'hooks/useToolbarGuards'
import { Themes, getSystemTheme } from 'lib/theme'
import { useEffect, useMemo } from 'react'
import { useEffect, useMemo, useRef } from 'react'
import { useStore } from 'useStore'
import { processCodeMirrorRanges } from 'lib/selections'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
import { lineHighlightField } from 'editor/highlightextension'
import { roundOff } from 'lib/utils'
@ -16,6 +21,7 @@ import {
EditorView,
dropCursor,
drawSelection,
ViewUpdate,
} from '@codemirror/view'
import {
indentWithTab,
@ -23,7 +29,7 @@ import {
historyKeymap,
history,
} from '@codemirror/commands'
import { lintGutter, lintKeymap } from '@codemirror/lint'
import { lintGutter, lintKeymap, linter } from '@codemirror/lint'
import {
foldGutter,
foldKeymap,
@ -33,20 +39,25 @@ import {
syntaxHighlighting,
defaultHighlightStyle,
} from '@codemirror/language'
import { useModelingContext } from 'hooks/useModelingContext'
import interact from '@replit/codemirror-interact'
import { kclManager, editorManager, codeManager } from 'lib/singletons'
import { engineCommandManager, sceneInfra, kclManager } from 'lib/singletons'
import { useKclContext } from 'lang/KclProvider'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { useHotkeys } from 'react-hotkeys-hook'
import { isTauri } from 'lib/isTauri'
import { useNavigate } from 'react-router-dom'
import { paths } from 'lib/paths'
import makeUrlPathRelative from 'lib/makeUrlPathRelative'
import { useLspContext } from 'components/LspProvider'
import { Prec, EditorState, Extension } from '@codemirror/state'
import { Prec, EditorState, Extension, SelectionRange } from '@codemirror/state'
import {
closeBrackets,
closeBracketsKeymap,
completionKeymap,
hasNextSnippetField,
} from '@codemirror/autocomplete'
import { kclErrorsToDiagnostics } from 'lang/errors'
export const editorShortcutMeta = {
formatCode: {
@ -66,6 +77,13 @@ export const KclEditorPane = () => {
context.app.theme.current === Themes.System
? getSystemTheme()
: context.app.theme.current
const { editorView, setEditorView, isShiftDown } = useStore((s) => ({
editorView: s.editorView,
setEditorView: s.setEditorView,
isShiftDown: s.isShiftDown,
}))
const { editorCode, errors } = useKclContext()
const lastEvent = useRef({ event: '', time: Date.now() })
const { copilotLSP, kclLSP } = useLspContext()
const navigate = useNavigate()
@ -78,15 +96,90 @@ export const KclEditorPane = () => {
useHotkeys('mod+z', (e) => {
e.preventDefault()
editorManager.undo()
if (editorView) {
undo(editorView)
}
})
useHotkeys('mod+shift+z', (e) => {
e.preventDefault()
editorManager.redo()
if (editorView) {
redo(editorView)
}
})
const textWrapping = context.textEditor.textWrapping
const cursorBlinking = context.textEditor.blinkingCursor
const {
context: { selectionRanges },
send,
state,
} = useModelingContext()
const { settings } = useSettingsAuthContext()
const textWrapping = settings.context.textEditor.textWrapping
const cursorBlinking = settings.context.textEditor.blinkingCursor
const { commandBarSend } = useCommandsContext()
const { enable: convertEnabled, handleClick: convertCallback } =
useConvertToVariable()
const lastSelection = useRef('')
const onUpdate = (viewUpdate: ViewUpdate) => {
// If we are just fucking around in a snippet, return early and don't
// trigger stuff below that might cause the component to re-render.
// Otherwise we will not be able to tab thru the snippet portions.
// We explicitly dont check HasPrevSnippetField because we always add
// a ${} to the end of the function so that's fine.
if (hasNextSnippetField(viewUpdate.view.state)) {
return
}
if (!editorView) {
setEditorView(viewUpdate.view)
}
const selString = stringifyRanges(
viewUpdate?.state?.selection?.ranges || []
)
if (selString === lastSelection.current) {
// onUpdate is noisy and is fired a lot by extensions
// since we're only interested in selections changes we can ignore most of these.
return
}
lastSelection.current = selString
if (
// TODO find a less lazy way of getting the last
Date.now() - useStore.getState().lastCodeMirrorSelectionUpdatedFromScene <
150
)
return // update triggered by scene selection
if (sceneInfra.selected) return // mid drag
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
'Equip tangential arc to',
]
if (ignoreEvents.includes(state.event.type)) return
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges,
isShiftDown,
})
if (!eventInfo) return
const deterministicEventInfo = {
...eventInfo,
engineEvents: eventInfo.engineEvents.map((e) => ({
...e,
cmd_id: 'static',
})),
}
const stringEvent = JSON.stringify(deterministicEventInfo)
if (
stringEvent === lastEvent.current.event &&
Date.now() - lastEvent.current.time < 500
)
return // don't repeat events
lastEvent.current = { event: stringEvent, time: Date.now() }
send(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
}
const editorExtensions = useMemo(() => {
const extensions = [
@ -109,7 +202,7 @@ export const KclEditorPane = () => {
{
key: 'Meta-k',
run: () => {
editorManager.commandBarSend({ type: 'Open' })
commandBarSend({ type: 'Open' })
return false
},
},
@ -123,7 +216,11 @@ export const KclEditorPane = () => {
{
key: editorShortcutMeta.convertToVariable.codeMirror,
run: () => {
return editorManager.convertToVariable()
if (convertEnabled) {
convertCallback()
return true
}
return false
},
},
]),
@ -136,6 +233,9 @@ export const KclEditorPane = () => {
if (!TEST) {
extensions.push(
lintGutter(),
linter((_view: EditorView) => {
return kclErrorsToDiagnostics(errors)
}),
lineNumbers(),
highlightActiveLineGutter(),
highlightSpecialChars(),
@ -188,7 +288,13 @@ export const KclEditorPane = () => {
}
return extensions
}, [kclLSP, copilotLSP, textWrapping.current, cursorBlinking.current])
}, [
kclLSP,
copilotLSP,
textWrapping.current,
cursorBlinking.current,
convertCallback,
])
return (
<div
@ -196,15 +302,18 @@ export const KclEditorPane = () => {
className={'absolute inset-0 ' + (cursorBlinking.current ? 'blink' : '')}
>
<ReactCodeMirror
value={codeManager.code}
value={editorCode}
extensions={editorExtensions}
onUpdate={onUpdate}
theme={theme}
onCreateEditor={(_editorView) =>
editorManager.setEditorView(_editorView)
}
onCreateEditor={(_editorView) => setEditorView(_editorView)}
indentWithTab={false}
basicSetup={false}
/>
</div>
)
}
function stringifyRanges(ranges: readonly SelectionRange[]): string {
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
}

View File

@ -2,9 +2,7 @@ import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { initPromise, parse } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('processMemory', () => {
it('should grab the values and remove and geo data', async () => {
@ -28,7 +26,7 @@ describe('processMemory', () => {
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)`
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},
return: null,

View File

@ -7,12 +7,7 @@ import React, { createContext, useEffect } from 'react'
import useStateMachineCommands from '../hooks/useStateMachineCommands'
import { settingsMachine } from 'machines/settingsMachine'
import { toast } from 'react-hot-toast'
import {
getThemeColorForEngine,
getOppositeTheme,
setThemeClass,
Themes,
} from 'lib/theme'
import { getThemeColorForEngine, setThemeClass, Themes } from 'lib/theme'
import decamelize from 'decamelize'
import {
AnyStateMachine,
@ -104,9 +99,6 @@ export const SettingsAuthProviderBase = ({
{
context: loadedSettings,
actions: {
//TODO: batch all these and if that's difficult to do from tsx,
// make it easy to do
setClientSideSceneUnits: (context, event) => {
const newBaseUnit =
event.type === 'set.modeling.defaultUnit'
@ -123,16 +115,6 @@ export const SettingsAuthProviderBase = ({
color: getThemeColorForEngine(context.app.theme.current),
},
})
const opposingTheme = getOppositeTheme(context.app.theme.current)
engineCommandManager.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'set_default_system_properties',
color: getThemeColorForEngine(opposingTheme),
},
})
},
setEngineEdges: (context) => {
engineCommandManager.sendSceneCommand({

View File

@ -1,238 +0,0 @@
import { hasNextSnippetField } from '@codemirror/autocomplete'
import { EditorView, ViewUpdate } from '@codemirror/view'
import { EditorSelection, SelectionRange } from '@codemirror/state'
import { engineCommandManager, sceneInfra } from 'lib/singletons'
import { ModelingMachineEvent } from 'machines/modelingMachine'
import { Selections, processCodeMirrorRanges, Selection } from 'lib/selections'
import { undo, redo } from '@codemirror/commands'
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
import { addLineHighlight } from './highlightextension'
import { setDiagnostics, Diagnostic } from '@codemirror/lint'
export default class EditorManager {
private _editorView: EditorView | null = null
private _isShiftDown: boolean = false
private _selectionRanges: Selections = {
otherSelections: [],
codeBasedSelections: [],
}
private _lastSelectionEvent: number | null = null
private _lastSelection: string = ''
private _lastEvent: { event: string; time: number } | null = null
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
private _modelingEvent: ModelingMachineEvent | null = null
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
() => {}
private _convertToVariableEnabled: boolean = false
private _convertToVariableCallback: () => void = () => {}
private _highlightRange: [number, number] = [0, 0]
setEditorView(editorView: EditorView) {
this._editorView = editorView
}
get editorView(): EditorView | null {
return this._editorView
}
get isShiftDown(): boolean {
return this._isShiftDown
}
setIsShiftDown(isShiftDown: boolean) {
this._isShiftDown = isShiftDown
}
set selectionRanges(selectionRanges: Selections) {
this._selectionRanges = selectionRanges
}
set lastSelectionEvent(time: number) {
this._lastSelectionEvent = time
}
set modelingSend(send: (eventInfo: ModelingMachineEvent) => void) {
this._modelingSend = send
}
set modelingEvent(event: ModelingMachineEvent) {
this._modelingEvent = event
}
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
this._commandBarSend = send
}
commandBarSend(eventInfo: CommandBarMachineEvent): void {
return this._commandBarSend(eventInfo)
}
get highlightRange(): [number, number] {
return this._highlightRange
}
setHighlightRange(selection: Selection['range']): void {
this._highlightRange = selection
const editorView = this.editorView
const safeEnd = Math.min(
selection[1],
editorView?.state.doc.length || selection[1]
)
if (editorView) {
editorView.dispatch({
effects: addLineHighlight.of([selection[0], safeEnd]),
})
}
}
setDiagnostics(diagnostics: Diagnostic[]): void {
if (!this.editorView) return
this.editorView.dispatch(setDiagnostics(this.editorView.state, diagnostics))
}
undo() {
if (this._editorView) {
undo(this._editorView)
}
}
redo() {
if (this._editorView) {
redo(this._editorView)
}
}
set convertToVariableEnabled(enabled: boolean) {
this._convertToVariableEnabled = enabled
}
set convertToVariableCallback(callback: () => void) {
this._convertToVariableCallback = callback
}
convertToVariable() {
if (this._convertToVariableEnabled) {
this._convertToVariableCallback()
return true
}
return false
}
selectRange(selections: Selections) {
if (selections.codeBasedSelections.length === 0) {
return
}
if (!this.editorView) {
return
}
let codeBasedSelections = []
for (const selection of selections.codeBasedSelections) {
codeBasedSelections.push(
EditorSelection.range(selection.range[0], selection.range[1])
)
}
codeBasedSelections.push(
EditorSelection.cursor(
selections.codeBasedSelections[
selections.codeBasedSelections.length - 1
].range[1]
)
)
this.editorView.dispatch({
selection: EditorSelection.create(codeBasedSelections, 1),
})
}
handleOnViewUpdate(viewUpdate: ViewUpdate): void {
// If we are just fucking around in a snippet, return early and don't
// trigger stuff below that might cause the component to re-render.
// Otherwise we will not be able to tab thru the snippet portions.
// We explicitly dont check HasPrevSnippetField because we always add
// a ${} to the end of the function so that's fine.
if (hasNextSnippetField(viewUpdate.view.state)) {
return
}
if (this.editorView === null) {
this.setEditorView(viewUpdate.view)
}
const selString = stringifyRanges(
viewUpdate?.state?.selection?.ranges || []
)
if (selString === this._lastSelection) {
// onUpdate is noisy and is fired a lot by extensions
// since we're only interested in selections changes we can ignore most of these.
return
}
this._lastSelection = selString
if (
this._lastSelectionEvent &&
Date.now() - this._lastSelectionEvent < 150
) {
return // update triggered by scene selection
}
if (sceneInfra.selected) {
return // mid drag
}
const ignoreEvents: ModelingMachineEvent['type'][] = [
'Equip Line tool',
'Equip tangential arc to',
]
if (!this._modelingEvent) {
return
}
if (ignoreEvents.includes(this._modelingEvent.type)) {
return
}
const eventInfo = processCodeMirrorRanges({
codeMirrorRanges: viewUpdate.state.selection.ranges,
selectionRanges: this._selectionRanges,
isShiftDown: this._isShiftDown,
})
if (!eventInfo) {
return
}
const deterministicEventInfo = {
...eventInfo,
engineEvents: eventInfo.engineEvents.map((e) => ({
...e,
cmd_id: 'static',
})),
}
const stringEvent = JSON.stringify(deterministicEventInfo)
if (
this._lastEvent &&
stringEvent === this._lastEvent.event &&
Date.now() - this._lastEvent.time < 500
) {
return // don't repeat events
}
this._lastEvent = { event: stringEvent, time: Date.now() }
this._modelingSend(eventInfo.modelingEvent)
eventInfo.engineEvents.forEach((event) =>
engineCommandManager.sendSceneCommand(event)
)
}
}
function stringifyRanges(ranges: readonly SelectionRange[]): string {
return ranges.map(({ to, from }) => `${to}->${from}`).join('&')
}

View File

@ -21,7 +21,7 @@ import { LanguageServerClient } from 'editor/plugins/lsp'
import { Marked } from '@ts-stack/markdown'
import { posToOffset } from 'editor/plugins/lsp/util'
import { Program, ProgramMemory } from 'lang/wasm'
import { codeManager, editorManager, kclManager } from 'lib/singletons'
import { codeManager, kclManager } from 'lib/singletons'
import type { UnitLength } from 'wasm-lib/kcl/bindings/UnitLength'
import { UpdateUnitsResponse } from 'wasm-lib/kcl/bindings/UpdateUnitsResponse'
import { UpdateCanExecuteResponse } from 'wasm-lib/kcl/bindings/UpdateCanExecuteResponse'
@ -39,8 +39,6 @@ const CompletionItemKindMap = Object.fromEntries(
) as Record<CompletionItemKind, string>
const changesDelay = 600
let debounceTimer: ReturnType<typeof setTimeout> | null = null
const updateDelay = 100
export class LanguageServerPlugin implements PluginValue {
public client: LanguageServerClient
@ -49,7 +47,6 @@ export class LanguageServerPlugin implements PluginValue {
public workspaceFolders: LSP.WorkspaceFolder[]
private documentVersion: number
private foldingRanges: LSP.FoldingRange[] | null = null
private viewUpdate: ViewUpdate | null = null
private _defferer = deferExecution((code: string) => {
try {
// Update the state (not the editor) with the new code.
@ -60,10 +57,6 @@ export class LanguageServerPlugin implements PluginValue {
},
contentChanges: [{ text: code }],
})
if (this.viewUpdate) {
editorManager.handleOnViewUpdate(this.viewUpdate)
}
} catch (e) {
console.error(e)
}
@ -87,27 +80,14 @@ export class LanguageServerPlugin implements PluginValue {
})
}
update(viewUpdate: ViewUpdate) {
this.viewUpdate = viewUpdate
if (!viewUpdate.docChanged) {
// debounce the view update.
// otherwise it is laggy for typing.
if (debounceTimer) {
clearTimeout(debounceTimer)
}
debounceTimer = setTimeout(() => {
editorManager.handleOnViewUpdate(viewUpdate)
}, updateDelay)
return
}
update({ docChanged }: ViewUpdate) {
if (!docChanged) return
const newCode = this.view.state.doc.toString()
codeManager.code = newCode
codeManager.writeToFile()
kclManager.executeCode()
this.sendChange({
documentText: newCode,
})
@ -377,9 +357,15 @@ export class LanguageServerPlugin implements PluginValue {
try {
switch (notification.method) {
case 'textDocument/publishDiagnostics':
//const params = notification.params as PublishDiagnosticsParams
// this is sometimes slower than our actual typing.
//this.processDiagnostics(params)
const params = notification.params as PublishDiagnosticsParams
this.processDiagnostics(params)
// Update the kcl errors pane.
/*if (!kclManager.isExecuting) {
kclManager.kclErrors = lspDiagnosticsToKclErrors(
this.view.state.doc,
params.diagnostics
)
}*/
break
case 'window/logMessage':
console.log(
@ -399,6 +385,17 @@ export class LanguageServerPlugin implements PluginValue {
// The server has updated the AST, we should update elsewhere.
let updatedAst = notification.params as Program
console.log('[lsp]: Updated AST', updatedAst)
// Update the ast when we are not already executing.
/* if (!kclManager.isExecuting) {
kclManager.ast = updatedAst
// Execute the ast.
console.log('[lsp]: executing ast')
await kclManager.executeAst(updatedAst)
console.log('[lsp]: executed ast', kclManager.kclErrors)
let diagnostics = kclErrorsToDiagnostics(kclManager.kclErrors)
this.view.dispatch(setDiagnostics(this.view.state, diagnostics))
console.log('[lsp]: updated diagnostics')
}*/
// Update the folding ranges, since the AST has changed.
// This is a hack since codemirror does not support async foldService.

View File

@ -7,7 +7,5 @@ export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_WASM_OVERRIDE_URL = import.meta.env
.VITE_KC_WASM_OVERRIDE_URL
export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -1,9 +1,14 @@
import { useEffect } from 'react'
import { editorManager, engineCommandManager } from 'lib/singletons'
import { useStore } from 'useStore'
import { engineCommandManager } from 'lib/singletons'
import { useModelingContext } from './useModelingContext'
import { getEventForSelectWithPoint } from 'lib/selections'
export function useEngineConnectionSubscriptions() {
const { setHighlightRange, highlightRange } = useStore((s) => ({
setHighlightRange: s.setHighlightRange,
highlightRange: s.highlightRange,
}))
const { send, context } = useModelingContext()
useEffect(() => {
@ -16,13 +21,12 @@ export function useEngineConnectionSubscriptions() {
if (data?.entity_id) {
const sourceRange =
engineCommandManager.artifactMap?.[data.entity_id]?.range
editorManager.setHighlightRange(sourceRange)
setHighlightRange(sourceRange)
} else if (
!editorManager.highlightRange ||
(editorManager.highlightRange[0] !== 0 &&
editorManager.highlightRange[1] !== 0)
!highlightRange ||
(highlightRange[0] !== 0 && highlightRange[1] !== 0)
) {
editorManager.setHighlightRange([0, 0])
setHighlightRange([0, 0])
}
},
})
@ -39,5 +43,10 @@ export function useEngineConnectionSubscriptions() {
unSubHover()
unSubClick()
}
}, [engineCommandManager, context?.sketchEnginePathId])
}, [
engineCommandManager,
setHighlightRange,
highlightRange,
context?.sketchEnginePathId,
])
}

View File

@ -1,4 +1,4 @@
import { editorManager } from 'lib/singletons'
import { useStore } from '../useStore'
import { useEffect } from 'react'
// Kurt's note: codeMirror styling overrides were needed to make this work
@ -6,17 +6,20 @@ import { useEffect } from 'react'
// search for code-mirror-override in the repo to find the relevant styles
export function useHotKeyListener() {
const { setIsShiftDown } = useStore((s) => ({
setIsShiftDown: s.setIsShiftDown,
}))
const keyName = 'Shift'
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) =>
event.key === keyName && editorManager.setIsShiftDown(true)
event.key === keyName && setIsShiftDown(true)
const handleKeyUp = (event: KeyboardEvent) =>
event.key === keyName && editorManager.setIsShiftDown(false)
event.key === keyName && setIsShiftDown(false)
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
return () => {
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
}
})
}, [setIsShiftDown])
}

View File

@ -11,11 +11,9 @@ export function useSetupEngineManager(
settings = {
theme: Themes.System,
highlightEdges: true,
enableSSAO: true,
} as {
theme: Themes
highlightEdges: boolean
enableSSAO: boolean
}
) {
const {

View File

@ -2,7 +2,7 @@ import {
SetVarNameModal,
createSetVarNameModal,
} from 'components/SetVarNameModal'
import { editorManager, kclManager } from 'lib/singletons'
import { kclManager } from 'lib/singletons'
import { moveValueIntoNewVariable } from 'lang/modifyAst'
import { isNodeSafeToReplace } from 'lang/queryAst'
import { useEffect, useState } from 'react'
@ -13,11 +13,6 @@ const getModalInfo = createSetVarNameModal(SetVarNameModal)
export function useConvertToVariable() {
const { context } = useModelingContext()
const [enable, setEnabled] = useState(false)
useEffect(() => {
editorManager.convertToVariableEnabled = enable
}, [enable])
useEffect(() => {
const { isSafe, value } = isNodeSafeToReplace(
kclManager.ast,
@ -50,7 +45,5 @@ export function useConvertToVariable() {
}
}
editorManager.convertToVariableCallback = handleClick
return { enable, handleClick }
}

View File

@ -2,10 +2,12 @@ import { KCLError } from './errors'
import { createContext, useContext, useEffect, useState } from 'react'
import { type IndexLoaderData } from 'lib/types'
import { useLoaderData } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { codeManager, kclManager } from 'lib/singletons'
const KclContext = createContext({
code: codeManager?.code || '',
editorCode: codeManager?.code || '',
programMemory: kclManager?.programMemory,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
@ -28,6 +30,7 @@ export function KclContextProvider({
const { code: loadedCode } = useLoaderData() as IndexLoaderData
// Both the code state and the editor state start off with the same code.
const [code, setCode] = useState(loadedCode || codeManager.code)
const [editorCode, setEditorCode] = useState(code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [ast, setAst] = useState(kclManager.ast)
@ -39,6 +42,7 @@ export function KclContextProvider({
useEffect(() => {
codeManager.registerCallBacks({
setCode,
setEditorCode,
})
kclManager.registerCallBacks({
setProgramMemory,
@ -50,10 +54,15 @@ export function KclContextProvider({
})
}, [])
const params = useParams()
useEffect(() => {
codeManager.setParams(params)
}, [params])
return (
<KclContext.Provider
value={{
code,
editorCode,
programMemory,
ast,
isExecuting,

View File

@ -1,6 +1,6 @@
import { executeAst } from 'useStore'
import { Selections } from 'lib/selections'
import { KCLError, kclErrorsToDiagnostics } from './errors'
import { KCLError } from './errors'
import { uuidv4 } from 'lib/utils'
import { EngineCommandManager } from './std/engineConnection'
@ -17,7 +17,7 @@ import {
ExtrudeGroup,
} from 'lang/wasm'
import { getNodeFromPath } from './queryAst'
import { codeManager, editorManager } from 'lib/singletons'
import { codeManager } from 'lib/singletons'
export class KclManager {
private _ast: Program = {
@ -43,14 +43,16 @@ export class KclManager {
const ast = this.safeParse(code)
if (!ast) return
try {
parse(recast(ast)).then((newAst) => {
const fmtAndStringify = (ast: Program) =>
JSON.stringify(parse(recast(ast)))
JSON.stringify(newAst)
const isAstTheSame = fmtAndStringify(ast) === fmtAndStringify(this._ast)
if (isAstTheSame) return
this.executeAst(ast)
})
} catch (e) {
console.error(e)
}
this.executeAst(ast)
}, 600)
private _isExecutingCallback: (arg: boolean) => void = () => {}
@ -90,8 +92,6 @@ export class KclManager {
}
set kclErrors(kclErrors) {
this._kclErrors = kclErrors
let diagnostics = kclErrorsToDiagnostics(kclErrors)
editorManager.setDiagnostics(diagnostics)
this._kclErrorsCallBack(kclErrors)
}
@ -145,9 +145,9 @@ export class KclManager {
this._executeCallback = callback
}
safeParse(code: string): Program | null {
async safeParse(code: string): Promise<Program | null> {
try {
const ast = parse(code)
const ast = await parse(code)
this.kclErrors = []
return ast
} catch (e) {
@ -347,16 +347,6 @@ export class KclManager {
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
void this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
}
enterEditMode() {
enterEditMode(this.programMemory, this.engineCommandManager)
}
exitEditMode() {
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: { type: 'edit_mode_exit' },
})
}
}
function enterEditMode(

View File

@ -1,13 +1,11 @@
import { KCLError } from './errors'
import { initPromise, parse } from './wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing AST', () => {
test('5 + 6', () => {
const result = parse('5 +6')
test('5 + 6', async () => {
const result = await parse('5 +6')
delete (result as any).nonCodeMeta
expect(result.body).toEqual([
{
@ -37,8 +35,8 @@ describe('testing AST', () => {
},
])
})
test('const myVar = 5', () => {
const { body } = parse('const myVar = 5')
test('const myVar = 5', async () => {
const { body } = await parse('const myVar = 5')
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -68,11 +66,11 @@ describe('testing AST', () => {
},
])
})
test('multi-line', () => {
test('multi-line', async () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -143,8 +141,8 @@ const newVar = myVar + 1
})
describe('testing function declaration', () => {
test('fn funcN = (a, b) => {return a + b}', () => {
const { body } = parse(
test('fn funcN = (a, b) => {return a + b}', async () => {
const { body } = await parse(
['fn funcN = (a, b) => {', ' return a + b', '}'].join('\n')
)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
@ -226,10 +224,10 @@ describe('testing function declaration', () => {
},
])
})
test('call expression assignment', () => {
test('call expression assignment', async () => {
const code = `fn funcN = (a, b) => { return a + b }
const myVar = funcN(1, 2)`
const { body } = parse(code)
const { body } = await parse(code)
delete (body[0] as any).declarations[0].init.body.nonCodeMeta
expect(body).toEqual([
{
@ -359,14 +357,14 @@ const myVar = funcN(1, 2)`
})
describe('testing pipe operator special', () => {
test('pipe operator with sketch', () => {
test('pipe operator with sketch', async () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo([2, 3], %)
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> rx(45, %)
`
const { body } = parse(code)
const { body } = await parse(code)
delete (body[0] as any).declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -564,9 +562,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('pipe operator with binary expression', () => {
test('pipe operator with binary expression', async () => {
let code = `const myVar = 5 + 6 |> myFunc(45, %)`
const { body } = parse(code)
const { body } = await parse(code)
delete (body as any)[0].declarations[0].init.nonCodeMeta
expect(body).toEqual([
{
@ -643,9 +641,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('array expression', () => {
test('array expression', async () => {
let code = `const yo = [1, '2', three, 4 + 5]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -715,12 +713,12 @@ describe('testing pipe operator special', () => {
},
])
})
test('object expression ast', () => {
test('object expression ast', async () => {
const code = [
'const three = 3',
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -860,11 +858,11 @@ describe('testing pipe operator special', () => {
},
])
})
test('nested object expression ast', () => {
test('nested object expression ast', async () => {
const code = `const yo = {key: {
key2: 'value'
}}`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -930,9 +928,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object expression with array ast', () => {
test('object expression with array ast', async () => {
const code = `const yo = {key: [1, '2']}`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -994,9 +992,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression simple', () => {
test('object memberExpression simple', async () => {
const code = `const prop = yo.one.two`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1049,9 +1047,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression with square braces', () => {
test('object memberExpression with square braces', async () => {
const code = `const prop = yo.one["two"]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1105,9 +1103,9 @@ describe('testing pipe operator special', () => {
},
])
})
test('object memberExpression with two square braces literal and identifier', () => {
test('object memberExpression with two square braces literal and identifier', async () => {
const code = `const prop = yo["one"][two]`
const { body } = parse(code)
const { body } = await parse(code)
expect(body).toEqual([
{
type: 'VariableDeclaration',
@ -1164,9 +1162,9 @@ describe('testing pipe operator special', () => {
})
describe('nests binary expressions correctly', () => {
it('works with the simple case', () => {
it('works with the simple case', async () => {
const code = `const yo = 1 + 2`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1207,10 +1205,10 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest according to precedence with multiply first', () => {
it('should nest according to precedence with multiply first', async () => {
// should be binExp { binExp { lit-1 * lit-2 } + lit}
const code = `const yo = 1 * 2 + 3`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1264,10 +1262,10 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest according to precedence with sum first', () => {
it('should nest according to precedence with sum first', async () => {
// should be binExp { lit-1 + binExp { lit-2 * lit-3 } }
const code = `const yo = 1 + 2 * 3`
const { body } = parse(code)
const { body } = await parse(code)
expect(body[0]).toEqual({
type: 'VariableDeclaration',
start: 0,
@ -1321,9 +1319,9 @@ describe('nests binary expressions correctly', () => {
],
})
})
it('should nest properly with two operators of equal precedence', () => {
it('should nest properly with two operators of equal precedence', async () => {
const code = `const yo = 1 + 2 - 3`
const { body } = parse(code)
const { body } = await parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1358,9 +1356,9 @@ describe('nests binary expressions correctly', () => {
},
})
})
it('should nest properly with two operators of equal (but higher) precedence', () => {
it('should nest properly with two operators of equal (but higher) precedence', async () => {
const code = `const yo = 1 * 2 / 3`
const { body } = parse(code)
const { body } = await parse(code)
expect((body[0] as any).declarations[0].init).toEqual({
type: 'BinaryExpression',
start: 11,
@ -1395,9 +1393,9 @@ describe('nests binary expressions correctly', () => {
},
})
})
it('should nest properly with longer example', () => {
it('should nest properly with longer example', async () => {
const code = `const yo = 1 + 2 * (3 - 4) / 5 + 6`
const { body } = parse(code)
const { body } = await parse(code)
const init = (body[0] as any).declarations[0].init
expect(init).toEqual({
type: 'BinaryExpression',
@ -1445,7 +1443,7 @@ describe('nests binary expressions correctly', () => {
})
describe('check nonCodeMeta data is attached to the AST correctly', () => {
it('comments between expressions', () => {
it('comments between expressions', async () => {
const code = `
const yo = { a: { b: { c: '123' } } }
// this is a comment
@ -1460,12 +1458,14 @@ const key = 'c'`
value: 'this is a comment',
},
}
const { nonCodeMeta } = parse(code)
const { nonCodeMeta } = await parse(code)
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 { nonCodeMeta: nonCodeMeta2 } = await parse(
codeWithExtraStartWhitespace
)
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
nonCodeMetaInstance.value
)
@ -1473,7 +1473,7 @@ const key = 'c'`
nonCodeMetaInstance.start
)
})
it('comments nested within a block statement', () => {
it('comments nested within a block statement', async () => {
const code = `const mySketch = startSketchAt([0,0])
|> lineTo([0, 1], %, 'myPath')
|> lineTo([1, 1], %) /* this is
@ -1483,7 +1483,7 @@ const key = 'c'`
|> close(%)
`
const { body } = parse(code)
const { body } = await parse(code)
const indexOfSecondLineToExpression = 2
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
.nonCodeNodes
@ -1498,7 +1498,7 @@ const key = 'c'`
},
})
})
it('comments in a pipe expression', () => {
it('comments in a pipe expression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -1508,7 +1508,7 @@ const key = 'c'`
' |> rx(90, %)',
].join('\n')
const { body } = parse(code)
const { body } = await parse(code)
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
.nonCodeNodes[3][0]
expect(sketchNonCodeMeta).toEqual({
@ -1525,9 +1525,9 @@ const key = 'c'`
})
describe('test UnaryExpression', () => {
it('should parse a unary expression in simple var dec situation', () => {
it('should parse a unary expression in simple var dec situation', async () => {
const code = `const myVar = -min(4, 100)`
const { body } = parse(code)
const { body } = await parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'UnaryExpression',
@ -1550,9 +1550,9 @@ describe('test UnaryExpression', () => {
})
describe('testing nested call expressions', () => {
it('callExp in a binExp in a callExp', () => {
it('callExp in a binExp in a callExp', async () => {
const code = 'const myVar = min(100, 1 + legLen(5, 3))'
const { body } = parse(code)
const { body } = await parse(code)
const myVarInit = (body?.[0] as any).declarations[0]?.init
expect(myVarInit).toEqual({
type: 'CallExpression',
@ -1587,8 +1587,8 @@ 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)
it('should recognise the callExp', async () => {
const { body } = await parse(code)
const callExpArgs = (body?.[0] as any).expression?.arguments
expect(callExpArgs).toEqual([
{

View File

@ -1,9 +1,7 @@
import { parse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing artifacts', () => {
// Enable rotations #152
@ -14,7 +12,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
@ -70,7 +68,7 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
@ -152,7 +150,7 @@ const sk2 = startSketchOn('XY')
|> extrude(2, %)
`
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
// @ts-ignore
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
expect(geos).toEqual([

View File

@ -5,7 +5,7 @@ import { bracket } from 'lib/exampleKcl'
import { isTauri } from 'lib/isTauri'
import { writeTextFile } from '@tauri-apps/plugin-fs'
import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons'
import { Params } from 'react-router-dom'
const PERSIST_CODE_TOKEN = 'persistCode'
@ -13,7 +13,7 @@ export default class CodeManager {
private _code: string = bracket
private _updateState: (arg: string) => void = () => {}
private _updateEditor: (arg: string) => void = () => {}
private _currentFilePath: string | null = null
private _params: Params<string> = {}
constructor() {
if (isTauri()) {
@ -45,12 +45,19 @@ export default class CodeManager {
return this._code
}
registerCallBacks({ setCode }: { setCode: (arg: string) => void }) {
registerCallBacks({
setCode,
setEditorCode,
}: {
setCode: (arg: string) => void
setEditorCode: (arg: string) => void
}) {
this._updateState = setCode
this._updateEditor = setEditorCode
}
updateCurrentFilePath(path: string) {
this._currentFilePath = path
setParams(params: Params<string>) {
this._params = params
}
// This updates the code state and calls the updateState function.
@ -63,14 +70,11 @@ export default class CodeManager {
// Update the code in the editor.
updateCodeEditor(code: string): void {
const lastCode = this._code
if (this._code !== code) {
this.code = code
this._updateEditor(code)
if (editorManager.editorView) {
editorManager.editorView.dispatch({
changes: { from: 0, to: lastCode.length, insert: code },
})
}
this._updateEditor(code)
}
// Update the code, state, and the code the code mirror editor sees.
@ -87,8 +91,8 @@ export default class CodeManager {
setTimeout(() => {
// Wait one event loop to give a chance for params to be set
// Save the file to disk
this._currentFilePath &&
writeTextFile(this._currentFilePath, this.code).catch((err) => {
this._params.id &&
writeTextFile(this._params.id, this.code).catch((err) => {
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')

View File

@ -4,9 +4,7 @@ import { parse, ProgramMemory, SketchGroup, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('test executor', () => {
it('test assigning two variables, the second summing with the first', async () => {
@ -400,7 +398,7 @@ async function exe(
code: string,
programMemory: ProgramMemory = { root: {}, return: null }
) {
const ast = parse(code)
const ast = await parse(code)
const result = await enginelessExecutor(ast, programMemory)
return result

View File

@ -1,12 +1,10 @@
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
import { Identifier, parse, initPromise, Parameter } from './wasm'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getNodePathFromSourceRange', () => {
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', () => {
it('test it gets the right path for a `lineTo` CallExpression within a SketchExpression', async () => {
const code = `
const myVar = 5
const sk3 = startSketchAt([0, 0])
@ -21,14 +19,14 @@ const sk3 = startSketchAt([0, 0])
lineToSubstringIndex + subStr.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const { node } = getNodeFromPath<any>(ast, nodePath)
expect([node.start, node.end]).toEqual(sourceRange)
expect(node.type).toBe('CallExpression')
})
it('gets path right for function definition params', () => {
it('gets path right for function definition params', async () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
@ -46,7 +44,7 @@ const b1 = cube([0,0], 10)`
subStrIndex + 'pos'.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Parameter>(ast, nodePath).node
@ -62,7 +60,7 @@ const b1 = cube([0,0], 10)`
expect(node.type).toBe('Parameter')
expect(node.identifier.name).toBe('pos')
})
it('gets path right for deep within function definition body', () => {
it('gets path right for deep within function definition body', async () => {
const code = `fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
|> line([0, scale], %)
@ -80,7 +78,7 @@ const b1 = cube([0,0], 10)`
subStrIndex + 'scale'.length,
]
const ast = parse(code)
const ast = await parse(code)
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
const node = getNodeFromPath<Identifier>(ast, nodePath).node
expect(nodePath).toEqual([

View File

@ -17,9 +17,7 @@ import {
import { enginelessExecutor } from '../lib/testHelpers'
import { getNodePathFromSourceRange } from './queryAst'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('Testing createLiteral', () => {
it('should create a literal', () => {

81
src/lang/parser.ts Normal file
View File

@ -0,0 +1,81 @@
import {
ParserWorkerResponse,
WasmWorker,
WasmWorkerEventType,
ParserWorkerCallResponse,
} from 'lang/workers/types'
import Worker from 'lang/workers/parser?worker'
import { Program, wasmUrl } from 'lang/wasm'
import { KCLError } from 'lang/errors'
import { v4 as uuidv4 } from 'uuid'
export default class Parser {
worker: any = new Worker({ name: 'parse' })
intitalized: boolean = false
mappings: Map<string, Program | KCLError> = new Map()
async parse(code: string): Promise<Program> {
this.ensureWorker()
const uuid = uuidv4()
this.worker.postMessage({
worker: WasmWorker.Parser,
eventType: WasmWorkerEventType.Call,
eventData: {
uuid,
code,
},
})
let result = await this.waitForResult(uuid)
if (result instanceof KCLError) {
throw result
}
return result
}
waitForResult(uuid: string): Promise<Program | KCLError> {
return new Promise((resolve) => {
const result = this.mappings.get(uuid)
if (result) {
this.mappings.delete(uuid)
resolve(result)
} else {
setTimeout(() => {
resolve(this.waitForResult(uuid))
}, 100)
}
})
}
ensureWorker() {
if (!this.intitalized) {
this.start()
}
}
// Start the worker.
start() {
if (this.intitalized) {
console.log('Worker already initialized')
return
}
this.worker.postMessage({
worker: WasmWorker.Parser,
eventType: WasmWorkerEventType.Init,
eventData: {
wasmUrl: wasmUrl(),
},
})
this.worker.onmessage = function (r: ParserWorkerResponse) {
switch (r.eventType) {
case WasmWorkerEventType.Init:
this.intitalized = true
break
case WasmWorkerEventType.Call:
const c = r.response as ParserWorkerCallResponse
this.mappings.set(c.uuid, c.response)
break
}
}
}
}

View File

@ -15,9 +15,7 @@ import {
createPipeSubstitution,
} from './modifyAst'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('findAllPreviousVariables', () => {
it('should find all previous variables', async () => {

View File

@ -1,58 +1,56 @@
import { parse, Program, recast, initPromise } from './wasm'
import fs from 'node:fs'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('recast', () => {
it('recasts a simple program', () => {
it('recasts a simple program', async () => {
const code = '1 + 2'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('variable declaration', () => {
it('variable declaration', async () => {
const code = 'const myVar = 5'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it("variable declaration that's binary with string", () => {
it("variable declaration that's binary with string", async () => {
const code = "const myVar = 5 + 'yo'"
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
const codeWithOtherQuotes = 'const myVar = 5 + "yo"'
const { ast: ast2 } = code2ast(codeWithOtherQuotes)
const { ast: ast2 } = await code2ast(codeWithOtherQuotes)
expect(recast(ast2).trim()).toBe(codeWithOtherQuotes)
})
it('test assigning two variables, the second summing with the first', () => {
it('test assigning two variables, the second summing with the first', async () => {
const code = `const myVar = 5
const newVar = myVar + 1
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('test assigning a var by cont concatenating two strings string', () => {
it('test assigning a var by cont concatenating two strings string', async () => {
const code = fs.readFileSync(
'./src/lang/testExamples/variableDeclaration.cado',
'utf-8'
)
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('test with function call', () => {
it('test with function call', async () => {
const code = `const myVar = "hello"
log(5, myVar)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('function declaration with call', () => {
it('function declaration with call', async () => {
const code = [
'fn funcN = (a, b) => {',
' return a + b',
@ -60,22 +58,22 @@ log(5, myVar)
'const theVar = 60',
'const magicNum = funcN(9, theVar)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('recast sketch declaration', () => {
it('recast sketch declaration', async () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> lineTo([1, 0], %, "rightPath")
|> close(%)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('sketch piped into callExpression', () => {
it('sketch piped into callExpression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -83,11 +81,11 @@ log(5, myVar)
' |> lineTo([1, 1], %)',
' |> rx(90, %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast BinaryExpression piped into CallExpression', () => {
it('recast BinaryExpression piped into CallExpression', async () => {
const code = [
'fn myFn = (a) => {',
' return a + 1',
@ -95,49 +93,49 @@ log(5, myVar)
'const myVar = 5 + 1',
' |> myFn(%)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('recast nested binary expression', () => {
it('recast nested binary expression', async () => {
const code = ['const myVar = 1 + 2 * 5'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast nested binary expression with parans', () => {
it('recast nested binary expression with parans', async () => {
const code = ['const myVar = 1 + (1 + 2) * 5'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('unnecessary paran wrap will be remove', () => {
it('unnecessary paran wrap will be remove', async () => {
const code = ['const myVar = 1 + (2 * 5)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.replace('(', '').replace(')', ''))
})
it('complex nested binary expression', () => {
it('complex nested binary expression', async () => {
const code = ['1 * ((2 + 3) / 4 + 5)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('multiplied paren expressions', () => {
it('multiplied paren expressions', async () => {
const code = ['3 + (1 + 2) * (3 + 4)'].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast array declaration', () => {
it('recast array declaration', async () => {
const code = ['const three = 3', "const yo = [1, '2', three, 4 + 5]"].join(
'\n'
)
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast long array declaration', () => {
it('recast long array declaration', async () => {
const code = [
'const three = 3',
'const yo = [',
@ -148,11 +146,11 @@ log(5, myVar)
" 'hey oooooo really long long long'",
']',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code.trim())
})
it('recast long object execution', () => {
it('recast long object execution', async () => {
const code = `const three = 3
const yo = {
aStr: 'str',
@ -161,43 +159,43 @@ const yo = {
binExp: 4 + 5
}
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recast short object execution', () => {
it('recast short object execution', async () => {
const code = `const yo = { key: 'val' }
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recast object execution with member expression', () => {
it('recast object execution with member expression', async () => {
const code = `const yo = { a: { b: { c: '123' } } }
const key = 'c'
const myVar = yo.a['b'][key]
const key2 = 'b'
const myVar2 = yo['a'][key2].c
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
})
describe('testing recasting with comments and whitespace', () => {
it('code with comments', () => {
it('code with comments', async () => {
const code = `const yo = { a: { b: { c: '123' } } }
// this is a comment
const key = 'c'
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('code with comment and extra lines', () => {
it('code with comment and extra lines', async () => {
const code = `const yo = 'c'
/* this is
@ -205,23 +203,23 @@ a
comment */
const yo = 'bing'
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments at the start and end', () => {
it('comments at the start and end', async () => {
const code = `// this is a comment
const yo = { a: { b: { c: '123' } } }
const key = 'c'
// this is also a comment
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments in a fn block', () => {
const code = `fn myFn = () => {
it('comments in a fn block', async () => {
const code = `fn myFn = async () => {
// this is a comment
const yo = { a: { b: { c: '123' } } }
@ -231,11 +229,11 @@ const key = 'c'
// this is also a comment
}
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('comments in a pipe expression', () => {
it('comments in a pipe expression', async () => {
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
@ -244,11 +242,11 @@ const key = 'c'
' // a comment',
' |> rx(90, %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('comments sprinkled in all over the place', () => {
it('comments sprinkled in all over the place', async () => {
const code = `
/* comment at start */
@ -270,7 +268,7 @@ const mySk1 = startSketchAt([0, 0])
one more for good measure
*/
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(`/* comment at start */
@ -291,37 +289,37 @@ const mySk1 = startSketchAt([0, 0])
})
describe('testing call Expressions in BinaryExpressions and UnaryExpressions', () => {
it('nested callExpression in binaryExpression', () => {
it('nested callExpression in binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('nested callExpression in unaryExpression', () => {
it('nested callExpression in unaryExpression', async () => {
const code = 'const myVar = -min(100, legLen(5, 3))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('with unaryExpression in callExpression', () => {
it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(5, -legLen(5, 4))'
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
it('with unaryExpression in sketch situation', () => {
it('with unaryExpression in sketch situation', async () => {
const code = [
'const part001 = startSketchAt([0, 0])',
' |> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted.trim()).toBe(code)
})
})
describe('it recasts wrapped object expressions in pipe bodies with correct indentation', () => {
it('with a single line', () => {
it('with a single line', async () => {
const code = `const part001 = startSketchAt([-0.01, -0.08])
|> line([0.62, 4.15], %, 'seg01')
|> line([2.77, -1.24], %)
@ -332,35 +330,35 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
}, %)
|> line([-0.42, -1.72], %)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
it('recasts wrapped object expressions NOT in pipe body correctly', () => {
it('recasts wrapped object expressions NOT in pipe body correctly', async () => {
const code = `angledLineThatIntersects({
angle: 201,
offset: -1.35,
intersectTag: 'seg01'
}, %)
`
const { ast } = code2ast(code)
const { ast } = await code2ast(code)
const recasted = recast(ast)
expect(recasted).toBe(code)
})
})
describe('it recasts binary expression using brackets where needed', () => {
it('when there are two minus in a row', () => {
it('when there are two minus in a row', async () => {
const code = `const part001 = 1 - (def - abc)
`
const recasted = recast(code2ast(code).ast)
const recasted = recast((await code2ast(code)).ast)
expect(recasted).toBe(code)
})
})
// helpers
function code2ast(code: string): { ast: Program } {
const ast = parse(code)
async function code2ast(code: string): Promise<{ ast: Program }> {
const ast = await parse(code)
return { ast }
}

View File

@ -4,7 +4,7 @@ import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
import { uuidv4 } from 'lib/utils'
import { getNodePathFromSourceRange } from 'lang/queryAst'
import { Themes, getThemeColorForEngine, getOppositeTheme } from 'lib/theme'
import { Themes, getThemeColorForEngine } from 'lib/theme'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
let lastMessage = ''
@ -941,7 +941,6 @@ export class EngineCommandManager {
settings = {
theme: Themes.Dark,
highlightEdges: true,
enableSSAO: true,
},
}: {
setMediaStream: (stream: MediaStream) => void
@ -954,7 +953,6 @@ export class EngineCommandManager {
settings?: {
theme: Themes
highlightEdges: boolean
enableSSAO: boolean
}
}) {
this.makeDefaultPlanes = makeDefaultPlanes
@ -971,8 +969,7 @@ export class EngineCommandManager {
return
}
const additionalSettings = settings.enableSSAO ? '&post_effect=ssao' : ''
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}${additionalSettings}`
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
this.engineConnection = new EngineConnection({
engineCommandManager: this,
url,
@ -992,18 +989,6 @@ export class EngineCommandManager {
color: getThemeColorForEngine(settings.theme),
},
})
// Sets the default line colors
const opposingTheme = getOppositeTheme(settings.theme)
this.sendSceneCommand({
cmd_id: uuidv4(),
type: 'modeling_cmd_req',
cmd: {
type: 'set_default_system_properties',
color: getThemeColorForEngine(opposingTheme),
},
})
// Set the edge lines visibility
this.sendSceneCommand({
type: 'modeling_cmd_req',
@ -1186,10 +1171,7 @@ export class EngineCommandManager {
type: 'receive-reliable',
data: message,
id,
cmd_type:
command?.commandType ||
this.lastArtifactMap[id]?.commandType ||
sceneCommand?.commandType,
cmd_type: command?.commandType || this.lastArtifactMap[id]?.commandType,
})
Object.values(this.subscriptions[modelingResponse.type] || {}).forEach(
(callback) => callback(modelingResponse)
@ -1341,17 +1323,6 @@ export class EngineCommandManager {
this.lastArtifactMap = this.artifactMap
this.artifactMap = {}
await this.initPlanes()
await this.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'make_axes_gizmo',
clobber: false,
// If true, axes gizmo will be placed in the corner of the screen.
// If false, it will be placed at the origin of the scene.
gizmo_mode: true,
},
})
}
subscribeTo<T extends ModelTypes>({
event,

View File

@ -25,9 +25,7 @@ const eachQuad: [number, [number, number]][] = [
[675, [1, -1]],
]
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getYComponent', () => {
it('should return the vertical component of a vector correctly when given angles in each quadrant (and with angles < 0, or > 360)', () => {
@ -106,7 +104,7 @@ describe('testing changeSketchArguments', () => {
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
const { modifiedAst } = changeSketchArguments(
@ -130,7 +128,7 @@ const mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)`
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(95)
@ -192,7 +190,7 @@ describe('testing addTagForSketchOnFace', () => {
|> lineTo([0.46, -5.82], %)
`
const code = genCode(originalLine)
const ast = parse(code)
const ast = await parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(originalLine)
const sourceRange: [number, number] = [

View File

@ -8,9 +8,7 @@ import { getSketchSegmentFromSourceRange } from './sketchConstraints'
import { Selection } from 'lib/selections'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
// testing helper function
async function testingSwapSketchFnCall({
@ -30,7 +28,7 @@ async function testingSwapSketchFnCall({
type: 'default',
range: [startIndex, startIndex + callToSwap.length],
}
const ast = parse(inputCode)
const ast = await parse(inputCode)
const programMemory = await enginelessExecutor(ast)
const selections = {
codeBasedSelections: [range],
@ -353,7 +351,7 @@ const part001 = startSketchOn('XY')
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)`
it('normal case works', async () => {
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
const index = code.indexOf('// normal-segment') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,
@ -367,7 +365,7 @@ const part001 = startSketchOn('XY')
})
})
it('verify it works when the segment is in the `start` property', async () => {
const programMemory = await enginelessExecutor(parse(code))
const programMemory = await enginelessExecutor(await parse(code))
const index = code.indexOf('// segment-in-start') - 7
const { __geoMeta, ...segment } = getSketchSegmentFromSourceRange(
programMemory.root['part001'] as SketchGroup,

View File

@ -11,59 +11,57 @@ import { ToolTip } from '../../useStore'
import { Selections } from 'lib/selections'
import { enginelessExecutor } from '../../lib/testHelpers'
beforeAll(async () => {
await initPromise
})
beforeAll(() => initPromise)
describe('testing getConstraintType', () => {
const helper = getConstraintTypeFromSourceHelper
it('testing line', () => {
expect(helper(`line([5, myVar], %)`)).toBe('yRelative')
expect(helper(`line([myVar, 5], %)`)).toBe('xRelative')
it('testing line', async () => {
expect(await helper(`line([5, myVar], %)`)).toBe('yRelative')
expect(await helper(`line([myVar, 5], %)`)).toBe('xRelative')
})
it('testing lineTo', () => {
expect(helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
expect(helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
it('testing lineTo', async () => {
expect(await helper(`lineTo([5, myVar], %)`)).toBe('yAbsolute')
expect(await helper(`lineTo([myVar, 5], %)`)).toBe('xAbsolute')
})
it('testing angledLine', () => {
expect(helper(`angledLine([5, myVar], %)`)).toBe('length')
expect(helper(`angledLine([myVar, 5], %)`)).toBe('angle')
it('testing angledLine', async () => {
expect(await helper(`angledLine([5, myVar], %)`)).toBe('length')
expect(await helper(`angledLine([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineOfXLength', () => {
expect(helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
expect(helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
it('testing angledLineOfXLength', async () => {
expect(await helper(`angledLineOfXLength([5, myVar], %)`)).toBe('xRelative')
expect(await helper(`angledLineOfXLength([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineToX', () => {
expect(helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
expect(helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
it('testing angledLineToX', async () => {
expect(await helper(`angledLineToX([5, myVar], %)`)).toBe('xAbsolute')
expect(await helper(`angledLineToX([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineOfYLength', () => {
expect(helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
expect(helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
it('testing angledLineOfYLength', async () => {
expect(await helper(`angledLineOfYLength([5, myVar], %)`)).toBe('yRelative')
expect(await helper(`angledLineOfYLength([myVar, 5], %)`)).toBe('angle')
})
it('testing angledLineToY', () => {
expect(helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
expect(helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
it('testing angledLineToY', async () => {
expect(await helper(`angledLineToY([5, myVar], %)`)).toBe('yAbsolute')
expect(await helper(`angledLineToY([myVar, 5], %)`)).toBe('angle')
})
const helper2 = getConstraintTypeFromSourceHelper2
it('testing xLine', () => {
expect(helper2(`xLine(5, %)`)).toBe('yRelative')
it('testing xLine', async () => {
expect(await helper2(`xLine(5, %)`)).toBe('yRelative')
})
it('testing yLine', () => {
expect(helper2(`yLine(5, %)`)).toBe('xRelative')
it('testing yLine', async () => {
expect(await helper2(`yLine(5, %)`)).toBe('xRelative')
})
it('testing xLineTo', () => {
expect(helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
it('testing xLineTo', async () => {
expect(await helper2(`xLineTo(5, %)`)).toBe('yAbsolute')
})
it('testing yLineTo', () => {
expect(helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
it('testing yLineTo', async () => {
expect(await helper2(`yLineTo(5, %)`)).toBe('xAbsolute')
})
})
function getConstraintTypeFromSourceHelper(
async function getConstraintTypeFromSourceHelper(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parse(code)
): Promise<ReturnType<typeof getConstraintType>> {
const ast = await parse(code)
const args = (ast.body[0] as any).expression.arguments[0].elements as [
Value,
Value
@ -71,10 +69,10 @@ function getConstraintTypeFromSourceHelper(
const fnName = (ast.body[0] as any).expression.callee.name as ToolTip
return getConstraintType(args, fnName)
}
function getConstraintTypeFromSourceHelper2(
async function getConstraintTypeFromSourceHelper2(
code: string
): ReturnType<typeof getConstraintType> {
const ast = parse(code)
): Promise<ReturnType<typeof getConstraintType>> {
const ast = await parse(code)
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)
@ -199,7 +197,7 @@ const part001 = startSketchOn('XY')
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
`
it('should transform the ast', async () => {
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('//'))
@ -286,7 +284,7 @@ const part001 = startSketchOn('XY')
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
`
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for horizontal constraint'))
@ -344,7 +342,7 @@ const part001 = startSketchOn('XY')
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10
`
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) => ln.includes('// select for vertical constraint'))
@ -435,7 +433,7 @@ async function helperThing(
linesOfInterest: string[],
constraint: ConstraintType
): Promise<string> {
const ast = parse(inputScript)
const ast = await parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
.split('\n')
.filter((ln) =>
@ -467,7 +465,7 @@ async function helperThing(
}
describe('testing getConstraintLevelFromSourceRange', () => {
it('should divide up lines into free, partial and fully contrained', () => {
it('should divide up lines into free, partial and fully contrained', async () => {
const code = `const baseLength = 3
const baseThick = 1
const armThick = 0.5
@ -497,7 +495,7 @@ const part001 = startSketchOn('XY')
|> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = parse(code)
const ast = await parse(code)
const constraintLevels: ReturnType<
typeof getConstraintLevelFromSourceRange
>['level'][] = ['full', 'partial', 'free']

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