Compare commits
6 Commits
jtran/fix-
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
8fa8121af1 | |||
abbb8e6d66 | |||
ddaab1cc3f | |||
efb4314046 | |||
08938d087f | |||
697c2744cc |
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
||||||
skip: **/target,node_modules,build,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo
|
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts,./packages/codemirror-lang-kcl/test/all.test.ts
|
||||||
|
12
.eslintrc
@ -5,24 +5,16 @@
|
|||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
"css-modules",
|
"css-modules",
|
||||||
"jest",
|
|
||||||
"react",
|
|
||||||
"suggest-no-throw",
|
"suggest-no-throw",
|
||||||
"@typescript-eslint"
|
|
||||||
],
|
],
|
||||||
"extends": [
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest",
|
||||||
"plugin:css-modules/recommended"
|
"plugin:css-modules/recommended"
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"@typescript-eslint/no-floating-promises": "error",
|
"@typescript-eslint/no-floating-promises": "error",
|
||||||
"@typescript-eslint/no-misused-promises": "error",
|
"@typescript-eslint/no-misused-promises": "error",
|
||||||
"no-restricted-globals": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"name": "isNaN",
|
|
||||||
"message": "Use Number.isNaN() instead."
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"semi": [
|
"semi": [
|
||||||
"error",
|
"error",
|
||||||
"never"
|
"never"
|
||||||
|
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
retry=1
|
retry=1
|
||||||
max_retrys=5
|
max_retrys=4
|
||||||
|
|
||||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retrys ]]; do
|
while [[ $retry -le $max_retrys ]]; do
|
||||||
|
46
.github/dependabot.yml
vendored
@ -5,28 +5,24 @@
|
|||||||
|
|
||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
- package-ecosystem: 'npm' # See documentation for possible values
|
- package-ecosystem: 'npm' # See documentation for possible values
|
||||||
directory: '/' # Location of package manifests
|
directory: '/' # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: 'weekly'
|
||||||
reviewers:
|
reviewers:
|
||||||
- franknoirot
|
- franknoirot
|
||||||
- irev-dev
|
- irev-dev
|
||||||
- package-ecosystem: 'github-actions' # See documentation for possible values
|
- package-ecosystem: 'github-actions' # See documentation for possible values
|
||||||
directory: '/' # Location of package manifests
|
directory: '/' # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: 'weekly'
|
||||||
reviewers:
|
reviewers:
|
||||||
- adamchalmers
|
- adamchalmers
|
||||||
- jessfraz
|
- jessfraz
|
||||||
- package-ecosystem: 'cargo' # See documentation for possible values
|
- package-ecosystem: 'cargo' # See documentation for possible values
|
||||||
directory: '/src/wasm-lib/' # Location of package manifests
|
directory: '/src/wasm-lib/' # Location of package manifests
|
||||||
schedule:
|
schedule:
|
||||||
interval: 'weekly'
|
interval: 'weekly'
|
||||||
reviewers:
|
reviewers:
|
||||||
- adamchalmers
|
- adamchalmers
|
||||||
- jessfraz
|
- jessfraz
|
||||||
groups:
|
|
||||||
serde-dependencies:
|
|
||||||
patterns:
|
|
||||||
- "serde*"
|
|
||||||
|
2
.github/workflows/e2e-tests.yml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ main ]
|
branches: [ main, pierremtb/e2e-snapshots-linux-only-test-bot-update ]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
|
||||||
|
38
README.md
@ -337,47 +337,13 @@ For individual testing:
|
|||||||
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
yarn test abstractSyntaxTree -t "unexpected closed curly brace" --silent=false
|
||||||
```
|
```
|
||||||
|
|
||||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro) tests, in interactive mode by default.
|
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||||
|
|
||||||
### Rust tests
|
### Rust tests
|
||||||
|
|
||||||
**Dependencies**
|
|
||||||
|
|
||||||
- `KITTYCAD_API_TOKEN`
|
|
||||||
- `cargo-nextest`
|
|
||||||
- `just`
|
|
||||||
|
|
||||||
#### Setting KITTYCAD_API_TOKEN
|
|
||||||
Use the production zoo.dev token, set this environment variable before running the tests
|
|
||||||
|
|
||||||
#### Installing cargonextest
|
|
||||||
|
|
||||||
```
|
|
||||||
cd src/wasm-lib
|
|
||||||
cargo search cargo-nextest
|
|
||||||
cargo install cargo-nextest
|
|
||||||
```
|
|
||||||
|
|
||||||
#### just
|
|
||||||
install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries)
|
|
||||||
|
|
||||||
#### Running the tests
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# With just
|
|
||||||
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
|
||||||
# Make sure you installed cargo-nextest
|
|
||||||
# Make sure you installed just
|
|
||||||
cd src/wasm-lib
|
cd src/wasm-lib
|
||||||
just test
|
KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Without just
|
|
||||||
# Make sure KITTYCAD_API_TOKEN=<prod zoo.dev token> is set
|
|
||||||
# Make sure you installed cargo-nextest
|
|
||||||
cd src/wasm-lib
|
|
||||||
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
Where `XXX` is an API token from the production engine (NOT the dev environment).
|
||||||
|
@ -24,5 +24,3 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
chamfer cases work currently.
|
chamfer cases work currently.
|
||||||
|
|
||||||
- **Appearance**: Changing the appearance on a loft does not work.
|
- **Appearance**: Changing the appearance on a loft does not work.
|
||||||
|
|
||||||
- **Helix**: Currently sweeping a helix does not work.
|
|
||||||
|
@ -48,7 +48,6 @@ layout: manual
|
|||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
* [`getPreviousAdjacentEdge`](kcl/getPreviousAdjacentEdge)
|
||||||
* [`helix`](kcl/helix)
|
* [`helix`](kcl/helix)
|
||||||
* [`helixRevolutions`](kcl/helixRevolutions)
|
|
||||||
* [`hole`](kcl/hole)
|
* [`hole`](kcl/hole)
|
||||||
* [`hollow`](kcl/hollow)
|
* [`hollow`](kcl/hollow)
|
||||||
* [`import`](kcl/import)
|
* [`import`](kcl/import)
|
||||||
@ -82,7 +81,6 @@ layout: manual
|
|||||||
* [`pi`](kcl/pi)
|
* [`pi`](kcl/pi)
|
||||||
* [`polar`](kcl/polar)
|
* [`polar`](kcl/polar)
|
||||||
* [`polygon`](kcl/polygon)
|
* [`polygon`](kcl/polygon)
|
||||||
* [`pop`](kcl/pop)
|
|
||||||
* [`pow`](kcl/pow)
|
* [`pow`](kcl/pow)
|
||||||
* [`profileStart`](kcl/profileStart)
|
* [`profileStart`](kcl/profileStart)
|
||||||
* [`profileStartX`](kcl/profileStartX)
|
* [`profileStartX`](kcl/profileStartX)
|
||||||
|
6781
docs/kcl/std.json
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Axis3dOrEdgeReference"
|
|
||||||
excerpt: "A 3D axis or tagged edge."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A 3D axis or tagged edge.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
|
||||||
|
|
||||||
3D axis and origin.
|
|
||||||
|
|
||||||
[`AxisAndOrigin3d`](/docs/kcl/types/AxisAndOrigin3d)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Tagged edge.
|
|
||||||
|
|
||||||
[`EdgeReference`](/docs/kcl/types/EdgeReference)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "AxisAndOrigin2d"
|
title: "AxisAndOrigin"
|
||||||
excerpt: "A 2D axis and origin."
|
excerpt: "Axis and origin."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
A 2D axis and origin.
|
Axis and origin.
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,105 +0,0 @@
|
|||||||
---
|
|
||||||
title: "AxisAndOrigin3d"
|
|
||||||
excerpt: "A 3D axis and origin."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A 3D axis and origin.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts exactly one of the following:**
|
|
||||||
|
|
||||||
X-axis.
|
|
||||||
|
|
||||||
**enum:** `X`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Y-axis.
|
|
||||||
|
|
||||||
**enum:** `Y`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Z-axis.
|
|
||||||
|
|
||||||
**enum:** `Z`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Flip the X-axis.
|
|
||||||
|
|
||||||
**enum:** `-X`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Flip the Y-axis.
|
|
||||||
|
|
||||||
**enum:** `-Y`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
Flip the Z-axis.
|
|
||||||
|
|
||||||
**enum:** `-Z`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `custom` |`object`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: "Axis2dOrEdgeReference"
|
title: "AxisOrEdgeReference"
|
||||||
excerpt: "A 2D axis or tagged edge."
|
excerpt: "Axis or tagged edge."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
A 2D axis or tagged edge.
|
Axis or tagged edge.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
**This schema accepts any of the following:**
|
||||||
|
|
||||||
2D axis and origin.
|
Axis and origin.
|
||||||
|
|
||||||
[`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d)
|
[`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Helix"
|
|
||||||
excerpt: "A helix."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A helix.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `value` |`string`| The id of the helix. | No |
|
|
||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "HelixData"
|
title: "HelixData"
|
||||||
excerpt: "Data for a helix."
|
excerpt: "Data for helices."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Data for a helix.
|
Data for helices.
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -19,8 +19,6 @@ Data for a helix.
|
|||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
| `revolutions` |`number`| Number of revolutions. | No |
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
| `angleStart` |`number`| Start angle (in degrees). | No |
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||||
| `length` |`number`| Length of the helix. | No |
|
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
||||||
| `radius` |`number`| Radius of the helix. | No |
|
|
||||||
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,24 +0,0 @@
|
|||||||
---
|
|
||||||
title: "HelixRevolutionsData"
|
|
||||||
excerpt: "Data for helix revolutions."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
Data for helix revolutions.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? The default is `false`. | No |
|
|
||||||
| `length` |`number`| Length of the helix. If this argument is not provided, the height of the solid is used. | No |
|
|
||||||
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
title: "HelixValue"
|
|
||||||
excerpt: "A helix."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A helix.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `value` |`string`| The id of the helix. | No |
|
|
||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
@ -285,27 +285,6 @@ An solid is a collection of extrude surfaces.
|
|||||||
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
| `value` |`[` [`Solid`](/docs/kcl/types/Solid) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
A helix.
|
|
||||||
|
|
||||||
**Type:** `object`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
|
||||||
|----------|------|-------------|----------|
|
|
||||||
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
|
|
||||||
| `value` |`string`| The id of the helix. | No |
|
|
||||||
| `revolutions` |`number`| Number of revolutions. | No |
|
|
||||||
| `angleStart` |`number`| Start angle (in degrees). | No |
|
|
||||||
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
Data for an imported geometry.
|
Data for an imported geometry.
|
||||||
|
|
||||||
|
@ -16,6 +16,6 @@ Data for a mirror.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis to use as mirror. | No |
|
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ Data for revolution surfaces.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
| `angle` |`number` (**maximum:** 360.0) (**minimum:** -360.0)| Angle to revolve (in degrees). Default is 360. | No |
|
||||||
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| Axis of revolution. | No |
|
| `axis` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No |
|
||||||
| `tolerance` |`number`| Tolerance for the revolve operation. | No |
|
| `tolerance` |`number`| Tolerance for the revolve operation. | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ Data for a sweep.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| The path to sweep along. | No |
|
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
||||||
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||||
|
|
||||||
|
@ -1,42 +0,0 @@
|
|||||||
---
|
|
||||||
title: "SweepPath"
|
|
||||||
excerpt: "A path to sweep along."
|
|
||||||
layout: manual
|
|
||||||
---
|
|
||||||
|
|
||||||
A path to sweep along.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
|
||||||
|
|
||||||
A path to sweep along.
|
|
||||||
|
|
||||||
[`Sketch`](/docs/kcl/types/Sketch)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
A path to sweep along.
|
|
||||||
|
|
||||||
[`Helix`](/docs/kcl/types/Helix)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,8 +36,7 @@ type DragFromHandler = (
|
|||||||
|
|
||||||
export class SceneFixture {
|
export class SceneFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
public streamWrapper!: Locator
|
|
||||||
public loadingIndicator!: Locator
|
|
||||||
private exeIndicator!: Locator
|
private exeIndicator!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
@ -65,8 +64,6 @@ export class SceneFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
|
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
this.streamWrapper = page.getByTestId('stream')
|
|
||||||
this.loadingIndicator = this.streamWrapper.getByTestId('loading')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
makeMouseHelpers = (
|
makeMouseHelpers = (
|
||||||
|
@ -14,7 +14,6 @@ export class ToolbarFixture {
|
|||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
loftButton!: Locator
|
loftButton!: Locator
|
||||||
sweepButton!: Locator
|
|
||||||
shellButton!: Locator
|
shellButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
@ -41,7 +40,6 @@ export class ToolbarFixture {
|
|||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
this.loftButton = page.getByTestId('loft')
|
this.loftButton = page.getByTestId('loft')
|
||||||
this.sweepButton = page.getByTestId('sweep')
|
|
||||||
this.shellButton = page.getByTestId('shell')
|
this.shellButton = page.getByTestId('shell')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
|
@ -756,17 +756,6 @@ test(`Offset plane point-and-click`, async ({
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Delete offset plane via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
|
||||||
'Offset Plane',
|
|
||||||
0
|
|
||||||
)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const loftPointAndClickCases = [
|
const loftPointAndClickCases = [
|
||||||
@ -862,173 +851,6 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Delete loft via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: merge with above test. Right now we're not able to delete a loft
|
|
||||||
// right after creation via selection for some reason, so we go with a new instance
|
|
||||||
test('Loft and offset plane deletion via selection', async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
}) => {
|
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
plane001 = offsetPlane('XZ', 50)
|
|
||||||
sketch002 = startSketchOn(plane001)
|
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|
||||||
loft001 = loft([sketch001, sketch002])
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 575, y: 200 }
|
|
||||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 80)
|
|
||||||
|
|
||||||
await test.step(`Delete loft`, async () => {
|
|
||||||
// Check for loft
|
|
||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
|
||||||
await clickOnSketch1()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for sketch 1
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete sketch002', async () => {
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await clickOnSketch2()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for plane001
|
|
||||||
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete plane001', async () => {
|
|
||||||
await page.waitForTimeout(1000)
|
|
||||||
await clickOnSketch2()
|
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
|
||||||
plane001 = offsetPlane('XZ', 50)
|
|
||||||
`)
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
// Check for sketch 1
|
|
||||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Sweep point-and-click`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
const initialCode = `sketch001 = startSketchOn('YZ')
|
|
||||||
|> circle({
|
|
||||||
center = [0, 0],
|
|
||||||
radius = 500
|
|
||||||
}, %)
|
|
||||||
sketch002 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> xLine(-500, %)
|
|
||||||
|> tangentialArcTo([-2000, 500], %)
|
|
||||||
`
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 700, y: 250 }
|
|
||||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const [clickOnSketch2] = scene.makeMouseHelpers(testPoint.x - 50, testPoint.y)
|
|
||||||
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
|
|
||||||
|
|
||||||
await test.step(`Look for sketch001`, async () => {
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow`, async () => {
|
|
||||||
await toolbar.sweepButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
commandName: 'Sweep',
|
|
||||||
currentArgKey: 'profile',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Path: '',
|
|
||||||
Profile: '',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'profile',
|
|
||||||
stage: 'arguments',
|
|
||||||
})
|
|
||||||
await clickOnSketch1()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
commandName: 'Sweep',
|
|
||||||
currentArgKey: 'path',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Path: '',
|
|
||||||
Profile: '1 face',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'path',
|
|
||||||
stage: 'arguments',
|
|
||||||
})
|
|
||||||
await clickOnSketch2()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
commandName: 'Sweep',
|
|
||||||
headerArguments: {
|
|
||||||
Path: '1 face',
|
|
||||||
Profile: '1 face',
|
|
||||||
},
|
|
||||||
stage: 'review',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
|
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(sweepDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [sweepDeclaration],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Delete sweep via feature tree selection', async () => {
|
|
||||||
await toolbar.openPane('feature-tree')
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await toolbar.closePane('feature-tree')
|
|
||||||
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -1208,104 +1030,4 @@ extrude001 = extrude(40, sketch001)
|
|||||||
})
|
})
|
||||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Delete shell via feature tree selection', async () => {
|
|
||||||
await editor.closePane()
|
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
|
||||||
await operationButton.click({ button: 'left' })
|
|
||||||
await page.keyboard.press('Backspace')
|
|
||||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const shellSketchOnFacesCases = [
|
|
||||||
`sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|
||||||
|> extrude(100, %)
|
|
||||||
|
|
||||||
sketch002 = startSketchOn(sketch001, 'END')
|
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|
||||||
|> extrude(50, %)
|
|
||||||
`,
|
|
||||||
`sketch001 = startSketchOn('XZ')
|
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|
||||||
extrude001 = extrude(100, sketch001)
|
|
||||||
|
|
||||||
sketch002 = startSketchOn(extrude001, 'END')
|
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|
||||||
extrude002 = extrude(50, sketch002)
|
|
||||||
`,
|
|
||||||
]
|
|
||||||
shellSketchOnFacesCases.forEach((initialCode, index) => {
|
|
||||||
const hasExtrudesInPipe = index === 0
|
|
||||||
test(`Shell point-and-click sketch on face (extrudes in pipes: ${hasExtrudesInPipe})`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
editor,
|
|
||||||
toolbar,
|
|
||||||
cmdBar,
|
|
||||||
}) => {
|
|
||||||
await context.addInitScript((initialCode) => {
|
|
||||||
localStorage.setItem('persistCode', initialCode)
|
|
||||||
}, initialCode)
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
|
||||||
const testPoint = { x: 550, y: 295 }
|
|
||||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
|
||||||
const shellDeclaration = `shell001 = shell({ faces = ['end'], thickness = 5 }, ${
|
|
||||||
hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
|
||||||
})`
|
|
||||||
|
|
||||||
await test.step(`Look for the grey of the shape`, async () => {
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
|
|
||||||
await toolbar.shellButton.click()
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'arguments',
|
|
||||||
currentArgKey: 'selection',
|
|
||||||
currentArgValue: '',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '',
|
|
||||||
Thickness: '',
|
|
||||||
},
|
|
||||||
highlightedHeaderArg: 'selection',
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await clickOnCap()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await cmdBar.expectState({
|
|
||||||
stage: 'review',
|
|
||||||
headerArguments: {
|
|
||||||
Selection: '1 cap',
|
|
||||||
Thickness: '5',
|
|
||||||
},
|
|
||||||
commandName: 'Shell',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
|
||||||
await toolbar.openPane('code')
|
|
||||||
await editor.expectEditor.toContain(shellDeclaration)
|
|
||||||
await editor.expectState({
|
|
||||||
diagnostics: [],
|
|
||||||
activeLines: [shellDeclaration],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
await scene.expectPixelColor([73, 73, 73], testPoint, 15)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -115,7 +115,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -199,7 +199,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -276,7 +276,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'open a file in a project works and renders, open empty file, it should clear the scene',
|
'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
@ -1885,48 +1885,3 @@ test.fixme(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test(
|
|
||||||
'project name with foreign characters should open',
|
|
||||||
{ tag: '@electron' },
|
|
||||||
async ({ context, page }, testInfo) => {
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
|
||||||
const bracketDir = path.join(dir, 'اَلْعَرَبِيَّةُ')
|
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
|
||||||
await fsp.copyFile(
|
|
||||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
|
||||||
path.join(bracketDir, 'main.kcl')
|
|
||||||
)
|
|
||||||
|
|
||||||
await fsp.writeFile(path.join(bracketDir, 'empty.kcl'), '')
|
|
||||||
})
|
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
page.on('console', console.log)
|
|
||||||
|
|
||||||
const pointOnModel = { x: 630, y: 280 }
|
|
||||||
|
|
||||||
await test.step('Opening the اَلْعَرَبِيَّةُ project should load the stream', async () => {
|
|
||||||
// expect to see the text bracket
|
|
||||||
await expect(page.getByText('اَلْعَرَبِيَّةُ')).toBeVisible()
|
|
||||||
|
|
||||||
await page.getByText('اَلْعَرَبِيَّةُ').click()
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
|
||||||
).toBeEnabled({
|
|
||||||
timeout: 20_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
// gray at this pixel means the stream has loaded in the most
|
|
||||||
// user way we can verify it (pixel color)
|
|
||||||
await expect
|
|
||||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
.toBeLessThan(15)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
@ -614,38 +614,6 @@ extrude001 = extrude(50, sketch001)
|
|||||||
await expect(gizmo).toBeVisible()
|
await expect(gizmo).toBeVisible()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Refreshing the app doesn't cause the stream to pause on long-executing files`, async ({
|
|
||||||
context,
|
|
||||||
homePage,
|
|
||||||
scene,
|
|
||||||
toolbar,
|
|
||||||
viewport,
|
|
||||||
}) => {
|
|
||||||
await context.folderSetupFn(async (dir) => {
|
|
||||||
const legoDir = path.join(dir, 'lego')
|
|
||||||
await fsp.mkdir(legoDir, { recursive: true })
|
|
||||||
await fsp.copyFile(
|
|
||||||
executorInputPath('lego.kcl'),
|
|
||||||
path.join(legoDir, 'main.kcl')
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
|
||||||
await homePage.openProject('lego')
|
|
||||||
await toolbar.closePane('code')
|
|
||||||
})
|
|
||||||
await test.step(`Waiting for the loading spinner to disappear`, async () => {
|
|
||||||
await scene.loadingIndicator.waitFor({ state: 'detached' })
|
|
||||||
})
|
|
||||||
await test.step(`The part should start loading quickly, not waiting until execution is complete`, async () => {
|
|
||||||
await scene.expectPixelColor(
|
|
||||||
[143, 143, 143],
|
|
||||||
{ x: (viewport?.width ?? 1200) / 2, y: (viewport?.height ?? 500) / 2 },
|
|
||||||
15
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function clickExportButton(page: Page) {
|
async function clickExportButton(page: Page) {
|
||||||
|
@ -39,8 +39,8 @@ test.describe('Sketch tests', () => {
|
|||||||
${startProfileAt1}
|
${startProfileAt1}
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = screwRadius,
|
radius = screwRadius,
|
||||||
angleStart = 0,
|
angle_start = 0,
|
||||||
angleEnd = 360
|
angle_end = 360
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
part001 = startSketchOn('XY')
|
part001 = startSketchOn('XY')
|
||||||
@ -60,8 +60,8 @@ test.describe('Sketch tests', () => {
|
|||||||
|> yLine(wireOffset, %)
|
|> yLine(wireOffset, %)
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = wireRadius,
|
radius = wireRadius,
|
||||||
angleStart = 0,
|
angle_start = 0,
|
||||||
angleEnd = 180
|
angle_end = 180
|
||||||
}, %)
|
}, %)
|
||||||
|> yLine(-wireOffset, %)
|
|> yLine(-wireOffset, %)
|
||||||
|> xLine(-width / 4, %)
|
|> xLine(-width / 4, %)
|
||||||
@ -1323,85 +1323,3 @@ test.describe(`Sketching with offset planes`, () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Regression test for https://github.com/KittyCAD/modeling-app/issues/4891
|
|
||||||
test.describe(`Click based selection don't brick the app when clicked out of range after format using cache`, () => {
|
|
||||||
test(`Can select a line that reformmed after entering sketch mode`, async ({
|
|
||||||
context,
|
|
||||||
page,
|
|
||||||
scene,
|
|
||||||
toolbar,
|
|
||||||
editor,
|
|
||||||
homePage,
|
|
||||||
}) => {
|
|
||||||
// We seed the scene with a single offset plane
|
|
||||||
await context.addInitScript(() => {
|
|
||||||
localStorage.setItem(
|
|
||||||
'persistCode',
|
|
||||||
`sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> line([3.14, 3.14], %)
|
|
||||||
|> arcTo({
|
|
||||||
end = [4, 2],
|
|
||||||
interior = [1, 2]
|
|
||||||
}, %)
|
|
||||||
`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
|
||||||
await scene.waitForExecutionDone()
|
|
||||||
|
|
||||||
await test.step(`format the code`, async () => {
|
|
||||||
// doesn't contain condensed version
|
|
||||||
await editor.expectEditor.not.toContain(
|
|
||||||
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
|
||||||
)
|
|
||||||
// click the code to enter sketch mode
|
|
||||||
await page.getByText(`arcTo`).click()
|
|
||||||
// Format the code.
|
|
||||||
await page.locator('#code-pane button:first-child').click()
|
|
||||||
await page.locator('button:has-text("Format code")').click()
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step(`Ensure the code reformatted`, async () => {
|
|
||||||
await editor.expectEditor.toContain(
|
|
||||||
`arcTo({ end = [4, 2], interior = [1, 2] }, %)`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const [arcClick, arcHover] = scene.makeMouseHelpers(699, 337)
|
|
||||||
await test.step('Ensure we can hover the arc', async () => {
|
|
||||||
await arcHover()
|
|
||||||
|
|
||||||
// Check that the code is highlighted
|
|
||||||
await editor.expectState({
|
|
||||||
activeLines: ["sketch001=startSketchOn('XZ')"],
|
|
||||||
diagnostics: [],
|
|
||||||
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('reset the selection', async () => {
|
|
||||||
// Move the mouse out of the way
|
|
||||||
await page.mouse.move(655, 337)
|
|
||||||
|
|
||||||
await editor.expectState({
|
|
||||||
activeLines: ["sketch001=startSketchOn('XZ')"],
|
|
||||||
diagnostics: [],
|
|
||||||
highlightedCode: '',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
await test.step('Ensure we can click the arc', async () => {
|
|
||||||
await arcClick()
|
|
||||||
|
|
||||||
// Check that the code is highlighted
|
|
||||||
await editor.expectState({
|
|
||||||
activeLines: [],
|
|
||||||
diagnostics: [],
|
|
||||||
highlightedCode: 'arcTo({end = [4, 2], interior = [1, 2]}, %)',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 38 KiB |
@ -389,25 +389,25 @@ test.describe('Testing selections', () => {
|
|||||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 0, y = -50, z = 0 },
|
origin = { x = 0, y = -50, z = 0 },
|
||||||
xAxis = { x = 1, y = 0, z = 0 },
|
x_axis = { x = 1, y = 0, z = 0 },
|
||||||
yAxis = { x = 0, y = 0, z = 1 },
|
y_axis = { x = 0, y = 0, z = 1 },
|
||||||
zAxis = { x = 0, y = -1, z = 0 }
|
z_axis = { x = 0, y = -1, z = 0 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||||
xAxis = { x = -0.81, y = 0, z = 0.58 },
|
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
||||||
yAxis = { x = 0, y = -1, z = 0 },
|
y_axis = { x = 0, y = -1, z = 0 },
|
||||||
zAxis = { x = 0.58, y = 0, z = 0.81 }
|
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||||
xAxis = { x = -0.66, y = 0, z = -0.75 },
|
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
||||||
yAxis = { x = 0, y = -1, z = 0 },
|
y_axis = { x = 0, y = -1, z = 0 },
|
||||||
zAxis = { x = -0.75, y = 0, z = 0.66 }
|
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
|
|
||||||
|
@ -156,13 +156,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
const prompt = page.getByText('Prompt')
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
@ -224,13 +224,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
const prompt = page.getByText('Prompt')
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||||
@ -314,13 +314,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
const prompt = page.getByText('Prompt')
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||||
@ -392,13 +392,13 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByRole('option', { name: 'Text-to-CAD' })
|
const textToCadCommand = page.getByText('Text-to-CAD')
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().click()
|
await textToCadCommand.first().click()
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
const prompt = page.getByText('Prompt')
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
@ -604,7 +604,7 @@ async function sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
// Enter the prompt.
|
// Enter the prompt.
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
const prompt = page.getByText('Prompt')
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
|
18
flake.lock
generated
@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736320768,
|
"lastModified": 1721933792,
|
||||||
"narHash": "sha256-nIYdTAiKIGnFNugbomgBJR+Xv5F1ZQU+HfaBqJKroC0=",
|
"narHash": "sha256-zYVwABlQnxpbaHMfX6Wt9jhyQstFYwN2XjleOJV3VVg=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "4bc9c909d9ac828a039f288cf872d16d38185db8",
|
"rev": "2122a9b35b35719ad9a395fe783eabb092df01b1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -18,11 +18,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
"nixpkgs_2": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1728538411,
|
"lastModified": 1718428119,
|
||||||
"narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=",
|
"narHash": "sha256-WdWDpNaq6u1IPtxtYHHWpl5BmabtpmLnMAx0RdJ/vo8=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221",
|
"rev": "e6cea36f83499eb4e9cd184c8a8e823296b50ad5",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -43,11 +43,11 @@
|
|||||||
"nixpkgs": "nixpkgs_2"
|
"nixpkgs": "nixpkgs_2"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1736476219,
|
"lastModified": 1721960387,
|
||||||
"narHash": "sha256-+qyv3QqdZCdZ3cSO/cbpEY6tntyYjfe1bB12mdpNFaY=",
|
"narHash": "sha256-o21ax+745ETGXrcgc/yUuLw1SI77ymp3xEpJt+w/kks=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "de30cc5963da22e9742bbbbb9a3344570ed237b9",
|
"rev": "9cbf831c5b20a53354fc12758abd05966f9f1699",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
8
interface.d.ts
vendored
@ -11,13 +11,6 @@ export interface IElectronAPI {
|
|||||||
open: typeof dialog.showOpenDialog
|
open: typeof dialog.showOpenDialog
|
||||||
save: typeof dialog.showSaveDialog
|
save: typeof dialog.showSaveDialog
|
||||||
openExternal: typeof shell.openExternal
|
openExternal: typeof shell.openExternal
|
||||||
takeElectronWindowScreenshot: ({
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
}: {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}) => Promise<string>
|
|
||||||
showInFolder: typeof shell.showItemInFolder
|
showInFolder: typeof shell.showItemInFolder
|
||||||
/** Require to be called first before {@link loginWithDeviceFlow} */
|
/** Require to be called first before {@link loginWithDeviceFlow} */
|
||||||
startDeviceFlow: (host: string) => Promise<string>
|
startDeviceFlow: (host: string) => Promise<string>
|
||||||
@ -93,6 +86,5 @@ export interface IElectronAPI {
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: IElectronAPI
|
electron: IElectronAPI
|
||||||
openExternalLink: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
24
package.json
@ -15,7 +15,7 @@
|
|||||||
"@codemirror/autocomplete": "^6.17.0",
|
"@codemirror/autocomplete": "^6.17.0",
|
||||||
"@codemirror/commands": "^6.6.0",
|
"@codemirror/commands": "^6.6.0",
|
||||||
"@codemirror/language": "^6.10.3",
|
"@codemirror/language": "^6.10.3",
|
||||||
"@codemirror/lint": "^6.8.4",
|
"@codemirror/lint": "^6.8.1",
|
||||||
"@codemirror/search": "^6.5.6",
|
"@codemirror/search": "^6.5.6",
|
||||||
"@codemirror/state": "^6.4.1",
|
"@codemirror/state": "^6.4.1",
|
||||||
"@codemirror/theme-one-dark": "^6.1.2",
|
"@codemirror/theme-one-dark": "^6.1.2",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.19",
|
"@headlessui/react": "^1.7.19",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "2.0.13",
|
"@kittycad/lib": "2.0.12",
|
||||||
"@lezer/highlight": "^1.2.1",
|
"@lezer/highlight": "^1.2.1",
|
||||||
"@lezer/lr": "^1.4.1",
|
"@lezer/lr": "^1.4.1",
|
||||||
"@react-hook/resize-observer": "^2.0.1",
|
"@react-hook/resize-observer": "^2.0.1",
|
||||||
@ -52,13 +52,13 @@
|
|||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-hotkeys-hook": "^4.6.1",
|
"react-hotkeys-hook": "^4.5.1",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-modal": "^3.16.3",
|
"react-modal": "^3.16.1",
|
||||||
"react-modal-promise": "^1.0.2",
|
"react-modal-promise": "^1.0.2",
|
||||||
"react-router-dom": "^6.28.0",
|
"react-router-dom": "^6.28.0",
|
||||||
"sketch-helpers": "^0.0.4",
|
"sketch-helpers": "^0.0.4",
|
||||||
"three": "^0.172.0",
|
"three": "^0.166.1",
|
||||||
"ua-parser-js": "^1.0.37",
|
"ua-parser-js": "^1.0.37",
|
||||||
"uuid": "^11.0.2",
|
"uuid": "^11.0.2",
|
||||||
"vscode-jsonrpc": "^8.2.1",
|
"vscode-jsonrpc": "^8.2.1",
|
||||||
@ -91,8 +91,8 @@
|
|||||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||||
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
"lint-fix": "eslint --fix src e2e packages/codemirror-lsp-client",
|
||||||
"lint": "eslint --max-warnings 0 --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
"lint": "eslint --max-warnings 0 src e2e packages/codemirror-lsp-client",
|
||||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||||
@ -166,11 +166,13 @@
|
|||||||
"@types/react": "^18.3.4",
|
"@types/react": "^18.3.4",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
"@types/react-modal": "^3.16.3",
|
"@types/react-modal": "^3.16.3",
|
||||||
"@types/three": "^0.172.0",
|
"@types/three": "^0.163.0",
|
||||||
"@types/ua-parser-js": "^0.7.39",
|
"@types/ua-parser-js": "^0.7.39",
|
||||||
"@types/uuid": "^9.0.8",
|
"@types/uuid": "^9.0.8",
|
||||||
"@types/wicg-file-system-access": "^2023.10.5",
|
"@types/wicg-file-system-access": "^2023.10.5",
|
||||||
"@types/ws": "^8.5.13",
|
"@types/ws": "^8.5.13",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
"@vitejs/plugin-react": "^4.3.0",
|
"@vitejs/plugin-react": "^4.3.0",
|
||||||
"@vitest/web-worker": "^1.5.0",
|
"@vitest/web-worker": "^1.5.0",
|
||||||
"@xstate/cli": "^0.5.17",
|
"@xstate/cli": "^0.5.17",
|
||||||
@ -180,12 +182,11 @@
|
|||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
"electron-notarize": "1.2.2",
|
"electron-notarize": "1.2.2",
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.30.0",
|
"eslint-plugin-import": "^2.30.0",
|
||||||
"eslint-plugin-jest": "^28.10.0",
|
|
||||||
"eslint-plugin-react": "^7.37.3",
|
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^16.3.0",
|
"happy-dom": "^15.11.7",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"husky": "^9.1.5",
|
"husky": "^9.1.5",
|
||||||
"kill-port": "^2.0.1",
|
"kill-port": "^2.0.1",
|
||||||
@ -199,7 +200,6 @@
|
|||||||
"tailwindcss": "^3.4.1",
|
"tailwindcss": "^3.4.1",
|
||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.19.1",
|
|
||||||
"vite": "^5.4.6",
|
"vite": "^5.4.6",
|
||||||
"vite-plugin-package-version": "^1.1.0",
|
"vite-plugin-package-version": "^1.1.0",
|
||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
statement[@isGroup=Statement] {
|
statement[@isGroup=Statement] {
|
||||||
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
ImportStatement { kw<"import"> ImportItems ImportFrom String } |
|
||||||
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } |
|
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals ParamList Arrow Body } |
|
||||||
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||||
ReturnStatement { kw<"return"> expression } |
|
ReturnStatement { kw<"return"> expression } |
|
||||||
ExpressionStatement { expression }
|
ExpressionStatement { expression }
|
||||||
@ -85,7 +85,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
|||||||
@tokens {
|
@tokens {
|
||||||
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
String[isolate] { "'" ("\\" _ | !['\\])* "'" | '"' ("\\" _ | !["\\])* '"' }
|
||||||
|
|
||||||
Number { "." @digit+ | @digit+ ("." @digit+)? }
|
Number { "." @digit+ | @digit+ ("." @digit*)? }
|
||||||
@precedence { Number, "." }
|
@precedence { Number, "." }
|
||||||
|
|
||||||
AddOp { "+" | "-" }
|
AddOp { "+" | "-" }
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
# full
|
|
||||||
|
|
||||||
fn two = () => {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(FunctionDeclaration(fn,
|
|
||||||
VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ParamList,
|
|
||||||
Arrow,
|
|
||||||
Body(ReturnStatement(return,
|
|
||||||
Number))))
|
|
||||||
|
|
||||||
# = is optional
|
|
||||||
|
|
||||||
fn one () => {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(FunctionDeclaration(fn,
|
|
||||||
VariableDefinition,
|
|
||||||
ParamList,
|
|
||||||
Arrow,
|
|
||||||
Body(ReturnStatement(return,
|
|
||||||
Number))))
|
|
||||||
|
|
||||||
# => is optional
|
|
||||||
|
|
||||||
fn one = () {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(FunctionDeclaration(fn,
|
|
||||||
VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ParamList,
|
|
||||||
Body(ReturnStatement(return,
|
|
||||||
Number))))
|
|
||||||
|
|
||||||
# terse
|
|
||||||
|
|
||||||
fn two() {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
|
|
||||||
==>
|
|
||||||
|
|
||||||
Program(FunctionDeclaration(fn,
|
|
||||||
VariableDefinition,
|
|
||||||
ParamList,
|
|
||||||
Body(ReturnStatement(return,
|
|
||||||
Number))))
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
|||||||
# spaced
|
|
||||||
|
|
||||||
a = [0 .. 1]
|
|
||||||
|
|
||||||
==>
|
|
||||||
Program(VariableDeclaration(VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ArrayExpression(IntegerRange(Number,
|
|
||||||
Number))))
|
|
||||||
|
|
||||||
# compact
|
|
||||||
|
|
||||||
a = [0..1]
|
|
||||||
|
|
||||||
==>
|
|
||||||
Program(VariableDeclaration(VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ArrayExpression(IntegerRange(Number,
|
|
||||||
Number))))
|
|
||||||
|
|
||||||
# expr spaced
|
|
||||||
|
|
||||||
a = [start .. start + 10]
|
|
||||||
|
|
||||||
==>
|
|
||||||
Program(VariableDeclaration(VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ArrayExpression(IntegerRange(VariableName,
|
|
||||||
BinaryExpression(VariableName,
|
|
||||||
AddOp,
|
|
||||||
Number)))))
|
|
||||||
|
|
||||||
# expr compact
|
|
||||||
|
|
||||||
a = [start..start + 10]
|
|
||||||
|
|
||||||
==>
|
|
||||||
Program(VariableDeclaration(VariableDefinition,
|
|
||||||
Equals,
|
|
||||||
ArrayExpression(IntegerRange(VariableName,
|
|
||||||
BinaryExpression(VariableName,
|
|
||||||
AddOp,
|
|
||||||
Number)))))
|
|
@ -42,7 +42,7 @@ export default class StreamDemuxer extends Queue<Uint8Array> {
|
|||||||
// try to parse the content-length from the headers
|
// try to parse the content-length from the headers
|
||||||
const length = parseInt(match[1])
|
const length = parseInt(match[1])
|
||||||
|
|
||||||
if (Number.isNaN(length))
|
if (isNaN(length))
|
||||||
return Promise.reject(new Error('invalid content length'))
|
return Promise.reject(new Error('invalid content length'))
|
||||||
|
|
||||||
// slice the headers since we now have the content length
|
// slice the headers since we now have the content length
|
||||||
|
@ -368,20 +368,13 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
sortText,
|
sortText,
|
||||||
filterText,
|
filterText,
|
||||||
}) => {
|
}) => {
|
||||||
const detailText = [
|
|
||||||
deprecated ? 'Deprecated' : undefined,
|
|
||||||
labelDetails ? labelDetails.detail : detail,
|
|
||||||
]
|
|
||||||
// Don't let undefined appear.
|
|
||||||
.filter(Boolean)
|
|
||||||
.join(' ')
|
|
||||||
const completion: Completion & {
|
const completion: Completion & {
|
||||||
filterText: string
|
filterText: string
|
||||||
sortText?: string
|
sortText?: string
|
||||||
apply: string
|
apply: string
|
||||||
} = {
|
} = {
|
||||||
label,
|
label,
|
||||||
detail: detailText,
|
detail: labelDetails ? labelDetails.detail : detail,
|
||||||
apply: label,
|
apply: label,
|
||||||
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
type: kind && CompletionItemKindMap[kind].toLowerCase(),
|
||||||
sortText: sortText ?? label,
|
sortText: sortText ?? label,
|
||||||
@ -389,11 +382,7 @@ export class LanguageServerPlugin implements PluginValue {
|
|||||||
}
|
}
|
||||||
if (documentation) {
|
if (documentation) {
|
||||||
completion.info = () => {
|
completion.info = () => {
|
||||||
const deprecatedHtml = deprecated
|
const htmlString = formatMarkdownContents(documentation)
|
||||||
? '<p><strong>Deprecated</strong></p>'
|
|
||||||
: ''
|
|
||||||
const htmlString =
|
|
||||||
deprecatedHtml + formatMarkdownContents(documentation)
|
|
||||||
const htmlNode = document.createElement('div')
|
const htmlNode = document.createElement('div')
|
||||||
htmlNode.style.display = 'contents'
|
htmlNode.style.display = 'contents'
|
||||||
htmlNode.innerHTML = htmlString
|
htmlNode.innerHTML = htmlString
|
||||||
|
@ -32,9 +32,10 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
projects: [
|
projects: [
|
||||||
{
|
{
|
||||||
name: 'chromium',
|
name: 'Google Chrome',
|
||||||
use: {
|
use: {
|
||||||
...devices['Desktop Chrome'],
|
...devices['Desktop Chrome'],
|
||||||
|
channel: 'chrome',
|
||||||
contextOptions: {
|
contextOptions: {
|
||||||
/* Chromium is the only one with these permission types */
|
/* Chromium is the only one with these permission types */
|
||||||
permissions: ['clipboard-write', 'clipboard-read'],
|
permissions: ['clipboard-write', 'clipboard-read'],
|
||||||
|
@ -75,7 +75,6 @@ function CommandBarTextareaInput({
|
|||||||
target.selectionStart = selectionStart + 1
|
target.selectionStart = selectionStart + 1
|
||||||
target.selectionEnd = selectionStart + 1
|
target.selectionEnd = selectionStart + 1
|
||||||
} else if (event.key === 'Enter') {
|
} else if (event.key === 'Enter') {
|
||||||
event.preventDefault()
|
|
||||||
formRef.current?.dispatchEvent(
|
formRef.current?.dispatchEvent(
|
||||||
new Event('submit', { bubbles: true })
|
new Event('submit', { bubbles: true })
|
||||||
)
|
)
|
||||||
|
@ -157,38 +157,39 @@ export const ModelingMachineProvider = ({
|
|||||||
'enable copilot': () => {
|
'enable copilot': () => {
|
||||||
editorManager.setCopilotEnabled(true)
|
editorManager.setCopilotEnabled(true)
|
||||||
},
|
},
|
||||||
'sketch exit execute': ({ context: { store } }) => {
|
// tsc reports this typing as perfectly fine, but eslint is complaining.
|
||||||
// TODO: Remove this async callback. For some reason eslint wouldn't
|
// It's actually nonsensical, so I'm quieting.
|
||||||
// let me disable @typescript-eslint/no-misused-promises for the line.
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
;(async () => {
|
'sketch exit execute': async ({
|
||||||
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
context: { store },
|
||||||
await engineCommandManager.sendSceneCommand({
|
}): Promise<void> => {
|
||||||
type: 'modeling_cmd_req',
|
// When cancelling the sketch mode we should disable sketch mode within the engine.
|
||||||
cmd_id: uuidv4(),
|
await engineCommandManager.sendSceneCommand({
|
||||||
cmd: { type: 'sketch_mode_disable' },
|
type: 'modeling_cmd_req',
|
||||||
})
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'sketch_mode_disable' },
|
||||||
|
})
|
||||||
|
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
|
|
||||||
if (cameraProjection.current === 'perspective') {
|
if (cameraProjection.current === 'perspective') {
|
||||||
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
await sceneInfra.camControls.snapToPerspectiveBeforeHandingBackControlToEngine()
|
||||||
}
|
}
|
||||||
|
|
||||||
sceneInfra.camControls.syncDirection = 'engineToClient'
|
sceneInfra.camControls.syncDirection = 'engineToClient'
|
||||||
|
|
||||||
store.videoElement?.pause()
|
store.videoElement?.pause()
|
||||||
|
|
||||||
return kclManager
|
return kclManager
|
||||||
.executeCode()
|
.executeCode()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (engineCommandManager.engineConnection?.idleMode) return
|
if (engineCommandManager.engineConnection?.idleMode) return
|
||||||
|
|
||||||
store.videoElement?.play().catch((e) => {
|
store.videoElement?.play().catch((e) => {
|
||||||
console.warn('Video playing was prevented', e)
|
console.warn('Video playing was prevented', e)
|
||||||
})
|
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
})
|
||||||
})().catch(reportRejection)
|
.catch(reportRejection)
|
||||||
},
|
},
|
||||||
'Set mouse state': assign(({ context, event }) => {
|
'Set mouse state': assign(({ context, event }) => {
|
||||||
if (event.type !== 'Set mouse state') return {}
|
if (event.type !== 'Set mouse state') return {}
|
||||||
@ -270,7 +271,6 @@ export const ModelingMachineProvider = ({
|
|||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_center_to_selection',
|
type: 'default_camera_center_to_selection',
|
||||||
camera_movement: 'vantage',
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
|
@ -97,7 +97,6 @@ export const KclEditorPane = () => {
|
|||||||
if (!editorIsMounted || !lastSelectionEvent || !editorManager.editorView) {
|
if (!editorIsMounted || !lastSelectionEvent || !editorManager.editorView) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
editorManager.editorView.dispatch({
|
editorManager.editorView.dispatch({
|
||||||
selection: lastSelectionEvent.codeMirrorSelection,
|
selection: lastSelectionEvent.codeMirrorSelection,
|
||||||
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
annotations: [modelingMachineEvent, Transaction.addToHistory.of(false)],
|
||||||
|
@ -60,7 +60,7 @@ function AppLogoLink({
|
|||||||
}) {
|
}) {
|
||||||
const { onProjectClose } = useLspContext()
|
const { onProjectClose } = useLspContext()
|
||||||
const wrapperClassName =
|
const wrapperClassName =
|
||||||
"relative h-full grid place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-2.5 before:z-[-1] before:bg-primary before:rounded-b-sm"
|
"relative h-full grid place-content-center group p-1.5 before:block before:content-[''] before:absolute before:inset-0 before:bottom-1.5 before:z-[-1] before:bg-primary before:rounded-b-sm"
|
||||||
const logoClassName = 'w-auto h-4 text-chalkboard-10'
|
const logoClassName = 'w-auto h-4 text-chalkboard-10'
|
||||||
|
|
||||||
return isDesktop() ? (
|
return isDesktop() ? (
|
||||||
|
@ -218,6 +218,20 @@ export const Stream = () => {
|
|||||||
}
|
}
|
||||||
}, [IDLE, streamState])
|
}, [IDLE, streamState])
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play the vid
|
||||||
|
*/
|
||||||
|
useEffect(() => {
|
||||||
|
if (!kclManager.isExecuting) {
|
||||||
|
setTimeout(() => {
|
||||||
|
// execute in the next event loop
|
||||||
|
videoRef.current?.play().catch((e) => {
|
||||||
|
console.warn('Video playing was prevented', e, videoRef.current)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [kclManager.isExecuting])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
typeof window === 'undefined' ||
|
typeof window === 'undefined' ||
|
||||||
@ -229,15 +243,9 @@ export const Stream = () => {
|
|||||||
|
|
||||||
// The browser complains if we try to load a new stream without pausing first.
|
// The browser complains if we try to load a new stream without pausing first.
|
||||||
// Do not immediately play the stream!
|
// Do not immediately play the stream!
|
||||||
// we instead use a setTimeout to play the stream in the next event loop
|
|
||||||
try {
|
try {
|
||||||
videoRef.current.srcObject = mediaStream
|
videoRef.current.srcObject = mediaStream
|
||||||
videoRef.current.pause()
|
videoRef.current.pause()
|
||||||
setTimeout(() => {
|
|
||||||
videoRef.current?.play().catch((e) => {
|
|
||||||
console.warn('Video playing was prevented', e, videoRef.current)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Attempted to pause stream while play was still loading', e)
|
console.warn('Attempted to pause stream while play was still loading', e)
|
||||||
}
|
}
|
||||||
|
@ -150,31 +150,4 @@ describe('ToastUpdate tests', () => {
|
|||||||
expect(restartButton).toBeEnabled()
|
expect(restartButton).toBeEnabled()
|
||||||
expect(dismissButton).toBeEnabled()
|
expect(dismissButton).toBeEnabled()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Happy path: external links render correctly', () => {
|
|
||||||
const releaseNotesWithBreakingChanges = `
|
|
||||||
## Some markdown release notes
|
|
||||||
- [Zoo](https://zoo.dev/)
|
|
||||||
`
|
|
||||||
const onRestart = vi.fn()
|
|
||||||
const onDismiss = vi.fn()
|
|
||||||
|
|
||||||
render(
|
|
||||||
<ToastUpdate
|
|
||||||
onRestart={onRestart}
|
|
||||||
onDismiss={onDismiss}
|
|
||||||
version={testData.version}
|
|
||||||
releaseNotes={releaseNotesWithBreakingChanges}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Locators and other constants
|
|
||||||
const zooDev = screen.getByText('Zoo', {
|
|
||||||
selector: 'a',
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(zooDev).toHaveAttribute('href', 'https://zoo.dev/')
|
|
||||||
expect(zooDev).toHaveAttribute('target', '_blank')
|
|
||||||
expect(zooDev).toHaveAttribute('onClick')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { escape, Marked, MarkedOptions, unescape } from '@ts-stack/markdown'
|
import { Marked } from '@ts-stack/markdown'
|
||||||
import { getReleaseUrl } from 'routes/Settings'
|
import { getReleaseUrl } from 'routes/Settings'
|
||||||
import { SafeRenderer } from 'lib/markdown'
|
|
||||||
|
|
||||||
export function ToastUpdate({
|
export function ToastUpdate({
|
||||||
version,
|
version,
|
||||||
@ -20,14 +19,6 @@ export function ToastUpdate({
|
|||||||
?.toLocaleLowerCase()
|
?.toLocaleLowerCase()
|
||||||
.includes('breaking')
|
.includes('breaking')
|
||||||
|
|
||||||
const markedOptions: MarkedOptions = {
|
|
||||||
gfm: true,
|
|
||||||
breaks: true,
|
|
||||||
sanitize: true,
|
|
||||||
unescape,
|
|
||||||
escape,
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
<div className="inset-0 z-50 grid place-content-center rounded bg-chalkboard-110/50 shadow-md">
|
||||||
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
<div className="max-w-3xl min-w-[35rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
@ -67,8 +58,9 @@ export function ToastUpdate({
|
|||||||
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
|
className="parsed-markdown py-2 px-4 mt-2 border-t border-chalkboard-30 dark:border-chalkboard-60 max-h-60 overflow-y-auto"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: Marked.parse(releaseNotes, {
|
__html: Marked.parse(releaseNotes, {
|
||||||
renderer: new SafeRenderer(markedOptions),
|
gfm: true,
|
||||||
...markedOptions,
|
breaks: true,
|
||||||
|
sanitize: true,
|
||||||
}),
|
}),
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
|
42
src/components/UpdaterModal.test.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
|
import { vi } from 'vitest'
|
||||||
|
import { UpdaterModal } from './UpdaterModal'
|
||||||
|
|
||||||
|
describe('UpdaterModal tests', () => {
|
||||||
|
test('Renders the modal', () => {
|
||||||
|
const callback = vi.fn()
|
||||||
|
const data = {
|
||||||
|
version: '1.2.3',
|
||||||
|
date: '2021-22-23T21:22:23Z',
|
||||||
|
body: 'This is the body.',
|
||||||
|
}
|
||||||
|
|
||||||
|
render(
|
||||||
|
<UpdaterModal
|
||||||
|
isOpen={true}
|
||||||
|
onReject={() => {}}
|
||||||
|
onResolve={callback}
|
||||||
|
instanceId=""
|
||||||
|
open={false}
|
||||||
|
close={(res) => {}}
|
||||||
|
version={data.version}
|
||||||
|
date={data.date}
|
||||||
|
body={data.body}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(screen.getByTestId('update-version')).toHaveTextContent(data.version)
|
||||||
|
|
||||||
|
const updateButton = screen.getByTestId('update-button-update')
|
||||||
|
expect(updateButton).toBeEnabled()
|
||||||
|
fireEvent.click(updateButton)
|
||||||
|
expect(callback.mock.calls).toHaveLength(1)
|
||||||
|
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: true })
|
||||||
|
|
||||||
|
const cancelButton = screen.getByTestId('update-button-cancel')
|
||||||
|
expect(cancelButton).toBeEnabled()
|
||||||
|
fireEvent.click(cancelButton)
|
||||||
|
expect(callback.mock.calls).toHaveLength(2)
|
||||||
|
expect(callback.mock.lastCall[0]).toEqual({ wantUpdate: false })
|
||||||
|
})
|
||||||
|
})
|
87
src/components/UpdaterModal.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { create, InstanceProps } from 'react-modal-promise'
|
||||||
|
import { ActionButton } from './ActionButton'
|
||||||
|
import { Logo } from './Logo'
|
||||||
|
import { Marked } from '@ts-stack/markdown'
|
||||||
|
|
||||||
|
type ModalResolve = {
|
||||||
|
wantUpdate: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModalReject = boolean
|
||||||
|
|
||||||
|
type UpdaterModalProps = InstanceProps<ModalResolve, ModalReject> & {
|
||||||
|
version: string
|
||||||
|
date?: string
|
||||||
|
body?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createUpdaterModal = create<
|
||||||
|
UpdaterModalProps,
|
||||||
|
ModalResolve,
|
||||||
|
ModalReject
|
||||||
|
>
|
||||||
|
|
||||||
|
export const UpdaterModal = ({
|
||||||
|
onResolve,
|
||||||
|
version,
|
||||||
|
date,
|
||||||
|
body,
|
||||||
|
}: UpdaterModalProps) => (
|
||||||
|
<div className="fixed inset-0 z-50 grid place-content-center bg-chalkboard-110/50">
|
||||||
|
<div className="max-w-3xl min-w-[45rem] p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<h1 className="flex-grow text-3xl font-bold">New version available!</h1>
|
||||||
|
<Logo className="h-9" />
|
||||||
|
</div>
|
||||||
|
<div className="my-4 flex items-baseline">
|
||||||
|
<span
|
||||||
|
className="px-3 py-1 text-xl rounded-full bg-energy-10 text-energy-80"
|
||||||
|
data-testid="update-version"
|
||||||
|
>
|
||||||
|
v{version}
|
||||||
|
</span>
|
||||||
|
<span className="ml-4 text-sm text-gray-400">Published on {date}</span>
|
||||||
|
</div>
|
||||||
|
{/* TODO: fix list bullets */}
|
||||||
|
{body && (
|
||||||
|
<div
|
||||||
|
className="my-4 max-h-60 overflow-y-auto"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Marked.parse(body, {
|
||||||
|
gfm: true,
|
||||||
|
breaks: true,
|
||||||
|
sanitize: true,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)}
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => onResolve({ wantUpdate: false })}
|
||||||
|
iconStart={{
|
||||||
|
icon: 'close',
|
||||||
|
bgClassName: 'bg-destroy-80',
|
||||||
|
iconClassName: 'text-destroy-20 group-hover:text-destroy-10',
|
||||||
|
}}
|
||||||
|
className="hover:border-destroy-40 hover:bg-destroy-10/50 dark:hover:bg-destroy-80/50"
|
||||||
|
data-testid="update-button-cancel"
|
||||||
|
>
|
||||||
|
Not now
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={() => onResolve({ wantUpdate: true })}
|
||||||
|
iconStart={{
|
||||||
|
icon: 'arrowRight',
|
||||||
|
bgClassName: 'dark:bg-chalkboard-80',
|
||||||
|
}}
|
||||||
|
className="dark:hover:bg-chalkboard-80/50"
|
||||||
|
data-testid="update-button-update"
|
||||||
|
>
|
||||||
|
Update
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
@ -40,7 +40,6 @@ export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
|||||||
export default class EditorManager {
|
export default class EditorManager {
|
||||||
private _copilotEnabled: boolean = true
|
private _copilotEnabled: boolean = true
|
||||||
|
|
||||||
private _isAllTextSelected: boolean = false
|
|
||||||
private _isShiftDown: boolean = false
|
private _isShiftDown: boolean = false
|
||||||
private _selectionRanges: Selections = {
|
private _selectionRanges: Selections = {
|
||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
@ -118,10 +117,6 @@ export default class EditorManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
get isAllTextSelected() {
|
|
||||||
return this._isAllTextSelected
|
|
||||||
}
|
|
||||||
|
|
||||||
get editorView(): EditorView | null {
|
get editorView(): EditorView | null {
|
||||||
return this._editorView
|
return this._editorView
|
||||||
}
|
}
|
||||||
@ -134,21 +129,6 @@ export default class EditorManager {
|
|||||||
this._isShiftDown = isShiftDown
|
this._isShiftDown = isShiftDown
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectionsWithSafeEnds(
|
|
||||||
selection: Array<Selection['codeRef']['range']>
|
|
||||||
): Array<[number, number]> {
|
|
||||||
if (!this._editorView) {
|
|
||||||
return selection.map((s): [number, number] => {
|
|
||||||
return [s[0], s[1]]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return selection.map((s): [number, number] => {
|
|
||||||
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
|
||||||
return [s[0], safeEnd]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
set selectionRanges(selectionRanges: Selections) {
|
set selectionRanges(selectionRanges: Selections) {
|
||||||
this._selectionRanges = selectionRanges
|
this._selectionRanges = selectionRanges
|
||||||
}
|
}
|
||||||
@ -174,9 +154,14 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
||||||
const selectionsWithSafeEnds = this.selectionsWithSafeEnds(range)
|
this._highlightRange = range.map((s): [number, number] => {
|
||||||
|
return [s[0], s[1]]
|
||||||
|
})
|
||||||
|
|
||||||
this._highlightRange = selectionsWithSafeEnds
|
const selectionsWithSafeEnds = range.map((s): [number, number] => {
|
||||||
|
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
||||||
|
return [s[0], safeEnd]
|
||||||
|
})
|
||||||
|
|
||||||
if (this._editorView) {
|
if (this._editorView) {
|
||||||
this._editorView.dispatch({
|
this._editorView.dispatch({
|
||||||
@ -317,20 +302,20 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
let codeBasedSelections = []
|
let codeBasedSelections = []
|
||||||
for (const selection of selections.graphSelections) {
|
for (const selection of selections.graphSelections) {
|
||||||
const safeEnd = Math.min(
|
|
||||||
selection.codeRef.range[1],
|
|
||||||
this._editorView?.state.doc.length || selection.codeRef.range[1]
|
|
||||||
)
|
|
||||||
codeBasedSelections.push(
|
codeBasedSelections.push(
|
||||||
EditorSelection.range(selection.codeRef.range[0], safeEnd)
|
EditorSelection.range(
|
||||||
|
selection.codeRef.range[0],
|
||||||
|
selection.codeRef.range[1]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const end =
|
codeBasedSelections.push(
|
||||||
selections.graphSelections[selections.graphSelections.length - 1].codeRef
|
EditorSelection.cursor(
|
||||||
.range[1]
|
selections.graphSelections[selections.graphSelections.length - 1]
|
||||||
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
|
.codeRef.range[1]
|
||||||
codeBasedSelections.push(EditorSelection.cursor(safeEnd))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if (!this._editorView) {
|
if (!this._editorView) {
|
||||||
return
|
return
|
||||||
@ -367,16 +352,6 @@ export default class EditorManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._isAllTextSelected = viewUpdate.state.selection.ranges.some(
|
|
||||||
(selection) => {
|
|
||||||
return (
|
|
||||||
// The user will need to select the empty new lines as well to be considered all of the text.
|
|
||||||
// CTRL+A is the best way to select all the text
|
|
||||||
selection.from === 0 && selection.to === viewUpdate.state.doc.length
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const eventInfo = processCodeMirrorRanges({
|
const eventInfo = processCodeMirrorRanges({
|
||||||
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
codeMirrorRanges: viewUpdate.state.selection.ranges,
|
||||||
selectionRanges: this._selectionRanges,
|
selectionRanges: this._selectionRanges,
|
||||||
|
@ -10,11 +10,8 @@ import { AppStreamProvider } from 'AppState'
|
|||||||
import { ToastUpdate } from 'components/ToastUpdate'
|
import { ToastUpdate } from 'components/ToastUpdate'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
|
import { AUTO_UPDATER_TOAST_ID } from 'lib/constants'
|
||||||
import { initializeWindowExceptionHandler } from 'lib/exceptions'
|
|
||||||
|
|
||||||
markOnce('code/willAuth')
|
markOnce('code/willAuth')
|
||||||
initializeWindowExceptionHandler()
|
|
||||||
|
|
||||||
// uncomment for xstate inspector
|
// uncomment for xstate inspector
|
||||||
// import { DEV } from 'env'
|
// import { DEV } from 'env'
|
||||||
// import { inspect } from '@xstate/inspect'
|
// import { inspect } from '@xstate/inspect'
|
||||||
|
@ -376,11 +376,7 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
// updateArtifactGraph relies on updated executeState/programMemory
|
// updateArtifactGraph relies on updated executeState/programMemory
|
||||||
await this.engineCommandManager.updateArtifactGraph(
|
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
||||||
this.ast,
|
|
||||||
execState.artifactCommands,
|
|
||||||
execState.artifacts
|
|
||||||
)
|
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
@ -394,24 +390,6 @@ export class KclManager {
|
|||||||
this._cancelTokens.delete(currentExecutionId)
|
this._cancelTokens.delete(currentExecutionId)
|
||||||
markOnce('code/endExecuteAst')
|
markOnce('code/endExecuteAst')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This cleanup function is external and internal to the KclSingleton class.
|
|
||||||
* Since the WASM runtime can panic and the error cannot be caught in executeAst
|
|
||||||
* we need a global exception handler in exceptions.ts
|
|
||||||
* This file will interface with this cleanup as if it caught the original error
|
|
||||||
* to properly restore the TS application state.
|
|
||||||
*/
|
|
||||||
executeAstCleanUp() {
|
|
||||||
this.isExecuting = false
|
|
||||||
this.executeIsStale = null
|
|
||||||
this.engineCommandManager.addCommandLog({
|
|
||||||
type: 'execution-done',
|
|
||||||
data: null,
|
|
||||||
})
|
|
||||||
markOnce('code/endExecuteAst')
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: this always updates the code state and editor.
|
// NOTE: this always updates the code state and editor.
|
||||||
// DO NOT CALL THIS from codemirror ever.
|
// DO NOT CALL THIS from codemirror ever.
|
||||||
async executeAstMock(
|
async executeAstMock(
|
||||||
@ -486,42 +464,13 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
async executeCode(zoomToFit?: boolean): Promise<void> {
|
async executeCode(zoomToFit?: boolean): Promise<void> {
|
||||||
const ast = await this.safeParse(codeManager.code)
|
const ast = await this.safeParse(codeManager.code)
|
||||||
|
|
||||||
if (!ast) {
|
if (!ast) {
|
||||||
this.clearAst()
|
this.clearAst()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomToFit = this.tryToZoomToFitOnCodeUpdate(ast, zoomToFit)
|
|
||||||
|
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
return this.executeAst({ zoomToFit })
|
return this.executeAst({ zoomToFit })
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* This will override the zoom to fit to zoom into the model if the previous AST was empty.
|
|
||||||
* Workflows this improves,
|
|
||||||
* When someone comments the entire file then uncomments the entire file it zooms to the model
|
|
||||||
* When someone CRTL+A and deletes the code then adds the code back it zooms to the model
|
|
||||||
* When someone CRTL+A and copies new code into the editor it zooms to the model
|
|
||||||
*/
|
|
||||||
tryToZoomToFitOnCodeUpdate(
|
|
||||||
ast: Node<Program>,
|
|
||||||
zoomToFit: boolean | undefined
|
|
||||||
) {
|
|
||||||
const isAstEmpty = this._isAstEmpty(this._ast)
|
|
||||||
const isRequestedAstEmpty = this._isAstEmpty(ast)
|
|
||||||
|
|
||||||
// If the AST went from empty to not empty or
|
|
||||||
// If the user has all of the content selected and they copy new code in
|
|
||||||
if (
|
|
||||||
(isAstEmpty && !isRequestedAstEmpty) ||
|
|
||||||
editorManager.isAllTextSelected
|
|
||||||
) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return zoomToFit
|
|
||||||
}
|
|
||||||
async format() {
|
async format() {
|
||||||
const originalCode = codeManager.code
|
const originalCode = codeManager.code
|
||||||
const ast = await this.safeParse(originalCode)
|
const ast = await this.safeParse(originalCode)
|
||||||
|
@ -47,7 +47,7 @@ describe('parsing errors', () => {
|
|||||||
const result = parse(code)
|
const result = parse(code)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
const error = result.errors[0]
|
const error = result.errors[0]
|
||||||
expect(error.message).toBe('Array is missing a closing bracket(`]`)')
|
expect(error.message).toBe('Unexpected token: (')
|
||||||
expect(error.sourceRange).toEqual([28, 29, 0])
|
expect(error.sourceRange).toEqual([27, 28, 0])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,6 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
msg: 'Semantic error',
|
msg: 'Semantic error',
|
||||||
sourceRange: [0, 1, true],
|
sourceRange: [0, 1, true],
|
||||||
operations: [],
|
operations: [],
|
||||||
artifactCommands: [],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
@ -19,7 +18,6 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
msg: 'Type error',
|
msg: 'Type error',
|
||||||
sourceRange: [4, 5, true],
|
sourceRange: [4, 5, true],
|
||||||
operations: [],
|
operations: [],
|
||||||
artifactCommands: [],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
const diagnostics = kclErrorsToDiagnostics(errors)
|
||||||
|
@ -5,7 +5,7 @@ import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
|||||||
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||||
import { Text } from '@codemirror/state'
|
import { Text } from '@codemirror/state'
|
||||||
import { EditorView } from 'codemirror'
|
import { EditorView } from 'codemirror'
|
||||||
import { ArtifactCommand, SourceRange } from 'lang/wasm'
|
import { SourceRange } from 'lang/wasm'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
|
|
||||||
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
||||||
@ -14,141 +14,86 @@ export class KCLError extends Error {
|
|||||||
sourceRange: SourceRange
|
sourceRange: SourceRange
|
||||||
msg: string
|
msg: string
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kind: ExtractKind<RustKclError> | 'name',
|
kind: ExtractKind<RustKclError> | 'name',
|
||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[]
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.kind = kind
|
this.kind = kind
|
||||||
this.msg = msg
|
this.msg = msg
|
||||||
this.sourceRange = sourceRange
|
this.sourceRange = sourceRange
|
||||||
this.operations = operations
|
this.operations = operations
|
||||||
this.artifactCommands = artifactCommands
|
|
||||||
Object.setPrototypeOf(this, KCLError.prototype)
|
Object.setPrototypeOf(this, KCLError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLLexicalError extends KCLError {
|
export class KCLLexicalError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('lexical', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('lexical', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLInternalError extends KCLError {
|
export class KCLInternalError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('internal', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('internal', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLSyntaxError extends KCLError {
|
export class KCLSyntaxError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('syntax', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('syntax', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLSemanticError extends KCLError {
|
export class KCLSemanticError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('semantic', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('semantic', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLTypeError extends KCLError {
|
export class KCLTypeError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('type', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('type', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUnimplementedError extends KCLError {
|
export class KCLUnimplementedError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('unimplemented', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('unimplemented', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUnexpectedError extends KCLError {
|
export class KCLUnexpectedError extends KCLError {
|
||||||
constructor(
|
constructor(msg: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
msg: string,
|
super('unexpected', msg, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super('unexpected', msg, sourceRange, operations, artifactCommands)
|
|
||||||
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLValueAlreadyDefined extends KCLError {
|
export class KCLValueAlreadyDefined extends KCLError {
|
||||||
constructor(
|
constructor(key: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
key: string,
|
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super(
|
super(
|
||||||
'name',
|
'name',
|
||||||
`Key ${key} was already defined elsewhere`,
|
`Key ${key} was already defined elsewhere`,
|
||||||
sourceRange,
|
sourceRange,
|
||||||
operations,
|
operations
|
||||||
artifactCommands
|
|
||||||
)
|
)
|
||||||
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUndefinedValueError extends KCLError {
|
export class KCLUndefinedValueError extends KCLError {
|
||||||
constructor(
|
constructor(key: string, sourceRange: SourceRange, operations: Operation[]) {
|
||||||
key: string,
|
super('name', `Key ${key} has not been defined`, sourceRange, operations)
|
||||||
sourceRange: SourceRange,
|
|
||||||
operations: Operation[],
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
'name',
|
|
||||||
`Key ${key} has not been defined`,
|
|
||||||
sourceRange,
|
|
||||||
operations,
|
|
||||||
artifactCommands
|
|
||||||
)
|
|
||||||
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -168,7 +113,6 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
'unexpected',
|
'unexpected',
|
||||||
message,
|
message,
|
||||||
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true],
|
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true],
|
||||||
[],
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -481,7 +481,6 @@ const theExtrude = startSketchOn('XY')
|
|||||||
'undefined_value',
|
'undefined_value',
|
||||||
'memory item key `myVarZ` is not defined',
|
'memory item key `myVarZ` is not defined',
|
||||||
[129, 135, true],
|
[129, 135, true],
|
||||||
[],
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Program,
|
Program,
|
||||||
executor,
|
_executor,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
kclLint,
|
kclLint,
|
||||||
emptyExecState,
|
emptyExecState,
|
||||||
@ -64,7 +64,7 @@ export async function executeAst({
|
|||||||
try {
|
try {
|
||||||
const execState = await (programMemoryOverride
|
const execState = await (programMemoryOverride
|
||||||
? enginelessExecutor(ast, programMemoryOverride)
|
? enginelessExecutor(ast, programMemoryOverride)
|
||||||
: executor(ast, engineCommandManager))
|
: _executor(ast, engineCommandManager))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands()
|
await engineCommandManager.waitForAllCommands()
|
||||||
|
|
||||||
|
@ -806,9 +806,9 @@ sketch001 = startSketchOn('XZ')
|
|||||||
sketch002 = startSketchOn({
|
sketch002 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 1, y = 2, z = 3 },
|
origin = { x = 1, y = 2, z = 3 },
|
||||||
xAxis = { x = 4, y = 5, z = 6 },
|
x_axis = { x = 4, y = 5, z = 6 },
|
||||||
yAxis = { x = 7, y = 8, z = 9 },
|
y_axis = { x = 7, y = 8, z = 9 },
|
||||||
zAxis = { x = 10, y = 11, z = 12 }
|
z_axis = { x = 10, y = 11, z = 12 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|> startProfileAt([-12.55, 2.89], %)
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
@ -862,9 +862,9 @@ sketch001 = startSketchOn('XZ')
|
|||||||
sketch002 = startSketchOn({
|
sketch002 = startSketchOn({
|
||||||
plane = {
|
plane = {
|
||||||
origin = { x = 1, y = 2, z = 3 },
|
origin = { x = 1, y = 2, z = 3 },
|
||||||
xAxis = { x = 4, y = 5, z = 6 },
|
x_axis = { x = 4, y = 5, z = 6 },
|
||||||
yAxis = { x = 7, y = 8, z = 9 },
|
y_axis = { x = 7, y = 8, z = 9 },
|
||||||
zAxis = { x = 10, y = 11, z = 12 }
|
z_axis = { x = 10, y = 11, z = 12 }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|> startProfileAt([-12.55, 2.89], %)
|
|> startProfileAt([-12.55, 2.89], %)
|
||||||
|
@ -374,37 +374,6 @@ export function loftSketches(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addSweep(
|
|
||||||
node: Node<Program>,
|
|
||||||
profileDeclarator: VariableDeclarator,
|
|
||||||
pathDeclarator: VariableDeclarator
|
|
||||||
): {
|
|
||||||
modifiedAst: Node<Program>
|
|
||||||
pathToNode: PathToNode
|
|
||||||
} {
|
|
||||||
const modifiedAst = structuredClone(node)
|
|
||||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.SWEEP)
|
|
||||||
const sweep = createCallExpressionStdLib('sweep', [
|
|
||||||
createObjectExpression({ path: createIdentifier(pathDeclarator.id.name) }),
|
|
||||||
createIdentifier(profileDeclarator.id.name),
|
|
||||||
])
|
|
||||||
const declaration = createVariableDeclaration(name, sweep)
|
|
||||||
modifiedAst.body.push(declaration)
|
|
||||||
const pathToNode: PathToNode = [
|
|
||||||
['body', ''],
|
|
||||||
[modifiedAst.body.length - 1, 'index'],
|
|
||||||
['declaration', 'VariableDeclaration'],
|
|
||||||
['init', 'VariableDeclarator'],
|
|
||||||
['arguments', 'CallExpression'],
|
|
||||||
[0, 'index'],
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
|
||||||
modifiedAst,
|
|
||||||
pathToNode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function revolveSketch(
|
export function revolveSketch(
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
pathToNode: PathToNode,
|
pathToNode: PathToNode,
|
||||||
@ -1180,17 +1149,11 @@ export async function deleteFromSelection(
|
|||||||
((selection?.artifact?.type === 'wall' ||
|
((selection?.artifact?.type === 'wall' ||
|
||||||
selection?.artifact?.type === 'cap') &&
|
selection?.artifact?.type === 'cap') &&
|
||||||
varDec.node.init.type === 'PipeExpression') ||
|
varDec.node.init.type === 'PipeExpression') ||
|
||||||
selection.artifact?.type === 'sweep' ||
|
selection.artifact?.type === 'sweep'
|
||||||
selection.artifact?.type === 'plane' ||
|
|
||||||
!selection.artifact // aka expected to be a shell at this point
|
|
||||||
) {
|
) {
|
||||||
let extrudeNameToDelete = ''
|
let extrudeNameToDelete = ''
|
||||||
let pathToNode: PathToNode | null = null
|
let pathToNode: PathToNode | null = null
|
||||||
if (
|
if (selection.artifact?.type !== 'sweep') {
|
||||||
selection.artifact &&
|
|
||||||
selection.artifact.type !== 'sweep' &&
|
|
||||||
selection.artifact.type !== 'plane'
|
|
||||||
) {
|
|
||||||
const varDecName = varDec.node.id.name
|
const varDecName = varDec.node.id.name
|
||||||
traverse(astClone, {
|
traverse(astClone, {
|
||||||
enter: (node, path) => {
|
enter: (node, path) => {
|
||||||
@ -1206,17 +1169,6 @@ export async function deleteFromSelection(
|
|||||||
pathToNode = path
|
pathToNode = path
|
||||||
extrudeNameToDelete = dec.id.name
|
extrudeNameToDelete = dec.id.name
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
dec.init.type === 'CallExpression' &&
|
|
||||||
dec.init.callee.name === 'loft' &&
|
|
||||||
dec.init.arguments?.[0].type === 'ArrayExpression' &&
|
|
||||||
dec.init.arguments?.[0].elements.some(
|
|
||||||
(a) => a.type === 'Identifier' && a.name === varDecName
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
pathToNode = path
|
|
||||||
extrudeNameToDelete = dec.id.name
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1326,17 +1278,17 @@ export async function deleteFromSelection(
|
|||||||
y: roundLiteral(faceDetails.origin.y),
|
y: roundLiteral(faceDetails.origin.y),
|
||||||
z: roundLiteral(faceDetails.origin.z),
|
z: roundLiteral(faceDetails.origin.z),
|
||||||
}),
|
}),
|
||||||
xAxis: createObjectExpression({
|
x_axis: createObjectExpression({
|
||||||
x: roundLiteral(faceDetails.x_axis.x),
|
x: roundLiteral(faceDetails.x_axis.x),
|
||||||
y: roundLiteral(faceDetails.x_axis.y),
|
y: roundLiteral(faceDetails.x_axis.y),
|
||||||
z: roundLiteral(faceDetails.x_axis.z),
|
z: roundLiteral(faceDetails.x_axis.z),
|
||||||
}),
|
}),
|
||||||
yAxis: createObjectExpression({
|
y_axis: createObjectExpression({
|
||||||
x: roundLiteral(faceDetails.y_axis.x),
|
x: roundLiteral(faceDetails.y_axis.x),
|
||||||
y: roundLiteral(faceDetails.y_axis.y),
|
y: roundLiteral(faceDetails.y_axis.y),
|
||||||
z: roundLiteral(faceDetails.y_axis.z),
|
z: roundLiteral(faceDetails.y_axis.z),
|
||||||
}),
|
}),
|
||||||
zAxis: createObjectExpression({
|
z_axis: createObjectExpression({
|
||||||
x: roundLiteral(faceDetails.z_axis.x),
|
x: roundLiteral(faceDetails.z_axis.x),
|
||||||
y: roundLiteral(faceDetails.z_axis.y),
|
y: roundLiteral(faceDetails.z_axis.y),
|
||||||
z: roundLiteral(faceDetails.z_axis.z),
|
z: roundLiteral(faceDetails.z_axis.z),
|
||||||
|
@ -61,18 +61,19 @@ export interface FilletParameters {
|
|||||||
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
|
export type EdgeTreatmentParameters = ChamferParameters | FilletParameters
|
||||||
|
|
||||||
// Apply Edge Treatment (Fillet or Chamfer) To Selection
|
// Apply Edge Treatment (Fillet or Chamfer) To Selection
|
||||||
export async function applyEdgeTreatmentToSelection(
|
export function applyEdgeTreatmentToSelection(
|
||||||
ast: Node<Program>,
|
ast: Node<Program>,
|
||||||
selection: Selections,
|
selection: Selections,
|
||||||
parameters: EdgeTreatmentParameters
|
parameters: EdgeTreatmentParameters
|
||||||
): Promise<void | Error> {
|
): void | Error {
|
||||||
// 1. clone and modify with edge treatment and tag
|
// 1. clone and modify with edge treatment and tag
|
||||||
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
const result = modifyAstWithEdgeTreatmentAndTag(ast, selection, parameters)
|
||||||
if (err(result)) return result
|
if (err(result)) return result
|
||||||
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
const { modifiedAst, pathToEdgeTreatmentNode } = result
|
||||||
|
|
||||||
// 2. update ast
|
// 2. update ast
|
||||||
await updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
updateAstAndFocus(modifiedAst, pathToEdgeTreatmentNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function modifyAstWithEdgeTreatmentAndTag(
|
export function modifyAstWithEdgeTreatmentAndTag(
|
||||||
@ -290,7 +291,7 @@ export function getPathToExtrudeForSegmentSelection(
|
|||||||
async function updateAstAndFocus(
|
async function updateAstAndFocus(
|
||||||
modifiedAst: Node<Program>,
|
modifiedAst: Node<Program>,
|
||||||
pathToEdgeTreatmentNode: Array<PathToNode>
|
pathToEdgeTreatmentNode: Array<PathToNode>
|
||||||
): Promise<void> {
|
) {
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
focusPath: pathToEdgeTreatmentNode,
|
focusPath: pathToEdgeTreatmentNode,
|
||||||
})
|
})
|
||||||
|
@ -29,9 +29,7 @@ export function revolveSketch(
|
|||||||
pathToSketchNode: PathToNode,
|
pathToSketchNode: PathToNode,
|
||||||
shouldPipe = false,
|
shouldPipe = false,
|
||||||
angle: Expr = createLiteral(4),
|
angle: Expr = createLiteral(4),
|
||||||
axisOrEdge: string,
|
axis: Selections
|
||||||
axis: string,
|
|
||||||
edge: Selections
|
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
modifiedAst: Node<Program>
|
modifiedAst: Node<Program>
|
||||||
@ -43,34 +41,31 @@ export function revolveSketch(
|
|||||||
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
|
||||||
if (err(sketchNode)) return sketchNode
|
if (err(sketchNode)) return sketchNode
|
||||||
|
|
||||||
let generatedAxis
|
// testing code
|
||||||
|
const pathToAxisSelection = getNodePathFromSourceRange(
|
||||||
|
clonedAst,
|
||||||
|
axis.graphSelections[0]?.codeRef.range
|
||||||
|
)
|
||||||
|
|
||||||
if (axisOrEdge === 'Edge') {
|
const lineNode = getNodeFromPath<CallExpression>(
|
||||||
const pathToAxisSelection = getNodePathFromSourceRange(
|
clonedAst,
|
||||||
clonedAst,
|
pathToAxisSelection,
|
||||||
edge.graphSelections[0]?.codeRef.range
|
'CallExpression'
|
||||||
)
|
)
|
||||||
const lineNode = getNodeFromPath<CallExpression>(
|
if (err(lineNode)) return lineNode
|
||||||
clonedAst,
|
|
||||||
pathToAxisSelection,
|
|
||||||
'CallExpression'
|
|
||||||
)
|
|
||||||
if (err(lineNode)) return lineNode
|
|
||||||
|
|
||||||
const tagResult = mutateAstWithTagForSketchSegment(
|
// TODO Kevin: What if |> close(%)?
|
||||||
clonedAst,
|
// TODO Kevin: What if opposite edge
|
||||||
pathToAxisSelection
|
// TODO Kevin: What if the edge isn't planar to the sketch?
|
||||||
)
|
// TODO Kevin: add a tag.
|
||||||
|
const tagResult = mutateAstWithTagForSketchSegment(
|
||||||
|
clonedAst,
|
||||||
|
pathToAxisSelection
|
||||||
|
)
|
||||||
|
|
||||||
// Have the tag whether it is already created or a new one is generated
|
// Have the tag whether it is already created or a new one is generated
|
||||||
if (err(tagResult)) return tagResult
|
if (err(tagResult)) return tagResult
|
||||||
const { tag } = tagResult
|
const { tag } = tagResult
|
||||||
const axisSelection = edge?.graphSelections[0]?.artifact
|
|
||||||
if (!axisSelection) return new Error('Generated axis selection is missing.')
|
|
||||||
generatedAxis = getEdgeTagCall(tag, axisSelection)
|
|
||||||
} else {
|
|
||||||
generatedAxis = createLiteral(axis)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Original Code */
|
/* Original Code */
|
||||||
const { node: sketchExpression } = sketchNode
|
const { node: sketchExpression } = sketchNode
|
||||||
@ -96,12 +91,14 @@ export function revolveSketch(
|
|||||||
shallowPath: sketchPathToDecleration,
|
shallowPath: sketchPathToDecleration,
|
||||||
} = sketchVariableDeclaratorNode
|
} = sketchVariableDeclaratorNode
|
||||||
|
|
||||||
if (!generatedAxis) return new Error('Generated axis selection is missing.')
|
const axisSelection = axis?.graphSelections[0]?.artifact
|
||||||
|
|
||||||
|
if (!axisSelection) return new Error('Axis selection is missing.')
|
||||||
|
|
||||||
const revolveCall = createCallExpressionStdLib('revolve', [
|
const revolveCall = createCallExpressionStdLib('revolve', [
|
||||||
createObjectExpression({
|
createObjectExpression({
|
||||||
angle: angle,
|
angle: angle,
|
||||||
axis: generatedAxis,
|
axis: getEdgeTagCall(tag, axisSelection),
|
||||||
}),
|
}),
|
||||||
createIdentifier(sketchVariableDeclarator.id.name),
|
createIdentifier(sketchVariableDeclarator.id.name),
|
||||||
])
|
])
|
||||||
|
@ -49,27 +49,17 @@ export function addShell({
|
|||||||
return new Error("Couldn't find extrude")
|
return new Error("Couldn't find extrude")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
|
||||||
|
// Get the sketch ref from the selection
|
||||||
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
|
// TODO: this assumes the segment is piped directly from the sketch, with no intermediate `VariableDeclarator` between.
|
||||||
// We must find a technique for these situations that is robust to intermediate declarations
|
// We must find a technique for these situations that is robust to intermediate declarations
|
||||||
const extrudeNode = getNodeFromPath<VariableDeclarator>(
|
const sketchNode = getNodeFromPath<VariableDeclarator>(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
extrudeLookupResult.pathToExtrudeNode,
|
graphSelection.codeRef.pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)
|
)
|
||||||
const segmentNode = getNodeFromPath<VariableDeclarator>(
|
if (err(sketchNode)) {
|
||||||
modifiedAst,
|
return sketchNode
|
||||||
extrudeLookupResult.pathToSegmentNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)
|
|
||||||
if (err(extrudeNode) || err(segmentNode)) {
|
|
||||||
return new Error("Couldn't find extrude")
|
|
||||||
}
|
|
||||||
if (extrudeNode.node.init.type === 'CallExpression') {
|
|
||||||
pathToExtrudeNode = extrudeLookupResult.pathToExtrudeNode
|
|
||||||
} else if (segmentNode.node.init.type === 'PipeExpression') {
|
|
||||||
pathToExtrudeNode = extrudeLookupResult.pathToSegmentNode
|
|
||||||
} else {
|
|
||||||
return new Error("Couldn't find extrude")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedArtifact = graphSelection.artifact
|
const selectedArtifact = graphSelection.artifact
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
import {
|
import { makeDefaultPlanes, assertParse, initPromise, Program } from 'lang/wasm'
|
||||||
makeDefaultPlanes,
|
|
||||||
assertParse,
|
|
||||||
initPromise,
|
|
||||||
Program,
|
|
||||||
ArtifactCommand,
|
|
||||||
ExecState,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import {
|
import {
|
||||||
|
OrderedCommand,
|
||||||
ResponseMap,
|
ResponseMap,
|
||||||
createArtifactGraph,
|
createArtifactGraph,
|
||||||
filterArtifacts,
|
filterArtifacts,
|
||||||
@ -28,7 +22,6 @@ import * as d3 from 'd3-force'
|
|||||||
import path from 'path'
|
import path from 'path'
|
||||||
import pixelmatch from 'pixelmatch'
|
import pixelmatch from 'pixelmatch'
|
||||||
import { PNG } from 'pngjs'
|
import { PNG } from 'pngjs'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Note this is an integration test, these tests connect to our real dev server and make websocket commands.
|
Note this is an integration test, these tests connect to our real dev server and make websocket commands.
|
||||||
@ -115,7 +108,7 @@ sketch002 = startSketchOn(offsetPlane001)
|
|||||||
|> line([6.78, 15.01], %)
|
|> line([6.78, 15.01], %)
|
||||||
`
|
`
|
||||||
|
|
||||||
// add more code snippets here and use `getCommands` to get the artifactCommands and responseMap for more tests
|
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests
|
||||||
const codeToWriteCacheFor = {
|
const codeToWriteCacheFor = {
|
||||||
exampleCode1,
|
exampleCode1,
|
||||||
sketchOnFaceOnFaceEtc,
|
sketchOnFaceOnFaceEtc,
|
||||||
@ -127,9 +120,8 @@ type CodeKey = keyof typeof codeToWriteCacheFor
|
|||||||
|
|
||||||
type CacheShape = {
|
type CacheShape = {
|
||||||
[key in CodeKey]: {
|
[key in CodeKey]: {
|
||||||
artifactCommands: ArtifactCommand[]
|
orderedCommands: OrderedCommand[]
|
||||||
responseMap: ResponseMap
|
responseMap: ResponseMap
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,9 +151,8 @@ beforeAll(async () => {
|
|||||||
await kclManager.executeAst({ ast })
|
await kclManager.executeAst({ ast })
|
||||||
|
|
||||||
cacheToWriteToFileTemp[codeKey] = {
|
cacheToWriteToFileTemp[codeKey] = {
|
||||||
artifactCommands: kclManager.execState.artifactCommands,
|
orderedCommands: engineCommandManager.orderedCommands,
|
||||||
responseMap: engineCommandManager.responseMap,
|
responseMap: engineCommandManager.responseMap,
|
||||||
execStateArtifacts: kclManager.execState.artifacts,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const cache = JSON.stringify(cacheToWriteToFileTemp)
|
const cache = JSON.stringify(cacheToWriteToFileTemp)
|
||||||
@ -180,24 +171,18 @@ afterAll(() => {
|
|||||||
|
|
||||||
describe('testing createArtifactGraph', () => {
|
describe('testing createArtifactGraph', () => {
|
||||||
describe('code with offset planes and a sketch:', () => {
|
describe('code with offset planes and a sketch:', () => {
|
||||||
let ast: Node<Program>
|
let ast: Program
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
|
|
||||||
it('setup', () => {
|
it('setup', () => {
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||||
const {
|
const {
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCodeOffsetPlanes')
|
} = getCommands('exampleCodeOffsetPlanes')
|
||||||
ast = _ast
|
ast = _ast
|
||||||
theMap = createArtifactGraph({
|
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it(`there should be one sketch`, () => {
|
it(`there should be one sketch`, () => {
|
||||||
@ -232,23 +217,17 @@ describe('testing createArtifactGraph', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
describe('code with an extrusion, fillet and sketch of face:', () => {
|
||||||
let ast: Node<Program>
|
let ast: Program
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
it('setup', () => {
|
it('setup', () => {
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||||
const {
|
const {
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCode1')
|
} = getCommands('exampleCode1')
|
||||||
ast = _ast
|
ast = _ast
|
||||||
theMap = createArtifactGraph({
|
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('there should be two planes for the extrusion and the sketch on face', () => {
|
it('there should be two planes for the extrusion and the sketch on face', () => {
|
||||||
@ -333,23 +312,17 @@ describe('testing createArtifactGraph', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe(`code with sketches but no extrusions or other 3D elements`, () => {
|
describe(`code with sketches but no extrusions or other 3D elements`, () => {
|
||||||
let ast: Node<Program>
|
let ast: Program
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
it(`setup`, () => {
|
it(`setup`, () => {
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||||
const {
|
const {
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCodeNo3D')
|
} = getCommands('exampleCodeNo3D')
|
||||||
ast = _ast
|
ast = _ast
|
||||||
theMap = createArtifactGraph({
|
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('there should be two planes, one for each sketch path', () => {
|
it('there should be two planes, one for each sketch path', () => {
|
||||||
@ -404,23 +377,17 @@ describe('testing createArtifactGraph', () => {
|
|||||||
|
|
||||||
describe('capture graph of sketchOnFaceOnFace...', () => {
|
describe('capture graph of sketchOnFaceOnFace...', () => {
|
||||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
describe('code with an extrusion, fillet and sketch of face:', () => {
|
||||||
let ast: Node<Program>
|
let ast: Program
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
it('setup', async () => {
|
it('setup', async () => {
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||||
const {
|
const {
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('sketchOnFaceOnFaceEtc')
|
} = getCommands('sketchOnFaceOnFaceEtc')
|
||||||
ast = _ast
|
ast = _ast
|
||||||
theMap = createArtifactGraph({
|
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Ostensibly this takes a screen shot of the graph of the artifactGraph
|
// Ostensibly this takes a screen shot of the graph of the artifactGraph
|
||||||
// but it's it also tests that all of the id links are correct because if one
|
// but it's it also tests that all of the id links are correct because if one
|
||||||
@ -432,21 +399,17 @@ describe('capture graph of sketchOnFaceOnFace...', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function getCommands(
|
function getCommands(codeKey: CodeKey): CacheShape[CodeKey] & { ast: Program } {
|
||||||
codeKey: CodeKey
|
|
||||||
): CacheShape[CodeKey] & { ast: Node<Program> } {
|
|
||||||
const ast = assertParse(codeKey)
|
const ast = assertParse(codeKey)
|
||||||
const file = fs.readFileSync(fullPath, 'utf-8')
|
const file = fs.readFileSync(fullPath, 'utf-8')
|
||||||
const parsed: CacheShape = JSON.parse(file)
|
const parsed: CacheShape = JSON.parse(file)
|
||||||
// these either already exist from the last run, or were created in
|
// these either already exist from the last run, or were created in
|
||||||
const artifactCommands = parsed[codeKey].artifactCommands
|
const orderedCommands = parsed[codeKey].orderedCommands
|
||||||
const responseMap = parsed[codeKey].responseMap
|
const responseMap = parsed[codeKey].responseMap
|
||||||
const execStateArtifacts = parsed[codeKey].execStateArtifacts
|
|
||||||
return {
|
return {
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -672,30 +635,20 @@ async function GraphTheGraph(
|
|||||||
|
|
||||||
describe('testing getArtifactsToUpdate', () => {
|
describe('testing getArtifactsToUpdate', () => {
|
||||||
it('should return an array of artifacts to update', () => {
|
it('should return an array of artifacts to update', () => {
|
||||||
const { artifactCommands, responseMap, ast, execStateArtifacts } =
|
const { orderedCommands, responseMap, ast } = getCommands('exampleCode1')
|
||||||
getCommands('exampleCode1')
|
const map = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
const map = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
const getArtifact = (id: string) => map.get(id)
|
const getArtifact = (id: string) => map.get(id)
|
||||||
const currentPlaneId = 'UUID-1'
|
const currentPlaneId = 'UUID-1'
|
||||||
const getUpdateObjects = (type: Models['ModelingCmd_type']['type']) => {
|
const getUpdateObjects = (type: Models['ModelingCmd_type']['type']) => {
|
||||||
const artifactCommand = artifactCommands.find(
|
|
||||||
(a) => a.command.type === type
|
|
||||||
)
|
|
||||||
if (!artifactCommand) {
|
|
||||||
throw new Error(`No artifactCommand found for ${type}`)
|
|
||||||
}
|
|
||||||
const artifactsToUpdate = getArtifactsToUpdate({
|
const artifactsToUpdate = getArtifactsToUpdate({
|
||||||
artifactCommand,
|
orderedCommand: orderedCommands.find(
|
||||||
|
(a) =>
|
||||||
|
a.command.type === 'modeling_cmd_req' && a.command.cmd.type === type
|
||||||
|
)!,
|
||||||
responseMap,
|
responseMap,
|
||||||
getArtifact,
|
getArtifact,
|
||||||
currentPlaneId,
|
currentPlaneId,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
})
|
})
|
||||||
return artifactsToUpdate.map(({ artifact }) => artifact)
|
return artifactsToUpdate.map(({ artifact }) => artifact)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,7 @@
|
|||||||
import {
|
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||||
ArtifactCommand,
|
|
||||||
ExecState,
|
|
||||||
PathToNode,
|
|
||||||
Program,
|
|
||||||
SourceRange,
|
|
||||||
sourceRangeFromRust,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|
||||||
|
|
||||||
export type ArtifactId = string
|
export type ArtifactId = string
|
||||||
|
|
||||||
@ -77,7 +69,7 @@ interface SegmentArtifactRich extends BaseArtifact {
|
|||||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
||||||
interface SweepArtifact extends BaseArtifact {
|
interface SweepArtifact extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
subType: 'extrusion' | 'revolve'
|
||||||
pathId: string
|
pathId: string
|
||||||
surfaceIds: Array<string>
|
surfaceIds: Array<string>
|
||||||
edgeIds: Array<string>
|
edgeIds: Array<string>
|
||||||
@ -85,7 +77,7 @@ interface SweepArtifact extends BaseArtifact {
|
|||||||
}
|
}
|
||||||
interface SweepArtifactRich extends BaseArtifact {
|
interface SweepArtifactRich extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
subType: 'extrusion' | 'revolve'
|
||||||
path: PathArtifact
|
path: PathArtifact
|
||||||
surfaces: Array<WallArtifact | CapArtifact>
|
surfaces: Array<WallArtifact | CapArtifact>
|
||||||
edges: Array<SweepEdge>
|
edges: Array<SweepEdge>
|
||||||
@ -151,47 +143,50 @@ type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
|||||||
export interface ResponseMap {
|
export interface ResponseMap {
|
||||||
[commandId: string]: OkWebSocketResponseData
|
[commandId: string]: OkWebSocketResponseData
|
||||||
}
|
}
|
||||||
|
export interface OrderedCommand {
|
||||||
|
command: EngineCommand
|
||||||
|
range: SourceRange
|
||||||
|
}
|
||||||
|
|
||||||
/** Creates a graph of artifacts from a list of ordered commands and their responses
|
/** Creates a graph of artifacts from a list of ordered commands and their responses
|
||||||
* muting the Map should happen entirely this function, other functions called within
|
* muting the Map should happen entirely this function, other functions called within
|
||||||
* should return data on how to update the map, and not do so directly.
|
* should return data on how to update the map, and not do so directly.
|
||||||
*/
|
*/
|
||||||
export function createArtifactGraph({
|
export function createArtifactGraph({
|
||||||
artifactCommands,
|
orderedCommands,
|
||||||
responseMap,
|
responseMap,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
}: {
|
}: {
|
||||||
artifactCommands: Array<ArtifactCommand>
|
orderedCommands: Array<OrderedCommand>
|
||||||
responseMap: ResponseMap
|
responseMap: ResponseMap
|
||||||
ast: Node<Program>
|
ast: Program
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}) {
|
}) {
|
||||||
const myMap = new Map<ArtifactId, Artifact>()
|
const myMap = new Map<ArtifactId, Artifact>()
|
||||||
|
|
||||||
/** see docstring for {@link getArtifactsToUpdate} as to why this is needed */
|
/** see docstring for {@link getArtifactsToUpdate} as to why this is needed */
|
||||||
let currentPlaneId = ''
|
let currentPlaneId = ''
|
||||||
|
|
||||||
for (const artifactCommand of artifactCommands) {
|
orderedCommands.forEach((orderedCommand) => {
|
||||||
if (artifactCommand.command.type === 'enable_sketch_mode') {
|
if (orderedCommand.command?.type === 'modeling_cmd_req') {
|
||||||
currentPlaneId = artifactCommand.command.entity_id
|
if (orderedCommand.command.cmd.type === 'enable_sketch_mode') {
|
||||||
}
|
currentPlaneId = orderedCommand.command.cmd.entity_id
|
||||||
if (artifactCommand.command.type === 'sketch_mode_disable') {
|
}
|
||||||
currentPlaneId = ''
|
if (orderedCommand.command.cmd.type === 'sketch_mode_disable') {
|
||||||
|
currentPlaneId = ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const artifactsToUpdate = getArtifactsToUpdate({
|
const artifactsToUpdate = getArtifactsToUpdate({
|
||||||
artifactCommand,
|
orderedCommand,
|
||||||
responseMap,
|
responseMap,
|
||||||
getArtifact: (id: ArtifactId) => myMap.get(id),
|
getArtifact: (id: ArtifactId) => myMap.get(id),
|
||||||
currentPlaneId,
|
currentPlaneId,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
})
|
})
|
||||||
artifactsToUpdate.forEach(({ id, artifact }) => {
|
artifactsToUpdate.forEach(({ id, artifact }) => {
|
||||||
const mergedArtifact = mergeArtifacts(myMap.get(id), artifact)
|
const mergedArtifact = mergeArtifacts(myMap.get(id), artifact)
|
||||||
myMap.set(id, mergedArtifact)
|
myMap.set(id, mergedArtifact)
|
||||||
})
|
})
|
||||||
}
|
})
|
||||||
return myMap
|
return myMap
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,30 +227,30 @@ function mergeArtifacts(
|
|||||||
* can remove this.
|
* can remove this.
|
||||||
*/
|
*/
|
||||||
export function getArtifactsToUpdate({
|
export function getArtifactsToUpdate({
|
||||||
artifactCommand,
|
orderedCommand: { command, range },
|
||||||
getArtifact,
|
getArtifact,
|
||||||
responseMap,
|
responseMap,
|
||||||
currentPlaneId,
|
currentPlaneId,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
}: {
|
}: {
|
||||||
artifactCommand: ArtifactCommand
|
orderedCommand: OrderedCommand
|
||||||
responseMap: ResponseMap
|
responseMap: ResponseMap
|
||||||
/** Passing in a getter because we don't wan this function to update the map directly */
|
/** Passing in a getter because we don't wan this function to update the map directly */
|
||||||
getArtifact: (id: ArtifactId) => Artifact | undefined
|
getArtifact: (id: ArtifactId) => Artifact | undefined
|
||||||
currentPlaneId: ArtifactId
|
currentPlaneId: ArtifactId
|
||||||
ast: Node<Program>
|
ast: Program
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}): Array<{
|
}): Array<{
|
||||||
id: ArtifactId
|
id: ArtifactId
|
||||||
artifact: Artifact
|
artifact: Artifact
|
||||||
}> {
|
}> {
|
||||||
const range = sourceRangeFromRust(artifactCommand.range)
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
|
|
||||||
const id = artifactCommand.cmdId
|
// expect all to be `modeling_cmd_req` as batch commands have
|
||||||
|
// already been expanded before being added to orderedCommands
|
||||||
|
if (command.type !== 'modeling_cmd_req') return []
|
||||||
|
const id = command.cmd_id
|
||||||
const response = responseMap[id]
|
const response = responseMap[id]
|
||||||
const cmd = artifactCommand.command
|
const cmd = command.cmd
|
||||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
||||||
if (!response) return returnArr
|
if (!response) return returnArr
|
||||||
if (cmd.type === 'make_plane' && range[1] !== 0) {
|
if (cmd.type === 'make_plane' && range[1] !== 0) {
|
||||||
@ -377,11 +372,7 @@ export function getArtifactsToUpdate({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
return returnArr
|
return returnArr
|
||||||
} else if (
|
} else if (cmd.type === 'extrude' || cmd.type === 'revolve') {
|
||||||
cmd.type === 'extrude' ||
|
|
||||||
cmd.type === 'revolve' ||
|
|
||||||
cmd.type === 'sweep'
|
|
||||||
) {
|
|
||||||
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
||||||
returnArr.push({
|
returnArr.push({
|
||||||
id,
|
id,
|
||||||
@ -402,33 +393,6 @@ export function getArtifactsToUpdate({
|
|||||||
artifact: { ...path, sweepId: id },
|
artifact: { ...path, sweepId: id },
|
||||||
})
|
})
|
||||||
return returnArr
|
return returnArr
|
||||||
} else if (
|
|
||||||
cmd.type === 'loft' &&
|
|
||||||
response.type === 'modeling' &&
|
|
||||||
response.data.modeling_response.type === 'loft'
|
|
||||||
) {
|
|
||||||
returnArr.push({
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'loft',
|
|
||||||
id,
|
|
||||||
// TODO: make sure to revisit this choice, don't think it matters for now
|
|
||||||
pathId: cmd.section_ids[0],
|
|
||||||
surfaceIds: [],
|
|
||||||
edgeIds: [],
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
for (const sectionId of cmd.section_ids) {
|
|
||||||
const path = getArtifact(sectionId)
|
|
||||||
if (path?.type === 'path')
|
|
||||||
returnArr.push({
|
|
||||||
id: sectionId,
|
|
||||||
artifact: { ...path, sweepId: id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return returnArr
|
|
||||||
} else if (
|
} else if (
|
||||||
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
||||||
response?.type === 'modeling' &&
|
response?.type === 'modeling' &&
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
ArtifactCommand,
|
|
||||||
defaultRustSourceRange,
|
defaultRustSourceRange,
|
||||||
ExecState,
|
defaultSourceRange,
|
||||||
Program,
|
Program,
|
||||||
RustSourceRange,
|
RustSourceRange,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
sourceRangeFromRust,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_DEV_TOKEN } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
@ -20,6 +20,7 @@ import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
|||||||
import {
|
import {
|
||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
EngineCommand,
|
EngineCommand,
|
||||||
|
OrderedCommand,
|
||||||
ResponseMap,
|
ResponseMap,
|
||||||
createArtifactGraph,
|
createArtifactGraph,
|
||||||
} from 'lang/std/artifactGraph'
|
} from 'lang/std/artifactGraph'
|
||||||
@ -36,7 +37,6 @@ import { KclManager } from 'lang/KclSingleton'
|
|||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
import { MachineManager } from 'components/MachineManagerProvider'
|
import { MachineManager } from 'components/MachineManagerProvider'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
// TODO(paultag): This ought to be tweakable.
|
||||||
const pingIntervalMs = 5_000
|
const pingIntervalMs = 5_000
|
||||||
@ -1303,7 +1303,7 @@ export enum EngineCommandManagerEvents {
|
|||||||
*
|
*
|
||||||
* As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response.
|
* As commands are send their state is tracked in {@link pendingCommands} and clear as soon as we receive a response.
|
||||||
*
|
*
|
||||||
* Also all commands that are sent are kept track of in WASM artifactCommands and their responses are kept in {@link responseMap}
|
* Also all commands that are sent are kept track of in {@link orderedCommands} and their responses are kept in {@link responseMap}
|
||||||
* Both of these data structures are used to process the {@link artifactGraph}.
|
* Both of these data structures are used to process the {@link artifactGraph}.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -1329,7 +1329,12 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
[commandId: string]: PendingMessage
|
[commandId: string]: PendingMessage
|
||||||
} = {}
|
} = {}
|
||||||
/**
|
/**
|
||||||
* A map of the responses to the WASM artifactCommands, when processing the commands into the artifactGraph, this response map allow
|
* The orderedCommands array of all the the commands sent to the engine, un-folded from batches, and made into one long
|
||||||
|
* list of the individual commands, this is used to process all the commands into the artifactGraph
|
||||||
|
*/
|
||||||
|
orderedCommands: Array<OrderedCommand> = []
|
||||||
|
/**
|
||||||
|
* A map of the responses to the {@link orderedCommands}, when processing the commands into the artifactGraph, this response map allow
|
||||||
* us to look up the response by command id
|
* us to look up the response by command id
|
||||||
*/
|
*/
|
||||||
responseMap: ResponseMap = {}
|
responseMap: ResponseMap = {}
|
||||||
@ -1825,6 +1830,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
async startNewSession() {
|
async startNewSession() {
|
||||||
|
this.orderedCommands = []
|
||||||
this.responseMap = {}
|
this.responseMap = {}
|
||||||
await this.initPlanes()
|
await this.initPlanes()
|
||||||
}
|
}
|
||||||
@ -2067,6 +2073,28 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
isSceneCommand,
|
isSceneCommand,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.command.type === 'modeling_cmd_req') {
|
||||||
|
this.orderedCommands.push({
|
||||||
|
command: message.command,
|
||||||
|
range: sourceRangeFromRust(message.range),
|
||||||
|
})
|
||||||
|
} else if (message.command.type === 'modeling_cmd_batch_req') {
|
||||||
|
message.command.requests.forEach((req) => {
|
||||||
|
const cmdId = req.cmd_id || ''
|
||||||
|
const range = cmdId
|
||||||
|
? sourceRangeFromRust(message.idToRangeMap[cmdId])
|
||||||
|
: defaultSourceRange()
|
||||||
|
const cmd: EngineCommand = {
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: req.cmd_id,
|
||||||
|
cmd: req.cmd,
|
||||||
|
}
|
||||||
|
this.orderedCommands.push({
|
||||||
|
command: cmd,
|
||||||
|
range,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
this.engineConnection?.send(message.command)
|
this.engineConnection?.send(message.command)
|
||||||
return promise
|
return promise
|
||||||
}
|
}
|
||||||
@ -2087,16 +2115,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
Object.values(this.pendingCommands).map((a) => a.promise)
|
Object.values(this.pendingCommands).map((a) => a.promise)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateArtifactGraph(
|
updateArtifactGraph(ast: Program) {
|
||||||
ast: Node<Program>,
|
|
||||||
artifactCommands: ArtifactCommand[],
|
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
) {
|
|
||||||
this.artifactGraph = createArtifactGraph({
|
this.artifactGraph = createArtifactGraph({
|
||||||
artifactCommands,
|
orderedCommands: this.orderedCommands,
|
||||||
responseMap: this.responseMap,
|
responseMap: this.responseMap,
|
||||||
ast,
|
ast,
|
||||||
execStateArtifacts,
|
|
||||||
})
|
})
|
||||||
// TODO check if these still need to be deferred once e2e tests are working again.
|
// TODO check if these still need to be deferred once e2e tests are working again.
|
||||||
if (this.artifactGraph.size) {
|
if (this.artifactGraph.size) {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import {
|
import init, {
|
||||||
init,
|
|
||||||
parse_wasm,
|
parse_wasm,
|
||||||
recast_wasm,
|
recast_wasm,
|
||||||
execute,
|
execute,
|
||||||
@ -17,9 +16,7 @@ import {
|
|||||||
default_project_settings,
|
default_project_settings,
|
||||||
base64_decode,
|
base64_decode,
|
||||||
clear_scene_and_bust_cache,
|
clear_scene_and_bust_cache,
|
||||||
reloadModule,
|
} from '../wasm-lib/pkg/wasm_lib'
|
||||||
} from 'lib/wasm_lib_wrapper'
|
|
||||||
|
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
@ -48,13 +45,7 @@ import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRang
|
|||||||
import { getAllCurrentSettings } from 'lib/settings/settingsUtils'
|
import { getAllCurrentSettings } from 'lib/settings/settingsUtils'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
import { KclErrorWithOutputs } from 'wasm-lib/kcl/bindings/KclErrorWithOutputs'
|
import { KclErrorWithOutputs } from 'wasm-lib/kcl/bindings/KclErrorWithOutputs'
|
||||||
import { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
|
||||||
import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
|
||||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
|
||||||
export type { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
|
||||||
export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
export type { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
export type { Expr } from '../wasm-lib/kcl/bindings/Expr'
|
||||||
@ -153,7 +144,6 @@ export const wasmUrl = () => {
|
|||||||
// Initialise the wasm module.
|
// Initialise the wasm module.
|
||||||
const initialise = async () => {
|
const initialise = async () => {
|
||||||
try {
|
try {
|
||||||
await reloadModule()
|
|
||||||
const fullUrl = wasmUrl()
|
const fullUrl = wasmUrl()
|
||||||
const input = await fetch(fullUrl)
|
const input = await fetch(fullUrl)
|
||||||
const buffer = await input.arrayBuffer()
|
const buffer = await input.arrayBuffer()
|
||||||
@ -233,7 +223,6 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
|||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -258,8 +247,6 @@ export const isPathToNodeNumber = (
|
|||||||
export interface ExecState {
|
export interface ExecState {
|
||||||
memory: ProgramMemory
|
memory: ProgramMemory
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
artifacts: { [key in ArtifactId]?: Artifact }
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -270,8 +257,6 @@ export function emptyExecState(): ExecState {
|
|||||||
return {
|
return {
|
||||||
memory: ProgramMemory.empty(),
|
memory: ProgramMemory.empty(),
|
||||||
operations: [],
|
operations: [],
|
||||||
artifacts: {},
|
|
||||||
artifactCommands: [],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -279,8 +264,6 @@ function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
|||||||
return {
|
return {
|
||||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||||
operations: execOutcome.operations,
|
operations: execOutcome.operations,
|
||||||
artifacts: execOutcome.artifacts,
|
|
||||||
artifactCommands: execOutcome.artifactCommands,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,6 +506,22 @@ export const executor = async (
|
|||||||
return Promise.reject(programMemoryOverride)
|
return Promise.reject(programMemoryOverride)
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
engineCommandManager.startNewSession()
|
||||||
|
const _programMemory = await _executor(
|
||||||
|
node,
|
||||||
|
engineCommandManager,
|
||||||
|
programMemoryOverride
|
||||||
|
)
|
||||||
|
await engineCommandManager.waitForAllCommands()
|
||||||
|
|
||||||
|
return _programMemory
|
||||||
|
}
|
||||||
|
|
||||||
|
export const _executor = async (
|
||||||
|
node: Node<Program>,
|
||||||
|
engineCommandManager: EngineCommandManager,
|
||||||
|
programMemoryOverride: ProgramMemory | Error | null = null
|
||||||
|
): Promise<ExecState> => {
|
||||||
if (programMemoryOverride !== null && err(programMemoryOverride))
|
if (programMemoryOverride !== null && err(programMemoryOverride))
|
||||||
return Promise.reject(programMemoryOverride)
|
return Promise.reject(programMemoryOverride)
|
||||||
|
|
||||||
@ -551,8 +550,7 @@ export const executor = async (
|
|||||||
parsed.error.kind,
|
parsed.error.kind,
|
||||||
parsed.error.msg,
|
parsed.error.msg,
|
||||||
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
||||||
parsed.operations,
|
parsed.operations
|
||||||
parsed.artifactCommands
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return Promise.reject(kclError)
|
return Promise.reject(kclError)
|
||||||
@ -612,7 +610,6 @@ export const modifyAstForSketch = async (
|
|||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -682,7 +679,6 @@ export function programMemoryInit(): ProgramMemory | Error {
|
|||||||
parsed.kind,
|
parsed.kind,
|
||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -37,10 +37,6 @@ export type ModelingCommandSchema = {
|
|||||||
// result: (typeof EXTRUSION_RESULTS)[number]
|
// result: (typeof EXTRUSION_RESULTS)[number]
|
||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
Sweep: {
|
|
||||||
path: Selections
|
|
||||||
profile: Selections
|
|
||||||
}
|
|
||||||
Loft: {
|
Loft: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
}
|
}
|
||||||
@ -51,9 +47,7 @@ export type ModelingCommandSchema = {
|
|||||||
Revolve: {
|
Revolve: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
angle: KclCommandValue
|
angle: KclCommandValue
|
||||||
axisOrEdge: string
|
axis: Selections
|
||||||
axis: string
|
|
||||||
edge: Selections
|
|
||||||
}
|
}
|
||||||
Fillet: {
|
Fillet: {
|
||||||
// todo
|
// todo
|
||||||
@ -296,33 +290,6 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Sweep: {
|
|
||||||
description:
|
|
||||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
|
||||||
icon: 'sweep',
|
|
||||||
status: 'development',
|
|
||||||
needsReview: true,
|
|
||||||
args: {
|
|
||||||
profile: {
|
|
||||||
inputType: 'selection',
|
|
||||||
selectionTypes: ['solid2D'],
|
|
||||||
required: true,
|
|
||||||
skip: true,
|
|
||||||
multiple: false,
|
|
||||||
// TODO: add dry-run validation
|
|
||||||
warningMessage:
|
|
||||||
'The sweep workflow is new and under tested. Please break it and report issues.',
|
|
||||||
},
|
|
||||||
path: {
|
|
||||||
inputType: 'selection',
|
|
||||||
selectionTypes: ['segment', 'path'],
|
|
||||||
required: true,
|
|
||||||
skip: true,
|
|
||||||
multiple: false,
|
|
||||||
// TODO: add dry-run validation
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Loft: {
|
Loft: {
|
||||||
description: 'Create a 3D body by blending between two or more sketches',
|
description: 'Create a 3D body by blending between two or more sketches',
|
||||||
icon: 'loft',
|
icon: 'loft',
|
||||||
@ -357,10 +324,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// TODO: Update this configuration, copied from extrude for MVP of revolve, specifically the args.selection
|
||||||
Revolve: {
|
Revolve: {
|
||||||
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
description: 'Create a 3D body by rotating a sketch region about an axis.',
|
||||||
icon: 'revolve',
|
icon: 'revolve',
|
||||||
status: 'development',
|
|
||||||
needsReview: true,
|
needsReview: true,
|
||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
@ -369,34 +336,9 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
warningMessage:
|
|
||||||
'The revolve workflow is new and under tested. Please break it and report issues.',
|
|
||||||
},
|
|
||||||
axisOrEdge: {
|
|
||||||
inputType: 'options',
|
|
||||||
required: true,
|
|
||||||
defaultValue: 'Axis',
|
|
||||||
options: [
|
|
||||||
{ name: 'Axis', isCurrent: true, value: 'Axis' },
|
|
||||||
{ name: 'Edge', isCurrent: false, value: 'Edge' },
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
axis: {
|
axis: {
|
||||||
required: (commandContext) =>
|
required: true,
|
||||||
['Axis'].includes(
|
|
||||||
commandContext.argumentsToSubmit.axisOrEdge as string
|
|
||||||
),
|
|
||||||
inputType: 'options',
|
|
||||||
options: [
|
|
||||||
{ name: 'X Axis', isCurrent: true, value: 'X' },
|
|
||||||
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
edge: {
|
|
||||||
required: (commandContext) =>
|
|
||||||
['Edge'].includes(
|
|
||||||
commandContext.argumentsToSubmit.axisOrEdge as string
|
|
||||||
),
|
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
selectionTypes: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||||
multiple: false,
|
multiple: false,
|
||||||
|
@ -68,7 +68,7 @@ export const revolveAxisValidator = async ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sketchSelection = artifact.pathId
|
const sketchSelection = artifact.pathId
|
||||||
let edgeSelection = data.edge.graphSelections[0].artifact?.id
|
let edgeSelection = data.axis.graphSelections[0].artifact?.id
|
||||||
|
|
||||||
if (!sketchSelection) {
|
if (!sketchSelection) {
|
||||||
return 'Unable to revolve, sketch is missing'
|
return 'Unable to revolve, sketch is missing'
|
||||||
@ -101,7 +101,7 @@ export const revolveAxisValidator = async ({
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
// return error message for the toast
|
// return error message for the toast
|
||||||
return 'Unable to revolve with selected edge'
|
return 'Unable to revolve with selected axis'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +53,6 @@ export const KCL_DEFAULT_CONSTANT_PREFIXES = {
|
|||||||
SKETCH: 'sketch',
|
SKETCH: 'sketch',
|
||||||
EXTRUDE: 'extrude',
|
EXTRUDE: 'extrude',
|
||||||
LOFT: 'loft',
|
LOFT: 'loft',
|
||||||
SWEEP: 'sweep',
|
|
||||||
SHELL: 'shell',
|
SHELL: 'shell',
|
||||||
SEGMENT: 'seg',
|
SEGMENT: 'seg',
|
||||||
REVOLVE: 'revolve',
|
REVOLVE: 'revolve',
|
||||||
|
@ -15,7 +15,6 @@ import {
|
|||||||
StateMachineCommandSetSchema,
|
StateMachineCommandSetSchema,
|
||||||
} from './commandTypes'
|
} from './commandTypes'
|
||||||
import { DEV } from 'env'
|
import { DEV } from 'env'
|
||||||
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
|
|
||||||
|
|
||||||
interface CreateMachineCommandProps<
|
interface CreateMachineCommandProps<
|
||||||
T extends AnyStateMachine,
|
T extends AnyStateMachine,
|
||||||
@ -85,7 +84,7 @@ export function createMachineCommand<
|
|||||||
} else if ('status' in commandConfig) {
|
} else if ('status' in commandConfig) {
|
||||||
const { status } = commandConfig
|
const { status } = commandConfig
|
||||||
if (status === 'inactive') return null
|
if (status === 'inactive') return null
|
||||||
if (status === 'development' && !(DEV || IS_NIGHTLY_OR_DEBUG)) return null
|
if (status === 'development' && !DEV) return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
const icon = ('icon' in commandConfig && commandConfig.icon) || undefined
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
import { kclManager } from 'lib/singletons'
|
|
||||||
import { reloadModule, getModule } from 'lib/wasm_lib_wrapper'
|
|
||||||
import toast from 'react-hot-toast'
|
|
||||||
import { reportRejection } from './trap'
|
|
||||||
|
|
||||||
let initialized = false
|
|
||||||
|
|
||||||
/**
|
|
||||||
* WASM/Rust runtime can panic and the original try/catch/finally blocks will not trigger
|
|
||||||
* on the await promise. The interface will killed. This means we need to catch the error at
|
|
||||||
* the global/DOM level. This will have to interface with whatever controlflow that needs to be picked up
|
|
||||||
* within the error branch in the typescript to cover the application state.
|
|
||||||
*/
|
|
||||||
export const initializeWindowExceptionHandler = () => {
|
|
||||||
if (window && !initialized) {
|
|
||||||
window.addEventListener('error', (event) => {
|
|
||||||
void (async () => {
|
|
||||||
if (matchImportExportErrorCrash(event.message)) {
|
|
||||||
// do global singleton cleanup
|
|
||||||
kclManager.executeAstCleanUp()
|
|
||||||
toast.error(
|
|
||||||
'You have hit a KCL execution bug! Put your KCL code in a github issue to help us resolve this bug.'
|
|
||||||
)
|
|
||||||
try {
|
|
||||||
await reloadModule()
|
|
||||||
await getModule().default()
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Failed to initialize wasm_lib')
|
|
||||||
console.error(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})().catch(reportRejection)
|
|
||||||
})
|
|
||||||
// Make sure we only initialize this event listener once
|
|
||||||
initialized = true
|
|
||||||
} else {
|
|
||||||
console.error(
|
|
||||||
`Failed to initialize, window: ${window}, initialized:${initialized}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifically match a substring of the message error to detect an import export runtime issue
|
|
||||||
* when the WASM runtime panics
|
|
||||||
*/
|
|
||||||
const matchImportExportErrorCrash = (message: string): boolean => {
|
|
||||||
// called `Result::unwrap_throw()` on an `Err` value
|
|
||||||
const substringError = '`Result::unwrap_throw()` on an `Err` value'
|
|
||||||
return message.indexOf(substringError) !== -1 ? true : false
|
|
||||||
}
|
|
2
src/lib/machine-api.d.ts
vendored
@ -155,7 +155,7 @@ export interface components {
|
|||||||
color?: string | null
|
color?: string | null
|
||||||
/** @description The material that the filament is made of. */
|
/** @description The material that the filament is made of. */
|
||||||
material: components['schemas']['FilamentMaterial']
|
material: components['schemas']['FilamentMaterial']
|
||||||
/** @description The name of the filament, this is likely specific to the manufacturer. */
|
/** @description The name of the filament, this is likely specfic to the manufacturer. */
|
||||||
name?: string | null
|
name?: string | null
|
||||||
}
|
}
|
||||||
/** @description The material that the filament is made of. */
|
/** @description The material that the filament is made of. */
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
import { MarkedOptions, Renderer, unescape } from '@ts-stack/markdown'
|
|
||||||
import { openExternalBrowserIfDesktop } from './openWindow'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main goal of this custom renderer is to prevent links from changing the current location
|
|
||||||
* this is specially important for the desktop app.
|
|
||||||
*/
|
|
||||||
export class SafeRenderer extends Renderer {
|
|
||||||
constructor(options: MarkedOptions) {
|
|
||||||
super(options)
|
|
||||||
|
|
||||||
// Attach a global function for non-react anchor elements that need safe navigation
|
|
||||||
window.openExternalLink = (e: React.MouseEvent<HTMLAnchorElement>) => {
|
|
||||||
openExternalBrowserIfDesktop()(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extended from https://github.com/ts-stack/markdown/blob/c5c1925c1153ca2fe9051c356ef0ddc60b3e1d6a/packages/markdown/src/renderer.ts#L116
|
|
||||||
link(href: string, title: string, text: string): string {
|
|
||||||
if (this.options.sanitize) {
|
|
||||||
let prot: string
|
|
||||||
|
|
||||||
try {
|
|
||||||
prot = decodeURIComponent(unescape(href))
|
|
||||||
.replace(/[^\w:]/g, '')
|
|
||||||
.toLowerCase()
|
|
||||||
} catch (e) {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
// eslint-disable-next-line no-script-url
|
|
||||||
prot.indexOf('javascript:') === 0 ||
|
|
||||||
prot.indexOf('vbscript:') === 0 ||
|
|
||||||
prot.indexOf('data:') === 0
|
|
||||||
) {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let out =
|
|
||||||
'<a onclick="openExternalLink(event)" target="_blank" href="' + href + '"'
|
|
||||||
|
|
||||||
if (title) {
|
|
||||||
out += ' title="' + title + '"'
|
|
||||||
}
|
|
||||||
|
|
||||||
out += '>' + text + '</a>'
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,24 +1,6 @@
|
|||||||
function takeScreenshotOfVideoStreamCanvas() {
|
import html2canvas from 'html2canvas-pro'
|
||||||
const canvas = document.querySelector('[data-engine]')
|
|
||||||
const video = document.getElementById('video-stream')
|
|
||||||
if (
|
|
||||||
canvas &&
|
|
||||||
video &&
|
|
||||||
canvas instanceof HTMLCanvasElement &&
|
|
||||||
video instanceof HTMLVideoElement
|
|
||||||
) {
|
|
||||||
const videoCanvas = document.createElement('canvas')
|
|
||||||
videoCanvas.width = canvas.width
|
|
||||||
videoCanvas.height = canvas.height
|
|
||||||
const context = videoCanvas.getContext('2d')
|
|
||||||
context?.drawImage(video, 0, 0, videoCanvas.width, videoCanvas.height)
|
|
||||||
const url = videoCanvas.toDataURL('image/png')
|
|
||||||
return url
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Return a data URL (png format) of the screenshot of the current page.
|
||||||
export default async function screenshot(): Promise<string> {
|
export default async function screenshot(): Promise<string> {
|
||||||
if (typeof window === 'undefined') {
|
if (typeof window === 'undefined') {
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
@ -27,17 +9,11 @@ export default async function screenshot(): Promise<string> {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return html2canvas(document.documentElement)
|
||||||
if (window.electron) {
|
.then((canvas) => {
|
||||||
const canvas = document.querySelector('[data-engine]')
|
return canvas.toDataURL()
|
||||||
if (canvas instanceof HTMLCanvasElement) {
|
})
|
||||||
const url = await window.electron.takeElectronWindowScreenshot({
|
.catch((error) => {
|
||||||
width: canvas?.width || 500,
|
return Promise.reject(error)
|
||||||
height: canvas?.height || 500,
|
})
|
||||||
})
|
|
||||||
return url !== '' ? url : takeScreenshotOfVideoStreamCanvas()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return takeScreenshotOfVideoStreamCanvas()
|
|
||||||
}
|
}
|
||||||
|
@ -323,8 +323,7 @@ export function handleSelectionBatch({
|
|||||||
resetAndSetEngineEntitySelectionCmds(selectionToEngine)
|
resetAndSetEngineEntitySelectionCmds(selectionToEngine)
|
||||||
selections.graphSelections.forEach(({ codeRef }) => {
|
selections.graphSelections.forEach(({ codeRef }) => {
|
||||||
if (codeRef.range?.[1]) {
|
if (codeRef.range?.[1]) {
|
||||||
const safeEnd = Math.min(codeRef.range[1], codeManager.code.length)
|
ranges.push(EditorSelection.cursor(codeRef.range[1]))
|
||||||
ranges.push(EditorSelection.cursor(safeEnd))
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (ranges.length)
|
if (ranges.length)
|
||||||
|