Merge branch 'main' into kurt-bring-back-multi-profile
@ -1,3 +1,3 @@
|
|||||||
[codespell]
|
[codespell]
|
||||||
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey,atleast,ue,afterall
|
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,atleast,ue,afterall
|
||||||
skip: **/target,node_modules,build,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./src/lib/machine-api.d.ts
|
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
|
||||||
|
4
.github/dependabot.yml
vendored
@ -26,3 +26,7 @@ updates:
|
|||||||
reviewers:
|
reviewers:
|
||||||
- adamchalmers
|
- adamchalmers
|
||||||
- jessfraz
|
- jessfraz
|
||||||
|
groups:
|
||||||
|
serde-dependencies:
|
||||||
|
patterns:
|
||||||
|
- "serde*"
|
||||||
|
16
.github/workflows/build-apps.yml
vendored
@ -173,7 +173,13 @@ jobs:
|
|||||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||||
run: yarn electron-builder --config --publish always
|
DEBUG: "electron-notarize*"
|
||||||
|
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||||
|
uses: nick-fields/retry@v3.0.0
|
||||||
|
with:
|
||||||
|
timeout_minutes: 10
|
||||||
|
max_attempts: 3
|
||||||
|
command: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
- name: List artifacts in out/
|
- name: List artifacts in out/
|
||||||
run: ls -R out
|
run: ls -R out
|
||||||
@ -228,7 +234,13 @@ jobs:
|
|||||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||||
run: yarn electron-builder --config --publish always
|
DEBUG: "electron-notarize*"
|
||||||
|
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||||
|
uses: nick-fields/retry@v3.0.0
|
||||||
|
with:
|
||||||
|
timeout_minutes: 10
|
||||||
|
max_attempts: 3
|
||||||
|
command: yarn electron-builder --config --publish always
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: ${{ env.IS_RELEASE == 'true' }}
|
if: ${{ env.IS_RELEASE == 'true' }}
|
||||||
|
8
.github/workflows/e2e-tests.yml
vendored
@ -18,7 +18,6 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-rust-changes:
|
check-rust-changes:
|
||||||
if: github.event.pull_request.draft == false
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
rust-changed: ${{ steps.filter.outputs.rust }}
|
rust-changed: ${{ steps.filter.outputs.rust }}
|
||||||
@ -35,7 +34,6 @@ jobs:
|
|||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
electron:
|
electron:
|
||||||
if: github.event.pull_request.draft == false
|
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||||
strategy:
|
strategy:
|
||||||
@ -129,9 +127,12 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: yarn tron:package
|
run: yarn tron:package
|
||||||
- name: Run ubuntu/chrome snapshots
|
- name: Run ubuntu/chrome snapshots
|
||||||
|
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
# TODO: break this in its own job, for now it's not slowing down the overall execution as ubuntu is the quickest,
|
||||||
|
# but we could do better. This forces a large 1/1 shard of all 20 snapshot tests that runs in about 3 minutes.
|
||||||
run: |
|
run: |
|
||||||
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}
|
PLATFORM=web yarn playwright test --config=playwright.config.ts --retries="3" --update-snapshots --grep=@snapshot --shard=1/1
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
@ -152,6 +153,7 @@ jobs:
|
|||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: rm -r test-results
|
run: rm -r test-results
|
||||||
- name: check for changes
|
- name: check for changes
|
||||||
|
if: ${{ matrix.os == 'namespace-profile-ubuntu-8-cores' && matrix.shardIndex == 1 }}
|
||||||
shell: bash
|
shell: bash
|
||||||
id: git-check
|
id: git-check
|
||||||
run: |
|
run: |
|
||||||
|
40
README.md
@ -337,13 +337,47 @@ 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
|
||||||
|
|
||||||
```bash
|
**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
|
cd src/wasm-lib
|
||||||
KITTYCAD_API_TOKEN=XXX cargo test -- --test-threads=1
|
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
|
||||||
|
# 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
|
||||||
|
just test
|
||||||
|
```
|
||||||
|
|
||||||
|
```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,3 +24,5 @@ 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.
|
||||||
|
42
docs/kcl/circleThreePoint.md
Normal file
43
docs/kcl/helixRevolutions.md
Normal file
@ -35,6 +35,7 @@ layout: manual
|
|||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
* [`circle`](kcl/circle)
|
* [`circle`](kcl/circle)
|
||||||
|
* [`circleThreePoint`](kcl/circleThreePoint)
|
||||||
* [`close`](kcl/close)
|
* [`close`](kcl/close)
|
||||||
* [`cm`](kcl/cm)
|
* [`cm`](kcl/cm)
|
||||||
* [`cos`](kcl/cos)
|
* [`cos`](kcl/cos)
|
||||||
@ -47,6 +48,7 @@ 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)
|
||||||
@ -80,6 +82,7 @@ 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)
|
||||||
|
39
docs/kcl/pop.md
Normal file
10899
docs/kcl/std.json
@ -1,19 +1,19 @@
|
|||||||
---
|
---
|
||||||
title: "AxisOrEdgeReference"
|
title: "Axis2dOrEdgeReference"
|
||||||
excerpt: "Axis or tagged edge."
|
excerpt: "A 2D axis or tagged edge."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Axis or tagged edge.
|
A 2D axis or tagged edge.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**This schema accepts any of the following:**
|
**This schema accepts any of the following:**
|
||||||
|
|
||||||
Axis and origin.
|
2D axis and origin.
|
||||||
|
|
||||||
[`AxisAndOrigin`](/docs/kcl/types/AxisAndOrigin)
|
[`AxisAndOrigin2d`](/docs/kcl/types/AxisAndOrigin2d)
|
||||||
|
|
||||||
|
|
||||||
|
|
42
docs/kcl/types/Axis3dOrEdgeReference.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
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: "AxisAndOrigin"
|
title: "AxisAndOrigin2d"
|
||||||
excerpt: "Axis and origin."
|
excerpt: "A 2D axis and origin."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Axis and origin.
|
A 2D axis and origin.
|
||||||
|
|
||||||
|
|
||||||
|
|
105
docs/kcl/types/AxisAndOrigin3d.md
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
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 |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
23
docs/kcl/types/CircleThreePointData.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: "CircleThreePointData"
|
||||||
|
excerpt: "Data for drawing a 3-point circle"
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Data for drawing a 3-point circle
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `p1` |`[number, number]`| Point one for circle derivation. | No |
|
||||||
|
| `p2` |`[number, number]`| Point two for circle derivation. | No |
|
||||||
|
| `p3` |`[number, number]`| Point three for circle derivation. | No |
|
||||||
|
|
||||||
|
|
25
docs/kcl/types/Helix.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
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 helices."
|
excerpt: "Data for a helix."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Data for helices.
|
Data for a helix.
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -19,6 +19,8 @@ Data for helices.
|
|||||||
| `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. If this argument is not provided, the height of the solid is used. | No |
|
| `length` |`number`| Length of the helix. | No |
|
||||||
|
| `radius` |`number`| Radius of the helix. | No |
|
||||||
|
| `axis` |[`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference)| Axis to use as mirror. | No |
|
||||||
|
|
||||||
|
|
||||||
|
24
docs/kcl/types/HelixRevolutionsData.md
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
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 |
|
||||||
|
|
||||||
|
|
25
docs/kcl/types/HelixValue.md
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
---
|
||||||
|
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,6 +285,27 @@ 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` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis to use as mirror. | No |
|
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| 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` |[`AxisOrEdgeReference`](/docs/kcl/types/AxisOrEdgeReference)| Axis of revolution. | No |
|
| `axis` |[`Axis2dOrEdgeReference`](/docs/kcl/types/Axis2dOrEdgeReference)| 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` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
| `path` |[`SweepPath`](/docs/kcl/types/SweepPath)| 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 |
|
||||||
|
|
||||||
|
42
docs/kcl/types/SweepPath.md
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
---
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ test.describe('Basic sketch', () => {
|
|||||||
await doBasicSketch(page, homePage, ['code'])
|
await doBasicSketch(page, homePage, ['code'])
|
||||||
})
|
})
|
||||||
|
|
||||||
test.fixme('code pane closed at start', async ({ page, homePage }) => {
|
test('code pane closed at start', async ({ page, homePage }) => {
|
||||||
// Load the app with the code panes
|
// Load the app with the code panes
|
||||||
await page.addInitScript(async (persistModelingContext) => {
|
await page.addInitScript(async (persistModelingContext) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
127
e2e/playwright/feature-tree-pane.spec.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import { test, expect } from './zoo-test'
|
||||||
|
import * as fsp from 'fs/promises'
|
||||||
|
import { join } from 'path'
|
||||||
|
|
||||||
|
const FEATURE_TREE_EXAMPLE_CODE = `export fn timesFive(x) {
|
||||||
|
return 5 * x
|
||||||
|
}
|
||||||
|
export fn triangle() {
|
||||||
|
return startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> xLine(10, %)
|
||||||
|
|> line([-10, -5], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
}
|
||||||
|
|
||||||
|
length001 = timesFive(1) * 5
|
||||||
|
sketch001 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([20, 10], %)
|
||||||
|
|> line([10, 10], %)
|
||||||
|
|> angledLine([-45, length001], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
revolve001 = revolve({ axis = "X" }, sketch001)
|
||||||
|
triangle()
|
||||||
|
|> extrude(30, %)
|
||||||
|
plane001 = offsetPlane('XY', 10)
|
||||||
|
sketch002 = startSketchOn(plane001)
|
||||||
|
|> startProfileAt([-20, 0], %)
|
||||||
|
|> line([5, -15], %)
|
||||||
|
|> xLine(-10, %)
|
||||||
|
|> lineTo([-40, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
extrude001 = extrude(10, sketch002)
|
||||||
|
`
|
||||||
|
|
||||||
|
test.describe('Feature Tree pane', () => {
|
||||||
|
test(
|
||||||
|
'User can go to definition and go to function definition',
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ context, homePage, scene, editor, toolbar }) => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const bracketDir = join(dir, 'test-sample')
|
||||||
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
|
await fsp.writeFile(
|
||||||
|
join(bracketDir, 'main.kcl'),
|
||||||
|
FEATURE_TREE_EXAMPLE_CODE,
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('setup test', async () => {
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: 'test-sample',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.openProject('test-sample')
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
await editor.closePane()
|
||||||
|
await toolbar.openFeatureTreePane()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function testViewSource({
|
||||||
|
operationName,
|
||||||
|
operationIndex,
|
||||||
|
expectedActiveLine,
|
||||||
|
}: {
|
||||||
|
operationName: string
|
||||||
|
operationIndex: number
|
||||||
|
expectedActiveLine: string
|
||||||
|
}) {
|
||||||
|
await test.step(`Go to definition of the ${operationName}`, async () => {
|
||||||
|
await toolbar.viewSourceOnOperation(operationName, operationIndex)
|
||||||
|
await editor.expectState({
|
||||||
|
highlightedCode: '',
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: [expectedActiveLine],
|
||||||
|
})
|
||||||
|
await expect(
|
||||||
|
editor.activeLine.first(),
|
||||||
|
`${operationName} code should be scrolled into view`
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await testViewSource({
|
||||||
|
operationName: 'Offset Plane',
|
||||||
|
operationIndex: 0,
|
||||||
|
expectedActiveLine: "plane001 = offsetPlane('XY', 10)",
|
||||||
|
})
|
||||||
|
await testViewSource({
|
||||||
|
operationName: 'Extrude',
|
||||||
|
operationIndex: 1,
|
||||||
|
expectedActiveLine: 'extrude001 = extrude(10, sketch002)',
|
||||||
|
})
|
||||||
|
await testViewSource({
|
||||||
|
operationName: 'Revolve',
|
||||||
|
operationIndex: 0,
|
||||||
|
expectedActiveLine: 'revolve001 = revolve({ axis = "X" }, sketch001)',
|
||||||
|
})
|
||||||
|
await testViewSource({
|
||||||
|
operationName: 'Triangle',
|
||||||
|
operationIndex: 0,
|
||||||
|
expectedActiveLine: 'triangle()',
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Go to definition on the triangle function', async () => {
|
||||||
|
await toolbar.goToDefinitionOnOperation('Triangle', 0)
|
||||||
|
await editor.expectState({
|
||||||
|
highlightedCode: '',
|
||||||
|
diagnostics: [],
|
||||||
|
activeLines: ['export fn triangle() {'],
|
||||||
|
})
|
||||||
|
await expect(
|
||||||
|
editor.activeLine.first(),
|
||||||
|
'Triangle function definition should be scrolled into view'
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
@ -20,7 +20,7 @@ export class EditorFixture {
|
|||||||
private diagnosticsTooltip!: Locator
|
private diagnosticsTooltip!: Locator
|
||||||
private diagnosticsGutterIcon!: Locator
|
private diagnosticsGutterIcon!: Locator
|
||||||
private codeContent!: Locator
|
private codeContent!: Locator
|
||||||
private activeLine!: Locator
|
public activeLine!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
|
@ -38,7 +38,8 @@ 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) {
|
||||||
@ -66,6 +67,8 @@ 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 = (
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
import type { Page, Locator } from '@playwright/test'
|
import type { Page, Locator } from '@playwright/test'
|
||||||
import { expect } from '../zoo-test'
|
import { expect } from '../zoo-test'
|
||||||
import { doAndWaitForImageDiff } from '../test-utils'
|
import {
|
||||||
|
checkIfPaneIsOpen,
|
||||||
|
closePane,
|
||||||
|
doAndWaitForImageDiff,
|
||||||
|
openPane,
|
||||||
|
} from '../test-utils'
|
||||||
|
import { SidebarType } from 'components/ModelingSidebar/ModelingPanes'
|
||||||
|
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||||
|
|
||||||
export class ToolbarFixture {
|
export class ToolbarFixture {
|
||||||
public page: Page
|
public page: Page
|
||||||
@ -23,6 +30,10 @@ export class ToolbarFixture {
|
|||||||
filePane!: Locator
|
filePane!: Locator
|
||||||
exeIndicator!: Locator
|
exeIndicator!: Locator
|
||||||
treeInputField!: Locator
|
treeInputField!: Locator
|
||||||
|
/** The sidebar button for the Feature Tree pane */
|
||||||
|
featureTreeId = 'feature-tree' as const
|
||||||
|
/** The pane element for the Feature Tree */
|
||||||
|
featureTreePane!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -47,6 +58,7 @@ export class ToolbarFixture {
|
|||||||
this.treeInputField = page.getByTestId('tree-input-field')
|
this.treeInputField = page.getByTestId('tree-input-field')
|
||||||
|
|
||||||
this.filePane = page.locator('#files-pane')
|
this.filePane = page.locator('#files-pane')
|
||||||
|
this.featureTreePane = page.locator('#feature-tree-pane')
|
||||||
this.fileCreateToast = page.getByText('Successfully created')
|
this.fileCreateToast = page.getByText('Successfully created')
|
||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
}
|
}
|
||||||
@ -106,4 +118,76 @@ export class ToolbarFixture {
|
|||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await this.page.getByTestId('dropdown-center-rectangle').click()
|
await this.page.getByTestId('dropdown-center-rectangle').click()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async closePane(paneId: SidebarType) {
|
||||||
|
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||||
|
}
|
||||||
|
async openPane(paneId: SidebarType) {
|
||||||
|
return openPane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||||
|
}
|
||||||
|
async checkIfPaneIsOpen(paneId: SidebarType) {
|
||||||
|
return checkIfPaneIsOpen(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)
|
||||||
|
}
|
||||||
|
|
||||||
|
async openFeatureTreePane() {
|
||||||
|
return this.openPane(this.featureTreeId)
|
||||||
|
}
|
||||||
|
async closeFeatureTreePane() {
|
||||||
|
await this.closePane(this.featureTreeId)
|
||||||
|
}
|
||||||
|
async checkIfFeatureTreePaneIsOpen() {
|
||||||
|
return this.checkIfPaneIsOpen(this.featureTreeId)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a specific operation button from the Feature Tree pane
|
||||||
|
*/
|
||||||
|
async getFeatureTreeOperation(operationName: string, operationIndex: number) {
|
||||||
|
await this.openFeatureTreePane()
|
||||||
|
await expect(this.featureTreePane).toBeVisible()
|
||||||
|
return this.featureTreePane
|
||||||
|
.getByRole('button', {
|
||||||
|
name: operationName,
|
||||||
|
})
|
||||||
|
.nth(operationIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View source on a specific operation in the Feature Tree pane.
|
||||||
|
* @param operationName The name of the operation type
|
||||||
|
* @param operationIndex The index out of operations of this type
|
||||||
|
*/
|
||||||
|
async viewSourceOnOperation(operationName: string, operationIndex: number) {
|
||||||
|
const operationButton = await this.getFeatureTreeOperation(
|
||||||
|
operationName,
|
||||||
|
operationIndex
|
||||||
|
)
|
||||||
|
const viewSourceMenuButton = this.page.getByRole('button', {
|
||||||
|
name: 'View KCL source code',
|
||||||
|
})
|
||||||
|
|
||||||
|
await operationButton.click({ button: 'right' })
|
||||||
|
await expect(viewSourceMenuButton).toBeVisible()
|
||||||
|
await viewSourceMenuButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Go to definition on a specific operation in the Feature Tree pane
|
||||||
|
*/
|
||||||
|
async goToDefinitionOnOperation(
|
||||||
|
operationName: string,
|
||||||
|
operationIndex: number
|
||||||
|
) {
|
||||||
|
const operationButton = await this.getFeatureTreeOperation(
|
||||||
|
operationName,
|
||||||
|
operationIndex
|
||||||
|
)
|
||||||
|
const goToDefinitionMenuButton = this.page.getByRole('button', {
|
||||||
|
name: 'View function definition',
|
||||||
|
})
|
||||||
|
|
||||||
|
await operationButton.click({ button: 'right' })
|
||||||
|
await expect(goToDefinitionMenuButton).toBeVisible()
|
||||||
|
await goToDefinitionMenuButton.click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'yyyyyyyyy open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
'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(
|
||||||
'aaayyyyyyyy open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
'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(
|
||||||
'nooooooooooooo open a file in a project works and renders, open empty file, it should clear the scene',
|
'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,3 +1885,48 @@ 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,6 +614,38 @@ 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) {
|
||||||
|
@ -40,8 +40,8 @@ test.describe('Sketch tests', () => {
|
|||||||
${startProfileAt1}
|
${startProfileAt1}
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = screwRadius,
|
radius = screwRadius,
|
||||||
angle_start = 0,
|
angleStart = 0,
|
||||||
angle_end = 360
|
angleEnd = 360
|
||||||
}, %)
|
}, %)
|
||||||
|
|
||||||
part001 = startSketchOn('XY')
|
part001 = startSketchOn('XY')
|
||||||
@ -61,8 +61,8 @@ test.describe('Sketch tests', () => {
|
|||||||
|> yLine(wireOffset, %)
|
|> yLine(wireOffset, %)
|
||||||
|> arc({
|
|> arc({
|
||||||
radius = wireRadius,
|
radius = wireRadius,
|
||||||
angle_start = 0,
|
angleStart = 0,
|
||||||
angle_end = 180
|
angleEnd = 180
|
||||||
}, %)
|
}, %)
|
||||||
|> yLine(-wireOffset, %)
|
|> yLine(-wireOffset, %)
|
||||||
|> xLine(-width / 4, %)
|
|> xLine(-width / 4, %)
|
||||||
@ -2263,3 +2263,85 @@ loft([profile001, profile002])
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 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]}, %)',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -375,6 +375,7 @@ const extrudeDefaultPlane = async (context: any, page: any, plane: string) => {
|
|||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
await expect(page).toHaveScreenshot({
|
await expect(page).toHaveScreenshot({
|
||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 45 KiB |
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 65 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: 66 KiB |
Before Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 152 KiB |
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
Before Width: | Height: | Size: 130 KiB |
Before Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 128 KiB After Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 112 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 38 KiB |
@ -413,25 +413,25 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
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 },
|
||||||
x_axis = { x = 1, y = 0, z = 0 },
|
xAxis = { x = 1, y = 0, z = 0 },
|
||||||
y_axis = { x = 0, y = 0, z = 1 },
|
yAxis = { x = 0, y = 0, z = 1 },
|
||||||
z_axis = { x = 0, y = -1, z = 0 }
|
zAxis = { 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 },
|
||||||
x_axis = { x = -0.81, y = 0, z = 0.58 },
|
xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
yAxis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = 0.58, y = 0, z = 0.81 }
|
zAxis = { 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 },
|
||||||
x_axis = { x = -0.66, y = 0, z = -0.75 },
|
xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||||
y_axis = { x = 0, y = -1, z = 0 },
|
yAxis = { x = 0, y = -1, z = 0 },
|
||||||
z_axis = { x = -0.75, y = 0, z = 0.66 }
|
zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||||
}
|
}
|
||||||
})`)
|
})`)
|
||||||
|
|
||||||
@ -524,7 +524,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
|||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.keyboard.press('Backspace')
|
await page.keyboard.press('Backspace')
|
||||||
|
|
||||||
await expect(page.getByText('Unable to delete part')).toBeVisible()
|
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||||
})
|
})
|
||||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||||
page,
|
page,
|
||||||
|
@ -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.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: '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.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: '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.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: '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.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: '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.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: '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.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: '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.getByText('Text-to-CAD')
|
const textToCadCommand = page.getByRole('option', { name: '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.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: '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.getByText('Prompt')
|
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
||||||
await expect(prompt.first()).toBeVisible()
|
await expect(prompt.first()).toBeVisible()
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
|
@ -38,7 +38,7 @@ win:
|
|||||||
# - arm64
|
# - arm64
|
||||||
signingHashAlgorithms:
|
signingHashAlgorithms:
|
||||||
- sha256
|
- sha256
|
||||||
sign: "./sign-win.js"
|
sign: "./scripts/sign-win.js"
|
||||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||||
icon: "assets/icon.ico"
|
icon: "assets/icon.ico"
|
||||||
fileAssociations:
|
fileAssociations:
|
||||||
|
8
interface.d.ts
vendored
@ -11,6 +11,13 @@ 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>
|
||||||
@ -86,5 +93,6 @@ export interface IElectronAPI {
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
electron: IElectronAPI
|
electron: IElectronAPI
|
||||||
|
openExternalLink: (e: React.MouseEvent<HTMLAnchorElement>) => void
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
18
package.json
@ -10,23 +10,23 @@
|
|||||||
},
|
},
|
||||||
"description": "Edit CAD visually or with code",
|
"description": "Edit CAD visually or with code",
|
||||||
"main": ".vite/build/main.js",
|
"main": ".vite/build/main.js",
|
||||||
"license": "none",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@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.1",
|
"@codemirror/lint": "^6.8.4",
|
||||||
"@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",
|
||||||
"@csstools/postcss-oklab-function": "^4.0.2",
|
"@csstools/postcss-oklab-function": "^4.0.7",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
"@fortawesome/fontawesome-svg-core": "^6.5.2",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
"@fortawesome/free-brands-svg-icons": "^6.5.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
"@fortawesome/free-solid-svg-icons": "^6.4.2",
|
||||||
"@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.7",
|
"@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.5.1",
|
"react-hotkeys-hook": "^4.6.1",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
"react-modal": "^3.16.1",
|
"react-modal": "^3.16.3",
|
||||||
"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.166.1",
|
"three": "^0.172.0",
|
||||||
"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",
|
||||||
@ -166,7 +166,7 @@
|
|||||||
"@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.163.0",
|
"@types/three": "^0.172.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",
|
||||||
@ -186,7 +186,7 @@
|
|||||||
"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-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^15.11.7",
|
"happy-dom": "^16.3.0",
|
||||||
"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",
|
||||||
|
7
packages/codemirror-lang-kcl/.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
tsconfig.tsbuildinfo
|
||||||
|
*.d.ts
|
||||||
|
*.js
|
||||||
|
!rollup.config.js
|
36
packages/codemirror-lang-kcl/package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@kittycad/codemirror-lang-kcl",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Zoo KCL language support for CodeMirror 6.",
|
||||||
|
"main": "src/index.ts",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rollup -c",
|
||||||
|
"test": "vitest --config vitest.main.config.ts run"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"repository": "https://github.com/KittyCAD/modeling-app",
|
||||||
|
"author": "Zoo Engineering Team",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": false,
|
||||||
|
"exports": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
},
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"dependencies": {
|
||||||
|
"@codemirror/language": "^6.10.3",
|
||||||
|
"@codemirror/state": "^6.4.1",
|
||||||
|
"@lezer/highlight": "^1.2.1",
|
||||||
|
"typescript": "^5.7.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@lezer/generator": "^1.7.2",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"rollup": "^4.29.1",
|
||||||
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
|
"vitest": "^2.1.8"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/"
|
||||||
|
]
|
||||||
|
}
|
25
packages/codemirror-lang-kcl/rollup.config.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import dts from 'rollup-plugin-dts'
|
||||||
|
import { lezer } from '@lezer/generator/rollup'
|
||||||
|
import typescript from '@rollup/plugin-typescript'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
input: 'src/index.ts',
|
||||||
|
// imports are considered internal if they start with './' or '/' or 'word:'
|
||||||
|
external: (id) => id != 'tslib' && !/^(\.?\/|\w:)/.test(id),
|
||||||
|
output: [
|
||||||
|
{ file: 'dist/index.cjs', format: 'cjs' },
|
||||||
|
{ file: 'dist/index.js', format: 'es' },
|
||||||
|
],
|
||||||
|
plugins: [lezer(), typescript()],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'src/index.ts',
|
||||||
|
external: (id) => id != 'tslib' && !/^(\.?\/|\w:)/.test(id),
|
||||||
|
output: [
|
||||||
|
{ file: 'dist/index.d.cts', format: 'cjs' },
|
||||||
|
{ file: 'dist/index.d.ts', format: 'es' },
|
||||||
|
],
|
||||||
|
plugins: [lezer(), typescript(), dts()],
|
||||||
|
},
|
||||||
|
]
|
42
packages/codemirror-lang-kcl/src/index.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
// Base CodeMirror language support for kcl.
|
||||||
|
|
||||||
|
import {
|
||||||
|
LRLanguage,
|
||||||
|
LanguageSupport,
|
||||||
|
indentNodeProp,
|
||||||
|
continuedIndent,
|
||||||
|
delimitedIndent,
|
||||||
|
foldNodeProp,
|
||||||
|
foldInside,
|
||||||
|
} from '@codemirror/language'
|
||||||
|
// @ts-ignore: No types available
|
||||||
|
import { parser } from './kcl.grammar'
|
||||||
|
|
||||||
|
export const KclLanguage = LRLanguage.define({
|
||||||
|
name: 'kcl',
|
||||||
|
parser: parser.configure({
|
||||||
|
props: [
|
||||||
|
indentNodeProp.add({
|
||||||
|
Body: delimitedIndent({ closing: '}' }),
|
||||||
|
BlockComment: () => null,
|
||||||
|
'Statement Property': continuedIndent({ except: /^{/ }),
|
||||||
|
}),
|
||||||
|
foldNodeProp.add({
|
||||||
|
'Body ArrayExpression ObjectExpression': foldInside,
|
||||||
|
BlockComment(tree) {
|
||||||
|
return { from: tree.from + 2, to: tree.to - 2 }
|
||||||
|
},
|
||||||
|
PipeExpression(tree) {
|
||||||
|
return { from: tree.firstChild!.to, to: tree.to }
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
languageData: {
|
||||||
|
commentTokens: { line: '//', block: { open: '/*', close: '*/' } },
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function kcl() {
|
||||||
|
return new LanguageSupport(KclLanguage)
|
||||||
|
}
|
@ -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 }
|
||||||
@ -57,7 +57,7 @@ expression[@isGroup=Expression] {
|
|||||||
|
|
||||||
UnaryOp { AddOp | BangOp }
|
UnaryOp { AddOp | BangOp }
|
||||||
|
|
||||||
ObjectProperty { PropertyName ":" expression }
|
ObjectProperty { PropertyName (":" | Equals) expression }
|
||||||
|
|
||||||
ArgumentList { "(" commaSep<expression> ")" }
|
ArgumentList { "(" commaSep<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 { "+" | "-" }
|
22
packages/codemirror-lang-kcl/test/all.test.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { KclLanguage } from '../src/index'
|
||||||
|
import { fileTests } from '@lezer/generator/dist/test'
|
||||||
|
|
||||||
|
import * as fs from 'fs'
|
||||||
|
import * as path from 'path'
|
||||||
|
|
||||||
|
let caseDir = path.dirname(__filename)
|
||||||
|
|
||||||
|
for (let file of fs.readdirSync(caseDir)) {
|
||||||
|
if (!/\.txt$/.test(file)) continue
|
||||||
|
|
||||||
|
let fname = /^[^\.]*/.exec(file)?.at(0)
|
||||||
|
if (fname) {
|
||||||
|
let tests = fileTests(
|
||||||
|
fs.readFileSync(path.join(caseDir, file), 'utf8'),
|
||||||
|
file
|
||||||
|
)
|
||||||
|
describe(fname, () => {
|
||||||
|
for (let { name, run } of tests) it(name, () => run(KclLanguage.parser))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|