Change artifact IDs to be stable across KCL executions (#4101)
* Add ID generator to ExecState
* Change default plane IDs to be hardcoded
* Fix lint warning
* Add exposing ID generator as output of executor
* Change to use generated definition of ExecState in TS
* Fix IdGenerator to use camel case in TS
* Fix TS type errors
* Add exposing id_generator parameter
* Add using the previously generated ID generator
* wip: Add display of feature tree in debug pane
* Remove artifact graph augmentation
* Change default planes to use id generator instead of hardcoded UUIDs
* Fix to reuse previously generated IDs
* Add e2e test
* Change feature tree to be collapsed by default
* Remove debug prints
* Fix unit test to use execState
* Fix type to be more general
* Remove outdated comment
* Update derive-docs output
* Fix object display component to be more general
* Remove unused ArtifactId type
* Fix test to be less brittle
* Remove codeRef and pathToNode from display
* Fix to remove test.only
Co-authored-by: Frank Noirot <frank@zoo.dev>
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)
* Move plane conversion code to be next to type
* Revert "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)"
This reverts commit 3455cc951b.
* Rename file
* Rename components and add doc comments
* Revive the collapse button
* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest)
* Confirm
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)
* Confirm
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)
* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)
* Confirm
---------
Co-authored-by: Frank Noirot <frank@zoo.dev>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
			
			
							
								
								
									
										80
									
								
								e2e/playwright/debug-pane.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,80 @@
 | 
			
		||||
import { test, expect } from '@playwright/test'
 | 
			
		||||
 | 
			
		||||
import { getUtils, setup, tearDown } from './test-utils'
 | 
			
		||||
 | 
			
		||||
