Merge remote-tracking branch 'origin/main' into kurt-scale-sketch
							
								
								
									
										2
									
								
								.github/workflows/build-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -362,7 +362,7 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Authenticate to Google Cloud
 | 
			
		||||
        if: ${{ env.IS_STAGING == 'true' }}
 | 
			
		||||
        uses: 'google-github-actions/auth@v2.1.8'
 | 
			
		||||
        uses: 'google-github-actions/auth@v2.1.10'
 | 
			
		||||
        with:
 | 
			
		||||
          credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/kcl-language-server.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -328,7 +328,7 @@ jobs:
 | 
			
		||||
          mkdir -p releases/language-server/${{ env.TAG }}
 | 
			
		||||
          cp -r build/* releases/language-server/${{ env.TAG }}
 | 
			
		||||
      - name: "Authenticate to Google Cloud"
 | 
			
		||||
        uses: "google-github-actions/auth@v2.1.8"
 | 
			
		||||
        uses: "google-github-actions/auth@v2.1.10"
 | 
			
		||||
        with:
 | 
			
		||||
          credentials_json: "${{ secrets.GOOGLE_CLOUD_DL_SA }}"
 | 
			
		||||
      - name: Set up Cloud SDK
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/publish-apps-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						@ -108,7 +108,7 @@ jobs:
 | 
			
		||||
        run: npm run files:set-notes
 | 
			
		||||
 | 
			
		||||
      - name: Authenticate to Google Cloud
 | 
			
		||||
        uses: 'google-github-actions/auth@v2.1.8'
 | 
			
		||||
        uses: 'google-github-actions/auth@v2.1.10'
 | 
			
		||||
        with:
 | 
			
		||||
          credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -280,6 +280,9 @@ Assign someone to each section of the manual checklist generated by the issue te
 | 
			
		||||
Follow the instructions [here](./rust/README.md) to publish new crates.
 | 
			
		||||
This ensures that the KCL accepted by the app is also accepted by the CLI.
 | 
			
		||||
 | 
			
		||||
If there are documentation changes, merge the corresponding Dependabot PRs [here](https://github.com/KittyCAD/website/pulls/app%2Fdependabot) for the website.
 | 
			
		||||
You can trigger Dependabot to check for updates [here](https://github.com/KittyCAD/website/network/updates/17261214/jobs).
 | 
			
		||||
 | 
			
		||||
#### 5. Publish the release
 | 
			
		||||
 | 
			
		||||
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the **Release title** field as well.
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						@ -62,7 +62,10 @@ else
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
public/kcl-samples/manifest.json: $(KCL_SOURCES)
 | 
			
		||||
ifndef WINDOWS
 | 
			
		||||
	cd rust/kcl-lib && EXPECTORATE=overwrite cargo test generate_manifest
 | 
			
		||||
	@ touch $@
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
.vite/build/main.js: $(REACT_SOURCES) $(TYPESCRIPT_SOURCES) $(VITE_SOURCES)
 | 
			
		||||
	npm run tronb:vite:dev
 | 
			
		||||
 | 
			
		||||
@ -83,6 +83,13 @@ Allow orbiting in sketch mode.
 | 
			
		||||
Whether to show the debug panel, which lets you see various states of the app to aid in development.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**Default:** None
 | 
			
		||||
 | 
			
		||||
##### fixed_size_grid
 | 
			
		||||
 | 
			
		||||
If true, the grid cells will be fixed-size, where the width is your default length unit. If false, the grid will get larger as you zoom out, and smaller as you zoom in.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
**Default:** None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -12,7 +12,7 @@ test.describe('Point and click for boolean workflows', () => {
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'subtract',
 | 
			
		||||
      code: 'subtract([extrude001], tools = [extrude006])',
 | 
			
		||||
      code: 'subtract(extrude001, tools = extrude006)',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name: 'intersect',
 | 
			
		||||
@ -81,6 +81,8 @@ test.describe('Point and click for boolean workflows', () => {
 | 
			
		||||
        if (operationName !== 'subtract') {
 | 
			
		||||
          // should down shift key to select multiple objects
 | 
			
		||||
          await page.keyboard.down('Shift')
 | 
			
		||||
        } else {
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Select second object
 | 
			
		||||
@ -103,8 +105,8 @@ test.describe('Point and click for boolean workflows', () => {
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            stage: 'review',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              Tool: '1 path',
 | 
			
		||||
              Target: '1 path',
 | 
			
		||||
              Solids: '1 path',
 | 
			
		||||
              Tools: '1 path',
 | 
			
		||||
            },
 | 
			
		||||
            commandName,
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
@ -5,6 +5,7 @@ import { uuidv4 } from '@src/lib/utils'
 | 
			
		||||
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
 | 
			
		||||
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | 
			
		||||
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
 | 
			
		||||
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
 | 
			
		||||
import { getUtils } from '@e2e/playwright/test-utils'
 | 
			
		||||
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
			
		||||
 | 
			
		||||
@ -14,13 +15,18 @@ test.describe('Can create sketches on all planes and their back sides', () => {
 | 
			
		||||
    homePage: HomePageFixture,
 | 
			
		||||
    scene: SceneFixture,
 | 
			
		||||
    toolbar: ToolbarFixture,
 | 
			
		||||
    cmdBar: CmdBarFixture,
 | 
			
		||||
    plane: string,
 | 
			
		||||
    clickCoords: { x: number; y: number }
 | 
			
		||||
  ) => {
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    // await page.addInitScript(() => {
 | 
			
		||||
    //   localStorage.setItem('persistCode', '@settings(defaultLengthUnit = in)')
 | 
			
		||||
    // })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    // await scene.settled(cmdBar)
 | 
			
		||||
    const XYPlanRed: [number, number, number] = [98, 50, 51]
 | 
			
		||||
    await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
 | 
			
		||||
 | 
			
		||||
@ -119,12 +125,166 @@ test.describe('Can create sketches on all planes and their back sides', () => {
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  for (const config of planeConfigs) {
 | 
			
		||||
    test(config.plane, async ({ page, homePage, scene, toolbar }) => {
 | 
			
		||||
    test(config.plane, async ({ page, homePage, scene, toolbar, cmdBar }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(
 | 
			
		||||
        page,
 | 
			
		||||
        homePage,
 | 
			
		||||
        scene,
 | 
			
		||||
        toolbar,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
        config.plane,
 | 
			
		||||
        config.coords
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
test.describe('Can create sketches on offset planes and their back sides', () => {
 | 
			
		||||
  const sketchOnPlaneAndBackSideTest = async (
 | 
			
		||||
    page: Page,
 | 
			
		||||
    homePage: HomePageFixture,
 | 
			
		||||
    scene: SceneFixture,
 | 
			
		||||
    toolbar: ToolbarFixture,
 | 
			
		||||
    cmdbar: CmdBarFixture,
 | 
			
		||||
    plane: string,
 | 
			
		||||
    clickCoords: { x: number; y: number }
 | 
			
		||||
  ) => {
 | 
			
		||||
    const u = await getUtils(page)
 | 
			
		||||
    await page.addInitScript(() => {
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        'persistCode',
 | 
			
		||||
        `@settings(defaultLengthUnit = in)
 | 
			
		||||
xyPlane = offsetPlane(XY, offset = 0.05)
 | 
			
		||||
xzPlane = offsetPlane(XZ, offset = 0.05)
 | 
			
		||||
yzPlane = offsetPlane(YZ, offset = 0.05)
 | 
			
		||||
`
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
    await page.setBodyDimensions({ width: 1200, height: 500 })
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    // await scene.settled(cmdbar)
 | 
			
		||||
    const XYPlanRed: [number, number, number] = [74, 74, 74]
 | 
			
		||||
    await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
 | 
			
		||||
 | 
			
		||||
    await u.openDebugPanel()
 | 
			
		||||
 | 
			
		||||
    const coord =
 | 
			
		||||
      plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
 | 
			
		||||
    const camCommand: EngineCommand = {
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'default_camera_look_at',
 | 
			
		||||
        center: { x: 0, y: 0, z: 0 },
 | 
			
		||||
        vantage: { x: coord, y: coord, z: coord },
 | 
			
		||||
        up: { x: 0, y: 0, z: 1 },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
    const updateCamCommand: EngineCommand = {
 | 
			
		||||
      type: 'modeling_cmd_req',
 | 
			
		||||
      cmd_id: uuidv4(),
 | 
			
		||||
      cmd: {
 | 
			
		||||
        type: 'default_camera_get_settings',
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const prefix = plane.length === 3 ? '-' : ''
 | 
			
		||||
    const planeName = plane
 | 
			
		||||
      .slice(plane.length === 3 ? 1 : 0)
 | 
			
		||||
      .toLocaleLowerCase()
 | 
			
		||||
 | 
			
		||||
    const codeLine1 = `sketch001 = startSketchOn(${prefix}${planeName}Plane)`
 | 
			
		||||
    const codeLine2 = `profile001 = startProfile(sketch001, at = [${0.91 + (plane[0] === '-' ? 0.01 : 0)}, -${1.21 + (plane[0] === '-' ? 0.01 : 0)}])`
 | 
			
		||||
 | 
			
		||||
    await u.openDebugPanel()
 | 
			
		||||
 | 
			
		||||
    await u.clearCommandLogs()
 | 
			
		||||
    await page.getByRole('button', { name: 'Start Sketch' }).click()
 | 
			
		||||
 | 
			
		||||
    await u.sendCustomCmd(camCommand)
 | 
			
		||||
    await page.waitForTimeout(100)
 | 
			
		||||
    await u.sendCustomCmd(updateCamCommand)
 | 
			
		||||
 | 
			
		||||
    await u.closeDebugPanel()
 | 
			
		||||
 | 
			
		||||
    await toolbar.openFeatureTreePane()
 | 
			
		||||
    await toolbar.getDefaultPlaneVisibilityButton('XY').click()
 | 
			
		||||
    await toolbar.getDefaultPlaneVisibilityButton('XZ').click()
 | 
			
		||||
    await toolbar.getDefaultPlaneVisibilityButton('YZ').click()
 | 
			
		||||
    await expect(
 | 
			
		||||
      toolbar
 | 
			
		||||
        .getDefaultPlaneVisibilityButton('YZ')
 | 
			
		||||
        .locator('[aria-label="eye crossed out"]')
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    await page.mouse.click(clickCoords.x, clickCoords.y)
 | 
			
		||||
    await page.waitForTimeout(600) // wait for animation
 | 
			
		||||
 | 
			
		||||
    await toolbar.waitUntilSketchingReady()
 | 
			
		||||
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByRole('button', { name: 'line Line', exact: true })
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    await u.closeDebugPanel()
 | 
			
		||||
    await page.mouse.click(707, 393)
 | 
			
		||||
 | 
			
		||||
    await expect(page.locator('.cm-content')).toContainText(codeLine1)
 | 
			
		||||
    await expect(page.locator('.cm-content')).toContainText(codeLine2)
 | 
			
		||||
 | 
			
		||||
    await page
 | 
			
		||||
      .getByRole('button', { name: 'line Line', exact: true })
 | 
			
		||||
      .first()
 | 
			
		||||
      .click()
 | 
			
		||||
    await u.openAndClearDebugPanel()
 | 
			
		||||
    await page.getByRole('button', { name: 'Exit Sketch' }).click()
 | 
			
		||||
    await u.expectCmdLog('[data-message-type="execution-done"]')
 | 
			
		||||
 | 
			
		||||
    await u.clearCommandLogs()
 | 
			
		||||
    await u.removeCurrentCode()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const planeConfigs = [
 | 
			
		||||
    {
 | 
			
		||||
      plane: 'XY',
 | 
			
		||||
      coords: { x: 600, y: 388 },
 | 
			
		||||
      description: 'red plane',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      plane: 'YZ',
 | 
			
		||||
      coords: { x: 700, y: 250 },
 | 
			
		||||
      description: 'green plane',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      plane: 'XZ',
 | 
			
		||||
      coords: { x: 684, y: 427 },
 | 
			
		||||
      description: 'blue plane',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      plane: '-XY',
 | 
			
		||||
      coords: { x: 600, y: 118 },
 | 
			
		||||
      description: 'back of red plane',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      plane: '-YZ',
 | 
			
		||||
      coords: { x: 700, y: 219 },
 | 
			
		||||
      description: 'back of green plane',
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      plane: '-XZ',
 | 
			
		||||
      coords: { x: 700, y: 80 },
 | 
			
		||||
      description: 'back of blue plane',
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
 | 
			
		||||
  for (const config of planeConfigs) {
 | 
			
		||||
    test(config.plane, async ({ page, homePage, scene, toolbar, cmdBar }) => {
 | 
			
		||||
      await sketchOnPlaneAndBackSideTest(
 | 
			
		||||
        page,
 | 
			
		||||
        homePage,
 | 
			
		||||
        scene,
 | 
			
		||||
        toolbar,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
        config.plane,
 | 
			
		||||
        config.coords
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
@ -265,6 +265,8 @@ middle(0)
 | 
			
		||||
    })
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
 | 
			
		||||
 | 
			
		||||
Backtrace:
 | 
			
		||||
assert()
 | 
			
		||||
check()
 | 
			
		||||
middle()`)
 | 
			
		||||
 | 
			
		||||
@ -288,7 +288,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
 | 
			
		||||
    // error text on hover
 | 
			
		||||
    await page.hover('.cm-lint-marker-info')
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Identifiers must be lowerCamelCase').first()
 | 
			
		||||
      page.getByText('Identifiers should be lowerCamelCase').first()
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    await page.locator('#code-pane button:first-child').click()
 | 
			
		||||
@ -314,7 +314,7 @@ sketch_001 = startSketchOn(XY)
 | 
			
		||||
    // error text on hover
 | 
			
		||||
    await page.hover('.cm-lint-marker-info')
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Identifiers must be lowerCamelCase').first()
 | 
			
		||||
      page.getByText('Identifiers should be lowerCamelCase').first()
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -511,7 +511,7 @@ sketch_001 = startSketchOn(XY)
 | 
			
		||||
    // error text on hover
 | 
			
		||||
    await page.hover('.cm-lint-marker-info')
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Identifiers must be lowerCamelCase').first()
 | 
			
		||||
      page.getByText('Identifiers should be lowerCamelCase').first()
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    // focus the editor
 | 
			
		||||
@ -539,7 +539,7 @@ sketch_001 = startSketchOn(XY)
 | 
			
		||||
    // error text on hover
 | 
			
		||||
    await page.hover('.cm-lint-marker-info')
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Identifiers must be lowerCamelCase').first()
 | 
			
		||||
      page.getByText('Identifiers should be lowerCamelCase').first()
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -681,7 +681,7 @@ a1 = startSketchOn(offsetPlane(XY, offset = 10))
 | 
			
		||||
    // error text on hover
 | 
			
		||||
    await page.hover('.cm-lint-marker-info')
 | 
			
		||||
    await expect(
 | 
			
		||||
      page.getByText('Identifiers must be lowerCamelCase').first()
 | 
			
		||||
      page.getByText('Identifiers should be lowerCamelCase').first()
 | 
			
		||||
    ).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    // select the line that's causing the error and delete it
 | 
			
		||||
@ -1617,4 +1617,33 @@ sketch001 = startSketchOn(XZ)
 | 
			
		||||
    // Verify error is still visible
 | 
			
		||||
    await expect(page.locator('.cm-lint-marker-error')).toHaveCount(1)
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('Core dump hotkey', async ({ page, scene, cmdBar, homePage }) => {
 | 
			
		||||
    await page.addInitScript(async () => {
 | 
			
		||||
      localStorage.setItem(
 | 
			
		||||
        'persistCode',
 | 
			
		||||
        `sketch001 = startSketchOn(XZ)
 | 
			
		||||
    profile001 = circle(sketch001, center = [-100.0, -100.0], radius = 50.0)
 | 
			
		||||
`
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    const viewportSize = { width: 1200, height: 800 }
 | 
			
		||||
    await page.setBodyDimensions(viewportSize)
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    const modifier = process.platform === 'darwin' ? 'Meta' : 'Control'
 | 
			
		||||
 | 
			
		||||
    await page.keyboard.press(`${modifier}+Shift+.`)
 | 
			
		||||
 | 
			
		||||
    const toast1 = page.getByText('Starting core dump...')
 | 
			
		||||
    await expect(toast1).toBeVisible()
 | 
			
		||||
 | 
			
		||||
    const toast2 = page.getByText('Core dump completed')
 | 
			
		||||
    await expect(toast2).toBeVisible()
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
@ -187,6 +187,13 @@ export class CmdBarFixture {
 | 
			
		||||
    return this.page.getByRole('option', options)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Select an optional argument from the command bar during review
 | 
			
		||||
   */
 | 
			
		||||
  clickOptionalArgument = async (argName: string) => {
 | 
			
		||||
    await this.page.getByTestId(`cmd-bar-add-optional-arg-${argName}`).click()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Clicks the Create new variable button for kcl input
 | 
			
		||||
   */
 | 
			
		||||
 | 
			
		||||
@ -274,6 +274,13 @@ export class ToolbarFixture {
 | 
			
		||||
      .nth(operationIndex)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getDefaultPlaneVisibilityButton(plane: 'XY' | 'XZ' | 'YZ' = 'XY') {
 | 
			
		||||
    const index = plane === 'XZ' ? 0 : plane === 'XY' ? 1 : 2
 | 
			
		||||
    return this.featureTreePane
 | 
			
		||||
      .getByTestId('feature-tree-visibility-toggle')
 | 
			
		||||
      .nth(index)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * View source on a specific operation in the Feature Tree pane.
 | 
			
		||||
   * @param operationName The name of the operation type
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
 | 
			
		||||
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
 | 
			
		||||
import { expect, test } from '@e2e/playwright/zoo-test'
 | 
			
		||||
import { bracket } from '@e2e/playwright/fixtures/bracket'
 | 
			
		||||
import type { CmdBarSerialised } from '@e2e/playwright/fixtures/cmdBarFixture'
 | 
			
		||||
 | 
			
		||||
// test file is for testing point an click code gen functionality that's not sketch mode related
 | 
			
		||||
 | 
			
		||||
@ -1141,6 +1142,20 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const initialCmdBarStateHelix: CmdBarSerialised = {
 | 
			
		||||
    stage: 'arguments',
 | 
			
		||||
    currentArgKey: 'mode',
 | 
			
		||||
    currentArgValue: '',
 | 
			
		||||
    headerArguments: {
 | 
			
		||||
      Mode: '',
 | 
			
		||||
      AngleStart: '',
 | 
			
		||||
      Revolutions: '',
 | 
			
		||||
      Radius: '',
 | 
			
		||||
    },
 | 
			
		||||
    highlightedHeaderArg: 'mode',
 | 
			
		||||
    commandName: 'Helix',
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  test('Helix point-and-click on default axis', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
@ -1150,30 +1165,14 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    // One dumb hardcoded screen pixel value
 | 
			
		||||
    const testPoint = { x: 620, y: 257 }
 | 
			
		||||
    const expectedOutput = `helix001 = helix(  axis = X,  radius = 5,  length = 5,  revolutions = 1,  angleStart = 270,  ccw = false,)`
 | 
			
		||||
    const expectedOutput = `helix001 = helix(  axis = X,  radius = 5,  length = 5,  revolutions = 1,  angleStart = 270,)`
 | 
			
		||||
    const expectedLine = `axis=X,`
 | 
			
		||||
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.connectionEstablished()
 | 
			
		||||
 | 
			
		||||
    await test.step(`Go through the command bar flow`, async () => {
 | 
			
		||||
      await toolbar.helixButton.click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'mode',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Mode: '',
 | 
			
		||||
          AngleStart: '',
 | 
			
		||||
          Revolutions: '',
 | 
			
		||||
          Radius: '',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'mode',
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.expectState(initialCmdBarStateHelix)
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await expect.poll(() => page.getByText('Axis').count()).toBe(6)
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
@ -1190,7 +1189,6 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
          AngleStart: '',
 | 
			
		||||
          Length: '',
 | 
			
		||||
          Radius: '',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
@ -1207,11 +1205,10 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          Length: '5',
 | 
			
		||||
          Radius: '5',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
@ -1221,8 +1218,6 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
        activeLines: [expectedLine],
 | 
			
		||||
        highlightedCode: '',
 | 
			
		||||
      })
 | 
			
		||||
      // Red plane is now gone, white helix is there
 | 
			
		||||
      await scene.expectPixelColor([250, 250, 250], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Edit helix through the feature tree`, async () => {
 | 
			
		||||
@ -1234,21 +1229,18 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'CounterClockWise',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        currentArgKey: 'length',
 | 
			
		||||
        currentArgValue: '5',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Axis: 'X',
 | 
			
		||||
          AngleStart: '270',
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          Radius: '5',
 | 
			
		||||
          Length: initialInput,
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'CounterClockWise',
 | 
			
		||||
        highlightedHeaderArg: 'length',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.press('Shift+Backspace')
 | 
			
		||||
      await expect(cmdBar.currentArgumentInput).toBeVisible()
 | 
			
		||||
      await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
 | 
			
		||||
      await page.keyboard.insertText(newInput)
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
@ -1258,11 +1250,10 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          Radius: '5',
 | 
			
		||||
          Length: newInput,
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await toolbar.closeFeatureTreePane()
 | 
			
		||||
      await editor.openPane()
 | 
			
		||||
      await editor.expectEditor.toContain('length = ' + newInput)
 | 
			
		||||
@ -1273,174 +1264,238 @@ openSketch = startSketchOn(XY)
 | 
			
		||||
      const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
      await operationButton.click({ button: 'left' })
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      // Red plane is back
 | 
			
		||||
      await scene.expectPixelColor([96, 52, 52], testPoint, 15)
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
      await editor.expectEditor.not.toContain('helix')
 | 
			
		||||
      await expect(
 | 
			
		||||
        await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
      ).not.toBeVisible()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const helixCases = [
 | 
			
		||||
    {
 | 
			
		||||
      selectionType: 'segment',
 | 
			
		||||
      testPoint: { x: 513, y: 221 },
 | 
			
		||||
      expectedOutput: `helix001 = helix(  axis = seg01,  radius = 1,  revolutions = 20,  angleStart = 0,  ccw = false,)`,
 | 
			
		||||
      expectedEditedOutput: `helix001 = helix(  axis = seg01,  radius = 5,  revolutions = 20,  angleStart = 0,  ccw = false,)`,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      selectionType: 'sweepEdge',
 | 
			
		||||
      testPoint: { x: 564, y: 364 },
 | 
			
		||||
      expectedOutput: `helix001 = helix(  axis =   getOppositeEdge(seg01),  radius = 1,  revolutions = 20,  angleStart = 0,  ccw = false,)`,
 | 
			
		||||
      expectedEditedOutput: `helix001 = helix(  axis =   getOppositeEdge(seg01),  radius = 5,  revolutions = 20,  angleStart = 0,  ccw = false,)`,
 | 
			
		||||
    },
 | 
			
		||||
  ]
 | 
			
		||||
  helixCases.map(
 | 
			
		||||
    ({ selectionType, testPoint, expectedOutput, expectedEditedOutput }) => {
 | 
			
		||||
      test(`Helix point-and-click around ${selectionType}`, async ({
 | 
			
		||||
        context,
 | 
			
		||||
        page,
 | 
			
		||||
        homePage,
 | 
			
		||||
        scene,
 | 
			
		||||
        editor,
 | 
			
		||||
        toolbar,
 | 
			
		||||
        cmdBar,
 | 
			
		||||
      }) => {
 | 
			
		||||
        page.on('console', console.log)
 | 
			
		||||
        const initialCode = `sketch001 = startSketchOn(XZ)
 | 
			
		||||
  profile001 = startProfile(sketch001, at = [0, 0])
 | 
			
		||||
    |> yLine(length = 100)
 | 
			
		||||
    |> line(endAbsolute = [100, 0])
 | 
			
		||||
    |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
			
		||||
    |> close()
 | 
			
		||||
  extrude001 = extrude(profile001, length = 100)`
 | 
			
		||||
  test(`Helix point-and-click around segment`, async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `sketch001 = startSketchOn(XZ)
 | 
			
		||||
profile001 = startProfile(sketch001, at = [0, 0])
 | 
			
		||||
|> yLine(length = 100)
 | 
			
		||||
|> line(endAbsolute = [100, 0])
 | 
			
		||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
			
		||||
|> close()`
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
        // One dumb hardcoded screen pixel value
 | 
			
		||||
        const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
 | 
			
		||||
 | 
			
		||||
        await context.addInitScript((initialCode) => {
 | 
			
		||||
          localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
        }, initialCode)
 | 
			
		||||
        await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
        await homePage.goToModelingScene()
 | 
			
		||||
        await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
        await test.step(`Go through the command bar flow`, async () => {
 | 
			
		||||
          await toolbar.closePane('code')
 | 
			
		||||
          await toolbar.helixButton.click()
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            stage: 'arguments',
 | 
			
		||||
            currentArgKey: 'mode',
 | 
			
		||||
            currentArgValue: '',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              AngleStart: '',
 | 
			
		||||
              Mode: '',
 | 
			
		||||
              CounterClockWise: '',
 | 
			
		||||
              Radius: '',
 | 
			
		||||
              Revolutions: '',
 | 
			
		||||
            },
 | 
			
		||||
            highlightedHeaderArg: 'mode',
 | 
			
		||||
            commandName: 'Helix',
 | 
			
		||||
          })
 | 
			
		||||
          await cmdBar.selectOption({ name: 'Edge' }).click()
 | 
			
		||||
          await expect
 | 
			
		||||
            .poll(() => page.getByText('Please select one').count())
 | 
			
		||||
            .toBe(1)
 | 
			
		||||
          await clickOnEdge()
 | 
			
		||||
          await page.waitForTimeout(1000)
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await page.waitForTimeout(1000)
 | 
			
		||||
          await cmdBar.argumentInput.focus()
 | 
			
		||||
          await page.waitForTimeout(1000)
 | 
			
		||||
          await page.keyboard.insertText('20')
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await page.keyboard.insertText('0')
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await page.keyboard.insertText('1')
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await page.keyboard.insertText('100')
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            stage: 'review',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              Mode: 'Edge',
 | 
			
		||||
              Edge: `1 ${selectionType}`,
 | 
			
		||||
              AngleStart: '0',
 | 
			
		||||
              Revolutions: '20',
 | 
			
		||||
              Radius: '1',
 | 
			
		||||
              CounterClockWise: '',
 | 
			
		||||
            },
 | 
			
		||||
            commandName: 'Helix',
 | 
			
		||||
          })
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await page.waitForTimeout(1000)
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
          await toolbar.openPane('code')
 | 
			
		||||
          await editor.expectEditor.toContain(expectedOutput)
 | 
			
		||||
          await toolbar.closePane('code')
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await test.step(`Edit helix through the feature tree`, async () => {
 | 
			
		||||
          await toolbar.openPane('feature-tree')
 | 
			
		||||
          const operationButton = await toolbar.getFeatureTreeOperation(
 | 
			
		||||
            'Helix',
 | 
			
		||||
            0
 | 
			
		||||
          )
 | 
			
		||||
          await operationButton.dblclick()
 | 
			
		||||
          const initialInput = '1'
 | 
			
		||||
          const newInput = '5'
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            commandName: 'Helix',
 | 
			
		||||
            stage: 'arguments',
 | 
			
		||||
            currentArgKey: 'CounterClockWise',
 | 
			
		||||
            currentArgValue: '',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              AngleStart: '0',
 | 
			
		||||
              Revolutions: '20',
 | 
			
		||||
              Radius: initialInput,
 | 
			
		||||
              CounterClockWise: '',
 | 
			
		||||
            },
 | 
			
		||||
            highlightedHeaderArg: 'CounterClockWise',
 | 
			
		||||
          })
 | 
			
		||||
          await page
 | 
			
		||||
            .getByRole('button', { name: 'radius', exact: false })
 | 
			
		||||
            .click()
 | 
			
		||||
          await expect(cmdBar.currentArgumentInput).toBeVisible()
 | 
			
		||||
          await cmdBar.currentArgumentInput
 | 
			
		||||
            .locator('.cm-content')
 | 
			
		||||
            .fill(newInput)
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await cmdBar.expectState({
 | 
			
		||||
            stage: 'review',
 | 
			
		||||
            headerArguments: {
 | 
			
		||||
              AngleStart: '0',
 | 
			
		||||
              Revolutions: '20',
 | 
			
		||||
              Radius: newInput,
 | 
			
		||||
              CounterClockWise: '',
 | 
			
		||||
            },
 | 
			
		||||
            commandName: 'Helix',
 | 
			
		||||
          })
 | 
			
		||||
          await cmdBar.progressCmdBar()
 | 
			
		||||
          await toolbar.closePane('feature-tree')
 | 
			
		||||
          await toolbar.openPane('code')
 | 
			
		||||
          await editor.expectEditor.toContain(expectedEditedOutput)
 | 
			
		||||
          await toolbar.closePane('code')
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        await test.step('Delete helix via feature tree selection', async () => {
 | 
			
		||||
          await toolbar.openPane('feature-tree')
 | 
			
		||||
          const operationButton = await toolbar.getFeatureTreeOperation(
 | 
			
		||||
            'Helix',
 | 
			
		||||
            0
 | 
			
		||||
          )
 | 
			
		||||
          await operationButton.click({ button: 'left' })
 | 
			
		||||
          await page.keyboard.press('Delete')
 | 
			
		||||
          await editor.expectEditor.not.toContain(expectedEditedOutput)
 | 
			
		||||
          await expect(
 | 
			
		||||
            await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
          ).not.toBeVisible()
 | 
			
		||||
        })
 | 
			
		||||
    await test.step(`Go through the command bar flow`, async () => {
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
      await toolbar.helixButton.click()
 | 
			
		||||
      await cmdBar.expectState(initialCmdBarStateHelix)
 | 
			
		||||
      await cmdBar.selectOption({ name: 'Edge' }).click()
 | 
			
		||||
      await editor.selectText('yLine(length = 100)')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('1')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('2')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('3')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Mode: 'Edge',
 | 
			
		||||
          Edge: `1 segment`,
 | 
			
		||||
          AngleStart: '2',
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          Radius: '3',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
  )
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
      await toolbar.openPane('code')
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `
 | 
			
		||||
        helix001 = helix(
 | 
			
		||||
          axis = seg01,
 | 
			
		||||
          radius = 3,
 | 
			
		||||
          revolutions = 1,
 | 
			
		||||
          angleStart = 2,
 | 
			
		||||
        )`,
 | 
			
		||||
        { shouldNormalise: true }
 | 
			
		||||
      )
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test(`Helix point-and-click around sweepEdge with edit and delete flows`, async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `sketch001 = startSketchOn(XZ)
 | 
			
		||||
profile001 = startProfile(sketch001, at = [0, 0])
 | 
			
		||||
|> yLine(length = 100)
 | 
			
		||||
|> line(endAbsolute = [100, 0])
 | 
			
		||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
			
		||||
|> close()
 | 
			
		||||
extrude001 = extrude(profile001, length = 100)`
 | 
			
		||||
 | 
			
		||||
    // One dumb hardcoded screen pixel value to click on the sweepEdge, can't think of another way?
 | 
			
		||||
    const testPoint = { x: 564, y: 364 }
 | 
			
		||||
    const [clickOnEdge] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
 | 
			
		||||
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    await test.step(`Go through the command bar flow`, async () => {
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
      await toolbar.helixButton.click()
 | 
			
		||||
      await cmdBar.expectState(initialCmdBarStateHelix)
 | 
			
		||||
      await cmdBar.selectOption({ name: 'Edge' }).click()
 | 
			
		||||
      await expect
 | 
			
		||||
        .poll(() => page.getByText('Please select one').count())
 | 
			
		||||
        .toBe(1)
 | 
			
		||||
      await clickOnEdge()
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.argumentInput.focus()
 | 
			
		||||
      await page.keyboard.insertText('20')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('0')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('1')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.keyboard.insertText('100')
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Mode: 'Edge',
 | 
			
		||||
          Edge: `1 sweepEdge`,
 | 
			
		||||
          AngleStart: '0',
 | 
			
		||||
          Revolutions: '20',
 | 
			
		||||
          Radius: '1',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
      await toolbar.openPane('code')
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `
 | 
			
		||||
        helix001 = helix(
 | 
			
		||||
          axis = getOppositeEdge(seg01),
 | 
			
		||||
          radius = 1,
 | 
			
		||||
          revolutions = 20,
 | 
			
		||||
          angleStart = 0,
 | 
			
		||||
        )`,
 | 
			
		||||
        { shouldNormalise: true }
 | 
			
		||||
      )
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Edit helix through the feature tree`, async () => {
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
      await operationButton.dblclick()
 | 
			
		||||
      const initialInput = '1'
 | 
			
		||||
      const newInput = '5'
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'radius',
 | 
			
		||||
        currentArgValue: initialInput,
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '0',
 | 
			
		||||
          Revolutions: '20',
 | 
			
		||||
          Radius: initialInput,
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'radius',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.insertText(newInput)
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '0',
 | 
			
		||||
          Revolutions: '20',
 | 
			
		||||
          Radius: newInput,
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.clickOptionalArgument('ccw')
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'CounterClockWise',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '0',
 | 
			
		||||
          Revolutions: '20',
 | 
			
		||||
          Radius: newInput,
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'CounterClockWise',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.selectOption({ name: 'True' }).click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '0',
 | 
			
		||||
          Revolutions: '20',
 | 
			
		||||
          Radius: newInput,
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await toolbar.closePane('feature-tree')
 | 
			
		||||
      await toolbar.openPane('code')
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `
 | 
			
		||||
        helix001 = helix(
 | 
			
		||||
          axis = getOppositeEdge(seg01),
 | 
			
		||||
          radius = 5,
 | 
			
		||||
          revolutions = 20,
 | 
			
		||||
          angleStart = 0,
 | 
			
		||||
          ccw = true,
 | 
			
		||||
        )`,
 | 
			
		||||
        { shouldNormalise: true }
 | 
			
		||||
      )
 | 
			
		||||
      await toolbar.closePane('code')
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Delete helix via feature tree selection', async () => {
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
      await operationButton.click({ button: 'left' })
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      await editor.expectEditor.not.toContain('helix')
 | 
			
		||||
      await expect(
 | 
			
		||||
        await toolbar.getFeatureTreeOperation('Helix', 0)
 | 
			
		||||
      ).not.toBeVisible()
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('Helix point-and-click on cylinder', async ({
 | 
			
		||||
    context,
 | 
			
		||||
@ -1470,26 +1525,12 @@ extrude001 = extrude(profile001, length = 100)
 | 
			
		||||
    // One dumb hardcoded screen pixel value
 | 
			
		||||
    const testPoint = { x: 620, y: 257 }
 | 
			
		||||
    const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
 | 
			
		||||
    const expectedOutput = `helix001 = helix(  cylinder = extrude001,  revolutions = 1,  angleStart = 360,  ccw = false,)`
 | 
			
		||||
    const expectedLine = `cylinder = extrude001,`
 | 
			
		||||
    const expectedEditedOutput = `helix001 = helix(  cylinder = extrude001,  revolutions = 1,  angleStart = 360,  ccw = true,)`
 | 
			
		||||
    const expectedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 360)`
 | 
			
		||||
    const expectedEditedOutput = `helix001 = helix(cylinder = extrude001, revolutions = 1, angleStart = 10)`
 | 
			
		||||
 | 
			
		||||
    await test.step(`Go through the command bar flow`, async () => {
 | 
			
		||||
      await toolbar.helixButton.click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'mode',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Mode: '',
 | 
			
		||||
          AngleStart: '',
 | 
			
		||||
          Revolutions: '',
 | 
			
		||||
          Radius: '',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'mode',
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.expectState(initialCmdBarStateHelix)
 | 
			
		||||
      await cmdBar.selectOption({ name: 'Cylinder' }).click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
@ -1500,7 +1541,6 @@ extrude001 = extrude(profile001, length = 100)
 | 
			
		||||
          Cylinder: '',
 | 
			
		||||
          AngleStart: '',
 | 
			
		||||
          Revolutions: '',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'cylinder',
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
@ -1516,18 +1556,17 @@ extrude001 = extrude(profile001, length = 100)
 | 
			
		||||
          Cylinder: '1 face',
 | 
			
		||||
          AngleStart: '360',
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
 | 
			
		||||
      await editor.expectEditor.toContain(expectedOutput)
 | 
			
		||||
      await editor.expectState({
 | 
			
		||||
        diagnostics: [],
 | 
			
		||||
        activeLines: [expectedLine],
 | 
			
		||||
        activeLines: [expectedOutput],
 | 
			
		||||
        highlightedCode: '',
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
@ -1539,22 +1578,21 @@ extrude001 = extrude(profile001, length = 100)
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'CounterClockWise',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        currentArgKey: 'angleStart',
 | 
			
		||||
        currentArgValue: '360',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '360',
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          CounterClockWise: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'CounterClockWise',
 | 
			
		||||
        highlightedHeaderArg: 'angleStart',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.selectOption({ name: 'True' }).click()
 | 
			
		||||
      await page.keyboard.insertText('10')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          AngleStart: '360',
 | 
			
		||||
          AngleStart: '10',
 | 
			
		||||
          Revolutions: '1',
 | 
			
		||||
          CounterClockWise: 'true',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Helix',
 | 
			
		||||
      })
 | 
			
		||||
@ -1610,6 +1648,8 @@ sketch002 = startSketchOn(plane001)
 | 
			
		||||
        testPoint.y + 80
 | 
			
		||||
      )
 | 
			
		||||
      const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
 | 
			
		||||
      const editedLoftDeclaration =
 | 
			
		||||
        'loft001 = loft([sketch001, sketch002], vDegree = 3)'
 | 
			
		||||
 | 
			
		||||
      await test.step(`Look for the white of the sketch001 shape`, async () => {
 | 
			
		||||
        await scene.expectPixelColor([254, 254, 254], testPoint, 15)
 | 
			
		||||
@ -1681,6 +1721,39 @@ sketch002 = startSketchOn(plane001)
 | 
			
		||||
        await scene.expectPixelColor([89, 89, 89], testPoint, 15)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Go through the edit flow via feature tree', async () => {
 | 
			
		||||
        await toolbar.openPane('feature-tree')
 | 
			
		||||
        const op = await toolbar.getFeatureTreeOperation('Loft', 0)
 | 
			
		||||
        await op.dblclick()
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'review',
 | 
			
		||||
          headerArguments: {},
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await cmdBar.clickOptionalArgument('vDegree')
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'arguments',
 | 
			
		||||
          currentArgKey: 'vDegree',
 | 
			
		||||
          currentArgValue: '',
 | 
			
		||||
          headerArguments: {
 | 
			
		||||
            VDegree: '',
 | 
			
		||||
          },
 | 
			
		||||
          highlightedHeaderArg: 'vDegree',
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await page.keyboard.insertText('3')
 | 
			
		||||
        await cmdBar.progressCmdBar()
 | 
			
		||||
        await cmdBar.expectState({
 | 
			
		||||
          stage: 'review',
 | 
			
		||||
          headerArguments: {
 | 
			
		||||
            VDegree: '3',
 | 
			
		||||
          },
 | 
			
		||||
          commandName: 'Loft',
 | 
			
		||||
        })
 | 
			
		||||
        await cmdBar.submit()
 | 
			
		||||
        await editor.expectEditor.toContain(editedLoftDeclaration)
 | 
			
		||||
      })
 | 
			
		||||
 | 
			
		||||
      await test.step('Delete loft via feature tree selection', async () => {
 | 
			
		||||
        await editor.closePane()
 | 
			
		||||
        const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
 | 
			
		||||
@ -1691,72 +1764,6 @@ sketch002 = startSketchOn(plane001)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  // TODO: merge with above test. Right now we're not able to delete a loft
 | 
			
		||||
  // right after creation via selection for some reason, so we go with a new instance
 | 
			
		||||
  test('Loft and offset plane deletion via selection', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const initialCode = `sketch001 = startSketchOn(XZ)
 | 
			
		||||
  |> circle(center = [0, 0], radius = 30)
 | 
			
		||||
  plane001 = offsetPlane(XZ, offset = 50)
 | 
			
		||||
  sketch002 = startSketchOn(plane001)
 | 
			
		||||
  |> circle(center = [0, 0], radius = 20)
 | 
			
		||||
loft001 = loft([sketch001, sketch002])
 | 
			
		||||
`
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, initialCode)
 | 
			
		||||
    await page.setBodyDimensions({ width: 1000, height: 500 })
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    // One dumb hardcoded screen pixel value
 | 
			
		||||
    const testPoint = { x: 575, y: 200 }
 | 
			
		||||
    const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
 | 
			
		||||
    const [clickOnSketch2] = scene.makeMouseHelpers(
 | 
			
		||||
      testPoint.x,
 | 
			
		||||
      testPoint.y + 80
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    await test.step(`Delete loft`, async () => {
 | 
			
		||||
      // Check for loft
 | 
			
		||||
      await scene.expectPixelColor([89, 89, 89], testPoint, 15)
 | 
			
		||||
      await clickOnSketch1()
 | 
			
		||||
      await expect(page.locator('.cm-activeLine')).toHaveText(`
 | 
			
		||||
      |> circle(center = [0, 0], radius = 30)
 | 
			
		||||
    `)
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      // Check for sketch 1
 | 
			
		||||
      await scene.expectPixelColor([254, 254, 254], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Delete sketch002', async () => {
 | 
			
		||||
      await page.waitForTimeout(1000)
 | 
			
		||||
      await clickOnSketch2()
 | 
			
		||||
      await expect(page.locator('.cm-activeLine')).toHaveText(`
 | 
			
		||||
      |> circle(center = [0, 0], radius = 20)
 | 
			
		||||
    `)
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      // Check for plane001
 | 
			
		||||
      await scene.expectPixelColor([228, 228, 228], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Delete plane001', async () => {
 | 
			
		||||
      await page.waitForTimeout(1000)
 | 
			
		||||
      await clickOnSketch2()
 | 
			
		||||
      await expect(page.locator('.cm-activeLine')).toHaveText(`
 | 
			
		||||
      plane001 = offsetPlane(XZ, offset = 50)
 | 
			
		||||
    `)
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      // Check for sketch 1
 | 
			
		||||
      await scene.expectPixelColor([254, 254, 254], testPoint, 15)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  const sweepCases = [
 | 
			
		||||
    {
 | 
			
		||||
      targetType: 'circle',
 | 
			
		||||
@ -1954,6 +1961,7 @@ profile002 = startProfile(sketch002, at = [0, 0])
 | 
			
		||||
sketch001 = startSketchOn(XZ)
 | 
			
		||||
profile001 = ${circleCode}`
 | 
			
		||||
    const sweepDeclaration = 'sweep001 = sweep(profile001, path = helix001)'
 | 
			
		||||
    const editedSweepDeclaration = `sweep001 = sweep(profile001, path = helix001, relativeTo = 'sketchPlane')`
 | 
			
		||||
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
@ -2015,11 +2023,43 @@ profile001 = ${circleCode}`
 | 
			
		||||
      await editor.expectEditor.toContain(sweepDeclaration)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Go through the edit flow via feature tree', async () => {
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const op = await toolbar.getFeatureTreeOperation('Sweep', 0)
 | 
			
		||||
      await op.dblclick()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {},
 | 
			
		||||
        commandName: 'Sweep',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.clickOptionalArgument('relativeTo')
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'relativeTo',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          RelativeTo: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'relativeTo',
 | 
			
		||||
        commandName: 'Sweep',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.selectOption({ name: 'sketchPlane' }).click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          RelativeTo: 'sketchPlane',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Sweep',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await editor.expectEditor.toContain(editedSweepDeclaration)
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Delete sweep via feature tree selection', async () => {
 | 
			
		||||
      const sweep = await toolbar.getFeatureTreeOperation('Sweep', 0)
 | 
			
		||||
      await sweep.click()
 | 
			
		||||
      await page.keyboard.press('Delete')
 | 
			
		||||
      await editor.expectEditor.not.toContain(sweepDeclaration)
 | 
			
		||||
      await editor.expectEditor.not.toContain(editedSweepDeclaration)
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -3879,6 +3919,8 @@ sketch002 = startSketchOn(extrude001, face = rectangleSegmentA001)
 | 
			
		||||
 | 
			
		||||
      // Edit flow
 | 
			
		||||
      const newAngle = '270'
 | 
			
		||||
      const newAngle2 = '5'
 | 
			
		||||
      const editedCodeToFind = `revolve001 = revolve(sketch003, angle = ${newAngle}, axis = seg01, bidirectionalAngle = ${newAngle2}, )`
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const operationButton = await toolbar.getFeatureTreeOperation(
 | 
			
		||||
        'Revolve',
 | 
			
		||||
@ -3904,11 +3946,33 @@ sketch002 = startSketchOn(extrude001, face = rectangleSegmentA001)
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Revolve',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.clickOptionalArgument('bidirectionalAngle')
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        commandName: 'Revolve',
 | 
			
		||||
        currentArgKey: 'bidirectionalAngle',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Angle: newAngle,
 | 
			
		||||
          BidirectionalAngle: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'bidirectionalAngle',
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.insertText(newAngle2)
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Angle: newAngle,
 | 
			
		||||
          BidirectionalAngle: newAngle2,
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Revolve',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
      await toolbar.closePane('feature-tree')
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        newCodeToFind.replace('angle = 360', 'angle = ' + newAngle)
 | 
			
		||||
      )
 | 
			
		||||
      await editor.expectEditor.toContain(editedCodeToFind, {
 | 
			
		||||
        shouldNormalise: true,
 | 
			
		||||
      })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
@ -4923,4 +4987,154 @@ extrude001 = extrude(profile001 length = 1)`
 | 
			
		||||
      await editor.expectEditor.toContain(badCode, { shouldNormalise: true })
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  test('Point-and-click extrude with optional args', async ({
 | 
			
		||||
    context,
 | 
			
		||||
    page,
 | 
			
		||||
    homePage,
 | 
			
		||||
    scene,
 | 
			
		||||
    editor,
 | 
			
		||||
    toolbar,
 | 
			
		||||
    cmdBar,
 | 
			
		||||
  }) => {
 | 
			
		||||
    const squareProfileCode = `length001 = 100
 | 
			
		||||
sketch001 = startSketchOn(XY)
 | 
			
		||||
profile001 = startProfile(sketch001, at = [0, 0])
 | 
			
		||||
  |> yLine(length = length001)
 | 
			
		||||
  |> xLine(length = length001)
 | 
			
		||||
  |> yLine(length = -length001)
 | 
			
		||||
  |> line(endAbsolute = [profileStartX(%), profileStartY(%)])
 | 
			
		||||
  |> close()
 | 
			
		||||
    `
 | 
			
		||||
    await context.addInitScript((initialCode) => {
 | 
			
		||||
      localStorage.setItem('persistCode', initialCode)
 | 
			
		||||
    }, squareProfileCode)
 | 
			
		||||
    await homePage.goToModelingScene()
 | 
			
		||||
    await scene.settled(cmdBar)
 | 
			
		||||
 | 
			
		||||
    await test.step('Select through code', async () => {
 | 
			
		||||
      await editor.selectText('startProfile(sketch001, at = [0, 0])')
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Go through command bar flow', async () => {
 | 
			
		||||
      await toolbar.extrudeButton.click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'sketches',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Profiles: '',
 | 
			
		||||
          Length: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'Profiles',
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'length',
 | 
			
		||||
        currentArgValue: '5',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Profiles: '1 profile',
 | 
			
		||||
          Length: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'length',
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Profiles: '1 profile',
 | 
			
		||||
          Length: '5',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.clickOptionalArgument('bidirectionalLength')
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'bidirectionalLength',
 | 
			
		||||
        currentArgValue: '',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Profiles: '1 profile',
 | 
			
		||||
          Length: '5',
 | 
			
		||||
          BidirectionalLength: '',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'bidirectionalLength',
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.insertText('10')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Profiles: '1 profile',
 | 
			
		||||
          Length: '5',
 | 
			
		||||
          BidirectionalLength: '10',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Check that the code has changed', async () => {
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `extrude001 = extrude(profile001, length = 5, bidirectionalLength = 10)`,
 | 
			
		||||
        { shouldNormalise: true }
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Go through the edit flow via feature tree', async () => {
 | 
			
		||||
      await toolbar.openPane('feature-tree')
 | 
			
		||||
      const op = await toolbar.getFeatureTreeOperation('Extrude', 0)
 | 
			
		||||
      await op.dblclick()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'length',
 | 
			
		||||
        currentArgValue: '5',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Length: '5',
 | 
			
		||||
          BidirectionalLength: '10',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'length',
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.insertText('10')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await page.getByRole('button', { name: 'BidirectionalLength' }).click()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'arguments',
 | 
			
		||||
        currentArgKey: 'bidirectionalLength',
 | 
			
		||||
        currentArgValue: '10',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Length: '10',
 | 
			
		||||
          BidirectionalLength: '10',
 | 
			
		||||
        },
 | 
			
		||||
        highlightedHeaderArg: 'bidirectionalLength',
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await page.keyboard.insertText('20')
 | 
			
		||||
      await cmdBar.progressCmdBar()
 | 
			
		||||
      await cmdBar.expectState({
 | 
			
		||||
        stage: 'review',
 | 
			
		||||
        headerArguments: {
 | 
			
		||||
          Length: '10',
 | 
			
		||||
          BidirectionalLength: '20',
 | 
			
		||||
        },
 | 
			
		||||
        commandName: 'Extrude',
 | 
			
		||||
      })
 | 
			
		||||
      await cmdBar.submit()
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
    await test.step('Check that the code has changed again', async () => {
 | 
			
		||||
      await scene.settled(cmdBar)
 | 
			
		||||
      await toolbar.closePane('feature-tree')
 | 
			
		||||
      await toolbar.openPane('code')
 | 
			
		||||
      await editor.expectEditor.toContain(
 | 
			
		||||
        `extrude001 = extrude(profile001, length = 10, bidirectionalLength = 20)`,
 | 
			
		||||
        { shouldNormalise: true }
 | 
			
		||||
      )
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB  | 
| 
		 Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 32 KiB  | 
| 
		 Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 51 KiB  | 
| 
		 Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB  | 
| 
		 Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB  | 
| 
		 Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB  | 
| 
		 Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB  | 
| 
		 Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB  | 
| 
		 Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 135 KiB  | 
| 
		 Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 69 KiB  | 
| 
		 Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB  | 
@ -12,6 +12,7 @@ export const TEST_SETTINGS: DeepPartial<Settings> = {
 | 
			
		||||
    },
 | 
			
		||||
    onboarding_status: 'dismissed',
 | 
			
		||||
    show_debug_panel: true,
 | 
			
		||||
    fixed_size_grid: false,
 | 
			
		||||
  },
 | 
			
		||||
  modeling: {
 | 
			
		||||
    enable_ssao: false,
 | 
			
		||||
 | 
			
		||||
@ -880,6 +880,10 @@ export async function setup(
 | 
			
		||||
            },
 | 
			
		||||
            ...TEST_SETTINGS.project,
 | 
			
		||||
            onboarding_status: 'dismissed',
 | 
			
		||||
            // Tests were written before this setting existed.
 | 
			
		||||
            // It's true by default because it's a good user experience, but
 | 
			
		||||
            // these tests require it to be false.
 | 
			
		||||
            fixed_size_grid: false,
 | 
			
		||||
          },
 | 
			
		||||
          project: {
 | 
			
		||||
            ...TEST_SETTINGS.project,
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										18
									
								
								flake.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -20,11 +20,11 @@
 | 
			
		||||
    },
 | 
			
		||||
    "nixpkgs": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1745998881,
 | 
			
		||||
        "narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
 | 
			
		||||
        "lastModified": 1750865895,
 | 
			
		||||
        "narHash": "sha256-p2dWAQcLVzquy9LxYCZPwyUdugw78Qv3ChvnX755qHA=",
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
 | 
			
		||||
        "rev": "61c0f513911459945e2cb8bf333dc849f1b976ff",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
@ -36,11 +36,11 @@
 | 
			
		||||
    },
 | 
			
		||||
    "nixpkgs_2": {
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1745998881,
 | 
			
		||||
        "narHash": "sha256-vonyYAKJSlsX4n9GCsS0pHxR6yCrfqBIuGvANlkwG6U=",
 | 
			
		||||
        "lastModified": 1750865895,
 | 
			
		||||
        "narHash": "sha256-p2dWAQcLVzquy9LxYCZPwyUdugw78Qv3ChvnX755qHA=",
 | 
			
		||||
        "owner": "NixOS",
 | 
			
		||||
        "repo": "nixpkgs",
 | 
			
		||||
        "rev": "423d2df5b04b4ee7688c3d71396e872afa236a89",
 | 
			
		||||
        "rev": "61c0f513911459945e2cb8bf333dc849f1b976ff",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
@ -78,11 +78,11 @@
 | 
			
		||||
        "nixpkgs": "nixpkgs_3"
 | 
			
		||||
      },
 | 
			
		||||
      "locked": {
 | 
			
		||||
        "lastModified": 1745980514,
 | 
			
		||||
        "narHash": "sha256-CITAeiuXGjDvT5iZBXr6vKVWQwsUQLJUMFO91bfJFC4=",
 | 
			
		||||
        "lastModified": 1750964660,
 | 
			
		||||
        "narHash": "sha256-YQ6EyFetjH1uy5JhdhRdPe6cuNXlYpMAQePFfZj4W7M=",
 | 
			
		||||
        "owner": "oxalica",
 | 
			
		||||
        "repo": "rust-overlay",
 | 
			
		||||
        "rev": "7fbdae44b0f40ea432e46fd152ad8be0f8f41ad6",
 | 
			
		||||
        "rev": "04f0fcfb1a50c63529805a798b4b5c21610ff390",
 | 
			
		||||
        "type": "github"
 | 
			
		||||
      },
 | 
			
		||||
      "original": {
 | 
			
		||||
 | 
			
		||||
@ -125,18 +125,57 @@ test('Shows a loading spinner when uninitialized credit count', async () => {
 | 
			
		||||
  await expect(queryByTestId('spinner')).toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Shows the total credits for Unknown subscription', async () => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    balance: {
 | 
			
		||||
      monthlyApiCreditsRemaining: 10,
 | 
			
		||||
      stableApiCreditsRemaining: 25,
 | 
			
		||||
    },
 | 
			
		||||
    subscriptions: {
 | 
			
		||||
      monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
      name: "unknown",
 | 
			
		||||
    }
 | 
			
		||||
const unKnownTierData = {
 | 
			
		||||
  balance: {
 | 
			
		||||
    monthlyApiCreditsRemaining: 10,
 | 
			
		||||
    stableApiCreditsRemaining: 25,
 | 
			
		||||
  },
 | 
			
		||||
  subscriptions: {
 | 
			
		||||
    monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
    name: "unknown",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const freeTierData = {
 | 
			
		||||
  balance: {
 | 
			
		||||
    monthlyApiCreditsRemaining: 10,
 | 
			
		||||
    stableApiCreditsRemaining: 0,
 | 
			
		||||
  },
 | 
			
		||||
  subscriptions: {
 | 
			
		||||
    monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
    name: "free",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const proTierData = {
 | 
			
		||||
  // These are all ignored
 | 
			
		||||
  balance: {
 | 
			
		||||
    monthlyApiCreditsRemaining: 10,
 | 
			
		||||
    stableApiCreditsRemaining: 0,
 | 
			
		||||
  },
 | 
			
		||||
  subscriptions: {
 | 
			
		||||
    // This should be ignored because it's Pro tier.
 | 
			
		||||
    monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
    name: "pro",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const enterpriseTierData = {
 | 
			
		||||
  // These are all ignored, user is part of an org.
 | 
			
		||||
  balance: {
 | 
			
		||||
    monthlyApiCreditsRemaining: 10,
 | 
			
		||||
    stableApiCreditsRemaining: 0,
 | 
			
		||||
  },
 | 
			
		||||
  subscriptions: {
 | 
			
		||||
    // This should be ignored because it's Pro tier.
 | 
			
		||||
    monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
    // This should be ignored because the user is part of an Org.
 | 
			
		||||
    name: "free",
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test('Shows the total credits for Unknown subscription', async () => {
 | 
			
		||||
  const data = unKnownTierData
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
 | 
			
		||||
@ -166,17 +205,7 @@ test('Shows the total credits for Unknown subscription', async () => {
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Progress bar reflects ratio left of Free subscription', async () => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    balance: {
 | 
			
		||||
      monthlyApiCreditsRemaining: 10,
 | 
			
		||||
      stableApiCreditsRemaining: 0,
 | 
			
		||||
    },
 | 
			
		||||
    subscriptions: {
 | 
			
		||||
      monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
      name: "free",
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = freeTierData
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
 | 
			
		||||
@ -212,19 +241,7 @@ test('Progress bar reflects ratio left of Free subscription', async () => {
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
test('Shows infinite credits for Pro subscription', async () => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    // These are all ignored
 | 
			
		||||
    balance: {
 | 
			
		||||
      monthlyApiCreditsRemaining: 10,
 | 
			
		||||
      stableApiCreditsRemaining: 0,
 | 
			
		||||
    },
 | 
			
		||||
    subscriptions: {
 | 
			
		||||
      // This should be ignored because it's Pro tier.
 | 
			
		||||
      monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
      name: "pro",
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const data = proTierData
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
 | 
			
		||||
@ -255,19 +272,7 @@ test('Shows infinite credits for Pro subscription', async () => {
 | 
			
		||||
  await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
 | 
			
		||||
})
 | 
			
		||||
test('Shows infinite credits for Enterprise subscription', async () => {
 | 
			
		||||
  const data = {
 | 
			
		||||
    // These are all ignored, user is part of an org.
 | 
			
		||||
    balance: {
 | 
			
		||||
      monthlyApiCreditsRemaining: 10,
 | 
			
		||||
      stableApiCreditsRemaining: 0,
 | 
			
		||||
    },
 | 
			
		||||
    subscriptions: {
 | 
			
		||||
      // This should be ignored because it's Pro tier.
 | 
			
		||||
      monthlyPayAsYouGoApiCreditsTotal: 20,
 | 
			
		||||
      // This should be ignored because the user is part of an Org.
 | 
			
		||||
      name: "free",
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  const data = enterpriseTierData
 | 
			
		||||
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
@ -297,3 +302,58 @@ test('Shows infinite credits for Enterprise subscription', async () => {
 | 
			
		||||
  await expect(queryByTestId('infinity')).toBeVisible()
 | 
			
		||||
  await expect(queryByTestId('billing-remaining-progress-bar-inline')).toBe(null)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Show upgrade button if credits are not infinite', async () => {
 | 
			
		||||
  const data = freeTierData
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
 | 
			
		||||
    }),
 | 
			
		||||
    http.get('*/user/payment/subscriptions', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
 | 
			
		||||
    }),
 | 
			
		||||
    http.get('*/org', (req, res, ctx) => {
 | 
			
		||||
      return new HttpResponse(403)
 | 
			
		||||
    }),
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
 | 
			
		||||
 | 
			
		||||
  const { queryByTestId } = render(<BillingDialog
 | 
			
		||||
    billingActor={billingActor}
 | 
			
		||||
  />)
 | 
			
		||||
 | 
			
		||||
  await act(() => {
 | 
			
		||||
    billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  await expect(queryByTestId('billing-upgrade-button')).toBeVisible()
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
test('Hide upgrade button if credits are infinite', async () => {
 | 
			
		||||
  const data = enterpriseTierData
 | 
			
		||||
  server.use(
 | 
			
		||||
    http.get('*/user/payment/balance', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentBalanceResponse(data.balance))
 | 
			
		||||
    }),
 | 
			
		||||
    http.get('*/user/payment/subscriptions', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createUserPaymentSubscriptionsResponse(data.subscriptions))
 | 
			
		||||
    }),
 | 
			
		||||
    // Ok finally the first use of an org lol
 | 
			
		||||
    http.get('*/org', (req, res, ctx) => {
 | 
			
		||||
      return HttpResponse.json(createOrgResponse())
 | 
			
		||||
    }),
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  const billingActor = createActor(billingMachine, { input: BILLING_CONTEXT_DEFAULTS }).start()
 | 
			
		||||
 | 
			
		||||
  const { queryByTestId } = render(<BillingDialog
 | 
			
		||||
    billingActor={billingActor}
 | 
			
		||||
  />)
 | 
			
		||||
 | 
			
		||||
  await act(() => {
 | 
			
		||||
    billingActor.send({ type: BillingTransition.Update, apiToken: "it doesn't matter wtf this is :)" })
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
  await expect(queryByTestId('billing-upgrade-button')).toBe(null)
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB  | 
| 
		 Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 84 KiB  | 
| 
		 Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 126 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB  | 
| 
		 Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB  | 
| 
		 Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 81 KiB  | 
| 
		 Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB  | 
| 
		 Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 105 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB  | 
| 
		 Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 93 KiB  | 
| 
		 Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB  | 
| 
		 Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB  | 
| 
		 Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB  | 
| 
		 Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB  | 
| 
		 Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB  | 
| 
		 Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB  | 
| 
		 Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 75 KiB  | 
| 
		 Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB  | 
| 
		 Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB  | 
| 
		 Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB  | 
| 
		 Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB  | 
| 
		 Before Width: | Height: | Size: 88 KiB After Width: | Height: | Size: 88 KiB  | 
| 
		 Before Width: | Height: | Size: 71 KiB After Width: | Height: | Size: 71 KiB  | 
| 
		 Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB  | 
| 
		 Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB  | 
| 
		 Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB  | 
| 
		 Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB  | 
| 
		 Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB  | 
| 
		 Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB  | 
| 
		 Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 114 KiB  | 
| 
		 Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB  | 
| 
		 Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB  | 
| 
		 Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 77 KiB  | 
| 
		 Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB  | 
| 
		 Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB  | 
| 
		 Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB  | 
| 
		 Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB  | 
| 
		 Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB  | 
| 
		 Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB  | 
| 
		 Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB  | 
| 
		 Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB  | 
| 
		 Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 154 KiB  | 
| 
		 Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB  | 
| 
		 Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB  | 
| 
		 Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB  | 
| 
		 Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB  | 
| 
		 Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB  | 
| 
		 Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB  | 
| 
		 Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB  | 
							
								
								
									
										33
									
								
								rust/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						@ -1814,7 +1814,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-bumper"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1825,7 +1825,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-derive-docs"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
@ -1834,7 +1834,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-directory-test-macro"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "convert_case",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
@ -1844,7 +1844,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-language-server"
 | 
			
		||||
version = "0.2.82"
 | 
			
		||||
version = "0.2.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1865,7 +1865,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-language-server-release"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "clap",
 | 
			
		||||
@ -1885,7 +1885,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-lib"
 | 
			
		||||
version = "0.2.82"
 | 
			
		||||
version = "0.2.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "approx 0.5.1",
 | 
			
		||||
@ -1962,7 +1962,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-python-bindings"
 | 
			
		||||
version = "0.3.82"
 | 
			
		||||
version = "0.3.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "kcl-lib",
 | 
			
		||||
@ -1977,7 +1977,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-test-server"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "hyper 0.14.32",
 | 
			
		||||
@ -1990,7 +1990,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-to-core"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -2004,7 +2004,7 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kcl-wasm-lib"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bson",
 | 
			
		||||
@ -2071,9 +2071,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "kittycad-modeling-cmds"
 | 
			
		||||
version = "0.2.123"
 | 
			
		||||
version = "0.2.125"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "b8f3c1b4b4ddb9aa336a09933f2550f9882552e321187b7bcff47f006379c3aa"
 | 
			
		||||
checksum = "cfd09d95f8bbeb090d4d1137c9bf421eb75763f7a30e4a9e8eefa249ddf20bd3"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "chrono",
 | 
			
		||||
@ -4428,13 +4428,12 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ts-rs"
 | 
			
		||||
version = "10.1.0"
 | 
			
		||||
version = "11.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "e640d9b0964e9d39df633548591090ab92f7a4567bc31d3891af23471a3365c6"
 | 
			
		||||
checksum = "6ef1b7a6d914a34127ed8e1fa927eb7088903787bcded4fa3eef8f85ee1568be"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "chrono",
 | 
			
		||||
 "indexmap 2.9.0",
 | 
			
		||||
 "lazy_static",
 | 
			
		||||
 "serde_json",
 | 
			
		||||
 "thiserror 2.0.12",
 | 
			
		||||
 "ts-rs-macros",
 | 
			
		||||
@ -4444,9 +4443,9 @@ dependencies = [
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "ts-rs-macros"
 | 
			
		||||
version = "10.1.0"
 | 
			
		||||
version = "11.0.1"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "0e9d8656589772eeec2cf7a8264d9cda40fb28b9bc53118ceb9e8c07f8f38730"
 | 
			
		||||
checksum = "e9d4ed7b4c18cc150a6a0a1e9ea1ecfa688791220781af6e119f9599a8502a0a"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 | 
			
		||||
@ -36,7 +36,7 @@ dashmap = { version = "6.1.0" }
 | 
			
		||||
http = "1"
 | 
			
		||||
indexmap = "2.9.0"
 | 
			
		||||
kittycad = { version = "0.3.37", default-features = false, features = ["js", "requests"] }
 | 
			
		||||
kittycad-modeling-cmds = { version = "0.2.123", features = ["ts-rs", "websocket"] }
 | 
			
		||||
kittycad-modeling-cmds = { version = "0.2.125", features = ["ts-rs", "websocket"] }
 | 
			
		||||
lazy_static = "1.5.0"
 | 
			
		||||
miette = "7.6.0"
 | 
			
		||||
pyo3 = { version = "0.24.2" }
 | 
			
		||||
@ -60,6 +60,6 @@ lossy_float_literal = "warn"
 | 
			
		||||
result_large_err = "allow"
 | 
			
		||||
 | 
			
		||||
# Example: how to point modeling-app at a different repo (e.g. a branch or a local clone)
 | 
			
		||||
#[patch.crates-io]
 | 
			
		||||
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
 | 
			
		||||
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
 | 
			
		||||
# [patch.crates-io]
 | 
			
		||||
# kittycad-modeling-cmds = { path = "../../modeling-api/modeling-cmds/" }
 | 
			
		||||
# kittycad-modeling-session = { path = "../../modeling-api/modeling-session" }
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-bumper"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-api"
 | 
			
		||||
rust-version = "1.76"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-derive-docs"
 | 
			
		||||
description = "A tool for generating documentation from Rust derive macros"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-directory-test-macro"
 | 
			
		||||
description = "A tool for generating tests from a directory of kcl files"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
license = "MIT"
 | 
			
		||||
repository = "https://github.com/KittyCAD/modeling-app"
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "kcl-language-server-release"
 | 
			
		||||
version = "0.1.82"
 | 
			
		||||
version = "0.1.83"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
 | 
			
		||||
publish = false
 | 
			
		||||
 | 
			
		||||