Merge branch 'main' into achalmers/kw-fn-sketches
@ -18,6 +18,7 @@ export class ToolbarFixture {
 | 
			
		||||
  filletButton!: Locator
 | 
			
		||||
  chamferButton!: Locator
 | 
			
		||||
  shellButton!: Locator
 | 
			
		||||
  revolveButton!: Locator
 | 
			
		||||
  offsetPlaneButton!: Locator
 | 
			
		||||
  startSketchBtn!: Locator
 | 
			
		||||
  lineBtn!: Locator
 | 
			
		||||
@ -47,6 +48,7 @@ export class ToolbarFixture {
 | 
			
		||||
    this.filletButton = page.getByTestId('fillet3d')
 | 
			
		||||
    this.chamferButton = page.getByTestId('chamfer3d')
 | 
			
		||||
    this.shellButton = page.getByTestId('shell')
 | 
			
		||||
    this.revolveButton = page.getByTestId('revolve')
 | 
			
		||||
    this.offsetPlaneButton = page.getByTestId('plane-offset')
 | 
			
		||||
    this.startSketchBtn = page.getByTestId('sketch')
 | 
			
		||||
    this.lineBtn = page.getByTestId('line')
 | 
			
		||||
 | 
			
		||||
@ -1078,7 +1078,7 @@ sketch002 = startSketchOn('XZ')
 | 
			
		||||
    await page.waitForTimeout(500)
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Unable to sweep with the provided selection')
 | 
			
		||||
      page.getByText('Unable to sweep with the current selection. Reason:')
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -1183,7 +1183,7 @@ extrude001 = extrude(-12, sketch001)
 | 
			
		||||
      currentArgKey: 'radius',
 | 
			
		||||
      currentArgValue: '5',
 | 
			
		||||
      headerArguments: {
 | 
			
		||||
        Selection: '1 face',
 | 
			
		||||
        Selection: '1 segment',
 | 
			
		||||
        Radius: '',
 | 
			
		||||
      },
 | 
			
		||||
      stage: 'arguments',
 | 
			
		||||
@ -1192,7 +1192,7 @@ extrude001 = extrude(-12, sketch001)
 | 
			
		||||
    await cmdBar.expectState({
 | 
			
		||||
      commandName: 'Fillet',
 | 
			
		||||
      headerArguments: {
 | 
			
		||||
        Selection: '1 face',
 | 
			
		||||
        Selection: '1 segment',
 | 
			
		||||
        Radius: '5',
 | 
			
		||||
      },
 | 
			
		||||
      stage: 'review',
 | 
			
		||||
@ -1398,7 +1398,7 @@ extrude001 = extrude(sketch001, length = -12)
 | 
			
		||||
      currentArgKey: 'length',
 | 
			
		||||
      currentArgValue: '5',
 | 
			
		||||
      headerArguments: {
 | 
			
		||||
        Selection: '1 face',
 | 
			
		||||
        Selection: '1 segment',
 | 
			
		||||
        Length: '',
 | 
			
		||||
      },
 | 
			
		||||
      stage: 'arguments',
 | 
			
		||||
@ -1407,7 +1407,7 @@ extrude001 = extrude(sketch001, length = -12)
 | 
			
		||||
    await cmdBar.expectState({
 | 
			
		||||
      commandName: 'Chamfer',
 | 
			
		||||
      headerArguments: {
 | 
			
		||||
        Selection: '1 face',
 | 
			
		||||
        Selection: '1 segment',
 | 
			
		||||
        Length: '5',
 | 
			
		||||
      },
 | 
			
		||||
      stage: 'review',
 | 
			
		||||
@ -1846,8 +1846,176 @@ sweep001 = sweep({ path = sketch002 }, sketch001)
 | 
			
		||||
    await page.waitForTimeout(500)
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Unable to shell with the provided selection')
 | 
			
		||||
      page.getByText('Unable to shell with the current selection. Reason:')
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
    await page.waitForTimeout(1000)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.describe('Revolve point and click workflows', () => {
 | 
			
		||||
  test('Base case workflow, auto spam continue in command bar', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `
 | 
			
		||||
sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([-100.0, 100.0], %)
 | 
			
		||||
|> angledLine([0, 200.0], %, $rectangleSegmentA001)
 | 
			
		||||
|> angledLine([segAng(rectangleSegmentA001) - 90, 200], %, $rectangleSegmentB001)
 | 
			
		||||
|> angledLine([
 | 
			
		||||
segAng(rectangleSegmentA001),
 | 
			
		||||
-segLen(rectangleSegmentA001)
 | 
			
		||||
], %, $rectangleSegmentC001)
 | 
			
		||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
|> close(%)
 | 
			
		||||
extrude001 = extrude(200, sketch001)
 | 
			
		||||
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
 | 
			
		||||
|> startProfileAt([-66.77, 84.81], %)
 | 
			
		||||
|> angledLine([180, 27.08], %, $rectangleSegmentA002)
 | 
			
		||||
|> angledLine([
 | 
			
		||||
segAng(rectangleSegmentA002) - 90,
 | 
			
		||||
27.8
 | 
			
		||||
], %, $rectangleSegmentB002)
 | 
			
		||||
|> angledLine([
 | 
			
		||||
segAng(rectangleSegmentA002),
 | 
			
		||||
-segLen(rectangleSegmentA002)
 | 
			
		||||
], %, $rectangleSegmentC002)
 | 
			
		||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
|> close(%)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.waitForExecutionDone()
 | 
			
		||||
 | 
			
		||||
    // select line of code
 | 
			
		||||
    const codeToSelecton = `segAng(rectangleSegmentA002) - 90,`
 | 
			
		||||
    // revolve
 | 
			
		||||
    await page.getByText(codeToSelecton).click()
 | 
			
		||||
    await toolbar.revolveButton.click()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
 | 
			
		||||
    const newCodeToFind = `revolve001 = revolve({ angle = 360, axis = 'X' }, sketch002)`
 | 
			
		||||
    expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  test('revolve surface around edge from an extruded solid2d', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `
 | 
			
		||||
sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([-102.57, 101.72], %)
 | 
			
		||||
|> angledLine([0, 202.6], %, $rectangleSegmentA001)
 | 
			
		||||
|> angledLine([
 | 
			
		||||
segAng(rectangleSegmentA001) - 90,
 | 
			
		||||
202.6
 | 
			
		||||
], %, $rectangleSegmentB001)
 | 
			
		||||
|> angledLine([
 | 
			
		||||
segAng(rectangleSegmentA001),
 | 
			
		||||
-segLen(rectangleSegmentA001)
 | 
			
		||||
], %, $rectangleSegmentC001)
 | 
			
		||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
|> close(%)
 | 
			
		||||
extrude001 = extrude(50, sketch001)
 | 
			
		||||
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
 | 
			
		||||
|> circle({
 | 
			
		||||
center = [-11.34, 10.0],
 | 
			
		||||
radius = 8.69
 | 
			
		||||
}, %)
 | 
			
		||||
`
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.waitForExecutionDone()
 | 
			
		||||
 | 
			
		||||
    // select line of code
 | 
			
		||||
    const codeToSelecton = `center = [-11.34, 10.0]`
 | 
			
		||||
    // revolve
 | 
			
		||||
    await page.getByText(codeToSelecton).click()
 | 
			
		||||
    await toolbar.revolveButton.click()
 | 
			
		||||
    await page.getByText('Edge', { exact: true }).click()
 | 
			
		||||
    const lineCodeToSelection = `|> angledLine([0, 202.6], %, $rectangleSegmentA001)`
 | 
			
		||||
    await page.getByText(lineCodeToSelection).click()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
 | 
			
		||||
    const newCodeToFind = `revolve001 = revolve({angle = 360, axis = getOppositeEdge(rectangleSegmentA001)}, sketch002) `
 | 
			
		||||
    expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
  test('revolve sketch circle around line segment from startProfileAt sketch', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `
 | 
			
		||||
    sketch002 = startSketchOn('XY')
 | 
			
		||||
      |> startProfileAt([-2.02, 1.79], %)
 | 
			
		||||
      |> xLine(2.6, %)
 | 
			
		||||
    sketch001 = startSketchOn('-XY')
 | 
			
		||||
      |> startProfileAt([-0.48, 1.25], %)
 | 
			
		||||
      |> angledLine([0, 2.38], %, $rectangleSegmentA001)
 | 
			
		||||
      |> angledLine([segAng(rectangleSegmentA001) - 90, 2.4], %, $rectangleSegmentB001)
 | 
			
		||||
      |> angledLine([
 | 
			
		||||
        segAng(rectangleSegmentA001),
 | 
			
		||||
          -segLen(rectangleSegmentA001)
 | 
			
		||||
      ], %, $rectangleSegmentC001)
 | 
			
		||||
      |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
      |> close(%)
 | 
			
		||||
    extrude001 = extrude(5, sketch001)
 | 
			
		||||
    sketch003 = startSketchOn(extrude001, 'START')
 | 
			
		||||
      |> circle({
 | 
			
		||||
        center = [-0.69, 0.56],
 | 
			
		||||
        radius = 0.28
 | 
			
		||||
      }, %)
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.waitForExecutionDone()
 | 
			
		||||
 | 
			
		||||
    // select line of code
 | 
			
		||||
    const codeToSelecton = `center = [-0.69, 0.56]`
 | 
			
		||||
    // revolve
 | 
			
		||||
    await page.getByText(codeToSelecton).click()
 | 
			
		||||
    await toolbar.revolveButton.click()
 | 
			
		||||
    await page.getByText('Edge', { exact: true }).click()
 | 
			
		||||
    const lineCodeToSelection = `|> xLine(2.6, %)`
 | 
			
		||||
    await page.getByText(lineCodeToSelection).click()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
    await cmdBar.progressCmdBar()
 | 
			
		||||
 | 
			
		||||
    const newCodeToFind = `revolve001 = revolve({ angle = 360, axis = seg01 }, sketch003)`
 | 
			
		||||
    expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -572,7 +572,7 @@ test(
 | 
			
		||||
      fs.utimesSync(`${dir}/lego/main.kcl`, _1995, _1995)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 600 })
 | 
			
		||||
 | 
			
		||||
    page.on('console', console.log)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -886,7 +886,7 @@ test.describe('Sketch tests', () => {
 | 
			
		||||
    // sketch selection should already have been made. "Selection: 1 face" only show up when the selection has been made already
 | 
			
		||||
    // otherwise the cmdbar would be waiting for a selection.
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByRole('button', { name: 'selection : 1 face', exact: false })
 | 
			
		||||
      page.getByRole('button', { name: 'selection : 1 segment', exact: false })
 | 
			
		||||
    ).toBeVisible({
 | 
			
		||||
      timeout: 10_000,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB  | 
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB  | 
| 
		 Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 74 KiB  | 
| 
		 Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB  | 
| 
		 Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB  | 
| 
		 Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB  | 
@ -69,7 +69,6 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
      await confirmButton.click()
 | 
			
		||||
 | 
			
		||||
      await editor.expectEditor.toContain('// ' + newSample.title)
 | 
			
		||||
      await expect(unitsToast('in')).toBeVisible()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -158,7 +157,6 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
        await editor.expectEditor.toContain('// ' + sampleOne.title)
 | 
			
		||||
        await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
 | 
			
		||||
        await expect(projectMenuButton).toContainText(sampleOne.file)
 | 
			
		||||
        await expect(unitsToast('in')).toBeVisible()
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step(`Now overwrite the current file`, async () => {
 | 
			
		||||
@ -188,7 +186,6 @@ test.describe('Testing in-app sample loading', () => {
 | 
			
		||||
        await expect(newlyCreatedFile(sampleOne.file)).toBeVisible()
 | 
			
		||||
        await expect(newlyCreatedFile(sampleTwo.file)).not.toBeVisible()
 | 
			
		||||
        await expect(projectMenuButton).toContainText(sampleOne.file)
 | 
			
		||||
        await expect(unitsToast('mm')).toBeVisible()
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										39
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						@ -1,4 +1,4 @@
 | 
			
		||||
import { useEffect, useMemo, useRef } from 'react'
 | 
			
		||||
import { useEffect, useMemo, useRef, useState } from 'react'
 | 
			
		||||
import { useHotKeyListener } from './hooks/useHotKeyListener'
 | 
			
		||||
import { Stream } from './components/Stream'
 | 
			
		||||
import { AppHeader } from './components/AppHeader'
 | 
			
		||||
@ -24,7 +24,12 @@ import { UnitsMenu } from 'components/UnitsMenu'
 | 
			
		||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
 | 
			
		||||
import { useCreateFileLinkQuery } from 'hooks/useCreateFileLinkQueryWatcher'
 | 
			
		||||
import { maybeWriteToDisk } from 'lib/telemetry'
 | 
			
		||||
import { takeScreenshotOfVideoStreamCanvas } from 'lib/screenshot'
 | 
			
		||||
import { writeProjectThumbnailFile } from 'lib/desktop'
 | 
			
		||||
import { useRouteLoaderData } from 'react-router-dom'
 | 
			
		||||
import { useEngineCommands } from 'components/EngineCommands'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
maybeWriteToDisk()
 | 
			
		||||
  .then(() => {})
 | 
			
		||||
  .catch(() => {})
 | 
			
		||||
@ -54,14 +59,20 @@ export function App() {
 | 
			
		||||
 | 
			
		||||
  const projectName = project?.name || null
 | 
			
		||||
  const projectPath = project?.path || null
 | 
			
		||||
 | 
			
		||||
  const [commands] = useEngineCommands()
 | 
			
		||||
  const [capturedCanvas, setCapturedCanvas] = useState(false)
 | 
			
		||||
  const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
 | 
			
		||||
  const lastCommandType = commands[commands.length - 1]?.type
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    onProjectOpen({ name: projectName, path: projectPath }, file || null)
 | 
			
		||||
  }, [projectName, projectPath])
 | 
			
		||||
 | 
			
		||||
  useHotKeyListener()
 | 
			
		||||
 | 
			
		||||
  const { auth, settings } = useSettingsAuthContext()
 | 
			
		||||
  const token = auth?.context?.token
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
 | 
			
		||||
  const coreDumpManager = useMemo(
 | 
			
		||||
    () => new CoreDumpManager(engineCommandManager, codeManager, token),
 | 
			
		||||
@ -91,6 +102,28 @@ export function App() {
 | 
			
		||||
 | 
			
		||||
  useEngineConnectionSubscriptions()
 | 
			
		||||
 | 
			
		||||
  // Generate thumbnail.png when loading the app
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!capturedCanvas && lastCommandType === 'execution-done') {
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        const projectDirectoryWithoutEndingSlash = loaderData?.project?.path
 | 
			
		||||
        if (!projectDirectoryWithoutEndingSlash) {
 | 
			
		||||
          return
 | 
			
		||||
        }
 | 
			
		||||
        const dataUrl: string = takeScreenshotOfVideoStreamCanvas()
 | 
			
		||||
        // zoom to fit command does not wait, wait 500ms to see if zoom to fit finishes
 | 
			
		||||
        writeProjectThumbnailFile(dataUrl, projectDirectoryWithoutEndingSlash)
 | 
			
		||||
          .then(() => {})
 | 
			
		||||
          .catch((e) => {
 | 
			
		||||
            console.error(
 | 
			
		||||
              `Failed to generate thumbnail for ${projectDirectoryWithoutEndingSlash}`
 | 
			
		||||
            )
 | 
			
		||||
            console.error(e)
 | 
			
		||||
          })
 | 
			
		||||
      }, 500)
 | 
			
		||||
    }
 | 
			
		||||
  }, [lastCommandType])
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="relative h-full flex flex-col" ref={ref}>
 | 
			
		||||
      <AppHeader
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,10 @@
 | 
			
		||||
import { useAuthState } from 'machines/appMachine'
 | 
			
		||||
import Loading from './components/Loading'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
 | 
			
		||||
// Wrapper around protected routes, used in src/Router.tsx
 | 
			
		||||
export const Auth = ({ children }: React.PropsWithChildren) => {
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
 | 
			
		||||
  const authState = useAuthState()
 | 
			
		||||
  const isLoggingIn = authState.matches('checkIfLoggedIn')
 | 
			
		||||
 | 
			
		||||
  return isLoggingIn ? (
 | 
			
		||||
    <Loading>
 | 
			
		||||
 | 
			
		||||
@ -37,7 +37,6 @@ import { KclContextProvider } from 'lang/KclProvider'
 | 
			
		||||
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
 | 
			
		||||
import { CoreDumpManager } from 'lib/coredump'
 | 
			
		||||
import { codeManager, engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
 | 
			
		||||
import toast from 'react-hot-toast'
 | 
			
		||||
import { coreDump } from 'lang/wasm'
 | 
			
		||||
@ -47,6 +46,7 @@ import { reportRejection } from 'lib/trap'
 | 
			
		||||
import { RouteProvider } from 'components/RouteProvider'
 | 
			
		||||
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
 | 
			
		||||
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
 | 
			
		||||
 | 
			
		||||
@ -203,8 +203,7 @@ export const Router = () => {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function CoreDump() {
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const token = auth?.context?.token
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const coreDumpManager = useMemo(
 | 
			
		||||
    () => new CoreDumpManager(engineCommandManager, codeManager, token),
 | 
			
		||||
    []
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,11 @@ import { Toolbar } from '../Toolbar'
 | 
			
		||||
import UserSidebarMenu from 'components/UserSidebarMenu'
 | 
			
		||||
import { type IndexLoaderData } from 'lib/types'
 | 
			
		||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import styles from './AppHeader.module.css'
 | 
			
		||||
import { RefreshButton } from 'components/RefreshButton'
 | 
			
		||||
import { CommandBarOpenButton } from './CommandBarOpenButton'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { useUser } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
interface AppHeaderProps extends React.PropsWithChildren {
 | 
			
		||||
  showToolbar?: boolean
 | 
			
		||||
@ -24,8 +24,7 @@ export const AppHeader = ({
 | 
			
		||||
  style,
 | 
			
		||||
  enableMenu = false,
 | 
			
		||||
}: AppHeaderProps) => {
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const user = auth?.context?.user
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <header
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,7 @@ import {
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { markOnce } from 'lib/performance'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -47,7 +48,8 @@ export const FileMachineProvider = ({
 | 
			
		||||
  children: React.ReactNode
 | 
			
		||||
}) => {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { settings, auth } = useSettingsAuthContext()
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
 | 
			
		||||
  const { project, file } = projectData
 | 
			
		||||
  const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
 | 
			
		||||
@ -297,7 +299,7 @@ export const FileMachineProvider = ({
 | 
			
		||||
  const kclCommandMemo = useMemo(
 | 
			
		||||
    () =>
 | 
			
		||||
      kclCommands({
 | 
			
		||||
        authToken: auth?.context?.token ?? '',
 | 
			
		||||
        authToken: token ?? '',
 | 
			
		||||
        projectData,
 | 
			
		||||
        settings: {
 | 
			
		||||
          defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import { PROJECT_ENTRYPOINT } from 'lib/constants'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { codeManager } from 'lib/singletons'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
function getWorkspaceFolders(): LSP.WorkspaceFolder[] {
 | 
			
		||||
  return []
 | 
			
		||||
@ -69,8 +70,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
 | 
			
		||||
  const [isKclLspReady, setIsKclLspReady] = useState(false)
 | 
			
		||||
  const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
 | 
			
		||||
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const token = auth?.context.token
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
 | 
			
		||||
  // So this is a bit weird, we need to initialize the lsp server and client.
 | 
			
		||||
 | 
			
		||||
@ -1,10 +1,8 @@
 | 
			
		||||
import { useEngineCommands } from './EngineCommands'
 | 
			
		||||
import { Spinner } from './Spinner'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
 | 
			
		||||
export const ModelStateIndicator = () => {
 | 
			
		||||
  const [commands] = useEngineCommands()
 | 
			
		||||
 | 
			
		||||
  const lastCommandType = commands[commands.length - 1]?.type
 | 
			
		||||
 | 
			
		||||
  let className = 'w-6 h-6 '
 | 
			
		||||
 | 
			
		||||
@ -89,6 +89,7 @@ import { Node } from 'wasm-lib/kcl/bindings/Node'
 | 
			
		||||
import { promptToEditFlow } from 'lib/promptToEdit'
 | 
			
		||||
import { kclEditorActor } from 'machines/kclEditorMachine'
 | 
			
		||||
import { commandBarActor } from 'machines/commandBarMachine'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
  state: StateFrom<T>
 | 
			
		||||
@ -110,7 +111,6 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
  children: React.ReactNode
 | 
			
		||||
}) => {
 | 
			
		||||
  const {
 | 
			
		||||
    auth,
 | 
			
		||||
    settings: {
 | 
			
		||||
      context: {
 | 
			
		||||
        app: { theme, enableSSAO, allowOrbitInSketchMode },
 | 
			
		||||
@ -127,7 +127,7 @@ export const ModelingMachineProvider = ({
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const { context, send: fileMachineSend } = useFileContext()
 | 
			
		||||
  const { file } = useLoaderData() as IndexLoaderData
 | 
			
		||||
  const token = auth?.context?.token
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const streamRef = useRef<HTMLDivElement>(null)
 | 
			
		||||
  const persistedContext = useMemo(() => getPersistedContext(), [])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,7 @@ import { FormEvent, useEffect, useRef, useState } from 'react'
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { Link } from 'react-router-dom'
 | 
			
		||||
import { ActionButton } from '../ActionButton'
 | 
			
		||||
import { FILE_EXT } from 'lib/constants'
 | 
			
		||||
import { FILE_EXT, PROJECT_IMAGE_NAME } from 'lib/constants'
 | 
			
		||||
import { useHotkeys } from 'react-hotkeys-hook'
 | 
			
		||||
import Tooltip from '../Tooltip'
 | 
			
		||||
import { DeleteConfirmationDialog } from './DeleteProjectDialog'
 | 
			
		||||
@ -29,7 +29,7 @@ function ProjectCard({
 | 
			
		||||
  const [isConfirmingDelete, setIsConfirmingDelete] = useState(false)
 | 
			
		||||
  const [numberOfFiles, setNumberOfFiles] = useState(1)
 | 
			
		||||
  const [numberOfFolders, setNumberOfFolders] = useState(0)
 | 
			
		||||
  // const [imageUrl, setImageUrl] = useState('')
 | 
			
		||||
  const [imageUrl, setImageUrl] = useState('')
 | 
			
		||||
 | 
			
		||||
  let inputRef = useRef<HTMLInputElement>(null)
 | 
			
		||||
 | 
			
		||||
@ -53,18 +53,21 @@ function ProjectCard({
 | 
			
		||||
      setNumberOfFolders(project.directory_count)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // async function setupImageUrl() {
 | 
			
		||||
    //   const projectImagePath = await join(project.file.path, PROJECT_IMAGE_NAME)
 | 
			
		||||
    //   if (await exists(projectImagePath)) {
 | 
			
		||||
    //     const imageData = await readFile(projectImagePath)
 | 
			
		||||
    //     const blob = new Blob([imageData], { type: 'image/jpg' })
 | 
			
		||||
    //     const imageUrl = URL.createObjectURL(blob)
 | 
			
		||||
    //     setImageUrl(imageUrl)
 | 
			
		||||
    //   }
 | 
			
		||||
    // }
 | 
			
		||||
    async function setupImageUrl() {
 | 
			
		||||
      const projectImagePath = window.electron.path.join(
 | 
			
		||||
        project.path,
 | 
			
		||||
        PROJECT_IMAGE_NAME
 | 
			
		||||
      )
 | 
			
		||||
      if (await window.electron.exists(projectImagePath)) {
 | 
			
		||||
        const imageData = await window.electron.readFile(projectImagePath)
 | 
			
		||||
        const blob = new Blob([imageData], { type: 'image/png' })
 | 
			
		||||
        const imageUrl = URL.createObjectURL(blob)
 | 
			
		||||
        setImageUrl(imageUrl)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void getNumberOfFiles()
 | 
			
		||||
    // void setupImageUrl()
 | 
			
		||||
    void setupImageUrl()
 | 
			
		||||
  }, [project.kcl_file_count, project.directory_count])
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
@ -84,7 +87,7 @@ function ProjectCard({
 | 
			
		||||
        to={`${PATHS.FILE}/${encodeURIComponent(project.default_file)}`}
 | 
			
		||||
        className="flex flex-col flex-1 !no-underline !text-chalkboard-110 dark:!text-chalkboard-10 group-hover:!hue-rotate-0 min-h-[5em] divide-y divide-primary/40 dark:divide-chalkboard-80 group-hover:!divide-primary"
 | 
			
		||||
      >
 | 
			
		||||
        {/* <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
 | 
			
		||||
        <div className="h-36 relative overflow-hidden bg-gradient-to-b from-transparent to-primary/10 rounded-t-sm">
 | 
			
		||||
          {imageUrl && (
 | 
			
		||||
            <img
 | 
			
		||||
              src={imageUrl}
 | 
			
		||||
@ -92,7 +95,7 @@ function ProjectCard({
 | 
			
		||||
              className="h-full w-full transition-transform group-hover:scale-105 object-cover"
 | 
			
		||||
            />
 | 
			
		||||
          )}
 | 
			
		||||
        </div> */}
 | 
			
		||||
        </div>
 | 
			
		||||
        <div className="pb-2 flex flex-col flex-grow flex-auto gap-2 rounded-b-sm">
 | 
			
		||||
          {isEditing ? (
 | 
			
		||||
            <ProjectCardRenameForm
 | 
			
		||||
 | 
			
		||||
@ -20,6 +20,7 @@ import { useSelector } from '@xstate/react'
 | 
			
		||||
import { copyFileShareLink } from 'lib/links'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { DEV } from 'env'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
const ProjectSidebarMenu = ({
 | 
			
		||||
  project,
 | 
			
		||||
@ -103,7 +104,8 @@ function ProjectMenuPopover({
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const filePath = useAbsoluteFilePath()
 | 
			
		||||
  const { settings, auth } = useSettingsAuthContext()
 | 
			
		||||
  const { settings } = useSettingsAuthContext()
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const machineManager = useContext(MachineManagerContext)
 | 
			
		||||
  const commands = useSelector(commandBarActor, commandsSelector)
 | 
			
		||||
 | 
			
		||||
@ -194,7 +196,7 @@ function ProjectMenuPopover({
 | 
			
		||||
          disabled: !DEV,
 | 
			
		||||
          onClick: async () => {
 | 
			
		||||
            await copyFileShareLink({
 | 
			
		||||
              token: auth?.context.token || '',
 | 
			
		||||
              token: token ?? '',
 | 
			
		||||
              code: codeManager.code,
 | 
			
		||||
              name: project?.name || '',
 | 
			
		||||
              units: settings.context.modeling.defaultUnit.current,
 | 
			
		||||
 | 
			
		||||
@ -8,10 +8,10 @@ import Tooltip from './Tooltip'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
import { toSync } from 'lib/utils'
 | 
			
		||||
import { useToken } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
export const RefreshButton = ({ children }: React.PropsWithChildren) => {
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const token = auth?.context?.token
 | 
			
		||||
  const token = useToken()
 | 
			
		||||
  const coreDumpManager = useMemo(
 | 
			
		||||
    () => new CoreDumpManager(engineCommandManager, codeManager, token),
 | 
			
		||||
    []
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,12 @@ import { useEffect, useState, createContext, ReactNode } from 'react'
 | 
			
		||||
import { useNavigation, useLocation } from 'react-router-dom'
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { markOnce } from 'lib/performance'
 | 
			
		||||
import { useAuthNavigation } from 'hooks/useAuthNavigation'
 | 
			
		||||
 | 
			
		||||
export const RouteProviderContext = createContext({})
 | 
			
		||||
 | 
			
		||||
export function RouteProvider({ children }: { children: ReactNode }) {
 | 
			
		||||
  useAuthNavigation()
 | 
			
		||||
  const [first, setFirstState] = useState(true)
 | 
			
		||||
  const navigation = useNavigation()
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
 | 
			
		||||
@ -2,10 +2,7 @@ import { trap } from 'lib/trap'
 | 
			
		||||
import { useMachine, useSelector } from '@xstate/react'
 | 
			
		||||
import { useNavigate, useRouteLoaderData, useLocation } from 'react-router-dom'
 | 
			
		||||
import { PATHS, BROWSER_PATH } from 'lib/paths'
 | 
			
		||||
import { authMachine, TOKEN_PERSIST_KEY } from '../machines/authMachine'
 | 
			
		||||
import withBaseUrl from '../lib/withBaseURL'
 | 
			
		||||
import React, { createContext, useEffect, useState } from 'react'
 | 
			
		||||
import useStateMachineCommands from '../hooks/useStateMachineCommands'
 | 
			
		||||
import { settingsMachine } from 'machines/settingsMachine'
 | 
			
		||||
import { toast } from 'react-hot-toast'
 | 
			
		||||
import {
 | 
			
		||||
@ -16,7 +13,6 @@ import {
 | 
			
		||||
} from 'lib/theme'
 | 
			
		||||
import decamelize from 'decamelize'
 | 
			
		||||
import { Actor, AnyStateMachine, ContextFrom, Prop, StateFrom } from 'xstate'
 | 
			
		||||
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
 | 
			
		||||
import {
 | 
			
		||||
  kclManager,
 | 
			
		||||
  sceneInfra,
 | 
			
		||||
@ -50,7 +46,6 @@ type MachineContext<T extends AnyStateMachine> = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type SettingsAuthContextType = {
 | 
			
		||||
  auth: MachineContext<typeof authMachine>
 | 
			
		||||
  settings: MachineContext<typeof settingsMachine>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -370,40 +365,9 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
    )
 | 
			
		||||
  }, [settingsState.context.textEditor.blinkingCursor.current])
 | 
			
		||||
 | 
			
		||||
  // Auth machine setup
 | 
			
		||||
  const [authState, authSend, authActor] = useMachine(
 | 
			
		||||
    authMachine.provide({
 | 
			
		||||
      actions: {
 | 
			
		||||
        goToSignInPage: () => {
 | 
			
		||||
          navigate(PATHS.SIGN_IN)
 | 
			
		||||
          // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
          logout()
 | 
			
		||||
        },
 | 
			
		||||
        goToIndexPage: () => {
 | 
			
		||||
          if (location.pathname.includes(PATHS.SIGN_IN)) {
 | 
			
		||||
            navigate(PATHS.INDEX)
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  useStateMachineCommands({
 | 
			
		||||
    machineId: 'auth',
 | 
			
		||||
    state: authState,
 | 
			
		||||
    send: authSend,
 | 
			
		||||
    commandBarConfig: authCommandBarConfig,
 | 
			
		||||
    actor: authActor,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <SettingsAuthContext.Provider
 | 
			
		||||
      value={{
 | 
			
		||||
        auth: {
 | 
			
		||||
          state: authState,
 | 
			
		||||
          context: authState.context,
 | 
			
		||||
          send: authSend,
 | 
			
		||||
        },
 | 
			
		||||
        settings: {
 | 
			
		||||
          state: settingsState,
 | 
			
		||||
          context: settingsState.context,
 | 
			
		||||
@ -417,12 +381,3 @@ export const SettingsAuthProviderBase = ({
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default SettingsAuthProvider
 | 
			
		||||
 | 
			
		||||
export async function logout() {
 | 
			
		||||
  localStorage.removeItem(TOKEN_PERSIST_KEY)
 | 
			
		||||
  if (isDesktop()) return Promise.resolve(null)
 | 
			
		||||
  return fetch(withBaseUrl('/logout'), {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    credentials: 'include',
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,12 +4,12 @@ import { useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
import { Fragment, useMemo, useState } from 'react'
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { Models } from '@kittycad/lib'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
 | 
			
		||||
import Tooltip from './Tooltip'
 | 
			
		||||
import usePlatform from 'hooks/usePlatform'
 | 
			
		||||
import { isDesktop } from 'lib/isDesktop'
 | 
			
		||||
import { CustomIcon } from './CustomIcon'
 | 
			
		||||
import { authActor } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
type User = Models['User_type']
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
 | 
			
		||||
  const displayedName = getDisplayName(user)
 | 
			
		||||
  const [imageLoadFailed, setImageLoadFailed] = useState(false)
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const send = useSettingsAuthContext()?.auth?.send
 | 
			
		||||
  const send = authActor.send
 | 
			
		||||
 | 
			
		||||
  // We filter this memoized list so that no orphan "break" elements are rendered.
 | 
			
		||||
  const userMenuItems = useMemo<(ActionButtonProps | 'break')[]>(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								src/hooks/useAuthNavigation.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,29 @@
 | 
			
		||||
import { PATHS } from 'lib/paths'
 | 
			
		||||
import { useAuthState } from 'machines/appMachine'
 | 
			
		||||
import { useEffect } from 'react'
 | 
			
		||||
import { useLocation, useNavigate } from 'react-router-dom'
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A simple hook that listens to the auth state of the app and navigates
 | 
			
		||||
 * accordingly.
 | 
			
		||||
 */
 | 
			
		||||
export function useAuthNavigation() {
 | 
			
		||||
  const navigate = useNavigate()
 | 
			
		||||
  const location = useLocation()
 | 
			
		||||
  const authState = useAuthState()
 | 
			
		||||
 | 
			
		||||
  // Subscribe to the auth state of the app and navigate accordingly.
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (
 | 
			
		||||
      authState.matches('loggedIn') &&
 | 
			
		||||
      location.pathname.includes(PATHS.SIGN_IN)
 | 
			
		||||
    ) {
 | 
			
		||||
      navigate(PATHS.INDEX)
 | 
			
		||||
    } else if (
 | 
			
		||||
      authState.matches('loggedOut') &&
 | 
			
		||||
      !location.pathname.includes(PATHS.SIGN_IN)
 | 
			
		||||
    ) {
 | 
			
		||||
      navigate(PATHS.SIGN_IN)
 | 
			
		||||
    }
 | 
			
		||||
  }, [authState])
 | 
			
		||||
}
 | 
			
		||||
@ -5,7 +5,11 @@ import {
 | 
			
		||||
  PathToNode,
 | 
			
		||||
  Identifier,
 | 
			
		||||
  topLevelRange,
 | 
			
		||||
  PipeExpression,
 | 
			
		||||
  CallExpression,
 | 
			
		||||
  VariableDeclarator,
 | 
			
		||||
} from './wasm'
 | 
			
		||||
import { ProgramMemory } from 'lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  findAllPreviousVariables,
 | 
			
		||||
  isNodeSafeToReplace,
 | 
			
		||||
@ -25,9 +29,11 @@ import {
 | 
			
		||||
  createCallExpression,
 | 
			
		||||
  createLiteral,
 | 
			
		||||
  createPipeSubstitution,
 | 
			
		||||
  createCallExpressionStdLib,
 | 
			
		||||
} from './modifyAst'
 | 
			
		||||
import { err } from 'lib/trap'
 | 
			
		||||
import { codeRefFromRange } from './std/artifactGraph'
 | 
			
		||||
import { addCallExpressionsToPipe, addCloseToPipe } from 'lang/std/sketch'
 | 
			
		||||
 | 
			
		||||
beforeAll(async () => {
 | 
			
		||||
  await initPromise
 | 
			
		||||
@ -680,3 +686,115 @@ myNestedVar = [
 | 
			
		||||
    expect(pathToNode).toEqual(pathToNode2)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
describe('Testing specific sketch getNodeFromPath workflow', () => {
 | 
			
		||||
  it('should parse the code', () => {
 | 
			
		||||
    const openSketch = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([0.02, 0.22], %)
 | 
			
		||||
|> xLine(0.39, %)
 | 
			
		||||
|> line([0.02, -0.17], %)
 | 
			
		||||
|> yLine(-0.15, %)
 | 
			
		||||
|> line([-0.21, -0.02], %)
 | 
			
		||||
|> xLine(-0.15, %)
 | 
			
		||||
|> line([-0.02, 0.21], %)
 | 
			
		||||
|> line([-0.08, 0.05], %)`
 | 
			
		||||
    const ast = assertParse(openSketch)
 | 
			
		||||
    expect(ast.start).toEqual(0)
 | 
			
		||||
    expect(ast.end).toEqual(227)
 | 
			
		||||
  })
 | 
			
		||||
  it('should find the location to add new lineTo', () => {
 | 
			
		||||
    const openSketch = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([0.02, 0.22], %)
 | 
			
		||||
|> xLine(0.39, %)
 | 
			
		||||
|> line([0.02, -0.17], %)
 | 
			
		||||
|> yLine(-0.15, %)
 | 
			
		||||
|> line([-0.21, -0.02], %)
 | 
			
		||||
|> xLine(-0.15, %)
 | 
			
		||||
|> line([-0.02, 0.21], %)
 | 
			
		||||
|> line([-0.08, 0.05], %)`
 | 
			
		||||
    const ast = assertParse(openSketch)
 | 
			
		||||
 | 
			
		||||
    const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
 | 
			
		||||
    const sketchRange = topLevelRange(
 | 
			
		||||
      openSketch.indexOf(sketchSnippet),
 | 
			
		||||
      openSketch.indexOf(sketchSnippet) + sketchSnippet.length
 | 
			
		||||
    )
 | 
			
		||||
    const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
 | 
			
		||||
    const modifiedAst = addCallExpressionsToPipe({
 | 
			
		||||
      node: ast,
 | 
			
		||||
      programMemory: ProgramMemory.empty(),
 | 
			
		||||
      pathToNode: sketchPathToNode,
 | 
			
		||||
      expressions: [
 | 
			
		||||
        createCallExpressionStdLib(
 | 
			
		||||
          'lineTo', // We are forcing lineTo!
 | 
			
		||||
          [
 | 
			
		||||
            createArrayExpression([
 | 
			
		||||
              createCallExpressionStdLib('profileStartX', [
 | 
			
		||||
                createPipeSubstitution(),
 | 
			
		||||
              ]),
 | 
			
		||||
              createCallExpressionStdLib('profileStartY', [
 | 
			
		||||
                createPipeSubstitution(),
 | 
			
		||||
              ]),
 | 
			
		||||
            ]),
 | 
			
		||||
            createPipeSubstitution(),
 | 
			
		||||
          ]
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    })
 | 
			
		||||
    if (err(modifiedAst)) throw modifiedAst
 | 
			
		||||
    const recasted = recast(modifiedAst)
 | 
			
		||||
    const expectedCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([0.02, 0.22], %)
 | 
			
		||||
  |> xLine(0.39, %)
 | 
			
		||||
  |> line([0.02, -0.17], %)
 | 
			
		||||
  |> yLine(-0.15, %)
 | 
			
		||||
  |> line([-0.21, -0.02], %)
 | 
			
		||||
  |> xLine(-0.15, %)
 | 
			
		||||
  |> line([-0.02, 0.21], %)
 | 
			
		||||
  |> line([-0.08, 0.05], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
`
 | 
			
		||||
    expect(recasted).toEqual(expectedCode)
 | 
			
		||||
  })
 | 
			
		||||
  it('it should find the location to add close', () => {
 | 
			
		||||
    const openSketch = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
|> startProfileAt([0.02, 0.22], %)
 | 
			
		||||
|> xLine(0.39, %)
 | 
			
		||||
|> line([0.02, -0.17], %)
 | 
			
		||||
|> yLine(-0.15, %)
 | 
			
		||||
|> line([-0.21, -0.02], %)
 | 
			
		||||
|> xLine(-0.15, %)
 | 
			
		||||
|> line([-0.02, 0.21], %)
 | 
			
		||||
|> line([-0.08, 0.05], %)
 | 
			
		||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
`
 | 
			
		||||
    const ast = assertParse(openSketch)
 | 
			
		||||
    const sketchSnippet = `startProfileAt([0.02, 0.22], %)`
 | 
			
		||||
    const sketchRange = topLevelRange(
 | 
			
		||||
      openSketch.indexOf(sketchSnippet),
 | 
			
		||||
      openSketch.indexOf(sketchSnippet) + sketchSnippet.length
 | 
			
		||||
    )
 | 
			
		||||
    const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
 | 
			
		||||
    const modifiedAst = addCloseToPipe({
 | 
			
		||||
      node: ast,
 | 
			
		||||
      programMemory: ProgramMemory.empty(),
 | 
			
		||||
      pathToNode: sketchPathToNode,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    if (err(modifiedAst)) throw modifiedAst
 | 
			
		||||
    const recasted = recast(modifiedAst)
 | 
			
		||||
    const expectedCode = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([0.02, 0.22], %)
 | 
			
		||||
  |> xLine(0.39, %)
 | 
			
		||||
  |> line([0.02, -0.17], %)
 | 
			
		||||
  |> yLine(-0.15, %)
 | 
			
		||||
  |> line([-0.21, -0.02], %)
 | 
			
		||||
  |> xLine(-0.15, %)
 | 
			
		||||
  |> line([-0.02, 0.21], %)
 | 
			
		||||
  |> line([-0.08, 0.05], %)
 | 
			
		||||
  |> lineTo([profileStartX(%), profileStartY(%)], %)
 | 
			
		||||
  |> close(%)
 | 
			
		||||
`
 | 
			
		||||
    expect(recasted).toEqual(expectedCode)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -22,6 +22,7 @@ import {
 | 
			
		||||
  topLevelRange,
 | 
			
		||||
  VariableDeclaration,
 | 
			
		||||
  VariableDeclarator,
 | 
			
		||||
  recast,
 | 
			
		||||
} from './wasm'
 | 
			
		||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
 | 
			
		||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
 | 
			
		||||
@ -79,7 +80,28 @@ export function getNodeFromPath<T>(
 | 
			
		||||
          deepPath: successfulPaths,
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return new Error('not an object')
 | 
			
		||||
      const stackTraceError = new Error()
 | 
			
		||||
      const sourceCode = recast(node)
 | 
			
		||||
      const levels = stackTraceError.stack?.split('\n')
 | 
			
		||||
      const aFewFunctionNames: string[] = []
 | 
			
		||||
      let tree = ''
 | 
			
		||||
      levels?.forEach((val, index) => {
 | 
			
		||||
        const fnName = val.trim().split(' ')[1]
 | 
			
		||||
        const ending = index === levels.length - 1 ? ' ' : ' > '
 | 
			
		||||
        tree += fnName + ending
 | 
			
		||||
        if (index < 3) {
 | 
			
		||||
          aFewFunctionNames.push(fnName)
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      const error = new Error(
 | 
			
		||||
        `Failed to stopAt ${stopAt}, ${aFewFunctionNames
 | 
			
		||||
          .filter((a) => a)
 | 
			
		||||
          .join(' > ')}`
 | 
			
		||||
      )
 | 
			
		||||
      console.error(tree)
 | 
			
		||||
      console.error(sourceCode)
 | 
			
		||||
      console.error(error.stack)
 | 
			
		||||
      return error
 | 
			
		||||
    }
 | 
			
		||||
    parent = currentNode
 | 
			
		||||
    parentEdge = pathItem[0]
 | 
			
		||||
 | 
			
		||||
@ -1999,7 +1999,7 @@ export class EngineCommandManager extends EventTarget {
 | 
			
		||||
      .catch((e) => {
 | 
			
		||||
        // TODO: Previously was never caught, we are not rejecting these pendingCommands but this needs to be handled at some point.
 | 
			
		||||
        /*noop*/
 | 
			
		||||
        return null
 | 
			
		||||
        return e
 | 
			
		||||
      })
 | 
			
		||||
  }
 | 
			
		||||
  /**
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import {
 | 
			
		||||
  default_project_settings,
 | 
			
		||||
  base64_decode,
 | 
			
		||||
  clear_scene_and_bust_cache,
 | 
			
		||||
  change_kcl_settings,
 | 
			
		||||
  reloadModule,
 | 
			
		||||
} from 'lib/wasm_lib_wrapper'
 | 
			
		||||
 | 
			
		||||
@ -56,6 +57,7 @@ import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifa
 | 
			
		||||
import { Artifact } from './std/artifactGraph'
 | 
			
		||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
 | 
			
		||||
import { NumericSuffix } from 'wasm-lib/kcl/bindings/NumericSuffix'
 | 
			
		||||
import { MetaSettings } from 'wasm-lib/kcl/bindings/MetaSettings'
 | 
			
		||||
 | 
			
		||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
 | 
			
		||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
 | 
			
		||||
@ -848,3 +850,17 @@ export function base64Decode(base64: string): ArrayBuffer | Error {
 | 
			
		||||
    return new Error('Caught error decoding base64 string: ' + e)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Change the meta settings for the kcl file.
 | 
			
		||||
/// Returns the new kcl string with the updated settings.
 | 
			
		||||
export function changeKclSettings(
 | 
			
		||||
  kcl: string,
 | 
			
		||||
  settings: MetaSettings
 | 
			
		||||
): string | Error {
 | 
			
		||||
  try {
 | 
			
		||||
    return change_kcl_settings(kcl, JSON.stringify(settings))
 | 
			
		||||
  } catch (e) {
 | 
			
		||||
    console.error('Caught error changing kcl settings: ' + e)
 | 
			
		||||
    return new Error('Caught error changing kcl settings: ' + e)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,14 @@
 | 
			
		||||
import { StateMachineCommandSetConfig } from 'lib/commandTypes'
 | 
			
		||||
import { authMachine } from 'machines/authMachine'
 | 
			
		||||
import { Command } from 'lib/commandTypes'
 | 
			
		||||
import { authActor } from 'machines/appMachine'
 | 
			
		||||
import { ACTOR_IDS } from 'machines/machineConstants'
 | 
			
		||||
 | 
			
		||||
type AuthCommandSchema = {}
 | 
			
		||||
 | 
			
		||||
export const authCommandBarConfig: StateMachineCommandSetConfig<
 | 
			
		||||
  typeof authMachine,
 | 
			
		||||
  AuthCommandSchema
 | 
			
		||||
> = {
 | 
			
		||||
  'Log in': {
 | 
			
		||||
    hide: 'both',
 | 
			
		||||
  },
 | 
			
		||||
  'Log out': {
 | 
			
		||||
    args: [],
 | 
			
		||||
export const authCommands: Command[] = [
 | 
			
		||||
  {
 | 
			
		||||
    groupId: ACTOR_IDS.AUTH,
 | 
			
		||||
    name: 'log-out',
 | 
			
		||||
    displayName: 'Log out',
 | 
			
		||||
    icon: 'arrowLeft',
 | 
			
		||||
    needsReview: false,
 | 
			
		||||
    onSubmit: () => authActor.send({ type: 'Log out' }),
 | 
			
		||||
  },
 | 
			
		||||
}
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -308,7 +308,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
    description:
 | 
			
		||||
      'Create a 3D body by moving a sketch region along an arbitrary path.',
 | 
			
		||||
    icon: 'sweep',
 | 
			
		||||
    status: 'development',
 | 
			
		||||
    needsReview: false,
 | 
			
		||||
    args: {
 | 
			
		||||
      target: {
 | 
			
		||||
@ -317,8 +316,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: true,
 | 
			
		||||
        multiple: false,
 | 
			
		||||
        warningMessage:
 | 
			
		||||
          'The sweep workflow is new and under tested. Please break it and report issues.',
 | 
			
		||||
      },
 | 
			
		||||
      trajectory: {
 | 
			
		||||
        inputType: 'selection',
 | 
			
		||||
@ -368,7 +365,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
  Revolve: {
 | 
			
		||||
    description: 'Create a 3D body by rotating a sketch region about an axis.',
 | 
			
		||||
    icon: 'revolve',
 | 
			
		||||
    status: 'development',
 | 
			
		||||
    needsReview: true,
 | 
			
		||||
    args: {
 | 
			
		||||
      selection: {
 | 
			
		||||
@ -377,8 +373,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
 | 
			
		||||
        multiple: false, // TODO: multiple selection
 | 
			
		||||
        required: true,
 | 
			
		||||
        skip: true,
 | 
			
		||||
        warningMessage:
 | 
			
		||||
          'The revolve workflow is new and under tested. Please break it and report issues.',
 | 
			
		||||
      },
 | 
			
		||||
      axisOrEdge: {
 | 
			
		||||
        inputType: 'options',
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										19
									
								
								src/lib/commandBarConfigs/validators.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,19 @@
 | 
			
		||||
import { parseEngineErrorMessage } from './validators'
 | 
			
		||||
 | 
			
		||||
describe('parseEngineErrorMessage', () => {
 | 
			
		||||
  it('takes an engine error string and parses its json message', () => {
 | 
			
		||||
    const engineError =
 | 
			
		||||
      'engine error: [{"error_code":"internal_engine","message":"Trajectory curve must be G1 continuous (with continuous tangents)"}]'
 | 
			
		||||
    const message = parseEngineErrorMessage(engineError)
 | 
			
		||||
    expect(message).toEqual(
 | 
			
		||||
      'Trajectory curve must be G1 continuous (with continuous tangents)'
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  it('retuns undefined on strings with different formats', () => {
 | 
			
		||||
    const s1 = 'engine error: []'
 | 
			
		||||
    const s2 = 'blabla'
 | 
			
		||||
    expect(parseEngineErrorMessage(s1)).toBeUndefined()
 | 
			
		||||
    expect(parseEngineErrorMessage(s2)).toBeUndefined()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -3,6 +3,7 @@ import { engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import { uuidv4 } from 'lib/utils'
 | 
			
		||||
import { CommandBarContext } from 'machines/commandBarMachine'
 | 
			
		||||
import { Selections } from 'lib/selections'
 | 
			
		||||
import { ApiError_type } from '@kittycad/lib/dist/types/src/models'
 | 
			
		||||
 | 
			
		||||
export const disableDryRunWithRetry = async (numberOfRetries = 3) => {
 | 
			
		||||
  for (let tries = 0; tries < numberOfRetries; tries++) {
 | 
			
		||||
@ -46,6 +47,20 @@ function isSelections(selections: unknown): selections is Selections {
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function parseEngineErrorMessage(engineError: string) {
 | 
			
		||||
  const parts = engineError.split('engine error: ')
 | 
			
		||||
  if (parts.length < 2) {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const errors = JSON.parse(parts[1]) as ApiError_type[]
 | 
			
		||||
  if (!errors[0]) {
 | 
			
		||||
    return undefined
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return errors[0].message
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const revolveAxisValidator = async ({
 | 
			
		||||
  data,
 | 
			
		||||
  context,
 | 
			
		||||
@ -83,7 +98,7 @@ export const revolveAxisValidator = async ({
 | 
			
		||||
    value: 360,
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const revolveAboutEdgeCommand = async () => {
 | 
			
		||||
  const command = async () => {
 | 
			
		||||
    return await engineCommandManager.sendSceneCommand({
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
@ -92,17 +107,18 @@ export const revolveAxisValidator = async ({
 | 
			
		||||
        angle: angleInDegrees,
 | 
			
		||||
        edge_id: edgeSelection,
 | 
			
		||||
        target: sketchSelection,
 | 
			
		||||
        tolerance: 0.0001,
 | 
			
		||||
        // Gotcha: Playwright will fail with larger tolerances, need to use a smaller one.
 | 
			
		||||
        tolerance: 1e-7,
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  const attemptRevolve = await dryRunWrapper(revolveAboutEdgeCommand)
 | 
			
		||||
  if (attemptRevolve?.success) {
 | 
			
		||||
  const result = await dryRunWrapper(command)
 | 
			
		||||
  if (result?.success) {
 | 
			
		||||
    return true
 | 
			
		||||
  } else {
 | 
			
		||||
    // return error message for the toast
 | 
			
		||||
    return 'Unable to revolve with selected edge'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const reason = parseEngineErrorMessage(result) || 'unknown'
 | 
			
		||||
  return `Unable to revolve with the current selection. Reason: ${reason}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const loftValidator = async ({
 | 
			
		||||
@ -128,7 +144,7 @@ export const loftValidator = async ({
 | 
			
		||||
    return 'Unable to loft, selection contains less than two solid2ds'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const loftCommand = async () => {
 | 
			
		||||
  const command = async () => {
 | 
			
		||||
    // TODO: check what to do with these
 | 
			
		||||
    const DEFAULT_V_DEGREE = 2
 | 
			
		||||
    const DEFAULT_TOLERANCE = 2
 | 
			
		||||
@ -145,13 +161,13 @@ export const loftValidator = async ({
 | 
			
		||||
      },
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  const attempt = await dryRunWrapper(loftCommand)
 | 
			
		||||
  if (attempt?.success) {
 | 
			
		||||
  const result = await dryRunWrapper(command)
 | 
			
		||||
  if (result?.success) {
 | 
			
		||||
    return true
 | 
			
		||||
  } else {
 | 
			
		||||
    // return error message for the toast
 | 
			
		||||
    return 'Unable to loft with selected sketches'
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const reason = parseEngineErrorMessage(result) || 'unknown'
 | 
			
		||||
  return `Unable to loft with the current selection. Reason: ${reason}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const shellValidator = async ({
 | 
			
		||||
@ -180,7 +196,7 @@ export const shellValidator = async ({
 | 
			
		||||
    return "Unable to shell, couldn't find the solid"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const shellCommand = async () => {
 | 
			
		||||
  const command = async () => {
 | 
			
		||||
    // TODO: figure out something better than an arbitrarily small value
 | 
			
		||||
    const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
 | 
			
		||||
    const DEFAULT_HOLLOW = false
 | 
			
		||||
@ -200,12 +216,13 @@ export const shellValidator = async ({
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const attemptShell = await dryRunWrapper(shellCommand)
 | 
			
		||||
  if (attemptShell?.success) {
 | 
			
		||||
  const result = await dryRunWrapper(command)
 | 
			
		||||
  if (result?.success) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 'Unable to shell with the provided selection'
 | 
			
		||||
  const reason = parseEngineErrorMessage(result) || 'unknown'
 | 
			
		||||
  return `Unable to shell with the current selection. Reason: ${reason}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const sweepValidator = async ({
 | 
			
		||||
@ -241,7 +258,7 @@ export const sweepValidator = async ({
 | 
			
		||||
  }
 | 
			
		||||
  const target = targetArtifact.pathId
 | 
			
		||||
 | 
			
		||||
  const sweepCommand = async () => {
 | 
			
		||||
  const command = async () => {
 | 
			
		||||
    // TODO: second look on defaults here
 | 
			
		||||
    const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
 | 
			
		||||
    const DEFAULT_SECTIONAL = false
 | 
			
		||||
@ -261,10 +278,11 @@ export const sweepValidator = async ({
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const attemptSweep = await dryRunWrapper(sweepCommand)
 | 
			
		||||
  if (attemptSweep?.success) {
 | 
			
		||||
  const result = await dryRunWrapper(command)
 | 
			
		||||
  if (result?.success) {
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return 'Unable to sweep with the provided selection'
 | 
			
		||||
  const reason = parseEngineErrorMessage(result) || 'unknown'
 | 
			
		||||
  return `Unable to sweep with the current selection. Reason: ${reason}`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,7 +26,7 @@ export const FILE_EXT = '.kcl'
 | 
			
		||||
/** Default file to open when a project is opened */
 | 
			
		||||
export const PROJECT_ENTRYPOINT = `main${FILE_EXT}` as const
 | 
			
		||||
/** Thumbnail file name */
 | 
			
		||||
export const PROJECT_IMAGE_NAME = `main.jpg` as const
 | 
			
		||||
export const PROJECT_IMAGE_NAME = `thumbnail.png` as const
 | 
			
		||||
/** The localStorage key for last-opened projects */
 | 
			
		||||
export const FILE_PERSIST_KEY = `${PROJECT_FOLDER}-last-opened` as const
 | 
			
		||||
/** The default name given to new kcl files in a project */
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import {
 | 
			
		||||
import {
 | 
			
		||||
  PROJECT_ENTRYPOINT,
 | 
			
		||||
  PROJECT_FOLDER,
 | 
			
		||||
  PROJECT_IMAGE_NAME,
 | 
			
		||||
  PROJECT_SETTINGS_FILE_NAME,
 | 
			
		||||
  SETTINGS_FILE_NAME,
 | 
			
		||||
  TELEMETRY_FILE_NAME,
 | 
			
		||||
@ -625,3 +626,19 @@ export const getUser = async (
 | 
			
		||||
  }
 | 
			
		||||
  return Promise.reject(new Error('unreachable'))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const writeProjectThumbnailFile = async (
 | 
			
		||||
  dataUrl: string,
 | 
			
		||||
  projectDirectoryPath: string
 | 
			
		||||
) => {
 | 
			
		||||
  const filePath = window.electron.path.join(
 | 
			
		||||
    projectDirectoryPath,
 | 
			
		||||
    PROJECT_IMAGE_NAME
 | 
			
		||||
  )
 | 
			
		||||
  const data = atob(dataUrl.substring('data:image/png;base64,'.length))
 | 
			
		||||
  const asArray = new Uint8Array(data.length)
 | 
			
		||||
  for (let i = 0, len = data.length; i < len; ++i) {
 | 
			
		||||
    asArray[i] = data.charCodeAt(i)
 | 
			
		||||
  }
 | 
			
		||||
  return window.electron.writeFile(filePath, asArray)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,13 +1,10 @@
 | 
			
		||||
import { CommandBarOverwriteWarning } from 'components/CommandBarOverwriteWarning'
 | 
			
		||||
import { Command, CommandArgumentOption } from './commandTypes'
 | 
			
		||||
import { codeManager, kclManager } from './singletons'
 | 
			
		||||
import { kclManager } from './singletons'
 | 
			
		||||
import { isDesktop } from './isDesktop'
 | 
			
		||||
import { FILE_EXT, PROJECT_SETTINGS_FILE_NAME } from './constants'
 | 
			
		||||
import { FILE_EXT } from './constants'
 | 
			
		||||
import { UnitLength_type } from '@kittycad/lib/dist/types/src/models'
 | 
			
		||||
import { parseProjectSettings } from 'lang/wasm'
 | 
			
		||||
import { err, reportRejection } from './trap'
 | 
			
		||||
import { projectConfigurationToSettingsPayload } from './settings/settingsUtils'
 | 
			
		||||
import { copyFileShareLink } from './links'
 | 
			
		||||
import { reportRejection } from './trap'
 | 
			
		||||
import { IndexLoaderData } from './types'
 | 
			
		||||
 | 
			
		||||
interface OnSubmitProps {
 | 
			
		||||
@ -68,56 +65,23 @@ export function kclCommands(commandProps: KclCommandConfig): Command[] {
 | 
			
		||||
        const sampleCodeUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
 | 
			
		||||
          projectPathPart
 | 
			
		||||
        )}/${encodeURIComponent(primaryKclFile)}`
 | 
			
		||||
        const sampleSettingsFileUrl = `https://raw.githubusercontent.com/KittyCAD/kcl-samples/main/${encodeURIComponent(
 | 
			
		||||
          projectPathPart
 | 
			
		||||
        )}/${PROJECT_SETTINGS_FILE_NAME}`
 | 
			
		||||
 | 
			
		||||
        Promise.allSettled([fetch(sampleCodeUrl), fetch(sampleSettingsFileUrl)])
 | 
			
		||||
          .then((results) => {
 | 
			
		||||
            const a =
 | 
			
		||||
              'value' in results[0] ? results[0].value : results[0].reason
 | 
			
		||||
            const b =
 | 
			
		||||
              'value' in results[1] ? results[1].value : results[1].reason
 | 
			
		||||
            return [a, b]
 | 
			
		||||
          })
 | 
			
		||||
          .then(
 | 
			
		||||
            async ([
 | 
			
		||||
              codeResponse,
 | 
			
		||||
              settingsResponse,
 | 
			
		||||
            ]): Promise<OnSubmitProps> => {
 | 
			
		||||
              if (!codeResponse.ok) {
 | 
			
		||||
                console.error(
 | 
			
		||||
                  'Failed to fetch sample code:',
 | 
			
		||||
                  codeResponse.statusText
 | 
			
		||||
                )
 | 
			
		||||
                return Promise.reject(new Error('Failed to fetch sample code'))
 | 
			
		||||
              }
 | 
			
		||||
              const code = await codeResponse.text()
 | 
			
		||||
 | 
			
		||||
              // It's possible that a sample doesn't have a project.toml
 | 
			
		||||
              // associated with it.
 | 
			
		||||
              let projectSettingsPayload: ReturnType<
 | 
			
		||||
                typeof projectConfigurationToSettingsPayload
 | 
			
		||||
              > = {}
 | 
			
		||||
              if (settingsResponse.ok) {
 | 
			
		||||
                const parsedProjectSettings = parseProjectSettings(
 | 
			
		||||
                  await settingsResponse.text()
 | 
			
		||||
                )
 | 
			
		||||
                if (!err(parsedProjectSettings)) {
 | 
			
		||||
                  projectSettingsPayload =
 | 
			
		||||
                    projectConfigurationToSettingsPayload(parsedProjectSettings)
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
 | 
			
		||||
              return {
 | 
			
		||||
                sampleName: data.sample.split('/')[0] + FILE_EXT,
 | 
			
		||||
                code,
 | 
			
		||||
                method: data.method,
 | 
			
		||||
                sampleUnits:
 | 
			
		||||
                  projectSettingsPayload.modeling?.defaultUnit || 'mm',
 | 
			
		||||
              }
 | 
			
		||||
        fetch(sampleCodeUrl)
 | 
			
		||||
          .then(async (codeResponse): Promise<OnSubmitProps> => {
 | 
			
		||||
            if (!codeResponse.ok) {
 | 
			
		||||
              console.error(
 | 
			
		||||
                'Failed to fetch sample code:',
 | 
			
		||||
                codeResponse.statusText
 | 
			
		||||
              )
 | 
			
		||||
              return Promise.reject(new Error('Failed to fetch sample code'))
 | 
			
		||||
            }
 | 
			
		||||
          )
 | 
			
		||||
            const code = await codeResponse.text()
 | 
			
		||||
            return {
 | 
			
		||||
              sampleName: data.sample.split('/')[0] + FILE_EXT,
 | 
			
		||||
              code,
 | 
			
		||||
              method: data.method,
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
          .then((props) => {
 | 
			
		||||
            if (props?.code) {
 | 
			
		||||
              commandProps.specialPropsForSampleCommand
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
function takeScreenshotOfVideoStreamCanvas() {
 | 
			
		||||
export function takeScreenshotOfVideoStreamCanvas() {
 | 
			
		||||
  const canvas = document.querySelector('[data-engine]')
 | 
			
		||||
  const video = document.getElementById('video-stream')
 | 
			
		||||
  if (
 | 
			
		||||
 | 
			
		||||
@ -577,10 +577,9 @@ export function getSelectionTypeDisplayText(
 | 
			
		||||
    .map(
 | 
			
		||||
      // Hack for showing "face" instead of "extrude-wall" in command bar text
 | 
			
		||||
      ([type, count]) =>
 | 
			
		||||
        `${count} ${type
 | 
			
		||||
          .replace('wall', 'face')
 | 
			
		||||
          .replace('solid2d', 'face')
 | 
			
		||||
          .replace('segment', 'face')}${count > 1 ? 's' : ''}`
 | 
			
		||||
        `${count} ${type.replace('wall', 'face').replace('solid2d', 'face')}${
 | 
			
		||||
          count > 1 ? 's' : ''
 | 
			
		||||
        }`
 | 
			
		||||
    )
 | 
			
		||||
    .toArray()
 | 
			
		||||
    .join(', ')
 | 
			
		||||
 | 
			
		||||
@ -103,7 +103,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
            data: { name: 'Revolve', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
        icon: 'revolve',
 | 
			
		||||
        status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
 | 
			
		||||
        status: 'available',
 | 
			
		||||
        title: 'Revolve',
 | 
			
		||||
        hotkey: 'R',
 | 
			
		||||
        description:
 | 
			
		||||
@ -124,7 +124,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
 | 
			
		||||
            data: { name: 'Sweep', groupId: 'modeling' },
 | 
			
		||||
          }),
 | 
			
		||||
        icon: 'sweep',
 | 
			
		||||
        status: DEV || IS_NIGHTLY_OR_DEBUG ? 'available' : 'kcl-only',
 | 
			
		||||
        status: 'available',
 | 
			
		||||
        title: 'Sweep',
 | 
			
		||||
        hotkey: 'W',
 | 
			
		||||
        description:
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ import {
 | 
			
		||||
  default_project_settings as DefaultProjectSettings,
 | 
			
		||||
  base64_decode as Base64Decode,
 | 
			
		||||
  clear_scene_and_bust_cache as ClearSceneAndBustCache,
 | 
			
		||||
  change_kcl_settings as ChangeKclSettings,
 | 
			
		||||
} from '../wasm-lib/pkg/wasm_lib'
 | 
			
		||||
 | 
			
		||||
type ModuleType = typeof import('../wasm-lib/pkg/wasm_lib')
 | 
			
		||||
@ -110,3 +111,6 @@ export const clear_scene_and_bust_cache: typeof ClearSceneAndBustCache = (
 | 
			
		||||
) => {
 | 
			
		||||
  return getModule().clear_scene_and_bust_cache(...args)
 | 
			
		||||
}
 | 
			
		||||
export const change_kcl_settings: typeof ChangeKclSettings = (...args) => {
 | 
			
		||||
  return getModule().change_kcl_settings(...args)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										30
									
								
								src/machines/appMachine.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,30 @@
 | 
			
		||||
import { ActorRefFrom, createActor, setup } from 'xstate'
 | 
			
		||||
import { authMachine } from './authMachine'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import { ACTOR_IDS } from './machineConstants'
 | 
			
		||||
 | 
			
		||||
const appMachine = setup({
 | 
			
		||||
  actors: {
 | 
			
		||||
    [ACTOR_IDS.AUTH]: authMachine,
 | 
			
		||||
  },
 | 
			
		||||
}).createMachine({
 | 
			
		||||
  /** @xstate-layout N4IgpgJg5mDOIC5gF8A0IB2B7CdGgAoBbAQwGMALASwzAEp8QAHLWKgFyqw0YA9EAjACZ0AT0FDkU5EA */
 | 
			
		||||
  id: 'modeling-app',
 | 
			
		||||
  invoke: [
 | 
			
		||||
    {
 | 
			
		||||
      src: ACTOR_IDS.AUTH,
 | 
			
		||||
      systemId: ACTOR_IDS.AUTH,
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export const appActor = createActor(appMachine).start()
 | 
			
		||||
 | 
			
		||||
export const authActor = appActor.system.get(ACTOR_IDS.AUTH) as ActorRefFrom<
 | 
			
		||||
  typeof authMachine
 | 
			
		||||
>
 | 
			
		||||
export const useAuthState = () => useSelector(authActor, (state) => state)
 | 
			
		||||
export const useToken = () =>
 | 
			
		||||
  useSelector(authActor, (state) => state.context.token)
 | 
			
		||||
export const useUser = () =>
 | 
			
		||||
  useSelector(authActor, (state) => state.context.user)
 | 
			
		||||
@ -15,6 +15,8 @@ import {
 | 
			
		||||
} from 'lib/desktop'
 | 
			
		||||
import { COOKIE_NAME } from 'lib/constants'
 | 
			
		||||
import { markOnce } from 'lib/performance'
 | 
			
		||||
import { ACTOR_IDS } from './machineConstants'
 | 
			
		||||
import withBaseUrl from '../lib/withBaseURL'
 | 
			
		||||
 | 
			
		||||
const SKIP_AUTH = VITE_KC_SKIP_AUTH === 'true' && DEV
 | 
			
		||||
 | 
			
		||||
@ -50,7 +52,7 @@ export type Events =
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
export const TOKEN_PERSIST_KEY = 'TOKEN_PERSIST_KEY'
 | 
			
		||||
const persistedToken =
 | 
			
		||||
export const persistedToken =
 | 
			
		||||
  VITE_KC_DEV_TOKEN ||
 | 
			
		||||
  getCookie(COOKIE_NAME) ||
 | 
			
		||||
  localStorage?.getItem(TOKEN_PERSIST_KEY) ||
 | 
			
		||||
@ -69,18 +71,17 @@ export const authMachine = setup({
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
    goToIndexPage: () => {},
 | 
			
		||||
    goToSignInPage: () => {},
 | 
			
		||||
  },
 | 
			
		||||
  actors: {
 | 
			
		||||
    getUser: fromPromise(({ input }: { input: { token?: string } }) =>
 | 
			
		||||
      getUser(input)
 | 
			
		||||
    ),
 | 
			
		||||
    logout: fromPromise(async () =>
 | 
			
		||||
      isDesktop() ? writeTokenFile('') : logout()
 | 
			
		||||
    ),
 | 
			
		||||
  },
 | 
			
		||||
}).createMachine({
 | 
			
		||||
  /** @xstate-layout N4IgpgJg5mDOIC5QEECuAXAFgOgMabFwGsBJAMwBkB7KGCEgOwGIIqGxsBLBgNyqI75CRALQAbGnRHcA2gAYAuolAAHKrE7pObZSAAeiAIwAWQ9gBspuQCYAnAGYAHPYCsx+4ccAaEAE9E1q7YcoZyxrYR1m7mcrYAvnE+aFh4BMTk1LSQjExgAE55VHnYKmIAhuhkRQC2qcLikpDSDPJKSCBqGlo67QYI9gDs5tge5o6h5vau7oY+-v3mA9jWco4u5iu21ua2YcYJSRg4Eln0zJkABFQYrbqdmtoMun2GA7YjxuPmLqvGNh5zRCfJaOcyLUzuAYuFyGcwHEDJY6NCAAeQwTEuskUd3UDx6oD6Im2wUcAzkMJ2cjBxlMgIWLmwZLWljecjJTjh8IYVAgcF0iJxXUez0QIgGxhJZIpu2ptL8AWwtje1nCW2iq1shns8MRdXSlGRjEFeKevUQjkcy3sqwGHimbg83nlCF22GMytVUWMMUc8USCKO2BOdCN7Xu3VNBKMKsVFp2hm2vu+1id83slkVrgTxhcW0pNJ1geDkDR6GNEZFCAT1kZZLk9cMLltb0WdPMjewjjC1mzOZCtk5CSAA */
 | 
			
		||||
  id: 'Auth',
 | 
			
		||||
  /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOhzEwGsBJAMwBkB7KGCa-AYgkcJIIDdGlMGWwVKAWgA2zVhIIBtAAwBdRKAAOjWLgAuuHupAAPRAGYArAEYSADgu2AnGYBMLpVYBsZz7YA0IACeiG6OJM62tmZKLgDsno5KtvEAvikBaFh4hKTkVHRMLJDsHGAATmWMZSQaUui6tFWoouLSspDy+MpqSCBaOvqGvaYIljb2Tq7uXj7+QYgALFYW4clWy1ZmVgsWsZtpGRg4BMQkMkVsnIUABIwArrrdRv16BvhGI74LJBYW7o5WKJmKILObBUZeEgJP4LTxKMwIhZmBYLA4gTLHHJnWQEKAAeQeXB4IgEQhEGOyp3OUFxBN0CFJmHqb26T16L0G72GiCsSg8PyszkBCViTiUjgC4Jcnhc4SUsQcvgsoL2VjRFJOpGptMJ5Uq1Vq9UaZWaGqx2vw+IeDPwgiZnNZqme2leQ1An1s31+-0BCJBYJCLm+lk8CRl9hRyos6qOlK17QgdI4N0UTvZLs5Hx58NsJARuys0tDSl+AYQthsgNi0TMqt2LjVaPwjAgcCMZuIzoGbyzCAknkliH7Maympa+QYCfYXddXPdixcg4QvKUdk2u2iLkcsXhCRHmKpU7nfQzPe5CAsMpIXi8MvFKM8VliS5c1jzj53W3isNFqPS6NjMcLStXQZ0zc8ohsJI-kcFxXEcR9HAWF9gTzDxbCUXxAQWEsdn3ONsQuOkwLPedl22MIzFg3YP1gl9PG+bYvGsSxlUcRJozSFIgA */
 | 
			
		||||
  id: ACTOR_IDS.AUTH,
 | 
			
		||||
  initial: 'checkIfLoggedIn',
 | 
			
		||||
  context: {
 | 
			
		||||
    token: persistedToken,
 | 
			
		||||
@ -112,19 +113,30 @@ export const authMachine = setup({
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    loggedIn: {
 | 
			
		||||
      entry: ['goToIndexPage'],
 | 
			
		||||
      on: {
 | 
			
		||||
        'Log out': {
 | 
			
		||||
          target: 'loggedOut',
 | 
			
		||||
          actions: () => {
 | 
			
		||||
            // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
            if (isDesktop()) writeTokenFile('')
 | 
			
		||||
          },
 | 
			
		||||
          target: 'loggingOut',
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    loggingOut: {
 | 
			
		||||
      invoke: {
 | 
			
		||||
        src: 'logout',
 | 
			
		||||
        onDone: 'loggedOut',
 | 
			
		||||
        onError: {
 | 
			
		||||
          target: 'loggedIn',
 | 
			
		||||
          actions: [
 | 
			
		||||
            ({ event }) => {
 | 
			
		||||
              console.error(
 | 
			
		||||
                'Error while logging out',
 | 
			
		||||
                'error' in event ? `: ${event.error}` : ''
 | 
			
		||||
              )
 | 
			
		||||
            },
 | 
			
		||||
          ],
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    loggedOut: {
 | 
			
		||||
      entry: ['goToSignInPage'],
 | 
			
		||||
      on: {
 | 
			
		||||
        'Log in': {
 | 
			
		||||
          target: 'checkIfLoggedIn',
 | 
			
		||||
@ -235,3 +247,12 @@ async function getAndSyncStoredToken(input: {
 | 
			
		||||
  localStorage.setItem(TOKEN_PERSIST_KEY, fileToken)
 | 
			
		||||
  return fileToken
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function logout() {
 | 
			
		||||
  localStorage.removeItem(TOKEN_PERSIST_KEY)
 | 
			
		||||
  if (isDesktop()) return Promise.resolve(null)
 | 
			
		||||
  return fetch(withBaseUrl('/logout'), {
 | 
			
		||||
    method: 'POST',
 | 
			
		||||
    credentials: 'include',
 | 
			
		||||
  })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,7 @@ import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
 | 
			
		||||
import { MachineManager } from 'components/MachineManagerProvider'
 | 
			
		||||
import toast from 'react-hot-toast'
 | 
			
		||||
import { useSelector } from '@xstate/react'
 | 
			
		||||
import { authCommands } from 'lib/commandBarConfigs/authCommandConfig'
 | 
			
		||||
 | 
			
		||||
export type CommandBarContext = {
 | 
			
		||||
  commands: Command[]
 | 
			
		||||
@ -80,6 +81,7 @@ export type CommandBarMachineEvent =
 | 
			
		||||
export const commandBarMachine = setup({
 | 
			
		||||
  types: {
 | 
			
		||||
    context: {} as CommandBarContext,
 | 
			
		||||
    input: {} as { commands: Command[] },
 | 
			
		||||
    events: {} as CommandBarMachineEvent,
 | 
			
		||||
  },
 | 
			
		||||
  actions: {
 | 
			
		||||
@ -409,8 +411,8 @@ export const commandBarMachine = setup({
 | 
			
		||||
  },
 | 
			
		||||
}).createMachine({
 | 
			
		||||
  /** @xstate-layout N4IgpgJg5mDOIC5QGED2BbdBDAdhABAEJYBOAxMgDaqxgDaADALqKgAONAlgC6eo6sQAD0QBaAIwB2AHTiAHAE45AZjkAmdcoaSArCoA0IAJ6JxDOdJ2SF4gCySHaqZIa2Avm8NpMuAsXJUYKSMLEggHLA8fAJhIgiiOgBssokKTpJqiXK2OsqJaoYm8eJqytKJ9hqq+eJW2h5eGNh4RKRkAGKcLb74tJRgAMbc+ANNviGCEVH8gnEKubK5ympVrrlyhabm0rZ5CtYM4hVq83ININ7NfqTSVDSQZADybGA4E2FTvDOxiGoMDNJMjo9H9lLYGDpKpsEModOJpA5XOIwakwcidOdLj1-LdqLQIGQAIIQAijHx4WDvdhcL4xUBxURqSTw3LzfYKZSSOQZWzQ5S1crMuTAiElRKuTFjFo4u74sgAJTA6FQADcwCMpRBKcxJjTorMxEzkhouftuXCGGpIdCpADmZJxfzJLY5Cp5pLydcSLj7gSqeE9d96Yh+TppKoXfkGKdlHloUyFIDUvMXBzbFl3J4LprWt6AMpgfpDLpQDWesgFovDMlXf2ffU-BBZZKuczWWylRRwvlM6Q2LIMZQ2QdHZQeq65245vqDbgPOuBunCEP88MqPQchwVNLKaHptTSYUuRI6f4npyJcfYm5Ylozobz8ShamRWkGhBmeTSQeJX+JXQ6H88jQnCMiugoELCpypQKJeWa3l6U6er0hazvOajPgGr4NsGH6WhYsHOkycglLCEJ7iclgaIosKSLGGgKFe0o3AA4lg3AABZgCQJb4KQUAAK7oK83CwBQHG4DAIwCSQJAiXxJCCcJODcAu2FBsuH55GGJ7irYHYqHpGzGKYWiWB2nK2Kcv6wWO8E5jibGcdxvH8UJIliQAInAqFDGWtY6h8i7vlkZREbYZg6JuwEmQgfxhnkHbiHYJ7yHYTGIU5XE8TgpZucponSISADuWBRLl+BdGwAncBWAkAEboDwClKSJanTEucS1Bk36DicDCpOYLoKHu-x9tGuhgoozKpBlk5ZS5FX5R50gAGpYJQnAQOxJZkBA-BgNIXQqqgADWh0qhtW3sWAeYlv0hKKe5KntW+YRFGi5RyOKDrZEaUW8pppTguGuwDRFEO-nNjnsdlrlPQVsBrVd228LlZDcSQqDemwlDsQAZtj6DSJdm2o7d91gI9rUvYFL4dYIH0RV9P0Zv9CiA3EwMAmCWgVHYKVwY0yE4oqKqcGAxV1Y1zU1uMdNYQzjbMpYcgQlFxFq66u6xXRALbkyg7-Bz0bQzcYsS1LxIEMttOYfWGldYcCWwQmNiRrU0Jq2GRxJCbHZqC6ahm96FuSwqSqquqtuqQrDudVs4iJhUyZtn9qjQokYKHjk6hSLsZhciH0hh1LACiEDNTHr04ZpJT6bIEXxcKp50YDRT-mGrrJbUgFDp3xfIFxAynbx1PPaJe0HUdOAnedJMozd4+IzXjuIJCLIQqUWjJS4MVFBByS7BzyJq38WTB-ZIs3sPo8VcvHlTzgh3HWdF2L3OD8qZST66upCdxUTLBJOUUHB6GFPpSQXt1CWEGpaOiv4EyD1vmPBGj9MbY2kLjAmRMF5kyXmg7+q8AFJwbjkXQEVsj5FyO3RAiQjjfi5CReYPck7iA8FmHAqAIBwEEAhXMf8la4VEMsWwgJuTTXNGYK0tC4rwkDvsAaSQ-qlAhIPPEkBBFvWEVycoFQhywUHGaHWH04Q7FUNBdc+x2FXwnDiSss5eJyzwFo2ucQIplBWHrcEVhuoFFirCeEuxsj8mWEOFYmZhZ2JvNOXyc4ICuLXggaxCJwG70AiUHQfIzHBKHGYDMJFhTFwWjlPKhDRKJJIRFGQcIFBsiOIHJw8ZIQ7CUGrY+9g9AnmKbDRaZSaaFRKmVNGpYqo1Uqe+FKYZan1PyElbJYjrB6C5CUJQ9DL5ROvN6Ep8MBlI3WvgkZEzGzyAbnkZK-wjan38UzeEzZtBswdADYupdjm4XoTIOwuwHB5HyGkYyRRdBBLosCIE6ZvpnFsVs24KD77lPgEFf+kzxRH32EyFYlpOzQjAZYV2ehTkfI4W4IAA */
 | 
			
		||||
  context: {
 | 
			
		||||
    commands: [],
 | 
			
		||||
  context: ({ input }) => ({
 | 
			
		||||
    commands: input.commands || [],
 | 
			
		||||
    selectedCommand: undefined,
 | 
			
		||||
    currentArgument: undefined,
 | 
			
		||||
    selectionRanges: {
 | 
			
		||||
@ -425,7 +427,7 @@ export const commandBarMachine = setup({
 | 
			
		||||
      setCurrentMachine: () => {},
 | 
			
		||||
      noMachinesReason: () => undefined,
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  }),
 | 
			
		||||
  id: 'Command Bar',
 | 
			
		||||
  initial: 'Closed',
 | 
			
		||||
  states: {
 | 
			
		||||
@ -631,7 +633,11 @@ function sortCommands(a: Command, b: Command) {
 | 
			
		||||
  return a.name.localeCompare(b.name)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const commandBarActor = createActor(commandBarMachine).start()
 | 
			
		||||
export const commandBarActor = createActor(commandBarMachine, {
 | 
			
		||||
  input: {
 | 
			
		||||
    commands: [...authCommands],
 | 
			
		||||
  },
 | 
			
		||||
}).start()
 | 
			
		||||
 | 
			
		||||
/** Basic state snapshot selector */
 | 
			
		||||
const cmdBarStateSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								src/machines/machineConstants.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,3 @@
 | 
			
		||||
export const ACTOR_IDS = {
 | 
			
		||||
  AUTH: 'auth',
 | 
			
		||||
}
 | 
			
		||||
@ -1452,13 +1452,14 @@ export const modelingMachine = setup({
 | 
			
		||||
      unknown,
 | 
			
		||||
      ModelingCommandSchema['Extrude'] | undefined
 | 
			
		||||
    >(async ({ input }) => {
 | 
			
		||||
      if (!input) return new Error('No input provided')
 | 
			
		||||
      if (!input) return Promise.reject('No input provided')
 | 
			
		||||
      const { selection, distance } = input
 | 
			
		||||
      let ast = structuredClone(kclManager.ast)
 | 
			
		||||
      let extrudeName: string | undefined = undefined
 | 
			
		||||
 | 
			
		||||
      const sourceRange = selection.graphSelections[0]?.codeRef.range
 | 
			
		||||
      const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
 | 
			
		||||
      const pathToNode = getNodePathFromSourceRange(
 | 
			
		||||
        ast,
 | 
			
		||||
        selection.graphSelections[0]?.codeRef.range
 | 
			
		||||
      )
 | 
			
		||||
      // Add an extrude statement to the AST
 | 
			
		||||
      const extrudeSketchRes = extrudeSketch(
 | 
			
		||||
        ast,
 | 
			
		||||
@ -1468,7 +1469,7 @@ export const modelingMachine = setup({
 | 
			
		||||
          ? distance.variableIdentifierAst
 | 
			
		||||
          : distance.valueAst
 | 
			
		||||
      )
 | 
			
		||||
      if (err(extrudeSketchRes)) return extrudeSketchRes
 | 
			
		||||
      if (err(extrudeSketchRes)) return Promise.reject(extrudeSketchRes)
 | 
			
		||||
      const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
 | 
			
		||||
 | 
			
		||||
      // Insert the distance variable if the user has provided a variable name
 | 
			
		||||
@ -2559,7 +2560,7 @@ export const modelingMachine = setup({
 | 
			
		||||
 | 
			
		||||
        'Delete segment': {
 | 
			
		||||
          reenter: false,
 | 
			
		||||
          actions: ['Delete segment', 'Set sketchDetails'],
 | 
			
		||||
          actions: ['Delete segment', 'Set sketchDetails', 'reset selections'],
 | 
			
		||||
        },
 | 
			
		||||
        'code edit during sketch': '.clean slate',
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
@ -1,15 +1,14 @@
 | 
			
		||||
import { OnboardingButtons, useDismiss, useNextClick } from '.'
 | 
			
		||||
import { onboardingPaths } from 'routes/Onboarding/paths'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
 | 
			
		||||
import { useUser } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
export default function UserMenu() {
 | 
			
		||||
  const { auth } = useSettingsAuthContext()
 | 
			
		||||
  const user = useUser()
 | 
			
		||||
  const dismiss = useDismiss()
 | 
			
		||||
  const next = useNextClick(onboardingPaths.PROJECT_MENU)
 | 
			
		||||
  const [avatarErrored, setAvatarErrored] = useState(false)
 | 
			
		||||
 | 
			
		||||
  const user = auth?.context?.user
 | 
			
		||||
  const errorOrNoImage = !user?.image || avatarErrored
 | 
			
		||||
  const buttonDescription = errorOrNoImage ? 'the menu button' : 'your avatar'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,6 +14,7 @@ import { openExternalBrowserIfDesktop } from 'lib/openWindow'
 | 
			
		||||
import { toSync } from 'lib/utils'
 | 
			
		||||
import { reportRejection } from 'lib/trap'
 | 
			
		||||
import toast from 'react-hot-toast'
 | 
			
		||||
import { authActor } from 'machines/appMachine'
 | 
			
		||||
 | 
			
		||||
const subtleBorder =
 | 
			
		||||
  'border border-solid border-chalkboard-30 dark:border-chalkboard-80'
 | 
			
		||||
@ -22,7 +23,6 @@ const cardArea = `${subtleBorder} rounded-lg px-6 py-3 text-chalkboard-70 dark:t
 | 
			
		||||
const SignIn = () => {
 | 
			
		||||
  const [userCode, setUserCode] = useState('')
 | 
			
		||||
  const {
 | 
			
		||||
    auth: { send },
 | 
			
		||||
    settings: {
 | 
			
		||||
      state: {
 | 
			
		||||
        context: {
 | 
			
		||||
@ -70,7 +70,7 @@ const SignIn = () => {
 | 
			
		||||
      toast.error('Error while trying to log in')
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
    send({ type: 'Log in', token })
 | 
			
		||||
    authActor.send({ type: 'Log in', token })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								src/wasm-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -1710,7 +1710,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.32"
 | 
			
		||||
version = "0.2.33"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
description = "KittyCAD Language implementation and tools"
 | 
			
		||||
version = "0.2.32"
 | 
			
		||||
version = "0.2.33"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -13,9 +13,7 @@ use tower_lsp::lsp_types::{
 | 
			
		||||
    MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::execution::Sketch;
 | 
			
		||||
 | 
			
		||||
use crate::std::Primitive;
 | 
			
		||||
use crate::{execution::Sketch, std::Primitive};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
 | 
			
		||||
@ -7,9 +7,9 @@ use crate::{
 | 
			
		||||
    KclError, SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
pub(super) const SETTINGS: &str = "settings";
 | 
			
		||||
pub(super) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
 | 
			
		||||
pub(super) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
 | 
			
		||||
pub(crate) const SETTINGS: &str = "settings";
 | 
			
		||||
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
 | 
			
		||||
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
 | 
			
		||||
 | 
			
		||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
 | 
			
		||||
pub(super) enum AnnotationScope {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ use std::collections::HashMap;
 | 
			
		||||
 | 
			
		||||
use async_recursion::async_recursion;
 | 
			
		||||
 | 
			
		||||
use super::cad_op::{OpArg, Operation};
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    execution::{
 | 
			
		||||
@ -19,8 +20,6 @@ use crate::{
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::cad_op::{OpArg, Operation};
 | 
			
		||||
 | 
			
		||||
impl BinaryPart {
 | 
			
		||||
    #[async_recursion]
 | 
			
		||||
    pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@ use kittycad_modeling_cmds as kcmc;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
use super::ExecutorContext;
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    execution::{ExecState, ImportedGeometry},
 | 
			
		||||
@ -22,8 +23,6 @@ use crate::{
 | 
			
		||||
    source_range::SourceRange,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::ExecutorContext;
 | 
			
		||||
 | 
			
		||||
// Zoo co-ordinate system.
 | 
			
		||||
//
 | 
			
		||||
// * Forward: -Y
 | 
			
		||||
 | 
			
		||||
@ -581,10 +581,11 @@ impl KclValue {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO called UnitLen so as not to clash with UnitLength in settings)
 | 
			
		||||
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
 | 
			
		||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(tag = "type")]
 | 
			
		||||
pub enum UnitLen {
 | 
			
		||||
    #[default]
 | 
			
		||||
    Mm,
 | 
			
		||||
    Cm,
 | 
			
		||||
    M,
 | 
			
		||||
@ -593,6 +594,19 @@ pub enum UnitLen {
 | 
			
		||||
    Yards,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for UnitLen {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            UnitLen::Mm => write!(f, "mm"),
 | 
			
		||||
            UnitLen::Cm => write!(f, "cm"),
 | 
			
		||||
            UnitLen::M => write!(f, "m"),
 | 
			
		||||
            UnitLen::Inches => write!(f, "in"),
 | 
			
		||||
            UnitLen::Feet => write!(f, "ft"),
 | 
			
		||||
            UnitLen::Yards => write!(f, "yd"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<NumericSuffix> for UnitLen {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
@ -644,6 +658,15 @@ pub enum UnitAngle {
 | 
			
		||||
    Radians,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl std::fmt::Display for UnitAngle {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        match self {
 | 
			
		||||
            UnitAngle::Degrees => write!(f, "deg"),
 | 
			
		||||
            UnitAngle::Radians => write!(f, "rad"),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl TryFrom<NumericSuffix> for UnitAngle {
 | 
			
		||||
    type Error = ();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -13,8 +13,7 @@ use kcmc::{
 | 
			
		||||
    websocket::{ModelingSessionData, OkWebSocketResponseData},
 | 
			
		||||
    ImageFormat, ModelingCmd,
 | 
			
		||||
};
 | 
			
		||||
use kittycad_modeling_cmds::length_unit::LengthUnit;
 | 
			
		||||
use kittycad_modeling_cmds::{self as kcmc, websocket::WebSocketResponse};
 | 
			
		||||
use kittycad_modeling_cmds::{self as kcmc, length_unit::LengthUnit, websocket::WebSocketResponse};
 | 
			
		||||
use parse_display::{Display, FromStr};
 | 
			
		||||
use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@ -27,14 +26,18 @@ pub(crate) use import::{import_foreign, send_to_engine as send_import_to_engine,
 | 
			
		||||
pub use kcl_value::{KclObjectFields, KclValue, UnitAngle, UnitLen};
 | 
			
		||||
use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
mod annotations;
 | 
			
		||||
pub(crate) mod annotations;
 | 
			
		||||
mod artifact;
 | 
			
		||||
pub(crate) mod cache;
 | 
			
		||||
mod cad_op;
 | 
			
		||||
mod exec_ast;
 | 
			
		||||
mod function_param;
 | 
			
		||||
mod import;
 | 
			
		||||
mod kcl_value;
 | 
			
		||||
pub(crate) mod kcl_value;
 | 
			
		||||
 | 
			
		||||
// Re-exports.
 | 
			
		||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
 | 
			
		||||
pub use cad_op::Operation;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    engine::{EngineManager, ExecutionKind},
 | 
			
		||||
@ -52,10 +55,6 @@ use crate::{
 | 
			
		||||
    ExecError, KclErrorWithOutputs, Program,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Re-exports.
 | 
			
		||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
 | 
			
		||||
pub use cad_op::Operation;
 | 
			
		||||
 | 
			
		||||
/// State for executing a program.
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
@ -247,7 +246,7 @@ impl ModuleState {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct MetaSettings {
 | 
			
		||||
@ -256,7 +255,11 @@ pub struct MetaSettings {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl MetaSettings {
 | 
			
		||||
    fn update_from_annotation(&mut self, annotation: &NonCodeValue, source_range: SourceRange) -> Result<(), KclError> {
 | 
			
		||||
    pub fn update_from_annotation(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        annotation: &NonCodeValue,
 | 
			
		||||
        source_range: SourceRange,
 | 
			
		||||
    ) -> Result<(), KclError> {
 | 
			
		||||
        let properties = annotations::expect_properties(annotations::SETTINGS, annotation, source_range)?;
 | 
			
		||||
 | 
			
		||||
        for p in properties {
 | 
			
		||||
 | 
			
		||||
@ -84,7 +84,7 @@ pub use engine::{EngineManager, ExecutionKind};
 | 
			
		||||
pub use errors::{CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs};
 | 
			
		||||
pub use execution::{
 | 
			
		||||
    cache::{CacheInformation, OldAstState},
 | 
			
		||||
    ExecState, ExecutorContext, ExecutorSettings, Point2d,
 | 
			
		||||
    ExecState, ExecutorContext, ExecutorSettings, MetaSettings, Point2d,
 | 
			
		||||
};
 | 
			
		||||
pub use lsp::{
 | 
			
		||||
    copilot::Backend as CopilotLspBackend,
 | 
			
		||||
@ -121,8 +121,7 @@ pub mod std_utils {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub mod pretty {
 | 
			
		||||
    pub use crate::parsing::token::NumericSuffix;
 | 
			
		||||
    pub use crate::unparser::format_number;
 | 
			
		||||
    pub use crate::{parsing::token::NumericSuffix, unparser::format_number};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
@ -162,6 +161,18 @@ impl Program {
 | 
			
		||||
        self.ast.compute_digest()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the meta settings for the kcl file from the annotations.
 | 
			
		||||
    pub fn get_meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
 | 
			
		||||
        self.ast.get_meta_settings()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Change the meta settings for the kcl file.
 | 
			
		||||
    pub fn change_meta_settings(&mut self, settings: crate::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
        Ok(Self {
 | 
			
		||||
            ast: self.ast.change_meta_settings(settings)?,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
 | 
			
		||||
        self.ast.lint_all()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,6 @@ use tower_lsp::lsp_types::{
 | 
			
		||||
    CompletionItem, CompletionItemKind, DocumentSymbol, FoldingRange, FoldingRangeKind, Range as LspRange, SymbolKind,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::digest::Digest;
 | 
			
		||||
pub use crate::parsing::ast::types::{
 | 
			
		||||
    condition::{ElseIf, IfExpression},
 | 
			
		||||
    literal_value::LiteralValue,
 | 
			
		||||
@ -26,7 +25,8 @@ pub use crate::parsing::ast::types::{
 | 
			
		||||
use crate::{
 | 
			
		||||
    docs::StdLibFn,
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    execution::{KclValue, Metadata, TagIdentifier},
 | 
			
		||||
    execution::{annotations, KclValue, Metadata, TagIdentifier},
 | 
			
		||||
    parsing::ast::digest::Digest,
 | 
			
		||||
    parsing::PIPE_OPERATOR,
 | 
			
		||||
    source_range::{ModuleId, SourceRange},
 | 
			
		||||
};
 | 
			
		||||
@ -254,6 +254,52 @@ impl Node<Program> {
 | 
			
		||||
        }
 | 
			
		||||
        Ok(findings)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Get the annotations for the meta settings from the kcl file.
 | 
			
		||||
    pub fn get_meta_settings(&self) -> Result<Option<crate::execution::MetaSettings>, KclError> {
 | 
			
		||||
        let annotations = self
 | 
			
		||||
            .non_code_meta
 | 
			
		||||
            .start_nodes
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter_map(|n| n.annotation().map(|result| (result, n.as_source_range())));
 | 
			
		||||
        for (annotation, source_range) in annotations {
 | 
			
		||||
            if annotation.annotation_name() == Some(annotations::SETTINGS) {
 | 
			
		||||
                let mut meta_settings = crate::execution::MetaSettings::default();
 | 
			
		||||
                meta_settings.update_from_annotation(annotation, source_range)?;
 | 
			
		||||
                return Ok(Some(meta_settings));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(None)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn change_meta_settings(&mut self, settings: crate::execution::MetaSettings) -> Result<Self, KclError> {
 | 
			
		||||
        let mut new_program = self.clone();
 | 
			
		||||
        let mut found = false;
 | 
			
		||||
        for node in &mut new_program.non_code_meta.start_nodes {
 | 
			
		||||
            if let Some(annotation) = node.annotation() {
 | 
			
		||||
                if annotation.annotation_name() == Some(annotations::SETTINGS) {
 | 
			
		||||
                    let annotation = NonCodeValue::new_from_meta_settings(&settings);
 | 
			
		||||
                    *node = Node::no_src(NonCodeNode {
 | 
			
		||||
                        value: annotation,
 | 
			
		||||
                        digest: None,
 | 
			
		||||
                    });
 | 
			
		||||
                    found = true;
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if !found {
 | 
			
		||||
            let annotation = NonCodeValue::new_from_meta_settings(&settings);
 | 
			
		||||
            new_program.non_code_meta.start_nodes.push(Node::no_src(NonCodeNode {
 | 
			
		||||
                value: annotation,
 | 
			
		||||
                digest: None,
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Ok(new_program)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Program {
 | 
			
		||||
@ -1106,6 +1152,24 @@ impl NonCodeValue {
 | 
			
		||||
            _ => None,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn new_from_meta_settings(settings: &crate::execution::MetaSettings) -> NonCodeValue {
 | 
			
		||||
        let mut properties: Vec<Node<ObjectProperty>> = vec![ObjectProperty::new(
 | 
			
		||||
            Identifier::new(annotations::SETTINGS_UNIT_LENGTH),
 | 
			
		||||
            Expr::Identifier(Box::new(Identifier::new(&settings.default_length_units.to_string()))),
 | 
			
		||||
        )];
 | 
			
		||||
 | 
			
		||||
        if settings.default_angle_units != Default::default() {
 | 
			
		||||
            properties.push(ObjectProperty::new(
 | 
			
		||||
                Identifier::new(annotations::SETTINGS_UNIT_ANGLE),
 | 
			
		||||
                Expr::Identifier(Box::new(Identifier::new(&settings.default_angle_units.to_string()))),
 | 
			
		||||
            ));
 | 
			
		||||
        }
 | 
			
		||||
        NonCodeValue::Annotation {
 | 
			
		||||
            name: Identifier::new(annotations::SETTINGS),
 | 
			
		||||
            properties: Some(properties),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
@ -2365,6 +2429,14 @@ impl Node<ObjectProperty> {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ObjectProperty {
 | 
			
		||||
    pub fn new(key: Node<Identifier>, value: Expr) -> Node<Self> {
 | 
			
		||||
        Node::no_src(Self {
 | 
			
		||||
            key,
 | 
			
		||||
            value,
 | 
			
		||||
            digest: None,
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Returns a hover value that includes the given character position.
 | 
			
		||||
    pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
 | 
			
		||||
        let value_source_range: SourceRange = self.value.clone().into();
 | 
			
		||||
@ -3784,4 +3856,98 @@ const cylinder = startSketchOn('-XZ')
 | 
			
		||||
 | 
			
		||||
        assert_eq!(l.raw, "false");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_get_meta_settings_inch() {
 | 
			
		||||
        let some_program_string = r#"@settings(defaultLengthUnit = inch)
 | 
			
		||||
 | 
			
		||||
startSketchOn('XY')"#;
 | 
			
		||||
        let program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let result = program.get_meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_some());
 | 
			
		||||
        let meta_settings = result.unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            meta_settings.default_length_units,
 | 
			
		||||
            crate::execution::kcl_value::UnitLen::Inches
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_get_meta_settings_inch_to_mm() {
 | 
			
		||||
        let some_program_string = r#"@settings(defaultLengthUnit = inch)
 | 
			
		||||
 | 
			
		||||
startSketchOn('XY')"#;
 | 
			
		||||
        let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let result = program.get_meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_some());
 | 
			
		||||
        let meta_settings = result.unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            meta_settings.default_length_units,
 | 
			
		||||
            crate::execution::kcl_value::UnitLen::Inches
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Edit the ast.
 | 
			
		||||
        let new_program = program
 | 
			
		||||
            .change_meta_settings(crate::execution::MetaSettings {
 | 
			
		||||
                default_length_units: crate::execution::kcl_value::UnitLen::Mm,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let result = new_program.get_meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_some());
 | 
			
		||||
        let meta_settings = result.unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            meta_settings.default_length_units,
 | 
			
		||||
            crate::execution::kcl_value::UnitLen::Mm
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let formatted = new_program.recast(&Default::default(), 0);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            formatted,
 | 
			
		||||
            r#"@settings(defaultLengthUnit = mm)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
startSketchOn('XY')
 | 
			
		||||
"#
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn test_parse_get_meta_settings_nothing_to_mm() {
 | 
			
		||||
        let some_program_string = r#"startSketchOn('XY')"#;
 | 
			
		||||
        let mut program = crate::parsing::top_level_parse(some_program_string).unwrap();
 | 
			
		||||
        let result = program.get_meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_none());
 | 
			
		||||
 | 
			
		||||
        // Edit the ast.
 | 
			
		||||
        let new_program = program
 | 
			
		||||
            .change_meta_settings(crate::execution::MetaSettings {
 | 
			
		||||
                default_length_units: crate::execution::kcl_value::UnitLen::Mm,
 | 
			
		||||
                ..Default::default()
 | 
			
		||||
            })
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let result = new_program.get_meta_settings().unwrap();
 | 
			
		||||
        assert!(result.is_some());
 | 
			
		||||
        let meta_settings = result.unwrap();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            meta_settings.default_length_units,
 | 
			
		||||
            crate::execution::kcl_value::UnitLen::Mm
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        let formatted = new_program.recast(&Default::default(), 0);
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            formatted,
 | 
			
		||||
            r#"@settings(defaultLengthUnit = mm)
 | 
			
		||||
startSketchOn('XY')
 | 
			
		||||
"#
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,8 @@ use insta::rounded_redaction;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    exec::ArtifactCommand,
 | 
			
		||||
    execution::{ArtifactGraph, Operation},
 | 
			
		||||
    parsing::ast::types::{Node, Program},
 | 
			
		||||
    source_range::ModuleId,
 | 
			
		||||
};
 | 
			
		||||
@ -109,36 +111,12 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
                    ".environments[].**[].z[]" => rounded_redaction(4),
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            assert_snapshot(test_name, "Operations executed", || {
 | 
			
		||||
                insta::assert_json_snapshot!("ops", exec_state.mod_local.operations);
 | 
			
		||||
            });
 | 
			
		||||
            assert_snapshot(test_name, "Artifact commands", || {
 | 
			
		||||
                insta::assert_json_snapshot!("artifact_commands", exec_state.global.artifact_commands, {
 | 
			
		||||
                    "[].command.segment.*.x" => rounded_redaction(4),
 | 
			
		||||
                    "[].command.segment.*.y" => rounded_redaction(4),
 | 
			
		||||
                    "[].command.segment.*.z" => rounded_redaction(4),
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
            assert_snapshot(test_name, "Artifact graph flowchart", || {
 | 
			
		||||
                let flowchart = exec_state
 | 
			
		||||
                    .global
 | 
			
		||||
                    .artifact_graph
 | 
			
		||||
                    .to_mermaid_flowchart()
 | 
			
		||||
                    .unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
 | 
			
		||||
                // Change the snapshot suffix so that it is rendered as a
 | 
			
		||||
                // Markdown file in GitHub.
 | 
			
		||||
                insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
 | 
			
		||||
            });
 | 
			
		||||
            assert_snapshot(test_name, "Artifact graph mind map", || {
 | 
			
		||||
                let mind_map = exec_state
 | 
			
		||||
                    .global
 | 
			
		||||
                    .artifact_graph
 | 
			
		||||
                    .to_mermaid_mind_map()
 | 
			
		||||
                    .unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
 | 
			
		||||
                // Change the snapshot suffix so that it is rendered as a
 | 
			
		||||
                // Markdown file in GitHub.
 | 
			
		||||
                insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
 | 
			
		||||
            });
 | 
			
		||||
            assert_common_snapshots(
 | 
			
		||||
                test_name,
 | 
			
		||||
                exec_state.mod_local.operations,
 | 
			
		||||
                exec_state.global.artifact_commands,
 | 
			
		||||
                exec_state.global.artifact_graph,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            let ok_path_str = format!("tests/{test_name}/program_memory.snap");
 | 
			
		||||
@ -165,17 +143,12 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
                        insta::assert_snapshot!("execution_error", report);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    assert_snapshot(test_name, "Operations executed", || {
 | 
			
		||||
                        insta::assert_json_snapshot!("ops", error.operations);
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    assert_snapshot(test_name, "Artifact commands", || {
 | 
			
		||||
                        insta::assert_json_snapshot!("artifact_commands", error.artifact_commands, {
 | 
			
		||||
                            "[].command.segment.*.x" => rounded_redaction(4),
 | 
			
		||||
                            "[].command.segment.*.y" => rounded_redaction(4),
 | 
			
		||||
                            "[].command.segment.*.z" => rounded_redaction(4),
 | 
			
		||||
                        });
 | 
			
		||||
                    });
 | 
			
		||||
                    assert_common_snapshots(
 | 
			
		||||
                        test_name,
 | 
			
		||||
                        error.operations,
 | 
			
		||||
                        error.artifact_commands,
 | 
			
		||||
                        error.artifact_graph,
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                e => {
 | 
			
		||||
                    // These kinds of errors aren't expected to occur. We don't
 | 
			
		||||
@ -188,6 +161,42 @@ async fn execute(test_name: &str, render_to_png: bool) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Assert snapshots that should happen both when KCL execution succeeds and
 | 
			
		||||
/// when it results in an error.
 | 
			
		||||
fn assert_common_snapshots(
 | 
			
		||||
    test_name: &str,
 | 
			
		||||
    operations: Vec<Operation>,
 | 
			
		||||
    artifact_commands: Vec<ArtifactCommand>,
 | 
			
		||||
    artifact_graph: ArtifactGraph,
 | 
			
		||||
) {
 | 
			
		||||
    assert_snapshot(test_name, "Operations executed", || {
 | 
			
		||||
        insta::assert_json_snapshot!("ops", operations);
 | 
			
		||||
    });
 | 
			
		||||
    assert_snapshot(test_name, "Artifact commands", || {
 | 
			
		||||
        insta::assert_json_snapshot!("artifact_commands", artifact_commands, {
 | 
			
		||||
            "[].command.segment.*.x" => rounded_redaction(4),
 | 
			
		||||
            "[].command.segment.*.y" => rounded_redaction(4),
 | 
			
		||||
            "[].command.segment.*.z" => rounded_redaction(4),
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    assert_snapshot(test_name, "Artifact graph flowchart", || {
 | 
			
		||||
        let flowchart = artifact_graph
 | 
			
		||||
            .to_mermaid_flowchart()
 | 
			
		||||
            .unwrap_or_else(|e| format!("Failed to convert artifact graph to flowchart: {e}"));
 | 
			
		||||
        // Change the snapshot suffix so that it is rendered as a Markdown file
 | 
			
		||||
        // in GitHub.
 | 
			
		||||
        insta::assert_binary_snapshot!("artifact_graph_flowchart.md", flowchart.as_bytes().to_owned());
 | 
			
		||||
    });
 | 
			
		||||
    assert_snapshot(test_name, "Artifact graph mind map", || {
 | 
			
		||||
        let mind_map = artifact_graph
 | 
			
		||||
            .to_mermaid_mind_map()
 | 
			
		||||
            .unwrap_or_else(|e| format!("Failed to convert artifact graph to mind map: {e}"));
 | 
			
		||||
        // Change the snapshot suffix so that it is rendered as a Markdown file
 | 
			
		||||
        // in GitHub.
 | 
			
		||||
        insta::assert_binary_snapshot!("artifact_graph_mind_map.md", mind_map.as_bytes().to_owned());
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
mod cube {
 | 
			
		||||
    const TEST_NAME: &str = "cube";
 | 
			
		||||
 | 
			
		||||
@ -209,6 +218,27 @@ mod cube {
 | 
			
		||||
        super::execute(TEST_NAME, true).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
mod cube_with_error {
 | 
			
		||||
    const TEST_NAME: &str = "cube_with_error";
 | 
			
		||||
 | 
			
		||||
    /// Test parsing KCL.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn parse() {
 | 
			
		||||
        super::parse(TEST_NAME)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that parsing and unparsing KCL produces the original KCL input.
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn unparse() {
 | 
			
		||||
        super::unparse(TEST_NAME)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Test that KCL is executed correctly.
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread")]
 | 
			
		||||
    async fn kcl_test_execute() {
 | 
			
		||||
        super::execute(TEST_NAME, true).await
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
mod artifact_graph_example_code1 {
 | 
			
		||||
    const TEST_NAME: &str = "artifact_graph_example_code1";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -3,14 +3,13 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use derive_docs::stdlib;
 | 
			
		||||
 | 
			
		||||
use super::args::FromArgs;
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    execution::{ExecState, KclValue},
 | 
			
		||||
    std::Args,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::args::FromArgs;
 | 
			
		||||
 | 
			
		||||
/// Compute the remainder after dividing `num` by `div`.
 | 
			
		||||
/// If `num` is negative, the result will be too.
 | 
			
		||||
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
 | 
			
		||||
@ -11,12 +11,11 @@ use parse_display::{Display, FromStr};
 | 
			
		||||
use schemars::JsonSchema;
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
use crate::execution::{Artifact, ArtifactId};
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    execution::{
 | 
			
		||||
        BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
 | 
			
		||||
        Solid, TagEngineInfo, TagIdentifier,
 | 
			
		||||
        Artifact, ArtifactId, BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch,
 | 
			
		||||
        SketchSet, SketchSurface, Solid, TagEngineInfo, TagIdentifier,
 | 
			
		||||
    },
 | 
			
		||||
    parsing::ast::types::TagNode,
 | 
			
		||||
    std::{
 | 
			
		||||
@ -2302,7 +2301,10 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
    use pretty_assertions::assert_eq;
 | 
			
		||||
 | 
			
		||||
    use crate::{execution::TagIdentifier, std::sketch::PlaneData, std::utils::calculate_circle_center};
 | 
			
		||||
    use crate::{
 | 
			
		||||
        execution::TagIdentifier,
 | 
			
		||||
        std::{sketch::PlaneData, utils::calculate_circle_center},
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_deserialize_plane_data() {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart argument_error.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map argument_error.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart array_elem_pop_empty_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map array_elem_pop_empty_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart array_elem_pop_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map array_elem_pop_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart array_elem_push_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map array_elem_push_fail.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart array_index_oob.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map array_index_oob.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart comparisons_multiple.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map comparisons_multiple.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										538
									
								
								src/wasm-lib/kcl/tests/cube_with_error/artifact_commands.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,538 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact commands cube_with_error.kcl
 | 
			
		||||
---
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.7,
 | 
			
		||||
        "g": 0.28,
 | 
			
		||||
        "b": 0.28,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.28,
 | 
			
		||||
        "g": 0.7,
 | 
			
		||||
        "b": 0.28,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "plane_set_color",
 | 
			
		||||
      "plane_id": "[uuid]",
 | 
			
		||||
      "color": {
 | 
			
		||||
        "r": 0.28,
 | 
			
		||||
        "g": 0.28,
 | 
			
		||||
        "b": 0.7,
 | 
			
		||||
        "a": 0.4
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": -1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": -1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": -1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 100.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "edge_lines_visible",
 | 
			
		||||
      "hidden": false
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "set_scene_units",
 | 
			
		||||
      "unit": "mm"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      0,
 | 
			
		||||
      0,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_visible",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "hidden": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      177,
 | 
			
		||||
      194,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "make_plane",
 | 
			
		||||
      "origin": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "x_axis": {
 | 
			
		||||
        "x": 1.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "y_axis": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 1.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      },
 | 
			
		||||
      "size": 60.0,
 | 
			
		||||
      "clobber": false,
 | 
			
		||||
      "hide": true
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      177,
 | 
			
		||||
      194,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "enable_sketch_mode",
 | 
			
		||||
      "entity_id": "[uuid]",
 | 
			
		||||
      "ortho": false,
 | 
			
		||||
      "animated": false,
 | 
			
		||||
      "adjust_camera": false,
 | 
			
		||||
      "planar_normal": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      177,
 | 
			
		||||
      194,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "start_path"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      177,
 | 
			
		||||
      194,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "move_path_pen",
 | 
			
		||||
      "path": "[uuid]",
 | 
			
		||||
      "to": {
 | 
			
		||||
        "x": -20.0,
 | 
			
		||||
        "y": -20.0,
 | 
			
		||||
        "z": 0.0
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      202,
 | 
			
		||||
      215,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "extend_path",
 | 
			
		||||
      "path": "[uuid]",
 | 
			
		||||
      "segment": {
 | 
			
		||||
        "type": "line",
 | 
			
		||||
        "end": {
 | 
			
		||||
          "x": -20.0,
 | 
			
		||||
          "y": 20.0,
 | 
			
		||||
          "z": 0.0
 | 
			
		||||
        },
 | 
			
		||||
        "relative": false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      223,
 | 
			
		||||
      236,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "extend_path",
 | 
			
		||||
      "path": "[uuid]",
 | 
			
		||||
      "segment": {
 | 
			
		||||
        "type": "line",
 | 
			
		||||
        "end": {
 | 
			
		||||
          "x": 20.0,
 | 
			
		||||
          "y": 20.0,
 | 
			
		||||
          "z": 0.0
 | 
			
		||||
        },
 | 
			
		||||
        "relative": false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      244,
 | 
			
		||||
      257,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "extend_path",
 | 
			
		||||
      "path": "[uuid]",
 | 
			
		||||
      "segment": {
 | 
			
		||||
        "type": "line",
 | 
			
		||||
        "end": {
 | 
			
		||||
          "x": 20.0,
 | 
			
		||||
          "y": -20.0,
 | 
			
		||||
          "z": 0.0
 | 
			
		||||
        },
 | 
			
		||||
        "relative": false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      265,
 | 
			
		||||
      278,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "extend_path",
 | 
			
		||||
      "path": "[uuid]",
 | 
			
		||||
      "segment": {
 | 
			
		||||
        "type": "line",
 | 
			
		||||
        "end": {
 | 
			
		||||
          "x": -20.0,
 | 
			
		||||
          "y": -20.0,
 | 
			
		||||
          "z": 0.0
 | 
			
		||||
        },
 | 
			
		||||
        "relative": false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      286,
 | 
			
		||||
      294,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "close_path",
 | 
			
		||||
      "path_id": "[uuid]"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      286,
 | 
			
		||||
      294,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "sketch_mode_disable"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "enable_sketch_mode",
 | 
			
		||||
      "entity_id": "[uuid]",
 | 
			
		||||
      "ortho": false,
 | 
			
		||||
      "animated": false,
 | 
			
		||||
      "adjust_camera": false,
 | 
			
		||||
      "planar_normal": {
 | 
			
		||||
        "x": 0.0,
 | 
			
		||||
        "y": 0.0,
 | 
			
		||||
        "z": 1.0
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "extrude",
 | 
			
		||||
      "target": "[uuid]",
 | 
			
		||||
      "distance": 40.0,
 | 
			
		||||
      "faces": null
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "sketch_mode_disable"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "object_bring_to_front",
 | 
			
		||||
      "object_id": "[uuid]"
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "cmdId": "[uuid]",
 | 
			
		||||
    "range": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "command": {
 | 
			
		||||
      "type": "solid3d_get_extrusion_face_info",
 | 
			
		||||
      "object_id": "[uuid]",
 | 
			
		||||
      "edge_id": "[uuid]"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart cube_with_error.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,38 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
  subgraph path2 [Path]
 | 
			
		||||
    2["Path<br>[177, 194, 0]"]
 | 
			
		||||
    3["Segment<br>[202, 215, 0]"]
 | 
			
		||||
    4["Segment<br>[223, 236, 0]"]
 | 
			
		||||
    5["Segment<br>[244, 257, 0]"]
 | 
			
		||||
    6["Segment<br>[265, 278, 0]"]
 | 
			
		||||
    7["Segment<br>[286, 294, 0]"]
 | 
			
		||||
    8[Solid2d]
 | 
			
		||||
  end
 | 
			
		||||
  1["Plane<br>[177, 194, 0]"]
 | 
			
		||||
  9["Sweep Extrusion<br>[302, 320, 0]"]
 | 
			
		||||
  10[Wall]
 | 
			
		||||
  11[Wall]
 | 
			
		||||
  12[Wall]
 | 
			
		||||
  13[Wall]
 | 
			
		||||
  14["Cap Start"]
 | 
			
		||||
  15["Cap End"]
 | 
			
		||||
  1 --- 2
 | 
			
		||||
  2 --- 3
 | 
			
		||||
  2 --- 4
 | 
			
		||||
  2 --- 5
 | 
			
		||||
  2 --- 6
 | 
			
		||||
  2 --- 7
 | 
			
		||||
  2 ---- 9
 | 
			
		||||
  2 --- 8
 | 
			
		||||
  3 --- 13
 | 
			
		||||
  4 --- 12
 | 
			
		||||
  5 --- 11
 | 
			
		||||
  6 --- 10
 | 
			
		||||
  9 --- 10
 | 
			
		||||
  9 --- 11
 | 
			
		||||
  9 --- 12
 | 
			
		||||
  9 --- 13
 | 
			
		||||
  9 --- 14
 | 
			
		||||
  9 --- 15
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map cube_with_error.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,23 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
    Plane
 | 
			
		||||
      Path
 | 
			
		||||
        Segment
 | 
			
		||||
          Wall
 | 
			
		||||
        Segment
 | 
			
		||||
          Wall
 | 
			
		||||
        Segment
 | 
			
		||||
          Wall
 | 
			
		||||
        Segment
 | 
			
		||||
          Wall
 | 
			
		||||
        Segment
 | 
			
		||||
        Sweep Extrusion
 | 
			
		||||
          Wall
 | 
			
		||||
          Wall
 | 
			
		||||
          Wall
 | 
			
		||||
          Wall
 | 
			
		||||
          Cap Start
 | 
			
		||||
          Cap End
 | 
			
		||||
        Solid2d
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										809
									
								
								src/wasm-lib/kcl/tests/cube_with_error/ast.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,809 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Result of parsing cube_with_error.kcl
 | 
			
		||||
---
 | 
			
		||||
{
 | 
			
		||||
  "Ok": {
 | 
			
		||||
    "body": [
 | 
			
		||||
      {
 | 
			
		||||
        "declaration": {
 | 
			
		||||
          "end": 322,
 | 
			
		||||
          "id": {
 | 
			
		||||
            "end": 7,
 | 
			
		||||
            "name": "cube",
 | 
			
		||||
            "start": 3,
 | 
			
		||||
            "type": "Identifier"
 | 
			
		||||
          },
 | 
			
		||||
          "init": {
 | 
			
		||||
            "body": {
 | 
			
		||||
              "body": [
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 42,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 29,
 | 
			
		||||
                      "name": "l",
 | 
			
		||||
                      "start": 28,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "end": 42,
 | 
			
		||||
                      "left": {
 | 
			
		||||
                        "end": 38,
 | 
			
		||||
                        "name": "length",
 | 
			
		||||
                        "start": 32,
 | 
			
		||||
                        "type": "Identifier",
 | 
			
		||||
                        "type": "Identifier"
 | 
			
		||||
                      },
 | 
			
		||||
                      "operator": "/",
 | 
			
		||||
                      "right": {
 | 
			
		||||
                        "end": 42,
 | 
			
		||||
                        "raw": "2",
 | 
			
		||||
                        "start": 41,
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "value": {
 | 
			
		||||
                          "value": 2.0,
 | 
			
		||||
                          "suffix": "None"
 | 
			
		||||
                        }
 | 
			
		||||
                      },
 | 
			
		||||
                      "start": 32,
 | 
			
		||||
                      "type": "BinaryExpression",
 | 
			
		||||
                      "type": "BinaryExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 28,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 42,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 28,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 58,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 46,
 | 
			
		||||
                      "name": "x",
 | 
			
		||||
                      "start": 45,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "computed": false,
 | 
			
		||||
                      "end": 58,
 | 
			
		||||
                      "object": {
 | 
			
		||||
                        "end": 55,
 | 
			
		||||
                        "name": "center",
 | 
			
		||||
                        "start": 49,
 | 
			
		||||
                        "type": "Identifier",
 | 
			
		||||
                        "type": "Identifier"
 | 
			
		||||
                      },
 | 
			
		||||
                      "property": {
 | 
			
		||||
                        "end": 57,
 | 
			
		||||
                        "raw": "0",
 | 
			
		||||
                        "start": 56,
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "value": {
 | 
			
		||||
                          "value": 0.0,
 | 
			
		||||
                          "suffix": "None"
 | 
			
		||||
                        }
 | 
			
		||||
                      },
 | 
			
		||||
                      "start": 49,
 | 
			
		||||
                      "type": "MemberExpression",
 | 
			
		||||
                      "type": "MemberExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 45,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 58,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 45,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 74,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 62,
 | 
			
		||||
                      "name": "y",
 | 
			
		||||
                      "start": 61,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "computed": false,
 | 
			
		||||
                      "end": 74,
 | 
			
		||||
                      "object": {
 | 
			
		||||
                        "end": 71,
 | 
			
		||||
                        "name": "center",
 | 
			
		||||
                        "start": 65,
 | 
			
		||||
                        "type": "Identifier",
 | 
			
		||||
                        "type": "Identifier"
 | 
			
		||||
                      },
 | 
			
		||||
                      "property": {
 | 
			
		||||
                        "end": 73,
 | 
			
		||||
                        "raw": "1",
 | 
			
		||||
                        "start": 72,
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "type": "Literal",
 | 
			
		||||
                        "value": {
 | 
			
		||||
                          "value": 1.0,
 | 
			
		||||
                          "suffix": "None"
 | 
			
		||||
                        }
 | 
			
		||||
                      },
 | 
			
		||||
                      "start": 65,
 | 
			
		||||
                      "type": "MemberExpression",
 | 
			
		||||
                      "type": "MemberExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 61,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 74,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 61,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 98,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 79,
 | 
			
		||||
                      "name": "p0",
 | 
			
		||||
                      "start": 77,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "elements": [
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 89,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "argument": {
 | 
			
		||||
                              "end": 85,
 | 
			
		||||
                              "name": "l",
 | 
			
		||||
                              "start": 84,
 | 
			
		||||
                              "type": "Identifier",
 | 
			
		||||
                              "type": "Identifier"
 | 
			
		||||
                            },
 | 
			
		||||
                            "end": 85,
 | 
			
		||||
                            "operator": "-",
 | 
			
		||||
                            "start": 83,
 | 
			
		||||
                            "type": "UnaryExpression",
 | 
			
		||||
                            "type": "UnaryExpression"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 89,
 | 
			
		||||
                            "name": "x",
 | 
			
		||||
                            "start": 88,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 83,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 97,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "argument": {
 | 
			
		||||
                              "end": 93,
 | 
			
		||||
                              "name": "l",
 | 
			
		||||
                              "start": 92,
 | 
			
		||||
                              "type": "Identifier",
 | 
			
		||||
                              "type": "Identifier"
 | 
			
		||||
                            },
 | 
			
		||||
                            "end": 93,
 | 
			
		||||
                            "operator": "-",
 | 
			
		||||
                            "start": 91,
 | 
			
		||||
                            "type": "UnaryExpression",
 | 
			
		||||
                            "type": "UnaryExpression"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 97,
 | 
			
		||||
                            "name": "y",
 | 
			
		||||
                            "start": 96,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 91,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        }
 | 
			
		||||
                      ],
 | 
			
		||||
                      "end": 98,
 | 
			
		||||
                      "start": 82,
 | 
			
		||||
                      "type": "ArrayExpression",
 | 
			
		||||
                      "type": "ArrayExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 77,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 98,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 77,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 121,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 103,
 | 
			
		||||
                      "name": "p1",
 | 
			
		||||
                      "start": 101,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "elements": [
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 113,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "argument": {
 | 
			
		||||
                              "end": 109,
 | 
			
		||||
                              "name": "l",
 | 
			
		||||
                              "start": 108,
 | 
			
		||||
                              "type": "Identifier",
 | 
			
		||||
                              "type": "Identifier"
 | 
			
		||||
                            },
 | 
			
		||||
                            "end": 109,
 | 
			
		||||
                            "operator": "-",
 | 
			
		||||
                            "start": 107,
 | 
			
		||||
                            "type": "UnaryExpression",
 | 
			
		||||
                            "type": "UnaryExpression"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 113,
 | 
			
		||||
                            "name": "x",
 | 
			
		||||
                            "start": 112,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 107,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 120,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "end": 116,
 | 
			
		||||
                            "name": "l",
 | 
			
		||||
                            "start": 115,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 120,
 | 
			
		||||
                            "name": "y",
 | 
			
		||||
                            "start": 119,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 115,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        }
 | 
			
		||||
                      ],
 | 
			
		||||
                      "end": 121,
 | 
			
		||||
                      "start": 106,
 | 
			
		||||
                      "type": "ArrayExpression",
 | 
			
		||||
                      "type": "ArrayExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 101,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 121,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 101,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 143,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 126,
 | 
			
		||||
                      "name": "p2",
 | 
			
		||||
                      "start": 124,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "elements": [
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 135,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "end": 131,
 | 
			
		||||
                            "name": "l",
 | 
			
		||||
                            "start": 130,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 135,
 | 
			
		||||
                            "name": "x",
 | 
			
		||||
                            "start": 134,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 130,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 142,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "end": 138,
 | 
			
		||||
                            "name": "l",
 | 
			
		||||
                            "start": 137,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 142,
 | 
			
		||||
                            "name": "y",
 | 
			
		||||
                            "start": 141,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 137,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        }
 | 
			
		||||
                      ],
 | 
			
		||||
                      "end": 143,
 | 
			
		||||
                      "start": 129,
 | 
			
		||||
                      "type": "ArrayExpression",
 | 
			
		||||
                      "type": "ArrayExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 124,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 143,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 124,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "declaration": {
 | 
			
		||||
                    "end": 166,
 | 
			
		||||
                    "id": {
 | 
			
		||||
                      "end": 148,
 | 
			
		||||
                      "name": "p3",
 | 
			
		||||
                      "start": 146,
 | 
			
		||||
                      "type": "Identifier"
 | 
			
		||||
                    },
 | 
			
		||||
                    "init": {
 | 
			
		||||
                      "elements": [
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 157,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "end": 153,
 | 
			
		||||
                            "name": "l",
 | 
			
		||||
                            "start": 152,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 157,
 | 
			
		||||
                            "name": "x",
 | 
			
		||||
                            "start": 156,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 152,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        },
 | 
			
		||||
                        {
 | 
			
		||||
                          "end": 165,
 | 
			
		||||
                          "left": {
 | 
			
		||||
                            "argument": {
 | 
			
		||||
                              "end": 161,
 | 
			
		||||
                              "name": "l",
 | 
			
		||||
                              "start": 160,
 | 
			
		||||
                              "type": "Identifier",
 | 
			
		||||
                              "type": "Identifier"
 | 
			
		||||
                            },
 | 
			
		||||
                            "end": 161,
 | 
			
		||||
                            "operator": "-",
 | 
			
		||||
                            "start": 159,
 | 
			
		||||
                            "type": "UnaryExpression",
 | 
			
		||||
                            "type": "UnaryExpression"
 | 
			
		||||
                          },
 | 
			
		||||
                          "operator": "+",
 | 
			
		||||
                          "right": {
 | 
			
		||||
                            "end": 165,
 | 
			
		||||
                            "name": "y",
 | 
			
		||||
                            "start": 164,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          "start": 159,
 | 
			
		||||
                          "type": "BinaryExpression",
 | 
			
		||||
                          "type": "BinaryExpression"
 | 
			
		||||
                        }
 | 
			
		||||
                      ],
 | 
			
		||||
                      "end": 166,
 | 
			
		||||
                      "start": 151,
 | 
			
		||||
                      "type": "ArrayExpression",
 | 
			
		||||
                      "type": "ArrayExpression"
 | 
			
		||||
                    },
 | 
			
		||||
                    "start": 146,
 | 
			
		||||
                    "type": "VariableDeclarator"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 166,
 | 
			
		||||
                  "kind": "const",
 | 
			
		||||
                  "start": 146,
 | 
			
		||||
                  "type": "VariableDeclaration",
 | 
			
		||||
                  "type": "VariableDeclaration"
 | 
			
		||||
                },
 | 
			
		||||
                {
 | 
			
		||||
                  "argument": {
 | 
			
		||||
                    "body": [
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 193,
 | 
			
		||||
                            "name": "p0",
 | 
			
		||||
                            "start": 191,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 190,
 | 
			
		||||
                          "name": "startSketchAt",
 | 
			
		||||
                          "start": 177,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 194,
 | 
			
		||||
                        "start": 177,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 211,
 | 
			
		||||
                            "name": "p1",
 | 
			
		||||
                            "start": 209,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 214,
 | 
			
		||||
                            "start": 213,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 208,
 | 
			
		||||
                          "name": "lineTo",
 | 
			
		||||
                          "start": 202,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 215,
 | 
			
		||||
                        "start": 202,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 232,
 | 
			
		||||
                            "name": "p2",
 | 
			
		||||
                            "start": 230,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 235,
 | 
			
		||||
                            "start": 234,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 229,
 | 
			
		||||
                          "name": "lineTo",
 | 
			
		||||
                          "start": 223,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 236,
 | 
			
		||||
                        "start": 223,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 253,
 | 
			
		||||
                            "name": "p3",
 | 
			
		||||
                            "start": 251,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 256,
 | 
			
		||||
                            "start": 255,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 250,
 | 
			
		||||
                          "name": "lineTo",
 | 
			
		||||
                          "start": 244,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 257,
 | 
			
		||||
                        "start": 244,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 274,
 | 
			
		||||
                            "name": "p0",
 | 
			
		||||
                            "start": 272,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 277,
 | 
			
		||||
                            "start": 276,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 271,
 | 
			
		||||
                          "name": "lineTo",
 | 
			
		||||
                          "start": 265,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 278,
 | 
			
		||||
                        "start": 265,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 293,
 | 
			
		||||
                            "start": 292,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 291,
 | 
			
		||||
                          "name": "close",
 | 
			
		||||
                          "start": 286,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 294,
 | 
			
		||||
                        "start": 286,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      },
 | 
			
		||||
                      {
 | 
			
		||||
                        "arguments": [
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 316,
 | 
			
		||||
                            "name": "length",
 | 
			
		||||
                            "start": 310,
 | 
			
		||||
                            "type": "Identifier",
 | 
			
		||||
                            "type": "Identifier"
 | 
			
		||||
                          },
 | 
			
		||||
                          {
 | 
			
		||||
                            "end": 319,
 | 
			
		||||
                            "start": 318,
 | 
			
		||||
                            "type": "PipeSubstitution",
 | 
			
		||||
                            "type": "PipeSubstitution"
 | 
			
		||||
                          }
 | 
			
		||||
                        ],
 | 
			
		||||
                        "callee": {
 | 
			
		||||
                          "end": 309,
 | 
			
		||||
                          "name": "extrude",
 | 
			
		||||
                          "start": 302,
 | 
			
		||||
                          "type": "Identifier"
 | 
			
		||||
                        },
 | 
			
		||||
                        "end": 320,
 | 
			
		||||
                        "start": 302,
 | 
			
		||||
                        "type": "CallExpression",
 | 
			
		||||
                        "type": "CallExpression"
 | 
			
		||||
                      }
 | 
			
		||||
                    ],
 | 
			
		||||
                    "end": 320,
 | 
			
		||||
                    "start": 177,
 | 
			
		||||
                    "type": "PipeExpression",
 | 
			
		||||
                    "type": "PipeExpression"
 | 
			
		||||
                  },
 | 
			
		||||
                  "end": 320,
 | 
			
		||||
                  "start": 170,
 | 
			
		||||
                  "type": "ReturnStatement",
 | 
			
		||||
                  "type": "ReturnStatement"
 | 
			
		||||
                }
 | 
			
		||||
              ],
 | 
			
		||||
              "end": 322,
 | 
			
		||||
              "nonCodeMeta": {
 | 
			
		||||
                "nonCodeNodes": {
 | 
			
		||||
                  "6": [
 | 
			
		||||
                    {
 | 
			
		||||
                      "end": 170,
 | 
			
		||||
                      "start": 166,
 | 
			
		||||
                      "type": "NonCodeNode",
 | 
			
		||||
                      "value": {
 | 
			
		||||
                        "type": "newLine"
 | 
			
		||||
                      }
 | 
			
		||||
                    }
 | 
			
		||||
                  ]
 | 
			
		||||
                },
 | 
			
		||||
                "startNodes": []
 | 
			
		||||
              },
 | 
			
		||||
              "start": 24
 | 
			
		||||
            },
 | 
			
		||||
            "end": 322,
 | 
			
		||||
            "params": [
 | 
			
		||||
              {
 | 
			
		||||
                "type": "Parameter",
 | 
			
		||||
                "identifier": {
 | 
			
		||||
                  "end": 14,
 | 
			
		||||
                  "name": "length",
 | 
			
		||||
                  "start": 8,
 | 
			
		||||
                  "type": "Identifier"
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                "type": "Parameter",
 | 
			
		||||
                "identifier": {
 | 
			
		||||
                  "end": 22,
 | 
			
		||||
                  "name": "center",
 | 
			
		||||
                  "start": 16,
 | 
			
		||||
                  "type": "Identifier"
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
            ],
 | 
			
		||||
            "start": 7,
 | 
			
		||||
            "type": "FunctionExpression",
 | 
			
		||||
            "type": "FunctionExpression"
 | 
			
		||||
          },
 | 
			
		||||
          "start": 3,
 | 
			
		||||
          "type": "VariableDeclarator"
 | 
			
		||||
        },
 | 
			
		||||
        "end": 322,
 | 
			
		||||
        "kind": "fn",
 | 
			
		||||
        "start": 0,
 | 
			
		||||
        "type": "VariableDeclaration",
 | 
			
		||||
        "type": "VariableDeclaration"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "declaration": {
 | 
			
		||||
          "end": 349,
 | 
			
		||||
          "id": {
 | 
			
		||||
            "end": 330,
 | 
			
		||||
            "name": "myCube",
 | 
			
		||||
            "start": 324,
 | 
			
		||||
            "type": "Identifier"
 | 
			
		||||
          },
 | 
			
		||||
          "init": {
 | 
			
		||||
            "arguments": [
 | 
			
		||||
              {
 | 
			
		||||
                "end": 340,
 | 
			
		||||
                "raw": "40",
 | 
			
		||||
                "start": 338,
 | 
			
		||||
                "type": "Literal",
 | 
			
		||||
                "type": "Literal",
 | 
			
		||||
                "value": {
 | 
			
		||||
                  "value": 40.0,
 | 
			
		||||
                  "suffix": "None"
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
              {
 | 
			
		||||
                "elements": [
 | 
			
		||||
                  {
 | 
			
		||||
                    "end": 344,
 | 
			
		||||
                    "raw": "0",
 | 
			
		||||
                    "start": 343,
 | 
			
		||||
                    "type": "Literal",
 | 
			
		||||
                    "type": "Literal",
 | 
			
		||||
                    "value": {
 | 
			
		||||
                      "value": 0.0,
 | 
			
		||||
                      "suffix": "None"
 | 
			
		||||
                    }
 | 
			
		||||
                  },
 | 
			
		||||
                  {
 | 
			
		||||
                    "end": 347,
 | 
			
		||||
                    "raw": "0",
 | 
			
		||||
                    "start": 346,
 | 
			
		||||
                    "type": "Literal",
 | 
			
		||||
                    "type": "Literal",
 | 
			
		||||
                    "value": {
 | 
			
		||||
                      "value": 0.0,
 | 
			
		||||
                      "suffix": "None"
 | 
			
		||||
                    }
 | 
			
		||||
                  }
 | 
			
		||||
                ],
 | 
			
		||||
                "end": 348,
 | 
			
		||||
                "start": 342,
 | 
			
		||||
                "type": "ArrayExpression",
 | 
			
		||||
                "type": "ArrayExpression"
 | 
			
		||||
              }
 | 
			
		||||
            ],
 | 
			
		||||
            "callee": {
 | 
			
		||||
              "end": 337,
 | 
			
		||||
              "name": "cube",
 | 
			
		||||
              "start": 333,
 | 
			
		||||
              "type": "Identifier"
 | 
			
		||||
            },
 | 
			
		||||
            "end": 349,
 | 
			
		||||
            "start": 333,
 | 
			
		||||
            "type": "CallExpression",
 | 
			
		||||
            "type": "CallExpression"
 | 
			
		||||
          },
 | 
			
		||||
          "start": 324,
 | 
			
		||||
          "type": "VariableDeclarator"
 | 
			
		||||
        },
 | 
			
		||||
        "end": 349,
 | 
			
		||||
        "kind": "const",
 | 
			
		||||
        "start": 324,
 | 
			
		||||
        "type": "VariableDeclaration",
 | 
			
		||||
        "type": "VariableDeclaration"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "end": 398,
 | 
			
		||||
        "expression": {
 | 
			
		||||
          "end": 398,
 | 
			
		||||
          "name": "foo",
 | 
			
		||||
          "start": 395,
 | 
			
		||||
          "type": "Identifier",
 | 
			
		||||
          "type": "Identifier"
 | 
			
		||||
        },
 | 
			
		||||
        "start": 395,
 | 
			
		||||
        "type": "ExpressionStatement",
 | 
			
		||||
        "type": "ExpressionStatement"
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "end": 399,
 | 
			
		||||
    "nonCodeMeta": {
 | 
			
		||||
      "nonCodeNodes": {
 | 
			
		||||
        "0": [
 | 
			
		||||
          {
 | 
			
		||||
            "end": 324,
 | 
			
		||||
            "start": 322,
 | 
			
		||||
            "type": "NonCodeNode",
 | 
			
		||||
            "value": {
 | 
			
		||||
              "type": "newLine"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ],
 | 
			
		||||
        "1": [
 | 
			
		||||
          {
 | 
			
		||||
            "end": 394,
 | 
			
		||||
            "start": 349,
 | 
			
		||||
            "type": "NonCodeNode",
 | 
			
		||||
            "value": {
 | 
			
		||||
              "type": "newLineBlockComment",
 | 
			
		||||
              "value": "Error, after creating meaningful output.",
 | 
			
		||||
              "style": "line"
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "startNodes": []
 | 
			
		||||
    },
 | 
			
		||||
    "start": 0
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								src/wasm-lib/kcl/tests/cube_with_error/execution_error.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,12 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Error from executing cube_with_error.kcl
 | 
			
		||||
---
 | 
			
		||||
KCL UndefinedValue error
 | 
			
		||||
 | 
			
		||||
  × undefined value: memory item key `foo` is not defined
 | 
			
		||||
    ╭─[22:1]
 | 
			
		||||
 21 │ // Error, after creating meaningful output.
 | 
			
		||||
 22 │ foo
 | 
			
		||||
    · ───
 | 
			
		||||
    ╰────
 | 
			
		||||
							
								
								
									
										22
									
								
								src/wasm-lib/kcl/tests/cube_with_error/input.kcl
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,22 @@
 | 
			
		||||
fn cube(length, center) {
 | 
			
		||||
  l = length / 2
 | 
			
		||||
  x = center[0]
 | 
			
		||||
  y = center[1]
 | 
			
		||||
  p0 = [-l + x, -l + y]
 | 
			
		||||
  p1 = [-l + x, l + y]
 | 
			
		||||
  p2 = [l + x, l + y]
 | 
			
		||||
  p3 = [l + x, -l + y]
 | 
			
		||||
 | 
			
		||||
  return startSketchAt(p0)
 | 
			
		||||
    |> lineTo(p1, %)
 | 
			
		||||
    |> lineTo(p2, %)
 | 
			
		||||
    |> lineTo(p3, %)
 | 
			
		||||
    |> lineTo(p0, %)
 | 
			
		||||
    |> close(%)
 | 
			
		||||
    |> extrude(length, %)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
myCube = cube(40, [0, 0])
 | 
			
		||||
 | 
			
		||||
// Error, after creating meaningful output.
 | 
			
		||||
foo
 | 
			
		||||
							
								
								
									
										51
									
								
								src/wasm-lib/kcl/tests/cube_with_error/ops.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,51 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Operations executed cube_with_error.kcl
 | 
			
		||||
---
 | 
			
		||||
[
 | 
			
		||||
  {
 | 
			
		||||
    "type": "UserDefinedFunctionCall",
 | 
			
		||||
    "name": "cube",
 | 
			
		||||
    "functionSourceRange": [
 | 
			
		||||
      7,
 | 
			
		||||
      322,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "unlabeledArg": null,
 | 
			
		||||
    "labeledArgs": {},
 | 
			
		||||
    "sourceRange": [
 | 
			
		||||
      333,
 | 
			
		||||
      349,
 | 
			
		||||
      0
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "labeledArgs": {
 | 
			
		||||
      "length": {
 | 
			
		||||
        "sourceRange": [
 | 
			
		||||
          310,
 | 
			
		||||
          316,
 | 
			
		||||
          0
 | 
			
		||||
        ]
 | 
			
		||||
      },
 | 
			
		||||
      "sketch_set": {
 | 
			
		||||
        "sourceRange": [
 | 
			
		||||
          318,
 | 
			
		||||
          319,
 | 
			
		||||
          0
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "name": "extrude",
 | 
			
		||||
    "sourceRange": [
 | 
			
		||||
      302,
 | 
			
		||||
      320,
 | 
			
		||||
      0
 | 
			
		||||
    ],
 | 
			
		||||
    "type": "StdLibCall",
 | 
			
		||||
    "unlabeledArg": null
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    "type": "UserDefinedFunctionReturn"
 | 
			
		||||
  }
 | 
			
		||||
]
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart import_cycle1.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph mind map import_cycle1.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,4 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
mindmap
 | 
			
		||||
  root
 | 
			
		||||
```
 | 
			
		||||
@ -0,0 +1,6 @@
 | 
			
		||||
---
 | 
			
		||||
source: kcl/src/simulation_tests.rs
 | 
			
		||||
description: Artifact graph flowchart invalid_index_fractional.kcl
 | 
			
		||||
extension: md
 | 
			
		||||
snapshot_kind: binary
 | 
			
		||||
---
 | 
			
		||||
@ -0,0 +1,3 @@
 | 
			
		||||
```mermaid
 | 
			
		||||
flowchart LR
 | 
			
		||||
```
 | 
			
		||||