Merge branch 'main' into nadro/adhoc/system-io-machine

This commit is contained in:
Kevin Nadro
2025-04-15 13:06:24 -06:00
188 changed files with 8721 additions and 1815 deletions

View File

@ -285,7 +285,7 @@ jobs:
# TODO: enable namespace-profile-windows-latest once available
os:
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
- namespace-profile-macos-8-cores
- namespace-profile-macos-6-cores
- windows-latest-8-cores
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
@ -295,7 +295,7 @@ jobs:
isScheduled:
- ${{ github.event_name == 'schedule' }}
exclude:
- os: namespace-profile-macos-8-cores
- os: namespace-profile-macos-6-cores
isScheduled: true
- os: windows-latest-8-cores
isScheduled: true

View File

@ -25,7 +25,7 @@ jobs:
node-version-file: '.nvmrc'
cache: 'npm'
- run: npm install
- run: npm run fmt-check
- run: npm run fmt:check
npm-build-wasm:
# Build the wasm blob once on the fastest runner.

1
.gitignore vendored
View File

@ -84,3 +84,4 @@ dist
venv
.vscode-test
.biome/

View File

@ -250,55 +250,29 @@ You will need a `./e2e/playwright/playwright-secrets.env` file:
$ touch ./e2e/playwright/playwright-secrets.env
$ cat ./e2e/playwright/playwright-secrets.env
token=<dev.zoo.dev/account/api-tokens>
snapshottoken=<your-snapshot-token>
snapshottoken=<zoo.dev/account/api-tokens>
```
or use `export` to set the environment variables `token` and `snapshottoken`.
For a portable way to run Playwright you'll need Docker.
#### Snapshot tests (Google Chrome on Ubuntu only)
#### Generic example
After that, open a terminal and run:
```bash
docker run --network host --rm --init -it playwright/chrome:playwright-x.xx.x
Only Ubunu and Google Chrome is supported for the set of tests evaluating screenshot snapshots.
If you don't run Ubuntu locally or in a VM, you may use a GitHub Codespace.
```
and in another terminal, run:
```bash
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ npm run playwright test --project="Google Chrome" <test suite>
npm run playwright -- install chrome
npm run test:snapshots
```
You may use `-- --update-snapshots` as needed.
#### Specific example
open a terminal and run:
```bash
docker run --network host --rm --init -it playwright/chrome:playwright-1.46.0
```
and in another terminal, run:
```bash
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ npm run playwright test --project="Google Chrome" e2e/playwright/command-bar-tests.spec.ts
```
run a specific test change the test from `test('...` to `test.only('...`
(note if you commit this, the tests will instantly fail without running any of the tests)
**Gotcha**: running the docker container with a mismatched image against your `./node_modules/playwright` will cause a failure. Make sure the versions are matched and up to date.
run headed
#### Electron flow tests (Chromium on Ubuntu, macOS, Windows)
```
npm run playwright test --headed
npm run playwright -- install chromium
npm run test:playwright:electron:local
```
You may use `-- -g "my test"` to match specific test titles, or `-- path/to/file.spec.ts` for a test file.
run with step through debugger
```
PWDEBUG=1 npm run playwright test
```
#### Debugger
However, if you want a debugger I recommend using VSCode and the `playwright` extension, as the above command is a cruder debugger that steps into every function call which is annoying.
With the extension you can set a breakpoint after `waitForDefaultPlanesVisibilityChange` in order to skip app loading, then the vscode debugger's "step over" is much better for being able to stay at the right level of abstraction as you debug the code.
@ -446,7 +420,7 @@ npm run test-setup
```
npm run tsc
npm run fmt-check
npm run fmt:check
npm run lint
npm run test:unit:local
```

48
biome.json Normal file
View File

@ -0,0 +1,48 @@
{
"$schema": "https://biomejs.dev/schemas/1.6.0/schema.json",
"organizeImports": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": {
"noUnusedVariables": "error"
},
"suspicious": {
"noExplicitAny": "warn"
},
"style": {
"useBlockStatements": "error",
"useShorthandArrayType": "error"
}
}
},
"formatter": {
"enabled": true,
"indentWidth": 2,
"indentStyle": "space",
"lineWidth": 80,
"formatWithErrors": true
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "double",
"trailingCommas": "es5",
"semicolons": "asNeeded"
},
"parser": {
"unsafeParameterDecoratorsEnabled": true
}
},
"css": {
"parser": {
"cssModules": true
}
},
"files": {
"ignore": ["**/*.json"]
}
}

View File

@ -10,8 +10,10 @@ Draw an angled line from the current origin, constructing a line segment such th
```js
angledLineThatIntersects(
data: AngledLineThatIntersectsData,
sketch: Sketch,
angle: number,
intersectTag: TagIdentifier,
offset?: number,
tag?: TagDeclarator,
): Sketch
```
@ -21,9 +23,11 @@ angledLineThatIntersects(
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`AngledLineThatIntersectsData`](/docs/kcl/types/AngledLineThatIntersectsData) | Data for drawing an angled line that intersects with a given line. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
| `angle` | [`number`](/docs/kcl/types/number) | Which angle should the line be drawn at? | Yes |
| `intersectTag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the line to intersect with | Yes |
| `offset` | [`number`](/docs/kcl/types/number) | The offset from the intersecting line. Defaults to 0. | No |
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |
### Returns
@ -38,11 +42,7 @@ exampleSketch = startSketchOn(XZ)
|> line(endAbsolute = [5, 10])
|> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
|> line(endAbsolute = [0, 20])
|> angledLineThatIntersects({
angle = 80,
intersectTag = lineToIntersect,
offset = 10
}, %)
|> angledLineThatIntersects(angle = 80, intersectTag = lineToIntersect, offset = 10)
|> close()
example = extrude(exampleSketch, length = 10)

File diff suppressed because it is too large Load Diff

View File

@ -145,7 +145,7 @@ This helps keep your code neat and avoid unnecessary declarations.
Say you have a long pipeline of sketch functions, like this:
```norun
startSketchOn('XZ')
startSketchOn(XZ)
|> line(%, end = [3, 4])
|> line(%, end = [10, 10])
|> line(%, end = [-13, -14])
@ -160,7 +160,7 @@ means that `|> line(%, end = [3, 4])` and `|> line(end = [3, 4])` are equivalent
could be rewritten as
```norun
startSketchOn('XZ')
startSketchOn(XZ)
|> line(end = [3, 4])
|> line(end = [10, 10])
|> line(end = [-13, -14])
@ -182,7 +182,7 @@ The syntax for declaring a tag is `$myTag` you would use it in the following
way:
```norun
startSketchOn('XZ')
startSketchOn(XZ)
|> startProfileAt(origin, %)
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|> angledLine(
@ -217,7 +217,7 @@ However if the code was written like this:
```norun
fn rect(origin) {
return startSketchOn('XZ')
return startSketchOn(XZ)
|> startProfileAt(origin, %)
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|> angledLine(
@ -227,7 +227,7 @@ fn rect(origin) {
)
|> angledLine(
angle = segAng(rectangleSegmentA001),
length = -segLen(rectangleSegmentA001)
length = -segLen(rectangleSegmentA001),
tag = $rectangleSegmentC001,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -247,7 +247,7 @@ For example the following code works.
```norun
fn rect(origin) {
return startSketchOn('XZ')
return startSketchOn(XZ)
|> startProfileAt(origin, %)
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|> angledLine(
@ -257,7 +257,7 @@ fn rect(origin) {
)
|> angledLine(
angle = segAng(rectangleSegmentA001),
length = -segLen(rectangleSegmentA001)
length = -segLen(rectangleSegmentA001),
tag = $rectangleSegmentC001,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -268,11 +268,8 @@ rect([0, 0])
myRect = rect([20, 0])
myRect
|> extrude(10, %)
|> fillet(
radius = 0.5,
tags = [myRect.tags.rectangleSegmentA001]
)
|> extrude(length = 10)
|> fillet(radius = 0.5, tags = [myRect.tags.rectangleSegmentA001])
```
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

View File

@ -94,11 +94,8 @@ rect([0, 0])
myRect = rect([20, 0])
myRect
|> extrude(10, %)
|> fillet(
radius = 0.5,
tags = [myRect.tags.rectangleSegmentA001]
)
|> extrude(length = 10)
|> fillet(radius = 0.5, tags = [myRect.tags.rectangleSegmentA001])
```
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

View File

@ -66,15 +66,17 @@ async function doBasicSketch(
await page.waitForTimeout(500)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
await expect(
u.codeLocator
).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(length = ${commonPoints.num1})`)
}
await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
await expect(
u.codeLocator
).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -85,8 +87,9 @@ async function doBasicSketch(
await page.waitForTimeout(200)
await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
await expect(
u.codeLocator
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -142,8 +145,9 @@ async function doBasicSketch(
// Open the code pane.
await u.openKclCodePanel()
await expect(u.codeLocator)
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
await expect(
u.codeLocator
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1}, tag = $seg01)

View File

@ -1,6 +1,6 @@
import { join } from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import fsp from 'fs/promises'
import { join } from 'path'
import { TEST_CODE_LONG_WITH_ERROR_OUT_OF_VIEW } from '@e2e/playwright/storageStates'
import {

View File

@ -1,6 +1,6 @@
import path, { join } from 'path'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import * as fsp from 'fs/promises'
import path, { join } from 'path'
import {
executorInputPath,

View File

@ -1,5 +1,5 @@
import fsp from 'fs/promises'
import path from 'path'
import fsp from 'fs/promises'
import {
executorInputPath,

View File

@ -1,6 +1,6 @@
import { join } from 'path'
import { uuidv4 } from '@src/lib/utils'
import fsp from 'fs/promises'
import { join } from 'path'
import {
TEST_COLORS,
@ -82,7 +82,7 @@ sketch001 = startSketchOn(XY)
.poll(() =>
page.locator('[data-receive-command-type="scene_clear_all"]').count()
)
.toBe(1)
.toBe(2)
await expect
.poll(() => page.locator('[data-message-type="execution-done"]').count())
.toBe(2)
@ -106,7 +106,7 @@ sketch001 = startSketchOn(XY)
).toHaveCount(3)
await expect(
page.locator('[data-receive-command-type="scene_clear_all"]')
).toHaveCount(1)
).toHaveCount(2)
})
test('ensure we use the cache, and do not clear on append', async ({
@ -133,7 +133,7 @@ sketch001 = startSketchOn(XY)
await u.openDebugPanel()
await expect(
page.locator('[data-receive-command-type="scene_clear_all"]')
).toHaveCount(1)
).toHaveCount(2)
await expect(
page.locator('[data-message-type="execution-done"]')
).toHaveCount(2)
@ -161,7 +161,7 @@ sketch001 = startSketchOn(XY)
).toHaveCount(3)
await expect(
page.locator('[data-receive-command-type="scene_clear_all"]')
).toHaveCount(1)
).toHaveCount(2)
})
test('if you click the format button it formats your code', async ({
@ -409,8 +409,9 @@ sketch_001 = startSketchOn(XY)
// Hit alt+shift+f to format the code
await page.keyboard.press('Alt+Shift+KeyF')
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XY)
await expect(
page.locator('.cm-content')
).toHaveText(`sketch001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -462,8 +463,9 @@ sketch_001 = startSketchOn(XY)
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await expect(page.locator('.cm-content'))
.toHaveText(`sketch_001 = startSketchOn(XY)
await expect(
page.locator('.cm-content')
).toHaveText(`sketch_001 = startSketchOn(XY)
|> startProfileAt([-10, -10], %)
|> line(end = [20, 0])
|> line(end = [0, 20])
@ -978,8 +980,9 @@ sketch001 = startSketchOn(XZ)
await page.keyboard.up('Control')
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
await expect(
page.locator('.cm-content')
).toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([4.61, -14.01], %)
|> line(end = [12.73, -0.09])
|> tangentialArc(endAbsolute = [24.95, -5.38])

View File

@ -1,5 +1,5 @@
import * as fsp from 'fs/promises'
import { join } from 'path'
import * as fsp from 'fs/promises'
import { expect, test } from '@e2e/playwright/zoo-test'

View File

@ -1,7 +1,7 @@
import { FILE_EXT } from '@src/lib/constants'
import * as fs from 'fs'
import * as fsp from 'fs/promises'
import { join } from 'path'
import { FILE_EXT } from '@src/lib/constants'
import * as fsp from 'fs/promises'
import {
createProject,

View File

@ -1,9 +1,9 @@
import type { Locator, Page, Request, Route, TestInfo } from '@playwright/test'
import { expect } from '@playwright/test'
import * as fs from 'fs'
import * as path from 'path'
import type { Locator, Page, Request, Route, TestInfo } from '@playwright/test'
import { expect } from '@playwright/test'
type CmdBarSerialised =
export type CmdBarSerialised =
| {
stage: 'commandBarClosed'
}

View File

@ -7,11 +7,11 @@ import type {
} from '@playwright/test'
import { _electron as electron } from '@playwright/test'
import fs from 'node:fs'
import path from 'path'
import { SETTINGS_FILE_NAME } from '@src/lib/constants'
import type { DeepPartial } from '@src/lib/types'
import fsp from 'fs/promises'
import fs from 'node:fs'
import path from 'path'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'

View File

@ -44,6 +44,7 @@ export class SceneFixture {
public page: Page
public streamWrapper!: Locator
public networkToggleConnected!: Locator
public engineConnectionsSpinner!: Locator
public startEditSketchBtn!: Locator
constructor(page: Page) {
@ -52,6 +53,7 @@ export class SceneFixture {
this.networkToggleConnected = page
.getByTestId('network-toggle-ok')
.or(page.getByTestId('network-toggle-other'))
this.engineConnectionsSpinner = page.getByTestId(`loading-engine`)
this.startEditSketchBtn = page
.getByRole('button', { name: 'Start Sketch' })
.or(page.getByRole('button', { name: 'Edit Sketch' }))
@ -228,6 +230,7 @@ export class SceneFixture {
connectionEstablished = async () => {
const timeout = 30000
await expect(this.networkToggleConnected).toBeVisible({ timeout })
await expect(this.engineConnectionsSpinner).not.toBeVisible()
}
settled = async (cmdBar: CmdBarFixture) => {
@ -235,6 +238,7 @@ export class SceneFixture {
await expect(this.startEditSketchBtn).not.toBeDisabled({ timeout: 15_000 })
await expect(this.startEditSketchBtn).toBeVisible()
await expect(this.engineConnectionsSpinner).not.toBeVisible()
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · app · show debug panel')

View File

@ -27,6 +27,7 @@ export class ToolbarFixture {
offsetPlaneButton!: Locator
helixButton!: Locator
startSketchBtn!: Locator
insertButton!: Locator
lineBtn!: Locator
tangentialArcBtn!: Locator
circleBtn!: Locator
@ -44,7 +45,7 @@ export class ToolbarFixture {
featureTreePane!: Locator
gizmo!: Locator
gizmoDisabled!: Locator
insertButton!: Locator
loadButton!: Locator
constructor(page: Page) {
this.page = page
@ -59,6 +60,7 @@ export class ToolbarFixture {
this.offsetPlaneButton = page.getByTestId('plane-offset')
this.helixButton = page.getByTestId('helix')
this.startSketchBtn = page.getByTestId('sketch')
this.insertButton = page.getByTestId('insert')
this.lineBtn = page.getByTestId('line')
this.tangentialArcBtn = page.getByTestId('tangential-arc')
this.circleBtn = page.getByTestId('circle-center')
@ -68,6 +70,7 @@ export class ToolbarFixture {
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
this.createFileBtn = page.getByTestId('create-file-button')
this.treeInputField = page.getByTestId('tree-input-field')
this.loadButton = page.getByTestId('load-external-model-pane-button')
this.filePane = page.locator('#files-pane')
this.featureTreePane = page.locator('#feature-tree-pane')
@ -79,8 +82,6 @@ export class ToolbarFixture {
// element or two different elements can represent these states.
this.gizmo = page.getByTestId('gizmo')
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
this.insertButton = page.getByTestId('insert-pane-button')
}
get logoLink() {
@ -204,6 +205,11 @@ export class ToolbarFixture {
).toBeVisible()
await this.page.getByTestId('dropdown-three-point-arc').click()
}
selectLine = async () => {
await this.page
.getByRole('button', { name: 'line Line', exact: true })
.click()
}
async closePane(paneId: SidebarType) {
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)

View File

@ -1,9 +1,9 @@
import path from 'path'
import { expect, test } from '@e2e/playwright/zoo-test'
import * as fsp from 'fs/promises'
import path from 'path'
test.describe('Import UI tests', () => {
test('shows toast when trying to sketch on imported face', async ({
test('shows toast when trying to sketch on imported face, and hovering over imported geometry should NOT highlight any code', async ({
context,
page,
homePage,
@ -93,7 +93,6 @@ sketch002 = startSketchOn(extrude001, face = seg01)`
await toolbar.startSketchPlaneSelection()
// Click on a face from the imported model
// await new Promise(() => {})
await importedFaceClick()
// Verify toast appears with correct content

View File

@ -19,8 +19,9 @@ class MyAPIReporter implements Reporter {
target: process.env.TARGET || null,
platform: process.env.RUNNER_OS || process.platform,
// Extra test and result data
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
retry: result.retry,
tags: test.tags,
tags: test.tags, // e.g. '@snapshot' or '@skipWin'
// Extra environment variables
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
CI_PR_NUMBER: process.env.CI_PR_NUMBER || null,

View File

@ -1,5 +1,5 @@
import fsp from 'fs/promises'
import { join } from 'path'
import fsp from 'fs/promises'
import { executorInputPath } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'

View File

@ -1,6 +1,6 @@
import { join } from 'path'
import { PROJECT_SETTINGS_FILE_NAME } from '@src/lib/constants'
import * as fsp from 'fs/promises'
import { join } from 'path'
import type { NamedView } from '@rust/kcl-lib/bindings/NamedView'

View File

@ -534,7 +534,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
const expected = 'Open project'
expect(actual).toBe(expected)
})
test('Modeling.File.Load a sample model', async ({
test('Modeling.File.Load external model', async ({
tronApp,
cmdBar,
page,
@ -555,10 +555,10 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
throw new Error('app or app.applicationMenu is missing')
}
const openProject = app.applicationMenu.getMenuItemById(
'File.Load a sample model'
'File.Load external model'
)
if (!openProject) {
throw new Error('File.Load a sample model')
throw new Error('File.Load external model')
}
openProject.click()
})
@ -568,44 +568,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Open sample'
expect(actual).toBe(expected)
})
test('Modeling.File.Insert from project file', async ({
tronApp,
cmdBar,
page,
homePage,
scene,
}) => {
if (!tronApp) {
throwTronAppMissing()
return
}
await homePage.goToModelingScene()
await scene.settled(cmdBar)
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) {
throw new Error('app or app.applicationMenu is missing')
}
const openProject = app.applicationMenu.getMenuItemById(
'File.Insert from project file'
)
if (!openProject) {
throw new Error('File.Insert from project file')
}
openProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Insert'
const expected = 'Load external model'
expect(actual).toBe(expected)
})
test('Modeling.File.Export current part', async ({
@ -2159,6 +2122,44 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
expect(actual).toBe(expected)
})
test('Modeling.Design.Insert from project file', async ({
tronApp,
cmdBar,
page,
homePage,
scene,
}) => {
if (!tronApp) {
throwTronAppMissing()
return
}
await homePage.goToModelingScene()
await scene.settled(cmdBar)
// Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
await tronApp.electron.evaluate(async ({ app }) => {
if (!app || !app.applicationMenu) {
throw new Error('app or app.applicationMenu is missing')
}
const openProject = app.applicationMenu.getMenuItemById(
'Design.Insert from project file'
)
if (!openProject) {
throw new Error('Design.Insert from project file')
}
openProject.click()
})
// Check that the command bar is opened
await expect(cmdBar.cmdBarElement).toBeVisible()
// Check the placeholder project name exists
const actual = await cmdBar.cmdBarElement
.getByTestId('command-name')
.textContent()
const expected = 'Insert'
expect(actual).toBe(expected)
})
test('Modeling.Design.Create with Zoo Text-To-CAD', async ({
tronApp,
cmdBar,

View File

@ -1,7 +1,7 @@
import { join } from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import { onboardingPaths } from '@src/routes/Onboarding/paths'
import fsp from 'fs/promises'
import { join } from 'path'
import { expectPixelColor } from '@e2e/playwright/fixtures/sceneFixture'
import {

View File

@ -1,5 +1,5 @@
import * as fsp from 'fs/promises'
import path from 'path'
import * as fsp from 'fs/promises'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'

View File

@ -1,6 +1,6 @@
import type { Locator, Page } from '@playwright/test'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { Locator, Page } from '@playwright/test'
import type { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'

View File

@ -1,7 +1,7 @@
import { DEFAULT_PROJECT_KCL_FILE } from '@src/lib/constants'
import fs from 'fs'
import fsp from 'fs/promises'
import path from 'path'
import { DEFAULT_PROJECT_KCL_FILE } from '@src/lib/constants'
import fsp from 'fs/promises'
import type { Paths } from '@e2e/playwright/test-utils'
import {

View File

@ -1,8 +1,8 @@
import path from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import type { Page } from '@playwright/test'
import { reportRejection } from '@src/lib/trap'
import * as fsp from 'fs/promises'
import path from 'path'
import { TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR } from '@e2e/playwright/storageStates'
import type { TestColor } from '@e2e/playwright/test-utils'
@ -304,8 +304,9 @@ extrude001 = extrude(sketch001, length = 50)
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowLeft')
await expect(page.locator('.cm-content'))
.toContainText(`exampleSketch = startSketchOn("XZ")
await expect(
page.locator('.cm-content')
).toContainText(`exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine(angle = 50, length = 45 )
|> yLine(endAbsolute = 0)

View File

@ -1,7 +1,7 @@
import type { Page } from '@playwright/test'
import { roundOff, uuidv4 } from '@src/lib/utils'
import fs from 'node:fs/promises'
import path from 'node:path'
import type { Page } from '@playwright/test'
import { roundOff, uuidv4 } from '@src/lib/utils'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
@ -161,7 +161,8 @@ sketch001 = startSketchOn(XZ)
// click to add segment
await page.mouse.click(700, 200)
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
await expect
.poll(u.normalisedEditorCode, { timeout: 1000 })
.toBe(`@settings(defaultLengthUnit = in)
@ -403,8 +404,9 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
await u.openKclCodePanel()
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)
await expect(
page.locator('.cm-content')
).toHaveText(`sketch001 = startSketchOn(XZ)
|> startProfileAt([6.44, -12.07], %)
|> line(end = [14.72, 1.97])
|> tangentialArc(endAbsolute = [26.92, -3.32])
@ -1363,73 +1365,6 @@ profile001 = startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(
})
})
test.describe('Sketch mode should be toleratant to syntax errors', () => {
test(
'adding a syntax error, recovers after fixing',
{ tag: ['@skipWin'] },
async ({ page, homePage, context, scene, editor, toolbar }) => {
const file = await fs.readFile(
path.resolve(
__dirname,
'../../',
'./rust/kcl-lib/e2e/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
),
'utf-8'
)
await context.addInitScript((file) => {
localStorage.setItem('persistCode', file)
}, file)
await homePage.goToModelingScene()
const [objClick] = scene.makeMouseHelpers(600, 250)
const arrowHeadLocation = { x: 706, y: 129 } as const
const arrowHeadWhite = TEST_COLORS.WHITE
const backgroundGray: [number, number, number] = [28, 28, 28]
const verifyArrowHeadColor = async (c: [number, number, number]) =>
scene.expectPixelColor(c, arrowHeadLocation, 15)
await test.step('check chamfer selection changes cursor positon', async () => {
await expect(async () => {
// sometimes initial click doesn't register
await objClick()
await editor.expectActiveLinesToBe([
'|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]',
])
}).toPass({ timeout: 15_000, intervals: [500] })
})
await test.step('enter sketch and sanity check segments have been drawn', async () => {
await toolbar.editSketch()
// this checks sketch segments have been drawn
await verifyArrowHeadColor(arrowHeadWhite)
})
await test.step('Make typo and check the segments have Disappeared and there is a syntax error', async () => {
await editor.replaceCode('line(endAbsolute = [pro', 'badBadBadFn([pro')
await editor.expectState({
activeLines: [],
diagnostics: ['memoryitemkey`badBadBadFn`isnotdefined'],
highlightedCode: '',
})
// this checks sketch segments have failed to be drawn
await verifyArrowHeadColor(backgroundGray)
})
await test.step('', async () => {
await editor.replaceCode('badBadBadFn([pro', 'line(endAbsolute = [pro')
await editor.expectState({
activeLines: [],
diagnostics: [],
highlightedCode: '',
})
// this checks sketch segments have been drawn
await verifyArrowHeadColor(arrowHeadWhite)
})
await page.waitForTimeout(100)
}
)
})
test.describe(`Sketching with offset planes`, () => {
test(`Can select an offset plane to sketch on`, async ({
context,
@ -1623,6 +1558,7 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
localStorage.setItem(
'persistCode',
`@settings(defaultLengthUnit = in)
sketch001 = startSketchOn(XZ)
profile002 = startProfileAt([40.68, 87.67], sketch001)
|> xLine(length = 239.17)
@ -3034,4 +2970,387 @@ test.describe('Redirecting to home page and back to the original file should cle
await homePage.openProject('testDefault')
await expect(page.getByText('323.49')).not.toBeVisible()
})
test('Straight line snapping to previous tangent', async ({
page,
homePage,
toolbar,
scene,
cmdBar,
context,
editor,
}) => {
await context.addInitScript(() => {
localStorage.setItem('persistCode', `@settings(defaultLengthUnit = mm)`)
})
const viewportSize = { width: 1200, height: 900 }
await page.setBodyDimensions(viewportSize)
await homePage.goToModelingScene()
// wait until scene is ready to be interacted with
await scene.connectionEstablished()
await scene.settled(cmdBar)
await page.getByRole('button', { name: 'Start Sketch' }).click()
// select an axis plane
await page.mouse.click(700, 200)
// Needed as we don't yet have a way to get a signal from the engine that the camera has animated to the sketch plane
await page.waitForTimeout(3000)
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
const { click00r } = getMovementUtils({ center, page })
// Draw line
await click00r(0, 0)
await click00r(200, -200)
// Draw arc
await toolbar.tangentialArcBtn.click()
await click00r(0, 0)
await click00r(100, 100)
// Switch back to line
await toolbar.selectLine()
await click00r(0, 0)
await click00r(-100, 100)
// Draw a 3 point arc
await toolbar.selectThreePointArc()
await click00r(0, 0)
await click00r(0, 100)
await click00r(100, 0)
// draw a line to opposite tangnet direction of previous arc
await toolbar.selectLine()
await click00r(0, 0)
await click00r(-200, 200)
await editor.expectEditor.toContain(
`@settings(defaultLengthUnit = mm)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([0, 0], sketch001)
|> line(end = [191.39, 191.39])
|> tangentialArc(endAbsolute = [287.08, 95.69], tag = $seg01)
|> angledLine(angle = tangentToEnd(seg01), length = 135.34)
|> arcTo({
interior = [191.39, -95.69],
end = [287.08, -95.69]
}, %, $seg02)
|> angledLine(angle = tangentToEnd(seg02) + turns::HALF_TURN, length = 270.67)
`.replaceAll('\n', '')
)
})
})
test.describe('manual edits during sketch mode', () => {
test('Can edit sketch through feature tree with variable modifications', async ({
page,
context,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `myVar1 = 5
myVar2 = 6
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([106.68, 89.77], sketch001)
|> line(end = [132.34, 157.8])
|> line(end = [67.65, -460.55], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 500)
sketch002 = startSketchOn(extrude001, face = seg01)
profile002 = startProfileAt([83.39, 329.15], sketch002)
|> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 156.54, angle = -28)
|> angledLine(
angle = segAng(rectangleSegmentA001),
length = -segLen(rectangleSegmentA001),
angle = -151,
length = 116.27,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile003 = startProfileAt([-201.08, 254.17], sketch002)
|> line(end = [103.55, 33.32])
|> line(end = [48.8, -153.54])`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
const expectSketchOriginToBeDrawn = async () => {
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 672, y: 193 }, 15)
}
await test.step('Open feature tree and edit second sketch', async () => {
await toolbar.openFeatureTreePane()
const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
await sketchButton.dblclick()
await page.waitForTimeout(700) // Wait for engine animation
await expectSketchOriginToBeDrawn()
})
await test.step('Add new variable and wait for re-execution', async () => {
await page.waitForTimeout(500) // wait for deferred execution
await editor.replaceCode('myVar2 = 6', 'myVar2 = 6\nmyVar3 = 7')
await page.waitForTimeout(2000) // wait for deferred execution
await expectSketchOriginToBeDrawn()
})
const handle1Location = { x: 843, y: 235 }
await test.step('Edit sketch by dragging handle', async () => {
await page.waitForTimeout(500)
await editor.expectEditor.toContain('length = 156.54, angle = -28')
await page.mouse.move(handle1Location.x, handle1Location.y)
await page.mouse.down()
await page.mouse.move(handle1Location.x + 50, handle1Location.y + 50, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 231.59, angle = -34')
// await page.waitForTimeout(1000) // Wait for update
})
await test.step('Delete variables and wait for re-execution', async () => {
await page.waitForTimeout(500)
await editor.replaceCode('myVar3 = 7', '')
await page.waitForTimeout(50)
await editor.replaceCode('myVar2 = 6', '')
await page.waitForTimeout(2000) // Wait for deferred execution
await expectSketchOriginToBeDrawn()
})
const handle2Location = { x: 872, y: 273 }
await test.step('Edit sketch again', async () => {
await editor.expectEditor.toContain('length = 231.59, angle = -34')
await page.waitForTimeout(500)
await page.mouse.move(handle2Location.x, handle2Location.y)
await page.mouse.down()
await page.mouse.move(handle2Location.x, handle2Location.y - 50, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 167.36, angle = -14')
})
await test.step('add whole other sketch before current sketch', async () => {
await page.waitForTimeout(500)
await editor.replaceCode(
`myVar1 = 5`,
`myVar1 = 5
sketch003 = startSketchOn(XY)
profile004 = circle(sketch003, center = [143.91, 136.89], radius = 71.63)`
)
await page.waitForTimeout(2000) // Wait for deferred execution
await expectSketchOriginToBeDrawn()
})
const handle3Location = { x: 844, y: 212 }
await test.step('edit sketch again', async () => {
await editor.expectEditor.toContain('length = 167.36, angle = -14')
await page.mouse.move(handle3Location.x, handle3Location.y)
await page.mouse.down()
await page.mouse.move(handle3Location.x, handle3Location.y + 110, {
steps: 5,
})
await page.mouse.up()
await editor.expectEditor.toContain('length = 219.2, angle = -56')
})
// exit sketch and assert whole code
await test.step('Exit sketch and assert code', async () => {
await toolbar.exitSketch()
await editor.expectEditor.toContain(
`myVar1 = 5
sketch003 = startSketchOn(XY)
profile004 = circle(sketch003, center = [143.91, 136.89], radius = 71.63)
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([106.68, 89.77], sketch001)
|> line(end = [132.34, 157.8])
|> line(end = [67.65, -460.55], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 500)
sketch002 = startSketchOn(extrude001, face = seg01)
profile002 = startProfileAt([83.39, 329.15], sketch002)
|> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 219.2, angle = -56)
|> angledLine(
angle = segAng(rectangleSegmentA001),
length = -segLen(rectangleSegmentA001),
angle = -151,
length = 116.27,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile003 = startProfileAt([-201.08, 254.17], sketch002)
|> line(end = [103.55, 33.32])
|> line(end = [48.8, -153.54])
`,
{ shouldNormalise: true }
)
await editor.expectState({
activeLines: [],
diagnostics: [],
highlightedCode: '',
})
})
})
test('Will exit out of sketch mode for some incompatible edits', async ({
page,
context,
homePage,
scene,
editor,
toolbar,
cmdBar,
}) => {
const initialCode = `myVar1 = 5
myVar2 = 6
sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([106.68, 89.77], sketch001)
|> line(end = [132.34, 157.8])
|> line(end = [67.65, -460.55], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(profile001, length = 500)
sketch002 = startSketchOn(extrude001, face = seg01)
profile002 = startProfileAt([83.39, 329.15], sketch002)
|> angledLine(angle = 0, length = 119.61, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = 156.54, angle = -28)
|> angledLine(
angle = segAng(rectangleSegmentA001),
length = -segLen(rectangleSegmentA001),
angle = -151,
length = 116.27,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile003 = startProfileAt([-201.08, 254.17], sketch002)
|> line(end = [103.55, 33.32])
|> line(end = [48.8, -153.54])`
await context.addInitScript((initialCode) => {
localStorage.setItem('persistCode', initialCode)
}, initialCode)
await homePage.goToModelingScene()
await scene.connectionEstablished()
await scene.settled(cmdBar)
const expectSketchOriginToBeDrawn = async () => {
await scene.expectPixelColor(TEST_COLORS.WHITE, { x: 672, y: 193 }, 15)
}
await test.step('Open feature tree and edit second sketch', async () => {
await toolbar.openFeatureTreePane()
const sketchButton = await toolbar.getFeatureTreeOperation('Sketch', 1)
await sketchButton.dblclick()
await page.waitForTimeout(700) // Wait for engine animation
await expectSketchOriginToBeDrawn()
})
await test.step('rename variable of current sketch, sketch002 to changeSketchNamePartWayThrough', async () => {
await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
await page.waitForTimeout(100)
// three times to rename the declaration and it's use
await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
await page.waitForTimeout(100)
await editor.replaceCode('sketch002', 'changeSketchNamePartWayThrough')
await expect(
page.getByText('Unable to maintain sketch mode')
).toBeVisible()
})
})
test(
'adding a syntax error, recovers after fixing',
{ tag: ['@skipWin'] },
async ({ page, homePage, context, scene, editor, toolbar, cmdBar }) => {
const file = await fs.readFile(
path.resolve(
__dirname,
'../../',
'./rust/kcl-lib/e2e/executor/inputs/e2e-can-sketch-on-chamfer.kcl'
),
'utf-8'
)
await context.addInitScript((file) => {
localStorage.setItem('persistCode', file)
}, file)
await homePage.goToModelingScene()
const [objClick] = scene.makeMouseHelpers(600, 250)
const arrowHeadLocation = { x: 706, y: 129 } as const
const arrowHeadWhite = TEST_COLORS.WHITE
const backgroundGray: [number, number, number] = [28, 28, 28]
const verifyArrowHeadColor = async (c: [number, number, number]) =>
scene.expectPixelColor(c, arrowHeadLocation, 15)
// wait for scene to load
await scene.settled(cmdBar)
await test.step('check chamfer selection changes cursor positon', async () => {
await expect(async () => {
// sometimes initial click doesn't register
await objClick()
await editor.expectActiveLinesToBe([
'|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]',
])
}).toPass({ timeout: 15_000, intervals: [500] })
})
await test.step('enter sketch and sanity check segments have been drawn', async () => {
await toolbar.editSketch()
// this checks sketch segments have been drawn
await verifyArrowHeadColor(arrowHeadWhite)
})
await test.step('Make typo and check the segments have Disappeared and there is a syntax error', async () => {
await editor.replaceCode(
'line(endAbsolute = [pro',
'badBadBadFn(endAbsolute = [pro'
)
await editor.expectState({
activeLines: [],
diagnostics: ['memoryitemkey`badBadBadFn`isnotdefined'],
highlightedCode: '',
})
await expect(
page.getByText(
"Error in kcl script, sketch cannot be drawn until it's fixed"
)
).toBeVisible()
// this checks sketch segments have failed to be drawn
await verifyArrowHeadColor(backgroundGray)
})
await test.step('', async () => {
await editor.replaceCode(
'badBadBadFn(endAbsolute = [pro',
'line(endAbsolute = [pro'
)
await editor.expectState({
activeLines: [],
diagnostics: [],
highlightedCode: '',
})
// this checks sketch segments have been drawn
await verifyArrowHeadColor(arrowHeadWhite)
})
await page.waitForTimeout(100)
}
)
})

View File

@ -1,9 +1,9 @@
import { spawn } from 'child_process'
import path from 'path'
import type { Models } from '@kittycad/lib'
import { KCL_DEFAULT_LENGTH } from '@src/lib/constants'
import { spawn } from 'child_process'
import fsp from 'fs/promises'
import JSZip from 'jszip'
import path from 'path'
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
@ -13,6 +13,8 @@ import type { Paths } from '@e2e/playwright/test-utils'
import {
doExport,
getUtils,
headerMasks,
networkingMasks,
orRunWhenFullSuiteEnabled,
settingsToToml,
} from '@e2e/playwright/test-utils'
@ -77,11 +79,7 @@ part001 = startSketchOn(-XZ)
)
|> xLine(endAbsolute = totalLen, tag = $seg03)
|> yLine(length = -armThick, tag = $seg01)
|> angledLineThatIntersects({
angle = turns::HALF_TURN,
offset = -armThick,
intersectTag = seg04
}, %)
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg04)
|> angledLine(angle = segAng(seg04, %) + 180, endAbsoluteY = turns::ZERO)
|> angledLine(
angle = -bottomAng,
@ -90,11 +88,7 @@ part001 = startSketchOn(-XZ)
)
|> xLine(length = endAbsolute = segEndX(seg03) + 0)
|> yLine(length = -segLen(seg01, %))
|> angledLineThatIntersects({
angle = turns::HALF_TURN,
offset = -armThick,
intersectTag = seg02
}, %)
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg02)
|> angledLine(angle = segAng(seg02, %) + 180, endAbsoluteY = -baseHeight)
|> xLine(endAbsolute = turns::ZERO)
|> close()
@ -374,7 +368,7 @@ const extrudeDefaultPlane = async (
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await u.openKclCodePanel()
}
@ -462,7 +456,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
const lineEndClick = () =>
@ -491,7 +485,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await endOfTangentClk()
@ -501,7 +495,7 @@ test(
await threePointArcMidPointMv()
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await threePointArcMidPointClk()
await page.waitForTimeout(100)
@ -510,7 +504,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await threePointArcEndPointClk()
@ -530,7 +524,7 @@ test(
await page.waitForTimeout(500)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await arcEndClk()
}
@ -580,7 +574,7 @@ test(
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
}
)
@ -623,7 +617,7 @@ test(
// Ensure the draft rectangle looks the same as it usually does
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn(XZ)profile001 = circle(sketch001, center = [366.89, -62.01], radius = 1)`
@ -696,7 +690,7 @@ test.describe(
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
await u.doAndWaitForImageDiff(
@ -709,7 +703,7 @@ test.describe(
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
})
@ -789,6 +783,7 @@ test.describe(
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
})
// exit sketch
@ -802,6 +797,7 @@ test.describe(
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
})
})
}
@ -863,7 +859,7 @@ part002 = startSketchOn(part001, face = seg01)
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
}
)
@ -902,7 +898,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
}
)
@ -942,7 +938,7 @@ test(
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
}
)
@ -955,11 +951,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
}) => {
const u = await getUtils(page)
const stream = page.getByTestId('stream')
const mask = [
page.locator('#app-header'),
page.locator('#sidebar-top-ribbon'),
page.locator('#sidebar-bottom-ribbon'),
]
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
@ -1012,18 +1003,13 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask,
mask: [...headerMasks(page), ...networkingMasks(page)],
})
})
test('Grid turned off', async ({ page, cmdBar, scene }) => {
const u = await getUtils(page)
const stream = page.getByTestId('stream')
const mask = [
page.locator('#app-header'),
page.locator('#sidebar-top-ribbon'),
page.locator('#sidebar-bottom-ribbon'),
]
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
@ -1038,7 +1024,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask,
mask: [...headerMasks(page), ...networkingMasks(page)],
})
})
@ -1063,11 +1049,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
const u = await getUtils(page)
const stream = page.getByTestId('stream')
const mask = [
page.locator('#app-header'),
page.locator('#sidebar-top-ribbon'),
page.locator('#sidebar-bottom-ribbon'),
]
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
@ -1082,7 +1063,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
await expect(stream).toHaveScreenshot({
maxDiffPixels: 100,
mask,
mask: [...headerMasks(page), ...networkingMasks(page)],
})
})
})
@ -1151,6 +1132,7 @@ test('theme persists', async ({ page, context }) => {
await expect(page, 'expect screenshot to have light theme').toHaveScreenshot({
maxDiffPixels: 100,
mask: networkingMasks(page),
})
})
@ -1166,9 +1148,9 @@ test.describe('code color goober', { tag: '@snapshot' }, () => {
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
sweepSketch = startSketchOn(XY)
@ -1195,7 +1177,7 @@ sweepSketch = startSketchOn(XY)
await expect(page, 'expect small color widget').toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
})
@ -1215,9 +1197,9 @@ sweepSketch = startSketchOn(XY)
sweepPath = startSketchOn(XZ)
|> startProfileAt([0.05, 0.05], %)
|> line(end = [0, 7])
|> tangentialArc({ offset = 90, radius = 5 }, %)
|> tangentialArc(angle = 90, radius = 5)
|> line(end = [-3, 0])
|> tangentialArc({ offset = -90, radius = 5 }, %)
|> tangentialArc(angle = -90, radius = 5)
|> line(end = [0, 7])
sweepSketch = startSketchOn(XY)
@ -1252,7 +1234,7 @@ sweepSketch = startSketchOn(XY)
'expect small color widget to have window open'
).toHaveScreenshot({
maxDiffPixels: 100,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
})
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 63 KiB