test.beforeEach(async ({ context, page }, testInfo) => {
 | 
			
		||||
  await setup(context, page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test.afterEach(async ({ page }, testInfo) => {
 | 
			
		||||
  await tearDown(page, testInfo)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
function countNewlines(input: string): number {
 | 
			
		||||
  let count = 0
 | 
			
		||||
  for (const char of input) {
 | 
			
		||||
    if (char === '\n') {
 | 
			
		||||
      count++
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test.describe('Debug pane', () => {
 | 
			
		||||
  test('Artifact IDs in the artifact graph are stable across code edits', async ({
 | 
			
		||||
    page,
 | 
			
		||||
    context,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const code = `sketch001 = startSketchOn('XZ')
 | 
			
		||||
  |> startProfileAt([0, 0], %)
 | 
			
		||||
|> line([1, 1], %)
 | 
			
		||||
`
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    await page.setViewportSize({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    const tree = page.getByTestId('debug-feature-tree')
 | 
			
		||||
    const segment = tree.locator('li', {
 | 
			
		||||
      hasText: 'segIds:',
 | 
			
		||||
      hasNotText: 'paths:',
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Test setup', async () => {
 | 
			
		||||
      await u.waitForAuthSkipAppStart()
 | 
			
		||||
      await u.openKclCodePanel()
 | 
			
		||||
      await u.openDebugPanel()
 | 
			
		||||
      // Set the code in the code editor.
 | 
			
		||||
      await u.codeLocator.click()
 | 
			
		||||
      await page.keyboard.type(code, { delay: 0 })
 | 
			
		||||
      // Scroll to the feature tree.
 | 
			
		||||
      await tree.scrollIntoViewIfNeeded()
 | 
			
		||||
      // Expand the feature tree.
 | 
			
		||||
      await tree.getByText('Feature Tree').click()
 | 
			
		||||
      // Just expanded the details, making the element taller, so scroll again.
 | 
			
		||||
      await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
 | 
			
		||||
    })
 | 
			
		||||
    // Extract the artifact IDs from the debug feature tree.
 | 
			
		||||
    const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
 | 
			
		||||
    // The artifact ID should include a UUID.
 | 
			
		||||
    expect(initialSegmentIds).toMatch(
 | 
			
		||||
      /[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/
 | 
			
		||||
    )
 | 
			
		||||
    await test.step('Move cursor to the bottom of the code editor', async () => {
 | 
			
		||||
      // Focus on the code editor.
 | 
			
		||||
      await u.codeLocator.click()
 | 
			
		||||
      // Make sure the cursor is at the end of the code.
 | 
			
		||||
      const lines = countNewlines(code) + 1
 | 
			
		||||
      for (let i = 0; i < lines; i++) {
 | 
			
		||||
        await page.keyboard.press('ArrowDown')
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    await test.step('Enter a comment', async () => {
 | 
			
		||||
      await page.keyboard.type('|> line([2, 2], %)', { delay: 0 })
 | 
			
		||||
      // Wait for keyboard input debounce and updated artifact graph.
 | 
			
		||||
      await page.waitForTimeout(1000)
 | 
			
		||||
    })
 | 
			
		||||
    const newSegmentIds = await segment.innerText()
 | 
			
		||||
    // Strip off the closing bracket.
 | 
			
		||||
    const initialIds = initialSegmentIds.slice(0, initialSegmentIds.length - 1)
 | 
			
		||||
    expect(newSegmentIds.slice(0, initialIds.length)).toEqual(initialIds)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB  | 
| 
		 Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB  | 
@ -408,6 +408,7 @@ export async function deleteSegment({
 | 
			
		||||
 | 
			
		||||
  const testExecute = await executeAst({
 | 
			
		||||
    ast: modifiedAst,
 | 
			
		||||
    idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
    useFakeExecutor: true,
 | 
			
		||||
    engineCommandManager: engineCommandManager,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -391,12 +391,14 @@ export class SceneEntities {
 | 
			
		||||
    const { truncatedAst, programMemoryOverride, variableDeclarationName } =
 | 
			
		||||
      prepared
 | 
			
		||||
 | 
			
		||||
    const { programMemory } = await executeAst({
 | 
			
		||||
    const { execState } = await executeAst({
 | 
			
		||||
      ast: truncatedAst,
 | 
			
		||||
      useFakeExecutor: true,
 | 
			
		||||
      engineCommandManager: this.engineCommandManager,
 | 
			
		||||
      programMemoryOverride,
 | 
			
		||||
      idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
    })
 | 
			
		||||
    const programMemory = execState.memory
 | 
			
		||||
    const sketch = sketchFromPathToNode({
 | 
			
		||||
      pathToNode: sketchPathToNode,
 | 
			
		||||
      ast: maybeModdedAst,
 | 
			
		||||
@ -801,12 +803,14 @@ export class SceneEntities {
 | 
			
		||||
          updateRectangleSketch(sketchInit, x, y, tags[0])
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { programMemory } = await executeAst({
 | 
			
		||||
        const { execState } = await executeAst({
 | 
			
		||||
          ast: truncatedAst,
 | 
			
		||||
          useFakeExecutor: true,
 | 
			
		||||
          engineCommandManager: this.engineCommandManager,
 | 
			
		||||
          programMemoryOverride,
 | 
			
		||||
          idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
        })
 | 
			
		||||
        const programMemory = execState.memory
 | 
			
		||||
        this.sceneProgramMemory = programMemory
 | 
			
		||||
        const sketch = sketchFromKclValue(
 | 
			
		||||
          programMemory.get(variableDeclarationName),
 | 
			
		||||
@ -848,12 +852,14 @@ export class SceneEntities {
 | 
			
		||||
          await kclManager.executeAstMock(_ast)
 | 
			
		||||
          sceneInfra.modelingSend({ type: 'Finish rectangle' })
 | 
			
		||||
 | 
			
		||||
          const { programMemory } = await executeAst({
 | 
			
		||||
          const { execState } = await executeAst({
 | 
			
		||||
            ast: _ast,
 | 
			
		||||
            useFakeExecutor: true,
 | 
			
		||||
            engineCommandManager: this.engineCommandManager,
 | 
			
		||||
            programMemoryOverride,
 | 
			
		||||
            idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
          })
 | 
			
		||||
          const programMemory = execState.memory
 | 
			
		||||
 | 
			
		||||
          // Prepare to update the THREEjs scene
 | 
			
		||||
          this.sceneProgramMemory = programMemory
 | 
			
		||||
@ -965,12 +971,14 @@ export class SceneEntities {
 | 
			
		||||
          modded = moddedResult.modifiedAst
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const { programMemory } = await executeAst({
 | 
			
		||||
        const { execState } = await executeAst({
 | 
			
		||||
          ast: modded,
 | 
			
		||||
          useFakeExecutor: true,
 | 
			
		||||
          engineCommandManager: this.engineCommandManager,
 | 
			
		||||
          programMemoryOverride,
 | 
			
		||||
          idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
        })
 | 
			
		||||
        const programMemory = execState.memory
 | 
			
		||||
        this.sceneProgramMemory = programMemory
 | 
			
		||||
        const sketch = sketchFromKclValue(
 | 
			
		||||
          programMemory.get(variableDeclarationName),
 | 
			
		||||
@ -1317,12 +1325,14 @@ export class SceneEntities {
 | 
			
		||||
        // don't want to mod the user's code yet as they have't committed to the change yet
 | 
			
		||||
        // plus this would be the truncated ast being recast, it would be wrong
 | 
			
		||||
        codeManager.updateCodeEditor(code)
 | 
			
		||||
      const { programMemory } = await executeAst({
 | 
			
		||||
      const { execState } = await executeAst({
 | 
			
		||||
        ast: truncatedAst,
 | 
			
		||||
        useFakeExecutor: true,
 | 
			
		||||
        engineCommandManager: this.engineCommandManager,
 | 
			
		||||
        programMemoryOverride,
 | 
			
		||||
        idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
      })
 | 
			
		||||
      const programMemory = execState.memory
 | 
			
		||||
      this.sceneProgramMemory = programMemory
 | 
			
		||||
 | 
			
		||||
      const maybeSketch = programMemory.get(variableDeclarationName)
 | 
			
		||||
 | 
			
		||||
@ -157,7 +157,7 @@ export function useCalc({
 | 
			
		||||
        engineCommandManager,
 | 
			
		||||
        useFakeExecutor: true,
 | 
			
		||||
        programMemoryOverride: kclManager.programMemory.clone(),
 | 
			
		||||
      }).then(({ programMemory }) => {
 | 
			
		||||
      }).then(({ execState }) => {
 | 
			
		||||
        const resultDeclaration = ast.body.find(
 | 
			
		||||
          (a) =>
 | 
			
		||||
            a.type === 'VariableDeclaration' &&
 | 
			
		||||
@ -166,7 +166,7 @@ export function useCalc({
 | 
			
		||||
        const init =
 | 
			
		||||
          resultDeclaration?.type === 'VariableDeclaration' &&
 | 
			
		||||
          resultDeclaration?.declarations?.[0]?.init
 | 
			
		||||
        const result = programMemory?.get('__result__')?.value
 | 
			
		||||
        const result = execState.memory?.get('__result__')?.value
 | 
			
		||||
        setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
 | 
			
		||||
        init && setValueNode(init)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										111
									
								
								src/components/DebugDisplayObj.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,111 @@
 | 
			
		||||
import { isArray, isNonNullable } from 'lib/utils'
 | 
			
		||||
import { useRef, useState } from 'react'
 | 
			
		||||
 | 
			
		||||
type Primitive = string | number | bigint | boolean | symbol | null | undefined
 | 
			
		||||
 | 
			
		||||
export type GenericObj = {
 | 
			
		||||
  type?: string
 | 
			
		||||
  [key: string]: GenericObj | Primitive | Array<GenericObj | Primitive>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display an array of objects or primitives for debug purposes. Nullable values
 | 
			
		||||
 * are displayed so that relative indexes are preserved.
 | 
			
		||||
 */
 | 
			
		||||
export function DebugDisplayArray({
 | 
			
		||||
  arr,
 | 
			
		||||
  filterKeys,
 | 
			
		||||
}: {
 | 
			
		||||
  arr: Array<GenericObj | Primitive>
 | 
			
		||||
  filterKeys: string[]
 | 
			
		||||
}) {
 | 
			
		||||
  return (
 | 
			
		||||
    <>
 | 
			
		||||
      {arr.map((obj, index) => {
 | 
			
		||||
        return (
 | 
			
		||||
          <div className="my-2" key={index}>
 | 
			
		||||
            {obj && typeof obj === 'object' ? (
 | 
			
		||||
              <DebugDisplayObj obj={obj} filterKeys={filterKeys} />
 | 
			
		||||
            ) : isNonNullable(obj) ? (
 | 
			
		||||
              <span>{obj.toString()}</span>
 | 
			
		||||
            ) : (
 | 
			
		||||
              <span>{obj}</span>
 | 
			
		||||
            )}
 | 
			
		||||
          </div>
 | 
			
		||||
        )
 | 
			
		||||
      })}
 | 
			
		||||
    </>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Display an object as a tree for debug purposes. Nullable values are omitted.
 | 
			
		||||
 * The only other property treated specially is the type property, which is
 | 
			
		||||
 * assumed to be a string.
 | 
			
		||||
 */
 | 
			
		||||
export function DebugDisplayObj({
 | 
			
		||||
  obj,
 | 
			
		||||
  filterKeys,
 | 
			
		||||
}: {
 | 
			
		||||
  obj: GenericObj
 | 
			
		||||
  filterKeys: string[]
 | 
			
		||||
}) {
 | 
			
		||||
  const ref = useRef<HTMLPreElement>(null)
 | 
			
		||||
  const hasCursor = false
 | 
			
		||||
  const [isCollapsed, setIsCollapsed] = useState(false)
 | 
			
		||||
  return (
 | 
			
		||||
    <pre
 | 
			
		||||
      ref={ref}
 | 
			
		||||
      className={`ml-2 border-l border-violet-600 pl-1 ${
 | 
			
		||||
        hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
 | 
			
		||||
      }`}
 | 
			
		||||
    >
 | 
			
		||||
      {isCollapsed ? (
 | 
			
		||||
        <button
 | 
			
		||||
          className="m-0 p-0 border-0"
 | 
			
		||||
          onClick={() => setIsCollapsed(false)}
 | 
			
		||||
        >
 | 
			
		||||
          {'>'}type: {obj.type}
 | 
			
		||||
        </button>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <span className="flex">
 | 
			
		||||
          <button
 | 
			
		||||
            className="m-0 p-0 border-0 mb-auto"
 | 
			
		||||
            onClick={() => setIsCollapsed(true)}
 | 
			
		||||
          >
 | 
			
		||||
            {'⬇️'}
 | 
			
		||||
          </button>
 | 
			
		||||
          <ul className="inline-block">
 | 
			
		||||
            {Object.entries(obj).map(([key, value]) => {
 | 
			
		||||
              if (filterKeys.includes(key)) {
 | 
			
		||||
                return null
 | 
			
		||||
              } else if (isArray(value)) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <li key={key}>
 | 
			
		||||
                    {`${key}: [`}
 | 
			
		||||
                    <DebugDisplayArray arr={value} filterKeys={filterKeys} />
 | 
			
		||||
                    {']'}
 | 
			
		||||
                  </li>
 | 
			
		||||
                )
 | 
			
		||||
              } else if (typeof value === 'object' && value !== null) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <li key={key}>
 | 
			
		||||
                    {key}:
 | 
			
		||||
                    <DebugDisplayObj obj={value} filterKeys={filterKeys} />
 | 
			
		||||
                  </li>
 | 
			
		||||
                )
 | 
			
		||||
              } else if (isNonNullable(value)) {
 | 
			
		||||
                return (
 | 
			
		||||
                  <li key={key}>
 | 
			
		||||
                    {key}: {value.toString()}
 | 
			
		||||
                  </li>
 | 
			
		||||
                )
 | 
			
		||||
              }
 | 
			
		||||
              return null
 | 
			
		||||
            })}
 | 
			
		||||
          </ul>
 | 
			
		||||
        </span>
 | 
			
		||||
      )}
 | 
			
		||||
    </pre>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										45
									
								
								src/components/DebugFeatureTree.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						@ -0,0 +1,45 @@
 | 
			
		||||
import { useMemo } from 'react'
 | 
			
		||||
import { engineCommandManager } from 'lib/singletons'
 | 
			
		||||
import {
 | 
			
		||||
  ArtifactGraph,
 | 
			
		||||
  expandPlane,
 | 
			
		||||
  PlaneArtifactRich,
 | 
			
		||||
} from 'lang/std/artifactGraph'
 | 
			
		||||
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
 | 
			
		||||
 | 
			
		||||
export function DebugFeatureTree() {
 | 
			
		||||
  const featureTree = useMemo(() => {
 | 
			
		||||
    return computeTree(engineCommandManager.artifactGraph)
 | 
			
		||||
  }, [engineCommandManager.artifactGraph])
 | 
			
		||||
 | 
			
		||||
  const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
 | 
			
		||||
  return (
 | 
			
		||||
    <details data-testid="debug-feature-tree" className="relative">
 | 
			
		||||
      <summary>Feature Tree</summary>
 | 
			
		||||
      {featureTree.length > 0 ? (
 | 
			
		||||
        <pre className="text-xs">
 | 
			
		||||
          <DebugDisplayArray arr={featureTree} filterKeys={filterKeys} />
 | 
			
		||||
        </pre>
 | 
			
		||||
      ) : (
 | 
			
		||||
        <p>(Empty)</p>
 | 
			
		||||
      )}
 | 
			
		||||
    </details>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function computeTree(artifactGraph: ArtifactGraph): GenericObj[] {
 | 
			
		||||
  let items: GenericObj[] = []
 | 
			
		||||
 | 
			
		||||
  const planes: PlaneArtifactRich[] = []
 | 
			
		||||
  for (const artifact of artifactGraph.values()) {
 | 
			
		||||
    if (artifact.type === 'plane') {
 | 
			
		||||
      planes.push(expandPlane(artifact, artifactGraph))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const extraRichPlanes: GenericObj[] = planes.map((plane) => {
 | 
			
		||||
    return plane as any as GenericObj
 | 
			
		||||
  })
 | 
			
		||||
  items = items.concat(extraRichPlanes)
 | 
			
		||||
 | 
			
		||||
  return items
 | 
			
		||||
}
 | 
			
		||||
@ -1,3 +1,4 @@
 | 
			
		||||
import { DebugFeatureTree } from 'components/DebugFeatureTree'
 | 
			
		||||
import { AstExplorer } from '../../AstExplorer'
 | 
			
		||||
import { EngineCommands } from '../../EngineCommands'
 | 
			
		||||
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
 | 
			
		||||
@ -12,6 +13,7 @@ export const DebugPane = () => {
 | 
			
		||||
        <EngineCommands />
 | 
			
		||||
        <CamDebugSettings />
 | 
			
		||||
        <AstExplorer />
 | 
			
		||||
        <DebugFeatureTree />
 | 
			
		||||
      </div>
 | 
			
		||||
    </section>
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
@ -29,8 +29,8 @@ describe('processMemory', () => {
 | 
			
		||||
    |> lineTo([2.15, 4.32], %)
 | 
			
		||||
    // |> rx(90, %)`
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast, ProgramMemory.empty())
 | 
			
		||||
    const output = processMemory(programMemory)
 | 
			
		||||
    const execState = await enginelessExecutor(ast, ProgramMemory.empty())
 | 
			
		||||
    const output = processMemory(execState.memory)
 | 
			
		||||
    expect(output.myVar).toEqual(5)
 | 
			
		||||
    expect(output.otherVar).toEqual(3)
 | 
			
		||||
    expect(output).toEqual({
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,8 @@ import { EXECUTE_AST_INTERRUPT_ERROR_MESSAGE } from 'lib/constants'
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
  CallExpression,
 | 
			
		||||
  emptyExecState,
 | 
			
		||||
  ExecState,
 | 
			
		||||
  initPromise,
 | 
			
		||||
  parse,
 | 
			
		||||
  PathToNode,
 | 
			
		||||
@ -42,6 +44,7 @@ export class KclManager {
 | 
			
		||||
    },
 | 
			
		||||
    digest: null,
 | 
			
		||||
  }
 | 
			
		||||
  private _execState: ExecState = emptyExecState()
 | 
			
		||||
  private _programMemory: ProgramMemory = ProgramMemory.empty()
 | 
			
		||||
  lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
 | 
			
		||||
  private _logs: string[] = []
 | 
			
		||||
@ -72,11 +75,21 @@ export class KclManager {
 | 
			
		||||
  get programMemory() {
 | 
			
		||||
    return this._programMemory
 | 
			
		||||
  }
 | 
			
		||||
  set programMemory(programMemory) {
 | 
			
		||||
  // This is private because callers should be setting the entire execState.
 | 
			
		||||
  private set programMemory(programMemory) {
 | 
			
		||||
    this._programMemory = programMemory
 | 
			
		||||
    this._programMemoryCallBack(programMemory)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  set execState(execState) {
 | 
			
		||||
    this._execState = execState
 | 
			
		||||
    this.programMemory = execState.memory
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get execState() {
 | 
			
		||||
    return this._execState
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get logs() {
 | 
			
		||||
    return this._logs
 | 
			
		||||
  }
 | 
			
		||||
@ -253,8 +266,9 @@ export class KclManager {
 | 
			
		||||
    // Make sure we clear before starting again. End session will do this.
 | 
			
		||||
    this.engineCommandManager?.endSession()
 | 
			
		||||
    await this.ensureWasmInit()
 | 
			
		||||
    const { logs, errors, programMemory, isInterrupted } = await executeAst({
 | 
			
		||||
    const { logs, errors, execState, isInterrupted } = await executeAst({
 | 
			
		||||
      ast,
 | 
			
		||||
      idGenerator: this.execState.idGenerator,
 | 
			
		||||
      engineCommandManager: this.engineCommandManager,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -264,7 +278,7 @@ export class KclManager {
 | 
			
		||||
      this.lints = await lintAst({ ast: ast })
 | 
			
		||||
 | 
			
		||||
      sceneInfra.modelingSend({ type: 'code edit during sketch' })
 | 
			
		||||
      defaultSelectionFilter(programMemory, this.engineCommandManager)
 | 
			
		||||
      defaultSelectionFilter(execState.memory, this.engineCommandManager)
 | 
			
		||||
 | 
			
		||||
      if (args.zoomToFit) {
 | 
			
		||||
        let zoomObjectId: string | undefined = ''
 | 
			
		||||
@ -304,9 +318,11 @@ export class KclManager {
 | 
			
		||||
    this.logs = logs
 | 
			
		||||
    // Do not add the errors since the program was interrupted and the error is not a real KCL error
 | 
			
		||||
    this.addKclErrors(isInterrupted ? [] : errors)
 | 
			
		||||
    this.programMemory = programMemory
 | 
			
		||||
    // Reset the next ID index so that we reuse the previous IDs next time.
 | 
			
		||||
    execState.idGenerator.nextId = 0
 | 
			
		||||
    this.execState = execState
 | 
			
		||||
    if (!errors.length) {
 | 
			
		||||
      this.lastSuccessfulProgramMemory = programMemory
 | 
			
		||||
      this.lastSuccessfulProgramMemory = execState.memory
 | 
			
		||||
    }
 | 
			
		||||
    this.ast = { ...ast }
 | 
			
		||||
    this._executeCallback()
 | 
			
		||||
@ -344,17 +360,19 @@ export class KclManager {
 | 
			
		||||
    await codeManager.writeToFile()
 | 
			
		||||
    this._ast = { ...newAst }
 | 
			
		||||
 | 
			
		||||
    const { logs, errors, programMemory } = await executeAst({
 | 
			
		||||
    const { logs, errors, execState } = await executeAst({
 | 
			
		||||
      ast: newAst,
 | 
			
		||||
      idGenerator: this.execState.idGenerator,
 | 
			
		||||
      engineCommandManager: this.engineCommandManager,
 | 
			
		||||
      useFakeExecutor: true,
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    this._logs = logs
 | 
			
		||||
    this._kclErrors = errors
 | 
			
		||||
    this._programMemory = programMemory
 | 
			
		||||
    this._execState = execState
 | 
			
		||||
    this._programMemory = execState.memory
 | 
			
		||||
    if (!errors.length) {
 | 
			
		||||
      this.lastSuccessfulProgramMemory = programMemory
 | 
			
		||||
      this.lastSuccessfulProgramMemory = execState.memory
 | 
			
		||||
    }
 | 
			
		||||
    if (updates !== 'artifactRanges') return
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -14,9 +14,9 @@ const mySketch001 = startSketchOn('XY')
 | 
			
		||||
  |> lineTo([-1.59, -1.54], %)
 | 
			
		||||
  |> lineTo([0.46, -5.82], %)
 | 
			
		||||
  // |> rx(45, %)`
 | 
			
		||||
    const programMemory = await enginelessExecutor(parse(code))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code))
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const sketch001 = programMemory?.get('mySketch001')
 | 
			
		||||
    const sketch001 = execState.memory.get('mySketch001')
 | 
			
		||||
    expect(sketch001).toEqual({
 | 
			
		||||
      type: 'UserVal',
 | 
			
		||||
      __meta: [{ sourceRange: [46, 71] }],
 | 
			
		||||
@ -68,9 +68,9 @@ const mySketch001 = startSketchOn('XY')
 | 
			
		||||
  |> lineTo([0.46, -5.82], %)
 | 
			
		||||
  // |> rx(45, %)
 | 
			
		||||
  |> extrude(2, %)`
 | 
			
		||||
    const programMemory = await enginelessExecutor(parse(code))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code))
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const sketch001 = programMemory?.get('mySketch001')
 | 
			
		||||
    const sketch001 = execState.memory.get('mySketch001')
 | 
			
		||||
    expect(sketch001).toEqual({
 | 
			
		||||
      type: 'Solid',
 | 
			
		||||
      id: expect.any(String),
 | 
			
		||||
@ -148,9 +148,10 @@ const sk2 = startSketchOn('XY')
 | 
			
		||||
  |> extrude(2, %)
 | 
			
		||||
 | 
			
		||||
`
 | 
			
		||||
    const programMemory = await enginelessExecutor(parse(code))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code))
 | 
			
		||||
    const programMemory = execState.memory
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    const geos = [programMemory?.get('theExtrude'), programMemory?.get('sk2')]
 | 
			
		||||
    const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
 | 
			
		||||
    expect(geos).toEqual([
 | 
			
		||||
      {
 | 
			
		||||
        type: 'Solid',
 | 
			
		||||
 | 
			
		||||
@ -443,6 +443,6 @@ async function exe(
 | 
			
		||||
) {
 | 
			
		||||
  const ast = parse(code)
 | 
			
		||||
 | 
			
		||||
  const result = await enginelessExecutor(ast, programMemory)
 | 
			
		||||
  return result
 | 
			
		||||
  const execState = await enginelessExecutor(ast, programMemory)
 | 
			
		||||
  return execState.memory
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -4,11 +4,14 @@ import {
 | 
			
		||||
  ProgramMemory,
 | 
			
		||||
  programMemoryInit,
 | 
			
		||||
  kclLint,
 | 
			
		||||
  emptyExecState,
 | 
			
		||||
  ExecState,
 | 
			
		||||
} from 'lang/wasm'
 | 
			
		||||
import { enginelessExecutor } from 'lib/testHelpers'
 | 
			
		||||
import { EngineCommandManager } from 'lang/std/engineConnection'
 | 
			
		||||
import { KCLError } from 'lang/errors'
 | 
			
		||||
import { Diagnostic } from '@codemirror/lint'
 | 
			
		||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
 | 
			
		||||
 | 
			
		||||
export type ToolTip =
 | 
			
		||||
  | 'lineTo'
 | 
			
		||||
@ -47,16 +50,18 @@ export async function executeAst({
 | 
			
		||||
  engineCommandManager,
 | 
			
		||||
  useFakeExecutor = false,
 | 
			
		||||
  programMemoryOverride,
 | 
			
		||||
  idGenerator,
 | 
			
		||||
}: {
 | 
			
		||||
  ast: Program
 | 
			
		||||
  engineCommandManager: EngineCommandManager
 | 
			
		||||
  useFakeExecutor?: boolean
 | 
			
		||||
  programMemoryOverride?: ProgramMemory
 | 
			
		||||
  idGenerator?: IdGenerator
 | 
			
		||||
  isInterrupted?: boolean
 | 
			
		||||
}): Promise<{
 | 
			
		||||
  logs: string[]
 | 
			
		||||
  errors: KCLError[]
 | 
			
		||||
  programMemory: ProgramMemory
 | 
			
		||||
  execState: ExecState
 | 
			
		||||
  isInterrupted: boolean
 | 
			
		||||
}> {
 | 
			
		||||
  try {
 | 
			
		||||
@ -65,15 +70,21 @@ export async function executeAst({
 | 
			
		||||
      // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
      engineCommandManager.startNewSession()
 | 
			
		||||
    }
 | 
			
		||||
    const programMemory = await (useFakeExecutor
 | 
			
		||||
    const execState = await (useFakeExecutor
 | 
			
		||||
      ? enginelessExecutor(ast, programMemoryOverride || programMemoryInit())
 | 
			
		||||
      : _executor(ast, programMemoryInit(), engineCommandManager, false))
 | 
			
		||||
      : _executor(
 | 
			
		||||
          ast,
 | 
			
		||||
          programMemoryInit(),
 | 
			
		||||
          idGenerator,
 | 
			
		||||
          engineCommandManager,
 | 
			
		||||
          false
 | 
			
		||||
        ))
 | 
			
		||||
 | 
			
		||||
    await engineCommandManager.waitForAllCommands()
 | 
			
		||||
    return {
 | 
			
		||||
      logs: [],
 | 
			
		||||
      errors: [],
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState,
 | 
			
		||||
      isInterrupted: false,
 | 
			
		||||
    }
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
@ -89,7 +100,7 @@ export async function executeAst({
 | 
			
		||||
      return {
 | 
			
		||||
        errors: [e],
 | 
			
		||||
        logs: [],
 | 
			
		||||
        programMemory: ProgramMemory.empty(),
 | 
			
		||||
        execState: emptyExecState(),
 | 
			
		||||
        isInterrupted,
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
@ -97,7 +108,7 @@ export async function executeAst({
 | 
			
		||||
      return {
 | 
			
		||||
        logs: [e],
 | 
			
		||||
        errors: [],
 | 
			
		||||
        programMemory: ProgramMemory.empty(),
 | 
			
		||||
        execState: emptyExecState(),
 | 
			
		||||
        isInterrupted,
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -220,11 +220,11 @@ yo2 = hmm([identifierGuy + 5])`
 | 
			
		||||
  it('should move a binary expression into a new variable', async () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const startIndex = code.indexOf('100 + 100') + 1
 | 
			
		||||
    const { modifiedAst } = moveValueIntoNewVariable(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [startIndex, startIndex],
 | 
			
		||||
      'newVar'
 | 
			
		||||
    )
 | 
			
		||||
@ -235,11 +235,11 @@ yo2 = hmm([identifierGuy + 5])`
 | 
			
		||||
  it('should move a value into a new variable', async () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const startIndex = code.indexOf('2.8') + 1
 | 
			
		||||
    const { modifiedAst } = moveValueIntoNewVariable(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [startIndex, startIndex],
 | 
			
		||||
      'newVar'
 | 
			
		||||
    )
 | 
			
		||||
@ -250,11 +250,11 @@ yo2 = hmm([identifierGuy + 5])`
 | 
			
		||||
  it('should move a callExpression into a new variable', async () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const startIndex = code.indexOf('def(')
 | 
			
		||||
    const { modifiedAst } = moveValueIntoNewVariable(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [startIndex, startIndex],
 | 
			
		||||
      'newVar'
 | 
			
		||||
    )
 | 
			
		||||
@ -265,11 +265,11 @@ yo2 = hmm([identifierGuy + 5])`
 | 
			
		||||
  it('should move a binary expression with call expression into a new variable', async () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const startIndex = code.indexOf('jkl(') + 1
 | 
			
		||||
    const { modifiedAst } = moveValueIntoNewVariable(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [startIndex, startIndex],
 | 
			
		||||
      'newVar'
 | 
			
		||||
    )
 | 
			
		||||
@ -280,11 +280,11 @@ yo2 = hmm([identifierGuy + 5])`
 | 
			
		||||
  it('should move a identifier into a new variable', async () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const startIndex = code.indexOf('identifierGuy +') + 1
 | 
			
		||||
    const { modifiedAst } = moveValueIntoNewVariable(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [startIndex, startIndex],
 | 
			
		||||
      'newVar'
 | 
			
		||||
    )
 | 
			
		||||
@ -465,7 +465,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
 | 
			
		||||
  |> line([306.21, 198.87], %)`
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const lineOfInterest = 'line([306.21, 198.85], %, $a)'
 | 
			
		||||
    const range: [number, number] = [
 | 
			
		||||
      code.indexOf(lineOfInterest),
 | 
			
		||||
@ -475,7 +475,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
 | 
			
		||||
    const modifiedAst = deleteSegmentFromPipeExpression(
 | 
			
		||||
      [],
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      code,
 | 
			
		||||
      pathToNode
 | 
			
		||||
    )
 | 
			
		||||
@ -543,7 +543,7 @@ ${!replace1 ? `  |> ${line}\n` : ''}  |> angledLine([-65, ${
 | 
			
		||||
      const code = makeCode(line)
 | 
			
		||||
      const ast = parse(code)
 | 
			
		||||
      if (err(ast)) throw ast
 | 
			
		||||
      const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
      const execState = await enginelessExecutor(ast)
 | 
			
		||||
      const lineOfInterest = line
 | 
			
		||||
      const range: [number, number] = [
 | 
			
		||||
        code.indexOf(lineOfInterest),
 | 
			
		||||
@ -554,7 +554,7 @@ ${!replace1 ? `  |> ${line}\n` : ''}  |> angledLine([-65, ${
 | 
			
		||||
      const modifiedAst = deleteSegmentFromPipeExpression(
 | 
			
		||||
        dependentSegments,
 | 
			
		||||
        ast,
 | 
			
		||||
        programMemory,
 | 
			
		||||
        execState.memory,
 | 
			
		||||
        code,
 | 
			
		||||
        pathToNode
 | 
			
		||||
      )
 | 
			
		||||
@ -632,7 +632,7 @@ describe('Testing removeSingleConstraintInfo', () => {
 | 
			
		||||
      const ast = parse(code)
 | 
			
		||||
      if (err(ast)) throw ast
 | 
			
		||||
 | 
			
		||||
      const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
      const execState = await enginelessExecutor(ast)
 | 
			
		||||
      const lineOfInterest = expectedFinish.split('(')[0] + '('
 | 
			
		||||
      const range: [number, number] = [
 | 
			
		||||
        code.indexOf(lineOfInterest) + 1,
 | 
			
		||||
@ -661,7 +661,7 @@ describe('Testing removeSingleConstraintInfo', () => {
 | 
			
		||||
        pathToNode,
 | 
			
		||||
        argPosition,
 | 
			
		||||
        ast,
 | 
			
		||||
        programMemory
 | 
			
		||||
        execState.memory
 | 
			
		||||
      )
 | 
			
		||||
      if (!mod) return new Error('mod is undefined')
 | 
			
		||||
      const recastCode = recast(mod.modifiedAst)
 | 
			
		||||
@ -686,7 +686,7 @@ describe('Testing removeSingleConstraintInfo', () => {
 | 
			
		||||
      const ast = parse(code)
 | 
			
		||||
      if (err(ast)) throw ast
 | 
			
		||||
 | 
			
		||||
      const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
      const execState = await enginelessExecutor(ast)
 | 
			
		||||
      const lineOfInterest = expectedFinish.split('(')[0] + '('
 | 
			
		||||
      const range: [number, number] = [
 | 
			
		||||
        code.indexOf(lineOfInterest) + 1,
 | 
			
		||||
@ -711,7 +711,7 @@ describe('Testing removeSingleConstraintInfo', () => {
 | 
			
		||||
        pathToNode,
 | 
			
		||||
        argPosition,
 | 
			
		||||
        ast,
 | 
			
		||||
        programMemory
 | 
			
		||||
        execState.memory
 | 
			
		||||
      )
 | 
			
		||||
      if (!mod) return new Error('mod is undefined')
 | 
			
		||||
      const recastCode = recast(mod.modifiedAst)
 | 
			
		||||
@ -882,7 +882,7 @@ sketch002 = startSketchOn({
 | 
			
		||||
      // const lineOfInterest = 'line([-2.94, 2.7], %)'
 | 
			
		||||
      const ast = parse(codeBefore)
 | 
			
		||||
      if (err(ast)) throw ast
 | 
			
		||||
      const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
      const execState = await enginelessExecutor(ast)
 | 
			
		||||
 | 
			
		||||
      // deleteFromSelection
 | 
			
		||||
      const range: [number, number] = [
 | 
			
		||||
@ -895,7 +895,7 @@ sketch002 = startSketchOn({
 | 
			
		||||
          range,
 | 
			
		||||
          type,
 | 
			
		||||
        },
 | 
			
		||||
        programMemory,
 | 
			
		||||
        execState.memory,
 | 
			
		||||
        async () => {
 | 
			
		||||
          await new Promise((resolve) => setTimeout(resolve, 100))
 | 
			
		||||
          return {
 | 
			
		||||
 | 
			
		||||
@ -45,11 +45,11 @@ variableBelowShouldNotBeIncluded = 3
 | 
			
		||||
    const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
 | 
			
		||||
    const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      [rangeStart, rangeStart]
 | 
			
		||||
    )
 | 
			
		||||
    expect(variables).toEqual([
 | 
			
		||||
@ -351,11 +351,11 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
  })
 | 
			
		||||
@ -370,11 +370,11 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [100, 101] },
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(true)
 | 
			
		||||
  })
 | 
			
		||||
@ -383,11 +383,11 @@ part001 = startSketchAt([-1.41, 3.46])
 | 
			
		||||
    const ast = parse(exampleCode)
 | 
			
		||||
    if (err(ast)) throw ast
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const result = hasExtrudeSketch({
 | 
			
		||||
      ast,
 | 
			
		||||
      selection: { type: 'default', range: [10, 11] },
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    expect(result).toEqual(false)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -117,11 +117,11 @@ describe('testing changeSketchArguments', () => {
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) return ast
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const sourceStart = code.indexOf(lineToChange)
 | 
			
		||||
    const changeSketchArgsRetVal = changeSketchArguments(
 | 
			
		||||
      ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      execState.memory,
 | 
			
		||||
      {
 | 
			
		||||
        type: 'sourceRange',
 | 
			
		||||
        sourceRange: [sourceStart, sourceStart + lineToChange.length],
 | 
			
		||||
@ -150,12 +150,12 @@ mySketch001 = startSketchOn('XY')
 | 
			
		||||
    const ast = parse(code)
 | 
			
		||||
    if (err(ast)) return ast
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const sourceStart = code.indexOf(lineToChange)
 | 
			
		||||
    expect(sourceStart).toBe(89)
 | 
			
		||||
    const newSketchLnRetVal = addNewSketchLn({
 | 
			
		||||
      node: ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
      input: {
 | 
			
		||||
        type: 'straight-segment',
 | 
			
		||||
        from: [0, 0],
 | 
			
		||||
@ -186,7 +186,7 @@ mySketch001 = startSketchOn('XY')
 | 
			
		||||
 | 
			
		||||
    const modifiedAst2 = addCloseToPipe({
 | 
			
		||||
      node: ast,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
      pathToNode: [
 | 
			
		||||
        ['body', ''],
 | 
			
		||||
        [0, 'index'],
 | 
			
		||||
@ -230,7 +230,7 @@ describe('testing addTagForSketchOnFace', () => {
 | 
			
		||||
    const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
 | 
			
		||||
    const sketchOnFaceRetVal = addTagForSketchOnFace(
 | 
			
		||||
      {
 | 
			
		||||
        // previousProgramMemory: programMemory, // redundant?
 | 
			
		||||
        // previousProgramMemory: execState.memory, // redundant?
 | 
			
		||||
        pathToNode,
 | 
			
		||||
        node: ast,
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
@ -34,7 +34,7 @@ async function testingSwapSketchFnCall({
 | 
			
		||||
  const ast = parse(inputCode)
 | 
			
		||||
  if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
 | 
			
		||||
  const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
  const execState = await enginelessExecutor(ast)
 | 
			
		||||
  const selections = {
 | 
			
		||||
    codeBasedSelections: [range],
 | 
			
		||||
    otherSelections: [],
 | 
			
		||||
@ -45,7 +45,7 @@ async function testingSwapSketchFnCall({
 | 
			
		||||
    return Promise.reject(new Error('transformInfos undefined'))
 | 
			
		||||
  const ast2 = transformAstSketchLines({
 | 
			
		||||
    ast,
 | 
			
		||||
    programMemory,
 | 
			
		||||
    programMemory: execState.memory,
 | 
			
		||||
    selectionRanges: selections,
 | 
			
		||||
    transformInfos,
 | 
			
		||||
    referenceSegName: '',
 | 
			
		||||
@ -360,10 +360,10 @@ part001 = startSketchOn('XY')
 | 
			
		||||
  |> line([2.14, 1.35], %) // normal-segment
 | 
			
		||||
  |> xLine(3.54, %)`
 | 
			
		||||
  it('normal case works', async () => {
 | 
			
		||||
    const programMemory = await enginelessExecutor(parse(code))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code))
 | 
			
		||||
    const index = code.indexOf('// normal-segment') - 7
 | 
			
		||||
    const sg = sketchFromKclValue(
 | 
			
		||||
      programMemory.get('part001'),
 | 
			
		||||
      execState.memory.get('part001'),
 | 
			
		||||
      'part001'
 | 
			
		||||
    ) as Sketch
 | 
			
		||||
    const _segment = getSketchSegmentFromSourceRange(sg, [index, index])
 | 
			
		||||
@ -377,10 +377,10 @@ part001 = startSketchOn('XY')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
  it('verify it works when the segment is in the `start` property', async () => {
 | 
			
		||||
    const programMemory = await enginelessExecutor(parse(code))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code))
 | 
			
		||||
    const index = code.indexOf('// segment-in-start') - 7
 | 
			
		||||
    const _segment = getSketchSegmentFromSourceRange(
 | 
			
		||||
      sketchFromKclValue(programMemory.get('part001'), 'part001') as Sketch,
 | 
			
		||||
      sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
 | 
			
		||||
      [index, index]
 | 
			
		||||
    )
 | 
			
		||||
    if (err(_segment)) throw _segment
 | 
			
		||||
 | 
			
		||||
@ -220,7 +220,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const transformInfos = getTransformInfos(
 | 
			
		||||
      makeSelections(selectionRanges.slice(1)),
 | 
			
		||||
      ast,
 | 
			
		||||
@ -231,7 +231,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
      ast,
 | 
			
		||||
      selectionRanges: makeSelections(selectionRanges),
 | 
			
		||||
      transformInfos,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
    })
 | 
			
		||||
    if (err(newAst)) return Promise.reject(newAst)
 | 
			
		||||
 | 
			
		||||
@ -311,7 +311,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const transformInfos = getTransformInfos(
 | 
			
		||||
      makeSelections(selectionRanges),
 | 
			
		||||
      ast,
 | 
			
		||||
@ -322,7 +322,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
      ast,
 | 
			
		||||
      selectionRanges: makeSelections(selectionRanges),
 | 
			
		||||
      transformInfos,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
      referenceSegName: '',
 | 
			
		||||
    })
 | 
			
		||||
    if (err(newAst)) return Promise.reject(newAst)
 | 
			
		||||
@ -373,7 +373,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
    const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
    const execState = await enginelessExecutor(ast)
 | 
			
		||||
    const transformInfos = getTransformInfos(
 | 
			
		||||
      makeSelections(selectionRanges),
 | 
			
		||||
      ast,
 | 
			
		||||
@ -384,7 +384,7 @@ part001 = startSketchOn('XY')
 | 
			
		||||
      ast,
 | 
			
		||||
      selectionRanges: makeSelections(selectionRanges),
 | 
			
		||||
      transformInfos,
 | 
			
		||||
      programMemory,
 | 
			
		||||
      programMemory: execState.memory,
 | 
			
		||||
      referenceSegName: '',
 | 
			
		||||
    })
 | 
			
		||||
    if (err(newAst)) return Promise.reject(newAst)
 | 
			
		||||
@ -470,7 +470,7 @@ async function helperThing(
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
  const programMemory = await enginelessExecutor(ast)
 | 
			
		||||
  const execState = await enginelessExecutor(ast)
 | 
			
		||||
  const transformInfos = getTransformInfos(
 | 
			
		||||
    makeSelections(selectionRanges.slice(1)),
 | 
			
		||||
    ast,
 | 
			
		||||
@ -481,7 +481,7 @@ async function helperThing(
 | 
			
		||||
    ast,
 | 
			
		||||
    selectionRanges: makeSelections(selectionRanges),
 | 
			
		||||
    transformInfos,
 | 
			
		||||
    programMemory,
 | 
			
		||||
    programMemory: execState.memory,
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  if (err(newAst)) return Promise.reject(newAst)
 | 
			
		||||
 | 
			
		||||
@ -17,9 +17,9 @@ describe('testing angledLineThatIntersects', () => {
 | 
			
		||||
  offset: ${offset},
 | 
			
		||||
}, %, $yo2)
 | 
			
		||||
intersect = segEndX(yo2)`
 | 
			
		||||
    const mem = await enginelessExecutor(parse(code('-1')))
 | 
			
		||||
    expect(mem.get('intersect')?.value).toBe(1 + Math.sqrt(2))
 | 
			
		||||
    const execState = await enginelessExecutor(parse(code('-1')))
 | 
			
		||||
    expect(execState.memory.get('intersect')?.value).toBe(1 + Math.sqrt(2))
 | 
			
		||||
    const noOffset = await enginelessExecutor(parse(code('0')))
 | 
			
		||||
    expect(noOffset.get('intersect')?.value).toBeCloseTo(1)
 | 
			
		||||
    expect(noOffset.memory.get('intersect')?.value).toBeCloseTo(1)
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -37,6 +37,11 @@ import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
 | 
			
		||||
import { DeepPartial } from 'lib/types'
 | 
			
		||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
 | 
			
		||||
import { Sketch } from '../wasm-lib/kcl/bindings/Sketch'
 | 
			
		||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
 | 
			
		||||
import { ExecState as RawExecState } from '../wasm-lib/kcl/bindings/ExecState'
 | 
			
		||||
import { ProgramMemory as RawProgramMemory } from '../wasm-lib/kcl/bindings/ProgramMemory'
 | 
			
		||||
import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
 | 
			
		||||
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
 | 
			
		||||
 | 
			
		||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
 | 
			
		||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
 | 
			
		||||
@ -136,29 +141,46 @@ export const parse = (code: string | Error): Program | Error => {
 | 
			
		||||
 | 
			
		||||
export type PathToNode = [string | number, string][]
 | 
			
		||||
 | 
			
		||||
interface Memory {
 | 
			
		||||
  [key: string]: KclValue
 | 
			
		||||
export interface ExecState {
 | 
			
		||||
  memory: ProgramMemory
 | 
			
		||||
  idGenerator: IdGenerator
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EnvironmentRef = number
 | 
			
		||||
/**
 | 
			
		||||
 * Create an empty ExecState.  This is useful on init to prevent needing an
 | 
			
		||||
 * Option.
 | 
			
		||||
 */
 | 
			
		||||
export function emptyExecState(): ExecState {
 | 
			
		||||
  return {
 | 
			
		||||
    memory: ProgramMemory.empty(),
 | 
			
		||||
    idGenerator: defaultIdGenerator(),
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function execStateFromRaw(raw: RawExecState): ExecState {
 | 
			
		||||
  return {
 | 
			
		||||
    memory: ProgramMemory.fromRaw(raw.memory),
 | 
			
		||||
    idGenerator: raw.idGenerator,
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function defaultIdGenerator(): IdGenerator {
 | 
			
		||||
  return {
 | 
			
		||||
    nextId: 0,
 | 
			
		||||
    ids: [],
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Memory {
 | 
			
		||||
  [key: string]: KclValue | undefined
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const ROOT_ENVIRONMENT_REF: EnvironmentRef = 0
 | 
			
		||||
 | 
			
		||||
interface Environment {
 | 
			
		||||
  bindings: Memory
 | 
			
		||||
  parent: EnvironmentRef | null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function emptyEnvironment(): Environment {
 | 
			
		||||
  return { bindings: {}, parent: null }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RawProgramMemory {
 | 
			
		||||
  environments: Environment[]
 | 
			
		||||
  currentEnv: EnvironmentRef
 | 
			
		||||
  return: KclValue | null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This duplicates logic in Rust.  The hope is to keep ProgramMemory internals
 | 
			
		||||
 * isolated from the rest of the TypeScript code so that we can move it to Rust
 | 
			
		||||
@ -217,7 +239,7 @@ export class ProgramMemory {
 | 
			
		||||
    while (true) {
 | 
			
		||||
      const env = this.environments[envRef]
 | 
			
		||||
      if (env.bindings.hasOwnProperty(name)) {
 | 
			
		||||
        return env.bindings[name]
 | 
			
		||||
        return env.bindings[name] ?? null
 | 
			
		||||
      }
 | 
			
		||||
      if (!env.parent) {
 | 
			
		||||
        break
 | 
			
		||||
@ -260,6 +282,7 @@ export class ProgramMemory {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (const [name, value] of Object.entries(env.bindings)) {
 | 
			
		||||
        if (value === undefined) continue
 | 
			
		||||
        // Check the predicate.
 | 
			
		||||
        if (!predicate(value)) {
 | 
			
		||||
          continue
 | 
			
		||||
@ -293,6 +316,7 @@ export class ProgramMemory {
 | 
			
		||||
    while (true) {
 | 
			
		||||
      const env = this.environments[envRef]
 | 
			
		||||
      for (const [name, value] of Object.entries(env.bindings)) {
 | 
			
		||||
        if (value === undefined) continue
 | 
			
		||||
        // Don't include shadowed variables.
 | 
			
		||||
        if (!map.has(name)) {
 | 
			
		||||
          map.set(name, value)
 | 
			
		||||
@ -356,9 +380,10 @@ export function sketchFromKclValue(
 | 
			
		||||
export const executor = async (
 | 
			
		||||
  node: Program,
 | 
			
		||||
  programMemory: ProgramMemory | Error = ProgramMemory.empty(),
 | 
			
		||||
  idGenerator: IdGenerator = defaultIdGenerator(),
 | 
			
		||||
  engineCommandManager: EngineCommandManager,
 | 
			
		||||
  isMock: boolean = false
 | 
			
		||||
): Promise<ProgramMemory> => {
 | 
			
		||||
): Promise<ExecState> => {
 | 
			
		||||
  if (err(programMemory)) return Promise.reject(programMemory)
 | 
			
		||||
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
@ -366,6 +391,7 @@ export const executor = async (
 | 
			
		||||
  const _programMemory = await _executor(
 | 
			
		||||
    node,
 | 
			
		||||
    programMemory,
 | 
			
		||||
    idGenerator,
 | 
			
		||||
    engineCommandManager,
 | 
			
		||||
    isMock
 | 
			
		||||
  )
 | 
			
		||||
@ -378,9 +404,10 @@ export const executor = async (
 | 
			
		||||
export const _executor = async (
 | 
			
		||||
  node: Program,
 | 
			
		||||
  programMemory: ProgramMemory | Error = ProgramMemory.empty(),
 | 
			
		||||
  idGenerator: IdGenerator = defaultIdGenerator(),
 | 
			
		||||
  engineCommandManager: EngineCommandManager,
 | 
			
		||||
  isMock: boolean
 | 
			
		||||
): Promise<ProgramMemory> => {
 | 
			
		||||
): Promise<ExecState> => {
 | 
			
		||||
  if (err(programMemory)) return Promise.reject(programMemory)
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
@ -392,15 +419,16 @@ export const _executor = async (
 | 
			
		||||
      baseUnit =
 | 
			
		||||
        (await getSettingsState)()?.modeling.defaultUnit.current || 'mm'
 | 
			
		||||
    }
 | 
			
		||||
    const memory: RawProgramMemory = await execute_wasm(
 | 
			
		||||
    const execState: RawExecState = await execute_wasm(
 | 
			
		||||
      JSON.stringify(node),
 | 
			
		||||
      JSON.stringify(programMemory.toRaw()),
 | 
			
		||||
      JSON.stringify(idGenerator),
 | 
			
		||||
      baseUnit,
 | 
			
		||||
      engineCommandManager,
 | 
			
		||||
      fileSystemManager,
 | 
			
		||||
      isMock
 | 
			
		||||
    )
 | 
			
		||||
    return ProgramMemory.fromRaw(memory)
 | 
			
		||||
    return execStateFromRaw(execState)
 | 
			
		||||
  } catch (e: any) {
 | 
			
		||||
    console.log(e)
 | 
			
		||||
    const parsed: RustKclError = JSON.parse(e.toString())
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,11 @@
 | 
			
		||||
import { Program, ProgramMemory, _executor, SourceRange } from '../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  Program,
 | 
			
		||||
  ProgramMemory,
 | 
			
		||||
  _executor,
 | 
			
		||||
  SourceRange,
 | 
			
		||||
  ExecState,
 | 
			
		||||
  defaultIdGenerator,
 | 
			
		||||
} from '../lang/wasm'
 | 
			
		||||
import {
 | 
			
		||||
  EngineCommandManager,
 | 
			
		||||
  EngineCommandManagerEvents,
 | 
			
		||||
@ -9,6 +16,7 @@ import { v4 as uuidv4 } from 'uuid'
 | 
			
		||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
 | 
			
		||||
import { err, reportRejection } from 'lib/trap'
 | 
			
		||||
import { toSync } from './utils'
 | 
			
		||||
import { IdGenerator } from 'wasm-lib/kcl/bindings/IdGenerator'
 | 
			
		||||
 | 
			
		||||
type WebSocketResponse = Models['WebSocketResponse_type']
 | 
			
		||||
 | 
			
		||||
@ -77,8 +85,9 @@ class MockEngineCommandManager {
 | 
			
		||||
 | 
			
		||||
export async function enginelessExecutor(
 | 
			
		||||
  ast: Program | Error,
 | 
			
		||||
  pm: ProgramMemory | Error = ProgramMemory.empty()
 | 
			
		||||
): Promise<ProgramMemory> {
 | 
			
		||||
  pm: ProgramMemory | Error = ProgramMemory.empty(),
 | 
			
		||||
  idGenerator: IdGenerator = defaultIdGenerator()
 | 
			
		||||
): Promise<ExecState> {
 | 
			
		||||
  if (err(ast)) return Promise.reject(ast)
 | 
			
		||||
  if (err(pm)) return Promise.reject(pm)
 | 
			
		||||
 | 
			
		||||
@ -88,15 +97,22 @@ export async function enginelessExecutor(
 | 
			
		||||
  }) as any as EngineCommandManager
 | 
			
		||||
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
  mockEngineCommandManager.startNewSession()
 | 
			
		||||
  const programMemory = await _executor(ast, pm, mockEngineCommandManager, true)
 | 
			
		||||
  const execState = await _executor(
 | 
			
		||||
    ast,
 | 
			
		||||
    pm,
 | 
			
		||||
    idGenerator,
 | 
			
		||||
    mockEngineCommandManager,
 | 
			
		||||
    true
 | 
			
		||||
  )
 | 
			
		||||
  await mockEngineCommandManager.waitForAllCommands()
 | 
			
		||||
  return programMemory
 | 
			
		||||
  return execState
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function executor(
 | 
			
		||||
  ast: Program,
 | 
			
		||||
  pm: ProgramMemory = ProgramMemory.empty()
 | 
			
		||||
): Promise<ProgramMemory> {
 | 
			
		||||
  pm: ProgramMemory = ProgramMemory.empty(),
 | 
			
		||||
  idGenerator: IdGenerator = defaultIdGenerator()
 | 
			
		||||
): Promise<ExecState> {
 | 
			
		||||
  const engineCommandManager = new EngineCommandManager()
 | 
			
		||||
  engineCommandManager.start({
 | 
			
		||||
    setIsStreamReady: () => {},
 | 
			
		||||
@ -117,14 +133,15 @@ export async function executor(
 | 
			
		||||
      toSync(async () => {
 | 
			
		||||
        // eslint-disable-next-line @typescript-eslint/no-floating-promises
 | 
			
		||||
        engineCommandManager.startNewSession()
 | 
			
		||||
        const programMemory = await _executor(
 | 
			
		||||
        const execState = await _executor(
 | 
			
		||||
          ast,
 | 
			
		||||
          pm,
 | 
			
		||||
          idGenerator,
 | 
			
		||||
          engineCommandManager,
 | 
			
		||||
          false
 | 
			
		||||
        )
 | 
			
		||||
        await engineCommandManager.waitForAllCommands()
 | 
			
		||||
        resolve(programMemory)
 | 
			
		||||
        resolve(execState)
 | 
			
		||||
      }, reportRejection)
 | 
			
		||||
    )
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -97,7 +97,7 @@ export function useCalculateKclExpression({
 | 
			
		||||
        })
 | 
			
		||||
        if (trap(error, { suppress: true })) return
 | 
			
		||||
      }
 | 
			
		||||
      const { programMemory } = await executeAst({
 | 
			
		||||
      const { execState } = await executeAst({
 | 
			
		||||
        ast,
 | 
			
		||||
        engineCommandManager,
 | 
			
		||||
        useFakeExecutor: true,
 | 
			
		||||
@ -111,7 +111,7 @@ export function useCalculateKclExpression({
 | 
			
		||||
      const init =
 | 
			
		||||
        resultDeclaration?.type === 'VariableDeclaration' &&
 | 
			
		||||
        resultDeclaration?.declarations?.[0]?.init
 | 
			
		||||
      const result = programMemory?.get('__result__')?.value
 | 
			
		||||
      const result = execState.memory?.get('__result__')?.value
 | 
			
		||||
      setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
 | 
			
		||||
      init && setValueNode(init)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -666,6 +666,7 @@ export const modelingMachine = setup({
 | 
			
		||||
 | 
			
		||||
        const testExecute = await executeAst({
 | 
			
		||||
          ast: modifiedAst,
 | 
			
		||||
          idGenerator: kclManager.execState.idGenerator,
 | 
			
		||||
          useFakeExecutor: true,
 | 
			
		||||
          engineCommandManager,
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
@ -753,6 +753,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
 | 
			
		||||
            let tokens = crate::token::lexer(#code_block).unwrap();
 | 
			
		||||
            let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
            let program = parser.ast().unwrap();
 | 
			
		||||
            let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
            let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
                engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
 | 
			
		||||
                fs: std::sync::Arc::new(crate::fs::FileManager::new()),
 | 
			
		||||
@ -761,7 +762,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
 | 
			
		||||
                context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
            };
 | 
			
		||||
 | 
			
		||||
            ctx.run(&program, None).await.unwrap();
 | 
			
		||||
            ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_someFn {
 | 
			
		||||
        let tokens = crate::token::lexer("someFn()").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_someFn {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_someFn {
 | 
			
		||||
        let tokens = crate::token::lexer("someFn()").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_someFn {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_show {
 | 
			
		||||
        let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nshow").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_show {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
@ -38,6 +39,7 @@ mod test_examples_show {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -49,7 +51,7 @@ mod test_examples_show {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_show {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_show {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ mod test_examples_my_func {
 | 
			
		||||
            crate::token::lexer("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -17,7 +18,7 @@ mod test_examples_my_func {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
@ -39,6 +40,7 @@ mod test_examples_my_func {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmyFunc").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -50,7 +52,7 @@ mod test_examples_my_func {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ mod test_examples_line_to {
 | 
			
		||||
            crate::token::lexer("This is another code block.\nyes sirrr.\nlineTo").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -17,7 +18,7 @@ mod test_examples_line_to {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
@ -39,6 +40,7 @@ mod test_examples_line_to {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nlineTo").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -50,7 +52,7 @@ mod test_examples_line_to {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_min {
 | 
			
		||||
        let tokens = crate::token::lexer("This is another code block.\nyes sirrr.\nmin").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_min {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
@ -38,6 +39,7 @@ mod test_examples_min {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nmin").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -49,7 +51,7 @@ mod test_examples_min {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_show {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_show {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_import {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_import {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_import {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_import {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_import {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nimport").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_import {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_show {
 | 
			
		||||
        let tokens = crate::token::lexer("This is code.\nIt does other shit.\nshow").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_show {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ mod test_examples_some_function {
 | 
			
		||||
        let tokens = crate::token::lexer("someFunction()").unwrap();
 | 
			
		||||
        let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
        let program = parser.ast().unwrap();
 | 
			
		||||
        let id_generator = crate::executor::IdGenerator::default();
 | 
			
		||||
        let ctx = crate::executor::ExecutorContext {
 | 
			
		||||
            engine: std::sync::Arc::new(Box::new(
 | 
			
		||||
                crate::engine::conn_mock::EngineConnection::new()
 | 
			
		||||
@ -16,7 +17,7 @@ mod test_examples_some_function {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: crate::executor::ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        ctx.run(&program, None).await.unwrap();
 | 
			
		||||
        ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
 | 
			
		||||
 | 
			
		||||
@ -166,15 +166,19 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
 | 
			
		||||
        Err(e) => return bad_request(format!("Parse error: {e}")),
 | 
			
		||||
    };
 | 
			
		||||
    eprintln!("Executing {test_name}");
 | 
			
		||||
    let mut id_generator = kcl_lib::executor::IdGenerator::default();
 | 
			
		||||
    // This is a shitty source range, I don't know what else to use for it though.
 | 
			
		||||
    // There's no actual KCL associated with this reset_scene call.
 | 
			
		||||
    if let Err(e) = state.reset_scene(kcl_lib::executor::SourceRange::default()).await {
 | 
			
		||||
    if let Err(e) = state
 | 
			
		||||
        .reset_scene(&mut id_generator, kcl_lib::executor::SourceRange::default())
 | 
			
		||||
        .await
 | 
			
		||||
    {
 | 
			
		||||
        return kcl_err(e);
 | 
			
		||||
    }
 | 
			
		||||
    // Let users know if the test is taking a long time.
 | 
			
		||||
    let (done_tx, done_rx) = oneshot::channel::<()>();
 | 
			
		||||
    let timer = time_until(done_rx);
 | 
			
		||||
    let snapshot = match state.execute_and_prepare_snapshot(&program).await {
 | 
			
		||||
    let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator).await {
 | 
			
		||||
        Ok(sn) => sn,
 | 
			
		||||
        Err(e) => return kcl_err(e),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,9 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use kcl_lib::{errors::KclError, executor::DefaultPlanes};
 | 
			
		||||
use kcl_lib::{
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    executor::{DefaultPlanes, IdGenerator},
 | 
			
		||||
};
 | 
			
		||||
use kittycad_modeling_cmds::{
 | 
			
		||||
    self as kcmc,
 | 
			
		||||
    id::ModelingCmdId,
 | 
			
		||||
@ -357,7 +360,11 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
 | 
			
		||||
        self.batch_end.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn default_planes(&self, source_range: kcl_lib::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
    async fn default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: kcl_lib::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        if NEED_PLANES {
 | 
			
		||||
            {
 | 
			
		||||
                let opt = self.default_planes.read().await.as_ref().cloned();
 | 
			
		||||
@ -366,7 +373,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
 | 
			
		||||
                }
 | 
			
		||||
            } // drop the read lock
 | 
			
		||||
 | 
			
		||||
            let new_planes = self.new_default_planes(source_range).await?;
 | 
			
		||||
            let new_planes = self.new_default_planes(id_generator, source_range).await?;
 | 
			
		||||
            *self.default_planes.write().await = Some(new_planes.clone());
 | 
			
		||||
 | 
			
		||||
            Ok(new_planes)
 | 
			
		||||
@ -375,7 +382,11 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn clear_scene_post_hook(&self, _source_range: kcl_lib::executor::SourceRange) -> Result<(), KclError> {
 | 
			
		||||
    async fn clear_scene_post_hook(
 | 
			
		||||
        &self,
 | 
			
		||||
        _id_generator: &mut IdGenerator,
 | 
			
		||||
        _source_range: kcl_lib::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), KclError> {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use kcl_lib::executor::ExecutorContext;
 | 
			
		||||
use kcl_lib::executor::{ExecutorContext, IdGenerator};
 | 
			
		||||
use std::sync::{Arc, Mutex};
 | 
			
		||||
 | 
			
		||||
#[cfg(not(target_arch = "wasm32"))]
 | 
			
		||||
@ -23,7 +23,7 @@ pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
 | 
			
		||||
        settings: Default::default(),
 | 
			
		||||
        context_type: kcl_lib::executor::ContextType::MockCustomForwarded,
 | 
			
		||||
    };
 | 
			
		||||
    let _memory = ctx.run(&program, None).await?;
 | 
			
		||||
    let _memory = ctx.run(&program, None, IdGenerator::default()).await?;
 | 
			
		||||
 | 
			
		||||
    let result = result.lock().expect("mutex lock").clone();
 | 
			
		||||
    Ok(result)
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ use tokio_tungstenite::tungstenite::Message as WsMsg;
 | 
			
		||||
use crate::{
 | 
			
		||||
    engine::EngineManager,
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::DefaultPlanes,
 | 
			
		||||
    executor::{DefaultPlanes, IdGenerator},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
@ -314,7 +314,11 @@ impl EngineManager for EngineConnection {
 | 
			
		||||
        self.batch_end.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
    async fn default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        {
 | 
			
		||||
            let opt = self.default_planes.read().await.as_ref().cloned();
 | 
			
		||||
            if let Some(planes) = opt {
 | 
			
		||||
@ -322,15 +326,19 @@ impl EngineManager for EngineConnection {
 | 
			
		||||
            }
 | 
			
		||||
        } // drop the read lock
 | 
			
		||||
 | 
			
		||||
        let new_planes = self.new_default_planes(source_range).await?;
 | 
			
		||||
        let new_planes = self.new_default_planes(id_generator, source_range).await?;
 | 
			
		||||
        *self.default_planes.write().await = Some(new_planes.clone());
 | 
			
		||||
 | 
			
		||||
        Ok(new_planes)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> {
 | 
			
		||||
    async fn clear_scene_post_hook(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), KclError> {
 | 
			
		||||
        // Remake the default planes, since they would have been removed after the scene was cleared.
 | 
			
		||||
        let new_planes = self.new_default_planes(source_range).await?;
 | 
			
		||||
        let new_planes = self.new_default_planes(id_generator, source_range).await?;
 | 
			
		||||
        *self.default_planes.write().await = Some(new_planes);
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,10 @@ use kcmc::{
 | 
			
		||||
};
 | 
			
		||||
use kittycad_modeling_cmds::{self as kcmc};
 | 
			
		||||
 | 
			
		||||
use crate::{errors::KclError, executor::DefaultPlanes};
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    executor::{DefaultPlanes, IdGenerator},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct EngineConnection {
 | 
			
		||||
@ -44,11 +47,19 @@ impl crate::engine::EngineManager for EngineConnection {
 | 
			
		||||
        self.batch_end.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn default_planes(&self, _source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
    async fn default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        _id_generator: &mut IdGenerator,
 | 
			
		||||
        _source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        Ok(DefaultPlanes::default())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn clear_scene_post_hook(&self, _source_range: crate::executor::SourceRange) -> Result<(), KclError> {
 | 
			
		||||
    async fn clear_scene_post_hook(
 | 
			
		||||
        &self,
 | 
			
		||||
        _id_generator: &mut IdGenerator,
 | 
			
		||||
        _source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), KclError> {
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -10,7 +10,7 @@ use wasm_bindgen::prelude::*;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::DefaultPlanes,
 | 
			
		||||
    executor::{DefaultPlanes, IdGenerator},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[wasm_bindgen(module = "/../../lang/std/engineConnection.ts")]
 | 
			
		||||
@ -68,7 +68,11 @@ impl crate::engine::EngineManager for EngineConnection {
 | 
			
		||||
        self.batch_end.clone()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
    async fn default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        _id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        // Get the default planes.
 | 
			
		||||
        let promise = self.manager.get_default_planes().map_err(|e| {
 | 
			
		||||
            KclError::Engine(KclErrorDetails {
 | 
			
		||||
@ -106,7 +110,11 @@ impl crate::engine::EngineManager for EngineConnection {
 | 
			
		||||
        Ok(default_planes)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn clear_scene_post_hook(&self, source_range: crate::executor::SourceRange) -> Result<(), KclError> {
 | 
			
		||||
    async fn clear_scene_post_hook(
 | 
			
		||||
        &self,
 | 
			
		||||
        _id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), KclError> {
 | 
			
		||||
        self.manager.clear_default_planes().map_err(|e| {
 | 
			
		||||
            KclError::Engine(KclErrorDetails {
 | 
			
		||||
                message: e.to_string().into(),
 | 
			
		||||
 | 
			
		||||
@ -32,7 +32,7 @@ use uuid::Uuid;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::{DefaultPlanes, Point3d},
 | 
			
		||||
    executor::{DefaultPlanes, IdGenerator, Point3d},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
lazy_static::lazy_static! {
 | 
			
		||||
@ -52,6 +52,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
    /// Get the default planes.
 | 
			
		||||
    async fn default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        _source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, crate::errors::KclError>;
 | 
			
		||||
 | 
			
		||||
@ -59,6 +60,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
    /// (These really only apply to wasm for now).
 | 
			
		||||
    async fn clear_scene_post_hook(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), crate::errors::KclError>;
 | 
			
		||||
 | 
			
		||||
@ -71,7 +73,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        id_to_source_range: HashMap<uuid::Uuid, crate::executor::SourceRange>,
 | 
			
		||||
    ) -> Result<kcmc::websocket::WebSocketResponse, crate::errors::KclError>;
 | 
			
		||||
 | 
			
		||||
    async fn clear_scene(&self, source_range: crate::executor::SourceRange) -> Result<(), crate::errors::KclError> {
 | 
			
		||||
    async fn clear_scene(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<(), crate::errors::KclError> {
 | 
			
		||||
        self.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            source_range,
 | 
			
		||||
@ -84,7 +90,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        self.flush_batch(false, source_range).await?;
 | 
			
		||||
 | 
			
		||||
        // Do the after clear scene hook.
 | 
			
		||||
        self.clear_scene_post_hook(source_range).await?;
 | 
			
		||||
        self.clear_scene_post_hook(id_generator, source_range).await?;
 | 
			
		||||
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
@ -265,6 +271,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
 | 
			
		||||
    async fn make_default_plane(
 | 
			
		||||
        &self,
 | 
			
		||||
        plane_id: uuid::Uuid,
 | 
			
		||||
        x_axis: Point3d,
 | 
			
		||||
        y_axis: Point3d,
 | 
			
		||||
        color: Option<Color>,
 | 
			
		||||
@ -274,7 +281,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        let default_size = 100.0;
 | 
			
		||||
        let default_origin = Point3d { x: 0.0, y: 0.0, z: 0.0 }.into();
 | 
			
		||||
 | 
			
		||||
        let plane_id = uuid::Uuid::new_v4();
 | 
			
		||||
        self.batch_modeling_cmd(
 | 
			
		||||
            plane_id,
 | 
			
		||||
            source_range,
 | 
			
		||||
@ -302,11 +308,16 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        Ok(plane_id)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn new_default_planes(&self, source_range: crate::executor::SourceRange) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        let plane_settings: HashMap<PlaneName, (Point3d, Point3d, Option<Color>)> = HashMap::from([
 | 
			
		||||
    async fn new_default_planes(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<DefaultPlanes, KclError> {
 | 
			
		||||
        let plane_settings: HashMap<PlaneName, (Uuid, Point3d, Point3d, Option<Color>)> = HashMap::from([
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Xy,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
@ -320,6 +331,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Yz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 0.0, y: 1.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
@ -333,6 +345,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::Xz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d { x: 1.0, y: 0.0, z: 0.0 },
 | 
			
		||||
                    Point3d { x: 0.0, y: 0.0, z: 1.0 },
 | 
			
		||||
                    Some(Color {
 | 
			
		||||
@ -346,6 +359,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegXy,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: -1.0,
 | 
			
		||||
                        y: 0.0,
 | 
			
		||||
@ -358,6 +372,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegYz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: 0.0,
 | 
			
		||||
                        y: -1.0,
 | 
			
		||||
@ -370,6 +385,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
            (
 | 
			
		||||
                PlaneName::NegXz,
 | 
			
		||||
                (
 | 
			
		||||
                    id_generator.next_uuid(),
 | 
			
		||||
                    Point3d {
 | 
			
		||||
                        x: -1.0,
 | 
			
		||||
                        y: 0.0,
 | 
			
		||||
@ -382,10 +398,11 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        let mut planes = HashMap::new();
 | 
			
		||||
        for (name, (x_axis, y_axis, color)) in plane_settings {
 | 
			
		||||
        for (name, (plane_id, x_axis, y_axis, color)) in plane_settings {
 | 
			
		||||
            planes.insert(
 | 
			
		||||
                name,
 | 
			
		||||
                self.make_default_plane(x_axis, y_axis, color, source_range).await?,
 | 
			
		||||
                self.make_default_plane(plane_id, x_axis, y_axis, color, source_range)
 | 
			
		||||
                    .await?,
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -40,6 +40,8 @@ use crate::{
 | 
			
		||||
pub struct ExecState {
 | 
			
		||||
    /// Program variable bindings.
 | 
			
		||||
    pub memory: ProgramMemory,
 | 
			
		||||
    /// The stable artifact ID generator.
 | 
			
		||||
    pub id_generator: IdGenerator,
 | 
			
		||||
    /// Dynamic state that follows dynamic flow of the program.
 | 
			
		||||
    pub dynamic_state: DynamicState,
 | 
			
		||||
    /// The current value of the pipe operator returned from the previous
 | 
			
		||||
@ -292,6 +294,33 @@ impl DynamicState {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A generator for ArtifactIds that can be stable across executions.
 | 
			
		||||
#[derive(Debug, Clone, Default, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
pub struct IdGenerator {
 | 
			
		||||
    next_id: usize,
 | 
			
		||||
    ids: Vec<uuid::Uuid>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl IdGenerator {
 | 
			
		||||
    pub fn new() -> Self {
 | 
			
		||||
        Self::default()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn next_uuid(&mut self) -> uuid::Uuid {
 | 
			
		||||
        if let Some(id) = self.ids.get(self.next_id) {
 | 
			
		||||
            self.next_id += 1;
 | 
			
		||||
            *id
 | 
			
		||||
        } else {
 | 
			
		||||
            let id = uuid::Uuid::new_v4();
 | 
			
		||||
            self.ids.push(id);
 | 
			
		||||
            self.next_id += 1;
 | 
			
		||||
            id
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Any KCL value.
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
@ -599,6 +628,82 @@ pub struct Plane {
 | 
			
		||||
    pub meta: Vec<Metadata>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Plane {
 | 
			
		||||
    pub(crate) fn from_plane_data(value: crate::std::sketch::PlaneData, exec_state: &mut ExecState) -> Self {
 | 
			
		||||
        let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
        match value {
 | 
			
		||||
            crate::std::sketch::PlaneData::XY => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                value: PlaneType::XY,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::NegXY => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 0.0, -1.0),
 | 
			
		||||
                value: PlaneType::XY,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::XZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, -1.0, 0.0),
 | 
			
		||||
                value: PlaneType::XZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::NegXZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(-1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                value: PlaneType::XZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::YZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                value: PlaneType::YZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::NegYZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(-1.0, 0.0, 0.0),
 | 
			
		||||
                value: PlaneType::YZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            crate::std::sketch::PlaneData::Plane {
 | 
			
		||||
                origin,
 | 
			
		||||
                x_axis,
 | 
			
		||||
                y_axis,
 | 
			
		||||
                z_axis,
 | 
			
		||||
            } => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: *origin,
 | 
			
		||||
                x_axis: *x_axis,
 | 
			
		||||
                y_axis: *y_axis,
 | 
			
		||||
                z_axis: *z_axis,
 | 
			
		||||
                value: PlaneType::Custom,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
 | 
			
		||||
#[ts(export)]
 | 
			
		||||
#[serde(rename_all = "camelCase")]
 | 
			
		||||
@ -1836,8 +1941,12 @@ impl ExecutorContext {
 | 
			
		||||
        Ok(ctx)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn reset_scene(&self, source_range: crate::executor::SourceRange) -> Result<()> {
 | 
			
		||||
        self.engine.clear_scene(source_range).await?;
 | 
			
		||||
    pub async fn reset_scene(
 | 
			
		||||
        &self,
 | 
			
		||||
        id_generator: &mut IdGenerator,
 | 
			
		||||
        source_range: crate::executor::SourceRange,
 | 
			
		||||
    ) -> Result<()> {
 | 
			
		||||
        self.engine.clear_scene(id_generator, source_range).await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1848,8 +1957,11 @@ impl ExecutorContext {
 | 
			
		||||
        &self,
 | 
			
		||||
        program: &crate::ast::types::Program,
 | 
			
		||||
        memory: Option<ProgramMemory>,
 | 
			
		||||
        id_generator: IdGenerator,
 | 
			
		||||
    ) -> Result<ExecState, KclError> {
 | 
			
		||||
        self.run_with_session_data(program, memory).await.map(|x| x.0)
 | 
			
		||||
        self.run_with_session_data(program, memory, id_generator)
 | 
			
		||||
            .await
 | 
			
		||||
            .map(|x| x.0)
 | 
			
		||||
    }
 | 
			
		||||
    /// Perform the execution of a program.
 | 
			
		||||
    /// You can optionally pass in some initialization memory.
 | 
			
		||||
@ -1858,11 +1970,22 @@ impl ExecutorContext {
 | 
			
		||||
        &self,
 | 
			
		||||
        program: &crate::ast::types::Program,
 | 
			
		||||
        memory: Option<ProgramMemory>,
 | 
			
		||||
        id_generator: IdGenerator,
 | 
			
		||||
    ) -> Result<(ExecState, Option<ModelingSessionData>), KclError> {
 | 
			
		||||
        let memory = if let Some(memory) = memory {
 | 
			
		||||
            memory.clone()
 | 
			
		||||
        } else {
 | 
			
		||||
            Default::default()
 | 
			
		||||
        };
 | 
			
		||||
        let mut exec_state = ExecState {
 | 
			
		||||
            memory,
 | 
			
		||||
            id_generator,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        };
 | 
			
		||||
        // Before we even start executing the program, set the units.
 | 
			
		||||
        self.engine
 | 
			
		||||
            .batch_modeling_cmd(
 | 
			
		||||
                uuid::Uuid::new_v4(),
 | 
			
		||||
                exec_state.id_generator.next_uuid(),
 | 
			
		||||
                SourceRange::default(),
 | 
			
		||||
                &ModelingCmd::from(mcmd::SetSceneUnits {
 | 
			
		||||
                    unit: match self.settings.units {
 | 
			
		||||
@ -1876,15 +1999,7 @@ impl ExecutorContext {
 | 
			
		||||
                }),
 | 
			
		||||
            )
 | 
			
		||||
            .await?;
 | 
			
		||||
        let memory = if let Some(memory) = memory {
 | 
			
		||||
            memory.clone()
 | 
			
		||||
        } else {
 | 
			
		||||
            Default::default()
 | 
			
		||||
        };
 | 
			
		||||
        let mut exec_state = ExecState {
 | 
			
		||||
            memory,
 | 
			
		||||
            ..Default::default()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        self.inner_execute(program, &mut exec_state, crate::executor::BodyType::Root)
 | 
			
		||||
            .await?;
 | 
			
		||||
        let session_data = self.engine.get_session_data();
 | 
			
		||||
@ -2029,8 +2144,12 @@ impl ExecutorContext {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Execute the program, then get a PNG screenshot.
 | 
			
		||||
    pub async fn execute_and_prepare_snapshot(&self, program: &Program) -> Result<TakeSnapshot> {
 | 
			
		||||
        let _ = self.run(program, None).await?;
 | 
			
		||||
    pub async fn execute_and_prepare_snapshot(
 | 
			
		||||
        &self,
 | 
			
		||||
        program: &Program,
 | 
			
		||||
        id_generator: IdGenerator,
 | 
			
		||||
    ) -> Result<TakeSnapshot> {
 | 
			
		||||
        let _ = self.run(program, None, id_generator).await?;
 | 
			
		||||
 | 
			
		||||
        // Zoom to fit.
 | 
			
		||||
        self.engine
 | 
			
		||||
@ -2175,7 +2294,7 @@ mod tests {
 | 
			
		||||
            settings: Default::default(),
 | 
			
		||||
            context_type: ContextType::Mock,
 | 
			
		||||
        };
 | 
			
		||||
        let exec_state = ctx.run(&program, None).await?;
 | 
			
		||||
        let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
 | 
			
		||||
 | 
			
		||||
        Ok(exec_state.memory)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -41,7 +41,7 @@ use tower_lsp::{
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    ast::types::{Expr, VariableKind},
 | 
			
		||||
    executor::SourceRange,
 | 
			
		||||
    executor::{IdGenerator, SourceRange},
 | 
			
		||||
    lsp::{backend::Backend as _, util::IntoDiagnostic},
 | 
			
		||||
    parser::PIPE_OPERATOR,
 | 
			
		||||
    token::TokenType,
 | 
			
		||||
@ -588,10 +588,15 @@ impl Backend {
 | 
			
		||||
            return Ok(());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Clear the scene, before we execute so it's not fugly as shit.
 | 
			
		||||
        executor_ctx.engine.clear_scene(SourceRange::default()).await?;
 | 
			
		||||
        let mut id_generator = IdGenerator::default();
 | 
			
		||||
 | 
			
		||||
        let exec_state = match executor_ctx.run(ast, None).await {
 | 
			
		||||
        // Clear the scene, before we execute so it's not fugly as shit.
 | 
			
		||||
        executor_ctx
 | 
			
		||||
            .engine
 | 
			
		||||
            .clear_scene(&mut id_generator, SourceRange::default())
 | 
			
		||||
            .await?;
 | 
			
		||||
 | 
			
		||||
        let exec_state = match executor_ctx.run(ast, None, id_generator).await {
 | 
			
		||||
            Ok(exec_state) => exec_state,
 | 
			
		||||
            Err(err) => {
 | 
			
		||||
                self.memory_map.remove(params.uri.as_str());
 | 
			
		||||
 | 
			
		||||
@ -133,7 +133,7 @@ async fn inner_chamfer(
 | 
			
		||||
            EdgeReference::Tag(edge_tag) => args.get_tag_engine_info(exec_state, &edge_tag)?.id,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let id = uuid::Uuid::new_v4();
 | 
			
		||||
        let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
        args.batch_end_cmd(
 | 
			
		||||
            id,
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dFilletEdge {
 | 
			
		||||
 | 
			
		||||
@ -18,10 +18,10 @@ use crate::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/// Extrudes by a given amount.
 | 
			
		||||
pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (length, sketch_set) = args.get_number_sketch_set()?;
 | 
			
		||||
 | 
			
		||||
    let result = inner_extrude(length, sketch_set, args).await?;
 | 
			
		||||
    let result = inner_extrude(length, sketch_set, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(result.into())
 | 
			
		||||
}
 | 
			
		||||
@ -75,8 +75,13 @@ pub async fn extrude(_exec_state: &mut ExecState, args: Args) -> Result<KclValue
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "extrude"
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result<SolidSet, KclError> {
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
async fn inner_extrude(
 | 
			
		||||
    length: f64,
 | 
			
		||||
    sketch_set: SketchSet,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<SolidSet, KclError> {
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    // Extrude the element(s).
 | 
			
		||||
    let sketches: Vec<Sketch> = sketch_set.into();
 | 
			
		||||
@ -85,7 +90,7 @@ async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result
 | 
			
		||||
        // Before we extrude, we need to enable the sketch mode.
 | 
			
		||||
        // We do this here in case extrude is called out of order.
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::EnableSketchMode {
 | 
			
		||||
                animated: false,
 | 
			
		||||
                ortho: false,
 | 
			
		||||
@ -112,21 +117,26 @@ async fn inner_extrude(length: f64, sketch_set: SketchSet, args: Args) -> Result
 | 
			
		||||
 | 
			
		||||
        // Disable the sketch mode.
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
        solids.push(do_post_extrude(sketch.clone(), length, args.clone()).await?);
 | 
			
		||||
        solids.push(do_post_extrude(sketch.clone(), length, exec_state, args.clone()).await?);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Ok(solids.into())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
pub(crate) async fn do_post_extrude(
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    length: f64,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
    // Bring the object to the front of the scene.
 | 
			
		||||
    // See: https://github.com/KittyCAD/modeling-app/issues/806
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        uuid::Uuid::new_v4(),
 | 
			
		||||
        exec_state.id_generator.next_uuid(),
 | 
			
		||||
        ModelingCmd::from(mcmd::ObjectBringToFront { object_id: sketch.id }),
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
@ -159,7 +169,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
 | 
			
		||||
 | 
			
		||||
    let solid3d_info = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetExtrusionFaceInfo {
 | 
			
		||||
                edge_id,
 | 
			
		||||
                object_id: sketch.id,
 | 
			
		||||
@ -192,7 +202,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
 | 
			
		||||
        // Instead, the Typescript codebases (which handles WebSocket sends when compiled via Wasm)
 | 
			
		||||
        // uses this to build the artifact graph, which the UI needs.
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
 | 
			
		||||
                edge_id: curve_id,
 | 
			
		||||
                object_id: sketch.id,
 | 
			
		||||
@ -202,7 +212,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
 | 
			
		||||
                edge_id: curve_id,
 | 
			
		||||
                object_id: sketch.id,
 | 
			
		||||
@ -216,7 +226,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
 | 
			
		||||
        sides: face_id_map,
 | 
			
		||||
        start_cap_id,
 | 
			
		||||
        end_cap_id,
 | 
			
		||||
    } = analyze_faces(&args, face_infos);
 | 
			
		||||
    } = analyze_faces(exec_state, &args, face_infos);
 | 
			
		||||
    // Iterate over the sketch.value array and add face_id to GeoMeta
 | 
			
		||||
    let new_value = sketch
 | 
			
		||||
        .value
 | 
			
		||||
@ -252,7 +262,7 @@ pub(crate) async fn do_post_extrude(sketch: Sketch, length: f64, args: Args) ->
 | 
			
		||||
 | 
			
		||||
                let extrude_surface = ExtrudeSurface::ExtrudePlane(crate::executor::ExtrudePlane {
 | 
			
		||||
                    // pushing this values with a fake face_id to make extrudes mock-execute safe
 | 
			
		||||
                    face_id: Uuid::new_v4(),
 | 
			
		||||
                    face_id: exec_state.id_generator.next_uuid(),
 | 
			
		||||
                    tag: path.get_base().tag.clone(),
 | 
			
		||||
                    geo_meta: GeoMeta {
 | 
			
		||||
                        id: path.get_base().geo_meta.id,
 | 
			
		||||
@ -291,15 +301,15 @@ struct Faces {
 | 
			
		||||
    start_cap_id: Option<Uuid>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn analyze_faces(args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
 | 
			
		||||
fn analyze_faces(exec_state: &mut ExecState, args: &Args, face_infos: Vec<ExtrusionFaceInfo>) -> Faces {
 | 
			
		||||
    let mut faces = Faces {
 | 
			
		||||
        sides: HashMap::with_capacity(face_infos.len()),
 | 
			
		||||
        ..Default::default()
 | 
			
		||||
    };
 | 
			
		||||
    if args.ctx.is_mock() {
 | 
			
		||||
        // Create fake IDs for start and end caps, to make extrudes mock-execute safe
 | 
			
		||||
        faces.start_cap_id = Some(Uuid::new_v4());
 | 
			
		||||
        faces.end_cap_id = Some(Uuid::new_v4());
 | 
			
		||||
        faces.start_cap_id = Some(exec_state.id_generator.next_uuid());
 | 
			
		||||
        faces.end_cap_id = Some(exec_state.id_generator.next_uuid());
 | 
			
		||||
    }
 | 
			
		||||
    for face_info in face_infos {
 | 
			
		||||
        match face_info.cap {
 | 
			
		||||
 | 
			
		||||
@ -142,7 +142,7 @@ async fn inner_fillet(
 | 
			
		||||
    for edge_tag in data.tags {
 | 
			
		||||
        let edge_id = edge_tag.get_engine_id(exec_state, &args)?;
 | 
			
		||||
 | 
			
		||||
        let id = uuid::Uuid::new_v4();
 | 
			
		||||
        let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
        args.batch_end_cmd(
 | 
			
		||||
            id,
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dFilletEdge {
 | 
			
		||||
@ -229,15 +229,16 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> {
 | 
			
		||||
    if args.ctx.is_mock() {
 | 
			
		||||
        return Ok(Uuid::new_v4());
 | 
			
		||||
        return Ok(exec_state.id_generator.next_uuid());
 | 
			
		||||
    }
 | 
			
		||||
    let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
 | 
			
		||||
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
 | 
			
		||||
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            id,
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetOppositeEdge {
 | 
			
		||||
                edge_id: tagged_path.id,
 | 
			
		||||
                object_id: tagged_path.sketch,
 | 
			
		||||
@ -310,15 +311,16 @@ async fn inner_get_next_adjacent_edge(
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Uuid, KclError> {
 | 
			
		||||
    if args.ctx.is_mock() {
 | 
			
		||||
        return Ok(Uuid::new_v4());
 | 
			
		||||
        return Ok(exec_state.id_generator.next_uuid());
 | 
			
		||||
    }
 | 
			
		||||
    let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
 | 
			
		||||
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
 | 
			
		||||
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            id,
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetNextAdjacentEdge {
 | 
			
		||||
                edge_id: tagged_path.id,
 | 
			
		||||
                object_id: tagged_path.sketch,
 | 
			
		||||
@ -399,15 +401,16 @@ async fn inner_get_previous_adjacent_edge(
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Uuid, KclError> {
 | 
			
		||||
    if args.ctx.is_mock() {
 | 
			
		||||
        return Ok(Uuid::new_v4());
 | 
			
		||||
        return Ok(exec_state.id_generator.next_uuid());
 | 
			
		||||
    }
 | 
			
		||||
    let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?;
 | 
			
		||||
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    let tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
 | 
			
		||||
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            id,
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid3dGetPrevAdjacentEdge {
 | 
			
		||||
                edge_id: tagged_path.id,
 | 
			
		||||
                object_id: tagged_path.sketch,
 | 
			
		||||
 | 
			
		||||
@ -32,10 +32,10 @@ pub struct HelixData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a helix on a cylinder.
 | 
			
		||||
pub async fn helix(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, solid): (HelixData, Box<Solid>) = args.get_data_and_solid()?;
 | 
			
		||||
 | 
			
		||||
    let solid = inner_helix(data, solid, args).await?;
 | 
			
		||||
    let solid = inner_helix(data, solid, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::Solid(solid))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -54,8 +54,13 @@ pub async fn helix(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "helix",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_helix(data: HelixData, solid: Box<Solid>, args: Args) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
async fn inner_helix(
 | 
			
		||||
    data: HelixData,
 | 
			
		||||
    solid: Box<Solid>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
        ModelingCmd::from(mcmd::EntityMakeHelix {
 | 
			
		||||
 | 
			
		||||
@ -126,10 +126,10 @@ impl From<ImportFormat> for InputFormat {
 | 
			
		||||
///
 | 
			
		||||
/// Import paths are relative to the current project directory. This only works in the desktop app
 | 
			
		||||
/// not in browser.
 | 
			
		||||
pub async fn import(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
 | 
			
		||||
 | 
			
		||||
    let imported_geometry = inner_import(file_path, options, args).await?;
 | 
			
		||||
    let imported_geometry = inner_import(file_path, options, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::ImportedGeometry(imported_geometry))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -170,6 +170,7 @@ pub async fn import(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
async fn inner_import(
 | 
			
		||||
    file_path: String,
 | 
			
		||||
    options: Option<ImportFormat>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<ImportedGeometry, KclError> {
 | 
			
		||||
    if file_path.is_empty() {
 | 
			
		||||
@ -286,13 +287,13 @@ async fn inner_import(
 | 
			
		||||
 | 
			
		||||
    if args.ctx.is_mock() {
 | 
			
		||||
        return Ok(ImportedGeometry {
 | 
			
		||||
            id: uuid::Uuid::new_v4(),
 | 
			
		||||
            id: exec_state.id_generator.next_uuid(),
 | 
			
		||||
            value: import_files.iter().map(|f| f.path.to_string()).collect(),
 | 
			
		||||
            meta: vec![args.source_range.into()],
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
            id,
 | 
			
		||||
 | 
			
		||||
@ -50,10 +50,10 @@ impl Default for LoftData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Create a 3D surface or solid by interpolating between two or more sketches.
 | 
			
		||||
pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (sketches, data): (Vec<Sketch>, Option<LoftData>) = args.get_sketches_and_data()?;
 | 
			
		||||
 | 
			
		||||
    let solid = inner_loft(sketches, data, args).await?;
 | 
			
		||||
    let solid = inner_loft(sketches, data, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::Solid(solid))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -135,7 +135,12 @@ pub async fn loft(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "loft",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
async fn inner_loft(
 | 
			
		||||
    sketches: Vec<Sketch>,
 | 
			
		||||
    data: Option<LoftData>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Box<Solid>, KclError> {
 | 
			
		||||
    // Make sure we have at least two sketches.
 | 
			
		||||
    if sketches.len() < 2 {
 | 
			
		||||
        return Err(KclError::Semantic(KclErrorDetails {
 | 
			
		||||
@ -150,7 +155,7 @@ async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -
 | 
			
		||||
    // Get the loft data.
 | 
			
		||||
    let data = data.unwrap_or_default();
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
        ModelingCmd::from(mcmd::Loft {
 | 
			
		||||
@ -166,5 +171,5 @@ async fn inner_loft(sketches: Vec<Sketch>, data: Option<LoftData>, args: Args) -
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    // Using the first sketch as the base curve, idk we might want to change this later.
 | 
			
		||||
    do_post_extrude(sketches[0].clone(), 0.0, args).await
 | 
			
		||||
    do_post_extrude(sketches[0].clone(), 0.0, exec_state, args).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -121,7 +121,7 @@ async fn inner_mirror_2d(
 | 
			
		||||
            let (axis, origin) = axis.axis_and_origin()?;
 | 
			
		||||
 | 
			
		||||
            args.batch_modeling_cmd(
 | 
			
		||||
                uuid::Uuid::new_v4(),
 | 
			
		||||
                exec_state.id_generator.next_uuid(),
 | 
			
		||||
                ModelingCmd::from(mcmd::EntityMirror {
 | 
			
		||||
                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
 | 
			
		||||
                    axis,
 | 
			
		||||
@ -134,7 +134,7 @@ async fn inner_mirror_2d(
 | 
			
		||||
            let edge_id = edge.get_engine_id(exec_state, &args)?;
 | 
			
		||||
 | 
			
		||||
            args.batch_modeling_cmd(
 | 
			
		||||
                uuid::Uuid::new_v4(),
 | 
			
		||||
                exec_state.id_generator.next_uuid(),
 | 
			
		||||
                ModelingCmd::from(mcmd::EntityMirrorAcrossEdge {
 | 
			
		||||
                    ids: starting_sketches.iter().map(|sketch| sketch.id).collect(),
 | 
			
		||||
                    edge_id,
 | 
			
		||||
 | 
			
		||||
@ -296,7 +296,7 @@ async fn inner_pattern_transform<'a>(
 | 
			
		||||
 | 
			
		||||
    let mut solids = Vec::new();
 | 
			
		||||
    for e in starting_solids {
 | 
			
		||||
        let new_solids = send_pattern_transform(transform.clone(), &e, args).await?;
 | 
			
		||||
        let new_solids = send_pattern_transform(transform.clone(), &e, exec_state, args).await?;
 | 
			
		||||
        solids.extend(new_solids);
 | 
			
		||||
    }
 | 
			
		||||
    Ok(solids)
 | 
			
		||||
@ -307,9 +307,10 @@ async fn send_pattern_transform(
 | 
			
		||||
    // https://github.com/KittyCAD/modeling-app/issues/2821
 | 
			
		||||
    transform: Vec<Transform>,
 | 
			
		||||
    solid: &Solid,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: &Args,
 | 
			
		||||
) -> Result<Vec<Box<Solid>>, KclError> {
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
@ -473,7 +474,7 @@ mod tests {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A linear pattern on a 2D sketch.
 | 
			
		||||
pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch_set): (LinearPattern2dData, SketchSet) = args.get_data_and_sketch_set()?;
 | 
			
		||||
 | 
			
		||||
    if data.axis == [0.0, 0.0] {
 | 
			
		||||
@ -485,7 +486,7 @@ pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Resul
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let sketches = inner_pattern_linear_2d(data, sketch_set, args).await?;
 | 
			
		||||
    let sketches = inner_pattern_linear_2d(data, sketch_set, exec_state, args).await?;
 | 
			
		||||
    Ok(sketches.into())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -509,6 +510,7 @@ pub async fn pattern_linear_2d(_exec_state: &mut ExecState, args: Args) -> Resul
 | 
			
		||||
async fn inner_pattern_linear_2d(
 | 
			
		||||
    data: LinearPattern2dData,
 | 
			
		||||
    sketch_set: SketchSet,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Vec<Box<Sketch>>, KclError> {
 | 
			
		||||
    let starting_sketches: Vec<Box<Sketch>> = sketch_set.into();
 | 
			
		||||
@ -522,6 +524,7 @@ async fn inner_pattern_linear_2d(
 | 
			
		||||
        let geometries = pattern_linear(
 | 
			
		||||
            LinearPattern::TwoD(data.clone()),
 | 
			
		||||
            Geometry::Sketch(sketch.clone()),
 | 
			
		||||
            exec_state,
 | 
			
		||||
            args.clone(),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -600,6 +603,7 @@ async fn inner_pattern_linear_3d(
 | 
			
		||||
        let geometries = pattern_linear(
 | 
			
		||||
            LinearPattern::ThreeD(data.clone()),
 | 
			
		||||
            Geometry::Solid(solid.clone()),
 | 
			
		||||
            exec_state,
 | 
			
		||||
            args.clone(),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -617,8 +621,13 @@ async fn inner_pattern_linear_3d(
 | 
			
		||||
    Ok(solids)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn pattern_linear(data: LinearPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
async fn pattern_linear(
 | 
			
		||||
    data: LinearPattern,
 | 
			
		||||
    geometry: Geometry,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Geometries, KclError> {
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    let resp = args
 | 
			
		||||
        .send_modeling_cmd(
 | 
			
		||||
@ -745,10 +754,10 @@ impl CircularPattern {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// A circular pattern on a 2D sketch.
 | 
			
		||||
pub async fn pattern_circular_2d(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch_set): (CircularPattern2dData, SketchSet) = args.get_data_and_sketch_set()?;
 | 
			
		||||
 | 
			
		||||
    let sketches = inner_pattern_circular_2d(data, sketch_set, args).await?;
 | 
			
		||||
    let sketches = inner_pattern_circular_2d(data, sketch_set, exec_state, args).await?;
 | 
			
		||||
    Ok(sketches.into())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -779,6 +788,7 @@ pub async fn pattern_circular_2d(_exec_state: &mut ExecState, args: Args) -> Res
 | 
			
		||||
async fn inner_pattern_circular_2d(
 | 
			
		||||
    data: CircularPattern2dData,
 | 
			
		||||
    sketch_set: SketchSet,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Vec<Box<Sketch>>, KclError> {
 | 
			
		||||
    let starting_sketches: Vec<Box<Sketch>> = sketch_set.into();
 | 
			
		||||
@ -792,6 +802,7 @@ async fn inner_pattern_circular_2d(
 | 
			
		||||
        let geometries = pattern_circular(
 | 
			
		||||
            CircularPattern::TwoD(data.clone()),
 | 
			
		||||
            Geometry::Sketch(sketch.clone()),
 | 
			
		||||
            exec_state,
 | 
			
		||||
            args.clone(),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -861,6 +872,7 @@ async fn inner_pattern_circular_3d(
 | 
			
		||||
        let geometries = pattern_circular(
 | 
			
		||||
            CircularPattern::ThreeD(data.clone()),
 | 
			
		||||
            Geometry::Solid(solid.clone()),
 | 
			
		||||
            exec_state,
 | 
			
		||||
            args.clone(),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -878,8 +890,13 @@ async fn inner_pattern_circular_3d(
 | 
			
		||||
    Ok(solids)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn pattern_circular(data: CircularPattern, geometry: Geometry, args: Args) -> Result<Geometries, KclError> {
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
async fn pattern_circular(
 | 
			
		||||
    data: CircularPattern,
 | 
			
		||||
    geometry: Geometry,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Geometries, KclError> {
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    let center = data.center();
 | 
			
		||||
    let resp = args
 | 
			
		||||
 | 
			
		||||
@ -48,10 +48,10 @@ impl From<StandardPlane> for PlaneData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Offset a plane by a distance along its normal.
 | 
			
		||||
pub async fn offset_plane(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?;
 | 
			
		||||
 | 
			
		||||
    let plane = inner_offset_plane(std_plane, offset).await?;
 | 
			
		||||
    let plane = inner_offset_plane(std_plane, offset, exec_state).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(KclValue::UserVal(UserVal::new(
 | 
			
		||||
        vec![Metadata {
 | 
			
		||||
@ -132,11 +132,15 @@ pub async fn offset_plane(_exec_state: &mut ExecState, args: Args) -> Result<Kcl
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "offsetPlane",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_offset_plane(std_plane: StandardPlane, offset: f64) -> Result<PlaneData, KclError> {
 | 
			
		||||
async fn inner_offset_plane(
 | 
			
		||||
    std_plane: StandardPlane,
 | 
			
		||||
    offset: f64,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
) -> Result<PlaneData, KclError> {
 | 
			
		||||
    // Convert to the plane type.
 | 
			
		||||
    let plane_data: PlaneData = std_plane.into();
 | 
			
		||||
    // Convert to a plane.
 | 
			
		||||
    let mut plane = Plane::from(plane_data);
 | 
			
		||||
    let mut plane = Plane::from_plane_data(plane_data, exec_state);
 | 
			
		||||
 | 
			
		||||
    match std_plane {
 | 
			
		||||
        StandardPlane::XY => {
 | 
			
		||||
 | 
			
		||||
@ -263,7 +263,7 @@ async fn inner_revolve(
 | 
			
		||||
 | 
			
		||||
    let angle = Angle::from_degrees(data.angle.unwrap_or(360.0));
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    match data.axis {
 | 
			
		||||
        AxisOrEdgeReference::Axis(axis) => {
 | 
			
		||||
            let (axis, origin) = axis.axis_and_origin()?;
 | 
			
		||||
@ -295,7 +295,7 @@ async fn inner_revolve(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    do_post_extrude(sketch, 0.0, args).await
 | 
			
		||||
    do_post_extrude(sketch, 0.0, exec_state, args).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 | 
			
		||||
@ -97,7 +97,7 @@ async fn inner_circle(
 | 
			
		||||
    let angle_start = Angle::zero();
 | 
			
		||||
    let angle_end = Angle::turn();
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
 | 
			
		||||
@ -229,7 +229,7 @@ async fn inner_shell(
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        uuid::Uuid::new_v4(),
 | 
			
		||||
        exec_state.id_generator.next_uuid(),
 | 
			
		||||
        ModelingCmd::from(mcmd::Solid3dShellFace {
 | 
			
		||||
            hollow: false,
 | 
			
		||||
            face_ids,
 | 
			
		||||
@ -314,7 +314,7 @@ async fn inner_hollow(
 | 
			
		||||
    args.flush_batch_for_solid_set(exec_state, solid.clone().into()).await?;
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        uuid::Uuid::new_v4(),
 | 
			
		||||
        exec_state.id_generator.next_uuid(),
 | 
			
		||||
        ModelingCmd::from(mcmd::Solid3dShellFace {
 | 
			
		||||
            hollow: true,
 | 
			
		||||
            face_ids: Vec::new(), // This is empty because we want to hollow the entire object.
 | 
			
		||||
 | 
			
		||||
@ -16,8 +16,8 @@ use crate::{
 | 
			
		||||
    ast::types::TagDeclarator,
 | 
			
		||||
    errors::{KclError, KclErrorDetails},
 | 
			
		||||
    executor::{
 | 
			
		||||
        BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, PlaneType, Point2d, Point3d, Sketch, SketchSet,
 | 
			
		||||
        SketchSurface, Solid, TagEngineInfo, TagIdentifier, UserVal,
 | 
			
		||||
        BasePath, ExecState, Face, GeoMeta, KclValue, Path, Plane, Point2d, Point3d, Sketch, SketchSet, SketchSurface,
 | 
			
		||||
        Solid, TagEngineInfo, TagIdentifier, UserVal,
 | 
			
		||||
    },
 | 
			
		||||
    std::{
 | 
			
		||||
        utils::{
 | 
			
		||||
@ -93,10 +93,10 @@ pub enum StartOrEnd {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line to a point.
 | 
			
		||||
pub async fn line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to(to, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to(to, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -119,10 +119,11 @@ async fn inner_line_to(
 | 
			
		||||
    to: [f64; 2],
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
@ -159,10 +160,10 @@ async fn inner_line_to(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line to a point on the x-axis.
 | 
			
		||||
pub async fn x_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn x_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_x_line_to(to, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_x_line_to(to, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -192,19 +193,25 @@ pub async fn x_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclVal
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "xLineTo",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_x_line_to(to: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
async fn inner_x_line_to(
 | 
			
		||||
    to: f64,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to([to, from.y], sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to([to, from.y], sketch, tag, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line to a point on the y-axis.
 | 
			
		||||
pub async fn y_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn y_line_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (to, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_y_line_to(to, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_y_line_to(to, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -227,18 +234,24 @@ pub async fn y_line_to(_exec_state: &mut ExecState, args: Args) -> Result<KclVal
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "yLineTo",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_y_line_to(to: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
async fn inner_y_line_to(
 | 
			
		||||
    to: f64,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to([from.x, to], sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to([from.x, to], sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line.
 | 
			
		||||
pub async fn line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line(delta, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line(delta, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -273,12 +286,13 @@ async fn inner_line(
 | 
			
		||||
    delta: [f64; 2],
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
    let to = [from.x + delta[0], from.y + delta[1]];
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
@ -315,10 +329,10 @@ async fn inner_line(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line on the x-axis.
 | 
			
		||||
pub async fn x_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_x_line(length, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_x_line(length, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -347,15 +361,21 @@ pub async fn x_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "xLine",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_x_line(length: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
    inner_line([length, 0.0], sketch, tag, args).await
 | 
			
		||||
async fn inner_x_line(
 | 
			
		||||
    length: f64,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    inner_line([length, 0.0], sketch, tag, exec_state, args).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a line on the y-axis.
 | 
			
		||||
pub async fn y_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (length, sketch, tag): (f64, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_y_line(length, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_y_line(length, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -379,8 +399,14 @@ pub async fn y_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "yLine",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_y_line(length: f64, sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
    inner_line([0.0, length], sketch, tag, args).await
 | 
			
		||||
async fn inner_y_line(
 | 
			
		||||
    length: f64,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    inner_line([0.0, length], sketch, tag, exec_state, args).await
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Data to draw an angled line.
 | 
			
		||||
@ -400,10 +426,10 @@ pub enum AngledLineData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an angled line.
 | 
			
		||||
pub async fn angled_line(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_angled_line(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_angled_line(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -431,6 +457,7 @@ async fn inner_angled_line(
 | 
			
		||||
    data: AngledLineData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
@ -448,7 +475,7 @@ async fn inner_angled_line(
 | 
			
		||||
 | 
			
		||||
    let to: [f64; 2] = [from.x + delta[0], from.y + delta[1]];
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
@ -484,10 +511,10 @@ async fn inner_angled_line(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an angled line of a given x length.
 | 
			
		||||
pub async fn angled_line_of_x_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn angled_line_of_x_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_angled_line_of_x_length(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -511,6 +538,7 @@ async fn inner_angled_line_of_x_length(
 | 
			
		||||
    data: AngledLineData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let (angle, length) = match data {
 | 
			
		||||
@ -534,7 +562,7 @@ async fn inner_angled_line_of_x_length(
 | 
			
		||||
 | 
			
		||||
    let to = get_y_component(Angle::from_degrees(angle), length);
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line(to.into(), sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line(to.into(), sketch, tag, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
@ -551,10 +579,10 @@ pub struct AngledLineToData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an angled line to a given x coordinate.
 | 
			
		||||
pub async fn angled_line_to_x(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn angled_line_to_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_angled_line_to_x(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_angled_line_to_x(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -579,6 +607,7 @@ async fn inner_angled_line_to_x(
 | 
			
		||||
    data: AngledLineToData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
@ -602,15 +631,15 @@ async fn inner_angled_line_to_x(
 | 
			
		||||
    let y_component = x_component * f64::tan(angle.to_radians());
 | 
			
		||||
    let y_to = from.y + y_component;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to([x_to, y_to], sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to([x_to, y_to], sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an angled line of a given y length.
 | 
			
		||||
pub async fn angled_line_of_y_length(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn angled_line_of_y_length(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (AngledLineData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_angled_line_of_y_length(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
@ -637,6 +666,7 @@ async fn inner_angled_line_of_y_length(
 | 
			
		||||
    data: AngledLineData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let (angle, length) = match data {
 | 
			
		||||
@ -660,16 +690,16 @@ async fn inner_angled_line_of_y_length(
 | 
			
		||||
 | 
			
		||||
    let to = get_x_component(Angle::from_degrees(angle), length);
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line(to.into(), sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line(to.into(), sketch, tag, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an angled line to a given y coordinate.
 | 
			
		||||
pub async fn angled_line_to_y(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn angled_line_to_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (AngledLineToData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_angled_line_to_y(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_angled_line_to_y(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -694,6 +724,7 @@ async fn inner_angled_line_to_y(
 | 
			
		||||
    data: AngledLineToData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
@ -717,7 +748,7 @@ async fn inner_angled_line_to_y(
 | 
			
		||||
    let x_component = y_component / f64::tan(angle.to_radians());
 | 
			
		||||
    let x_to = from.x + x_component;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to([x_to, y_to], sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to([x_to, y_to], sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -788,7 +819,7 @@ async fn inner_angled_line_that_intersects(
 | 
			
		||||
        from,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_line_to(to.into(), sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_line_to(to.into(), sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(new_sketch)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -891,82 +922,6 @@ pub enum PlaneData {
 | 
			
		||||
    },
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<PlaneData> for Plane {
 | 
			
		||||
    fn from(value: PlaneData) -> Self {
 | 
			
		||||
        let id = uuid::Uuid::new_v4();
 | 
			
		||||
        match value {
 | 
			
		||||
            PlaneData::XY => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                value: PlaneType::XY,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::NegXY => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 0.0, -1.0),
 | 
			
		||||
                value: PlaneType::XY,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::XZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, -1.0, 0.0),
 | 
			
		||||
                value: PlaneType::XZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::NegXZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(-1.0, 0.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                value: PlaneType::XZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::YZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(1.0, 0.0, 0.0),
 | 
			
		||||
                value: PlaneType::YZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::NegYZ => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: Point3d::new(0.0, 0.0, 0.0),
 | 
			
		||||
                x_axis: Point3d::new(0.0, 1.0, 0.0),
 | 
			
		||||
                y_axis: Point3d::new(0.0, 0.0, 1.0),
 | 
			
		||||
                z_axis: Point3d::new(-1.0, 0.0, 0.0),
 | 
			
		||||
                value: PlaneType::YZ,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
            PlaneData::Plane {
 | 
			
		||||
                origin,
 | 
			
		||||
                x_axis,
 | 
			
		||||
                y_axis,
 | 
			
		||||
                z_axis,
 | 
			
		||||
            } => Plane {
 | 
			
		||||
                id,
 | 
			
		||||
                origin: *origin,
 | 
			
		||||
                x_axis: *x_axis,
 | 
			
		||||
                y_axis: *y_axis,
 | 
			
		||||
                z_axis: *z_axis,
 | 
			
		||||
                value: PlaneType::Custom,
 | 
			
		||||
                meta: vec![],
 | 
			
		||||
            },
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Start a sketch on a specific plane or face.
 | 
			
		||||
pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, tag): (SketchData, Option<FaceTag>) = args.get_data_and_optional_tag()?;
 | 
			
		||||
@ -1089,7 +1044,7 @@ async fn inner_start_sketch_on(
 | 
			
		||||
) -> Result<SketchSurface, KclError> {
 | 
			
		||||
    match data {
 | 
			
		||||
        SketchData::Plane(plane_data) => {
 | 
			
		||||
            let plane = start_sketch_on_plane(plane_data, args).await?;
 | 
			
		||||
            let plane = start_sketch_on_plane(plane_data, exec_state, args).await?;
 | 
			
		||||
            Ok(SketchSurface::Plane(plane))
 | 
			
		||||
        }
 | 
			
		||||
        SketchData::Solid(solid) => {
 | 
			
		||||
@ -1125,11 +1080,19 @@ async fn start_sketch_on_face(
 | 
			
		||||
    }))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn start_sketch_on_plane(data: PlaneData, args: &Args) -> Result<Box<Plane>, KclError> {
 | 
			
		||||
    let mut plane: Plane = data.clone().into();
 | 
			
		||||
async fn start_sketch_on_plane(
 | 
			
		||||
    data: PlaneData,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: &Args,
 | 
			
		||||
) -> Result<Box<Plane>, KclError> {
 | 
			
		||||
    let mut plane = Plane::from_plane_data(data.clone(), exec_state);
 | 
			
		||||
 | 
			
		||||
    // Get the default planes.
 | 
			
		||||
    let default_planes = args.ctx.engine.default_planes(args.source_range).await?;
 | 
			
		||||
    let default_planes = args
 | 
			
		||||
        .ctx
 | 
			
		||||
        .engine
 | 
			
		||||
        .default_planes(&mut exec_state.id_generator, args.source_range)
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    plane.id = match data {
 | 
			
		||||
        PlaneData::XY => default_planes.xy,
 | 
			
		||||
@ -1145,7 +1108,7 @@ async fn start_sketch_on_plane(data: PlaneData, args: &Args) -> Result<Box<Plane
 | 
			
		||||
            z_axis: _,
 | 
			
		||||
        } => {
 | 
			
		||||
            // Create the custom plane on the fly.
 | 
			
		||||
            let id = uuid::Uuid::new_v4();
 | 
			
		||||
            let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
            args.batch_modeling_cmd(
 | 
			
		||||
                id,
 | 
			
		||||
                ModelingCmd::from(mcmd::MakePlane {
 | 
			
		||||
@ -1228,7 +1191,7 @@ pub(crate) async fn inner_start_profile_at(
 | 
			
		||||
 | 
			
		||||
    // Enter sketch mode on the surface.
 | 
			
		||||
    // We call this here so you can reuse the sketch surface for multiple sketches.
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
        ModelingCmd::from(mcmd::EnableSketchMode {
 | 
			
		||||
@ -1246,8 +1209,8 @@ pub(crate) async fn inner_start_profile_at(
 | 
			
		||||
    )
 | 
			
		||||
    .await?;
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let path_id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    let path_id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(path_id, ModelingCmd::from(mcmd::StartPath {}))
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -1375,10 +1338,10 @@ pub(crate) fn inner_profile_start(sketch: Sketch) -> Result<[f64; 2], KclError>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Close the current sketch.
 | 
			
		||||
pub async fn close(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (sketch, tag): (Sketch, Option<TagDeclarator>) = args.get_sketch_and_optional_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_close(sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_close(sketch, tag, exec_state, args).await?;
 | 
			
		||||
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
@ -1407,11 +1370,16 @@ pub async fn close(_exec_state: &mut ExecState, args: Args) -> Result<KclValue,
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "close",
 | 
			
		||||
}]
 | 
			
		||||
pub(crate) async fn inner_close(sketch: Sketch, tag: Option<TagDeclarator>, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
pub(crate) async fn inner_close(
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
    let to: Point2d = sketch.start.from.into();
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(id, ModelingCmd::from(mcmd::ClosePath { path_id: sketch.id }))
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -1420,7 +1388,7 @@ pub(crate) async fn inner_close(sketch: Sketch, tag: Option<TagDeclarator>, args
 | 
			
		||||
    if let SketchSurface::Plane(_) = sketch.on {
 | 
			
		||||
        // We were on a plane, disable the sketch mode.
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::SketchModeDisable(mcmd::SketchModeDisable {}),
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
@ -1478,10 +1446,10 @@ pub enum ArcData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw an arc.
 | 
			
		||||
pub async fn arc(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (ArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_arc(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_arc(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1514,6 +1482,7 @@ pub(crate) async fn inner_arc(
 | 
			
		||||
    data: ArcData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from: Point2d = sketch.current_pen_position()?;
 | 
			
		||||
@ -1542,7 +1511,7 @@ pub(crate) async fn inner_arc(
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
@ -1596,10 +1565,10 @@ pub enum TangentialArcData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a tangential arc.
 | 
			
		||||
pub async fn tangential_arc(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (TangentialArcData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_tangential_arc(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_tangential_arc(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1633,6 +1602,7 @@ async fn inner_tangential_arc(
 | 
			
		||||
    data: TangentialArcData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from: Point2d = sketch.current_pen_position()?;
 | 
			
		||||
@ -1644,7 +1614,7 @@ async fn inner_tangential_arc(
 | 
			
		||||
        tangent_info.center_or_tangent_point
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    let (center, to, ccw) = match data {
 | 
			
		||||
        TangentialArcData::RadiusAndOffset { radius, offset } => {
 | 
			
		||||
@ -1723,18 +1693,18 @@ fn tan_arc_to(sketch: &Sketch, to: &[f64; 2]) -> ModelingCmd {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a tangential arc to a specific point.
 | 
			
		||||
pub async fn tangential_arc_to(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn tangential_arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (to, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_tangential_arc_to(to, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_tangential_arc_to(to, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a tangential arc to point some distance away..
 | 
			
		||||
pub async fn tangential_arc_to_relative(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn tangential_arc_to_relative(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (delta, sketch, tag): ([f64; 2], Sketch, Option<TagDeclarator>) = super::args::FromArgs::from_args(&args, 0)?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_tangential_arc_to_relative(delta, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1762,6 +1732,7 @@ async fn inner_tangential_arc_to(
 | 
			
		||||
    to: [f64; 2],
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from: Point2d = sketch.current_pen_position()?;
 | 
			
		||||
@ -1780,7 +1751,7 @@ async fn inner_tangential_arc_to(
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    let delta = [to_x - from.x, to_y - from.y];
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
 | 
			
		||||
 | 
			
		||||
    let current_path = Path::TangentialArcTo {
 | 
			
		||||
@ -1831,6 +1802,7 @@ async fn inner_tangential_arc_to_relative(
 | 
			
		||||
    delta: [f64; 2],
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from: Point2d = sketch.current_pen_position()?;
 | 
			
		||||
@ -1864,7 +1836,7 @@ async fn inner_tangential_arc_to_relative(
 | 
			
		||||
        }));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
    args.batch_modeling_cmd(id, tan_arc_to(&sketch, &delta)).await?;
 | 
			
		||||
 | 
			
		||||
    let current_path = Path::TangentialArcTo {
 | 
			
		||||
@ -1905,10 +1877,10 @@ pub struct BezierData {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Draw a bezier curve.
 | 
			
		||||
pub async fn bezier_curve(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (data, sketch, tag): (BezierData, Sketch, Option<TagDeclarator>) = args.get_data_and_sketch_and_tag()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_bezier_curve(data, sketch, tag, args).await?;
 | 
			
		||||
    let new_sketch = inner_bezier_curve(data, sketch, tag, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1937,6 +1909,7 @@ async fn inner_bezier_curve(
 | 
			
		||||
    data: BezierData,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    tag: Option<TagDeclarator>,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let from = sketch.current_pen_position()?;
 | 
			
		||||
@ -1945,7 +1918,7 @@ async fn inner_bezier_curve(
 | 
			
		||||
    let delta = data.to;
 | 
			
		||||
    let to = [from.x + data.to[0], from.y + data.to[1]];
 | 
			
		||||
 | 
			
		||||
    let id = uuid::Uuid::new_v4();
 | 
			
		||||
    let id = exec_state.id_generator.next_uuid();
 | 
			
		||||
 | 
			
		||||
    args.batch_modeling_cmd(
 | 
			
		||||
        id,
 | 
			
		||||
@ -1984,10 +1957,10 @@ async fn inner_bezier_curve(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Use a sketch to cut a hole in another sketch.
 | 
			
		||||
pub async fn hole(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
pub async fn hole(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
 | 
			
		||||
    let (hole_sketch, sketch): (SketchSet, Sketch) = args.get_sketches()?;
 | 
			
		||||
 | 
			
		||||
    let new_sketch = inner_hole(hole_sketch, sketch, args).await?;
 | 
			
		||||
    let new_sketch = inner_hole(hole_sketch, sketch, exec_state, args).await?;
 | 
			
		||||
    Ok(KclValue::new_user_val(new_sketch.meta.clone(), new_sketch))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2025,11 +1998,16 @@ pub async fn hole(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
 | 
			
		||||
#[stdlib {
 | 
			
		||||
    name = "hole",
 | 
			
		||||
}]
 | 
			
		||||
async fn inner_hole(hole_sketch: SketchSet, sketch: Sketch, args: Args) -> Result<Sketch, KclError> {
 | 
			
		||||
async fn inner_hole(
 | 
			
		||||
    hole_sketch: SketchSet,
 | 
			
		||||
    sketch: Sketch,
 | 
			
		||||
    exec_state: &mut ExecState,
 | 
			
		||||
    args: Args,
 | 
			
		||||
) -> Result<Sketch, KclError> {
 | 
			
		||||
    let hole_sketches: Vec<Sketch> = hole_sketch.into();
 | 
			
		||||
    for hole_sketch in hole_sketches {
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::Solid2dAddHole {
 | 
			
		||||
                object_id: sketch.id,
 | 
			
		||||
                hole_id: hole_sketch.id,
 | 
			
		||||
@ -2040,7 +2018,7 @@ async fn inner_hole(hole_sketch: SketchSet, sketch: Sketch, args: Args) -> Resul
 | 
			
		||||
        // suggestion (mike)
 | 
			
		||||
        // we also hide the source hole since its essentially "consumed" by this operation
 | 
			
		||||
        args.batch_modeling_cmd(
 | 
			
		||||
            uuid::Uuid::new_v4(),
 | 
			
		||||
            exec_state.id_generator.next_uuid(),
 | 
			
		||||
            ModelingCmd::from(mcmd::ObjectVisible {
 | 
			
		||||
                object_id: hole_sketch.id,
 | 
			
		||||
                hidden: true,
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
//! Types used to send data to the test server.
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    executor::{ExecutorContext, ExecutorSettings},
 | 
			
		||||
    executor::{ExecutorContext, ExecutorSettings, IdGenerator},
 | 
			
		||||
    settings::types::UnitLength,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -29,7 +29,9 @@ async fn do_execute_and_snapshot(ctx: &ExecutorContext, code: &str) -> anyhow::R
 | 
			
		||||
    let parser = crate::parser::Parser::new(tokens);
 | 
			
		||||
    let program = parser.ast()?;
 | 
			
		||||
 | 
			
		||||
    let snapshot = ctx.execute_and_prepare_snapshot(&program).await?;
 | 
			
		||||
    let snapshot = ctx
 | 
			
		||||
        .execute_and_prepare_snapshot(&program, IdGenerator::default())
 | 
			
		||||
        .await?;
 | 
			
		||||
 | 
			
		||||
    // Create a temporary file to write the output to.
 | 
			
		||||
    let output_file = std::env::temp_dir().join(format!("kcl_output_{}.png", uuid::Uuid::new_v4()));
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ use wasm_bindgen::prelude::*;
 | 
			
		||||
pub async fn execute_wasm(
 | 
			
		||||
    program_str: &str,
 | 
			
		||||
    memory_str: &str,
 | 
			
		||||
    id_generator_str: &str,
 | 
			
		||||
    units: &str,
 | 
			
		||||
    engine_manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
 | 
			
		||||
    fs_manager: kcl_lib::fs::wasm::FileSystemManager,
 | 
			
		||||
@ -26,6 +27,8 @@ pub async fn execute_wasm(
 | 
			
		||||
 | 
			
		||||
    let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
 | 
			
		||||
    let memory: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
 | 
			
		||||
    let id_generator: kcl_lib::executor::IdGenerator =
 | 
			
		||||
        serde_json::from_str(id_generator_str).map_err(|e| e.to_string())?;
 | 
			
		||||
    let units = kcl_lib::settings::types::UnitLength::from_str(units).map_err(|e| e.to_string())?;
 | 
			
		||||
 | 
			
		||||
    let engine: std::sync::Arc<Box<dyn kcl_lib::engine::EngineManager>> = if is_mock {
 | 
			
		||||
@ -58,13 +61,16 @@ pub async fn execute_wasm(
 | 
			
		||||
        context_type,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let exec_state = ctx.run(&program, Some(memory)).await.map_err(String::from)?;
 | 
			
		||||
    let exec_state = ctx
 | 
			
		||||
        .run(&program, Some(memory), id_generator)
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(String::from)?;
 | 
			
		||||
 | 
			
		||||
    // The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
 | 
			
		||||
    // gloo-serialize crate instead.
 | 
			
		||||
    // DO NOT USE serde_wasm_bindgen::to_value(&memory).map_err(|e| e.to_string())
 | 
			
		||||
    // DO NOT USE serde_wasm_bindgen::to_value(&exec_state).map_err(|e| e.to_string())
 | 
			
		||||
    // it will break the frontend.
 | 
			
		||||
    JsValue::from_serde(&exec_state.memory).map_err(|e| e.to_string())
 | 
			
		||||
    JsValue::from_serde(&exec_state).map_err(|e| e.to_string())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// wasm_bindgen wrapper for execute
 | 
			
		||||
@ -93,7 +99,7 @@ pub async fn make_default_planes(
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(|e| format!("{:?}", e))?;
 | 
			
		||||
    let default_planes = engine
 | 
			
		||||
        .new_default_planes(Default::default())
 | 
			
		||||
        .new_default_planes(&mut kcl_lib::executor::IdGenerator::default(), Default::default())
 | 
			
		||||
        .await
 | 
			
		||||
        .map_err(String::from)?;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
use kcl_lib::{ast::types::Program, errors::KclError, executor::ExecutorContext};
 | 
			
		||||
use kcl_lib::{
 | 
			
		||||
    ast::types::Program,
 | 
			
		||||
    errors::KclError,
 | 
			
		||||
    executor::{ExecutorContext, IdGenerator},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
macro_rules! gen_test {
 | 
			
		||||
    ($file:ident) => {
 | 
			
		||||
@ -22,12 +26,12 @@ macro_rules! gen_test_fail {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run(code: &str) {
 | 
			
		||||
    let (ctx, program) = setup(code).await;
 | 
			
		||||
    let (ctx, program, id_generator) = setup(code).await;
 | 
			
		||||
 | 
			
		||||
    ctx.run(&program, None).await.unwrap();
 | 
			
		||||
    ctx.run(&program, None, id_generator).await.unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn setup(program: &str) -> (ExecutorContext, Program) {
 | 
			
		||||
async fn setup(program: &str) -> (ExecutorContext, Program, IdGenerator) {
 | 
			
		||||
    let tokens = kcl_lib::token::lexer(program).unwrap();
 | 
			
		||||
    let parser = kcl_lib::parser::Parser::new(tokens);
 | 
			
		||||
    let program = parser.ast().unwrap();
 | 
			
		||||
@ -40,12 +44,12 @@ async fn setup(program: &str) -> (ExecutorContext, Program) {
 | 
			
		||||
        settings: Default::default(),
 | 
			
		||||
        context_type: kcl_lib::executor::ContextType::Mock,
 | 
			
		||||
    };
 | 
			
		||||
    (ctx, program)
 | 
			
		||||
    (ctx, program, IdGenerator::default())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn run_fail(code: &str) -> KclError {
 | 
			
		||||
    let (ctx, program) = setup(code).await;
 | 
			
		||||
    let Err(e) = ctx.run(&program, None).await else {
 | 
			
		||||
    let (ctx, program, id_generator) = setup(code).await;
 | 
			
		||||
    let Err(e) = ctx.run(&program, None, id_generator).await else {
 | 
			
		||||
        panic!("Expected this KCL program to fail, but it (incorrectly) never threw an error.");
 | 
			
		||||
    };
 | 
			
		||||
    e
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
use anyhow::Result;
 | 
			
		||||
use kcl_lib::{
 | 
			
		||||
    ast::{modify::modify_ast_for_sketch, types::Program},
 | 
			
		||||
    executor::{ExecutorContext, KclValue, PlaneType, Sketch, SourceRange},
 | 
			
		||||
    executor::{ExecutorContext, IdGenerator, KclValue, PlaneType, Sketch, SourceRange},
 | 
			
		||||
};
 | 
			
		||||
use kittycad_modeling_cmds::{each_cmd as mcmd, length_unit::LengthUnit, shared::Point3d, ModelingCmd};
 | 
			
		||||
use pretty_assertions::assert_eq;
 | 
			
		||||
@ -35,7 +35,7 @@ async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid
 | 
			
		||||
    let parser = kcl_lib::parser::Parser::new(tokens);
 | 
			
		||||
    let program = parser.ast()?;
 | 
			
		||||
    let ctx = kcl_lib::executor::ExecutorContext::new(&client, Default::default()).await?;
 | 
			
		||||
    let exec_state = ctx.run(&program, None).await?;
 | 
			
		||||
    let exec_state = ctx.run(&program, None, IdGenerator::default()).await?;
 | 
			
		||||
 | 
			
		||||
    // We need to get the sketch ID.
 | 
			
		||||
    // Get the sketch ID from memory.
 | 
			
		||||
 | 
			
		||||