merge main
11
.github/workflows/e2e-tests.yml
vendored
@ -289,17 +289,6 @@ jobs:
|
||||
- windows-latest-8-cores
|
||||
shardIndex: [1, 2, 3, 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 }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
1
.husky/pre-commit
Executable file
@ -0,0 +1 @@
|
||||
npm run fmt
|
||||
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run fmt-check
|
||||
@ -9,7 +9,7 @@ Get the next adjacent edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
getNextAdjacentEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| 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
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getOppositeEdge(tag: TagIdentifier): Uuid
|
||||
getOppositeEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getOppositeEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| 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
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| 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
|
||||
|
||||
|
||||
@ -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
|
||||
to other modules.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
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.
|
||||
|
||||
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 if‑else.
|
||||
|
||||
Multiple functions can be exported in a file.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Functions vs `clone`
|
||||
|
||||
There are two common patterns for re‑using geometry:
|
||||
|
||||
1. **Wrap the construction in a function** – flexible and fully parametric.
|
||||
2. **Duplicate an existing object with `clone`** – lightning‑fast, 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 per‑instance, you’re 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.
|
||||
|
||||
---
|
||||
|
||||
## Module‑level parallelism
|
||||
|
||||
Under the hood, the Design Studio runs **every module in parallel** where it can. This means:
|
||||
|
||||
- The top‑level 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.
|
||||
- CPU‑bound calculations in separate modules get their own worker threads.
|
||||
|
||||
### Why modules beat one‑big‑file
|
||||
|
||||
If you shoe‑horn 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 // non‑blocking
|
||||
|
||||
// ... 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 byte‑code. 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 – you’ve 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
|
||||
|
||||
`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.
|
||||
```
|
||||
|
||||
```norun
|
||||
import "tests/inputs/cube-2.sldprt" as cube
|
||||
```kcl
|
||||
import "tests/inputs/cube.sldprt" as cube
|
||||
|
||||
// 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
|
||||
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)
|
||||
import "tests/inputs/cube.obj"
|
||||
```
|
||||
@ -110,97 +307,55 @@ Coordinate systems:
|
||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
||||
|
||||
### Performance
|
||||
---
|
||||
|
||||
Parallelized foreign-file imports now let you overlap file reads, initialization,
|
||||
## Performance deep‑dive for foreign‑file imports
|
||||
|
||||
Parallelized foreign‑file imports now let you overlap file reads, initialization,
|
||||
and rendering. To maximize throughput, you need to understand the three distinct
|
||||
stages—reading, initializing (background render start), and invocation (blocking)
|
||||
—and structure your code to defer blocking operations until the end.
|
||||
|
||||
#### Foreign Import Execution Stages
|
||||
### Foreign import execution stages
|
||||
|
||||
1. **Import (Read) Stage**
|
||||
```norun
|
||||
1. **Import (Read / Initialization) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
```
|
||||
- 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 kick‑starts the render pipeline while you keep executing other code.
|
||||
|
||||
2. **Initialization (Background Render) Stage**
|
||||
```norun
|
||||
2. **Invocation (Blocking) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
myCube = cube // <- This line starts background rendering
|
||||
```
|
||||
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
|
||||
- This kick‑starts the render pipeline but doesn’t 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
|
||||
cube
|
||||
|> translate(z=10) // ← blocks here only
|
||||
```
|
||||
- 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
|
||||
Initialize early but delay all transformations until after your heavy computation:
|
||||
```norun
|
||||
import "tests/inputs/cube.step" as cube // 1) Read
|
||||
|
||||
myCube = cube // 2) Background render starts
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube // 1) Read / 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
|
||||
|> 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 IO‑heavy work into its own module so it can render in parallel while `main.kcl` continues.
|
||||
|
||||
#### Future improvements
|
||||
|
||||
Upcoming releases will auto‑analyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
|
||||
|
||||
Upcoming releases will auto‑analyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.
|
||||
|
||||
|
||||
@ -111819,10 +111819,10 @@
|
||||
"summary": "Get the next adjacent edge to the edge given.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"keywordArguments": false,
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "tag",
|
||||
"name": "edge",
|
||||
"type": "TagIdentifier",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -111839,7 +111839,8 @@
|
||||
},
|
||||
"required": true,
|
||||
"includeInSnippet": true,
|
||||
"labelRequired": true
|
||||
"description": "The tag of the edge you want to find the next adjacent edge of.",
|
||||
"labelRequired": false
|
||||
}
|
||||
],
|
||||
"returnValue": {
|
||||
@ -111866,10 +111867,10 @@
|
||||
"summary": "Get the opposite edge to the edge given.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"keywordArguments": false,
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "tag",
|
||||
"name": "edge",
|
||||
"type": "TagIdentifier",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -111886,7 +111887,8 @@
|
||||
},
|
||||
"required": true,
|
||||
"includeInSnippet": true,
|
||||
"labelRequired": true
|
||||
"description": "The tag of the edge you want to find the opposite edge of.",
|
||||
"labelRequired": false
|
||||
}
|
||||
],
|
||||
"returnValue": {
|
||||
@ -111913,10 +111915,10 @@
|
||||
"summary": "Get the previous adjacent edge to the edge given.",
|
||||
"description": "",
|
||||
"tags": [],
|
||||
"keywordArguments": false,
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "tag",
|
||||
"name": "edge",
|
||||
"type": "TagIdentifier",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -111933,7 +111935,8 @@
|
||||
},
|
||||
"required": true,
|
||||
"includeInSnippet": true,
|
||||
"labelRequired": true
|
||||
"description": "The tag of the edge you want to find the previous adjacent edge of.",
|
||||
"labelRequired": false
|
||||
}
|
||||
],
|
||||
"returnValue": {
|
||||
|
||||
@ -13,12 +13,22 @@ test.describe('Authentication tests', () => {
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
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 toolbar.userSidebarButton.click()
|
||||
await toolbar.signOutButton.click()
|
||||
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 signInPage.signInButton.click()
|
||||
await expect(signInPage.userCode).toBeVisible()
|
||||
@ -30,6 +40,7 @@ test.describe('Authentication tests', () => {
|
||||
await expect(signInPage.userCode).toBeVisible()
|
||||
const secondUserCode = await signInPage.userCode.textContent()
|
||||
expect(secondUserCode).not.toEqual(firstUserCode)
|
||||
await signInPage.cancelSignInButton.click()
|
||||
})
|
||||
|
||||
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
|
||||
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()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -155,7 +155,7 @@ async function doBasicSketch(
|
||||
|> xLine(length = -segLen(seg01))`)
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Basic sketch', () => {
|
||||
test('code pane open at start', async ({ page, homePage }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await doBasicSketch(page, homePage, ['code'])
|
||||
|
||||
@ -8,10 +8,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
import { getUtils } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe(
|
||||
'Can create sketches on all planes and their back sides',
|
||||
{ tag: ['@skipWin'] },
|
||||
() => {
|
||||
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
const sketchOnPlaneAndBackSideTest = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
@ -133,5 +130,4 @@ test.describe(
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
@ -179,10 +179,10 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||
})
|
||||
|
||||
test(
|
||||
'Command bar keybinding works from code editor and can change a setting',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
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 expect(
|
||||
page.getByRole('option', { name: 'system' })
|
||||
).toHaveAttribute('data-headlessui-state', 'active')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
@ -228,8 +229,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({
|
||||
page,
|
||||
|
||||
@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||
{ tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
|
||||
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
@ -989,10 +989,11 @@ sketch001 = startSketchOn(XZ)
|
||||
|> close()`)
|
||||
})
|
||||
|
||||
test(
|
||||
'Can undo a sketch modification with ctrl+z',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, editor }) => {
|
||||
test('Can undo a sketch modification with ctrl+z', async ({
|
||||
page,
|
||||
homePage,
|
||||
editor,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -1143,8 +1144,7 @@ sketch001 = startSketchOn(XZ)
|
||||
|> extrude(length = 5)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
`Can import a local OBJ 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 homePage.expectState({
|
||||
@ -73,7 +73,7 @@ test.describe('integrations tests', () => {
|
||||
})
|
||||
await test.step('setup for next assertion', async () => {
|
||||
await toolbar.openFile('main.kcl')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
await clickObj()
|
||||
await page.waitForTimeout(1000)
|
||||
await scene.moveNoWhere()
|
||||
|
||||
@ -174,6 +174,13 @@ export class ToolbarFixture {
|
||||
openFile = async (fileName: string) => {
|
||||
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 () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down rectangles:' })
|
||||
|
||||
@ -41,7 +41,7 @@ class MyAPIReporter implements Reporter {
|
||||
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
|
||||
id: test.id, // computed file/test/project ID used for reruns
|
||||
retry: result.retry,
|
||||
tags: test.tags, // e.g. '@snapshot' or '@skipWin'
|
||||
tags: test.tags, // e.g. '@snapshot' or '@skipLocalEngine'
|
||||
// Extra environment variables
|
||||
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
|
||||
CI_PR_NUMBER: process.env.CI_PR_NUMBER || null,
|
||||
|
||||
@ -6,7 +6,10 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
* Not all menu actions are tested. Some are default electron menu actions.
|
||||
* 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.describe('Home page', () => {
|
||||
test.describe('File role', () => {
|
||||
@ -161,7 +164,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
const defaultUnit = settings.locator('#defaultUnit')
|
||||
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()
|
||||
// Run electron snippet to find the Menu!
|
||||
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('Home.Help.Show all commands', async ({ tronApp, cmdBar, page }) => {
|
||||
test('Home.Help.Show all commands', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
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')
|
||||
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()
|
||||
// Run electron snippet to find the Menu!
|
||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||
@ -435,7 +450,11 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
)
|
||||
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()
|
||||
// Run electron snippet to find the Menu!
|
||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||
@ -2354,4 +2373,5 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@ -6,6 +6,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
import {
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
kclSamplesPath,
|
||||
testsInputPath,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@ -18,7 +18,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test(
|
||||
'projects reload if a new one is created, deleted, or renamed externally',
|
||||
{ tag: '@electron' },
|
||||
{ tag: ['@electron', '@macos', '@windows'] },
|
||||
async ({ context, page }, testInfo) => {
|
||||
let externalCreatedProjectName = 'external-created-project'
|
||||
|
||||
|
||||
@ -29,7 +29,7 @@ sketch003 = startSketchOn(XY)
|
||||
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', () => {
|
||||
const cases = [
|
||||
{
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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
|
||||
test('bad model has inline error #3251', async ({
|
||||
context,
|
||||
@ -239,10 +239,11 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
await expect(zooLogo).not.toHaveAttribute('href')
|
||||
})
|
||||
|
||||
test(
|
||||
'Position _ Is Out Of Range... regression test',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, homePage }) => {
|
||||
test('Position _ Is Out Of Range... regression test', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
@ -315,8 +316,7 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
'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.
|
||||
test(
|
||||
'ensure you CAN export while an export is already going',
|
||||
{ tag: ['@skipLinux', '@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
test('ensure you CAN export while an export is already going', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step('Set up the code and durations', async () => {
|
||||
await page.addInitScript(
|
||||
@ -560,8 +560,7 @@ extrude002 = extrude(profile002, length = 150)
|
||||
|
||||
await expect(successToastMessage).toHaveCount(2)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 ({
|
||||
page,
|
||||
context,
|
||||
@ -393,10 +393,13 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
|> close()
|
||||
`)
|
||||
}
|
||||
test(
|
||||
'code pane open at start-handles',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, scene, toolbar, cmdBar }) => {
|
||||
test('code pane open at start-handles', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -417,13 +420,15 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
toolbar,
|
||||
cmdBar
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
'code pane closed at start-handles',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, scene, toolbar, cmdBar }) => {
|
||||
test('code pane closed at start-handles', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// Load the app with the code panes
|
||||
await page.addInitScript(async (persistModelingContext) => {
|
||||
localStorage.setItem(
|
||||
@ -439,8 +444,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
||||
toolbar,
|
||||
cmdBar
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
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(
|
||||
`test it removes half-finished expressions when changing tools in sketch mode`,
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => {
|
||||
test(`test it removes half-finished expressions when changing tools in sketch mode`, async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
// We seed the scene with a single offset plane
|
||||
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 toolbar.lineBtn.click()
|
||||
await editor.expectEditor.not.toContain(
|
||||
'profile003 = circleThreePoint('
|
||||
)
|
||||
await editor.expectEditor.not.toContain('profile003 = circleThreePoint(')
|
||||
})
|
||||
|
||||
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.toContain('profile002')
|
||||
})
|
||||
}
|
||||
)
|
||||
test(
|
||||
`snapToProfile start only works for current profile`,
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, scene, toolbar, editor, homePage, cmdBar }) => {
|
||||
})
|
||||
test(`snapToProfile start only works for current profile`, async ({
|
||||
context,
|
||||
page,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// We seed the scene with a single offset plane
|
||||
await context.addInitScript(() => {
|
||||
localStorage.setItem(
|
||||
@ -1564,7 +1575,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
|
||||
const codeFromTangentialArc = ` |> tangentialArc(endAbsolute = [39.49, 88.22])`
|
||||
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 endOfLowerSegMove()
|
||||
await page.waitForTimeout(1000)
|
||||
@ -1604,8 +1615,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
`[profileStartX(%), profileStartY(%)]`
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
test('can enter sketch mode for sketch with no profiles', async ({
|
||||
scene,
|
||||
toolbar,
|
||||
@ -1777,7 +1787,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
|
||||
await endLineStartTanArc()
|
||||
await editor.expectEditor.toContain(`|> line(end = [9.02, -0.55])`)
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await page.waitForTimeout(300)
|
||||
await page.mouse.click(745, 359)
|
||||
await page.waitForTimeout(300)
|
||||
@ -2071,10 +2081,13 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
'Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page }) => {
|
||||
test('Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile', async ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2204,12 +2217,15 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
|
||||
|> 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',
|
||||
{ tag: ['@skipWin', '@skipLinux'] },
|
||||
async ({ scene, toolbar, editor, cmdBar, page, homePage }) => {
|
||||
})
|
||||
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 ({
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
cmdBar,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2303,13 +2319,15 @@ profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
||||
await editor.expectEditor.not.toContain('length001 = 7')
|
||||
await sketchIsDrawnProperly()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
'can enter sketch when there is an extrude',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, page, cmdBar }) => {
|
||||
test('can enter sketch when there is an extrude', async ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
page,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2361,8 +2379,7 @@ extrude001 = extrude(profile003, length = 5)
|
||||
scene.expectPixelColor(TEST_COLORS.WHITE, { x: 763, y: 214 }, 15),
|
||||
])
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
test('exit new sketch without drawing anything should not be a problem', async ({
|
||||
homePage,
|
||||
scene,
|
||||
@ -2416,10 +2433,13 @@ extrude001 = extrude(profile003, length = 5)
|
||||
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
||||
})
|
||||
})
|
||||
test(
|
||||
'A sketch with only "startProfileAt" and no segments should still be able to be continued',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page }) => {
|
||||
test('A sketch with only "startProfileAt" and no segments should still be able to be continued', async ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2457,12 +2477,14 @@ profile002 = startProfileAt([85.81, 52.55], sketch002)
|
||||
await page.waitForTimeout(100)
|
||||
await nextPoint()
|
||||
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',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page }) => {
|
||||
})
|
||||
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 ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2517,12 +2539,15 @@ extrude001 = extrude(thePart, length = 75)
|
||||
await profilePoint2()
|
||||
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',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page, cmdBar }) => {
|
||||
})
|
||||
test('Can enter sketch on sketch of wall and cap for segment, solid2d, extrude-wall, extrude-cap selections', async ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
page,
|
||||
cmdBar,
|
||||
}) => {
|
||||
// TODO this test should include a test for selecting revolve walls and caps
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
@ -2681,12 +2706,14 @@ extrude003 = extrude(profile011, length = 2.5)
|
||||
})
|
||||
}
|
||||
}) */
|
||||
}
|
||||
)
|
||||
test(
|
||||
'Can enter sketch loft edges, base and continue sketch',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ homePage, scene, toolbar, editor, page }) => {
|
||||
})
|
||||
test('Can enter sketch loft edges, base and continue sketch', async ({
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
editor,
|
||||
page,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -2736,8 +2763,7 @@ loft([profile001, profile002])
|
||||
await editor.expectEditor.toContain(
|
||||
`angledLine(angle = 0, length = 113.01, tag = $rectangleSegmentA001)`
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
test('Can enter sketch loft edges offsetPlane and continue sketch', async ({
|
||||
scene,
|
||||
toolbar,
|
||||
@ -2970,7 +2996,7 @@ test.describe('Redirecting to home page and back to the original file should cle
|
||||
await click00r(200, -200)
|
||||
|
||||
// Draw arc
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await click00r(0, 0)
|
||||
await click00r(100, 100)
|
||||
|
||||
@ -3245,10 +3271,15 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
|
||||
).toBeVisible()
|
||||
})
|
||||
})
|
||||
test(
|
||||
'adding a syntax error, recovers after fixing',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, context, scene, editor, toolbar, cmdBar }) => {
|
||||
test('adding a syntax error, recovers after fixing', async ({
|
||||
page,
|
||||
homePage,
|
||||
context,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const file = await fs.readFile(
|
||||
path.resolve(
|
||||
__dirname,
|
||||
@ -3335,6 +3366,5 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
|
||||
15
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@ -47,7 +47,7 @@ test.setTimeout(60_000)
|
||||
// up with another PR if we want this back.
|
||||
test(
|
||||
'exports of each format should work',
|
||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||
{ tag: ['@snapshot'] },
|
||||
async ({ page, context, scene, cmdBar, tronApp }) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
@ -464,9 +464,7 @@ test(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
|
||||
// click on the end of the profile to continue it
|
||||
await page.waitForTimeout(500)
|
||||
@ -621,7 +619,7 @@ test.describe(
|
||||
'Client side scene scale should match engine scale',
|
||||
{ tag: '@snapshot' },
|
||||
() => {
|
||||
test('Inch scale', async ({ page, cmdBar, scene }) => {
|
||||
test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -655,9 +653,7 @@ test.describe(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
@ -671,9 +667,8 @@ test.describe(
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
// click tangential arc tool again to unequip it
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
// it will be available directly in the toolbar since it was last equipped
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// 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(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
@ -749,9 +750,7 @@ test.describe(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
@ -764,9 +763,7 @@ test.describe(
|
||||
|> tangentialArc(endAbsolute = [551.2, -62.01])`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
|
||||
@ -8,7 +8,12 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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(
|
||||
'simulate network down and network little widget',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
@ -184,7 +189,9 @@ test.describe('Test network and connection issues', () => {
|
||||
await toolbar.editSketch()
|
||||
|
||||
// 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)
|
||||
|
||||
@ -254,4 +261,5 @@ profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
).not.toBeVisible()
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@ -1024,6 +1024,10 @@ export function testsInputPath(fileName: string): string {
|
||||
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(
|
||||
page: Page,
|
||||
fn: () => Promise<unknown>,
|
||||
|
||||
@ -4,7 +4,7 @@ import { uuidv4 } from '@src/lib/utils'
|
||||
import { getUtils, orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
||||
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 ({
|
||||
page,
|
||||
context,
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 }) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
||||
@ -4,7 +4,7 @@ import { TEST_CODE_GIZMO } from '@e2e/playwright/storageStates'
|
||||
import { getUtils } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Testing Gizmo', () => {
|
||||
const cases = [
|
||||
{
|
||||
testDescription: 'top view',
|
||||
|
||||
@ -11,7 +11,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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', () => {
|
||||
// TODO: fix this test on mac after the electron migration
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Testing selections', () => {
|
||||
test.setTimeout(90_000)
|
||||
test('Selections work on fresh and edited sketch', async ({
|
||||
page,
|
||||
@ -39,12 +39,12 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
const emptySpaceHover = () =>
|
||||
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()
|
||||
})
|
||||
const emptySpaceClick = () =>
|
||||
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(
|
||||
/cm-activeLine/
|
||||
)
|
||||
|
||||
@ -26,7 +26,12 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 ({
|
||||
page,
|
||||
homePage,
|
||||
@ -93,11 +98,14 @@ test.describe('Testing settings', () => {
|
||||
/** Test to close https://github.com/KittyCAD/modeling-app/issues/2713 */
|
||||
await test.step(`Confirm that this dialog has a solid background`, async () => {
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]), {
|
||||
.poll(
|
||||
() => u.getGreatestPixDiff({ x: 600, y: 250 }, [28, 28, 28]),
|
||||
{
|
||||
timeout: 1000,
|
||||
message:
|
||||
'Checking for solid background, should not see default plane colors',
|
||||
})
|
||||
}
|
||||
)
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
@ -130,7 +138,9 @@ test.describe('Testing settings', () => {
|
||||
|
||||
// Roll back to default of "off"
|
||||
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()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
@ -180,7 +190,10 @@ test.describe('Testing settings', () => {
|
||||
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)
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
@ -268,7 +281,7 @@ test.describe('Testing settings', () => {
|
||||
|
||||
test(
|
||||
`Project settings override user settings on desktop`,
|
||||
{ tag: ['@electron', '@skipWin'] },
|
||||
{ tag: ['@electron'] },
|
||||
async ({ context, page }, testInfo) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const projectName = 'bracket'
|
||||
@ -291,13 +304,18 @@ test.describe('Testing settings', () => {
|
||||
projectName,
|
||||
PROJECT_SETTINGS_FILE_NAME
|
||||
)
|
||||
const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
|
||||
const tempUserSettingsFilePath = join(
|
||||
projectDirName,
|
||||
SETTINGS_FILE_NAME
|
||||
)
|
||||
const userThemeColor = '120'
|
||||
const projectThemeColor = '50'
|
||||
const settingsOpenButton = page.getByRole('link', {
|
||||
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 userSettingsTab = page.getByRole('radio', { name: 'User' })
|
||||
const settingsCloseButton = page.getByTestId('settings-close-button')
|
||||
@ -532,7 +550,10 @@ test.describe('Testing settings', () => {
|
||||
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(
|
||||
executorInputPath('cylinder.kcl'),
|
||||
'utf8'
|
||||
@ -586,7 +607,9 @@ test.describe('Testing settings', () => {
|
||||
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
const toastMessage = page.getByText(`Successfully created "testDefault"`)
|
||||
const toastMessage = page.getByText(
|
||||
`Successfully created "testDefault"`
|
||||
)
|
||||
await expect(toastMessage).not.toBeVisible()
|
||||
await page
|
||||
.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -620,7 +643,9 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
|
||||
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 page
|
||||
.getByTestId('modeling-defaultUnit')
|
||||
@ -674,7 +699,9 @@ test.describe('Testing settings', () => {
|
||||
|
||||
await test.step('Change modeling default unit within command bar', async () => {
|
||||
const commands = page.getByRole('button', { name: 'Commands' })
|
||||
const changeUnitOfMeasureInCommandBar = async (unitOfMeasure: string) => {
|
||||
const changeUnitOfMeasureInCommandBar = async (
|
||||
unitOfMeasure: string
|
||||
) => {
|
||||
// Open command bar
|
||||
await commands.click()
|
||||
const settingsModelingDefaultUnitCommand = page.getByText(
|
||||
@ -885,7 +912,10 @@ test.describe('Testing settings', () => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem('persistModelingContext', '{"openPanes":["debug"]}')
|
||||
localStorage.setItem(
|
||||
'persistModelingContext',
|
||||
'{"openPanes":["debug"]}'
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
@ -1057,4 +1087,5 @@ fn cube`
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
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 }) => {
|
||||
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
|
||||
test(
|
||||
'can do many at once and get many prompts back, and interact with many',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
test('can do many at once and get many prompts back, and interact with many', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
// Let this test run longer since we've seen it timeout.
|
||||
test.setTimeout(180_000)
|
||||
@ -521,8 +521,7 @@ test.describe('Text-to-CAD tests', { tag: ['@skipWin'] }, () => {
|
||||
// Expect the code to be pasted.
|
||||
const code2x4 = await page.locator('.cm-content').innerText()
|
||||
await expect(code2x4.length).toBeGreaterThan(249)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||
page,
|
||||
|
||||
@ -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 ({
|
||||
page,
|
||||
homePage,
|
||||
toolbar,
|
||||
}) => {
|
||||
// Wait for the app to be ready for use
|
||||
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.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
|
||||
// focus is on the canvas
|
||||
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.click(800, 300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(lineButton).toBeVisible()
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await expect(toolbar.lineBtn).toBeVisible()
|
||||
await expect(toolbar.lineBtn).toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Draw a line
|
||||
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')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
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
|
||||
await page.keyboard.press('a')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
|
||||
await toolbar.selectTangentialArc()
|
||||
|
||||
// click in the same position again to continue the profile
|
||||
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.click(1000, 100)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(toolbar.tangentialArcBtn).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'false'
|
||||
)
|
||||
await expect
|
||||
.poll(async () => {
|
||||
await page.keyboard.press('l')
|
||||
return lineButton.getAttribute('aria-pressed')
|
||||
return toolbar.lineBtn.getAttribute('aria-pressed')
|
||||
})
|
||||
.toBe('true')
|
||||
|
||||
@ -251,8 +245,11 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
|
||||
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(lineButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(toolbar.lineBtn).toHaveAttribute('aria-pressed', 'false')
|
||||
await expect(toolbar.tangentialArcBtn).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'false'
|
||||
)
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
await expect(page.getByRole('button', { name: 'Exit Sketch' })).toBeVisible()
|
||||
// Exit sketch
|
||||
|
||||
1
interface.d.ts
vendored
@ -20,6 +20,7 @@ export interface IElectronAPI {
|
||||
open: typeof dialog.showOpenDialog
|
||||
save: typeof dialog.showSaveDialog
|
||||
openExternal: typeof shell.openExternal
|
||||
openInNewWindow: (name: string) => void
|
||||
takeElectronWindowScreenshot: ({
|
||||
width,
|
||||
height,
|
||||
|
||||
@ -3,14 +3,13 @@
|
||||
> dpdm --no-warning --no-tree -T --skip-dynamic-imports=circular src/index.tsx
|
||||
|
||||
• Circular Dependencies
|
||||
01) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts
|
||||
02) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
|
||||
03) 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
|
||||
05) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/engineStreamMachine.ts
|
||||
06) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts -> src/machines/appMachine.ts -> src/machines/settingsMachine.ts
|
||||
07) src/machines/appMachine.ts -> src/machines/settingsMachine.ts -> src/machines/commandBarMachine.ts -> src/lib/commandBarConfigs/authCommandConfig.ts
|
||||
08) src/lib/singletons.ts -> src/lang/codeManager.ts
|
||||
09) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
|
||||
10) src/hooks/useModelingContext.ts -> src/components/ModelingMachineProvider.tsx -> src/components/Toolbar/Intersect.tsx -> src/components/SetHorVertDistanceModal.tsx -> src/lib/useCalculateKclExpression.ts
|
||||
11) src/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
|
||||
1) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/modifyAst/addEdgeTreatment.ts
|
||||
2) src/lang/std/sketch.ts -> src/lang/modifyAst.ts
|
||||
3) src/lang/std/sketch.ts -> src/lang/modifyAst.ts -> src/lang/std/sketchcombos.ts
|
||||
4) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
|
||||
5) src/lib/singletons.ts -> src/editor/manager.ts -> src/lib/selections.ts
|
||||
6) src/lib/singletons.ts -> src/lang/codeManager.ts
|
||||
7) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.ts -> src/components/Toolbar/angleLengthInfo.ts
|
||||
8) src/lib/singletons.ts -> src/clientSideScene/sceneEntities.ts -> src/clientSideScene/segments.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/routes/Onboarding/index.tsx -> src/routes/Onboarding/Camera.tsx -> src/routes/Onboarding/utils.tsx
|
||||
|
||||
22
package.json
@ -76,6 +76,7 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"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: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",
|
||||
@ -114,6 +115,7 @@
|
||||
"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: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-notes": "./scripts/set-files-notes.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:samples-manifest": "cd public/kcl-samples && node generate-manifest.js",
|
||||
"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: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",
|
||||
@ -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:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
|
||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
|
||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@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: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:macos:local": "npm run tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@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: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": "playwright test --config=playwright.electron.config.ts --grep-invert=@snapshot",
|
||||
"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=@macos --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: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=@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=@snapshot|@skipLocalEngine",
|
||||
"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"
|
||||
},
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -1076,7 +1076,7 @@ mod test {
|
||||
|
||||
#[for_each_std_mod]
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_examples() {
|
||||
async fn kcl_test_examples() {
|
||||
let std = walk_prelude();
|
||||
let mut errs = Vec::new();
|
||||
for d in std {
|
||||
|
||||
@ -459,7 +459,7 @@ impl ExecutorContext {
|
||||
exec_state.add_path_to_source_id(resolved_path.clone(), id);
|
||||
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?;
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom));
|
||||
exec_state.add_module(id, resolved_path, ModuleRepr::Foreign(geom, None));
|
||||
Ok(id)
|
||||
}
|
||||
ImportPath::Std { .. } => {
|
||||
@ -501,7 +501,7 @@ impl ExecutorContext {
|
||||
*cache = Some((val, er, items.clone()));
|
||||
(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(),
|
||||
source_ranges: vec![geom.source_range],
|
||||
})),
|
||||
@ -546,9 +546,20 @@ impl ExecutorContext {
|
||||
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
|
||||
.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!(),
|
||||
};
|
||||
|
||||
|
||||
@ -752,7 +752,12 @@ impl ExecutorContext {
|
||||
let mut universe = std::collections::HashMap::new();
|
||||
|
||||
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
|
||||
.map_err(|err| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
@ -799,16 +804,12 @@ impl ExecutorContext {
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
let (results_tx, mut results_rx): (
|
||||
tokio::sync::mpsc::Sender<(
|
||||
ModuleId,
|
||||
ModulePath,
|
||||
Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError>,
|
||||
)>,
|
||||
tokio::sync::mpsc::Sender<(ModuleId, ModulePath, Result<ModuleRepr, KclError>)>,
|
||||
tokio::sync::mpsc::Receiver<_>,
|
||||
) = tokio::sync::mpsc::channel(1);
|
||||
|
||||
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 {
|
||||
message: format!("Module {module} not found in universe"),
|
||||
source_ranges: Default::default(),
|
||||
@ -816,12 +817,41 @@ impl ExecutorContext {
|
||||
};
|
||||
let module_id = *module_id;
|
||||
let module_path = module_path.clone();
|
||||
let program = program.clone();
|
||||
let repr = repr.clone();
|
||||
let exec_state = exec_state.clone();
|
||||
let exec_ctxt = self.clone();
|
||||
let results_tx = results_tx.clone();
|
||||
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")]
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
@ -829,14 +859,13 @@ impl ExecutorContext {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_ctxt
|
||||
.exec_module_from_ast(
|
||||
&program,
|
||||
let result = exec_module(
|
||||
&exec_ctxt,
|
||||
&repr,
|
||||
module_id,
|
||||
&module_path,
|
||||
&mut exec_state,
|
||||
source_range,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -852,14 +881,13 @@ impl ExecutorContext {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
let result = exec_ctxt
|
||||
.exec_module_from_ast(
|
||||
&program,
|
||||
let result = exec_module(
|
||||
&exec_ctxt,
|
||||
&repr,
|
||||
module_id,
|
||||
&module_path,
|
||||
&mut exec_state,
|
||||
source_range,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
@ -875,13 +903,24 @@ impl ExecutorContext {
|
||||
|
||||
while let Some((module_id, _, result)) = results_rx.recv().await {
|
||||
match result {
|
||||
Ok((val, session_data, variables)) => {
|
||||
Ok(new_repr) => {
|
||||
let mut repr = exec_state.global.module_infos[&module_id].take_repr();
|
||||
|
||||
let ModuleRepr::Kcl(_, cache) = &mut repr else {
|
||||
continue;
|
||||
match &mut repr {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -28,6 +28,10 @@ pub enum RuntimeType {
|
||||
}
|
||||
|
||||
impl RuntimeType {
|
||||
pub fn edge() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Edge)
|
||||
}
|
||||
|
||||
pub fn sketch() -> Self {
|
||||
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("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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -124,7 +124,7 @@ pub enum ModuleRepr {
|
||||
Root,
|
||||
// AST, memory, exported names
|
||||
Kcl(Node<Program>, Option<(Option<KclValue>, EnvironmentRef, Vec<String>)>),
|
||||
Foreign(PreImportedGeometry),
|
||||
Foreign(PreImportedGeometry, Option<KclValue>),
|
||||
Dummy,
|
||||
}
|
||||
|
||||
|
||||
@ -2579,6 +2579,28 @@ mod loop_tag {
|
||||
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 {
|
||||
const TEST_NAME: &str = "involute_fail";
|
||||
|
||||
|
||||
@ -659,13 +659,6 @@ impl Args {
|
||||
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> {
|
||||
FromArgs::from_args(self, 0)
|
||||
}
|
||||
|
||||
@ -559,6 +559,8 @@ clonedCube = clone(cube)
|
||||
|
||||
assert_eq!(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
|
||||
@ -615,6 +617,8 @@ clonedCube = clone(cube)
|
||||
|
||||
assert_eq!(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
|
||||
@ -668,6 +672,8 @@ clonedCube = clone(cube)
|
||||
assert_eq!(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
|
||||
@ -734,6 +740,8 @@ clonedCube = clone(cube)
|
||||
|
||||
assert_eq!(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.
|
||||
@ -807,6 +815,8 @@ clonedCube = clone(cube)
|
||||
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
|
||||
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
|
||||
@ -905,5 +915,7 @@ clonedCube = clone(cube)
|
||||
assert_ne!(edge_cut.edge_id(), cloned_edge_cut.edge_id());
|
||||
assert_eq!(edge_cut.tag(), cloned_edge_cut.tag());
|
||||
}
|
||||
|
||||
ctx.close().await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,15 +8,15 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{ExecState, ExtrudeSurface, KclValue, TagIdentifier},
|
||||
execution::{types::RuntimeType, ExecState, ExtrudeSurface, KclValue, TagIdentifier},
|
||||
std::Args,
|
||||
};
|
||||
|
||||
/// Get the opposite edge to the edge given.
|
||||
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 {
|
||||
value: edge,
|
||||
meta: vec![args.source_range.into()],
|
||||
@ -53,15 +53,24 @@ pub async fn get_opposite_edge(exec_state: &mut ExecState, args: Args) -> Result
|
||||
/// ```
|
||||
#[stdlib {
|
||||
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 {
|
||||
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 tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
|
||||
let resp = args
|
||||
.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.
|
||||
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 {
|
||||
value: edge,
|
||||
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 {
|
||||
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(
|
||||
tag: TagIdentifier,
|
||||
edge: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Uuid, KclError> {
|
||||
if args.ctx.no_engine_commands().await {
|
||||
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 tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
|
||||
let resp = args
|
||||
.send_modeling_cmd(
|
||||
@ -167,7 +181,7 @@ async fn inner_get_next_adjacent_edge(
|
||||
|
||||
adjacent_edge.edge.ok_or_else(|| {
|
||||
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],
|
||||
})
|
||||
})
|
||||
@ -175,9 +189,9 @@ async fn inner_get_next_adjacent_edge(
|
||||
|
||||
/// 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> {
|
||||
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 {
|
||||
value: edge,
|
||||
meta: vec![args.source_range.into()],
|
||||
@ -214,19 +228,24 @@ pub async fn get_previous_adjacent_edge(exec_state: &mut ExecState, args: Args)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
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(
|
||||
tag: TagIdentifier,
|
||||
edge: TagIdentifier,
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Uuid, KclError> {
|
||||
if args.ctx.no_engine_commands().await {
|
||||
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 tagged_path = args.get_tag_engine_info(exec_state, &tag)?;
|
||||
let tagged_path = args.get_tag_engine_info(exec_state, &edge)?;
|
||||
|
||||
let resp = args
|
||||
.send_modeling_cmd(
|
||||
@ -253,7 +272,7 @@ async fn inner_get_previous_adjacent_edge(
|
||||
|
||||
adjacent_edge.edge.ok_or_else(|| {
|
||||
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],
|
||||
})
|
||||
})
|
||||
|
||||
@ -8,7 +8,7 @@ use anyhow::Result;
|
||||
use crate::{
|
||||
errors::KclErrorDetails,
|
||||
modules::{ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode, NodeRef, Program},
|
||||
parsing::ast::types::{ImportPath, ImportStatement, Node as AstNode},
|
||||
walk::{Node, Visitable},
|
||||
ExecState, ExecutorContext, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
@ -20,7 +20,7 @@ type Dependency = (String, String);
|
||||
|
||||
type Graph = Vec<Dependency>;
|
||||
|
||||
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, AstNode<Program>);
|
||||
type DependencyInfo = (AstNode<ImportStatement>, ModuleId, ModulePath, ModuleRepr);
|
||||
type Universe = HashMap<String, DependencyInfo>;
|
||||
|
||||
/// 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> {
|
||||
let mut graph = Graph::new();
|
||||
|
||||
for (name, (_, _, _, program)) in progs.iter() {
|
||||
for (name, (_, _, _, repr)) in progs.iter() {
|
||||
graph.extend(
|
||||
import_dependencies(program, ctx)?
|
||||
import_dependencies(repr, ctx)?
|
||||
.into_iter()
|
||||
.map(|(dependency, _, _)| (name.clone(), dependency))
|
||||
.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)>;
|
||||
|
||||
pub(crate) fn import_dependencies(
|
||||
prog: NodeRef<Program>,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<ImportDependencies, KclError> {
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> Result<ImportDependencies, KclError> {
|
||||
let ModuleRepr::Kcl(prog, _) = repr else {
|
||||
// It has no dependencies, so return an empty list.
|
||||
return Ok(vec![]);
|
||||
};
|
||||
|
||||
let ret = Arc::new(Mutex::new(vec![]));
|
||||
fn walk(ret: Arc<Mutex<ImportDependencies>>, node: Node<'_>, ctx: &ExecutorContext) -> Result<(), KclError> {
|
||||
if let Node::ImportStatement(is) = node {
|
||||
// We only care about Kcl imports for now.
|
||||
if let ImportPath::Kcl { filename } = &is.path {
|
||||
// We only care about Kcl and Foreign imports for now.
|
||||
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.
|
||||
// This is a bit of a hack, but it works for now.
|
||||
ret.lock()
|
||||
@ -141,6 +142,19 @@ pub(crate) fn import_dependencies(
|
||||
})?
|
||||
.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() {
|
||||
@ -164,11 +178,11 @@ pub(crate) fn import_dependencies(
|
||||
|
||||
pub(crate) async fn import_universe(
|
||||
ctx: &ExecutorContext,
|
||||
prog: NodeRef<'_, Program>,
|
||||
repr: &ModuleRepr,
|
||||
out: &mut Universe,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(), KclError> {
|
||||
let modules = import_dependencies(prog, ctx)?;
|
||||
let modules = import_dependencies(repr, ctx)?;
|
||||
for (filename, import_stmt, module_path) in modules {
|
||||
if out.contains_key(&filename) {
|
||||
continue;
|
||||
@ -178,26 +192,21 @@ pub(crate) async fn import_universe(
|
||||
.open_module(&import_stmt.path, &[], exec_state, Default::default())
|
||||
.await?;
|
||||
|
||||
let program = {
|
||||
let repr = {
|
||||
let Some(module_info) = exec_state.get_module(module_id) else {
|
||||
return Err(KclError::Internal(KclErrorDetails {
|
||||
message: format!("Module {} not found", module_id),
|
||||
source_ranges: vec![import_stmt.into()],
|
||||
}));
|
||||
};
|
||||
let ModuleRepr::Kcl(program, _) = &module_info.repr else {
|
||||
// if it's not a KCL module we can skip it since it has no
|
||||
// dependencies.
|
||||
continue;
|
||||
};
|
||||
program.clone()
|
||||
module_info.repr.clone()
|
||||
};
|
||||
|
||||
out.insert(
|
||||
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(())
|
||||
@ -206,7 +215,7 @@ pub(crate) async fn import_universe(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parsing::ast::types::ImportSelector;
|
||||
use crate::parsing::ast::types::{ImportSelector, Program};
|
||||
|
||||
macro_rules! kcl {
|
||||
( $kcl:expr ) => {{
|
||||
@ -224,7 +233,7 @@ mod tests {
|
||||
}),
|
||||
ModuleId::default(),
|
||||
ModulePath::Local { value: "".into() },
|
||||
program,
|
||||
ModuleRepr::Kcl(program, None),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@ -0,0 +1,3 @@
|
||||
import "../inputs/cube.step" as cube
|
||||
|
||||
clone(cube)
|
||||
@ -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
|
||||
---
|
||||
@ -0,0 +1,3 @@
|
||||
```mermaid
|
||||
flowchart LR
|
||||
```
|
||||
504
rust/kcl-lib/tests/multiple-foreign-imports-all-render/ast.snap
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
@ -0,0 +1,3 @@
|
||||
import "../inputs/cube.step" as cube
|
||||
|
||||
clone(cube)
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 81 KiB |
@ -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)
|
||||
@ -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)
|
||||
@ -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)
|
||||
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
59
scripts/diff.js
Normal 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}`)
|
||||
})
|
||||
10
src/App.tsx
@ -28,13 +28,9 @@ import { PATHS } from '@src/lib/paths'
|
||||
import { takeScreenshotOfVideoStreamCanvas } from '@src/lib/screenshot'
|
||||
import { sceneInfra } from '@src/lib/singletons'
|
||||
import { maybeWriteToDisk } from '@src/lib/telemetry'
|
||||
import type { IndexLoaderData } from '@src/lib/types'
|
||||
import {
|
||||
engineStreamActor,
|
||||
useSettings,
|
||||
useToken,
|
||||
} from '@src/machines/appMachine'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { type IndexLoaderData } from '@src/lib/types'
|
||||
import { engineStreamActor, useSettings, useToken } from '@src/lib/singletons'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
import { EngineStreamTransition } from '@src/machines/engineStreamMachine'
|
||||
import { onboardingPaths } from '@src/routes/Onboarding/paths'
|
||||
import { CommandBarOpenButton } from '@src/components/CommandBarOpenButton'
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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
|
||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||
|
||||
35
src/Root.tsx
Normal 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
|
||||
@ -9,22 +9,15 @@ import {
|
||||
} from 'react-router-dom'
|
||||
|
||||
import { App } from '@src/App'
|
||||
import { AppStateProvider } from '@src/AppState'
|
||||
import { Auth } from '@src/Auth'
|
||||
import { CommandBar } from '@src/components/CommandBar/CommandBar'
|
||||
import DownloadAppBanner from '@src/components/DownloadAppBanner'
|
||||
import { ErrorPage } from '@src/components/ErrorPage'
|
||||
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 { OpenInDesktopAppHandler } from '@src/components/OpenInDesktopAppHandler'
|
||||
import { ProjectsContextProvider } from '@src/components/ProjectsContextProvider'
|
||||
import { RouteProvider } from '@src/components/RouteProvider'
|
||||
import { WasmErrBanner } from '@src/components/WasmErrBanner'
|
||||
import { NetworkContext } from '@src/hooks/useNetworkContext'
|
||||
import { useNetworkStatus } from '@src/hooks/useNetworkStatus'
|
||||
import { KclContextProvider } from '@src/lang/KclProvider'
|
||||
import { coreDump } from '@src/lang/wasm'
|
||||
import {
|
||||
ASK_TO_OPEN_QUERY_PARAM,
|
||||
@ -42,7 +35,8 @@ import {
|
||||
rustContext,
|
||||
} from '@src/lib/singletons'
|
||||
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 Onboarding, { onboardingRoutes } from '@src/routes/Onboarding'
|
||||
import { Settings } from '@src/routes/Settings'
|
||||
@ -54,27 +48,13 @@ const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
|
||||
const router = createRouter([
|
||||
{
|
||||
id: PATHS.INDEX,
|
||||
element: (
|
||||
<OpenInDesktopAppHandler>
|
||||
<RouteProvider>
|
||||
<LspProvider>
|
||||
<ProjectsContextProvider>
|
||||
<KclContextProvider>
|
||||
<AppStateProvider>
|
||||
<MachineManagerProvider>
|
||||
<Outlet />
|
||||
</MachineManagerProvider>
|
||||
</AppStateProvider>
|
||||
</KclContextProvider>
|
||||
</ProjectsContextProvider>
|
||||
</LspProvider>
|
||||
</RouteProvider>
|
||||
</OpenInDesktopAppHandler>
|
||||
),
|
||||
errorElement: <ErrorPage />,
|
||||
element: <RootLayout />,
|
||||
// Gotcha: declaring errorElement on the root will unmount the element causing our forever React components to unmount.
|
||||
// Leave errorElement on the child components, this allows for the entire react context on error pages as well.
|
||||
children: [
|
||||
{
|
||||
path: PATHS.INDEX,
|
||||
errorElement: <ErrorPage />,
|
||||
loader: async ({ request }) => {
|
||||
const onDesktop = isDesktop()
|
||||
const url = new URL(request.url)
|
||||
@ -95,6 +75,7 @@ const router = createRouter([
|
||||
loader: fileLoader,
|
||||
id: PATHS.FILE,
|
||||
path: PATHS.FILE + '/:id',
|
||||
errorElement: <ErrorPage />,
|
||||
element: (
|
||||
<Auth>
|
||||
<FileMachineProvider>
|
||||
@ -141,6 +122,7 @@ const router = createRouter([
|
||||
},
|
||||
{
|
||||
path: PATHS.HOME,
|
||||
errorElement: <ErrorPage />,
|
||||
element: (
|
||||
<Auth>
|
||||
<Outlet />
|
||||
@ -169,6 +151,7 @@ const router = createRouter([
|
||||
},
|
||||
{
|
||||
path: PATHS.SIGN_IN,
|
||||
errorElement: <ErrorPage />,
|
||||
element: <SignIn />,
|
||||
},
|
||||
],
|
||||
|
||||
@ -26,7 +26,7 @@ import type {
|
||||
} from '@src/lib/toolbar'
|
||||
import { isToolbarItemResolvedDropdown, toolbarConfig } from '@src/lib/toolbar'
|
||||
import { isArray } from '@src/lib/utils'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
|
||||
export function Toolbar({
|
||||
className = '',
|
||||
|
||||
@ -40,8 +40,8 @@ import {
|
||||
} from '@src/lib/singletons'
|
||||
import { err, reportRejection, trap } from '@src/lib/trap'
|
||||
import { throttle, toSync } from '@src/lib/utils'
|
||||
import type { useSettings } from '@src/machines/appMachine'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import type { useSettings } from '@src/lib/singletons'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
import type { SegmentOverlay } from '@src/machines/modelingMachine'
|
||||
|
||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||
|
||||
@ -84,7 +84,7 @@ import { getThemeColorForThreeJs } from '@src/lib/theme'
|
||||
import { err } from '@src/lib/trap'
|
||||
import { isClockwise, normaliseAngle, roundOff } from '@src/lib/utils'
|
||||
import { getTangentPointFromPreviousArc } from '@src/lib/utils2d'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
import type {
|
||||
SegmentOverlay,
|
||||
SegmentOverlayPayload,
|
||||
|
||||
@ -4,7 +4,7 @@ import ProjectSidebarMenu from '@src/components/ProjectSidebarMenu'
|
||||
import UserSidebarMenu from '@src/components/UserSidebarMenu'
|
||||
import { isDesktop } from '@src/lib/isDesktop'
|
||||
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'
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Switch } from '@headlessui/react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { settingsActor, useSettings } from '@src/machines/appMachine'
|
||||
import { settingsActor, useSettings } from '@src/lib/singletons'
|
||||
|
||||
export function CameraProjectionToggle() {
|
||||
const settings = useSettings()
|
||||
|
||||
@ -8,10 +8,7 @@ import type {
|
||||
CommandArgument,
|
||||
CommandArgumentOption,
|
||||
} from '@src/lib/commandTypes'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
const contextSelector = (snapshot: StateFrom<AnyStateMachine> | undefined) =>
|
||||
snapshot?.context
|
||||
|
||||
@ -11,10 +11,7 @@ import { useNetworkContext } from '@src/hooks/useNetworkContext'
|
||||
import { EngineConnectionStateType } from '@src/lang/std/engineConnection'
|
||||
import useHotkeyWrapper from '@src/lib/hotkeyWrapper'
|
||||
import { engineCommandManager } from '@src/lib/singletons'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
||||
|
||||
@ -7,10 +7,7 @@ import CommandBarSelectionInput from '@src/components/CommandBar/CommandBarSelec
|
||||
import CommandBarSelectionMixedInput from '@src/components/CommandBar/CommandBarSelectionMixedInput'
|
||||
import CommandBarTextareaInput from '@src/components/CommandBar/CommandBarTextareaInput'
|
||||
import type { CommandArgument } from '@src/lib/commandTypes'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
||||
const commandBarState = useCommandBarState()
|
||||
|
||||
@ -3,10 +3,7 @@ import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
import type { CommandArgument } from '@src/lib/commandTypes'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
import type { AnyStateMachine, SnapshotFrom } from 'xstate'
|
||||
|
||||
// TODO: remove the need for this selector once we decouple all actors from React
|
||||
|
||||
@ -11,10 +11,7 @@ import type {
|
||||
import type { Selections } from '@src/lib/selections'
|
||||
import { getSelectionTypeDisplayText } from '@src/lib/selections'
|
||||
import { roundOff } from '@src/lib/utils'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
function CommandBarHeader({ children }: React.PropsWithChildren<object>) {
|
||||
const commandBarState = useCommandBarState()
|
||||
|
||||
@ -29,11 +29,8 @@ import { err } from '@src/lib/trap'
|
||||
import { useCalculateKclExpression } from '@src/lib/useCalculateKclExpression'
|
||||
import { roundOff } from '@src/lib/utils'
|
||||
import { varMentions } from '@src/lib/varCompletionExtension'
|
||||
import { useSettings } from '@src/machines/appMachine'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { useSettings } from '@src/lib/singletons'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
import styles from './CommandBarKclInput.module.css'
|
||||
|
||||
|
||||
@ -5,10 +5,7 @@ import { ActionButton } from '@src/components/ActionButton'
|
||||
import type { CommandArgument } from '@src/lib/commandTypes'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
import { isArray, toSync } from '@src/lib/utils'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import type { AnyStateMachine, SnapshotFrom } from 'xstate'
|
||||
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
import CommandBarHeader from '@src/components/CommandBar/CommandBarHeader'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||
const commandBarState = useCommandBarState()
|
||||
|
||||
@ -12,10 +12,7 @@ import {
|
||||
import { engineCommandManager, kclManager } from '@src/lib/singletons'
|
||||
import { reportRejection } from '@src/lib/trap'
|
||||
import { toSync } from '@src/lib/utils'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
import type { modelingMachine } from '@src/machines/modelingMachine'
|
||||
|
||||
const semanticEntityNames: {
|
||||
|
||||
@ -8,10 +8,7 @@ import {
|
||||
getSelectionCountByType,
|
||||
} from '@src/lib/selections'
|
||||
import { kclManager } from '@src/lib/singletons'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
const selectionSelector = (snapshot: any) => snapshot?.context.selectionRanges
|
||||
|
||||
|
||||
@ -3,10 +3,7 @@ import { useEffect, useRef } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
|
||||
import type { CommandArgument } from '@src/lib/commandTypes'
|
||||
import {
|
||||
commandBarActor,
|
||||
useCommandBarState,
|
||||
} from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor, useCommandBarState } from '@src/lib/singletons'
|
||||
|
||||
function CommandBarTextareaInput({
|
||||
arg,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { COMMAND_PALETTE_HOTKEY } from '@src/components/CommandBar/CommandBar'
|
||||
import usePlatform from '@src/hooks/usePlatform'
|
||||
import { hotkeyDisplay } from '@src/lib/hotkeyWrapper'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
import { CustomIcon } from '@src/components/CustomIcon'
|
||||
|
||||
export function CommandBarOpenButton() {
|
||||
|
||||
@ -6,7 +6,7 @@ import { CustomIcon } from '@src/components/CustomIcon'
|
||||
import type { Command } from '@src/lib/commandTypes'
|
||||
import { sortCommands } from '@src/lib/commandUtils'
|
||||
import { getActorNextEvents } from '@src/lib/utils'
|
||||
import { commandBarActor } from '@src/machines/commandBarMachine'
|
||||
import { commandBarActor } from '@src/lib/singletons'
|
||||
|
||||
function CommandComboBox({
|
||||
options,
|
||||
|
||||
@ -3,7 +3,7 @@ import { useState } from 'react'
|
||||
|
||||
import { ActionButton } from '@src/components/ActionButton'
|
||||
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'
|
||||
|
||||
const DownloadAppBanner = () => {
|
||||
|
||||