View File

@ -1,33 +0,0 @@
{
"original_source_code": "sketch001 = startSketchOn(XZ)\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn('XZ')\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn(XY)\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine(angle = 0, length = 176.4, tag = $rectangleSegmentA001)\n |> angledLine(angle = segAng(rectangleSegmentA001) - 90,\n length = 53.4\n ], tag = $rectangleSegmentB001)\n |> angledLine(angle = segAng(rectangleSegmentA001),\n length = -segLen(rectangleSegmentA001)\n tag = $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
"prompt": "make this neon green please, use #39FF14",
"source_ranges": [
{
"prompt": "The users main selection is the end cap of a general-sweep (that is an extrusion, revolve, sweep or loft).\nThe source range most likely refers to \"startProfileAt\" simply because this is the start of the profile that was swept.\nIf you need to operate on this cap, for example for sketching on the face, you can use the special string END i.e. `startSketchOn(someSweepVariable, END)`\nWhen they made this selection they main have intended this surface directly or meant something more general like the sweep body.\nSee later source ranges for more context.",
"range": {
"start": {
"line": 11,
"column": 5
},
"end": {
"line": 11,
"column": 40
}
}
},
{
"prompt": "This is the sweep's source range from the user's main selection of the end cap.",
"range": {
"start": {
"line": 17,
"column": 13
},
"end": {
"line": 17,
"column": 44
}
}
}
],
"kcl_version": "0.2.48"
}

