Merge branch 'main' into pierremtb/issue7349-transform-codemods-out-of-pipe
This commit is contained in:
		@ -534,7 +534,7 @@ profile001 = startProfile(sketch001, at = [-484.34, 484.95])
 | 
			
		||||
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
			
		||||
  |> close()
 | 
			
		||||
`
 | 
			
		||||
        const targetURL = `?create-file&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop`
 | 
			
		||||
        const targetURL = `?create-file=true&name=test&units=mm&code=${encodeURIComponent(btoa(code))}&ask-open-desktop=true`
 | 
			
		||||
        await page.goto(page.url() + targetURL)
 | 
			
		||||
        expect(page.url()).toContain(targetURL)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
@ -2362,7 +2362,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
 | 
			
		||||
 | 
			
		||||
    await test.step('add new profile', async () => {
 | 
			
		||||
      await toolbar.rectangleBtn.click()
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      await page.waitForTimeout(200)
 | 
			
		||||
      await rectStart()
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `profile005 = startProfile(sketch001, at = [15.68, -3.84])`
 | 
			
		||||
 | 
			
		||||
@ -3,187 +3,250 @@ import { uuidv4 } from '@src/lib/utils'
 | 
			
		||||
 | 
			
		||||
import { getUtils } from '@e2e/playwright/test-utils'
 | 
			
		||||
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
			
		||||
import type { Page } from '@playwright/test'
 | 
			
		||||
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | 
			
		||||
 | 
			
		||||
test.describe('Testing Camera Movement', () => {
 | 
			
		||||
  test('Can move camera reliably', async ({
 | 
			
		||||
  /**
 | 
			
		||||
   * hack that we're implemented our own retry instead of using retries built into playwright.
 | 
			
		||||
   * however each of these camera drags can be flaky, because of udp
 | 
			
		||||
   * and so putting them together means only one needs to fail to make this test extra flaky.
 | 
			
		||||
   * this way we can retry within the test
 | 
			
		||||
   * We could break them out into separate tests, but the longest past of the test is waiting
 | 
			
		||||
   * for the stream to start, so it can be good to bundle related things together.
 | 
			
		||||
   */
 | 
			
		||||
  const bakeInRetries = async ({
 | 
			
		||||
    mouseActions,
 | 
			
		||||
    afterPosition,
 | 
			
		||||
    beforePosition,
 | 
			
		||||
    retryCount = 0,
 | 
			
		||||
    page,
 | 
			
		||||
    context,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
  }: {
 | 
			
		||||
    mouseActions: () => Promise<void>
 | 
			
		||||
    beforePosition: [number, number, number]
 | 
			
		||||
    afterPosition: [number, number, number]
 | 
			
		||||
    retryCount?: number
 | 
			
		||||
    page: Page
 | 
			
		||||
    scene: SceneFixture
 | 
			
		||||
  }) => {
 | 
			
		||||
    const acceptableCamError = 5
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
    await test.step('Set up initial camera position', async () =>
 | 
			
		||||
      await scene.moveCameraTo({
 | 
			
		||||
        x: beforePosition[0],
 | 
			
		||||
        y: beforePosition[1],
 | 
			
		||||
        z: beforePosition[2],
 | 
			
		||||
      }))
 | 
			
		||||
 | 
			
		||||
    await u.openAndClearDebugPanel()
 | 
			
		||||
    await u.closeKclCodePanel()
 | 
			
		||||
 | 
			
		||||
    const camPos: [number, number, number] = [0, 85, 85]
 | 
			
		||||
    const bakeInRetries = async (
 | 
			
		||||
      mouseActions: any,
 | 
			
		||||
      xyz: [number, number, number],
 | 
			
		||||
      cnt = 0
 | 
			
		||||
    ) => {
 | 
			
		||||
      // hack that we're implemented our own retry instead of using retries built into playwright.
 | 
			
		||||
      // however each of these camera drags can be flaky, because of udp
 | 
			
		||||
      // and so putting them together means only one needs to fail to make this test extra flaky.
 | 
			
		||||
      // this way we can retry within the test
 | 
			
		||||
      // We could break them out into separate tests, but the longest past of the test is waiting
 | 
			
		||||
      // for the stream to start, so it can be good to bundle related things together.
 | 
			
		||||
 | 
			
		||||
      const camCommand: EngineCommand = {
 | 
			
		||||
        type: 'modeling_cmd_req',
 | 
			
		||||
        cmd_id: uuidv4(),
 | 
			
		||||
        cmd: {
 | 
			
		||||
          type: 'default_camera_look_at',
 | 
			
		||||
          center: { x: 0, y: 0, z: 0 },
 | 
			
		||||
          vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
 | 
			
		||||
          up: { x: 0, y: 0, z: 1 },
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      const updateCamCommand: EngineCommand = {
 | 
			
		||||
        type: 'modeling_cmd_req',
 | 
			
		||||
        cmd_id: uuidv4(),
 | 
			
		||||
        cmd: {
 | 
			
		||||
          type: 'default_camera_get_settings',
 | 
			
		||||
        },
 | 
			
		||||
      }
 | 
			
		||||
      await u.sendCustomCmd(camCommand)
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      await u.sendCustomCmd(updateCamCommand)
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
      // rotate
 | 
			
		||||
      await u.closeDebugPanel()
 | 
			
		||||
      await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      // const yo = page.getByTestId('cam-x-position').inputValue()
 | 
			
		||||
 | 
			
		||||
      await u.doAndWaitForImageDiff(async () => {
 | 
			
		||||
    await test.step('Do actions and watch for changes', async () =>
 | 
			
		||||
      u.doAndWaitForImageDiff(async () => {
 | 
			
		||||
        await mouseActions()
 | 
			
		||||
 | 
			
		||||
        await u.openAndClearDebugPanel()
 | 
			
		||||
 | 
			
		||||
        await u.closeDebugPanel()
 | 
			
		||||
        await page.waitForTimeout(100)
 | 
			
		||||
      }, 300)
 | 
			
		||||
      }, 300))
 | 
			
		||||
 | 
			
		||||
    await u.openAndClearDebugPanel()
 | 
			
		||||
    await expect(page.getByTestId('cam-x-position')).toBeAttached()
 | 
			
		||||
 | 
			
		||||
    const vals = await Promise.all([
 | 
			
		||||
      page.getByTestId('cam-x-position').inputValue(),
 | 
			
		||||
      page.getByTestId('cam-y-position').inputValue(),
 | 
			
		||||
      page.getByTestId('cam-z-position').inputValue(),
 | 
			
		||||
    ])
 | 
			
		||||
    const errors = vals.map((v, i) => Math.abs(Number(v) - afterPosition[i]))
 | 
			
		||||
    let shouldRetry = false
 | 
			
		||||
 | 
			
		||||
    if (errors.some((e) => e > acceptableCamError)) {
 | 
			
		||||
      if (retryCount > 2) {
 | 
			
		||||
        console.log('xVal', vals[0], 'xError', errors[0])
 | 
			
		||||
        console.log('yVal', vals[1], 'yError', errors[1])
 | 
			
		||||
        console.log('zVal', vals[2], 'zError', errors[2])
 | 
			
		||||
 | 
			
		||||
        throw new Error('Camera position not as expected', {
 | 
			
		||||
          cause: {
 | 
			
		||||
            vals,
 | 
			
		||||
            errors,
 | 
			
		||||
          },
 | 
			
		||||
        })
 | 
			
		||||
      }
 | 
			
		||||
      shouldRetry = true
 | 
			
		||||
    }
 | 
			
		||||
    if (shouldRetry) {
 | 
			
		||||
      await bakeInRetries({
 | 
			
		||||
        mouseActions,
 | 
			
		||||
        afterPosition: afterPosition,
 | 
			
		||||
        beforePosition: beforePosition,
 | 
			
		||||
        retryCount: retryCount + 1,
 | 
			
		||||
        page,
 | 
			
		||||
        scene,
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  test(
 | 
			
		||||
    'Can pan and zoom camera reliably',
 | 
			
		||||
    {
 | 
			
		||||
      tag: '@web',
 | 
			
		||||
    },
 | 
			
		||||
    async ({ page, homePage, scene, cmdBar }) => {
 | 
			
		||||
      const u = await getUtils(page)
 | 
			
		||||
      const camInitialPosition: [number, number, number] = [0, 85, 85]
 | 
			
		||||
 | 
			
		||||
      await homePage.goToModelingScene()
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
      await u.openAndClearDebugPanel()
 | 
			
		||||
      await page.getByTestId('cam-x-position').isVisible()
 | 
			
		||||
      await u.closeKclCodePanel()
 | 
			
		||||
 | 
			
		||||
      const vals = await Promise.all([
 | 
			
		||||
        page.getByTestId('cam-x-position').inputValue(),
 | 
			
		||||
        page.getByTestId('cam-y-position').inputValue(),
 | 
			
		||||
        page.getByTestId('cam-z-position').inputValue(),
 | 
			
		||||
      ])
 | 
			
		||||
      const xError = Math.abs(Number(vals[0]) + xyz[0])
 | 
			
		||||
      const yError = Math.abs(Number(vals[1]) + xyz[1])
 | 
			
		||||
      const zError = Math.abs(Number(vals[2]) + xyz[2])
 | 
			
		||||
      await test.step('Pan', async () => {
 | 
			
		||||
        await bakeInRetries({
 | 
			
		||||
          mouseActions: async () => {
 | 
			
		||||
            await page.keyboard.down('Shift')
 | 
			
		||||
            await page.mouse.move(600, 200)
 | 
			
		||||
            await page.mouse.down({ button: 'right' })
 | 
			
		||||
            // Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
 | 
			
		||||
            await page.mouse.move(700, 200)
 | 
			
		||||
            await page.mouse.up({ button: 'right' })
 | 
			
		||||
            await page.keyboard.up('Shift')
 | 
			
		||||
            await page.waitForTimeout(200)
 | 
			
		||||
          },
 | 
			
		||||
          afterPosition: [19, 85, 85],
 | 
			
		||||
          beforePosition: camInitialPosition,
 | 
			
		||||
          page,
 | 
			
		||||
          scene,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      let shouldRetry = false
 | 
			
		||||
      await test.step('Zoom with click and drag', async () => {
 | 
			
		||||
        await bakeInRetries({
 | 
			
		||||
          mouseActions: async () => {
 | 
			
		||||
            await page.keyboard.down('Control')
 | 
			
		||||
            await page.mouse.move(700, 400)
 | 
			
		||||
            await page.mouse.down({ button: 'right' })
 | 
			
		||||
            await page.mouse.move(700, 300)
 | 
			
		||||
            await page.mouse.up({ button: 'right' })
 | 
			
		||||
            await page.keyboard.up('Control')
 | 
			
		||||
          },
 | 
			
		||||
          afterPosition: [0, 118, 118],
 | 
			
		||||
          beforePosition: camInitialPosition,
 | 
			
		||||
          page,
 | 
			
		||||
          scene,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      if (xError > 5 || yError > 5 || zError > 5) {
 | 
			
		||||
        if (cnt > 2) {
 | 
			
		||||
          console.log('xVal', vals[0], 'xError', xError)
 | 
			
		||||
          console.log('yVal', vals[1], 'yError', yError)
 | 
			
		||||
          console.log('zVal', vals[2], 'zError', zError)
 | 
			
		||||
 | 
			
		||||
          throw new Error('Camera position not as expected')
 | 
			
		||||
      await test.step('Zoom with scrollwheel', async () => {
 | 
			
		||||
        const refreshCamValuesCmd: EngineCommand = {
 | 
			
		||||
          type: 'modeling_cmd_req',
 | 
			
		||||
          cmd_id: uuidv4(),
 | 
			
		||||
          cmd: {
 | 
			
		||||
            type: 'default_camera_get_settings',
 | 
			
		||||
          },
 | 
			
		||||
        }
 | 
			
		||||
        shouldRetry = true
 | 
			
		||||
      }
 | 
			
		||||
      await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
 | 
			
		||||
        await bakeInRetries({
 | 
			
		||||
          mouseActions: async () => {
 | 
			
		||||
            await page.mouse.move(700, 400)
 | 
			
		||||
            await page.mouse.wheel(0, -150)
 | 
			
		||||
 | 
			
		||||
            // Scroll zooming doesn't update the debug pane's cam position values,
 | 
			
		||||
            // so we have to force a refresh.
 | 
			
		||||
            await u.openAndClearDebugPanel()
 | 
			
		||||
            await u.sendCustomCmd(refreshCamValuesCmd)
 | 
			
		||||
            await u.waitForCmdReceive('default_camera_get_settings')
 | 
			
		||||
            await u.closeDebugPanel()
 | 
			
		||||
          },
 | 
			
		||||
          afterPosition: [0, 42.5, 42.5],
 | 
			
		||||
          beforePosition: camInitialPosition,
 | 
			
		||||
          page,
 | 
			
		||||
          scene,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    await bakeInRetries(async () => {
 | 
			
		||||
      await page.mouse.move(700, 200)
 | 
			
		||||
      await page.mouse.down({ button: 'right' })
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
      const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
 | 
			
		||||
      expect(appLogoBBox).not.toBeNull()
 | 
			
		||||
      if (!appLogoBBox) throw new Error('app logo not found')
 | 
			
		||||
      await page.mouse.move(
 | 
			
		||||
        appLogoBBox.x + appLogoBBox.width / 2,
 | 
			
		||||
        appLogoBBox.y + appLogoBBox.height / 2
 | 
			
		||||
      )
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      await page.mouse.move(600, 303)
 | 
			
		||||
      await page.waitForTimeout(100)
 | 
			
		||||
      await page.mouse.up({ button: 'right' })
 | 
			
		||||
    }, [4, -10.5, -120])
 | 
			
		||||
  test(
 | 
			
		||||
    'Can orbit camera reliably',
 | 
			
		||||
    {
 | 
			
		||||
      tag: '@web',
 | 
			
		||||
    },
 | 
			
		||||
    async ({ page, homePage, scene, cmdBar }) => {
 | 
			
		||||
      const u = await getUtils(page)
 | 
			
		||||
      const initialCamPosition: [number, number, number] = [0, 85, 85]
 | 
			
		||||
 | 
			
		||||
    await bakeInRetries(async () => {
 | 
			
		||||
      await page.keyboard.down('Shift')
 | 
			
		||||
      await page.mouse.move(600, 200)
 | 
			
		||||
      await page.mouse.down({ button: 'right' })
 | 
			
		||||
      // Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
 | 
			
		||||
      await page.mouse.move(700, 200)
 | 
			
		||||
      await page.mouse.up({ button: 'right' })
 | 
			
		||||
      await page.keyboard.up('Shift')
 | 
			
		||||
    }, [-19, -85, -85])
 | 
			
		||||
      await homePage.goToModelingScene()
 | 
			
		||||
      // this turns on the debug pane setting as well
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    const camCommand: EngineCommand = {
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'default_camera_look_at',
 | 
			
		||||
        center: { x: 0, y: 0, z: 0 },
 | 
			
		||||
        vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
 | 
			
		||||
        up: { x: 0, y: 0, z: 1 },
 | 
			
		||||
      },
 | 
			
		||||
      await u.openAndClearDebugPanel()
 | 
			
		||||
      await u.closeKclCodePanel()
 | 
			
		||||
 | 
			
		||||
      await test.step('Test orbit with spherical mode', async () => {
 | 
			
		||||
        await bakeInRetries({
 | 
			
		||||
          mouseActions: async () => {
 | 
			
		||||
            await page.mouse.move(700, 200)
 | 
			
		||||
            await page.mouse.down({ button: 'right' })
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
            const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
 | 
			
		||||
            expect(appLogoBBox).not.toBeNull()
 | 
			
		||||
            if (!appLogoBBox) throw new Error('app logo not found')
 | 
			
		||||
            await page.mouse.move(
 | 
			
		||||
              appLogoBBox.x + appLogoBBox.width / 2,
 | 
			
		||||
              appLogoBBox.y + appLogoBBox.height / 2
 | 
			
		||||
            )
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
            await page.mouse.move(600, 303)
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
            await page.mouse.up({ button: 'right' })
 | 
			
		||||
          },
 | 
			
		||||
          afterPosition: [-4, 10.5, 120],
 | 
			
		||||
          beforePosition: initialCamPosition,
 | 
			
		||||
          page,
 | 
			
		||||
          scene,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Test orbit with trackball mode', async () => {
 | 
			
		||||
        await test.step('Set orbitMode to trackball', async () => {
 | 
			
		||||
          await cmdBar.openCmdBar()
 | 
			
		||||
          await cmdBar.selectOption({ name: 'camera orbit' }).click()
 | 
			
		||||
          await cmdBar.selectOption({ name: 'trackball' }).click()
 | 
			
		||||
          await expect(
 | 
			
		||||
            page.getByText(`camera orbit to "trackball"`)
 | 
			
		||||
          ).toBeVisible()
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await bakeInRetries({
 | 
			
		||||
          mouseActions: async () => {
 | 
			
		||||
            await page.mouse.move(700, 200)
 | 
			
		||||
            await page.mouse.down({ button: 'right' })
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
            const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
 | 
			
		||||
            expect(appLogoBBox).not.toBeNull()
 | 
			
		||||
            if (!appLogoBBox) {
 | 
			
		||||
              throw new Error('app logo not found')
 | 
			
		||||
            }
 | 
			
		||||
            await page.mouse.move(
 | 
			
		||||
              appLogoBBox.x + appLogoBBox.width / 2,
 | 
			
		||||
              appLogoBBox.y + appLogoBBox.height / 2
 | 
			
		||||
            )
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
            await page.mouse.move(600, 303)
 | 
			
		||||
            await page.waitForTimeout(100)
 | 
			
		||||
            await page.mouse.up({ button: 'right' })
 | 
			
		||||
          },
 | 
			
		||||
          afterPosition: [18.06, -42.79, 110.87],
 | 
			
		||||
          beforePosition: initialCamPosition,
 | 
			
		||||
          page,
 | 
			
		||||
          scene,
 | 
			
		||||
        })
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    const updateCamCommand: EngineCommand = {
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'default_camera_get_settings',
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
    await u.sendCustomCmd(camCommand)
 | 
			
		||||
    await page.waitForTimeout(100)
 | 
			
		||||
    await u.sendCustomCmd(updateCamCommand)
 | 
			
		||||
    await page.waitForTimeout(100)
 | 
			
		||||
 | 
			
		||||
    await u.clearCommandLogs()
 | 
			
		||||
    await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
			
		||||
    await page.waitForTimeout(200)
 | 
			
		||||
 | 
			
		||||
    // zoom
 | 
			
		||||
    await u.doAndWaitForImageDiff(async () => {
 | 
			
		||||
      await page.keyboard.down('Control')
 | 
			
		||||
      await page.mouse.move(700, 400)
 | 
			
		||||
      await page.mouse.down({ button: 'right' })
 | 
			
		||||
      await page.mouse.move(700, 300)
 | 
			
		||||
      await page.mouse.up({ button: 'right' })
 | 
			
		||||
      await page.keyboard.up('Control')
 | 
			
		||||
 | 
			
		||||
      await u.openDebugPanel()
 | 
			
		||||
      await page.waitForTimeout(300)
 | 
			
		||||
      await u.clearCommandLogs()
 | 
			
		||||
 | 
			
		||||
      await u.closeDebugPanel()
 | 
			
		||||
    }, 300)
 | 
			
		||||
 | 
			
		||||
    // zoom with scroll
 | 
			
		||||
    await u.openAndClearDebugPanel()
 | 
			
		||||
    // TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
 | 
			
		||||
    // await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
 | 
			
		||||
    // await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
 | 
			
		||||
    // await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
 | 
			
		||||
 | 
			
		||||
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
 | 
			
		||||
    await bakeInRetries(async () => {
 | 
			
		||||
      await page.mouse.move(700, 400)
 | 
			
		||||
      await page.mouse.wheel(0, -100)
 | 
			
		||||
    }, [0, -85, -85])
 | 
			
		||||
  })
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // TODO: fix after electron migration is merged
 | 
			
		||||
  test('Zoom should be consistent when exiting or entering sketches', async ({
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -627,26 +627,22 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "criterion"
 | 
			
		||||
version = "0.5.1"
 | 
			
		||||
version = "0.6.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
 | 
			
		||||
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anes",
 | 
			
		||||
 "cast",
 | 
			
		||||
 "ciborium",
 | 
			
		||||
 "clap",
 | 
			
		||||
 "criterion-plot",
 | 
			
		||||
 "futures",
 | 
			
		||||
 "is-terminal",
 | 
			
		||||
 "itertools 0.10.5",
 | 
			
		||||
 "itertools 0.13.0",
 | 
			
		||||
 "num-traits 0.2.19",
 | 
			
		||||
 "once_cell",
 | 
			
		||||
 "oorandom",
 | 
			
		||||
 "plotters",
 | 
			
		||||
 "rayon",
 | 
			
		||||
 "regex",
 | 
			
		||||
 "serde",
 | 
			
		||||
 "serde_derive",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "tinytemplate",
 | 
			
		||||
 "tokio",
 | 
			
		||||
@ -1815,7 +1811,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-bumper"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1826,7 +1822,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-derive-docs"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "Inflector",
 | 
			
		||||
 "anyhow",
 | 
			
		||||
@ -1845,7 +1841,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-directory-test-macro"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "convert_case",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
@ -1855,7 +1851,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-language-server"
 | 
			
		||||
version = "0.2.78"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1876,7 +1872,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-language-server-release"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1896,7 +1892,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.78"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
@ -1973,7 +1969,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-python-bindings"
 | 
			
		||||
version = "0.3.78"
 | 
			
		||||
version = "0.3.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "kcl-lib",
 | 
			
		||||
@ -1988,7 +1984,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "hyper 0.14.32",
 | 
			
		||||
@ -2001,7 +1997,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-to-core"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -2015,7 +2011,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-wasm-lib"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bson",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-bumper"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-api"
 | 
			
		||||
rust-version = "1.76"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-derive-docs"
 | 
			
		||||
description = "A tool for generating documentation from Rust derive macros"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-directory-test-macro"
 | 
			
		||||
description = "A tool for generating tests from a directory of kcl files"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-language-server-release"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
 | 
			
		||||
publish = false
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@
 | 
			
		||||
name = "kcl-language-server"
 | 
			
		||||
description = "A language server for KCL."
 | 
			
		||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
 | 
			
		||||
version = "0.2.78"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
description = "KittyCAD Language implementation and tools"
 | 
			
		||||
version = "0.2.78"
 | 
			
		||||
version = "0.2.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
@ -130,7 +130,7 @@ tabled = ["dep:tabled"]
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
approx = "0.5"
 | 
			
		||||
base64 = "0.22.1"
 | 
			
		||||
criterion = { version = "0.5.1", features = ["async_tokio"] }
 | 
			
		||||
criterion = { version = "0.6.0", features = ["async_tokio"] }
 | 
			
		||||
expectorate = "1.1.0"
 | 
			
		||||
handlebars = "6.3.2"
 | 
			
		||||
image = { version = "0.25.6", default-features = false, features = ["png"] }
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,10 @@
 | 
			
		||||
use std::{
 | 
			
		||||
    fs,
 | 
			
		||||
    hint::black_box,
 | 
			
		||||
    path::{Path, PathBuf},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
 | 
			
		||||
use criterion::{criterion_group, criterion_main, Criterion};
 | 
			
		||||
 | 
			
		||||
const IGNORE_DIRS: [&str; 2] = ["step", "screenshots"];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
 | 
			
		||||
use std::hint::black_box;
 | 
			
		||||
 | 
			
		||||
use criterion::{criterion_group, criterion_main, Criterion};
 | 
			
		||||
 | 
			
		||||
pub fn bench_parse(c: &mut Criterion) {
 | 
			
		||||
    for (name, file) in [
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,6 @@
 | 
			
		||||
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
 | 
			
		||||
use std::hint::black_box;
 | 
			
		||||
 | 
			
		||||
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
 | 
			
		||||
use kcl_lib::kcl_lsp_server;
 | 
			
		||||
use tokio::runtime::Runtime;
 | 
			
		||||
use tower_lsp::LanguageServer;
 | 
			
		||||
 | 
			
		||||
@ -67,6 +67,7 @@ pub struct TcpRead {
 | 
			
		||||
 | 
			
		||||
/// Occurs when client couldn't read from the WebSocket to the engine.
 | 
			
		||||
// #[derive(Debug)]
 | 
			
		||||
#[allow(clippy::large_enum_variant)]
 | 
			
		||||
pub enum WebSocketReadError {
 | 
			
		||||
    /// Could not read a message due to WebSocket errors.
 | 
			
		||||
    Read(tokio_tungstenite::tungstenite::Error),
 | 
			
		||||
 | 
			
		||||
@ -1351,7 +1351,7 @@ pub(crate) async fn execute_pipe_body(
 | 
			
		||||
    // Now that we've evaluated the first child expression in the pipeline, following child expressions
 | 
			
		||||
    // should use the previous child expression for %.
 | 
			
		||||
    // This means there's no more need for the previous pipe_value from the parent AST node above this one.
 | 
			
		||||
    let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
 | 
			
		||||
    let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
 | 
			
		||||
    // Evaluate remaining elements.
 | 
			
		||||
    let result = inner_execute_pipe_body(exec_state, body, ctx).await;
 | 
			
		||||
    // Restore the previous pipe value.
 | 
			
		||||
 | 
			
		||||
@ -24,6 +24,7 @@ type Point3D = kcmc::shared::Point3d<f64>;
 | 
			
		||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
#[allow(clippy::large_enum_variant)]
 | 
			
		||||
pub enum Geometry {
 | 
			
		||||
    Sketch(Sketch),
 | 
			
		||||
    Solid(Solid),
 | 
			
		||||
@ -52,6 +53,7 @@ impl Geometry {
 | 
			
		||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
#[allow(clippy::large_enum_variant)]
 | 
			
		||||
pub enum GeometryWithImportedGeometry {
 | 
			
		||||
    Sketch(Sketch),
 | 
			
		||||
    Solid(Solid),
 | 
			
		||||
 | 
			
		||||
@ -187,7 +187,7 @@ impl RuntimeType {
 | 
			
		||||
                };
 | 
			
		||||
                RuntimeType::Primitive(PrimitiveType::Number(ty))
 | 
			
		||||
            }
 | 
			
		||||
            AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
 | 
			
		||||
            AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
 | 
			
		||||
            AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
 | 
			
		||||
            AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
 | 
			
		||||
            AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
 | 
			
		||||
 | 
			
		||||
@ -1189,7 +1189,6 @@ impl LanguageServer for Backend {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
 | 
			
		||||
        // ADAM: This is the entrypoint.
 | 
			
		||||
        let mut completions = vec![CompletionItem {
 | 
			
		||||
            label: PIPE_OPERATOR.to_string(),
 | 
			
		||||
            label_details: None,
 | 
			
		||||
 | 
			
		||||
@ -228,7 +228,7 @@ impl PrimitiveType {
 | 
			
		||||
        let mut hasher = Sha256::new();
 | 
			
		||||
        match self {
 | 
			
		||||
            PrimitiveType::Any => hasher.update(b"any"),
 | 
			
		||||
            PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
 | 
			
		||||
            PrimitiveType::Named { id } => hasher.update(id.compute_digest()),
 | 
			
		||||
            PrimitiveType::String => hasher.update(b"string"),
 | 
			
		||||
            PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
 | 
			
		||||
            PrimitiveType::Boolean => hasher.update(b"bool"),
 | 
			
		||||
 | 
			
		||||
@ -454,7 +454,7 @@ impl Node<Program> {
 | 
			
		||||
                        alpha: c.a,
 | 
			
		||||
                    },
 | 
			
		||||
                };
 | 
			
		||||
                if colors.borrow().iter().any(|c| *c == color) {
 | 
			
		||||
                if colors.borrow().contains(&color) {
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                colors.borrow_mut().push(color);
 | 
			
		||||
@ -3230,7 +3230,7 @@ pub enum PrimitiveType {
 | 
			
		||||
    /// `fn`, type of functions.
 | 
			
		||||
    Function(FunctionType),
 | 
			
		||||
    /// An identifier used as a type (not really a primitive type, but whatever).
 | 
			
		||||
    Named(Node<Identifier>),
 | 
			
		||||
    Named { id: Node<Identifier> },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl PrimitiveType {
 | 
			
		||||
@ -3286,7 +3286,7 @@ impl fmt::Display for PrimitiveType {
 | 
			
		||||
                }
 | 
			
		||||
                Ok(())
 | 
			
		||||
            }
 | 
			
		||||
            PrimitiveType::Named(n) => write!(f, "{}", n.name),
 | 
			
		||||
            PrimitiveType::Named { id: n } => write!(f, "{}", n.name),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2938,7 +2938,7 @@ fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
 | 
			
		||||
        (identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
 | 
			
		||||
            let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
 | 
			
		||||
            result.inner =
 | 
			
		||||
                PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
 | 
			
		||||
                PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident });
 | 
			
		||||
            result
 | 
			
		||||
        }),
 | 
			
		||||
    ))
 | 
			
		||||
 | 
			
		||||
@ -3504,3 +3504,24 @@ mod var_ref_in_own_def {
 | 
			
		||||
        super::execute(TEST_NAME, true).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
mod ascription_unknown_type {
 | 
			
		||||
    const TEST_NAME: &str = "ascription_unknown_type";
 | 
			
		||||
 | 
			
		||||
    /// Test parsing KCL.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse() {
 | 
			
		||||
        super::parse(TEST_NAME)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that parsing and unparsing KCL produces the original KCL input.
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn unparse() {
 | 
			
		||||
        super::unparse(TEST_NAME).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that KCL is executed correctly.
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn kcl_test_execute() {
 | 
			
		||||
        super::execute(TEST_NAME, true).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,7 @@ use crate::{
 | 
			
		||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(untagged)]
 | 
			
		||||
#[allow(clippy::large_enum_variant)]
 | 
			
		||||
pub enum SweepPath {
 | 
			
		||||
    Sketch(Sketch),
 | 
			
		||||
    Helix(Box<Helix>),
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,32 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Artifact commands ascription_unknown_type.kcl
 | 
			
		||||
---
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "edge_lines_visible",
 | 
			
		||||
      "hidden": false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart ascription_unknown_type.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										67
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ast.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ast.snap
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Result of parsing ascription_unknown_type.kcl
 | 
			
		||||
---
 | 
			
		||||
{
 | 
			
		||||
  "Ok": {
 | 
			
		||||
    "body": [
 | 
			
		||||
      {
 | 
			
		||||
        "commentStart": 0,
 | 
			
		||||
        "declaration": {
 | 
			
		||||
          "commentStart": 0,
 | 
			
		||||
          "end": 0,
 | 
			
		||||
          "id": {
 | 
			
		||||
            "commentStart": 0,
 | 
			
		||||
            "end": 0,
 | 
			
		||||
            "name": "z",
 | 
			
		||||
            "start": 0,
 | 
			
		||||
            "type": "Identifier"
 | 
			
		||||
          },
 | 
			
		||||
          "init": {
 | 
			
		||||
            "commentStart": 0,
 | 
			
		||||
            "end": 0,
 | 
			
		||||
            "expr": {
 | 
			
		||||
              "commentStart": 0,
 | 
			
		||||
              "end": 0,
 | 
			
		||||
              "raw": "10",
 | 
			
		||||
              "start": 0,
 | 
			
		||||
              "type": "Literal",
 | 
			
		||||
              "type": "Literal",
 | 
			
		||||
              "value": {
 | 
			
		||||
                "value": 10.0,
 | 
			
		||||
                "suffix": "None"
 | 
			
		||||
              }
 | 
			
		||||
            },
 | 
			
		||||
            "start": 0,
 | 
			
		||||
            "ty": {
 | 
			
		||||
              "commentStart": 0,
 | 
			
		||||
              "end": 0,
 | 
			
		||||
              "id": {
 | 
			
		||||
                "commentStart": 0,
 | 
			
		||||
                "end": 0,
 | 
			
		||||
                "name": "NotARealType",
 | 
			
		||||
                "start": 0,
 | 
			
		||||
                "type": "Identifier"
 | 
			
		||||
              },
 | 
			
		||||
              "p_type": "Named",
 | 
			
		||||
              "start": 0,
 | 
			
		||||
              "type": "Primitive"
 | 
			
		||||
            },
 | 
			
		||||
            "type": "AscribedExpression",
 | 
			
		||||
            "type": "AscribedExpression"
 | 
			
		||||
          },
 | 
			
		||||
          "start": 0,
 | 
			
		||||
          "type": "VariableDeclarator"
 | 
			
		||||
        },
 | 
			
		||||
        "end": 0,
 | 
			
		||||
        "kind": "const",
 | 
			
		||||
        "start": 0,
 | 
			
		||||
        "type": "VariableDeclaration",
 | 
			
		||||
        "type": "VariableDeclaration"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "commentStart": 0,
 | 
			
		||||
    "end": 0,
 | 
			
		||||
    "start": 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Error from executing ascription_unknown_type.kcl
 | 
			
		||||
---
 | 
			
		||||
KCL Semantic error
 | 
			
		||||
 | 
			
		||||
  × semantic: Unknown type: NotARealType
 | 
			
		||||
   ╭────
 | 
			
		||||
 1 │ z = 10: NotARealType
 | 
			
		||||
   ·     ─┬
 | 
			
		||||
   ·      ╰── tests/ascription_unknown_type/input.kcl
 | 
			
		||||
   ╰────
 | 
			
		||||
							
								
								
									
										1
									
								
								rust/kcl-lib/tests/ascription_unknown_type/input.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								rust/kcl-lib/tests/ascription_unknown_type/input.kcl
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
			
		||||
z = 10: NotARealType
 | 
			
		||||
							
								
								
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ops.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/ops.snap
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Operations executed ascription_unknown_type.kcl
 | 
			
		||||
---
 | 
			
		||||
[]
 | 
			
		||||
							
								
								
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/unparsed.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								rust/kcl-lib/tests/ascription_unknown_type/unparsed.snap
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl-lib/src/simulation_tests.rs
 | 
			
		||||
description: Result of unparsing ascription_unknown_type.kcl
 | 
			
		||||
---
 | 
			
		||||
z = 10: NotARealType
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-python-bindings"
 | 
			
		||||
version = "0.3.78"
 | 
			
		||||
version = "0.3.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
repository = "https://github.com/kittycad/modeling-app"
 | 
			
		||||
exclude = ["tests/*", "files/*", "venv/*"]
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
description = "A test server for KCL"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-to-core"
 | 
			
		||||
description = "Utility methods to convert kcl to engine core executable tests"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-wasm-lib"
 | 
			
		||||
version = "0.1.78"
 | 
			
		||||
version = "0.1.79"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
rust-version = "1.83"
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
 | 
			
		||||
use gloo_utils::format::JsValueSerdeExt;
 | 
			
		||||
use kcl_lib::{wasm_engine::FileManager, EngineManager, Program};
 | 
			
		||||
use kcl_lib::{wasm_engine::FileManager, EngineManager, ExecOutcome, KclError, KclErrorWithOutputs, Program};
 | 
			
		||||
use wasm_bindgen::prelude::*;
 | 
			
		||||
 | 
			
		||||
#[wasm_bindgen]
 | 
			
		||||
@ -56,7 +56,7 @@ impl Context {
 | 
			
		||||
            return Ok(kcl_lib::ExecutorContext::new_mock(
 | 
			
		||||
                self.mock_engine.clone(),
 | 
			
		||||
                self.fs.clone(),
 | 
			
		||||
                settings.into(),
 | 
			
		||||
                settings,
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -74,18 +74,38 @@ impl Context {
 | 
			
		||||
        program_ast_json: &str,
 | 
			
		||||
        path: Option<String>,
 | 
			
		||||
        settings: &str,
 | 
			
		||||
    ) -> Result<JsValue, String> {
 | 
			
		||||
    ) -> Result<JsValue, JsValue> {
 | 
			
		||||
        console_error_panic_hook::set_once();
 | 
			
		||||
 | 
			
		||||
        let program: Program = serde_json::from_str(program_ast_json).map_err(|e| e.to_string())?;
 | 
			
		||||
        self.execute_typed(program_ast_json, path, settings)
 | 
			
		||||
            .await
 | 
			
		||||
            .and_then(|outcome| JsValue::from_serde(&outcome).map_err(|e| {
 | 
			
		||||
                // The serde-wasm-bindgen does not work here because of weird HashMap issues.
 | 
			
		||||
                // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
 | 
			
		||||
                KclErrorWithOutputs::no_outputs(KclError::internal(
 | 
			
		||||
                    format!("Could not serialize successful KCL result. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"),
 | 
			
		||||
                ))}))
 | 
			
		||||
            .map_err(|e: KclErrorWithOutputs| JsValue::from_serde(&e).unwrap())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        let ctx = self.create_executor_ctx(settings, path, false)?;
 | 
			
		||||
        match ctx.run_with_caching(program).await {
 | 
			
		||||
            // The serde-wasm-bindgen does not work here because of weird HashMap issues.
 | 
			
		||||
            // DO NOT USE serde_wasm_bindgen::to_value it will break the frontend.
 | 
			
		||||
            Ok(outcome) => JsValue::from_serde(&outcome).map_err(|e| e.to_string()),
 | 
			
		||||
            Err(err) => Err(serde_json::to_string(&err).map_err(|serde_err| serde_err.to_string())?),
 | 
			
		||||
        }
 | 
			
		||||
    async fn execute_typed(
 | 
			
		||||
        &self,
 | 
			
		||||
        program_ast_json: &str,
 | 
			
		||||
        path: Option<String>,
 | 
			
		||||
        settings: &str,
 | 
			
		||||
    ) -> Result<ExecOutcome, KclErrorWithOutputs> {
 | 
			
		||||
        let program: Program = serde_json::from_str(program_ast_json).map_err(|e| {
 | 
			
		||||
                let err = KclError::internal(
 | 
			
		||||
                    format!("Could not deserialize KCL AST. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"),
 | 
			
		||||
                );
 | 
			
		||||
                KclErrorWithOutputs::no_outputs(err)
 | 
			
		||||
            })?;
 | 
			
		||||
        let ctx = self
 | 
			
		||||
                .create_executor_ctx(settings, path, false)
 | 
			
		||||
                .map_err(|e| KclErrorWithOutputs::no_outputs(KclError::internal(
 | 
			
		||||
                    format!("Could not create KCL executor context. This is a bug in KCL and not in your code, please report this to Zoo. Details: {e}"),
 | 
			
		||||
                )))?;
 | 
			
		||||
        ctx.run_with_caching(program).await
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Reset the scene and bust the cache.
 | 
			
		||||
 | 
			
		||||
@ -1,3 +1,3 @@
 | 
			
		||||
[toolchain]
 | 
			
		||||
channel = "1.86"
 | 
			
		||||
channel = "1.87"
 | 
			
		||||
components = ["clippy", "rustfmt"]
 | 
			
		||||
 | 
			
		||||
@ -1315,7 +1315,7 @@ export class CameraControls {
 | 
			
		||||
    )
 | 
			
		||||
    if (
 | 
			
		||||
      initialInteractionType === 'rotate' &&
 | 
			
		||||
      this.engineCommandManager.settings.cameraOrbit === 'trackball'
 | 
			
		||||
      this.getSettings?.().modeling.cameraOrbit.current === 'trackball'
 | 
			
		||||
    ) {
 | 
			
		||||
      return 'rotatetrackball'
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,11 @@
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
import { useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
import { isDesktop } from '@src/lib/isDesktop'
 | 
			
		||||
import { PATHS } from '@src/lib/paths'
 | 
			
		||||
import { IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM } from '@src/lib/constants'
 | 
			
		||||
import { useAuthState } from '@src/lib/singletons'
 | 
			
		||||
import { generateSignInUrl } from '@src/routes/utils'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A simple hook that listens to the auth state of the app and navigates
 | 
			
		||||
@ -12,6 +15,10 @@ export function useAuthNavigation() {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const authState = useAuthState()
 | 
			
		||||
  const [searchParams] = useSearchParams()
 | 
			
		||||
  const requestingImmediateSignInIfNecessary = searchParams.has(
 | 
			
		||||
    IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  // Subscribe to the auth state of the app and navigate accordingly.
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@ -24,6 +31,11 @@ export function useAuthNavigation() {
 | 
			
		||||
      authState.matches('loggedOut') &&
 | 
			
		||||
      !location.pathname.includes(PATHS.SIGN_IN)
 | 
			
		||||
    ) {
 | 
			
		||||
      if (requestingImmediateSignInIfNecessary && !isDesktop()) {
 | 
			
		||||
        window.location.href = generateSignInUrl()
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      navigate(PATHS.SIGN_IN + (location.search || ''))
 | 
			
		||||
    }
 | 
			
		||||
  }, [authState, location.pathname])
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import { useSearchParams } from 'react-router-dom'
 | 
			
		||||
import { base64ToString } from '@src/lib/base64'
 | 
			
		||||
import type { ProjectsCommandSchema } from '@src/lib/commandBarConfigs/projectsCommandConfig'
 | 
			
		||||
import {
 | 
			
		||||
  ASK_TO_OPEN_QUERY_PARAM,
 | 
			
		||||
  CMD_GROUP_QUERY_PARAM,
 | 
			
		||||
  CMD_NAME_QUERY_PARAM,
 | 
			
		||||
  CREATE_FILE_URL_PARAM,
 | 
			
		||||
@ -36,8 +37,15 @@ export type CreateFileSchemaMethodOptional = Omit<
 | 
			
		||||
export function useQueryParamEffects() {
 | 
			
		||||
  const authState = useAuthState()
 | 
			
		||||
  const [searchParams, setSearchParams] = useSearchParams()
 | 
			
		||||
  const shouldInvokeCreateFile = searchParams.has(CREATE_FILE_URL_PARAM)
 | 
			
		||||
  const hasAskToOpen = !isDesktop() && searchParams.has(ASK_TO_OPEN_QUERY_PARAM)
 | 
			
		||||
  // Let hasAskToOpen be handled by the OpenInDesktopAppHandler component first to avoid racing with it,
 | 
			
		||||
  // only deal with other params after user decided to open in desktop or web.
 | 
			
		||||
  // Without this the "Zoom to fit to shared model on web" test fails, although manually testing works due
 | 
			
		||||
  // to different timings.
 | 
			
		||||
  const shouldInvokeCreateFile =
 | 
			
		||||
    !hasAskToOpen && searchParams.has(CREATE_FILE_URL_PARAM)
 | 
			
		||||
  const shouldInvokeGenericCmd =
 | 
			
		||||
    !hasAskToOpen &&
 | 
			
		||||
    searchParams.has(CMD_NAME_QUERY_PARAM) &&
 | 
			
		||||
    searchParams.has(CMD_GROUP_QUERY_PARAM)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -245,6 +245,38 @@ ${insertCode}
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
describe('testing getConstraintInfo', () => {
 | 
			
		||||
  describe('when user edits KCL to be invalid', () => {
 | 
			
		||||
    const code = `part001 = startSketchOn(XZ)
 | 
			
		||||
  |> startProfile(at = [0,]) // Missing y coordinate
 | 
			
		||||
  |> line(end = [3, 4])`
 | 
			
		||||
    test.each([
 | 
			
		||||
      [
 | 
			
		||||
        'startProfile',
 | 
			
		||||
        [
 | 
			
		||||
          // No constraints
 | 
			
		||||
        ],
 | 
			
		||||
      ],
 | 
			
		||||
    ])('testing %s when inputs are unconstrained', (functionName, expected) => {
 | 
			
		||||
      const ast = assertParse(code)
 | 
			
		||||
      const match = new RegExp(functionName).exec(code)
 | 
			
		||||
      expect(match).toBeTruthy()
 | 
			
		||||
      if (match === null) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      const start = code.indexOf(match[0])
 | 
			
		||||
      expect(start).toBeGreaterThanOrEqual(0)
 | 
			
		||||
      const sourceRange = topLevelRange(start, start + functionName.length)
 | 
			
		||||
      if (err(ast)) return ast
 | 
			
		||||
      const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
 | 
			
		||||
      const callExp = getNodeFromPath<Node<CallExpressionKw>>(ast, pathToNode, [
 | 
			
		||||
        'CallExpressionKw',
 | 
			
		||||
      ])
 | 
			
		||||
      if (err(callExp)) return callExp
 | 
			
		||||
      const result = getConstraintInfoKw(callExp.node, code, pathToNode)
 | 
			
		||||
      expect(result).toEqual(expected)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  describe('object notation', () => {
 | 
			
		||||
    const code = `part001 = startSketchOn(-XZ)
 | 
			
		||||
  |> startProfile(at = [0,0])
 | 
			
		||||
 | 
			
		||||
