merge main

This commit is contained in:
benjamaan476
2025-04-25 15:31:54 +01:00
177 changed files with 34778 additions and 6264 deletions

View File

@ -289,17 +289,6 @@ jobs:
- windows-latest-8-cores - windows-latest-8-cores
shardIndex: [1, 2, 3, 4] shardIndex: [1, 2, 3, 4]
shardTotal: [4] shardTotal: [4]
# Disable macos and windows tests on hourly e2e tests since we only care
# about server side changes.
# Technique from https://github.com/joaomcteixeira/python-project-skeleton/pull/31/files
isScheduled:
- ${{ github.event_name == 'schedule' }}
exclude:
- os: namespace-profile-macos-8-cores
isScheduled: true
- os: windows-latest-8-cores
isScheduled: true
# TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

1
.husky/pre-commit Executable file
View File

@ -0,0 +1 @@
npm run fmt

View File

@ -1,4 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run fmt-check

View File

@ -9,7 +9,7 @@ Get the next adjacent edge to the edge given.
```js ```js
getNextAdjacentEdge(tag: TagIdentifier): Uuid getNextAdjacentEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getNextAdjacentEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the next adjacent edge of. | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
```js ```js
getOppositeEdge(tag: TagIdentifier): Uuid getOppositeEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getOppositeEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the opposite edge of. | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
```js ```js
getPreviousAdjacentEdge(tag: TagIdentifier): Uuid getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes | | `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the previous adjacent edge of. | Yes |
### Returns ### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,7 +10,7 @@ isolated from other files as a separate module.
When you define a function, you can use `export` before it to make it available When you define a function, you can use `export` before it to make it available
to other modules. to other modules.
``` ```kcl
// util.kcl // util.kcl
export fn increment(x) { export fn increment(x) {
return x + 1 return x + 1
@ -31,11 +31,11 @@ Imported files _must_ be in the same project so that units are uniform across
modules. This means that it must be in the same directory. modules. This means that it must be in the same directory.
Import statements must be at the top-level of a file. It is not allowed to have Import statements must be at the top-level of a file. It is not allowed to have
an `import` statement inside a function or in the body of an if-else. an `import` statement inside a function or in the body of an ifelse.
Multiple functions can be exported in a file. Multiple functions can be exported in a file.
``` ```kcl
// util.kcl // util.kcl
export fn increment(x) { export fn increment(x) {
return x + 1 return x + 1
@ -58,6 +58,211 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
import increment as inc, decrement as dec from "util.kcl" import increment as inc, decrement as dec from "util.kcl"
``` ```
---
## Functions vs `clone`
There are two common patterns for reusing geometry:
1. **Wrap the construction in a function** flexible and fully parametric.
2. **Duplicate an existing object with `clone`** lightningfast, but an exact
duplicate.
### Parametric function example
```kcl
fn cube(center) {
return startSketchOn(XY)
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|> close()
|> extrude(length = 10)
}
myCube = cube([0, 0])
```
*Pros*
- Any argument can be a parameter size, position, appearance, etc.
- Works great inside loops, arrays, or optimisation sweeps.
*Cons*
- Every invocation rebuilds the entire feature tree.
- **Slower** than a straight duplicate each call is its own render job.
### `clone` example
```kcl
sketch001 = startSketchOn(-XZ)
|> circle(center = [0, 0], radius = 10)
|> extrude(length = 5)
|> appearance(color = "#ff0000", metalness = 90, roughness = 90)
sketch002 = clone(sketch001) // ✓ instant copy
```
*Pros*
- Roughly an O(1) operation we just duplicate the underlying engine handle.
- Perfect when you need ten identical bolts or two copies of the same imported STEP file.
*Cons*
- **Not parametric** the clone is exactly the same shape as the source.
- If you need to tweak dimensions perinstance, youre back to a function.
> **Rule of thumb** Reach for `clone` when the geometry is already what you want. Reach for a function when you need customisation.
---
## Modulelevel parallelism
Under the hood, the Design Studio runs **every module in parallel** where it can. This means:
- The toplevel code of `foo.kcl`, `bar.kcl`, and `baz.kcl` all start executing immediately and concurrently.
- Imports that read foreign files (STEP/OBJ/…) overlap their I/O and background render.
- CPUbound calculations in separate modules get their own worker threads.
### Why modules beat onebigfile
If you shoehorn everything into `main.kcl`, each statement runs sequentially:
```norun
import "big.step" as gizmo // blocks main while reading
gizmo |> translate(x=50) // blocks again while waiting for render
```
Split `gizmo` into its own file and the read/render can overlap whatever else `main.kcl` is doing.
```norun
// gizmo.kcl (worker A)
import "big.step"
// main.kcl (worker B)
import "gizmo.kcl" as gizmo // nonblocking
// ... other setup ...
gizmo |> translate(x=50) // only blocks here
```
### Gotcha: defining but **not** calling functions
Defining a function inside a module is instantaneous we just record the bytecode. The heavy lifting happens when the function is **called**. So:
```norun
// util.kcl
export fn makeBolt(size) { /* … expensive CAD … */ }
```
If `main.kcl` waits until the very end to call `makeBolt`, *none* of that work was parallelised youve pushed the cost back onto the serial tail of your script.
**Better:** call it early or move the invocation into another module.
```norun
// bolt_instance.kcl
import makeBolt from "util.kcl"
bolt = makeBolt(5) // executed in parallel
bolt
```
Now `main.kcl` can `import "bolt_instance.kcl" as bolt` and get the result that was rendered while it was busy doing other things.
---
## Whole module import
You can also import the whole module. This is useful if you want to use the
result of a module as a variable, like a part.
```norun
import "tests/inputs/cube.kcl" as cube
cube
|> translate(x=10)
```
This imports the whole module and makes it available as `cube`. You can then
use it like any other object. The `cube` variable is now a reference to the
result of the module. This means that if you change the module, the `cube`
variable will change as well.
In `cube.kcl`, you cannot have multiple objects. It has to be a single part. If
you have multiple objects, you will get an error. This is because the module is
expected to return a single object that can be used as a variable.
You also cannot assign that object to a variable. This is because the module is
expected to return a single object that can be used as a variable.
So for example, this is not allowed:
```norun
... a bunch of code to create cube and cube2 ...
myUnion = union([cube, cube2])
```
What you need to do instead is:
```norun
... a bunch of code to create cube and cube2 ...
union([cube, cube2])
```
That way the last line will return the union of the two objects.
Or what you could do instead is:
```norun
... a bunch of code to create cube and cube2 ...
myUnion = union([cube, cube2])
myUnion
```
This will return the union of the two objects, but it will not be assigned to a
variable. This is because the module is expected to return a single object that
can be used as a variable.
---
## Multiple instances of the same import
Whether you are importing a file from another CAD system or a KCL file, that
file represents object(s) in memory. If you import the same file multiple times,
it will only be rendered once.
If you want to have multiple instances of the same object, you can use the
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
```norun
import cube from "tests/inputs/cube.kcl"
cube
|> translate(x=10)
clone(cube)
|> translate(x=20)
```
In the sample above, the `cube` object is imported from a KCL file. The first
instance is translated 10 units in the x direction. The second instance is
cloned and translated 20 units in the x direction. The two instances are now
separate objects in memory, and can be manipulated independently.
Here is an example with a file from another CAD system:
```kcl
import "tests/inputs/cube.step" as cube
cube
|> translate(x=10)
clone(cube)
|> translate(x=20)
```
---
## Importing files from other CAD systems ## Importing files from other CAD systems
`import` can also be used to import files from other CAD systems. The format of the statement is the `import` can also be used to import files from other CAD systems. The format of the statement is the
@ -69,25 +274,17 @@ import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object. // Use `cube` just like a KCL object.
``` ```
```norun ```kcl
import "tests/inputs/cube-2.sldprt" as cube import "tests/inputs/cube.sldprt" as cube
// Use `cube` just like a KCL object. // Use `cube` just like a KCL object.
``` ```
You can make the file format explicit using a format attribute (useful if using a different
extension), e.g.,
```norun
@(format = obj)
import "tests/inputs/cube"
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit unit of measurement is millimeters. Alternatively you may specify the unit
by using an attirbute. Likewise, you can also specify a coordinate system. E.g., by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
```norun ```kcl
@(unitLength = ft, coords = opengl) @(unitLength = ft, coords = opengl)
import "tests/inputs/cube.obj" import "tests/inputs/cube.obj"
``` ```
@ -110,97 +307,55 @@ Coordinate systems:
- `opengl`, forward: +Z, up: +Y, handedness: right - `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left - `vulkan`, forward: +Z, up: -Y, handedness: left
### Performance ---
Parallelized foreign-file imports now let you overlap file reads, initialization, ## Performance deepdive for foreignfile imports
Parallelized foreignfile imports now let you overlap file reads, initialization,
and rendering. To maximize throughput, you need to understand the three distinct and rendering. To maximize throughput, you need to understand the three distinct
stages—reading, initializing (background render start), and invocation (blocking) stages—reading, initializing (background render start), and invocation (blocking)
—and structure your code to defer blocking operations until the end. —and structure your code to defer blocking operations until the end.
#### Foreign Import Execution Stages ### Foreign import execution stages
1. **Import (Read) Stage** 1. **Import (Read / Initialization) Stage**
```norun ```kcl
import "tests/inputs/cube.step" as cube import "tests/inputs/cube.step" as cube
``` ```
- Reads the file from disk and makes its API available. - Reads the file from disk and makes its API available.
- **Does _not_** start Engine rendering or block your script. - Starts engine rendering but **does not block** your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Initialization (Background Render) Stage** 2. **Invocation (Blocking) Stage**
```norun ```kcl
import "tests/inputs/cube.step" as cube import "tests/inputs/cube.step" as cube
myCube = cube // <- This line starts background rendering cube
``` |> translate(z=10) // ← blocks here only
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
- This kickstarts the render pipeline but doesnt block—you can continue other work while the Engine processes the model.
3. **Invocation (Blocking) Stage**
```norun
import "tests/inputs/cube.step" as cube
myCube = cube
myCube
|> translate(z=10) // <- This line blocks
``` ```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations. - Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
- This is the only point where your script will block.
> **Nuance:** Foreign imports differ from pure KCL modules—calling the same import symbol multiple times (e.g., `screw` twice) starts background rendering twice. ### Best practices
#### Best Practices #### 1. Defer blocking calls
##### 1. Defer Blocking Calls ```kcl
Initialize early but delay all transformations until after your heavy computation: import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
```norun
import "tests/inputs/cube.step" as cube // 1) Read
myCube = cube // 2) Background render starts
// --- perform other operations and calculations or setup here --- // --- perform other operations and calculations here ---
myCube
|> translate(z=10) // 3) Blocks only here
```
##### 2. Encapsulate Imports in Modules
Keep `main.kcl` free of reads and initialization; wrap them:
```norun
// imports.kcl
import "tests/inputs/cube.step" as cube // Read only
export myCube = cube // Kick off rendering
```
```norun
// main.kcl
import myCube from "imports.kcl" // Import the initialized object
// ... computations ...
myCube
|> translate(z=10) // Blocking call at the end
```
##### 3. Avoid Immediate Method Calls
```norun
import "tests/inputs/cube.step" as cube
cube cube
|> translate(z=10) // Blocks immediately, negating parallelism |> translate(z=10) // 2) Blocks only here
``` ```
Both calling methods right on `cube` immediately or leaving an implicit import without assignment introduce blocking. #### 2. Split heavy work into separate modules
#### Future Improvements Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues.
#### Future improvements
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
Upcoming releases will autoanalyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.

File diff suppressed because one or more lines are too long

View File

@ -111819,10 +111819,10 @@
"summary": "Get the next adjacent edge to the edge given.", "summary": "Get the next adjacent edge to the edge given.",
"description": "", "description": "",
"tags": [], "tags": [],
"keywordArguments": false, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "tag", "name": "edge",
"type": "TagIdentifier", "type": "TagIdentifier",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -111839,7 +111839,8 @@
}, },
"required": true, "required": true,
"includeInSnippet": true, "includeInSnippet": true,
"labelRequired": true "description": "The tag of the edge you want to find the next adjacent edge of.",
"labelRequired": false
} }
], ],
"returnValue": { "returnValue": {
@ -111866,10 +111867,10 @@
"summary": "Get the opposite edge to the edge given.", "summary": "Get the opposite edge to the edge given.",
"description": "", "description": "",
"tags": [], "tags": [],
"keywordArguments": false, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "tag", "name": "edge",
"type": "TagIdentifier", "type": "TagIdentifier",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -111886,7 +111887,8 @@
}, },
"required": true, "required": true,
"includeInSnippet": true, "includeInSnippet": true,
"labelRequired": true "description": "The tag of the edge you want to find the opposite edge of.",
"labelRequired": false
} }
], ],
"returnValue": { "returnValue": {
@ -111913,10 +111915,10 @@
"summary": "Get the previous adjacent edge to the edge given.", "summary": "Get the previous adjacent edge to the edge given.",
"description": "", "description": "",
"tags": [], "tags": [],
"keywordArguments": false, "keywordArguments": true,
"args": [ "args": [
{ {
"name": "tag", "name": "edge",
"type": "TagIdentifier", "type": "TagIdentifier",
"schema": { "schema": {
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema", "$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
@ -111933,7 +111935,8 @@
}, },
"required": true, "required": true,
"includeInSnippet": true, "includeInSnippet": true,
"labelRequired": true "description": "The tag of the edge you want to find the previous adjacent edge of.",
"labelRequired": false
} }
], ],
"returnValue": { "returnValue": {

View File

@ -13,12 +13,22 @@ test.describe('Authentication tests', () => {
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.projectSection.waitFor() await homePage.projectSection.waitFor()
// This is only needed as an override to test-utils' setup() for this test
await page.addInitScript(() => {
localStorage.setItem('TOKEN_PERSIST_KEY', '')
})
await test.step('Click on sign out and expect sign in page', async () => { await test.step('Click on sign out and expect sign in page', async () => {
await toolbar.userSidebarButton.click() await toolbar.userSidebarButton.click()
await toolbar.signOutButton.click() await toolbar.signOutButton.click()
await expect(signInPage.signInButton).toBeVisible() await expect(signInPage.signInButton).toBeVisible()
}) })
await test.step("Refresh doesn't log the user back in", async () => {
await page.reload()
await expect(signInPage.signInButton).toBeVisible()
})
await test.step('Click on sign in and cancel, click again and expect different code', async () => { await test.step('Click on sign in and cancel, click again and expect different code', async () => {
await signInPage.signInButton.click() await signInPage.signInButton.click()
await expect(signInPage.userCode).toBeVisible() await expect(signInPage.userCode).toBeVisible()
@ -30,6 +40,7 @@ test.describe('Authentication tests', () => {
await expect(signInPage.userCode).toBeVisible() await expect(signInPage.userCode).toBeVisible()
const secondUserCode = await signInPage.userCode.textContent() const secondUserCode = await signInPage.userCode.textContent()
expect(secondUserCode).not.toEqual(firstUserCode) expect(secondUserCode).not.toEqual(firstUserCode)
await signInPage.cancelSignInButton.click()
}) })
await test.step('Press back button and remain on home page', async () => { await test.step('Press back button and remain on home page', async () => {
@ -48,6 +59,12 @@ test.describe('Authentication tests', () => {
// Longer timeout than usual here for the wait on home page // Longer timeout than usual here for the wait on home page
await expect(homePage.projectSection).toBeVisible({ timeout: 10000 }) await expect(homePage.projectSection).toBeVisible({ timeout: 10000 })
}) })
await test.step('Click on sign out and expect sign in page', async () => {
await toolbar.userSidebarButton.click()
await toolbar.signOutButton.click()
await expect(signInPage.signInButton).toBeVisible()
})
} }
) )
}) })

View File

@ -155,7 +155,7 @@ async function doBasicSketch(
|> xLine(length = -segLen(seg01))`) |> xLine(length = -segLen(seg01))`)
} }
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => { test.describe('Basic sketch', () => {
test('code pane open at start', async ({ page, homePage }) => { test('code pane open at start', async ({ page, homePage }) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
await doBasicSketch(page, homePage, ['code']) await doBasicSketch(page, homePage, ['code'])

View File

@ -8,10 +8,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { getUtils } from '@e2e/playwright/test-utils' import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe( test.describe('Can create sketches on all planes and their back sides', () => {
'Can create sketches on all planes and their back sides',
{ tag: ['@skipWin'] },
() => {
const sketchOnPlaneAndBackSideTest = async ( const sketchOnPlaneAndBackSideTest = async (
page: Page, page: Page,
homePage: HomePageFixture, homePage: HomePageFixture,
@ -133,5 +130,4 @@ test.describe(
) )
}) })
} }
} })
)

View File

@ -10,7 +10,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => { test.describe('Code pane and errors', () => {
test('Typing KCL errors induces a badge on the code pane button', async ({ test('Typing KCL errors induces a badge on the code pane button', async ({
page, page,
homePage, homePage,

View File

@ -9,7 +9,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Command bar tests', { tag: ['@skipWin'] }, () => { test.describe('Command bar tests', () => {
test('Extrude from command bar selects extrude line after', async ({ test('Extrude from command bar selects extrude line after', async ({
page, page,
homePage, homePage,
@ -179,10 +179,10 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await expect(commandLevelArgButton).toHaveText('level: project') await expect(commandLevelArgButton).toHaveText('level: project')
}) })
test( test('Command bar keybinding works from code editor and can change a setting', async ({
'Command bar keybinding works from code editor and can change a setting', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage }) => { }) => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
@ -217,9 +217,10 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown') await page.keyboard.press('ArrowDown')
await expect( await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
page.getByRole('option', { name: 'system' }) 'data-headlessui-state',
).toHaveAttribute('data-headlessui-state', 'active') 'active'
)
await page.keyboard.press('Enter') await page.keyboard.press('Enter')
// Check the toast appeared // Check the toast appeared
@ -228,8 +229,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
).toBeVisible() ).toBeVisible()
// Check that the theme changed // Check that the theme changed
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`) await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
} })
)
test('Can extrude from the command bar', async ({ test('Can extrude from the command bar', async ({
page, page,

View File

@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test( test(
'export works on the first try', 'export works on the first try',
{ tag: ['@electron', '@skipLocalEngine'] }, { tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => { async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
if (!tronApp) { if (!tronApp) {
fail() fail()

View File

@ -10,7 +10,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Editor tests', { tag: ['@skipWin'] }, () => { test.describe('Editor tests', () => {
test('can comment out code with ctrl+/', async ({ page, homePage }) => { test('can comment out code with ctrl+/', async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setBodyDimensions({ width: 1000, height: 500 }) await page.setBodyDimensions({ width: 1000, height: 500 })
@ -989,10 +989,11 @@ sketch001 = startSketchOn(XZ)
|> close()`) |> close()`)
}) })
test( test('Can undo a sketch modification with ctrl+z', async ({
'Can undo a sketch modification with ctrl+z', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage, editor }) => { editor,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -1143,8 +1144,7 @@ sketch001 = startSketchOn(XZ)
|> extrude(length = 5)`, |> extrude(length = 5)`,
{ shouldNormalise: true } { shouldNormalise: true }
) )
} })
)
test( test(
`Can import a local OBJ file`, `Can import a local OBJ file`,

View File

@ -29,7 +29,7 @@ test.describe('integrations tests', () => {
) )
}) })
const [clickObj] = await scene.makeMouseHelpers(726, 272) const [clickObj] = scene.makeMouseHelpers(726, 272)
await test.step('setup test', async () => { await test.step('setup test', async () => {
await homePage.expectState({ await homePage.expectState({
@ -73,7 +73,7 @@ test.describe('integrations tests', () => {
}) })
await test.step('setup for next assertion', async () => { await test.step('setup for next assertion', async () => {
await toolbar.openFile('main.kcl') await toolbar.openFile('main.kcl')
await page.waitForTimeout(1000) await page.waitForTimeout(2000)
await clickObj() await clickObj()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await scene.moveNoWhere() await scene.moveNoWhere()

View File

@ -174,6 +174,13 @@ export class ToolbarFixture {
openFile = async (fileName: string) => { openFile = async (fileName: string) => {
await this.filePane.getByText(fileName).click() await this.filePane.getByText(fileName).click()
} }
selectTangentialArc = async () => {
await this.page.getByRole('button', { name: 'caret down arcs:' }).click()
await expect(
this.page.getByTestId('dropdown-three-point-arc')
).toBeVisible()
await this.page.getByTestId('dropdown-tangential-arc').click()
}
selectCenterRectangle = async () => { selectCenterRectangle = async () => {
await this.page await this.page
.getByRole('button', { name: 'caret down rectangles:' }) .getByRole('button', { name: 'caret down rectangles:' })

View File

@ -41,7 +41,7 @@ class MyAPIReporter implements Reporter {
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme' annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
id: test.id, // computed file/test/project ID used for reruns id: test.id, // computed file/test/project ID used for reruns
retry: result.retry, retry: result.retry,
tags: test.tags, // e.g. '@snapshot' or '@skipWin' tags: test.tags, // e.g. '@snapshot' or '@skipLocalEngine'
// Extra environment variables // Extra environment variables
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null, CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
CI_PR_NUMBER: process.env.CI_PR_NUMBER || null, CI_PR_NUMBER: process.env.CI_PR_NUMBER || null,

View File

@ -6,7 +6,10 @@ import { expect, test } from '@e2e/playwright/zoo-test'
* Not all menu actions are tested. Some are default electron menu actions. * Not all menu actions are tested. Some are default electron menu actions.
* Test file menu actions that trigger something in the frontend * Test file menu actions that trigger something in the frontend
*/ */
test.describe('Native file menu', { tag: ['@electron'] }, () => { test.describe(
'Native file menu',
{ tag: ['@electron', '@macos', '@windows'] },
() => {
test.skip() // TODO: Reimplement native file menu tests test.skip() // TODO: Reimplement native file menu tests
test.describe('Home page', () => { test.describe('Home page', () => {
test.describe('File role', () => { test.describe('File role', () => {
@ -161,7 +164,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
const defaultUnit = settings.locator('#defaultUnit') const defaultUnit = settings.locator('#defaultUnit')
await expect(defaultUnit).toBeVisible() await expect(defaultUnit).toBeVisible()
}) })
test('Home.File.Preferences.Theme', async ({ tronApp, cmdBar, page }) => { test('Home.File.Preferences.Theme', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail() if (!tronApp) fail()
// Run electron snippet to find the Menu! // Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
@ -365,7 +372,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) })
}) })
test.describe('Help role', () => { test.describe('Help role', () => {
test('Home.Help.Show all commands', async ({ tronApp, cmdBar, page }) => { test('Home.Help.Show all commands', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail() if (!tronApp) fail()
// Run electron snippet to find the Menu! // Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
@ -389,7 +400,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search') const actual = cmdBar.cmdBarElement.getByTestId('cmd-bar-search')
await expect(actual).toBeVisible() await expect(actual).toBeVisible()
}) })
test('Home.Help.KCL code samples', async ({ tronApp, cmdBar, page }) => { test('Home.Help.KCL code samples', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail() if (!tronApp) fail()
// Run electron snippet to find the Menu! // Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
@ -435,7 +450,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
) )
await expect(actual).toBeVisible() await expect(actual).toBeVisible()
}) })
test('Home.Help.Reset onboarding', async ({ tronApp, cmdBar, page }) => { test('Home.Help.Reset onboarding', async ({
tronApp,
cmdBar,
page,
}) => {
if (!tronApp) fail() if (!tronApp) fail()
// Run electron snippet to find the Menu! // Run electron snippet to find the Menu!
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
@ -2354,4 +2373,5 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
}) })
}) })
}) })
}) }
)

View File

@ -6,6 +6,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
import { import {
executorInputPath, executorInputPath,
getUtils, getUtils,
kclSamplesPath,
testsInputPath, testsInputPath,
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
@ -472,4 +473,94 @@ test.describe('Point-and-click assemblies tests', () => {
}) })
} }
) )
test(
'Assembly gets reexecuted when imported models are updated externally',
{ tag: ['@electron'] },
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
if (!tronApp) {
fail()
}
const midPoint = { x: 500, y: 250 }
const washerPoint = { x: 645, y: 250 }
const partColor: [number, number, number] = [120, 120, 120]
const redPartColor: [number, number, number] = [200, 0, 0]
const bgColor: [number, number, number] = [30, 30, 30]
const tolerance = 50
const projectName = 'assembly'
await test.step('Setup parts and expect imported model', async () => {
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, projectName)
await fsp.mkdir(projectDir, { recursive: true })
await Promise.all([
fsp.copyFile(
executorInputPath('cube.kcl'),
path.join(projectDir, 'cube.kcl')
),
fsp.copyFile(
kclSamplesPath(
path.join(
'pipe-flange-assembly',
'mcmaster-parts',
'98017a257-washer.step'
)
),
path.join(projectDir, 'foreign.step')
),
fsp.writeFile(
path.join(projectDir, 'main.kcl'),
`
import "cube.kcl" as cube
import "foreign.step" as foreign
cube
foreign
|> translate(x = 40, z = 10)`
),
])
})
await page.setBodyDimensions({ width: 1000, height: 500 })
await homePage.openProject(projectName)
await scene.settled(cmdBar)
await toolbar.closePane('code')
await scene.expectPixelColor(partColor, midPoint, tolerance)
})
await test.step('Change imported kcl file and expect change', async () => {
await context.folderSetupFn(async (dir) => {
// Append appearance to the cube.kcl file
await fsp.appendFile(
path.join(dir, projectName, 'cube.kcl'),
`\n |> appearance(color = "#ff0000")`
)
})
await scene.settled(cmdBar)
await toolbar.closePane('code')
await scene.expectPixelColor(redPartColor, midPoint, tolerance)
await scene.expectPixelColor(partColor, washerPoint, tolerance)
})
await test.step('Change imported step file and expect change', async () => {
await context.folderSetupFn(async (dir) => {
// Replace the washer with a pipe
await fsp.copyFile(
kclSamplesPath(
path.join(
'pipe-flange-assembly',
'mcmaster-parts',
'1120t74-pipe.step'
)
),
path.join(dir, projectName, 'foreign.step')
)
})
await scene.settled(cmdBar)
await toolbar.closePane('code')
// Expect pipe to take over the red cube but leave some space where the washer was
await scene.expectPixelColor(partColor, midPoint, tolerance)
await scene.expectPixelColor(bgColor, washerPoint, tolerance)
})
}
)
}) })