View File

@ -1,9 +1,9 @@
{
"original_source_code": "sketch001 = startSketchOn(XZ)\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn(XZ)\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn(XY)\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine(angle = 0, 176.4], %, $rectangleSegmentA001)\n |> angledLine([\n segAng(rectangleSegmentA001) - 90,\n 53.4\n ], %, length = $rectangleSegmentB001)\n |> angledLine(\n angle = segAng(rectangleSegmentA001),\n length = -segLen(rectangleSegmentA001),\n tag = $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
"original_source_code": "sketch001 = startSketchOn(XZ)\nprofile001 = startProfileAt([57.81, 250.51], sketch001)\n |> line(end = [121.13, 56.63], tag = $seg02)\n |> line(end = [83.37, -34.61], tag = $seg01)\n |> line(end = [19.66, -116.4])\n |> line(end = [-221.8, -41.69])\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude001 = extrude(profile001, length = 200)\nsketch002 = startSketchOn(XZ)\n |> startProfileAt([-73.64, -42.89], %)\n |> xLine(length = 173.71)\n |> line(end = [-22.12, -94.4])\n |> xLine(length = -156.98)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude002 = extrude(sketch002, length = 50)\nsketch003 = startSketchOn(XY)\n |> startProfileAt([52.92, 157.81], %)\n |> angledLine(angle = 0, length = 176.4, tag = $rectangleSegmentA001)\n |> angledLine(\n angle = segAng(rectangleSegmentA001) - 90,\n length = 53.4,\n tag = $rectangleSegmentB001,\n )\n |> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $rectangleSegmentC001)\n |> line(endAbsolute = [profileStartX(%), profileStartY(%)])\n |> close()\nextrude003 = extrude(sketch003, length = 20)\n",
"prompt": "make this neon green please, use #39FF14",
"source_ranges": [
{
"prompt": "The users main selection is the end cap of a general-sweep (that is an extrusion, revolve, sweep or loft).\nThe source range most likely refers to \"startProfileAt\" simply because this is the start of the profile that was swept.\nIf you need to operate on this cap, for example for sketching on the face, you can use the special string END i.e. `startSketchOn(someSweepVariable, END)`\nWhen they made this selection they main have intended this surface directly or meant something more general like the sweep body.\nSee later source ranges for more context.",
"prompt": "The users main selection is the end cap of a general-sweep (that is an extrusion, revolve, sweep or loft).\nThe source range most likely refers to \"startProfileAt\" simply because this is the start of the profile that was swept.\nIf you need to operate on this cap, for example for sketching on the face, you can use the special string END i.e. `startSketchOn(someSweepVariable, face = END)`\nWhen they made this selection they main have intended this surface directly or meant something more general like the sweep body.\nSee later source ranges for more context.",
"range": {
"start": {
"line": 11,
@ -29,5 +29,5 @@
}
}
],
"kcl_version": "0.2.57"
}
"kcl_version": "0.2.61"
}