@ -1183,7 +1183,7 @@ export const startProfile: SketchLineHelperKw = {
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
    const argIndex = findKwArgAnyIndex([ARG_AT], callExp)
 | 
			
		||||
    if (argIndex === undefined) {
 | 
			
		||||
    if (argIndex === undefined || expr.elements.length < 2) {
 | 
			
		||||
      return []
 | 
			
		||||
    }
 | 
			
		||||
    const pathToXYArray: PathToNode = [
 | 
			
		||||
@ -1471,7 +1471,9 @@ export const circle: SketchLineHelperKw = {
 | 
			
		||||
          key: ARG_RADIUS,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
    ]
 | 
			
		||||
    if (centerInfo.expr.elements.length >= 2) {
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circle',
 | 
			
		||||
        type: 'xAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[0]),
 | 
			
		||||
@ -1489,8 +1491,8 @@ export const circle: SketchLineHelperKw = {
 | 
			
		||||
          index: 0,
 | 
			
		||||
          key: ARG_CIRCLE_CENTER,
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circle',
 | 
			
		||||
        type: 'yAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[1]),
 | 
			
		||||
@ -1508,8 +1510,8 @@ export const circle: SketchLineHelperKw = {
 | 
			
		||||
          index: 1,
 | 
			
		||||
          key: 'center',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    ]
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    return constraints
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
@ -2256,134 +2258,103 @@ export const circleThreePoint: SketchLineHelperKw = {
 | 
			
		||||
    const pathToP3XArg: PathToNode = [...pathToP3ArrayExpression, [0, 'index']]
 | 
			
		||||
    const pathToP3YArg: PathToNode = [...pathToP3ArrayExpression, [1, 'index']]
 | 
			
		||||
 | 
			
		||||
    const constraints: (ConstrainInfo & { filterValue: string })[] = [
 | 
			
		||||
      {
 | 
			
		||||
    const constraints: (ConstrainInfo & { filterValue: string })[] = []
 | 
			
		||||
    if (p1Details.expr.elements.length >= 2) {
 | 
			
		||||
      const p1XArg = p1Details.expr.elements[0]
 | 
			
		||||
      const p1YArg = p1Details.expr.elements[1]
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'xAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[0]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p1Details.expr.elements[0].start,
 | 
			
		||||
          p1Details.expr.elements[0].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p1XArg),
 | 
			
		||||
        sourceRange: topLevelRange(p1XArg.start, p1XArg.end),
 | 
			
		||||
        pathToNode: pathToP1XArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p1Details.expr.elements[0].start,
 | 
			
		||||
          p1Details.expr.elements[0].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p1XArg.start, p1XArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 0,
 | 
			
		||||
          key: 'p1',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p1',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'yAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[1]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p1Details.expr.elements[1].start,
 | 
			
		||||
          p1Details.expr.elements[1].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p1YArg),
 | 
			
		||||
        sourceRange: topLevelRange(p1YArg.start, p1YArg.end),
 | 
			
		||||
        pathToNode: pathToP1YArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p1Details.expr.elements[1].start,
 | 
			
		||||
          p1Details.expr.elements[1].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p1YArg.start, p1YArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 1,
 | 
			
		||||
          key: 'p1',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p1',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (p2Details.expr.elements.length >= 2) {
 | 
			
		||||
      const p2XArg = p2Details.expr.elements[0]
 | 
			
		||||
      const p2YArg = p2Details.expr.elements[1]
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'xAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[0]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p2Details.expr.elements[0].start,
 | 
			
		||||
          p2Details.expr.elements[0].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p2XArg),
 | 
			
		||||
        sourceRange: topLevelRange(p2XArg.start, p2XArg.end),
 | 
			
		||||
        pathToNode: pathToP2XArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p2Details.expr.elements[0].start,
 | 
			
		||||
          p2Details.expr.elements[0].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p2XArg.start, p2XArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 0,
 | 
			
		||||
          key: 'p2',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p2',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'yAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[1]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p2Details.expr.elements[1].start,
 | 
			
		||||
          p2Details.expr.elements[1].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p2YArg),
 | 
			
		||||
        sourceRange: topLevelRange(p2YArg.start, p2YArg.end),
 | 
			
		||||
        pathToNode: pathToP2YArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p2Details.expr.elements[1].start,
 | 
			
		||||
          p2Details.expr.elements[1].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p2YArg.start, p2YArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 1,
 | 
			
		||||
          key: 'p2',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p2',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    if (p3Details.expr.elements.length >= 2) {
 | 
			
		||||
      const p3XArg = p3Details.expr.elements[0]
 | 
			
		||||
      const p3YArg = p3Details.expr.elements[1]
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'xAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[0]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p3Details.expr.elements[0].start,
 | 
			
		||||
          p3Details.expr.elements[0].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p3XArg),
 | 
			
		||||
        sourceRange: topLevelRange(p3XArg.start, p3XArg.end),
 | 
			
		||||
        pathToNode: pathToP3XArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p3Details.expr.elements[0].start,
 | 
			
		||||
          p3Details.expr.elements[0].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p3XArg.start, p3XArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 0,
 | 
			
		||||
          key: 'p3',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p3',
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
      })
 | 
			
		||||
      constraints.push({
 | 
			
		||||
        stdLibFnName: 'circleThreePoint',
 | 
			
		||||
        type: 'yAbsolute',
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[1]),
 | 
			
		||||
        sourceRange: [
 | 
			
		||||
          p3Details.expr.elements[1].start,
 | 
			
		||||
          p3Details.expr.elements[1].end,
 | 
			
		||||
          0,
 | 
			
		||||
        ],
 | 
			
		||||
        isConstrained: isNotLiteralArrayOrStatic(p3YArg),
 | 
			
		||||
        sourceRange: topLevelRange(p3YArg.start, p3YArg.end),
 | 
			
		||||
        pathToNode: pathToP3YArg,
 | 
			
		||||
        value: code.slice(
 | 
			
		||||
          p3Details.expr.elements[1].start,
 | 
			
		||||
          p3Details.expr.elements[1].end
 | 
			
		||||
        ),
 | 
			
		||||
        value: code.slice(p3YArg.start, p3YArg.end),
 | 
			
		||||
        argPosition: {
 | 
			
		||||
          type: 'labeledArgArrayItem',
 | 
			
		||||
          index: 1,
 | 
			
		||||
          key: 'p3',
 | 
			
		||||
        },
 | 
			
		||||
        filterValue: 'p3',
 | 
			
		||||
      },
 | 
			
		||||
    ]
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
    const finalConstraints: ConstrainInfo[] = []
 | 
			
		||||
    constraints.forEach((constraint) => {
 | 
			
		||||
      if (!filterValue) {
 | 
			
		||||
 | 
			
		||||
@ -389,7 +389,20 @@ export function sketchFromKclValue(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const errFromErrWithOutputs = (e: any): KCLError => {
 | 
			
		||||
  const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
 | 
			
		||||
  // `e` is any, so let's figure out something useful to do with it.
 | 
			
		||||
  const parsed: KclErrorWithOutputs = (() => {
 | 
			
		||||
    // No need to parse, it's already an object.
 | 
			
		||||
    if (typeof e === 'object') {
 | 
			
		||||
      return e
 | 
			
		||||
    }
 | 
			
		||||
    // It's a string, so parse it.
 | 
			
		||||
    if (typeof e === 'string') {
 | 
			
		||||
      return JSON.parse(e)
 | 
			
		||||
    }
 | 
			
		||||
    // It can be converted to a string, then parsed.
 | 
			
		||||
    return JSON.parse(e.toString())
 | 
			
		||||
  })()
 | 
			
		||||
 | 
			
		||||
  return new KCLError(
 | 
			
		||||
    parsed.error.kind,
 | 
			
		||||
    parsed.error.details.msg,
 | 
			
		||||
 | 
			
		||||
@ -218,3 +218,6 @@ export const POOL_QUERY_PARAM = 'pool'
 | 
			
		||||
 * @deprecated: supporting old share links with this. For new command URLs, use "cmd"
 | 
			
		||||
 */
 | 
			
		||||
export const CREATE_FILE_URL_PARAM = 'create-file'
 | 
			
		||||
/** A query parameter to skip the sign-on view if unnecessary. */
 | 
			
		||||
export const IMMEDIATE_SIGN_IN_IF_NECESSARY_QUERY_PARAM =
 | 
			
		||||
  'immediate-sign-in-if-necessary'
 | 
			
		||||
 | 
			
		||||
@ -10,12 +10,11 @@ import { VITE_KC_API_BASE_URL, VITE_KC_SITE_BASE_URL } from '@src/env'
 | 
			
		||||
import { APP_NAME } from '@src/lib/constants'
 | 
			
		||||
import { isDesktop } from '@src/lib/isDesktop'
 | 
			
		||||
import { openExternalBrowserIfDesktop } from '@src/lib/openWindow'
 | 
			
		||||
import { PATHS } from '@src/lib/paths'
 | 
			
		||||
import { Themes, getSystemTheme } from '@src/lib/theme'
 | 
			
		||||
import { reportRejection } from '@src/lib/trap'
 | 
			
		||||
import { toSync } from '@src/lib/utils'
 | 
			
		||||
import { authActor, useSettings } from '@src/lib/singletons'
 | 
			
		||||
import { APP_VERSION } from '@src/routes/utils'
 | 
			
		||||
import { APP_VERSION, generateSignInUrl } from '@src/routes/utils'
 | 
			
		||||
 | 
			
		||||
const subtleBorder =
 | 
			
		||||
  'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
 | 
			
		||||
@ -36,11 +35,7 @@ const SignIn = () => {
 | 
			
		||||
  const {
 | 
			
		||||
    app: { theme },
 | 
			
		||||
  } = useSettings()
 | 
			
		||||
  const signInUrl = `${VITE_KC_SITE_BASE_URL}${
 | 
			
		||||
    PATHS.SIGN_IN
 | 
			
		||||
  }?callbackUrl=${encodeURIComponent(
 | 
			
		||||
    typeof window !== 'undefined' && window.location.href.replace('signin', '')
 | 
			
		||||
  )}`
 | 
			
		||||
  const signInUrl = generateSignInUrl()
 | 
			
		||||
  const kclSampleUrl = `${VITE_KC_SITE_BASE_URL}/docs/kcl-samples/car-wheel-assembly`
 | 
			
		||||
 | 
			
		||||
  const getThemeText = useCallback(
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import { NODE_ENV } from '@src/env'
 | 
			
		||||
import { NODE_ENV, VITE_KC_SITE_BASE_URL } from '@src/env'
 | 
			
		||||
import { isDesktop } from '@src/lib/isDesktop'
 | 
			
		||||
import { IS_PLAYWRIGHT_KEY } from '@src/lib/constants'
 | 
			
		||||
import { PATHS } from '@src/lib/paths'
 | 
			
		||||
 | 
			
		||||
const isTestEnv = window?.localStorage.getItem(IS_PLAYWRIGHT_KEY) === 'true'
 | 
			
		||||
 | 
			
		||||
@ -27,3 +28,11 @@ export function getReleaseUrl(version: string = APP_VERSION) {
 | 
			
		||||
 | 
			
		||||
  return `https://github.com/KittyCAD/modeling-app/releases/tag/v${version}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function generateSignInUrl() {
 | 
			
		||||
  return `${VITE_KC_SITE_BASE_URL}${
 | 
			
		||||
    PATHS.SIGN_IN
 | 
			
		||||
  }?callbackUrl=${encodeURIComponent(
 | 
			
		||||
    typeof window !== 'undefined' && window.location.href.replace('signin', '')
 | 
			
		||||
  )}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user