View File

@ -18,7 +18,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
test( test(
'projects reload if a new one is created, deleted, or renamed externally', 'projects reload if a new one is created, deleted, or renamed externally',
{ tag: '@electron' }, { tag: ['@electron', '@macos', '@windows'] },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
let externalCreatedProjectName = 'external-created-project' let externalCreatedProjectName = 'external-created-project'

View File

@ -29,7 +29,7 @@ sketch003 = startSketchOn(XY)
extrude003 = extrude(sketch003, length = 20) extrude003 = extrude(sketch003, length = 20)
` `
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => { test.describe('Prompt-to-edit tests', () => {
test.describe('Check the happy path, for basic changing color', () => { test.describe('Check the happy path, for basic changing color', () => {
const cases = [ const cases = [
{ {

View File

@ -14,7 +14,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Regression tests', { tag: ['@skipWin'] }, () => { test.describe('Regression tests', () => {
// bugs we found that don't fit neatly into other categories // bugs we found that don't fit neatly into other categories
test('bad model has inline error #3251', async ({ test('bad model has inline error #3251', async ({
context, context,
@ -239,10 +239,11 @@ extrude001 = extrude(sketch001, length = 50)
await expect(zooLogo).not.toHaveAttribute('href') await expect(zooLogo).not.toHaveAttribute('href')
}) })
test( test('Position _ Is Out Of Range... regression test', async ({
'Position _ Is Out Of Range... regression test', context,
{ tag: ['@skipWin'] }, page,
async ({ context, page, homePage }) => { homePage,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
// const PUR = 400 / 37.5 //pixeltoUnitRatio // const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -315,8 +316,7 @@ extrude001 = extrude(sketch001, length = 50)
thing: "blah"`) thing: "blah"`)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible() await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
} })
)
test( test(
'window resize updates should reconfigure the stream', 'window resize updates should reconfigure the stream',
@ -486,10 +486,10 @@ extrude002 = extrude(profile002, length = 150)
} }
) )
// We updated this test such that you can have multiple exports going at once. // We updated this test such that you can have multiple exports going at once.
test( test('ensure you CAN export while an export is already going', async ({
'ensure you CAN export while an export is already going', page,
{ tag: ['@skipLinux', '@skipWin'] }, homePage,
async ({ page, homePage }) => { }) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step('Set up the code and durations', async () => { await test.step('Set up the code and durations', async () => {
await page.addInitScript( await page.addInitScript(
@ -560,8 +560,7 @@ extrude002 = extrude(profile002, length = 150)
await expect(successToastMessage).toHaveCount(2) await expect(successToastMessage).toHaveCount(2)
}) })
} })
)
test( test(
`Network health indicator only appears in modeling view`, `Network health indicator only appears in modeling view`,

View File

@ -16,7 +16,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Sketch tests', { tag: ['@skipWin'] }, () => { test.describe('Sketch tests', () => {
test('multi-sketch file shows multiple Edit Sketch buttons', async ({ test('multi-sketch file shows multiple Edit Sketch buttons', async ({
page, page,
context, context,
@ -393,10 +393,13 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|> close() |> close()
`) `)
} }
test( test('code pane open at start-handles', async ({
'code pane open at start-handles', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage, scene, toolbar, cmdBar }) => { scene,
toolbar,
cmdBar,
}) => {
// Load the app with the code panes // Load the app with the code panes
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
@ -417,13 +420,15 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
toolbar, toolbar,
cmdBar cmdBar
) )
} })
)
test( test('code pane closed at start-handles', async ({
'code pane closed at start-handles', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage, scene, toolbar, cmdBar }) => { scene,
toolbar,
cmdBar,
}) => {
// Load the app with the code panes // Load the app with the code panes
await page.addInitScript(async (persistModelingContext) => { await page.addInitScript(async (persistModelingContext) => {
localStorage.setItem( localStorage.setItem(
@ -439,8 +444,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
toolbar, toolbar,
cmdBar cmdBar
) )
} })
)
}) })
test('Can edit a circle center and radius by dragging its handles', async ({ test('Can edit a circle center and radius by dragging its handles', async ({
@ -1389,10 +1393,15 @@ offsetPlane001 = offsetPlane(XY, offset = 10)`
}) })
test.describe('multi-profile sketching', () => { test.describe('multi-profile sketching', () => {
test( test(`test it removes half-finished expressions when changing tools in sketch mode`, async ({
`test it removes half-finished expressions when changing tools in sketch mode`, context,
{ tag: ['@skipWin'] }, page,
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => { scene,
toolbar,
editor,
homePage,
cmdBar,
}) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
// We seed the scene with a single offset plane // We seed the scene with a single offset plane
await context.addInitScript(() => { await context.addInitScript(() => {
@ -1461,9 +1470,7 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
await test.step('equip line tool and verify three-point circle code is removed', async () => { await test.step('equip line tool and verify three-point circle code is removed', async () => {
await toolbar.lineBtn.click() await toolbar.lineBtn.click()
await editor.expectEditor.not.toContain( await editor.expectEditor.not.toContain('profile003 = circleThreePoint(')
'profile003 = circleThreePoint('
)
}) })
await test.step('equip three-point-arc tool and click first two points', async () => { await test.step('equip three-point-arc tool and click first two points', async () => {
@ -1520,12 +1527,16 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
await editor.expectEditor.not.toContain('arc(') await editor.expectEditor.not.toContain('arc(')
await editor.expectEditor.toContain('profile002') await editor.expectEditor.toContain('profile002')
}) })
} })
) test(`snapToProfile start only works for current profile`, async ({
test( context,
`snapToProfile start only works for current profile`, page,
{ tag: ['@skipWin'] }, scene,
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => { toolbar,
editor,
homePage,
cmdBar,
}) => {
// We seed the scene with a single offset plane // We seed the scene with a single offset plane
await context.addInitScript(() => { await context.addInitScript(() => {
localStorage.setItem( localStorage.setItem(
@ -1564,7 +1575,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
const codeFromTangentialArc = ` |> tangentialArc(endAbsolute = [39.49, 88.22])` const codeFromTangentialArc = ` |> tangentialArc(endAbsolute = [39.49, 88.22])`
await test.step('check that tangential tool does not snap to other profile starts', async () => { await test.step('check that tangential tool does not snap to other profile starts', async () => {
await toolbar.tangentialArcBtn.click() await toolbar.selectTangentialArc()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await endOfLowerSegMove() await endOfLowerSegMove()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
@ -1604,8 +1615,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
`[profileStartX(%), profileStartY(%)]` `[profileStartX(%), profileStartY(%)]`
) )
}) })
} })
)
test('can enter sketch mode for sketch with no profiles', async ({ test('can enter sketch mode for sketch with no profiles', async ({
scene, scene,
toolbar, toolbar,
@ -1777,7 +1787,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
await endLineStartTanArc() await endLineStartTanArc()
await editor.expectEditor.toContain(`|> line(end = [9.02, -0.55])`) await editor.expectEditor.toContain(`|> line(end = [9.02, -0.55])`)
await toolbar.tangentialArcBtn.click() await toolbar.selectTangentialArc()
await page.waitForTimeout(300) await page.waitForTimeout(300)
await page.mouse.click(745, 359) await page.mouse.click(745, 359)
await page.waitForTimeout(300) await page.waitForTimeout(300)
@ -2071,10 +2081,13 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
}) })
}) })
test( test('Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile', async ({
'Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile', homePage,
{ tag: ['@skipWin'] }, scene,
async ({ homePage, scene, toolbar, editor, page }) => { toolbar,
editor,
page,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2204,12 +2217,15 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
|> close()`.replaceAll('\n', '') |> close()`.replaceAll('\n', '')
) )
}) })
} })
) test('Can delete a profile in the editor while is sketch mode, and sketch mode does not break, can ctrl+z to undo after constraint with variable was added', async ({
test( scene,
'Can delete a profile in the editor while is sketch mode, and sketch mode does not break, can ctrl+z to undo after constraint with variable was added', toolbar,
{ tag: ['@skipWin', '@skipLinux'] }, editor,
async ({ scene, toolbar, editor, cmdBar, page, homePage }) => { cmdBar,
page,
homePage,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2303,13 +2319,15 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
await editor.expectEditor.not.toContain('length001 = 7') await editor.expectEditor.not.toContain('length001 = 7')
await sketchIsDrawnProperly() await sketchIsDrawnProperly()
}) })
} })
)
test( test('can enter sketch when there is an extrude', async ({
'can enter sketch when there is an extrude', homePage,
{ tag: ['@skipWin'] }, scene,
async ({ homePage, scene, toolbar, page, cmdBar }) => { toolbar,
page,
cmdBar,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2361,8 +2379,7 @@ extrude001 = extrude(profile003, length = 5)
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 763, y: 214 }, 15), scene.expectPixelColor(TEST_COLORS.WHITE, { x: 763, y: 214 }, 15),
]) ])
}) })
} })
)
test('exit new sketch without drawing anything should not be a problem', async ({ test('exit new sketch without drawing anything should not be a problem', async ({
homePage, homePage,
scene, scene,
@ -2416,10 +2433,13 @@ extrude001 = extrude(profile003, length = 5)
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15) await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
}) })
}) })
test( test('A sketch with only "startProfileAt" and no segments should still be able to be continued', async ({
'A sketch with only "startProfileAt" and no segments should still be able to be continued', homePage,
{ tag: ['@skipWin'] }, scene,
async ({ homePage, scene, toolbar, editor, page }) => { toolbar,
editor,
page,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2457,12 +2477,14 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
await page.waitForTimeout(100) await page.waitForTimeout(100)
await nextPoint() await nextPoint()
await editor.expectEditor.toContain(`|> line(end = [126.05, 44.12])`) await editor.expectEditor.toContain(`|> line(end = [126.05, 44.12])`)
} })
) test('old style sketch all in one pipe (with extrude) will break up to allow users to add a new profile to the same sketch', async ({
test( homePage,
'old style sketch all in one pipe (with extrude) will break up to allow users to add a new profile to the same sketch', scene,
{ tag: ['@skipWin'] }, toolbar,
async ({ homePage, scene, toolbar, editor, page }) => { editor,
page,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2517,12 +2539,15 @@ extrude001 = extrude(thePart, length = 75)
await profilePoint2() await profilePoint2()
await editor.expectEditor.toContain(`|> line(end = [18.97, -18.06])`) await editor.expectEditor.toContain(`|> line(end = [18.97, -18.06])`)
}) })
} })
) test('Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections', async ({
test( homePage,
'Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections', scene,
{ tag: ['@skipWin'] }, toolbar,
async ({ homePage, scene, toolbar, editor, page, cmdBar }) => { editor,
page,
cmdBar,
}) => {
// TODO this test should include a test for selecting revolve walls and caps // TODO this test should include a test for selecting revolve walls and caps
await page.addInitScript(async () => { await page.addInitScript(async () => {
@ -2681,12 +2706,14 @@ extrude003 = extrude(profile011, length = 2.5)
}) })
} }
}) */ }) */
} })
) test('Can enter sketch loft edges, base and continue sketch', async ({
test( homePage,
'Can enter sketch loft edges, base and continue sketch', scene,
{ tag: ['@skipWin'] }, toolbar,
async ({ homePage, scene, toolbar, editor, page }) => { editor,
page,
}) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(
'persistCode', 'persistCode',
@ -2736,8 +2763,7 @@ loft([profile001, profile002])
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
`angledLine(angle = 0, length = 113.01, tag = $rectangleSegmentA001)` `angledLine(angle = 0, length = 113.01, tag = $rectangleSegmentA001)`
) )
} })
)
test('Can enter sketch loft edges offsetPlane and continue sketch', async ({ test('Can enter sketch loft edges offsetPlane and continue sketch', async ({
scene, scene,
toolbar, toolbar,
@ -2970,7 +2996,7 @@ test.describe('Redirecting to home page and back to the original file should cle
await click00r(200, -200) await click00r(200, -200)
// Draw arc // Draw arc
await toolbar.tangentialArcBtn.click() await toolbar.selectTangentialArc()
await click00r(0, 0) await click00r(0, 0)
await click00r(100, 100) await click00r(100, 100)
@ -3245,10 +3271,15 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
).toBeVisible() ).toBeVisible()
}) })
}) })
test( test('adding a syntax error, recovers after fixing', async ({
'adding a syntax error, recovers after fixing', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage, context, scene, editor, toolbar, cmdBar }) => { context,
scene,
editor,
toolbar,
cmdBar,
}) => {
const file = await fs.readFile( const file = await fs.readFile(
path.resolve( path.resolve(
__dirname, __dirname,
@ -3335,6 +3366,5 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
15 15
) )
}) })
} })
)
}) })

View File

@ -47,7 +47,7 @@ test.setTimeout(60_000)
// up with another PR if we want this back. // up with another PR if we want this back.
test( test(
'exports of each format should work', 'exports of each format should work',
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] }, { tag: ['@snapshot'] },
async ({ page, context, scene, cmdBar, tronApp }) => { async ({ page, context, scene, cmdBar, tronApp }) => {
if (!tronApp) { if (!tronApp) {
fail() fail()
@ -464,9 +464,7 @@ test(
|> xLine(length = 184.3)` |> xLine(length = 184.3)`
await expect(page.locator('.cm-content')).toHaveText(code) await expect(page.locator('.cm-content')).toHaveText(code)
await page await toolbar.selectTangentialArc()
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
.click()
// click on the end of the profile to continue it // click on the end of the profile to continue it
await page.waitForTimeout(500) await page.waitForTimeout(500)
@ -621,7 +619,7 @@ test.describe(
'Client side scene scale should match engine scale', 'Client side scene scale should match engine scale',
{ tag: '@snapshot' }, { tag: '@snapshot' },
() => { () => {
test('Inch scale', async ({ page, cmdBar, scene }) => { test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => {
const u = await getUtils(page) const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 }) await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -655,9 +653,7 @@ test.describe(
|> xLine(length = 184.3)` |> xLine(length = 184.3)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await toolbar.selectTangentialArc()
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// click to continue profile // click to continue profile
@ -671,9 +667,8 @@ test.describe(
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
// click tangential arc tool again to unequip it // click tangential arc tool again to unequip it
await page // it will be available directly in the toolbar since it was last equipped
.getByRole('button', { name: 'arc Tangential Arc', exact: true }) await toolbar.tangentialArcBtn.click()
.click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// screen shot should show the sketch // screen shot should show the sketch
@ -696,7 +691,13 @@ test.describe(
}) })
}) })
test('Millimeter scale', async ({ page, context, cmdBar, scene }) => { test('Millimeter scale', async ({
page,
context,
cmdBar,
scene,
toolbar,
}) => {
await context.addInitScript( await context.addInitScript(
async ({ settingsKey, settings }) => { async ({ settingsKey, settings }) => {
localStorage.setItem(settingsKey, settings) localStorage.setItem(settingsKey, settings)
@ -749,9 +750,7 @@ test.describe(
|> xLine(length = 184.3)` |> xLine(length = 184.3)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await toolbar.selectTangentialArc()
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// click to continue profile // click to continue profile
@ -764,9 +763,7 @@ test.describe(
|> tangentialArc(endAbsolute = [551.2, -62.01])` |> tangentialArc(endAbsolute = [551.2, -62.01])`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await page await toolbar.tangentialArcBtn.click()
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
.click()
await page.waitForTimeout(100) await page.waitForTimeout(100)
// screen shot should show the sketch // screen shot should show the sketch

View File

@ -8,7 +8,12 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Test network and connection issues', () => { test.describe(
'Test network and connection issues',
{
tag: ['@macos', '@windows'],
},
() => {
test( test(
'simulate network down and network little widget', 'simulate network down and network little widget',
{ tag: '@skipLocalEngine' }, { tag: '@skipLocalEngine' },
@ -184,7 +189,9 @@ test.describe('Test network and connection issues', () => {
await toolbar.editSketch() await toolbar.editSketch()
// Click the line tool // Click the line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click() await page
.getByRole('button', { name: 'line Line', exact: true })
.click()
await page.waitForTimeout(150) await page.waitForTimeout(150)
@ -254,4 +261,5 @@ profile001 = startProfileAt([12.34, -12.34], sketch001)
).not.toBeVisible() ).not.toBeVisible()
} }
) )
}) }
)

View File

@ -1024,6 +1024,10 @@ export function testsInputPath(fileName: string): string {
return path.join('rust', 'kcl-lib', 'tests', 'inputs', fileName) return path.join('rust', 'kcl-lib', 'tests', 'inputs', fileName)
} }
export function kclSamplesPath(fileName: string): string {
return path.join('public', 'kcl-samples', fileName)
}
export async function doAndWaitForImageDiff( export async function doAndWaitForImageDiff(
page: Page, page: Page,
fn: () => Promise<unknown>, fn: () => Promise<unknown>,

View File

@ -4,7 +4,7 @@ import { uuidv4 } from '@src/lib/utils'
import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils' import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => { test.describe('Testing Camera Movement', () => {
test('Can move camera reliably', async ({ test('Can move camera reliably', async ({
page, page,
context, context,

View File

@ -10,7 +10,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing constraints', { tag: ['@skipWin'] }, () => { test.describe('Testing constraints', () => {
test('Can constrain line length', async ({ page, homePage }) => { test('Can constrain line length', async ({ page, homePage }) => {
await page.addInitScript(async () => { await page.addInitScript(async () => {
localStorage.setItem( localStorage.setItem(

View File

@ -4,7 +4,7 @@ import { TEST_CODE_GIZMO } from '@e2e/playwright/storageStates'
import { getUtils } from '@e2e/playwright/test-utils' import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => { test.describe('Testing Gizmo', () => {
const cases = [ const cases = [
{ {
testDescription: 'top view', testDescription: 'top view',

View File

@ -11,7 +11,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => { test.describe('Testing segment overlays', () => {
test('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => { test('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => {
// TODO: fix this test on mac after the electron migration // TODO: fix this test on mac after the electron migration
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())

View File

@ -9,7 +9,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing selections', { tag: ['@skipWin'] }, () => { test.describe('Testing selections', () => {
test.setTimeout(90_000) test.setTimeout(90_000)
test('Selections work on fresh and edited sketch', async ({ test('Selections work on fresh and edited sketch', async ({
page, page,
@ -39,12 +39,12 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
}) })
const emptySpaceHover = () => const emptySpaceHover = () =>
test.step('Hover over empty space', async () => { test.step('Hover over empty space', async () => {
await page.mouse.move(700, 143, { steps: 5 }) await page.mouse.move(1000, 143, { steps: 5 })
await expect(page.locator('.hover-highlight')).not.toBeVisible() await expect(page.locator('.hover-highlight')).not.toBeVisible()
}) })
const emptySpaceClick = () => const emptySpaceClick = () =>
test.step(`Click in empty space`, async () => { test.step(`Click in empty space`, async () => {
await page.mouse.click(700, 143) await page.mouse.click(1000, 143)
await expect(page.locator('.cm-line').last()).toHaveClass( await expect(page.locator('.cm-line').last()).toHaveClass(
/cm-activeLine/ /cm-activeLine/
) )

View File

@ -26,7 +26,12 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Testing settings', () => { test.describe(
'Testing settings',
{
tag: ['@macos', '@windows'],
},
() => {
test('Stored settings are validated and fall back to defaults', async ({ test('Stored settings are validated and fall back to defaults', async ({
page, page,
homePage, homePage,
@ -93,11 +98,14 @@ test.describe('Testing settings', () => {
/** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */ /** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */
await test.step(`Confirm that this dialog has a solid background`, async () => { await test.step(`Confirm that this dialog has a solid background`, async () => {
await expect await expect
.poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), { .poll(
() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]),
{
timeout: 1000, timeout: 1000,
message: message:
'Checking for solid background, should not see default plane colors', 'Checking for solid background, should not see default plane colors',
}) }
)
.toBeLessThan(15) .toBeLessThan(15)
}) })
@ -130,7 +138,9 @@ test.describe('Testing settings', () => {
// Roll back to default of "off" // Roll back to default of "off"
await await page await await page
.getByText('show debug panelRoll back show debug panelRoll back to match') .getByText(
'show debug panelRoll back show debug panelRoll back to match'
)
.hover() .hover()
await page await page
.getByRole('button', { .getByRole('button', {
@ -180,7 +190,10 @@ test.describe('Testing settings', () => {
await expect(hotkey).toHaveText(text) await expect(hotkey).toHaveText(text)
}) })
test('Project and user settings can be reset', async ({ page, homePage }) => { test('Project and user settings can be reset', async ({
page,
homePage,
}) => {
const u = await getUtils(page) const u = await getUtils(page)
await test.step(`Setup`, async () => { await test.step(`Setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -268,7 +281,7 @@ test.describe('Testing settings', () => {
test( test(
`Project settings override user settings on desktop`, `Project settings override user settings on desktop`,
{ tag: ['@electron', '@skipWin'] }, { tag: ['@electron'] },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
const projectName = 'bracket' const projectName = 'bracket'
@ -291,13 +304,18 @@ test.describe('Testing settings', () => {
projectName, projectName,
PROJECT_SETTINGS_FILE_NAME PROJECT_SETTINGS_FILE_NAME
) )
const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) const tempUserSettingsFilePath = join(
projectDirName,
SETTINGS_FILE_NAME
)
const userThemeColor = '120' const userThemeColor = '120'
const projectThemeColor = '50' const projectThemeColor = '50'
const settingsOpenButton = page.getByRole('link', { const settingsOpenButton = page.getByRole('link', {
name: 'settings Settings', name: 'settings Settings',
}) })
const themeColorSetting = page.locator('#themeColor').getByRole('slider') const themeColorSetting = page
.locator('#themeColor')
.getByRole('slider')
const projectSettingsTab = page.getByRole('radio', { name: 'Project' }) const projectSettingsTab = page.getByRole('radio', { name: 'Project' })
const userSettingsTab = page.getByRole('radio', { name: 'User' }) const userSettingsTab = page.getByRole('radio', { name: 'User' })
const settingsCloseButton = page.getByTestId('settings-close-button') const settingsCloseButton = page.getByTestId('settings-close-button')
@ -532,7 +550,10 @@ test.describe('Testing settings', () => {
join(bracketDir, '2.kcl') join(bracketDir, '2.kcl')
) )
}) })
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8') const kclCube = await fsp.readFile(
executorInputPath('cube.kcl'),
'utf-8'
)
const kclCylinder = await fsp.readFile( const kclCylinder = await fsp.readFile(
executorInputPath('cylinder.kcl'), executorInputPath('cylinder.kcl'),
'utf8' 'utf8'
@ -586,7 +607,9 @@ test.describe('Testing settings', () => {
test.skip(process.platform === 'win32', 'Skip on windows') test.skip(process.platform === 'win32', 'Skip on windows')
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
const toastMessage = page.getByText(`Successfully created "testDefault"`) const toastMessage = page.getByText(
`Successfully created "testDefault"`
)
await expect(toastMessage).not.toBeVisible() await expect(toastMessage).not.toBeVisible()
await page await page
.getByRole('button', { name: 'Start Sketch' }) .getByRole('button', { name: 'Start Sketch' })
@ -620,7 +643,9 @@ test.describe('Testing settings', () => {
}) })
await test.step('Change modeling default unit within project tab', async () => { await test.step('Change modeling default unit within project tab', async () => {
const changeUnitOfMeasureInProjectTab = async (unitOfMeasure: string) => { const changeUnitOfMeasureInProjectTab = async (
unitOfMeasure: string
) => {
await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => { await test.step(`Set modeling default unit to ${unitOfMeasure}`, async () => {
await page await page
.getByTestId('modeling-defaultUnit') .getByTestId('modeling-defaultUnit')
@ -674,7 +699,9 @@ test.describe('Testing settings', () => {
await test.step('Change modeling default unit within command bar', async () => { await test.step('Change modeling default unit within command bar', async () => {
const commands = page.getByRole('button', { name: 'Commands' }) const commands = page.getByRole('button', { name: 'Commands' })
const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => { const changeUnitOfMeasureInCommandBar = async (
unitOfMeasure: string
) => {
// Open command bar // Open command bar
await commands.click() await commands.click()
const settingsModelingDefaultUnitCommand = page.getByText( const settingsModelingDefaultUnitCommand = page.getByText(
@ -885,7 +912,10 @@ test.describe('Testing settings', () => {
const u = await getUtils(page) const u = await getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}') localStorage.setItem(
'persistModelingContext',
'{"openPanes":["debug"]}'
)
}) })
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene() await homePage.goToModelingScene()
@ -1057,4 +1087,5 @@ fn cube`
} }
) )
}) })
}) }
)

View File

@ -9,7 +9,7 @@ import {
} from '@e2e/playwright/test-utils' } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => { test.describe('Text-to-CAD tests', () => {
test('basic lego happy case', async ({ page, homePage }) => { test('basic lego happy case', async ({ page, homePage }) => {
const u = await getUtils(page) const u = await getUtils(page)
@ -436,10 +436,10 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
}) })
// This will be fine once greg makes prompt at top of file deterministic // This will be fine once greg makes prompt at top of file deterministic
test( test('can do many at once and get many prompts back, and interact with many', async ({
'can do many at once and get many prompts back, and interact with many', page,
{ tag: ['@skipWin'] }, homePage,
async ({ page, homePage }) => { }) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
// Let this test run longer since we've seen it timeout. // Let this test run longer since we've seen it timeout.
test.setTimeout(180_000) test.setTimeout(180_000)
@ -521,8 +521,7 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
// Expect the code to be pasted. // Expect the code to be pasted.
const code2x4 = await page.locator('.cm-content').innerText() const code2x4 = await page.locator('.cm-content').innerText()
await expect(code2x4.length).toBeGreaterThan(249) await expect(code2x4.length).toBeGreaterThan(249)
} })
)
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({ test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
page, page,

View File

@ -178,6 +178,7 @@ test('Keyboard shortcuts can be viewed through the help menu', async ({
test('First escape in tool pops you out of tool, second exits sketch mode', async ({ test('First escape in tool pops you out of tool, second exits sketch mode', async ({
page, page,
homePage, homePage,
toolbar,
}) => { }) => {
// Wait for the app to be ready for use // Wait for the app to be ready for use
const u = await getUtils(page) const u = await getUtils(page)
@ -188,15 +189,6 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await u.expectCmdLog('[data-message-type="execution-done"]') await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel() await u.closeDebugPanel()
const lineButton = page.getByRole('button', {
name: 'line Line',
exact: true,
})
const arcButton = page.getByRole('button', {
name: 'arc Tangential Arc',
exact: true,
})
// Test these hotkeys perform actions when // Test these hotkeys perform actions when
// focus is on the canvas // focus is on the canvas
await page.mouse.move(600, 250) await page.mouse.move(600, 250)
@ -207,8 +199,8 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await page.mouse.move(800, 300) await page.mouse.move(800, 300)
await page.mouse.click(800, 300) await page.mouse.click(800, 300)
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await expect(lineButton).toBeVisible() await expect(toolbar.lineBtn).toBeVisible()
await expect(lineButton).toHaveAttribute('aria-pressed', 'true') await expect(toolbar.lineBtn).toHaveAttribute('aria-pressed', 'true')
// Draw a line // Draw a line
await page.mouse.move(700, 200, { steps: 5 }) await page.mouse.move(700, 200, { steps: 5 })
@ -224,10 +216,9 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
// Make sure we didn't pop out of sketch mode. // Make sure we didn't pop out of sketch mode.
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true') await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
// Equip arc tool // Equip arc tool
await page.keyboard.press('a') await toolbar.selectTangentialArc()
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
// click in the same position again to continue the profile // click in the same position again to continue the profile
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, { await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
@ -238,11 +229,14 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
await page.mouse.move(1000, 100, { steps: 5 }) await page.mouse.move(1000, 100, { steps: 5 })
await page.mouse.click(1000, 100) await page.mouse.click(1000, 100)
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect(arcButton).toHaveAttribute('aria-pressed', 'false') await expect(toolbar.tangentialArcBtn).toHaveAttribute(
'aria-pressed',
'false'
)
await expect await expect
.poll(async () => { .poll(async () => {
await page.keyboard.press('l') await page.keyboard.press('l')
return lineButton.getAttribute('aria-pressed') return toolbar.lineBtn.getAttribute('aria-pressed')
}) })
.toBe('true') .toBe('true')
@ -251,8 +245,11 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
// Unequip line tool // Unequip line tool
await page.keyboard.press('Escape') await page.keyboard.press('Escape')
await expect(lineButton).toHaveAttribute('aria-pressed', 'false') await expect(toolbar.lineBtn).toHaveAttribute('aria-pressed', 'false')
await expect(arcButton).toHaveAttribute('aria-pressed', 'false') await expect(toolbar.tangentialArcBtn).toHaveAttribute(
'aria-pressed',
'false'
)
// Make sure we didn't pop out of sketch mode. // Make sure we didn't pop out of sketch mode.
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible() await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
// Exit sketch // Exit sketch

1
interface.d.ts vendored
View File

@ -20,6 +20,7 @@ export interface IElectronAPI {
open: typeof dialog.showOpenDialog open: typeof dialog.showOpenDialog
save: typeof dialog.showSaveDialog save: typeof dialog.showSaveDialog
openExternal: typeof shell.openExternal openExternal: typeof shell.openExternal
openInNewWindow: (name: string) => void
takeElectronWindowScreenshot: ({ takeElectronWindowScreenshot: ({
width, width,
height, height,

View File

@ -3,14 +3,13 @@
> dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx > dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx
• Circular Dependencies • Circular Dependencies
01) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts 1) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts
02) src/lang/std/sketch.ts -> src/lang/modifyAst.ts 2) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
03) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts 3) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
04) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts 4) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
05) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/engineStreamMachine.ts 5) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
06) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts 6) src/lib/singletons.ts -> src/lang/codeManager.ts
07) src/machines/appMachine.ts -> src/machines/settingsMachine.ts -> src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts 7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
08) src/lib/singletons.ts -> src/lang/codeManager.ts 8) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts
09) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts 9) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts
10) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts 10) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
11) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx

View File

@ -76,6 +76,7 @@
"yargs": "^17.7.2" "yargs": "^17.7.2"
}, },
"scripts": { "scripts": {
"prepare": "husky",
"install:rust": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none && source \"$HOME/.cargo/env\" && (cd rust && (rustup show active-toolchain || rustup toolchain install))", "install:rust": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain none && source \"$HOME/.cargo/env\" && (cd rust && (rustup show active-toolchain || rustup toolchain install))",
"install:rust:windows": "winget install Microsoft.VisualStudio.2022.Community --silent --override \"--wait --quiet --add ProductLang En-us --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended\" && winget install Rustlang.Rustup", "install:rust:windows": "winget install Microsoft.VisualStudio.2022.Community --silent --override \"--wait --quiet --add ProductLang En-us --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended\" && winget install Rustlang.Rustup",
"install:wasm-pack:sh": ". $HOME/.cargo/env && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f", "install:wasm-pack:sh": ". $HOME/.cargo/env && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -f",
@ -114,6 +115,7 @@
"circular-deps": "dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx", "circular-deps": "dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx",
"circular-deps:overwrite": "npm run circular-deps | sed '$d' | grep -v '^npm run' > known-circular.txt", "circular-deps:overwrite": "npm run circular-deps | sed '$d' | grep -v '^npm run' > known-circular.txt",
"circular-deps:diff": "./scripts/diff-circular-deps.sh", "circular-deps:diff": "./scripts/diff-circular-deps.sh",
"circular-deps:diff:nodejs": "npm run circular-deps:diff || node ./scripts/diff.js",
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json", "files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
"files:set-notes": "./scripts/set-files-notes.sh", "files:set-notes": "./scripts/set-files-notes.sh",
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh", "files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
@ -125,7 +127,7 @@
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts", "generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
"generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js", "generate:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
"tron:start": "electron-forge start", "tron:start": "electron-forge start",
"chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'", "chrome:test": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert=@snapshot",
"tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development", "tronb:vite:dev": "vite build -c vite.main.config.ts -m development && vite build -c vite.preload.config.ts -m development && vite build -c vite.renderer.config.ts -m development",
"tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts", "tronb:vite:prod": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
"tronb:package:dev": "npm run tronb:vite:dev && electron-builder --config electron-builder.yml", "tronb:package:dev": "npm run tronb:vite:dev && electron-builder --config electron-builder.yml",
@ -135,15 +137,15 @@
"test:snapshots": "PLATFORM=web NODE_ENV=development playwright test --config=playwright.config.ts --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": "vitest run --mode development --exclude **/kclSamples.test.ts",
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/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'", "test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet", "test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep=@windows --quiet",
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet", "test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep=@macos --quiet",
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet", "test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot --quiet",
"test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'", "test:playwright:electron:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:windows:local": "npm run tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"", "test:playwright:electron:windows:local": "npm run tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:macos:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'", "test:playwright:electron:macos:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:ubuntu:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'", "test:playwright:electron:ubuntu:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
"test:playwright:electron:ubuntu:engine:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot|@skipLocalEngine'", "test:playwright:electron:ubuntu:engine:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot|@skipLocalEngine",
"test:unit:local": "npm run simpleserver:bg && npm run test:unit; kill-port 3000", "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" "test:unit:kcl-samples:local": "npm run simpleserver:bg && npm run test:unit:kcl-samples; kill-port 3000"
}, },

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1076,7 +1076,7 @@ mod test {
#[for_each_std_mod] #[for_each_std_mod]
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_examples() { async fn kcl_test_examples() {
let std = walk_prelude(); let std = walk_prelude();
let mut errs = Vec::new(); let mut errs = Vec::new();
for d in std { for d in std {

View File

@ -459,7 +459,7 @@ impl ExecutorContext {
exec_state.add_path_to_source_id(resolved_path.clone(), id); exec_state.add_path_to_source_id(resolved_path.clone(), id);
let format = super::import::format_from_annotations(attrs, path, source_range)?; let format = super::import::format_from_annotations(attrs, path, source_range)?;
let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?; let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom)); exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
Ok(id) Ok(id)
} }
ImportPath::Std { .. } => { ImportPath::Std { .. } => {
@ -501,7 +501,7 @@ impl ExecutorContext {
*cache = Some((val, er, items.clone())); *cache = Some((val, er, items.clone()));
(er, items) (er, items)
}), }),
ModuleRepr::Foreign(geom) => Err(KclError::Semantic(KclErrorDetails { ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails {
message: "Cannot import items from foreign modules".to_owned(), message: "Cannot import items from foreign modules".to_owned(),
source_ranges: vec![geom.source_range], source_ranges: vec![geom.source_range],
})), })),
@ -546,9 +546,20 @@ impl ExecutorContext {
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
ModuleRepr::Foreign(geom) => super::import::send_to_engine(geom.clone(), self) ModuleRepr::Foreign(_, Some(imported)) => Ok(Some(imported.clone())),
ModuleRepr::Foreign(geom, cached) => {
let result = super::import::send_to_engine(geom.clone(), self)
.await .await
.map(|geom| Some(KclValue::ImportedGeometry(geom))), .map(|geom| Some(KclValue::ImportedGeometry(geom)));
match result {
Ok(val) => {
*cached = val.clone();
Ok(val)
}
Err(e) => Err(e),
}
}
ModuleRepr::Dummy => unreachable!(), ModuleRepr::Dummy => unreachable!(),
}; };

View File

@ -752,7 +752,12 @@ impl ExecutorContext {
let mut universe = std::collections::HashMap::new(); let mut universe = std::collections::HashMap::new();
let default_planes = self.engine.get_default_planes().read().await.clone(); let default_planes = self.engine.get_default_planes().read().await.clone();
crate::walk::import_universe(self, &program.ast, &mut universe, exec_state) crate::walk::import_universe(
self,
&ModuleRepr::Kcl(program.ast.clone(), None),
&mut universe,
exec_state,
)
.await .await
.map_err(|err| { .map_err(|err| {
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
@ -799,16 +804,12 @@ impl ExecutorContext {
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
let (results_tx, mut results_rx): ( let (results_tx, mut results_rx): (
tokio::sync::mpsc::Sender<( tokio::sync::mpsc::Sender<(ModuleId, ModulePath, Result<ModuleRepr, KclError>)>,
ModuleId,
ModulePath,
Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError>,
)>,
tokio::sync::mpsc::Receiver<_>, tokio::sync::mpsc::Receiver<_>,
) = tokio::sync::mpsc::channel(1); ) = tokio::sync::mpsc::channel(1);
for module in modules { for module in modules {
let Some((import_stmt, module_id, module_path, program)) = universe.get(&module) else { let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails { return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails {
message: format!("Module {module} not found in universe"), message: format!("Module {module} not found in universe"),
source_ranges: Default::default(), source_ranges: Default::default(),
@ -816,12 +817,41 @@ impl ExecutorContext {
}; };
let module_id = *module_id; let module_id = *module_id;
let module_path = module_path.clone(); let module_path = module_path.clone();
let program = program.clone(); let repr = repr.clone();
let exec_state = exec_state.clone(); let exec_state = exec_state.clone();
let exec_ctxt = self.clone(); let exec_ctxt = self.clone();
let results_tx = results_tx.clone(); let results_tx = results_tx.clone();
let source_range = SourceRange::from(import_stmt); let source_range = SourceRange::from(import_stmt);
let exec_module = async |exec_ctxt: &ExecutorContext,
repr: &ModuleRepr,
module_id: ModuleId,
module_path: &ModulePath,
exec_state: &mut ExecState,
source_range: SourceRange|
-> Result<ModuleRepr, KclError> {
match repr {
ModuleRepr::Kcl(program, _) => {
let result = exec_ctxt
.exec_module_from_ast(program, module_id, module_path, exec_state, source_range, false)
.await;
result.map(|val| ModuleRepr::Kcl(program.clone(), Some(val)))
}
ModuleRepr::Foreign(geom, _) => {
let result = crate::execution::import::send_to_engine(geom.clone(), exec_ctxt)
.await
.map(|geom| Some(KclValue::ImportedGeometry(geom)));
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
}
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails {
message: format!("Module {module_path} not found in universe"),
source_ranges: vec![source_range],
})),
}
};
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
{ {
wasm_bindgen_futures::spawn_local(async move { wasm_bindgen_futures::spawn_local(async move {
@ -829,14 +859,13 @@ impl ExecutorContext {
let mut exec_state = exec_state; let mut exec_state = exec_state;
let exec_ctxt = exec_ctxt; let exec_ctxt = exec_ctxt;
let result = exec_ctxt let result = exec_module(
.exec_module_from_ast( &exec_ctxt,
&program, &repr,
module_id, module_id,
&module_path, &module_path,
&mut exec_state, &mut exec_state,
source_range, source_range,
false,
) )
.await; .await;
@ -852,14 +881,13 @@ impl ExecutorContext {
let mut exec_state = exec_state; let mut exec_state = exec_state;
let exec_ctxt = exec_ctxt; let exec_ctxt = exec_ctxt;
let result = exec_ctxt let result = exec_module(
.exec_module_from_ast( &exec_ctxt,
&program, &repr,
module_id, module_id,
&module_path, &module_path,
&mut exec_state, &mut exec_state,
source_range, source_range,
false,
) )
.await; .await;
@ -875,13 +903,24 @@ impl ExecutorContext {
while let Some((module_id, _, result)) = results_rx.recv().await { while let Some((module_id, _, result)) = results_rx.recv().await {
match result { match result {
Ok((val, session_data, variables)) => { Ok(new_repr) => {
let mut repr = exec_state.global.module_infos[&module_id].take_repr(); let mut repr = exec_state.global.module_infos[&module_id].take_repr();
let ModuleRepr::Kcl(_, cache) = &mut repr else { match &mut repr {
continue; ModuleRepr::Kcl(_, cache) => {
let ModuleRepr::Kcl(_, session_data) = new_repr else {
unreachable!();
}; };
*cache = Some((val, session_data, variables)); *cache = session_data;
}
ModuleRepr::Foreign(_, cache) => {
let ModuleRepr::Foreign(_, session_data) = new_repr else {
unreachable!();
};
*cache = session_data;
}
ModuleRepr::Dummy | ModuleRepr::Root => unreachable!(),
}
exec_state.global.module_infos[&module_id].restore_repr(repr); exec_state.global.module_infos[&module_id].restore_repr(repr);
} }

View File

@ -28,6 +28,10 @@ pub enum RuntimeType {
} }
impl RuntimeType { impl RuntimeType {
pub fn edge() -> Self {
RuntimeType::Primitive(PrimitiveType::Edge)
}
pub fn sketch() -> Self { pub fn sketch() -> Self {
RuntimeType::Primitive(PrimitiveType::Sketch) RuntimeType::Primitive(PrimitiveType::Sketch)
} }
@ -2115,4 +2119,73 @@ d = cos(30)
assert_value_and_type("c", &result, 1.0, NumericType::count()); assert_value_and_type("c", &result, 1.0, NumericType::count());
assert_value_and_type("d", &result, 0.0, NumericType::count()); assert_value_and_type("d", &result, 0.0, NumericType::count());
} }
#[tokio::test(flavor = "multi_thread")]
async fn coerce_nested_array() {
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
let mixed1 = KclValue::MixedArray {
value: vec![
KclValue::Number {
value: 0.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Number {
value: 1.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::HomArray {
value: vec![
KclValue::Number {
value: 2.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Number {
value: 3.0,
ty: NumericType::count(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
},
],
meta: Vec::new(),
};
// Principal types
let tym1 = RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
ArrayLen::NonEmpty,
);
let result = KclValue::HomArray {
value: vec![
KclValue::Number {
value: 0.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Number {
value: 1.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Number {
value: 2.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Number {
value: 3.0,
ty: NumericType::count(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
};
assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
}
} }

View File

@ -124,7 +124,7 @@ pub enum ModuleRepr {
Root, Root,
// AST, memory, exported names // AST, memory, exported names
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>), Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
Foreign(PreImportedGeometry), Foreign(PreImportedGeometry, Option<KclValue>),
Dummy, Dummy,
} }

View File

@ -2579,6 +2579,28 @@ mod loop_tag {
super::execute(TEST_NAME, true).await super::execute(TEST_NAME, true).await
} }
} }
mod multiple_foreign_imports_all_render {
const TEST_NAME: &str = "multiple-foreign-imports-all-render";
/// 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
}
}
mod involute_fail { mod involute_fail {
const TEST_NAME: &str = "involute_fail"; const TEST_NAME: &str = "involute_fail";

View File

@ -659,13 +659,6 @@ impl Args {
Ok((sketches, sketch)) Ok((sketches, sketch))
} }
pub(crate) fn get_data<'a, T>(&'a self) -> Result<T, KclError>
where
T: FromArgs<'a>,
{
FromArgs::from_args(self, 0)
}
pub(crate) fn get_data_and_sketch_surface(&self) -> Result<([TyF64; 2], SketchSurface, Option<TagNode>), KclError> { pub(crate) fn get_data_and_sketch_surface(&self) -> Result<([TyF64; 2], SketchSurface, Option<TagNode>), KclError> {
FromArgs::from_args(self, 0) FromArgs::from_args(self, 0)
} }

View File

@ -559,6 +559,8 @@ clonedCube = clone(cube)
assert_eq!(cube.tags.len(), 0); assert_eq!(cube.tags.len(), 0);
assert_eq!(cloned_cube.tags.len(), 0); assert_eq!(cloned_cube.tags.len(), 0);
ctx.close().await;
} }
// Ensure the clone function returns a solid with different ids for all the internal paths and // Ensure the clone function returns a solid with different ids for all the internal paths and
@ -615,6 +617,8 @@ clonedCube = clone(cube)
assert_eq!(cube.edge_cuts.len(), 0); assert_eq!(cube.edge_cuts.len(), 0);
assert_eq!(cloned_cube.edge_cuts.len(), 0); assert_eq!(cloned_cube.edge_cuts.len(), 0);
ctx.close().await;
} }
// Ensure the clone function returns a sketch with different ids for all the internal paths and // Ensure the clone function returns a sketch with different ids for all the internal paths and
@ -668,6 +672,8 @@ clonedCube = clone(cube)
assert_eq!(tag_info.surface, None); assert_eq!(tag_info.surface, None);
assert_eq!(cloned_tag_info.surface, None); assert_eq!(cloned_tag_info.surface, None);
} }
ctx.close().await;
} }
// Ensure the clone function returns a solid with different ids for all the internal paths and // Ensure the clone function returns a solid with different ids for all the internal paths and
@ -734,6 +740,8 @@ clonedCube = clone(cube)
assert_eq!(cube.edge_cuts.len(), 0); assert_eq!(cube.edge_cuts.len(), 0);
assert_eq!(cloned_cube.edge_cuts.len(), 0); assert_eq!(cloned_cube.edge_cuts.len(), 0);
ctx.close().await;
} }
// Ensure we can get all paths even on a sketch where we closed it and it was already closed. // Ensure we can get all paths even on a sketch where we closed it and it was already closed.
@ -807,6 +815,8 @@ clonedCube = clone(cube)
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id()); assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
assert_eq!(edge_cut.tag(), cloned_edge_cut.tag()); assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
} }
ctx.close().await;
} }
// Ensure the clone function returns a solid with different ids for all the internal paths and // Ensure the clone function returns a solid with different ids for all the internal paths and
@ -905,5 +915,7 @@ clonedCube = clone(cube)
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id()); assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
assert_eq!(edge_cut.tag(), cloned_edge_cut.tag()); assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
} }
ctx.close().await;
} }
} }

View File

@ -8,15 +8,15 @@ use uuid::Uuid;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ExecState, ExtrudeSurface, KclValue, TagIdentifier}, execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
std::Args, std::Args,
}; };
/// Get the opposite edge to the edge given. /// Get the opposite edge to the edge given.
pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_opposite_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_opposite_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -53,15 +53,24 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getOppositeEdge", name = "getOppositeEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the opposite edge of." },
}
}] }]
async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<Uuid, KclError> { async fn inner_get_opposite_edge(
edge: TagIdentifier,
exec_state: &mut ExecState,
args: Args,
) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -88,9 +97,9 @@ async fn inner_get_opposite_edge(tag: TagIdentifier, exec_state: &mut ExecState,
/// Get the next adjacent edge to the edge given. /// Get the next adjacent edge to the edge given.
pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_next_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_next_adjacent_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -127,19 +136,24 @@ pub async fn get_next_adjacent_edge(exec_state: &mut ExecState, args: Args) -> R
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getNextAdjacentEdge", name = "getNextAdjacentEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the next adjacent edge of." },
}
}] }]
async fn inner_get_next_adjacent_edge( async fn inner_get_next_adjacent_edge(
tag: TagIdentifier, edge: TagIdentifier,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -167,7 +181,7 @@ async fn inner_get_next_adjacent_edge(
adjacent_edge.edge.ok_or_else(|| { adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("No edge found next adjacent to tag: `{}`", tag.value), message: format!("No edge found next adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
}) })
}) })
@ -175,9 +189,9 @@ async fn inner_get_next_adjacent_edge(
/// Get the previous adjacent edge to the edge given. /// Get the previous adjacent edge to the edge given.
pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_data()?; let input_edge = args.get_unlabeled_kw_arg_typed("edge", &RuntimeType::edge(), exec_state)?;
let edge = inner_get_previous_adjacent_edge(tag, exec_state, args.clone()).await?; let edge = inner_get_previous_adjacent_edge(input_edge, exec_state, args.clone()).await?;
Ok(KclValue::Uuid { Ok(KclValue::Uuid {
value: edge, value: edge,
meta: vec![args.source_range.into()], meta: vec![args.source_range.into()],
@ -214,19 +228,24 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "getPreviousAdjacentEdge", name = "getPreviousAdjacentEdge",
keywords = true,
unlabeled_first = true,
args = {
edge = { docs = "The tag of the edge you want to find the previous adjacent edge of." },
}
}] }]
async fn inner_get_previous_adjacent_edge( async fn inner_get_previous_adjacent_edge(
tag: TagIdentifier, edge: TagIdentifier,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Uuid, KclError> { ) -> Result<Uuid, KclError> {
if args.ctx.no_engine_commands().await { if args.ctx.no_engine_commands().await {
return Ok(exec_state.next_uuid()); return Ok(exec_state.next_uuid());
} }
let face_id = args.get_adjacent_face_to_tag(exec_state, &tag, false).await?; let face_id = args.get_adjacent_face_to_tag(exec_state, &edge, false).await?;
let id = exec_state.next_uuid(); let id = exec_state.next_uuid();
let tagged_path = args.get_tag_engine_info(exec_state, &tag)?; let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
let resp = args let resp = args
.send_modeling_cmd( .send_modeling_cmd(
@ -253,7 +272,7 @@ async fn inner_get_previous_adjacent_edge(
adjacent_edge.edge.ok_or_else(|| { adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails { KclError::Type(KclErrorDetails {
message: format!("No edge found previous adjacent to tag: `{}`", tag.value), message: format!("No edge found previous adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range], source_ranges: vec![args.source_range],
}) })
}) })

View File

@ -8,7 +8,7 @@ use anyhow::Result;
use crate::{ use crate::{
errors::KclErrorDetails, errors::KclErrorDetails,
modules::{ModulePath, ModuleRepr}, modules::{ModulePath, ModuleRepr},
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode, NodeRef, Program}, parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
walk::{Node, Visitable}, walk::{Node, Visitable},
ExecState, ExecutorContext, KclError, ModuleId, SourceRange, ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
}; };
@ -20,7 +20,7 @@ type Dependency = (String, String);
type Graph = Vec<Dependency>; type Graph = Vec<Dependency>;
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, AstNode<Program>); type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
type Universe = HashMap<String, DependencyInfo>; type Universe = HashMap<String, DependencyInfo>;
/// Process a number of programs, returning the graph of dependencies. /// Process a number of programs, returning the graph of dependencies.
@ -32,9 +32,9 @@ type Universe = HashMap<String, DependencyInfo>;
pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> { pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
let mut graph = Graph::new(); let mut graph = Graph::new();
for (name, (_, _, _, program)) in progs.iter() { for (name, (_, _, _, repr)) in progs.iter() {
graph.extend( graph.extend(
import_dependencies(program, ctx)? import_dependencies(repr, ctx)?
.into_iter() .into_iter()
.map(|(dependency, _, _)| (name.clone(), dependency)) .map(|(dependency, _, _)| (name.clone(), dependency))
.collect::<Vec<_>>(), .collect::<Vec<_>>(),
@ -118,18 +118,19 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>; type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
pub(crate) fn import_dependencies( pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> Result<ImportDependencies, KclError> {
prog: NodeRef<Program>, let ModuleRepr::Kcl(prog, _) = repr else {
ctx: &ExecutorContext, // It has no dependencies, so return an empty list.
) -> Result<ImportDependencies, KclError> { return Ok(vec![]);
let ret = Arc::new(Mutex::new(vec![])); };
let ret = Arc::new(Mutex::new(vec![]));
fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> { fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> {
if let Node::ImportStatement(is) = node { if let Node::ImportStatement(is) = node {
// We only care about Kcl imports for now. // We only care about Kcl and Foreign imports for now.
if let ImportPath::Kcl { filename } = &is.path {
let resolved_path = ModulePath::from_import_path(&is.path, &ctx.settings.project_directory); let resolved_path = ModulePath::from_import_path(&is.path, &ctx.settings.project_directory);
match &is.path {
ImportPath::Kcl { filename } => {
// We need to lock the mutex to push the dependency. // We need to lock the mutex to push the dependency.
// This is a bit of a hack, but it works for now. // This is a bit of a hack, but it works for now.
ret.lock() ret.lock()
@ -141,6 +142,19 @@ pub(crate) fn import_dependencies(
})? })?
.push((filename.to_string(), is.clone(), resolved_path)); .push((filename.to_string(), is.clone(), resolved_path));
} }
ImportPath::Foreign { path } => {
ret.lock()
.map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to lock mutex: {}", err),
source_ranges: Default::default(),
})
})?
.push((path.to_string(), is.clone(), resolved_path));
}
ImportPath::Std { .. } => { // do nothing
}
}
} }
for child in node.children().iter() { for child in node.children().iter() {
@ -164,11 +178,11 @@ pub(crate) fn import_dependencies(
pub(crate) async fn import_universe( pub(crate) async fn import_universe(
ctx: &ExecutorContext, ctx: &ExecutorContext,
prog: NodeRef<'_, Program>, repr: &ModuleRepr,
out: &mut Universe, out: &mut Universe,
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
let modules = import_dependencies(prog, ctx)?; let modules = import_dependencies(repr, ctx)?;
for (filename, import_stmt, module_path) in modules { for (filename, import_stmt, module_path) in modules {
if out.contains_key(&filename) { if out.contains_key(&filename) {
continue; continue;
@ -178,26 +192,21 @@ pub(crate) async fn import_universe(
.open_module(&import_stmt.path, &[], exec_state, Default::default()) .open_module(&import_stmt.path, &[], exec_state, Default::default())
.await?; .await?;
let program = { let repr = {
let Some(module_info) = exec_state.get_module(module_id) else { let Some(module_info) = exec_state.get_module(module_id) else {
return Err(KclError::Internal(KclErrorDetails { return Err(KclError::Internal(KclErrorDetails {
message: format!("Module {} not found", module_id), message: format!("Module {} not found", module_id),
source_ranges: vec![import_stmt.into()], source_ranges: vec![import_stmt.into()],
})); }));
}; };
let ModuleRepr::Kcl(program, _) = &module_info.repr else { module_info.repr.clone()
// if it's not a KCL module we can skip it since it has no
// dependencies.
continue;
};
program.clone()
}; };
out.insert( out.insert(
filename.clone(), filename.clone(),
(import_stmt.clone(), module_id, module_path.clone(), program.clone()), (import_stmt.clone(), module_id, module_path.clone(), repr.clone()),
); );
Box::pin(import_universe(ctx, &program, out, exec_state)).await?; Box::pin(import_universe(ctx, &repr, out, exec_state)).await?;
} }
Ok(()) Ok(())
@ -206,7 +215,7 @@ pub(crate) async fn import_universe(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::parsing::ast::types::ImportSelector; use crate::parsing::ast::types::{ImportSelector, Program};
macro_rules! kcl { macro_rules! kcl {
( $kcl:expr ) => {{ ( $kcl:expr ) => {{
@ -224,7 +233,7 @@ mod tests {
}), }),
ModuleId::default(), ModuleId::default(),
ModulePath::Local { value: "".into() }, ModulePath::Local { value: "".into() },
program, ModuleRepr::Kcl(program, None),
) )
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

View File

@ -0,0 +1,3 @@
import "../inputs/cube.step" as cube
clone(cube)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart multiple-foreign-imports-all-render.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,504 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing multiple-foreign-imports-all-render.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Foreign",
"path": "../inputs/cube.step"
},
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Kcl",
"filename": "othercube.kcl"
},
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "othercube",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"end": 0,
"path": {
"type": "Kcl",
"filename": "anothercube.kcl"
},
"selector": {
"type": "None",
"alias": {
"commentStart": 0,
"end": 0,
"name": "anothercube",
"start": 0,
"type": "Identifier"
}
},
"start": 0,
"type": "ImportStatement",
"type": "ImportStatement"
},
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"name": "model",
"start": 0,
"type": "Identifier"
},
"init": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "cube",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
},
{
"commentStart": 0,
"end": 0,
"expression": {
"body": [
{
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "othercube",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "x",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "1020",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1020.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "translate",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "color",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "\"#ff001f\"",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": "#ff001f"
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "metalness",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "50",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 50.0,
"suffix": "None"
}
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "roughness",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "50",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 50.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "appearance",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
},
{
"commentStart": 0,
"end": 0,
"expression": {
"body": [
{
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "anothercube",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name",
"type": "Name"
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "x",
"start": 0,
"type": "Identifier"
},
"arg": {
"argument": {
"commentStart": 0,
"end": 0,
"raw": "1020",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 1020.0,
"suffix": "None"
}
},
"commentStart": 0,
"end": 0,
"operator": "-",
"start": 0,
"type": "UnaryExpression",
"type": "UnaryExpression"
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "translate",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
},
{
"arguments": [
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "color",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "\"#ff0000\"",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": "#ff0000"
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "metalness",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "50",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 50.0,
"suffix": "None"
}
}
},
{
"type": "LabeledArg",
"label": {
"commentStart": 0,
"end": 0,
"name": "roughness",
"start": 0,
"type": "Identifier"
},
"arg": {
"commentStart": 0,
"end": 0,
"raw": "50",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 50.0,
"suffix": "None"
}
}
}
],
"callee": {
"abs_path": false,
"commentStart": 0,
"end": 0,
"name": {
"commentStart": 0,
"end": 0,
"name": "appearance",
"start": 0,
"type": "Identifier"
},
"path": [],
"start": 0,
"type": "Name"
},
"commentStart": 0,
"end": 0,
"start": 0,
"type": "CallExpressionKw",
"type": "CallExpressionKw",
"unlabeled": null
}
],
"commentStart": 0,
"end": 0,
"start": 0,
"type": "PipeExpression",
"type": "PipeExpression"
},
"start": 0,
"type": "ExpressionStatement",
"type": "ExpressionStatement"
}
],
"commentStart": 0,
"end": 0,
"nonCodeMeta": {
"nonCodeNodes": {
"2": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"3": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"4": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
],
"5": [
{
"commentStart": 0,
"end": 0,
"start": 0,
"type": "NonCodeNode",
"value": {
"type": "newLine"
}
}
]
},
"startNodes": []
},
"start": 0
}
}

View File

@ -0,0 +1,22 @@
import "../inputs/cube.step" as cube
import "othercube.kcl" as othercube
import "anothercube.kcl" as anothercube
model = cube
othercube
|> translate(x=1020)
|> appearance(
color = "#ff001f",
metalness = 50,
roughness = 50
)
anothercube
|> translate(x=-1020)
|> appearance(
color = "#ff0000",
metalness = 50,
roughness = 50
)

View File

@ -0,0 +1,96 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed multiple-foreign-imports-all-render.kcl
---
[
{
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "cube",
"moduleId": 6
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "othercube",
"moduleId": 7
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "cube",
"moduleId": 6
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"labeledArgs": {
"geometry": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "clone",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
},
{
"type": "GroupEnd"
},
{
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "anothercube",
"moduleId": 8
},
"sourceRange": []
},
{
"type": "GroupBegin",
"group": {
"type": "ModuleInstance",
"name": "cube",
"moduleId": 6
},
"sourceRange": []
},
{
"type": "GroupEnd"
},
{
"labeledArgs": {
"geometry": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
}
},
"name": "clone",
"sourceRange": [],
"type": "StdLibCall",
"unlabeledArg": null
},
{
"type": "GroupEnd"
}
]

View File

@ -0,0 +1,3 @@
import "../inputs/cube.step" as cube
clone(cube)

View File

@ -0,0 +1,25 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Variables in memory after executing multiple-foreign-imports-all-render.kcl
---
{
"anothercube": {
"type": "Module",
"value": 8
},
"cube": {
"type": "Module",
"value": 6
},
"model": {
"type": "ImportedGeometry",
"id": "[uuid]",
"value": [
"cube.step"
]
},
"othercube": {
"type": "Module",
"value": 7
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -0,0 +1,17 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing multiple-foreign-imports-all-render.kcl
---
import "../inputs/cube.step" as cube
import "othercube.kcl" as othercube
import "anothercube.kcl" as anothercube
model = cube
othercube
|> translate(x = 1020)
|> appearance(color = "#ff001f", metalness = 50, roughness = 50)
anothercube
|> translate(x = -1020)
|> appearance(color = "#ff0000", metalness = 50, roughness = 50)

View File

@ -0,0 +1,7 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing tests/multiple-foreign-imports-all-render/anothercube.kcl
---
import "../inputs/cube.step" as cube
clone(cube)

View File

@ -0,0 +1,7 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing tests/multiple-foreign-imports-all-render/othercube.kcl
---
import "../inputs/cube.step" as cube
clone(cube)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

59
scripts/diff.js Normal file
View File

@ -0,0 +1,59 @@
const fs = require('fs')
const latestRun = fs.readFileSync('/tmp/circular-deps.txt','utf-8')
const knownCircular = fs.readFileSync('./known-circular.txt','utf-8')
function parseLine (line) {
let num = null
let depPath = null
const res = line.split(")",2)
if (res.length === 2) {
// should be a dep line
num = parseInt(res[0])
depPath = res[1]
}
return {
num,
depPath
}
}
function makeDependencyHash (file) {
const deps = {}
file.split("\n").forEach((line)=>{
const {num, depPath} = parseLine(line)
if (depPath && !isNaN(num)) {
deps[depPath] = 1
}
})
return deps
}
const latestRunDepHash = makeDependencyHash(latestRun)
const knownDepHash = makeDependencyHash(knownCircular)
const dup1 = JSON.parse(JSON.stringify(latestRunDepHash))
const dup2 = JSON.parse(JSON.stringify(knownDepHash))
Object.keys(knownDepHash).forEach((key)=>{
delete dup1[key]
})
Object.keys(latestRunDepHash).forEach((key)=>{
delete dup2[key]
})
console.log(" ")
console.log("diff.js - line item diff")
console.log(" ")
console.log("Added(+)")
Object.keys(dup1).forEach((dep, index)=>{
console.log(`${index+1}) ${dep}`)
})
console.log(" ")
console.log("Removed(-)")
if (Object.keys(dup2).length === 0) {
console.log("None")
}
Object.keys(dup2).forEach((dep, index)=>{
console.log(`${index+1}) ${dep}`)
})

View File

@ -28,13 +28,9 @@ import { PATHS } from '@src/lib/paths'
import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot' import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot'
import { sceneInfra } from '@src/lib/singletons' import { sceneInfra } from '@src/lib/singletons'
import { maybeWriteToDisk } from '@src/lib/telemetry' import { maybeWriteToDisk } from '@src/lib/telemetry'
import type { IndexLoaderData } from '@src/lib/types' import { type IndexLoaderData } from '@src/lib/types'
import { import { engineStreamActor, useSettings, useToken } from '@src/lib/singletons'
engineStreamActor, import { commandBarActor } from '@src/lib/singletons'
useSettings,
useToken,
} from '@src/machines/appMachine'
import { commandBarActor } from '@src/machines/commandBarMachine'
import { EngineStreamTransition } from '@src/machines/engineStreamMachine' import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
import { onboardingPaths } from '@src/routes/Onboarding/paths' import { onboardingPaths } from '@src/routes/Onboarding/paths'
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton' import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'

View File

@ -1,5 +1,5 @@
import Loading from '@src/components/Loading' import Loading from '@src/components/Loading'
import { useAuthState } from '@src/machines/appMachine' import { useAuthState } from '@src/lib/singletons'
// Wrapper around protected routes, used in src/Router.tsx // Wrapper around protected routes, used in src/Router.tsx
export const Auth = ({ children }: React.PropsWithChildren) => { export const Auth = ({ children }: React.PropsWithChildren) => {

35
src/Root.tsx Normal file
View File

@ -0,0 +1,35 @@
import { AppStateProvider } from '@src/AppState'
import LspProvider from '@src/components/LspProvider'
import { MachineManagerProvider } from '@src/components/MachineManagerProvider'
import { OpenInDesktopAppHandler } from '@src/components/OpenInDesktopAppHandler'
import { SystemIOMachineLogicListenerDesktop } from '@src/components/Providers/SystemIOProviderDesktop'
import { SystemIOMachineLogicListenerWeb } from '@src/components/Providers/SystemIOProviderWeb'
import { RouteProvider } from '@src/components/RouteProvider'
import { KclContextProvider } from '@src/lang/KclProvider'
import { Outlet } from 'react-router-dom'
import { isDesktop } from '@src/lib/isDesktop'
// Root component will live for the entire applications runtime
function RootLayout() {
return (
<OpenInDesktopAppHandler>
<RouteProvider>
<LspProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
{isDesktop() ? (
<SystemIOMachineLogicListenerDesktop />
) : (
<SystemIOMachineLogicListenerWeb />
)}
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</LspProvider>
</RouteProvider>
</OpenInDesktopAppHandler>
)
}
export default RootLayout

View File

@ -9,22 +9,15 @@ import {
} from 'react-router-dom' } from 'react-router-dom'
import { App } from '@src/App' import { App } from '@src/App'
import { AppStateProvider } from '@src/AppState'
import { Auth } from '@src/Auth' import { Auth } from '@src/Auth'
import { CommandBar } from '@src/components/CommandBar/CommandBar' import { CommandBar } from '@src/components/CommandBar/CommandBar'
import DownloadAppBanner from '@src/components/DownloadAppBanner' import DownloadAppBanner from '@src/components/DownloadAppBanner'
import { ErrorPage } from '@src/components/ErrorPage' import { ErrorPage } from '@src/components/ErrorPage'
import FileMachineProvider from '@src/components/FileMachineProvider' import FileMachineProvider from '@src/components/FileMachineProvider'
import LspProvider from '@src/components/LspProvider'
import { MachineManagerProvider } from '@src/components/MachineManagerProvider'
import ModelingMachineProvider from '@src/components/ModelingMachineProvider' import ModelingMachineProvider from '@src/components/ModelingMachineProvider'
import { OpenInDesktopAppHandler } from '@src/components/OpenInDesktopAppHandler'
import { ProjectsContextProvider } from '@src/components/ProjectsContextProvider'
import { RouteProvider } from '@src/components/RouteProvider'
import { WasmErrBanner } from '@src/components/WasmErrBanner' import { WasmErrBanner } from '@src/components/WasmErrBanner'
import { NetworkContext } from '@src/hooks/useNetworkContext' import { NetworkContext } from '@src/hooks/useNetworkContext'
import { useNetworkStatus } from '@src/hooks/useNetworkStatus' import { useNetworkStatus } from '@src/hooks/useNetworkStatus'
import { KclContextProvider } from '@src/lang/KclProvider'
import { coreDump } from '@src/lang/wasm' import { coreDump } from '@src/lang/wasm'
import { import {
ASK_TO_OPEN_QUERY_PARAM, ASK_TO_OPEN_QUERY_PARAM,
@ -42,7 +35,8 @@ import {
rustContext, rustContext,
} from '@src/lib/singletons' } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { useToken } from '@src/machines/appMachine' import { useToken } from '@src/lib/singletons'
import RootLayout from '@src/Root'
import Home from '@src/routes/Home' import Home from '@src/routes/Home'
import Onboarding, { onboardingRoutes } from '@src/routes/Onboarding' import Onboarding, { onboardingRoutes } from '@src/routes/Onboarding'
import { Settings } from '@src/routes/Settings' import { Settings } from '@src/routes/Settings'
@ -54,27 +48,13 @@ const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
const router = createRouter([ const router = createRouter([
{ {
id: PATHS.INDEX, id: PATHS.INDEX,
element: ( element: <RootLayout />,
<OpenInDesktopAppHandler> // Gotcha: declaring errorElement on the root will unmount the element causing our forever React components to unmount.
<RouteProvider> // Leave errorElement on the child components, this allows for the entire react context on error pages as well.
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
</RouteProvider>
</OpenInDesktopAppHandler>
),
errorElement: <ErrorPage />,
children: [ children: [
{ {
path: PATHS.INDEX, path: PATHS.INDEX,
errorElement: <ErrorPage />,
loader: async ({ request }) => { loader: async ({ request }) => {
const onDesktop = isDesktop() const onDesktop = isDesktop()
const url = new URL(request.url) const url = new URL(request.url)
@ -95,6 +75,7 @@ const router = createRouter([
loader: fileLoader, loader: fileLoader,
id: PATHS.FILE, id: PATHS.FILE,
path: PATHS.FILE + '/:id', path: PATHS.FILE + '/:id',
errorElement: <ErrorPage />,
element: ( element: (
<Auth> <Auth>
<FileMachineProvider> <FileMachineProvider>
@ -141,6 +122,7 @@ const router = createRouter([
}, },
{ {
path: PATHS.HOME, path: PATHS.HOME,
errorElement: <ErrorPage />,
element: ( element: (
<Auth> <Auth>
<Outlet /> <Outlet />
@ -169,6 +151,7 @@ const router = createRouter([
}, },
{ {
path: PATHS.SIGN_IN, path: PATHS.SIGN_IN,
errorElement: <ErrorPage />,
element: <SignIn />, element: <SignIn />,
}, },
], ],

View File

@ -26,7 +26,7 @@ import type {
} from '@src/lib/toolbar' } from '@src/lib/toolbar'
import { isToolbarItemResolvedDropdown, toolbarConfig } from '@src/lib/toolbar' import { isToolbarItemResolvedDropdown, toolbarConfig } from '@src/lib/toolbar'
import { isArray } from '@src/lib/utils' import { isArray } from '@src/lib/utils'
import { commandBarActor } from '@src/machines/commandBarMachine' import { commandBarActor } from '@src/lib/singletons'
export function Toolbar({ export function Toolbar({
className = '', className = '',

View File

@ -40,8 +40,8 @@ import {
} from '@src/lib/singletons' } from '@src/lib/singletons'
import { err, reportRejection, trap } from '@src/lib/trap' import { err, reportRejection, trap } from '@src/lib/trap'
import { throttle, toSync } from '@src/lib/utils' import { throttle, toSync } from '@src/lib/utils'
import type { useSettings } from '@src/machines/appMachine' import type { useSettings } from '@src/lib/singletons'
import { commandBarActor } from '@src/machines/commandBarMachine' import { commandBarActor } from '@src/lib/singletons'
import type { SegmentOverlay } from '@src/machines/modelingMachine' import type { SegmentOverlay } from '@src/machines/modelingMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } { function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {

View File

@ -84,7 +84,7 @@ import { getThemeColorForThreeJs } from '@src/lib/theme'
import { err } from '@src/lib/trap' import { err } from '@src/lib/trap'
import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils' import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils'
import { getTangentPointFromPreviousArc } from '@src/lib/utils2d' import { getTangentPointFromPreviousArc } from '@src/lib/utils2d'
import { commandBarActor } from '@src/machines/commandBarMachine' import { commandBarActor } from '@src/lib/singletons'
import type { import type {
SegmentOverlay, SegmentOverlay,
SegmentOverlayPayload, SegmentOverlayPayload,

View File

@ -4,7 +4,7 @@ import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
import UserSidebarMenu from '@src/components/UserSidebarMenu' import UserSidebarMenu from '@src/components/UserSidebarMenu'
import { isDesktop } from '@src/lib/isDesktop' import { isDesktop } from '@src/lib/isDesktop'
import { type IndexLoaderData } from '@src/lib/types' import { type IndexLoaderData } from '@src/lib/types'
import { useUser } from '@src/machines/appMachine' import { useUser } from '@src/lib/singletons'
import styles from './AppHeader.module.css' import styles from './AppHeader.module.css'

View File

@ -1,7 +1,7 @@
import { Switch } from '@headlessui/react' import { Switch } from '@headlessui/react'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { settingsActor, useSettings } from '@src/machines/appMachine' import { settingsActor, useSettings } from '@src/lib/singletons'
export function CameraProjectionToggle() { export function CameraProjectionToggle() {
const settings = useSettings() const settings = useSettings()

View File

@ -8,10 +8,7 @@ import type {
CommandArgument, CommandArgument,
CommandArgumentOption, CommandArgumentOption,
} from '@src/lib/commandTypes' } from '@src/lib/commandTypes'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) => const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) =>
snapshot?.context snapshot?.context

View File

@ -11,10 +11,7 @@ import { useNetworkContext } from '@src/hooks/useNetworkContext'
import { EngineConnectionStateType } from '@src/lang/std/engineConnection' import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
import useHotkeyWrapper from '@src/lib/hotkeyWrapper' import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
import { engineCommandManager } from '@src/lib/singletons' import { engineCommandManager } from '@src/lib/singletons'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
export const COMMAND_PALETTE_HOTKEY = 'mod+k' export const COMMAND_PALETTE_HOTKEY = 'mod+k'

View File

@ -7,10 +7,7 @@ import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelec
import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput' import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput'
import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput' import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput'
import type { CommandArgument } from '@src/lib/commandTypes' import type { CommandArgument } from '@src/lib/commandTypes'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
function CommandBarArgument({ stepBack }: { stepBack: () => void }) { function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()

View File

@ -3,10 +3,7 @@ import { useEffect, useMemo, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import type { CommandArgument } from '@src/lib/commandTypes' import type { CommandArgument } from '@src/lib/commandTypes'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import type { AnyStateMachine, SnapshotFrom } from 'xstate' import type { AnyStateMachine, SnapshotFrom } from 'xstate'
// TODO: remove the need for this selector once we decouple all actors from React // TODO: remove the need for this selector once we decouple all actors from React

View File

@ -11,10 +11,7 @@ import type {
import type { Selections } from '@src/lib/selections' import type { Selections } from '@src/lib/selections'
import { getSelectionTypeDisplayText } from '@src/lib/selections' import { getSelectionTypeDisplayText } from '@src/lib/selections'
import { roundOff } from '@src/lib/utils' import { roundOff } from '@src/lib/utils'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
function CommandBarHeader({ children }: React.PropsWithChildren<object>) { function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()

View File

@ -29,11 +29,8 @@ import { err } from '@src/lib/trap'
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression' import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
import { roundOff } from '@src/lib/utils' import { roundOff } from '@src/lib/utils'
import { varMentions } from '@src/lib/varCompletionExtension' import { varMentions } from '@src/lib/varCompletionExtension'
import { useSettings } from '@src/machines/appMachine' import { useSettings } from '@src/lib/singletons'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import styles from './CommandBarKclInput.module.css' import styles from './CommandBarKclInput.module.css'

View File

@ -5,10 +5,7 @@ import { ActionButton } from '@src/components/ActionButton'
import type { CommandArgument } from '@src/lib/commandTypes' import type { CommandArgument } from '@src/lib/commandTypes'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { isArray, toSync } from '@src/lib/utils' import { isArray, toSync } from '@src/lib/utils'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import { useSelector } from '@xstate/react' import { useSelector } from '@xstate/react'
import type { AnyStateMachine, SnapshotFrom } from 'xstate' import type { AnyStateMachine, SnapshotFrom } from 'xstate'

View File

@ -1,10 +1,7 @@
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader' import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
function CommandBarReview({ stepBack }: { stepBack: () => void }) { function CommandBarReview({ stepBack }: { stepBack: () => void }) {
const commandBarState = useCommandBarState() const commandBarState = useCommandBarState()

View File

@ -12,10 +12,7 @@ import {
import { engineCommandManager, kclManager } from '@src/lib/singletons' import { engineCommandManager, kclManager } from '@src/lib/singletons'
import { reportRejection } from '@src/lib/trap' import { reportRejection } from '@src/lib/trap'
import { toSync } from '@src/lib/utils' import { toSync } from '@src/lib/utils'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
import type { modelingMachine } from '@src/machines/modelingMachine' import type { modelingMachine } from '@src/machines/modelingMachine'
const semanticEntityNames: { const semanticEntityNames: {

View File

@ -8,10 +8,7 @@ import {
getSelectionCountByType, getSelectionCountByType,
} from '@src/lib/selections' } from '@src/lib/selections'
import { kclManager } from '@src/lib/singletons' import { kclManager } from '@src/lib/singletons'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
const selectionSelector = (snapshot: any) => snapshot?.context.selectionRanges const selectionSelector = (snapshot: any) => snapshot?.context.selectionRanges

View File

@ -3,10 +3,7 @@ import { useEffect, useRef } from 'react'
import { useHotkeys } from 'react-hotkeys-hook' import { useHotkeys } from 'react-hotkeys-hook'
import type { CommandArgument } from '@src/lib/commandTypes' import type { CommandArgument } from '@src/lib/commandTypes'
import { import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
commandBarActor,
useCommandBarState,
} from '@src/machines/commandBarMachine'
function CommandBarTextareaInput({ function CommandBarTextareaInput({
arg, arg,

View File

@ -1,7 +1,7 @@
import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar' import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar'
import usePlatform from '@src/hooks/usePlatform' import usePlatform from '@src/hooks/usePlatform'
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper' import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
import { commandBarActor } from '@src/machines/commandBarMachine' import { commandBarActor } from '@src/lib/singletons'
import { CustomIcon } from '@src/components/CustomIcon' import { CustomIcon } from '@src/components/CustomIcon'
export function CommandBarOpenButton() { export function CommandBarOpenButton() {

View File

@ -6,7 +6,7 @@ import { CustomIcon } from '@src/components/CustomIcon'
import type { Command } from '@src/lib/commandTypes' import type { Command } from '@src/lib/commandTypes'
import { sortCommands } from '@src/lib/commandUtils' import { sortCommands } from '@src/lib/commandUtils'
import { getActorNextEvents } from '@src/lib/utils' import { getActorNextEvents } from '@src/lib/utils'
import { commandBarActor } from '@src/machines/commandBarMachine' import { commandBarActor } from '@src/lib/singletons'
function CommandComboBox({ function CommandComboBox({
options, options,

View File

@ -3,7 +3,7 @@ import { useState } from 'react'
import { ActionButton } from '@src/components/ActionButton' import { ActionButton } from '@src/components/ActionButton'
import { CREATE_FILE_URL_PARAM } from '@src/lib/constants' import { CREATE_FILE_URL_PARAM } from '@src/lib/constants'
import { useSettings } from '@src/machines/appMachine' import { useSettings } from '@src/lib/singletons'
import { useSearchParams } from 'react-router-dom' import { useSearchParams } from 'react-router-dom'
const DownloadAppBanner = () => { const DownloadAppBanner = () => {

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