View File

@ -93,11 +93,7 @@ part001 = startSketchOn(XZ)
|> angledLine(angle = 30, lengthY = 3 + 0 )
|> angledLine(angle = 22.14 + 0, endAbsoluteX = 12)
|> angledLine(angle = 30, endAbsoluteY = 11.14)
|> angledLineThatIntersects({
angle: 3.14,
intersectTag: a,
offset: 0
}, %)
|> angledLineThatIntersects(angle = 3.14, intersectTag = a, offset = 0)
|> tangentialArc(endAbsolute = [13.14 + 0, 13.14])
|> close()
|> extrude(length = 5 + 7)

View File

@ -126,8 +126,9 @@ test.describe('Test network and connection issues', () => {
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
await expect(
page.locator('.cm-content')
).toHaveText(`sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(length = ${commonPoints.num1})`)
// Expect the network to be up
@ -215,7 +216,8 @@ test.describe('Test network and connection issues', () => {
await page.waitForTimeout(100)
// Ensure we can continue sketching
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect.poll(u.normalisedEditorCode)
await expect
.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([12.34, -12.34], sketch001)
|> xLine(length = 12.34)
@ -225,7 +227,8 @@ profile001 = startProfileAt([12.34, -12.34], sketch001)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect.poll(u.normalisedEditorCode)
await expect
.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn(XZ)
profile001 = startProfileAt([12.34, -12.34], sketch001)
|> xLine(length = 12.34)

View File

@ -1,3 +1,4 @@
import path from 'path'
import * as TOML from '@iarna/toml'
import type { Models } from '@kittycad/lib'
import type { BrowserContext, Locator, Page, TestInfo } from '@playwright/test'
@ -9,7 +10,6 @@ import { reportRejection } from '@src/lib/trap'
import type { DeepPartial } from '@src/lib/types'
import { isArray } from '@src/lib/utils'
import fsp from 'fs/promises'
import path from 'path'
import pixelMatch from 'pixelmatch'
import type { Protocol } from 'playwright-core/types/protocol'
import { PNG } from 'pngjs'
@ -29,6 +29,17 @@ const toNormalizedCode = (text: string) => {
return text.replace(/\s+/g, '')
}
export const headerMasks = (page: Page) => [
page.locator('#app-header'),
page.locator('#sidebar-top-ribbon'),
page.locator('#sidebar-bottom-ribbon'),
]
export const networkingMasks = (page: Page) => [
page.getByTestId('model-state-indicator'),
page.getByTestId('network-toggle'),
]
export type TestColor = [number, number, number]
export const TEST_COLORS: { [key: string]: TestColor } = {
WHITE: [249, 249, 249],

View File

@ -1,6 +1,6 @@
import path from 'node:path'
import { XOR } from '@src/lib/utils'
import * as fsp from 'fs/promises'
import path from 'node:path'
import {
TEST_COLORS,
@ -219,7 +219,9 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
await expect(activeLinesContent[0]).toHaveText(
`|> line(end = [74.36, 130.4], tag = $seg01)`
)
await expect(activeLinesContent[1]).toHaveText(`}, %)`)
await expect(activeLinesContent[1]).toHaveText(
` |> angledLineThatIntersects(angle = -57, offset = ${offset}, intersectTag = seg01)`
)
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)

View File

@ -1,16 +1,20 @@
import { join } from 'path'
import { bracket } from '@e2e/playwright/fixtures/bracket'
import { FILE_EXT } from '@src/lib/constants'
import * as fsp from 'fs/promises'
import { join } from 'path'
import type { CmdBarSerialised } from '@e2e/playwright/fixtures/cmdBarFixture'
import type { ElectronZoo } from '@e2e/playwright/fixtures/fixtureSetup'
import {
executorInputPath,
getUtils,
orRunWhenFullSuiteEnabled,
runningOnWindows,
testsInputPath,
} from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing in-app sample loading', () => {
test.describe('Testing loading external models', () => {
/**
* Note this test implicitly depends on the KCL sample "parametric-bearing-pillow-block",
* its title, and its units settings. https://github.com/KittyCAD/kcl-samples/blob/main/parametric-bearing-pillow-block/main.kcl
@ -39,7 +43,7 @@ test.describe('Testing in-app sample loading', () => {
}
const commandBarButton = page.getByRole('button', { name: 'Commands' })
const samplesCommandOption = page.getByRole('option', {
name: 'Open Sample',
name: 'Load external model',
})
const commandSampleOption = page.getByRole('option', {
name: newSample.title,
@ -83,7 +87,7 @@ test.describe('Testing in-app sample loading', () => {
test(
'Desktop: should create new file by default, optionally overwrite',
{ tag: '@electron' },
async ({ editor, context, page, scene, cmdBar }, testInfo) => {
async ({ editor, context, page, scene, cmdBar, toolbar }) => {
if (runningOnWindows()) {
test.fixme(orRunWhenFullSuiteEnabled())
}
@ -106,20 +110,12 @@ test.describe('Testing in-app sample loading', () => {
title: '100mm Gear Rack',
}
const projectCard = page.getByRole('link', { name: 'bracket' })
const commandBarButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'Open Sample' })
const commandSampleOption = (name: string) =>
page.getByRole('option', {
name,
exact: true,
})
const commandMethodArgButton = page.getByRole('button', {
name: 'Method',
})
const commandMethodOption = page.getByRole('option', {
name: 'Overwrite',
})
const newFileWarning = page.getByText('Create a new file from sample?')
const overwriteWarning = page.getByText(
'Overwrite current file with sample?'
)
@ -129,6 +125,18 @@ test.describe('Testing in-app sample loading', () => {
page.getByRole('listitem').filter({
has: page.getByRole('button', { name }),
})
const defaultLoadCmdBarState: CmdBarSerialised = {
commandName: 'Load external model',
currentArgKey: 'source',
currentArgValue: '',
headerArguments: {
Method: 'newFile',
Sample: '',
Source: '',
},
highlightedHeaderArg: 'source',
stage: 'arguments',
}
await test.step(`Test setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -147,14 +155,12 @@ test.describe('Testing in-app sample loading', () => {
})
await test.step(`Load a KCL sample with the command palette`, async () => {
await commandBarButton.click()
await page.waitForTimeout(1000)
await commandOption.click()
await page.waitForTimeout(1000)
await commandSampleOption(sampleOne.title).click()
await toolbar.loadButton.click()
await cmdBar.expectState(defaultLoadCmdBarState)
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: sampleOne.title }).click()
await expect(overwriteWarning).not.toBeVisible()
await expect(newFileWarning).toBeVisible()
await confirmButton.click()
await cmdBar.progressCmdBar()
await page.waitForTimeout(1000)
})
@ -165,21 +171,15 @@ test.describe('Testing in-app sample loading', () => {
})
await test.step(`Now overwrite the current file`, async () => {
await commandBarButton.click()
await page.waitForTimeout(1000)
await commandOption.click()
await page.waitForTimeout(1000)
await commandSampleOption(sampleTwo.title).click()
await page.waitForTimeout(1000)
await toolbar.loadButton.click()
await cmdBar.expectState(defaultLoadCmdBarState)
await cmdBar.progressCmdBar()
await cmdBar.selectOption({ name: sampleTwo.title }).click()
await commandMethodArgButton.click()
await page.waitForTimeout(1000)
await commandMethodOption.click()
await page.waitForTimeout(1000)
await expect(commandMethodArgButton).toContainText('overwrite')
await expect(newFileWarning).not.toBeVisible()
await expect(overwriteWarning).toBeVisible()
await confirmButton.click()
await page.waitForTimeout(1000)
})
await test.step(`Ensure we overwrote the current file without navigating`, async () => {
@ -200,4 +200,96 @@ test.describe('Testing in-app sample loading', () => {
})
}
)
const externalModelCases = [
{
modelName: 'cylinder.kcl',
deconflictedModelName: 'cylinder-1.kcl',
modelPath: executorInputPath('cylinder.kcl'),
},
{
modelName: 'cube.step',
deconflictedModelName: 'cube-1.step',
modelPath: testsInputPath('cube.step'),
},
]
externalModelCases.map(({ modelName, deconflictedModelName, modelPath }) => {
test(
`Load external models from local drive - ${modelName}`,
{ tag: ['@electron'] },
async ({ page, homePage, scene, toolbar, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
}
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.goToModelingScene()
await scene.settled(cmdBar)
const modelFileContent = await fsp.readFile(modelPath, 'utf-8')
const { editorTextMatches } = await getUtils(page, test)
async function loadExternalFileThroughCommandBar(tronApp: ElectronZoo) {
await toolbar.loadButton.click()
await cmdBar.expectState({
commandName: 'Load external model',
currentArgKey: 'source',
currentArgValue: '',
headerArguments: {
Method: 'newFile',
Sample: '',
Source: '',
},
highlightedHeaderArg: 'source',
stage: 'arguments',
})
await cmdBar.selectOption({ name: 'Local Drive' }).click()
// Mock the file picker selection
const handleFile = tronApp.electron.evaluate(
async ({ dialog }, filePaths) => {
dialog.showOpenDialog = () =>
Promise.resolve({ canceled: false, filePaths })
},
[modelPath]
)
await page.getByTestId('cmd-bar-arg-file-button').click()
await handleFile
await cmdBar.progressCmdBar()
await cmdBar.expectState({
commandName: 'Load external model',
headerArguments: {
Source: 'local',
Path: modelName,
},
stage: 'review',
})
await cmdBar.progressCmdBar()
}
await test.step('Load the external model from local drive', async () => {
await loadExternalFileThroughCommandBar(tronApp)
// TODO: I think the files pane should auto open?
await toolbar.openPane('files')
await toolbar.expectFileTreeState([modelName, 'main.kcl'])
if (modelName.endsWith('.kcl')) {
await editorTextMatches(modelFileContent)
}
})
await test.step('Load the same external model, except deconflicted name', async () => {
await loadExternalFileThroughCommandBar(tronApp)
await toolbar.openPane('files')
await toolbar.expectFileTreeState([
deconflictedModelName,
modelName,
'main.kcl',
])
if (modelName.endsWith('.kcl')) {
await editorTextMatches(modelFileContent)
}
})
}
)
})
})

View File

@ -224,11 +224,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|> angledLine(angle = -91, lengthY = 19 + 0)
|> angledLine(angle = 3 + 0, endAbsoluteX = 5 + 26)
|> angledLine(angle = 89, endAbsoluteY = 20 + 9.14 + 0)
|> angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)
|> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)
|> tangentialArc(endAbsolute = [5 + 3.14 + 13, 20 + 3.14])
`
)
@ -472,11 +468,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|> angledLine(angle = -91, lengthY = 19 + 0)
|> angledLine(angle = 3 + 0, endAbsoluteX = 26)
|> angledLine(angle = 89, endAbsoluteY = 9.14 + 0)
|> angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)
|> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)
|> tangentialArc(endAbsolute = [3.14 + 13, 3.14])
`
)
@ -597,11 +589,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|> angledLine(angle = -91, lengthY = 19 + 0)
|> angledLine(angle = 3 + 0, endAbsoluteX = 26)
|> angledLine(angle = 89, endAbsoluteY = 9.14 + 0)
|> angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)
|> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)
|> tangentialArc(endAbsolute = [3.14 + 13, 1.14])
`
)
@ -691,21 +679,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
y: angledLineThatIntersects.y,
},
constraintType: 'angle',
expectBeforeUnconstrained: `angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)`,
expectAfterUnconstrained: `angledLineThatIntersects({
angle = angle003,
intersectTag = a,
offset = 9
}, %)`,
expectFinal: `angledLineThatIntersects({
angle = -176,
offset = 9,
intersectTag = a
}, %)`,
expectBeforeUnconstrained: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`,
expectAfterUnconstrained: `angledLineThatIntersects(angle = angle003, intersectTag = a,offset = 9)`,
expectFinal: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`,
ang: ang + 180,
locator: '[data-overlay-toolbar-index="11"]',
})
@ -716,21 +692,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
y: angledLineThatIntersects.y,
},
constraintType: 'intersectionOffset',
expectBeforeUnconstrained: `angledLineThatIntersects({
angle = -176,
offset = 9,
intersectTag = a
}, %)`,
expectAfterUnconstrained: `angledLineThatIntersects({
angle = -176,
offset = perpDist001,
intersectTag = a
}, %)`,
expectFinal: `angledLineThatIntersects({
angle = -176,
offset = 9,
intersectTag = a
}, %)`,
expectBeforeUnconstrained: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`,
expectAfterUnconstrained: `angledLineThatIntersects(angle = -176, offset = perpDist001, intersectTag = a)`,
expectFinal: `angledLineThatIntersects(angle = -176, offset = 9, intersectTag = a)`,
ang: ang + 180,
locator: '[data-overlay-toolbar-index="11"]',
})
@ -753,11 +717,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|> angledLine(angle = -91, lengthY = 19 + 0)
|> angledLine(angle = 3 + 0, endAbsoluteX = 26)
|> angledLine(angle = 89, endAbsoluteY = 9.14 + 0)
|> angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)
|> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)
|> tangentialArc(endAbsolute = [3.14 + 13, -3.14])
`
)
@ -1083,11 +1043,7 @@ part001 = startSketchOn(XZ)
|> angledLine(angle = -91, lengthY = 19 + 0)
|> angledLine(angle = 3 + 0, endAbsoluteX = 26)
|> angledLine(angle = 89, endAbsoluteY = 9.14 + 0)
|> angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)
|> angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)
|> tangentialArc(endAbsolute = [3.14 + 13, 1.14])
|> arcTo({
interior = [16.25, 5.12],
@ -1169,11 +1125,7 @@ part001 = startSketchOn(XZ)
ang = await u.getAngle('[data-overlay-index="11"]')
await deleteSegmentSequence({
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
codeToBeDeleted: `angledLineThatIntersects({
angle = 4.14,
intersectTag = a,
offset = 9
}, %)`,
codeToBeDeleted: `angledLineThatIntersects(angle = 4.14, intersectTag = a, offset = 9)`,
stdLibFnName: 'angledLineThatIntersects',
ang: ang + 180,
steps: 7,

View File

@ -78,22 +78,25 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
await expect(
page.locator('.cm-content')
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(length = ${commonPoints.num1})`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
await expect(
page.locator('.cm-content')
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
|> yLine(length = ${commonPoints.num1 + 0.01})`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
await expect(
page.locator('.cm-content')
).toHaveText(`@settings(defaultLengthUnit = in)sketch001 = startSketchOn(XZ)profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(length = ${commonPoints.num1})
@ -524,11 +527,7 @@ part001 = startSketchOn(XZ)
|> angledLine(angle = 30, lengthY = 3 + 0)
|> angledLine(angle = 22.14 + 0, endAbsoluteX = 12)
|> angledLine(angle = 30, endAbsoluteY = 11.14)
|> angledLineThatIntersects({
angle = 3.14,
intersectTag = a,
offset = 0
}, %)
|> angledLineThatIntersects(angle = 3.14, intersectTag = a, offset = 0)
|> tangentialArc(endAbsolute = [13.14 + 0, 13.14])
|> close()
|> extrude(length = 5 + 7)
@ -660,8 +659,8 @@ part001 = startSketchOn(XZ)
await checkCodeAtHoverPosition(
'flatExtrusionFace',
flatExtrusionFace,
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)extrude(length=5+7)`,
'}, %)'
`angledLineThatIntersects(angle=3.14,intersectTag=a,offset=0)extrude(length=5+7)`,
'angledLineThatIntersects(angle = 3.14, intersectTag = a, offset = 0)'
)
await checkCodeAtHoverPosition(
@ -724,8 +723,8 @@ part001 = startSketchOn(XZ)
await checkCodeAtHoverPosition(
'straightSegmentAdjacentEdge',
straightSegmentAdjacentEdge,
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)`,
'}, %)'
`angledLineThatIntersects(angle=3.14,intersectTag=a,offset=0)`,
'angledLineThatIntersects(angle = 3.14, intersectTag = a, offset = 0)'
)
await page.waitForTimeout(200)

View File

@ -1,3 +1,4 @@
import { join } from 'path'
import {
PROJECT_SETTINGS_FILE_NAME,
SETTINGS_FILE_NAME,
@ -5,7 +6,6 @@ import {
import type { SettingsLevel } from '@src/lib/settings/settingsTypes'
import type { DeepPartial } from '@src/lib/types'
import * as fsp from 'fs/promises'
import { join } from 'path'
import type { Settings } from '@rust/kcl-lib/bindings/Settings'
@ -20,6 +20,7 @@ import {
createProject,
executorInputPath,
getUtils,
networkingMasks,
orRunWhenFullSuiteEnabled,
tomlToSettings,
} from '@e2e/playwright/test-utils'
@ -1036,7 +1037,7 @@ fn cube`
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
}
)
@ -1053,7 +1054,7 @@ fn cube`
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
mask: networkingMasks(page),
}
)
})

View File

@ -1,6 +1,6 @@
import type { Page } from '@playwright/test'
import fs from 'fs'
import { join } from 'path'
import type { Page } from '@playwright/test'
import {
createProject,

View File

@ -66,11 +66,7 @@ part001 = startSketchOn(-XZ)
)
|> xLine(endAbsolute = totalLen, tag = $seg03)
|> yLine(length = -armThick, tag = $seg01)
|> angledLineThatIntersects({
angle = turns::HALF_TURN,
offset = -armThick,
intersectTag = seg04
}, %)
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg04)
|> angledLine(angle = segAng(seg04) + 180, endAbsoluteY = turns::ZERO)
|> angledLine(
angle = -bottomAng,
@ -79,11 +75,7 @@ part001 = startSketchOn(-XZ)
)
|> xLine(endAbsolute = segEndX(seg03) + 0)
|> yLine(length = -segLen(seg01))
|> angledLineThatIntersects({
angle = turns::HALF_TURN,
offset = -armThick,
intersectTag = seg02
}, %)
|> angledLineThatIntersects(angle = turns::HALF_TURN, offset = -armThick, intersectTag = seg02)
|> angledLine(angle = segAng(seg02) + 180, endAbsoluteY = -baseHeight)
|> xLine(endAbsolute = turns::ZERO)
|> close()

View File

@ -1,8 +1,8 @@
import path from 'path'
import { FusesPlugin } from '@electron-forge/plugin-fuses'
import { VitePlugin } from '@electron-forge/plugin-vite'
import type { ForgeConfig } from '@electron-forge/shared-types'
import { FuseV1Options, FuseVersion } from '@electron/fuses'
import path from 'path'
const rootDir = process.cwd()

4
interface.d.ts vendored
View File

@ -1,10 +1,10 @@
import { MachinesListing } from 'components/MachineManagerProvider'
import 'electron'
import fs from 'node:fs/promises'
import path from 'path'
import { dialog, shell } from 'electron'
import type { WebContentSendPayload } from 'menu/channels'
import { ZooLabel } from 'menu/roles'
import fs from 'node:fs/promises'
import path from 'path'
// Extend the interface with additional custom properties
declare module 'electron' {

243
package-lock.json generated
View File

@ -76,6 +76,7 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.26.9",
"@biomejs/biome": "^1.9.4",
"@electron-forge/cli": "^7.8.0",
"@electron-forge/plugin-fuses": "^7.8.0",
"@electron-forge/plugin-vite": "^7.8.0",
@ -87,7 +88,6 @@
"@playwright/test": "^1.51.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/isomorphic-fetch": "^0.0.39",
@ -131,8 +131,6 @@
"pngjs": "^7.0.0",
"postcss": "^8.4.43",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.4.17",
"ts-node": "^10.0.0",
@ -1926,6 +1924,170 @@
"node": ">=6.9.0"
}
},
"node_modules/@biomejs/biome": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz",
"integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==",
"dev": true,
"hasInstallScript": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "1.9.4",
"@biomejs/cli-darwin-x64": "1.9.4",
"@biomejs/cli-linux-arm64": "1.9.4",
"@biomejs/cli-linux-arm64-musl": "1.9.4",
"@biomejs/cli-linux-x64": "1.9.4",
"@biomejs/cli-linux-x64-musl": "1.9.4",
"@biomejs/cli-win32-arm64": "1.9.4",
"@biomejs/cli-win32-x64": "1.9.4"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz",
"integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz",
"integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz",
"integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz",
"integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz",
"integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz",
"integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz",
"integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "1.9.4",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz",
"integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@codemirror/autocomplete": {
"version": "6.18.6",
"resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.6.tgz",
@ -5038,41 +5200,6 @@
"node": ">= 10"
}
},
"node_modules/@trivago/prettier-plugin-sort-imports": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-5.2.2.tgz",
"integrity": "sha512-fYDQA9e6yTNmA13TLVSA+WMQRc5Bn/c0EUBditUHNfMMxN7M82c38b1kEggVE3pLpZ0FwkwJkUEKMiOi52JXFA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@babel/generator": "^7.26.5",
"@babel/parser": "^7.26.7",
"@babel/traverse": "^7.26.7",
"@babel/types": "^7.26.7",
"javascript-natural-sort": "^0.7.1",
"lodash": "^4.17.21"
},
"engines": {
"node": ">18.12"
},
"peerDependencies": {
"@vue/compiler-sfc": "3.x",
"prettier": "2.x - 3.x",
"prettier-plugin-svelte": "3.x",
"svelte": "4.x || 5.x"
},
"peerDependenciesMeta": {
"@vue/compiler-sfc": {
"optional": true
},
"prettier-plugin-svelte": {
"optional": true
},
"svelte": {
"optional": true
}
}
},
"node_modules/@ts-stack/markdown": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@ts-stack/markdown/-/markdown-1.5.0.tgz",
@ -13349,13 +13476,6 @@
"node": ">=10"
}
},
"node_modules/javascript-natural-sort": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz",
"integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==",
"dev": true,
"license": "MIT"
},
"node_modules/jest-diff": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
@ -16552,39 +16672,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/prettier": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prettier-plugin-organize-imports": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz",
"integrity": "sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==",
"dev": true,
"license": "MIT",
"peerDependencies": {
"prettier": ">=2.0",
"typescript": ">=2.9",
"vue-tsc": "^2.1.0"
},
"peerDependenciesMeta": {
"vue-tsc": {
"optional": true
}
}
},
"node_modules/pretty-format": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",

View File

@ -96,9 +96,10 @@
"simpleserver:stop": "kill-port 3000",
"tsc": "tsc",
"playwright": "playwright",
"fmt": "prettier --write .eslintrc.json ./src *.ts *.json *.js ./e2e ./packages ./rust/kcl-language-server ./rust/kcl-lib/bindings ./rust/kcl-wasm-lib/pkg",
"fmt:generated": "prettier --write .eslintrc.json *.ts *.json *.js ./rust/kcl-lib/bindings ./rust/kcl-wasm-lib/pkg",
"fmt-check": "prettier --check .eslintrc.json ./src *.ts *.json *.js ./e2e ./packages ./rust/kcl-language-server",
"fmt": "biome format --write .eslintrc.json ./src *.ts *.js ./e2e ./packages ./rust/kcl-language-server ./rust/kcl-lib/bindings ./rust/kcl-wasm-lib/pkg",
"fmt:check": "biome check --write --organize-imports-enabled=true --formatter-enabled=false --linter-enabled=false --files-ignore-unknown=true ./src *.ts *.js ./e2e ./packages ./rust/kcl-language-server ./rust/kcl-lib/bindings ./rust/kcl-wasm-lib/pkg",
"organize-imports": "biome check --write --organize-imports-enabled=true --formatter-enabled=false --linter-enabled=false ./src ./e2e ./packages --files-ignore-unknown=true",
"check": "biome check ./src ./e2e ./packages/codemirror-lsp-client/src ./rust/kcl-language-server/client/src",
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
"fetch:wasm:windows": "powershell -ExecutionPolicy Bypass -File ./scripts/get-latest-wasm-bundle.ps1",
"fetch:samples": "rm -rf public/kcl-samples* && curl -L -o public/kcl-samples.zip https://github.com/KittyCAD/kcl-samples/archive/refs/heads/achalmers/kw-args-xylineto.zip && unzip -o public/kcl-samples.zip -d public && mv public/kcl-samples-* public/kcl-samples",
@ -131,7 +132,7 @@
"tronb:package:prod": "npm run tronb:vite:prod && electron-builder --config electron-builder.yml --publish always",
"test-setup": "npm install && npm run build:wasm",
"test": "vitest --mode development",
"test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --update-snapshots --grep=@snapshot --trace=on --shard=1/1",
"test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --grep=@snapshot --trace=on --shard=1/1",
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
@ -146,32 +147,8 @@
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000",
"test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
},
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": false,
"singleQuote": true,
"importOrder": [
"<THIRD_PARTY_MODULES>",
"^@rust/(.*)$",
"^@public/(.*)$",
"^@e2e/(.*)$",
"^@src/(.*)$",
"^[./]"
],
"importOrderSeparation": true,
"importOrderSortSpecifiers": true,
"plugins": [
"@trivago/prettier-plugin-sort-imports",
"prettier-plugin-organize-imports"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"production": [">0.2%", "not dead", "not op_mini all"],
"development": [
"last 1 chrome version",
"last 1 firefox version",
@ -181,6 +158,7 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@babel/preset-env": "^7.26.9",
"@biomejs/biome": "^1.9.4",
"@electron-forge/cli": "^7.8.0",
"@electron-forge/plugin-fuses": "^7.8.0",
"@electron-forge/plugin-vite": "^7.8.0",
@ -192,7 +170,6 @@
"@playwright/test": "^1.51.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^15.0.2",
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
"@types/diff": "^7.0.2",
"@types/electron": "^1.6.10",
"@types/isomorphic-fetch": "^0.0.39",
@ -236,8 +213,6 @@
"pngjs": "^7.0.0",
"postcss": "^8.4.43",
"postinstall-postinstall": "^2.1.0",
"prettier": "^3.5.3",
"prettier-plugin-organize-imports": "^4.1.0",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.4.17",
"ts-node": "^10.0.0",

View File

@ -32,7 +32,5 @@
"vite-tsconfig-paths": "^5.1.4",
"vitest": "^3.1.1"
},
"files": [
"dist/"
]
"files": ["dist/"]
}

View File

@ -1,6 +1,6 @@
import { fileTests } from '@lezer/generator/dist/test'
import * as fs from 'fs'
import * as path from 'path'
import { fileTests } from '@lezer/generator/dist/test'
import { KclLanguage } from '../src/index'

View File

@ -1,5 +1,5 @@
import { defineConfig, devices } from '@playwright/test'
import os from 'os'
import { defineConfig, devices } from '@playwright/test'
const platform = os.platform() // 'linux' (Ubuntu), 'darwin' (macOS), 'win32' (Windows)

View File

@ -1,5 +1,5 @@
import { defineConfig, devices } from '@playwright/test'
import os from 'os'
import { defineConfig, devices } from '@playwright/test'
const platform = os.platform() // 'linux' (Ubuntu), 'darwin' (macOS), 'win32' (Windows)
@ -20,7 +20,7 @@ if (process.env.E2E_WORKERS) {
case 'darwin':
case 'win32':
default:
workers = '25%' // Lower concurrency for heavier Electron processes
workers = '40%' // Lower concurrency for heavier Electron processes
break
}
}

View File

@ -153,11 +153,7 @@ fn z(origin, scale, depth) {
|> yLine(length = segLen(seg3))
|> xLine(endAbsolute = 0 + origin[0])
|> yLine(length = -0.225 * scale)
|> angledLineThatIntersects({
angle = 0,
intersectTag = seg2,
offset = 0
}, %)
|> angledLineThatIntersects(angle = 0, intersectTag = seg2, offset = 0)
|> close()
|> extrude(length = -depth)
|> appearance(color = baseColor)

View File

@ -21,11 +21,7 @@ export fn zLogo(surface, origin, scale) {
|> yLine(length = segLen(seg3))
|> xLine(endAbsolute = 0 + origin[0])
|> yLine(length = -0.225 * scale)
|> angledLineThatIntersects({
angle = 0,
intersectTag = seg2,
offset = 0
}, %)
|> angledLineThatIntersects(angle = 0, intersectTag = seg2, offset = 0)
|> close()
return zSketch
}

60
rust/Cargo.lock generated
View File

@ -133,9 +133,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.97"
version = "1.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487"
dependencies = [
"backtrace",
]
@ -482,9 +482,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.32"
version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83"
checksum = "2df961d8c8a0d08aa9945718ccf584145eee3f3aa06cddbeac12933781102e04"
dependencies = [
"clap_builder",
"clap_derive",
@ -492,9 +492,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.32"
version = "4.5.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8"
checksum = "132dbda40fb6753878316a489d5a1242a8ef2f0d9e47ba01c951ea8aa7d013a5"
dependencies = [
"anstream",
"anstyle",
@ -995,9 +995,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.1.0"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc"
checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece"
dependencies = [
"crc32fast",
"miniz_oxide",
@ -1615,9 +1615,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.25.5"
version = "0.25.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b"
checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a"
dependencies = [
"bytemuck",
"byteorder-lite",
@ -1780,7 +1780,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"anyhow",
"clap",
@ -1791,7 +1791,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"Inflector",
"anyhow",
@ -1810,7 +1810,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"proc-macro2",
"quote",
@ -1819,7 +1819,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.61"
version = "0.2.62"
dependencies = [
"anyhow",
"clap",
@ -1840,7 +1840,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"anyhow",
"clap",
@ -1860,7 +1860,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.61"
version = "0.2.62"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1928,7 +1928,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.61"
version = "0.3.62"
dependencies = [
"anyhow",
"kcl-lib",
@ -1943,7 +1943,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -1956,7 +1956,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"anyhow",
"async-trait",
@ -1970,7 +1970,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.61"
version = "0.1.62"
dependencies = [
"bson",
"console_error_panic_hook",
@ -2033,9 +2033,9 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.2.113"
version = "0.2.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa1c927569925425a1b03711617c384a30cb7554394e8a6a01266910b22421de"
checksum = "681ce29b9da92aa6f8bfc003ccb79a9f1a84368e064d68684327b3181dfe16ec"
dependencies = [
"anyhow",
"chrono",
@ -2156,9 +2156,9 @@ checksum = "9374ef4228402d4b7e403e5838cb880d9ee663314b0a900d5a6aabf0c213552e"
[[package]]
name = "log"
version = "0.4.26"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
dependencies = [
"serde",
]
@ -2414,9 +2414,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.21.1"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "oncemutex"
@ -3890,9 +3890,9 @@ dependencies = [
[[package]]
name = "time"
version = "0.3.40"
version = "0.3.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d9c75b47bdff86fa3334a3db91356b8d7d86a9b839dab7d0bdc5c3d3a077618"
checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40"
dependencies = [
"deranged",
"itoa",
@ -3911,9 +3911,9 @@ checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c"
[[package]]
name = "time-macros"
version = "0.2.21"
version = "0.2.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29aa485584182073ed57fd5004aa09c371f021325014694e432313345865fd04"
checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49"
dependencies = [
"num-conv",
"time-core",

View File

@ -31,12 +31,12 @@ debug = "line-tables-only"
async-trait = "0.1.88"
anyhow = { version = "1" }
bson = { version = "2.13.0", features = ["uuid-1", "chrono"] }
clap = { version = "4.5.31", features = ["derive"] }
clap = { version = "4.5.36", features = ["derive"] }
dashmap = { version = "6.1.0" }
http = "1"
indexmap = "2.7.0"
kittycad = { version = "0.3.36", default-features = false, features = ["js", "requests"] }
kittycad-modeling-cmds = { version = "0.2.113", features = ["ts-rs", "websocket"] }
kittycad-modeling-cmds = { version = "0.2.114", features = ["ts-rs", "websocket"] }
lazy_static = "1.5.0"
miette = "7.5.0"
pyo3 = { version = "0.24.1" }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-bumper"
version = "0.1.61"
version = "0.1.62"
edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros"
version = "0.1.61"
version = "0.1.62"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -14,7 +14,7 @@ bench = false
[dependencies]
Inflector = "0.11.4"
convert_case = "0.8.0"
once_cell = "1.20.3"
once_cell = "1.21.3"
proc-macro2 = "1"
quote = "1"
regex = "1.11"

View File

@ -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.61"
version = "0.1.62"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package]
name = "kcl-language-server-release"
version = "0.1.61"
version = "0.1.62"
edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false
@ -14,14 +14,14 @@ bench = false
[dependencies]
anyhow = { workspace = true }
clap = { workspace = true, features = ["cargo", "derive", "env", "unicode"] }
flate2 = "1.1.0"
flate2 = "1.1.1"
lazy_static = { workspace = true }
log = { version = "0.4.26", features = ["serde"] }
log = { version = "0.4.27", features = ["serde"] }
slog = { workspace = true }
slog-async = { workspace = true }
slog-json = { workspace = true }
slog-term = { workspace = true }
time = "0.3.40"
time = "0.3.41"
tokio = { workspace = true, features = ["full"] }
tracing-subscriber = { workspace = true }
xshell = "0.2.6"

View File

@ -2,7 +2,7 @@
name = "kcl-language-server"
description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.61"
version = "0.2.62"
edition = "2021"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -23,7 +23,7 @@ kcl-lib = { path = "../kcl-lib", default-features = false, features = [
] }
kittycad = { workspace = true }
lazy_static = { workspace = true }
log = { version = "0.4.26", features = ["serde"] }
log = { version = "0.4.27", features = ["serde"] }
slog = { workspace = true }
slog-async = { workspace = true }
slog-json = { workspace = true }

View File

@ -1,5 +1,5 @@
import { runTests } from '@vscode/test-electron'
import * as path from 'path'
import { runTests } from '@vscode/test-electron'
async function main() {
try {

View File

@ -17,9 +17,7 @@
"cad",
"manufacturing"
],
"categories": [
"Programming Languages"
],
"categories": ["Programming Languages"],
"repository": {
"url": "https://github.com/kittycad/modeling-app.git",
"type": "git"
@ -28,17 +26,13 @@
"vscode": "^1.97.0"
},
"enabledApiProposals": [],
"activationEvents": [
"onLanguage:kcl"
],
"activationEvents": ["onLanguage:kcl"],
"main": "./dist/main.js",
"contributes": {
"languages": [
{
"id": "kcl",
"extensions": [
".kcl"
]
"extensions": [".kcl"]
}
],
"configuration": {
@ -46,10 +40,7 @@
"title": "kcl-language-server",
"properties": {
"kcl-language-server.server.path": {
"type": [
"null",
"string"
],
"type": ["null", "string"],
"scope": "machine-overridable",
"default": null,
"markdownDescription": "Path to kcl-language-server executable (points to bundled binary by default)."
@ -57,16 +48,8 @@
"kcl-language-server.trace.server": {
"type": "string",
"scope": "window",
"enum": [
"off",
"messages",
"verbose"
],
"enumDescriptions": [
"No traces",
"Error only",
"Full log"
],
"enum": ["off", "messages", "verbose"],
"enumDescriptions": ["No traces", "Error only", "Full log"],
"default": "off",
"description": "Trace requests to the kcl-language-server (this is usually overly verbose and not recommended for regular users)."
},

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.61"
version = "0.2.62"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -26,7 +26,7 @@ async-trait = { workspace = true }
base64 = "0.22.1"
bson = { workspace = true }
chrono = "0.4.38"
clap = { version = "4.5.27", default-features = false, optional = true, features = [
clap = { version = "4.5.36", default-features = false, optional = true, features = [
"std",
"derive",
] }
@ -39,7 +39,7 @@ futures = { version = "0.3.31" }
git_rev = "0.1.0"
gltf-json = "1.4.1"
http = { workspace = true }
image = { version = "0.25.5", default-features = false, features = ["png"] }
image = { version = "0.25.6", default-features = false, features = ["png"] }
indexmap = { workspace = true, features = ["serde"] }
itertools = "0.13.0"
kcl-derive-docs = { version = "0.1", path = "../kcl-derive-docs" }
@ -121,7 +121,7 @@ base64 = "0.22.1"
criterion = { version = "0.5.1", features = ["async_tokio"] }
expectorate = "1.1.0"
handlebars = "6.3.2"
image = { version = "0.25.5", default-features = false, features = ["png"] }
image = { version = "0.25.6", default-features = false, features = ["png"] }
insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
kcl-directory-test-macro = { version = "0.1", path = "../kcl-directory-test-macro" }
miette = { version = "7.5.0", features = ["fancy"] }

View File

@ -152,11 +152,11 @@ const extrude005l = extrude(sketch005l, length = 1)
const sketch006l = startSketchOn(plane001)
|> startProfileAt([1, 1], %)
|> angledLineThatIntersects({
angle: 70,
intersectTag: lineToIntersect4,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = 70,
intersectTag = lineToIntersect4,
offset = 0,
)
|> angledLine(angle = -70, length = 1.414 )
|> angledLine(angle = 70 + 180, endAbsoluteY = 2 - 1)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -168,11 +168,11 @@ const sketch007l = startSketchOn(plane001)
serverDepth - 1.2,
railHeight * 1.75 + 1
], %)
|> angledLineThatIntersects({
angle: 70,
intersectTag: lineToIntersect5,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = 70,
intersectTag = lineToIntersect5,
offset = 0,
)
|> angledLine(angle = -70, length = 1.414 )
|> angledLine(angle = 70 + 180, endAbsoluteY = railHeight * 1.75 + 1)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -243,11 +243,12 @@ const sketch006w = startSketchOn(plane002)
], %)
|> angledLine(angle = -23 + 90, endAbsoluteX = depth - 1)
|> yLine(length = 2.56)
|> angledLineThatIntersects({
angle: -23 + 90 + 180,
intersectTag: lineToIntersect,
offset: 0
}, %, $lineToIntersect2)
|> angledLineThatIntersects(
angle = -23 + 90 + 180,
intersectTag = lineToIntersect,
offset = 0,
tag = $lineToIntersect2,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude006w = extrude(sketch006w, length = 1)
@ -256,28 +257,28 @@ const sketch007w = startSketchOn(plane002)
|> startProfileAt([depth - 1, 60.65 + 1.75 / 2], %)
|> angledLine(angle = -23 + 180, length = 34.93 , tag = $lineToIntersect3)
|> angledLine(angle = 23 - 90, length = 1.414 )
|> angledLineThatIntersects({
angle: -23 + 180,
intersectTag: lineToIntersect2,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 + 180,
intersectTag = lineToIntersect2,
offset = 0,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude007w = extrude(sketch007w, length = 1)
const sketch008w = startSketchOn(plane002)
|> startProfileAt([1, 41.7 + 1.75 / 2], %)
|> angledLineThatIntersects({
angle: -23 + 90,
intersectTag: lineToIntersect3,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 + 90,
intersectTag = lineToIntersect3,
offset = 0,
)
|> angledLine(angle = -23 - 45, length = 1.414 )
|> angledLineThatIntersects({
angle: -23 - 90,
intersectTag: lineToIntersect,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 - 90,
intersectTag = lineToIntersect,
offset = 0,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude008w = extrude(sketch008w, length = 1)

View File

@ -150,11 +150,7 @@ const extrude005l = extrude(sketch005l, length = 1)
const sketch006l = startSketchOn(plane001)
|> startProfileAt([1, 1], %)
|> angledLineThatIntersects({
angle: 70,
intersectTag: lineToIntersect4,
offset: 0
}, %)
|> angledLineThatIntersects(angle = 70, intersectTag = lineToIntersect4, offset = 0)
|> angledLine(angle = -70, length = 1.414 )
|> angledLine(angle = 70 + 180, endAbsoluteY = 2 - 1)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -166,11 +162,7 @@ const sketch007l = startSketchOn(plane001)
serverDepth - 1.2,
railHeight * 1.75 + 1
], %)
|> angledLineThatIntersects({
angle: 70,
intersectTag: lineToIntersect5,
offset: 0
}, %)
|> angledLineThatIntersects(angle = 70, intersectTag = lineToIntersect5, offset = 0)
|> angledLine(angle = -70, length = 1.414 )
|> angledLine(angle = 70 + 180, endAbsoluteY = railHeight * 1.75 + 1)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
@ -241,11 +233,12 @@ const sketch006w = startSketchOn(plane002)
], %)
|> angledLine(angle = -23 + 90, endAbsoluteX = depth - 1)
|> yLine(length = 2.56)
|> angledLineThatIntersects({
angle: -23 + 90 + 180,
intersectTag: lineToIntersect,
offset: 0
}, %, $lineToIntersect2)
|> angledLineThatIntersects(
angle = -23 + 90 + 180,
intersectTag = lineToIntersect,
offset = 0,
tag = $lineToIntersect2,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude006w = extrude(sketch006w, length = 1)
@ -254,28 +247,28 @@ const sketch007w = startSketchOn(plane002)
|> startProfileAt([depth - 1, 60.65 + 1.75 / 2], %)
|> angledLine(angle = -23 + 180, length = 34.93 , tag = $lineToIntersect3)
|> angledLine(angle = 23 - 90, length = 1.414 )
|> angledLineThatIntersects({
angle: -23 + 180,
intersectTag: lineToIntersect2,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 + 180,
intersectTag = lineToIntersect2,
offset = 0,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude007w = extrude(sketch007w, length = 1)
const sketch008w = startSketchOn(plane002)
|> startProfileAt([1, 41.7 + 1.75 / 2], %)
|> angledLineThatIntersects({
angle: -23 + 90,
intersectTag: lineToIntersect3,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 + 90,
intersectTag = lineToIntersect3,
offset = 0,
)
|> angledLine(angle = -23 - 45, length = 1.414 )
|> angledLineThatIntersects({
angle: -23 - 90,
intersectTag: lineToIntersect,
offset: 0
}, %)
|> angledLineThatIntersects(
angle = -23 - 90,
intersectTag = lineToIntersect,
offset = 0,
)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
const extrude008w = extrude(sketch008w, length = 1)

View File

@ -644,7 +644,7 @@ impl GetTangentialInfoFromPathsResult {
pub(crate) fn tan_previous_point(&self, last_arc_end: [f64; 2]) -> [f64; 2] {
match self {
GetTangentialInfoFromPathsResult::PreviousPoint(p) => *p,
GetTangentialInfoFromPathsResult::Arc { center, ccw, .. } => {
GetTangentialInfoFromPathsResult::Arc { center, ccw } => {
crate::std::utils::get_tangent_point_from_previous_arc(*center, *ccw, last_arc_end)
}
// The circle always starts at 0 degrees, so a suitable tangent
@ -1231,12 +1231,9 @@ impl Path {
},
Path::ArcThreePoint { p1, p2, p3, .. } => {
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1);
let center_point = [circle_center.center[0], circle_center.center[1]];
GetTangentialInfoFromPathsResult::Circle {
center: center_point,
ccw: true,
radius,
GetTangentialInfoFromPathsResult::Arc {
center: circle_center.center,
ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
}
}
Path::Circle {
@ -1252,6 +1249,7 @@ impl Path {
let center_point = [circle_center.center[0], circle_center.center[1]];
GetTangentialInfoFromPathsResult::Circle {
center: center_point,
// Note: a circle is always ccw regardless of the order of points
ccw: true,
radius,
}

View File

@ -4497,8 +4497,8 @@ e
/// )
/// |> yLine(endAbsolute = 0)
/// |> close(%)
///
/// example = extrude(5, exampleSketch)
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn cos(num: number(rad)): number(_) {}"#;

View File

@ -2482,6 +2482,7 @@ mod intersect_cubes {
super::execute(TEST_NAME, true).await
}
}
mod pattern_into_union {
const TEST_NAME: &str = "pattern_into_union";
@ -2524,3 +2525,24 @@ mod subtract_doesnt_need_brackets {
super::execute(TEST_NAME, true).await
}
}
mod tangent_to_3_point_arc {
const TEST_NAME: &str = "tangent_to_3_point_arc";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -991,20 +991,6 @@ macro_rules! let_field_of {
};
}
impl<'a> FromKclValue<'a> for super::sketch::AngledLineThatIntersectsData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, angle);
let_field_of!(obj, intersect_tag "intersectTag");
let_field_of!(obj, offset?);
Some(Self {
angle,
intersect_tag,
offset,
})
}
}
impl<'a> FromKclValue<'a> for super::shapes::PolygonData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;

View File

@ -617,12 +617,12 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
unlabeled_first = true,
args = {
sketch = { docs = "Which sketch should this path be added to?"},
angle = { docs = "Which angle should the line be drawn at?" },
length = { docs = "Draw the line this distance along the given angle. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
length_x = { docs = "Draw the line this distance along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
length_y = { docs = "Draw the line this distance along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
end_absolute_x = { docs = "Draw the line along the given angle until it reaches this point along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
end_absolute_y = { docs = "Draw the line along the given angle until it reaches this point along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
angle = { docs = "Which angle should the line be drawn at?" },
length = { docs = "Draw the line this distance along the given angle. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
length_x = { docs = "Draw the line this distance along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
length_y = { docs = "Draw the line this distance along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
end_absolute_x = { docs = "Draw the line along the given angle until it reaches this point along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
end_absolute_y = { docs = "Draw the line along the given angle until it reaches this point along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
tag = { docs = "Create a new tag which refers to this line"},
}
}]
@ -871,25 +871,16 @@ async fn inner_angled_line_to_y(
Ok(new_sketch)
}
/// Data for drawing an angled line that intersects with a given line.
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
// TODO: make sure the docs on the args below are correct.
pub struct AngledLineThatIntersectsData {
/// The angle of the line.
pub angle: TyF64,
/// The tag of the line to intersect with.
pub intersect_tag: TagIdentifier,
/// The offset from the intersecting line.
pub offset: Option<TyF64>,
}
/// Draw an angled line that intersects with a given line.
pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (AngledLineThatIntersectsData, Sketch, Option<TagNode>) =
args.get_data_and_sketch_and_tag(exec_state)?;
let new_sketch = inner_angled_line_that_intersects(data, sketch, tag, exec_state, args).await?;
let sketch =
args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
let angle: TyF64 = args.get_kw_arg("angle")?;
let intersect_tag: TagIdentifier = args.get_kw_arg("intersectTag")?;
let offset: Option<TyF64> = args.get_kw_arg_opt("offset")?;
let tag: Option<TagNode> = args.get_kw_arg_opt("tag")?;
let new_sketch =
inner_angled_line_that_intersects(sketch, angle, intersect_tag, offset, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
@ -905,26 +896,37 @@ pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args)
/// |> line(endAbsolute = [5, 10])
/// |> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
/// |> line(endAbsolute = [0, 20])
/// |> angledLineThatIntersects({
/// |> angledLineThatIntersects(
/// angle = 80,
/// intersectTag = lineToIntersect,
/// offset = 10
/// }, %)
/// offset = 10,
/// )
/// |> close()
///
/// example = extrude(exampleSketch, length = 10)
/// ```
#[stdlib {
name = "angledLineThatIntersects",
keywords = true,
unlabeled_first = true,
args = {
sketch = { docs = "Which sketch should this path be added to?"},
angle = { docs = "Which angle should the line be drawn at?" },
intersect_tag = { docs = "The tag of the line to intersect with" },
offset = { docs = "The offset from the intersecting line. Defaults to 0." },
tag = { docs = "Create a new tag which refers to this line"},
}
}]
pub async fn inner_angled_line_that_intersects(
data: AngledLineThatIntersectsData,
sketch: Sketch,
angle: TyF64,
intersect_tag: TagIdentifier,
offset: Option<TyF64>,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let intersect_path = args.get_tag_engine_info(exec_state, &data.intersect_tag)?;
let intersect_path = args.get_tag_engine_info(exec_state, &intersect_tag)?;
let path = intersect_path.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected an intersect path with a path, found `{:?}`", intersect_path),
@ -935,13 +937,12 @@ pub async fn inner_angled_line_that_intersects(
let from = sketch.current_pen_position()?;
let to = intersection_with_parallel_line(
&[untype_point(path.get_from()).0, untype_point(path.get_to()).0],
data.offset.map(|t| t.n).unwrap_or_default(),
data.angle.n,
offset.map(|t| t.n).unwrap_or_default(),
angle.n,
from.into(),
);
let new_sketch = straight_line(StraightLineParams::absolute(to, sketch, tag), exec_state, args).await?;
Ok(new_sketch)
straight_line(StraightLineParams::absolute(to, sketch, tag), exec_state, args).await
}
/// Data for start sketch on.

View File

@ -2137,16 +2137,13 @@ mySk1 = startSketchOn(XY)
#[test]
fn test_recast_multiline_object() {
let some_program_string = r#"part001 = startSketchOn(XY)
|> startProfileAt([-0.01, -0.08], %)
|> line([0.62, 4.15], %, $seg01)
|> line([2.77, -1.24], %)
|> angledLineThatIntersects({
angle = 201,
offset = -1.35,
intersectTag = seg01
}, %)
|> line([-0.42, -1.72], %)"#;
let some_program_string = r#"x = {
a = 1000000000,
b = 2000000000,
c = 3000000000,
d = 4000000000,
e = 5000000000
}"#;
let program = crate::parsing::top_level_parse(some_program_string).unwrap();
let recasted = program.recast(&Default::default(), 0);

View File

@ -122,11 +122,8 @@ export type string
/// myRect = rect([20, 0])
///
/// myRect
/// |> extrude(10, %)
/// |> fillet(
/// radius = 0.5,
/// tags = [myRect.tags.rectangleSegmentA001]
/// )
/// |> extrude(length = 10)
/// |> fillet(radius = 0.5, tags = [myRect.tags.rectangleSegmentA001])
/// ```
///
/// See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

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