Merge remote-tracking branch 'origin/main' into paultag/refgraph
This commit is contained in:
@ -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,ser
|
||||||
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,dist,./out,**/Cargo.lock,./docs/kcl/*.md,.yarn.lock,**/yarn.lock,./openapi/*.json,./packages/codemirror-lang-kcl/test/all.test.ts,tsconfig.tsbuildinfo
|
||||||
|
|||||||
44
.github/workflows/cargo-bench.yml
vendored
44
.github/workflows/cargo-bench.yml
vendored
@ -1,44 +0,0 @@
|
|||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- '**.rs'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- .github/workflows/cargo-bench.yml
|
|
||||||
pull_request:
|
|
||||||
paths:
|
|
||||||
- '**.rs'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- .github/workflows/cargo-bench.yml
|
|
||||||
workflow_dispatch:
|
|
||||||
permissions: read-all
|
|
||||||
concurrency:
|
|
||||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
||||||
cancel-in-progress: true
|
|
||||||
name: cargo bench
|
|
||||||
jobs:
|
|
||||||
cargo-bench:
|
|
||||||
name: Benchmark with iai
|
|
||||||
runs-on: ubuntu-latest-8-cores
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: dtolnay/rust-toolchain@stable
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
cargo install cargo-criterion
|
|
||||||
sudo apt update
|
|
||||||
sudo apt install -y valgrind
|
|
||||||
- name: Rust Cache
|
|
||||||
uses: Swatinem/rust-cache@v2.6.1
|
|
||||||
- name: Benchmark kcl library
|
|
||||||
shell: bash
|
|
||||||
run: |-
|
|
||||||
cd src/wasm-lib/kcl; cargo bench --all-features -- iai
|
|
||||||
env:
|
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
|
||||||
|
|
||||||
20587
docs/kcl/std.json
20587
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
28
docs/kcl/types/Face.md
Normal file
28
docs/kcl/types/Face.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
title: "Face"
|
||||||
|
excerpt: "A face."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A face.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `id` |`string`| The id of the face. | No |
|
||||||
|
| `value` |`string`| The tag of the face. | No |
|
||||||
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
||||||
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||||
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
@ -20,6 +20,7 @@ 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? | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ 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? | No |
|
| `ccw` |`boolean`| Is the helix rotation counter clockwise? | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A helix. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -168,7 +168,6 @@ Any KCL value.
|
|||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
A plane.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -181,17 +180,10 @@ A plane.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
||||||
| `id` |`string`| The id of the plane. | No |
|
| `value` |[`Plane`](/docs/kcl/types/Plane)| Any KCL value. | No |
|
||||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
|
|
||||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
A face.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -203,14 +195,8 @@ A face.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Face`| | No |
|
| `type` |enum: [`Face`](/docs/kcl/types/Face)| | No |
|
||||||
| `id` |`string`| The id of the face. | No |
|
| `value` |[`Face`](/docs/kcl/types/Face)| Any KCL value. | No |
|
||||||
| `value` |`string`| The tag of the face. | No |
|
|
||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s X axis be? | No |
|
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
|
||||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -246,7 +232,6 @@ A face.
|
|||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
An solid is a collection of extrude surfaces.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -259,14 +244,7 @@ An solid is a collection of extrude surfaces.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
||||||
| `id` |`string`| The id of the solid. | No |
|
| `value` |[`Solid`](/docs/kcl/types/Solid)| Any KCL value. | No |
|
||||||
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
|
|
||||||
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
|
|
||||||
| `height` |`number`| The height of the solid. | No |
|
|
||||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
|
||||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
|
||||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -286,7 +264,6 @@ An solid is a collection of extrude surfaces.
|
|||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
A helix.
|
|
||||||
|
|
||||||
**Type:** `object`
|
**Type:** `object`
|
||||||
|
|
||||||
@ -299,11 +276,7 @@ A helix.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
|
| `type` |enum: [`Helix`](/docs/kcl/types/Helix)| | No |
|
||||||
| `value` |`string`| The id of the helix. | No |
|
| `value` |[`Helix`](/docs/kcl/types/Helix)| Any KCL value. | 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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
|||||||
@ -22,6 +22,7 @@ A plane.
|
|||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -21,6 +21,7 @@ A sketch is a collection of paths.
|
|||||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch is a collection of paths. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -30,6 +30,7 @@ A sketch is a collection of paths.
|
|||||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch or a group of sketches. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,7 @@ A plane.
|
|||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
@ -54,6 +55,7 @@ A face.
|
|||||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s Y axis be? | No |
|
||||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -23,6 +23,7 @@ An solid is a collection of extrude surfaces.
|
|||||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| An solid is a collection of extrude surfaces. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ An solid is a collection of extrude surfaces.
|
|||||||
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
| `startCapId` |`string`| The id of the extrusion start cap | No |
|
||||||
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
| `endCapId` |`string`| The id of the extrusion end cap | No |
|
||||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||||
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A solid or a group of solids. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| Metadata. | No |
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
107
docs/kcl/types/UnitLen.md
Normal file
107
docs/kcl/types/UnitLen.md
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
---
|
||||||
|
title: "UnitLen"
|
||||||
|
excerpt: ""
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**This schema accepts exactly one of the following:**
|
||||||
|
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Mm`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Cm`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `M`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Inches`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Feet`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Yards`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -280,7 +280,7 @@ test(
|
|||||||
|
|
||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||||
await expect(page.getByText('New Project')).toBeVisible()
|
await expect(page.getByText('Create project')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Opening the router-template project should load', async () => {
|
await test.step('Opening the router-template project should load', async () => {
|
||||||
|
|||||||
@ -38,14 +38,14 @@ test.describe('Debug pane', () => {
|
|||||||
// Set the code in the code editor.
|
// Set the code in the code editor.
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type(code, { delay: 0 })
|
await page.keyboard.type(code, { delay: 0 })
|
||||||
// Scroll to the feature tree.
|
// Scroll to the artifact graph.
|
||||||
await tree.scrollIntoViewIfNeeded()
|
await tree.scrollIntoViewIfNeeded()
|
||||||
// Expand the feature tree.
|
// Expand the artifact graph.
|
||||||
await tree.getByText('Feature Tree').click()
|
await tree.getByText('Artifact Graph').click()
|
||||||
// Just expanded the details, making the element taller, so scroll again.
|
// Just expanded the details, making the element taller, so scroll again.
|
||||||
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
|
await tree.getByText('Plane').first().scrollIntoViewIfNeeded()
|
||||||
})
|
})
|
||||||
// Extract the artifact IDs from the debug feature tree.
|
// Extract the artifact IDs from the debug artifact graph.
|
||||||
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
|
const initialSegmentIds = await segment.innerText({ timeout: 5_000 })
|
||||||
// The artifact ID should include a UUID.
|
// The artifact ID should include a UUID.
|
||||||
expect(initialSegmentIds).toMatch(
|
expect(initialSegmentIds).toMatch(
|
||||||
|
|||||||
@ -135,4 +135,20 @@ export class CmdBarFixture {
|
|||||||
await promptEditCommand.first().click()
|
await promptEditCommand.first().click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get cmdSearchInput() {
|
||||||
|
return this.page.getByTestId('cmd-bar-search')
|
||||||
|
}
|
||||||
|
|
||||||
|
get argumentInput() {
|
||||||
|
return this.page.getByTestId('cmd-bar-arg-value')
|
||||||
|
}
|
||||||
|
|
||||||
|
get cmdOptions() {
|
||||||
|
return this.page.getByTestId('cmd-bar-option')
|
||||||
|
}
|
||||||
|
|
||||||
|
chooseCommand = async (commandName: string) => {
|
||||||
|
await this.cmdOptions.getByText(commandName).click()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -103,7 +103,7 @@ export class HomePageFixture {
|
|||||||
.toEqual(expectedState)
|
.toEqual(expectedState)
|
||||||
}
|
}
|
||||||
|
|
||||||
createAndGoToProject = async (projectTitle: string) => {
|
createAndGoToProject = async (projectTitle = 'project-$nnn') => {
|
||||||
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
await expect(this.projectSection).not.toHaveText('Loading your Projects...')
|
||||||
await this.projectButtonNew.click()
|
await this.projectButtonNew.click()
|
||||||
await this.projectTextName.click()
|
await this.projectTextName.click()
|
||||||
|
|||||||
@ -63,6 +63,10 @@ export class ToolbarFixture {
|
|||||||
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
this.exeIndicator = page.getByTestId('model-state-indicator-execution-done')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get logoLink() {
|
||||||
|
return this.page.getByTestId('app-logo')
|
||||||
|
}
|
||||||
|
|
||||||
startSketchPlaneSelection = async () =>
|
startSketchPlaneSelection = async () =>
|
||||||
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
doAndWaitForImageDiff(this.page, () => this.startSketchBtn.click(), 500)
|
||||||
|
|
||||||
|
|||||||
@ -963,37 +963,31 @@ sketch002 = startSketchOn('XZ')
|
|||||||
await toolbar.sweepButton.click()
|
await toolbar.sweepButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'profile',
|
currentArgKey: 'target',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Path: '',
|
Target: '',
|
||||||
Profile: '',
|
Trajectory: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'profile',
|
highlightedHeaderArg: 'target',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
currentArgKey: 'path',
|
currentArgKey: 'trajectory',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Path: '',
|
Target: '1 face',
|
||||||
Profile: '1 face',
|
Trajectory: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'trajectory',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch2()
|
await clickOnSketch2()
|
||||||
await cmdBar.expectState({
|
await page.waitForTimeout(500)
|
||||||
commandName: 'Sweep',
|
|
||||||
headerArguments: {
|
|
||||||
Path: '1 face',
|
|
||||||
Profile: '1 face',
|
|
||||||
},
|
|
||||||
stage: 'review',
|
|
||||||
})
|
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||||
@ -1020,6 +1014,75 @@ sketch002 = startSketchOn('XZ')
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Sweep point-and-click failing validation`, async ({
|
||||||
|
context,
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
scene,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|
|> circle({
|
||||||
|
center = [0, 0],
|
||||||
|
radius = 500
|
||||||
|
}, %)
|
||||||
|
sketch002 = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> xLine(-500, %)
|
||||||
|
|> lineTo([-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)
|
||||||
|
|
||||||
|
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 and fail validation with a toast`, async () => {
|
||||||
|
await toolbar.sweepButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'target',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Target: '',
|
||||||
|
Trajectory: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'target',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch1()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Sweep',
|
||||||
|
currentArgKey: 'trajectory',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Target: '1 face',
|
||||||
|
Trajectory: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'trajectory',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
await clickOnSketch2()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect(
|
||||||
|
page.getByText('Unable to sweep with the provided selection')
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Fillet point-and-click`, async ({
|
test(`Fillet point-and-click`, async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
@ -1503,6 +1566,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
await clickOnCap()
|
await clickOnCap()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1523,6 +1587,7 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||||
await toolbar.shellButton.click()
|
await toolbar.shellButton.click()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1604,6 +1669,7 @@ extrude001 = extrude(40, sketch001)
|
|||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
@ -1727,3 +1793,61 @@ shellSketchOnFacesCases.forEach((initialCode, index) => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Shell dry-run validation rejects sweeps`, 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(-2000, %)
|
||||||
|
sweep001 = sweep({ path = sketch002 }, sketch001)
|
||||||
|
`
|
||||||
|
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: 500, y: 250 }
|
||||||
|
const [clickOnSweep] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||||
|
|
||||||
|
await test.step(`Confirm sweep exists`, async () => {
|
||||||
|
await toolbar.closePane('code')
|
||||||
|
await scene.expectPixelColor([231, 231, 231], testPoint, 15)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Go through the Shell flow and fail validation with a toast`, async () => {
|
||||||
|
await toolbar.shellButton.click()
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'selection',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Selection: '',
|
||||||
|
Thickness: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'selection',
|
||||||
|
commandName: 'Shell',
|
||||||
|
})
|
||||||
|
await clickOnSweep()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
await expect(
|
||||||
|
page.getByText('Unable to shell with the provided selection')
|
||||||
|
).toBeVisible()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -172,7 +172,7 @@ test(
|
|||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('broken-code')).toBeVisible()
|
await expect(page.getByText('broken-code')).toBeVisible()
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
await expect(page.getByText('New Project')).toBeVisible()
|
await expect(page.getByText('Create project')).toBeVisible()
|
||||||
})
|
})
|
||||||
await test.step('opening broken code project should clear the scene and show the error', async () => {
|
await test.step('opening broken code project should clear the scene and show the error', async () => {
|
||||||
// Go back home.
|
// Go back home.
|
||||||
@ -253,7 +253,7 @@ test(
|
|||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('empty')).toBeVisible()
|
await expect(page.getByText('empty')).toBeVisible()
|
||||||
await expect(page.getByText('bracket')).toBeVisible()
|
await expect(page.getByText('bracket')).toBeVisible()
|
||||||
await expect(page.getByText('New Project')).toBeVisible()
|
await expect(page.getByText('Create project')).toBeVisible()
|
||||||
})
|
})
|
||||||
await test.step('opening empty code project should clear the scene', async () => {
|
await test.step('opening empty code project should clear the scene', async () => {
|
||||||
// Go back home.
|
// Go back home.
|
||||||
@ -985,6 +985,126 @@ test.describe(`Project management commands`, () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
test(`Create a new project with a colliding name`, async ({
|
||||||
|
context,
|
||||||
|
homePage,
|
||||||
|
toolbar,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
const projectName = 'test-project'
|
||||||
|
await test.step(`Setup`, async () => {
|
||||||
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const projectDir = path.join(dir, projectName)
|
||||||
|
await Promise.all([fsp.mkdir(projectDir, { recursive: true })])
|
||||||
|
await Promise.all([
|
||||||
|
fsp.copyFile(
|
||||||
|
executorInputPath('router-template-slate.kcl'),
|
||||||
|
path.join(projectDir, 'main.kcl')
|
||||||
|
),
|
||||||
|
])
|
||||||
|
})
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: projectName,
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Create a new project with the same name', async () => {
|
||||||
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.chooseCommand('create project')
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
commandName: 'Create project',
|
||||||
|
currentArgKey: 'name',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Name: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'name',
|
||||||
|
})
|
||||||
|
await cmdBar.argumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Check the project was created with a non-colliding name`, async () => {
|
||||||
|
await toolbar.logoLink.click()
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: projectName + '-1',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: projectName,
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Create another project with the same name', async () => {
|
||||||
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.chooseCommand('create project')
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
commandName: 'Create project',
|
||||||
|
currentArgKey: 'name',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Name: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'name',
|
||||||
|
})
|
||||||
|
await cmdBar.argumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`Check the second project was created with a non-colliding name`, async () => {
|
||||||
|
await toolbar.logoLink.click()
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: projectName + '-2',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: projectName + '-1',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: projectName,
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test(`Create a few projects using the default project name`, async ({
|
||||||
|
homePage,
|
||||||
|
toolbar,
|
||||||
|
}) => {
|
||||||
|
for (let i = 0; i < 12; i++) {
|
||||||
|
await test.step(`Create project ${i}`, async () => {
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: Array.from({ length: i }, (_, i) => ({
|
||||||
|
title: `project-${i.toString().padStart(3, '0')}`,
|
||||||
|
fileCount: 1,
|
||||||
|
})).toReversed(),
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.createAndGoToProject()
|
||||||
|
await toolbar.logoLink.click()
|
||||||
|
})
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test(
|
||||||
@ -1391,7 +1511,7 @@ extrude001 = extrude(200, sketch001)`)
|
|||||||
await page.getByTestId('app-logo').click()
|
await page.getByTestId('app-logo').click()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'New project' })
|
page.getByRole('button', { name: 'Create project' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
for (let i = 1; i <= 10; i++) {
|
for (let i = 1; i <= 10; i++) {
|
||||||
@ -1465,7 +1585,7 @@ test(
|
|||||||
|
|
||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||||
await expect(page.getByText('New Project')).toBeVisible()
|
await expect(page.getByText('Create project')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Opening the router-template project should load the stream', async () => {
|
await test.step('Opening the router-template project should load the stream', async () => {
|
||||||
@ -1494,7 +1614,7 @@ test(
|
|||||||
|
|
||||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||||
await expect(page.getByText('router-template-slate')).toBeVisible()
|
await expect(page.getByText('router-template-slate')).toBeVisible()
|
||||||
await expect(page.getByText('New Project')).toBeVisible()
|
await expect(page.getByText('Create project')).toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 44 KiB |
@ -1078,7 +1078,7 @@ export async function createProject({
|
|||||||
returnHome?: boolean
|
returnHome?: boolean
|
||||||
}) {
|
}) {
|
||||||
await test.step(`Create project and navigate to it`, async () => {
|
await test.step(`Create project and navigate to it`, async () => {
|
||||||
await page.getByRole('button', { name: 'New project' }).click()
|
await page.getByRole('button', { name: 'Create project' }).click()
|
||||||
await page.getByRole('textbox', { name: 'Name' }).fill(name)
|
await page.getByRole('textbox', { name: 'Name' }).fill(name)
|
||||||
await page.getByRole('button', { name: 'Continue' }).click()
|
await page.getByRole('button', { name: 'Continue' }).click()
|
||||||
|
|
||||||
|
|||||||
10
package.json
10
package.json
@ -113,9 +113,9 @@
|
|||||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\" --quiet",
|
||||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot' --quiet",
|
||||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot' --quiet",
|
||||||
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||||
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
@ -154,7 +154,6 @@
|
|||||||
"@playwright/test": "^1.49.0",
|
"@playwright/test": "^1.49.0",
|
||||||
"@testing-library/jest-dom": "^5.14.1",
|
"@testing-library/jest-dom": "^5.14.1",
|
||||||
"@testing-library/react": "^15.0.2",
|
"@testing-library/react": "^15.0.2",
|
||||||
"@types/d3-force": "^3.0.10",
|
|
||||||
"@types/diff": "^6.0.0",
|
"@types/diff": "^6.0.0",
|
||||||
"@types/electron": "^1.6.10",
|
"@types/electron": "^1.6.10",
|
||||||
"@types/isomorphic-fetch": "^0.0.39",
|
"@types/isomorphic-fetch": "^0.0.39",
|
||||||
@ -175,7 +174,6 @@
|
|||||||
"@vitest/web-worker": "^1.5.0",
|
"@vitest/web-worker": "^1.5.0",
|
||||||
"@xstate/cli": "^0.5.17",
|
"@xstate/cli": "^0.5.17",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"d3-force": "^3.0.0",
|
|
||||||
"electron": "32.1.2",
|
"electron": "32.1.2",
|
||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
"electron-notarize": "1.2.2",
|
"electron-notarize": "1.2.2",
|
||||||
@ -203,7 +201,7 @@
|
|||||||
"ts-node": "^10.0.0",
|
"ts-node": "^10.0.0",
|
||||||
"typescript": "^5.7.3",
|
"typescript": "^5.7.3",
|
||||||
"typescript-eslint": "^8.19.1",
|
"typescript-eslint": "^8.19.1",
|
||||||
"vite": "^5.4.6",
|
"vite": "^5.4.12",
|
||||||
"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",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
|
|||||||
@ -31,7 +31,6 @@ import {
|
|||||||
settingsLoader,
|
settingsLoader,
|
||||||
telemetryLoader,
|
telemetryLoader,
|
||||||
} from 'lib/routeLoaders'
|
} from 'lib/routeLoaders'
|
||||||
import { CommandBarProvider } from 'components/CommandBar/CommandBarProvider'
|
|
||||||
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
import SettingsAuthProvider from 'components/SettingsAuthProvider'
|
||||||
import LspProvider from 'components/LspProvider'
|
import LspProvider from 'components/LspProvider'
|
||||||
import { KclContextProvider } from 'lang/KclProvider'
|
import { KclContextProvider } from 'lang/KclProvider'
|
||||||
@ -58,23 +57,21 @@ const router = createRouter([
|
|||||||
/* Make sure auth is the outermost provider or else we will have
|
/* Make sure auth is the outermost provider or else we will have
|
||||||
* inefficient re-renders, use the react profiler to see. */
|
* inefficient re-renders, use the react profiler to see. */
|
||||||
element: (
|
element: (
|
||||||
<CommandBarProvider>
|
<RouteProvider>
|
||||||
<RouteProvider>
|
<SettingsAuthProvider>
|
||||||
<SettingsAuthProvider>
|
<LspProvider>
|
||||||
<LspProvider>
|
<ProjectsContextProvider>
|
||||||
<ProjectsContextProvider>
|
<KclContextProvider>
|
||||||
<KclContextProvider>
|
<AppStateProvider>
|
||||||
<AppStateProvider>
|
<MachineManagerProvider>
|
||||||
<MachineManagerProvider>
|
<Outlet />
|
||||||
<Outlet />
|
</MachineManagerProvider>
|
||||||
</MachineManagerProvider>
|
</AppStateProvider>
|
||||||
</AppStateProvider>
|
</KclContextProvider>
|
||||||
</KclContextProvider>
|
</ProjectsContextProvider>
|
||||||
</ProjectsContextProvider>
|
</LspProvider>
|
||||||
</LspProvider>
|
</SettingsAuthProvider>
|
||||||
</SettingsAuthProvider>
|
</RouteProvider>
|
||||||
</RouteProvider>
|
|
||||||
</CommandBarProvider>
|
|
||||||
),
|
),
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
221
src/Toolbar.tsx
221
src/Toolbar.tsx
@ -1,8 +1,7 @@
|
|||||||
import { useRef, useMemo, memo } from 'react'
|
import { useRef, useMemo, memo, useCallback, useState } from 'react'
|
||||||
import { isCursorInSketchCommandRange } from 'lang/util'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
@ -22,20 +21,19 @@ import {
|
|||||||
} from 'lib/toolbar'
|
} from 'lib/toolbar'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
export function Toolbar({
|
export function Toolbar({
|
||||||
className = '',
|
className = '',
|
||||||
...props
|
...props
|
||||||
}: React.HTMLAttributes<HTMLElement>) {
|
}: React.HTMLAttributes<HTMLElement>) {
|
||||||
const { state, send, context } = useModelingContext()
|
const { state, send, context } = useModelingContext()
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const iconClassName =
|
const iconClassName =
|
||||||
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
|
'group-disabled:text-chalkboard-50 !text-inherit dark:group-enabled:group-hover:!text-inherit'
|
||||||
const bgClassName = '!bg-transparent'
|
const bgClassName = '!bg-transparent'
|
||||||
const buttonBgClassName =
|
const buttonBgClassName =
|
||||||
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10'
|
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10'
|
||||||
const buttonBorderClassName =
|
const buttonBorderClassName = '!border-transparent'
|
||||||
'!border-transparent hover:!border-chalkboard-20 dark:enabled:hover:!border-primary pressed:!border-primary ui-open:!border-primary'
|
|
||||||
|
|
||||||
const sketchPathId = useMemo(() => {
|
const sketchPathId = useMemo(() => {
|
||||||
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
|
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
|
||||||
@ -50,6 +48,7 @@ export function Toolbar({
|
|||||||
const { overallState } = useNetworkContext()
|
const { overallState } = useNetworkContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { isStreamReady } = useAppState()
|
const { isStreamReady } = useAppState()
|
||||||
|
const [showRichContent, setShowRichContent] = useState(false)
|
||||||
|
|
||||||
const disableAllButtons =
|
const disableAllButtons =
|
||||||
(overallState !== NetworkHealthState.Ok &&
|
(overallState !== NetworkHealthState.Ok &&
|
||||||
@ -71,12 +70,45 @@ export function Toolbar({
|
|||||||
() => ({
|
() => ({
|
||||||
modelingState: state,
|
modelingState: state,
|
||||||
modelingSend: send,
|
modelingSend: send,
|
||||||
commandBarSend,
|
|
||||||
sketchPathId,
|
sketchPathId,
|
||||||
}),
|
}),
|
||||||
[state, send, commandBarSend, sketchPathId]
|
[state, send, commandBarActor.send, sketchPathId]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const tooltipContentClassName = !showRichContent
|
||||||
|
? ''
|
||||||
|
: '!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch'
|
||||||
|
const richContentTimeout = useRef<number | null>(null)
|
||||||
|
const richContentClearTimeout = useRef<number | null>(null)
|
||||||
|
// On mouse enter, show rich content after a 1s delay
|
||||||
|
const handleMouseEnter = useCallback(() => {
|
||||||
|
// Cancel the clear timeout if it's already set
|
||||||
|
if (richContentClearTimeout.current) {
|
||||||
|
clearTimeout(richContentClearTimeout.current)
|
||||||
|
}
|
||||||
|
// Start our own timeout to show the rich content
|
||||||
|
richContentTimeout.current = window.setTimeout(() => {
|
||||||
|
setShowRichContent(true)
|
||||||
|
if (richContentClearTimeout.current) {
|
||||||
|
clearTimeout(richContentClearTimeout.current)
|
||||||
|
}
|
||||||
|
}, 1000)
|
||||||
|
}, [setShowRichContent])
|
||||||
|
// On mouse leave, clear the timeout and hide rich content
|
||||||
|
const handleMouseLeave = useCallback(() => {
|
||||||
|
// Clear the timeout to show rich content
|
||||||
|
if (richContentTimeout.current) {
|
||||||
|
clearTimeout(richContentTimeout.current)
|
||||||
|
}
|
||||||
|
// Start a timeout to hide the rich content
|
||||||
|
richContentClearTimeout.current = window.setTimeout(() => {
|
||||||
|
setShowRichContent(false)
|
||||||
|
if (richContentClearTimeout.current) {
|
||||||
|
clearTimeout(richContentClearTimeout.current)
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}, [setShowRichContent])
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve all the callbacks and values for the current mode,
|
* Resolve all the callbacks and values for the current mode,
|
||||||
* so we don't need to worry about the other modes
|
* so we don't need to worry about the other modes
|
||||||
@ -174,43 +206,64 @@ export function Toolbar({
|
|||||||
status: itemConfig.status,
|
status: itemConfig.status,
|
||||||
}))}
|
}))}
|
||||||
>
|
>
|
||||||
<ActionButton
|
<div
|
||||||
Element="button"
|
className="contents"
|
||||||
id={maybeIconConfig[0].id}
|
// Mouse events do not fire on disabled buttons
|
||||||
data-testid={maybeIconConfig[0].id}
|
onMouseEnter={handleMouseEnter}
|
||||||
iconStart={{
|
onMouseLeave={handleMouseLeave}
|
||||||
icon: maybeIconConfig[0].icon,
|
|
||||||
className: iconClassName,
|
|
||||||
bgClassName: bgClassName,
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
'!border-transparent !px-0 pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
|
|
||||||
buttonBgClassName
|
|
||||||
}
|
|
||||||
aria-pressed={maybeIconConfig[0].isActive}
|
|
||||||
disabled={
|
|
||||||
disableAllButtons ||
|
|
||||||
maybeIconConfig[0].status !== 'available' ||
|
|
||||||
maybeIconConfig[0].disabled
|
|
||||||
}
|
|
||||||
name={maybeIconConfig[0].title}
|
|
||||||
// aria-description is still in ARIA 1.3 draft.
|
|
||||||
// eslint-disable-next-line jsx-a11y/aria-props
|
|
||||||
aria-description={maybeIconConfig[0].description}
|
|
||||||
onClick={() =>
|
|
||||||
maybeIconConfig[0].onClick(configCallbackProps)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<span
|
<ActionButton
|
||||||
className={!maybeIconConfig[0].showTitle ? 'sr-only' : ''}
|
Element="button"
|
||||||
|
id={maybeIconConfig[0].id}
|
||||||
|
data-testid={maybeIconConfig[0].id}
|
||||||
|
iconStart={{
|
||||||
|
icon: maybeIconConfig[0].icon,
|
||||||
|
className: iconClassName,
|
||||||
|
bgClassName: bgClassName,
|
||||||
|
}}
|
||||||
|
className={
|
||||||
|
'!border-transparent !px-0 pressed:!text-chalkboard-10 pressed:enabled:hovered:!text-chalkboard-10 ' +
|
||||||
|
buttonBgClassName
|
||||||
|
}
|
||||||
|
aria-pressed={maybeIconConfig[0].isActive}
|
||||||
|
disabled={
|
||||||
|
disableAllButtons ||
|
||||||
|
maybeIconConfig[0].status !== 'available' ||
|
||||||
|
maybeIconConfig[0].disabled
|
||||||
|
}
|
||||||
|
name={maybeIconConfig[0].title}
|
||||||
|
// aria-description is still in ARIA 1.3 draft.
|
||||||
|
// eslint-disable-next-line jsx-a11y/aria-props
|
||||||
|
aria-description={maybeIconConfig[0].description}
|
||||||
|
onClick={() =>
|
||||||
|
maybeIconConfig[0].onClick(configCallbackProps)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{maybeIconConfig[0].title}
|
<span
|
||||||
</span>
|
className={!maybeIconConfig[0].showTitle ? 'sr-only' : ''}
|
||||||
</ActionButton>
|
>
|
||||||
<ToolbarItemTooltip
|
{maybeIconConfig[0].title}
|
||||||
itemConfig={maybeIconConfig[0]}
|
</span>
|
||||||
configCallbackProps={configCallbackProps}
|
<ToolbarItemTooltip
|
||||||
/>
|
itemConfig={maybeIconConfig[0]}
|
||||||
|
configCallbackProps={configCallbackProps}
|
||||||
|
wrapperClassName="ui-open:!hidden"
|
||||||
|
contentClassName={tooltipContentClassName}
|
||||||
|
>
|
||||||
|
{showRichContent ? (
|
||||||
|
<ToolbarItemTooltipRichContent
|
||||||
|
itemConfig={maybeIconConfig[0]}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<ToolbarItemTooltipShortContent
|
||||||
|
status={maybeIconConfig[0].status}
|
||||||
|
title={maybeIconConfig[0].title}
|
||||||
|
hotkey={maybeIconConfig[0].hotkey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ToolbarItemTooltip>
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
</ActionButtonDropdown>
|
</ActionButtonDropdown>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -218,7 +271,13 @@ export function Toolbar({
|
|||||||
|
|
||||||
// A single button
|
// A single button
|
||||||
return (
|
return (
|
||||||
<div className="relative" key={itemConfig.id}>
|
<div
|
||||||
|
className="relative"
|
||||||
|
key={itemConfig.id}
|
||||||
|
// Mouse events do not fire on disabled buttons
|
||||||
|
onMouseEnter={handleMouseEnter}
|
||||||
|
onMouseLeave={handleMouseLeave}
|
||||||
|
>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
key={itemConfig.id}
|
key={itemConfig.id}
|
||||||
@ -255,7 +314,18 @@ export function Toolbar({
|
|||||||
<ToolbarItemTooltip
|
<ToolbarItemTooltip
|
||||||
itemConfig={itemConfig}
|
itemConfig={itemConfig}
|
||||||
configCallbackProps={configCallbackProps}
|
configCallbackProps={configCallbackProps}
|
||||||
/>
|
contentClassName={tooltipContentClassName}
|
||||||
|
>
|
||||||
|
{showRichContent ? (
|
||||||
|
<ToolbarItemTooltipRichContent itemConfig={itemConfig} />
|
||||||
|
) : (
|
||||||
|
<ToolbarItemTooltipShortContent
|
||||||
|
status={itemConfig.status}
|
||||||
|
title={itemConfig.title}
|
||||||
|
hotkey={itemConfig.hotkey}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</ToolbarItemTooltip>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@ -269,6 +339,12 @@ export function Toolbar({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ToolbarItemContentsProps extends React.PropsWithChildren {
|
||||||
|
itemConfig: ToolbarItemResolved
|
||||||
|
configCallbackProps: ToolbarItemCallbackProps
|
||||||
|
wrapperClassName?: string
|
||||||
|
contentClassName?: string
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* The single button and dropdown button share content, so we extract it here
|
* The single button and dropdown button share content, so we extract it here
|
||||||
* It contains a tooltip with the title, description, and links
|
* It contains a tooltip with the title, description, and links
|
||||||
@ -277,12 +353,10 @@ export function Toolbar({
|
|||||||
const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
||||||
itemConfig,
|
itemConfig,
|
||||||
configCallbackProps,
|
configCallbackProps,
|
||||||
}: {
|
wrapperClassName = '',
|
||||||
itemConfig: ToolbarItemResolved
|
contentClassName = '',
|
||||||
configCallbackProps: ToolbarItemCallbackProps
|
children,
|
||||||
}) {
|
}: ToolbarItemContentsProps) {
|
||||||
const { state } = useModelingContext()
|
|
||||||
|
|
||||||
useHotkeys(
|
useHotkeys(
|
||||||
itemConfig.hotkey || '',
|
itemConfig.hotkey || '',
|
||||||
() => {
|
() => {
|
||||||
@ -305,11 +379,50 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
|||||||
? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties)
|
? ({ '-webkit-app-region': 'no-drag' } as React.CSSProperties)
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
|
hoverOnly
|
||||||
position="bottom"
|
position="bottom"
|
||||||
wrapperClassName="!p-4 !pointer-events-auto"
|
wrapperClassName={'!p-4 !pointer-events-auto ' + wrapperClassName}
|
||||||
contentClassName="!text-left text-wrap !text-xs !p-0 !pb-2 flex gap-2 !max-w-none !w-72 flex-col items-stretch"
|
contentClassName={contentClassName}
|
||||||
|
delay={0}
|
||||||
>
|
>
|
||||||
|
{children}
|
||||||
|
</Tooltip>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const ToolbarItemTooltipShortContent = ({
|
||||||
|
status,
|
||||||
|
title,
|
||||||
|
hotkey,
|
||||||
|
}: {
|
||||||
|
status: string
|
||||||
|
title: string
|
||||||
|
hotkey?: string | string[]
|
||||||
|
}) => (
|
||||||
|
<span
|
||||||
|
className={`text-sm ${
|
||||||
|
status !== 'available' ? 'text-chalkboard-70 dark:text-chalkboard-40' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
{hotkey && (
|
||||||
|
<kbd className="inline-block ml-2 flex-none hotkey">{hotkey}</kbd>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
|
||||||
|
const ToolbarItemTooltipRichContent = ({
|
||||||
|
itemConfig,
|
||||||
|
}: {
|
||||||
|
itemConfig: ToolbarItemResolved
|
||||||
|
}) => {
|
||||||
|
const { state } = useModelingContext()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
|
<div className="rounded-top flex items-center gap-2 pt-3 pb-2 px-2 bg-chalkboard-20/50 dark:bg-chalkboard-80/50">
|
||||||
|
{itemConfig.icon && (
|
||||||
|
<CustomIcon className="w-5 h-5" name={itemConfig.icon} />
|
||||||
|
)}
|
||||||
<span
|
<span
|
||||||
className={`text-sm flex-1 ${
|
className={`text-sm flex-1 ${
|
||||||
itemConfig.status !== 'available'
|
itemConfig.status !== 'available'
|
||||||
@ -378,6 +491,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
|
|||||||
</ul>
|
</ul>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
|||||||
@ -25,13 +25,13 @@ import {
|
|||||||
CallExpression,
|
CallExpression,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
SourceRange,
|
|
||||||
Expr,
|
Expr,
|
||||||
parse,
|
parse,
|
||||||
recast,
|
recast,
|
||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||||
import { ConstrainInfo } from 'lang/std/stdTypes'
|
import { ConstrainInfo } from 'lang/std/stdTypes'
|
||||||
@ -46,8 +46,8 @@ import {
|
|||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
|
||||||
const [isCamMoving, setIsCamMoving] = useState(false)
|
const [isCamMoving, setIsCamMoving] = useState(false)
|
||||||
@ -510,7 +510,6 @@ const ConstraintSymbol = ({
|
|||||||
constrainInfo: ConstrainInfo
|
constrainInfo: ConstrainInfo
|
||||||
verticalPosition: 'top' | 'bottom'
|
verticalPosition: 'top' | 'bottom'
|
||||||
}) => {
|
}) => {
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
const varNameMap: {
|
const varNameMap: {
|
||||||
[key in ConstrainInfo['type']]: {
|
[key in ConstrainInfo['type']]: {
|
||||||
@ -600,8 +599,8 @@ const ConstraintSymbol = ({
|
|||||||
if (err(_node)) return
|
if (err(_node)) return
|
||||||
const node = _node.node
|
const node = _node.node
|
||||||
|
|
||||||
const range: SourceRange = node
|
const range = node
|
||||||
? [node.start, node.end, true]
|
? topLevelRange(node.start, node.end)
|
||||||
: defaultSourceRange()
|
: defaultSourceRange()
|
||||||
|
|
||||||
if (_type === 'intersectionTag') return null
|
if (_type === 'intersectionTag') return null
|
||||||
@ -630,7 +629,7 @@ const ConstraintSymbol = ({
|
|||||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||||
onClick={toSync(async () => {
|
onClick={toSync(async () => {
|
||||||
if (!isConstrained) {
|
if (!isConstrained) {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
name: 'Constrain with named value',
|
name: 'Constrain with named value',
|
||||||
@ -756,7 +755,6 @@ export const CamDebugSettings = () => {
|
|||||||
sceneInfra.camControls.reactCameraProperties
|
sceneInfra.camControls.reactCameraProperties
|
||||||
)
|
)
|
||||||
const [fov, setFov] = useState(12)
|
const [fov, setFov] = useState(12)
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
|
sceneInfra.camControls.setReactCameraPropertiesCallback(setCamSettings)
|
||||||
@ -775,7 +773,7 @@ export const CamDebugSettings = () => {
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={camSettings.type === 'perspective'}
|
checked={camSettings.type === 'perspective'}
|
||||||
onChange={() =>
|
onChange={() =>
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'settings',
|
groupId: 'settings',
|
||||||
|
|||||||
@ -59,6 +59,7 @@ import {
|
|||||||
sourceRangeFromRust,
|
sourceRangeFromRust,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
import { calculate_circle_from_3_points } from '../wasm-lib/pkg/wasm_lib'
|
||||||
import {
|
import {
|
||||||
@ -628,7 +629,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const startRange = _node1.node.start
|
const startRange = _node1.node.start
|
||||||
const endRange = _node1.node.end
|
const endRange = _node1.node.end
|
||||||
const sourceRange: SourceRange = [startRange, endRange, true]
|
const sourceRange = topLevelRange(startRange, endRange)
|
||||||
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
||||||
sourceRange,
|
sourceRange,
|
||||||
maybeModdedAst
|
maybeModdedAst
|
||||||
@ -1397,23 +1398,23 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const arg0 = arg(kclCircle3PointArgs[0])
|
const arg0 = arg(kclCircle3PointArgs[0])
|
||||||
if (!arg0) return kclManager.ast
|
if (!arg0) return kclManager.ast
|
||||||
arg0[0].value = points[0].x
|
arg0[0].value = { value: points[0].x, suffix: 'None' }
|
||||||
arg0[0].raw = points[0].x.toString()
|
arg0[0].raw = points[0].x.toString()
|
||||||
arg0[1].value = points[0].y
|
arg0[1].value = { value: points[0].y, suffix: 'None' }
|
||||||
arg0[1].raw = points[0].y.toString()
|
arg0[1].raw = points[0].y.toString()
|
||||||
|
|
||||||
const arg1 = arg(kclCircle3PointArgs[1])
|
const arg1 = arg(kclCircle3PointArgs[1])
|
||||||
if (!arg1) return kclManager.ast
|
if (!arg1) return kclManager.ast
|
||||||
arg1[0].value = points[1].x
|
arg1[0].value = { value: points[1].x, suffix: 'None' }
|
||||||
arg1[0].raw = points[1].x.toString()
|
arg1[0].raw = points[1].x.toString()
|
||||||
arg1[1].value = points[1].y
|
arg1[1].value = { value: points[1].y, suffix: 'None' }
|
||||||
arg1[1].raw = points[1].y.toString()
|
arg1[1].raw = points[1].y.toString()
|
||||||
|
|
||||||
const arg2 = arg(kclCircle3PointArgs[2])
|
const arg2 = arg(kclCircle3PointArgs[2])
|
||||||
if (!arg2) return kclManager.ast
|
if (!arg2) return kclManager.ast
|
||||||
arg2[0].value = points[2].x
|
arg2[0].value = { value: points[2].x, suffix: 'None' }
|
||||||
arg2[0].raw = points[2].x.toString()
|
arg2[0].raw = points[2].x.toString()
|
||||||
arg2[1].value = points[2].y
|
arg2[1].value = { value: points[2].y, suffix: 'None' }
|
||||||
arg2[1].raw = points[2].y.toString()
|
arg2[1].raw = points[2].y.toString()
|
||||||
|
|
||||||
const astSnapshot = structuredClone(kclManager.ast)
|
const astSnapshot = structuredClone(kclManager.ast)
|
||||||
@ -2012,7 +2013,7 @@ export class SceneEntities {
|
|||||||
kclManager.programMemory,
|
kclManager.programMemory,
|
||||||
{
|
{
|
||||||
type: 'sourceRange',
|
type: 'sourceRange',
|
||||||
sourceRange: [node.start, node.end, true],
|
sourceRange: topLevelRange(node.start, node.end),
|
||||||
},
|
},
|
||||||
getChangeSketchInput()
|
getChangeSketchInput()
|
||||||
)
|
)
|
||||||
@ -2050,8 +2051,8 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
if (!(sk instanceof Reason)) {
|
if (!(sk instanceof Reason)) {
|
||||||
sketch = sk
|
sketch = sk
|
||||||
} else if ((maybeSketch as Solid).sketch) {
|
} else if (maybeSketch && (maybeSketch.value as Solid)?.sketch) {
|
||||||
sketch = (maybeSketch as Solid).sketch
|
sketch = (maybeSketch.value as Solid).sketch
|
||||||
}
|
}
|
||||||
if (!sketch) return
|
if (!sketch) return
|
||||||
|
|
||||||
@ -2263,7 +2264,7 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
if (trap(_node, { suppress: true })) return
|
if (trap(_node, { suppress: true })) return
|
||||||
const node = _node.node
|
const node = _node.node
|
||||||
editorManager.setHighlightRange([[node.start, node.end, true]])
|
editorManager.setHighlightRange([topLevelRange(node.start, node.end)])
|
||||||
const yellow = 0xffff00
|
const yellow = 0xffff00
|
||||||
colorSegment(selected, yellow)
|
colorSegment(selected, yellow)
|
||||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
@ -2540,7 +2541,7 @@ export function sketchFromPathToNode({
|
|||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
const result = programMemory.get(varDec?.id?.name || '')
|
const result = programMemory.get(varDec?.id?.name || '')
|
||||||
if (result?.type === 'Solid') {
|
if (result?.type === 'Solid') {
|
||||||
return result.sketch
|
return result.value.sketch
|
||||||
}
|
}
|
||||||
const sg = sketchFromKclValue(result, varDec?.id?.name)
|
const sg = sketchFromKclValue(result, varDec?.id?.name)
|
||||||
if (err(sg)) {
|
if (err(sg)) {
|
||||||
|
|||||||
@ -61,6 +61,7 @@ import { SegmentInputs } from 'lang/std/stdTypes'
|
|||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { editorManager, sceneInfra } from 'lib/singletons'
|
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
@ -847,7 +848,7 @@ function createLengthIndicator({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Command Bar
|
// Command Bar
|
||||||
editorManager.commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
name: 'Constrain length',
|
name: 'Constrain length',
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import { ActionButtonProps } from './ActionButton'
|
import { ActionButtonProps } from './ActionButton'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import Tooltip from './Tooltip'
|
||||||
|
|
||||||
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
|
type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
|
||||||
name?: string
|
name?: string
|
||||||
|
dropdownTooltipText?: string
|
||||||
splitMenuItems: {
|
splitMenuItems: {
|
||||||
id: string
|
id: string
|
||||||
label: string
|
label: string
|
||||||
@ -17,6 +19,7 @@ type ActionButtonSplitProps = ActionButtonProps & { Element: 'button' } & {
|
|||||||
export function ActionButtonDropdown({
|
export function ActionButtonDropdown({
|
||||||
splitMenuItems,
|
splitMenuItems,
|
||||||
className,
|
className,
|
||||||
|
dropdownTooltipText = 'More tools',
|
||||||
children,
|
children,
|
||||||
...props
|
...props
|
||||||
}: ActionButtonSplitProps) {
|
}: ActionButtonSplitProps) {
|
||||||
@ -26,7 +29,14 @@ export function ActionButtonDropdown({
|
|||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<>
|
<>
|
||||||
{children}
|
{children}
|
||||||
<Popover.Button className="border-transparent dark:border-transparent p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary">
|
<Popover.Button
|
||||||
|
className={
|
||||||
|
'!border-transparent dark:!border-transparent ' +
|
||||||
|
'bg-chalkboard-transparent dark:bg-transparent disabled:bg-transparent dark:disabled:bg-transparent ' +
|
||||||
|
'enabled:hover:bg-chalkboard-10 dark:enabled:hover:bg-chalkboard-100 ' +
|
||||||
|
'pressed:!bg-primary pressed:enabled:hover:!text-chalkboard-10 p-0 m-0 rounded-none !outline-none ui-open:border-primary ui-open:bg-primary'
|
||||||
|
}
|
||||||
|
>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
name="caretDown"
|
name="caretDown"
|
||||||
className={
|
className={
|
||||||
@ -37,6 +47,14 @@ export function ActionButtonDropdown({
|
|||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{props.name ? props.name + ': ' : ''}open menu
|
{props.name ? props.name + ': ' : ''}open menu
|
||||||
</span>
|
</span>
|
||||||
|
<Tooltip
|
||||||
|
delay={0}
|
||||||
|
position="bottom"
|
||||||
|
hoverOnly
|
||||||
|
wrapperClassName="ui-open:!hidden"
|
||||||
|
>
|
||||||
|
{dropdownTooltipText}
|
||||||
|
</Tooltip>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Popover.Panel
|
<Popover.Panel
|
||||||
as="ul"
|
as="ul"
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { trap } from 'lib/trap'
|
import { trap } from 'lib/trap'
|
||||||
import { codeToIdSelections } from 'lib/selections'
|
import { codeToIdSelections } from 'lib/selections'
|
||||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
import { defaultSourceRange } from 'lang/wasm'
|
import { defaultSourceRange, SourceRange, topLevelRange } from 'lang/wasm'
|
||||||
|
|
||||||
export function AstExplorer() {
|
export function AstExplorer() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
@ -118,19 +118,19 @@ function DisplayObj({
|
|||||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||||
}`}
|
}`}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
editorManager.setHighlightRange([
|
||||||
|
topLevelRange(obj?.start || 0, obj.end),
|
||||||
|
])
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
onMouseMove={(e) => {
|
onMouseMove={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
editorManager.setHighlightRange([
|
||||||
|
topLevelRange(obj?.start || 0, obj.end),
|
||||||
|
])
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(obj?.start || 0, obj.end || 0)
|
||||||
obj?.start || 0,
|
|
||||||
obj.end || 0,
|
|
||||||
true,
|
|
||||||
]
|
|
||||||
const idInfo = codeToIdSelections([
|
const idInfo = codeToIdSelections([
|
||||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||||
])[0]
|
])[0]
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { Combobox } from '@headlessui/react'
|
import { Combobox } from '@headlessui/react'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
import { CommandArgument, CommandArgumentOption } from 'lib/commandTypes'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { AnyStateMachine, StateFrom } from 'xstate'
|
import { AnyStateMachine, StateFrom } from 'xstate'
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ function CommandArgOptionInput({
|
|||||||
placeholder?: string
|
placeholder?: string
|
||||||
}) {
|
}) {
|
||||||
const actorContext = useSelector(arg.machineActor, contextSelector)
|
const actorContext = useSelector(arg.machineActor, contextSelector)
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const resolvedOptions = useMemo(
|
const resolvedOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
typeof arg.options === 'function'
|
typeof arg.options === 'function'
|
||||||
@ -134,6 +134,7 @@ function CommandArgOptionInput({
|
|||||||
</label>
|
</label>
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
id="option-input"
|
id="option-input"
|
||||||
|
data-testid="cmd-bar-arg-value"
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
onChange={(event) =>
|
onChange={(event) =>
|
||||||
!event.target.disabled && setQuery(event.target.value)
|
!event.target.disabled && setQuery(event.target.value)
|
||||||
@ -141,7 +142,7 @@ function CommandArgOptionInput({
|
|||||||
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
className="flex-grow px-2 py-1 border-b border-b-chalkboard-100 dark:border-b-chalkboard-80 !bg-transparent focus:outline-none"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
if (event.metaKey && event.key === 'k')
|
if (event.metaKey && event.key === 'k')
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
if (event.key === 'Backspace' && !event.currentTarget.value) {
|
||||||
stepBack()
|
stepBack()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Dialog, Popover, Transition } from '@headlessui/react'
|
import { Dialog, Popover, Transition } from '@headlessui/react'
|
||||||
import { Fragment, useEffect } from 'react'
|
import { Fragment, useEffect } from 'react'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import CommandBarArgument from './CommandBarArgument'
|
import CommandBarArgument from './CommandBarArgument'
|
||||||
import CommandComboBox from '../CommandComboBox'
|
import CommandComboBox from '../CommandComboBox'
|
||||||
import CommandBarReview from './CommandBarReview'
|
import CommandBarReview from './CommandBarReview'
|
||||||
@ -8,12 +7,13 @@ import { useLocation } from 'react-router-dom'
|
|||||||
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
import useHotkeyWrapper from 'lib/hotkeyWrapper'
|
||||||
import { CustomIcon } from 'components/CustomIcon'
|
import { CustomIcon } from 'components/CustomIcon'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
export const COMMAND_PALETTE_HOTKEY = 'mod+k'
|
||||||
|
|
||||||
export const CommandBar = () => {
|
export const CommandBar = () => {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, commands },
|
context: { selectedCommand, currentArgument, commands },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
@ -23,16 +23,16 @@ export const CommandBar = () => {
|
|||||||
// Close the command bar when navigating
|
// Close the command bar when navigating
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (commandBarState.matches('Closed')) return
|
if (commandBarState.matches('Closed')) return
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}, [pathname])
|
}, [pathname])
|
||||||
|
|
||||||
// Hook up keyboard shortcuts
|
// Hook up keyboard shortcuts
|
||||||
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
useHotkeyWrapper([COMMAND_PALETTE_HOTKEY], () => {
|
||||||
if (commandBarState.context.commands.length === 0) return
|
if (commandBarState.context.commands.length === 0) return
|
||||||
if (commandBarState.matches('Closed')) {
|
if (commandBarState.matches('Closed')) {
|
||||||
commandBarSend({ type: 'Open' })
|
commandBarActor.send({ type: 'Open' })
|
||||||
} else {
|
} else {
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -52,14 +52,14 @@ export const CommandBar = () => {
|
|||||||
...entries[entries.length - 1][1],
|
...entries[entries.length - 1][1],
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Edit argument',
|
type: 'Edit argument',
|
||||||
data: {
|
data: {
|
||||||
arg: currentArg,
|
arg: currentArg,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
commandBarSend({ type: 'Deselect command' })
|
commandBarActor.send({ type: 'Deselect command' })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const entries = Object.entries(selectedCommand?.args || {})
|
const entries = Object.entries(selectedCommand?.args || {})
|
||||||
@ -68,9 +68,9 @@ export const CommandBar = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
commandBarSend({ type: 'Deselect command' })
|
commandBarActor.send({ type: 'Deselect command' })
|
||||||
} else {
|
} else {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Change current argument',
|
type: 'Change current argument',
|
||||||
data: {
|
data: {
|
||||||
arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
|
arg: { name: entries[index - 1][0], ...entries[index - 1][1] },
|
||||||
@ -85,14 +85,14 @@ export const CommandBar = () => {
|
|||||||
show={!commandBarState.matches('Closed') || false}
|
show={!commandBarState.matches('Closed') || false}
|
||||||
afterLeave={() => {
|
afterLeave={() => {
|
||||||
if (selectedCommand?.onCancel) selectedCommand.onCancel()
|
if (selectedCommand?.onCancel) selectedCommand.onCancel()
|
||||||
commandBarSend({ type: 'Clear' })
|
commandBarActor.send({ type: 'Clear' })
|
||||||
}}
|
}}
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
>
|
>
|
||||||
<WrapperComponent
|
<WrapperComponent
|
||||||
open={!commandBarState.matches('Closed') || isSelectionArgument}
|
open={!commandBarState.matches('Closed') || isSelectionArgument}
|
||||||
onClose={() => {
|
onClose={() => {
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}}
|
}}
|
||||||
className={
|
className={
|
||||||
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
|
'fixed inset-0 z-50 overflow-y-auto pb-4 pt-1 ' +
|
||||||
@ -122,7 +122,7 @@ export const CommandBar = () => {
|
|||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => commandBarSend({ type: 'Close' })}
|
onClick={() => commandBarActor.send({ type: 'Close' })}
|
||||||
className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
|
className="group block !absolute left-auto right-full top-[-3px] m-2.5 p-0 border-none bg-transparent hover:bg-transparent"
|
||||||
>
|
>
|
||||||
<CustomIcon
|
<CustomIcon
|
||||||
|
|||||||
@ -2,13 +2,13 @@ import CommandArgOptionInput from './CommandArgOptionInput'
|
|||||||
import CommandBarBasicInput from './CommandBarBasicInput'
|
import CommandBarBasicInput from './CommandBarBasicInput'
|
||||||
import CommandBarSelectionInput from './CommandBarSelectionInput'
|
import CommandBarSelectionInput from './CommandBarSelectionInput'
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import CommandBarHeader from './CommandBarHeader'
|
import CommandBarHeader from './CommandBarHeader'
|
||||||
import CommandBarKclInput from './CommandBarKclInput'
|
import CommandBarKclInput from './CommandBarKclInput'
|
||||||
import CommandBarTextareaInput from './CommandBarTextareaInput'
|
import CommandBarTextareaInput from './CommandBarTextareaInput'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const {
|
const {
|
||||||
context: { currentArgument },
|
context: { currentArgument },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
@ -16,7 +16,7 @@ function CommandBarArgument({ stepBack }: { stepBack: () => void }) {
|
|||||||
function onSubmit(data: unknown) {
|
function onSubmit(data: unknown) {
|
||||||
if (!currentArgument) return
|
if (!currentArgument) return
|
||||||
|
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Submit argument',
|
type: 'Submit argument',
|
||||||
data: {
|
data: {
|
||||||
[currentArgument.name]: data,
|
[currentArgument.name]: data,
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
import { useEffect, useRef } from 'react'
|
import { useEffect, useRef } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ function CommandBarBasicInput({
|
|||||||
stepBack: () => void
|
stepBack: () => void
|
||||||
onSubmit: (event: unknown) => void
|
onSubmit: (event: unknown) => void
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { CustomIcon } from '../CustomIcon'
|
import { CustomIcon } from '../CustomIcon'
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { ActionButton } from '../ActionButton'
|
import { ActionButton } from '../ActionButton'
|
||||||
@ -7,9 +6,10 @@ import { useHotkeys } from 'react-hotkeys-hook'
|
|||||||
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
|
import { KclCommandValue, KclExpressionWithVariable } from 'lib/commandTypes'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { roundOff } from 'lib/utils'
|
import { roundOff } from 'lib/utils'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
@ -49,7 +49,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
]
|
]
|
||||||
const arg = selectedCommand?.args[argName]
|
const arg = selectedCommand?.args[argName]
|
||||||
if (!argName || !arg) return
|
if (!argName || !arg) return
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Change current argument',
|
type: 'Change current argument',
|
||||||
data: { arg: { ...arg, name: argName } },
|
data: { arg: { ...arg, name: argName } },
|
||||||
})
|
})
|
||||||
@ -100,7 +100,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
}
|
}
|
||||||
disabled={!isReviewing && currentArgument?.name === argName}
|
disabled={!isReviewing && currentArgument?.name === argName}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: isReviewing
|
type: isReviewing
|
||||||
? 'Edit argument'
|
? 'Edit argument'
|
||||||
: 'Change current argument',
|
: 'Change current argument',
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
} from '@codemirror/autocomplete'
|
} from '@codemirror/autocomplete'
|
||||||
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
|
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
|
||||||
import { CustomIcon } from 'components/CustomIcon'
|
import { CustomIcon } from 'components/CustomIcon'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
|
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
|
||||||
import { getSystemTheme } from 'lib/theme'
|
import { getSystemTheme } from 'lib/theme'
|
||||||
@ -20,6 +19,7 @@ import styles from './CommandBarKclInput.module.css'
|
|||||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
const machineContextSelector = (snapshot?: {
|
const machineContextSelector = (snapshot?: {
|
||||||
context: Record<string, unknown>
|
context: Record<string, unknown>
|
||||||
@ -37,7 +37,7 @@ function CommandBarKclInput({
|
|||||||
stepBack: () => void
|
stepBack: () => void
|
||||||
onSubmit: (event: unknown) => void
|
onSubmit: (event: unknown) => void
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const previouslySetValue = commandBarState.context.argumentsToSubmit[
|
const previouslySetValue = commandBarState.context.argumentsToSubmit[
|
||||||
arg.name
|
arg.name
|
||||||
] as KclCommandValue | undefined
|
] as KclCommandValue | undefined
|
||||||
@ -82,7 +82,7 @@ function CommandBarKclInput({
|
|||||||
false
|
false
|
||||||
)
|
)
|
||||||
const [canSubmit, setCanSubmit] = useState(true)
|
const [canSubmit, setCanSubmit] = useState(true)
|
||||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||||
const editorRef = useRef<HTMLDivElement>(null)
|
const editorRef = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,43 +0,0 @@
|
|||||||
import { createActorContext } from '@xstate/react'
|
|
||||||
import { editorManager } from 'lib/singletons'
|
|
||||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
|
||||||
import { useEffect } from 'react'
|
|
||||||
|
|
||||||
export const CommandsContext = createActorContext(
|
|
||||||
commandBarMachine.provide({
|
|
||||||
guards: {
|
|
||||||
'Command has no arguments': ({ context }) => {
|
|
||||||
return (
|
|
||||||
!context.selectedCommand?.args ||
|
|
||||||
Object.keys(context.selectedCommand?.args).length === 0
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'All arguments are skippable': ({ context }) => {
|
|
||||||
return Object.values(context.selectedCommand!.args!).every(
|
|
||||||
(argConfig) => argConfig.skip
|
|
||||||
)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const CommandBarProvider = ({
|
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<CommandsContext.Provider>
|
|
||||||
<CommandBarProviderInner>{children}</CommandBarProviderInner>
|
|
||||||
</CommandsContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
function CommandBarProviderInner({ children }: { children: React.ReactNode }) {
|
|
||||||
const commandBarActor = CommandsContext.useActorRef()
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
editorManager.setCommandBarSend(commandBarActor.send)
|
|
||||||
})
|
|
||||||
|
|
||||||
return children
|
|
||||||
}
|
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
import CommandBarHeader from './CommandBarHeader'
|
import CommandBarHeader from './CommandBarHeader'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const {
|
const {
|
||||||
context: { argumentsToSubmit, selectedCommand },
|
context: { argumentsToSubmit, selectedCommand },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
@ -33,7 +33,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
|||||||
parseInt(b.keys[0], 10) - 1
|
parseInt(b.keys[0], 10) - 1
|
||||||
]
|
]
|
||||||
const arg = selectedCommand?.args[argName]
|
const arg = selectedCommand?.args[argName]
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Edit argument',
|
type: 'Edit argument',
|
||||||
data: { arg: { ...arg, name: argName } },
|
data: { arg: { ...arg, name: argName } },
|
||||||
})
|
})
|
||||||
@ -50,7 +50,7 @@ function CommandBarReview({ stepBack }: { stepBack: () => void }) {
|
|||||||
|
|
||||||
function submitCommand(e: React.FormEvent<HTMLFormElement>) {
|
function submitCommand(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Submit command',
|
type: 'Submit command',
|
||||||
output: argumentsToSubmit,
|
output: argumentsToSubmit,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useSelector } from '@xstate/react'
|
import { useSelector } from '@xstate/react'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { Artifact } from 'lang/std/artifactGraph'
|
import { Artifact } from 'lang/std/artifactGraph'
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
import {
|
import {
|
||||||
@ -10,6 +9,7 @@ import {
|
|||||||
import { kclManager } from 'lib/singletons'
|
import { kclManager } from 'lib/singletons'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
import { modelingMachine } from 'machines/modelingMachine'
|
import { modelingMachine } from 'machines/modelingMachine'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||||
import { StateFrom } from 'xstate'
|
import { StateFrom } from 'xstate'
|
||||||
@ -17,7 +17,7 @@ import { StateFrom } from 'xstate'
|
|||||||
const semanticEntityNames: {
|
const semanticEntityNames: {
|
||||||
[key: string]: Array<Artifact['type'] | 'defaultPlane'>
|
[key: string]: Array<Artifact['type'] | 'defaultPlane'>
|
||||||
} = {
|
} = {
|
||||||
face: ['wall', 'cap', 'solid2D'],
|
face: ['wall', 'cap', 'solid2d'],
|
||||||
edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
edge: ['segment', 'sweepEdge', 'edgeCutEdge'],
|
||||||
point: [],
|
point: [],
|
||||||
plane: ['defaultPlane'],
|
plane: ['defaultPlane'],
|
||||||
@ -49,7 +49,7 @@ function CommandBarSelectionInput({
|
|||||||
onSubmit: (data: unknown) => void
|
onSubmit: (data: unknown) => void
|
||||||
}) {
|
}) {
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const [hasSubmitted, setHasSubmitted] = useState(false)
|
const [hasSubmitted, setHasSubmitted] = useState(false)
|
||||||
const selection = useSelector(arg.machineActor, selectionSelector)
|
const selection = useSelector(arg.machineActor, selectionSelector)
|
||||||
const selectionsByType = useMemo(() => {
|
const selectionsByType = useMemo(() => {
|
||||||
@ -145,7 +145,7 @@ function CommandBarSelectionInput({
|
|||||||
if (event.key === 'Backspace') {
|
if (event.key === 'Backspace') {
|
||||||
stepBack()
|
stepBack()
|
||||||
} else if (event.key === 'Escape') {
|
} else if (event.key === 'Escape') {
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { CommandArgument } from 'lib/commandTypes'
|
import { CommandArgument } from 'lib/commandTypes'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
import { RefObject, useEffect, useRef } from 'react'
|
import { RefObject, useEffect, useRef } from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
|
|
||||||
@ -15,8 +15,8 @@ function CommandBarTextareaInput({
|
|||||||
stepBack: () => void
|
stepBack: () => void
|
||||||
onSubmit: (event: unknown) => void
|
onSubmit: (event: unknown) => void
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend, commandBarState } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
useHotkeys('mod + k, mod + /', () => commandBarActor.send({ type: 'Close' }))
|
||||||
const formRef = useRef<HTMLFormElement>(null)
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
const inputRef = useRef<HTMLTextAreaElement>(null)
|
const inputRef = useRef<HTMLTextAreaElement>(null)
|
||||||
useTextareaAutoGrow(inputRef)
|
useTextareaAutoGrow(inputRef)
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
import { hotkeyDisplay } from 'lib/hotkeyWrapper'
|
||||||
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
|
import { COMMAND_PALETTE_HOTKEY } from './CommandBar/CommandBar'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
export function CommandBarOpenButton() {
|
export function CommandBarOpenButton() {
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const platform = usePlatform()
|
const platform = usePlatform()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
|
className="group rounded-full flex items-center justify-center gap-2 px-2 py-1 bg-primary/10 dark:bg-chalkboard-90 dark:backdrop-blur-sm border-primary hover:border-primary dark:border-chalkboard-50 dark:hover:border-inherit text-primary dark:text-inherit"
|
||||||
onClick={() => commandBarSend({ type: 'Open' })}
|
onClick={() => commandBarActor.send({ type: 'Open' })}
|
||||||
data-testid="command-bar-open-button"
|
data-testid="command-bar-open-button"
|
||||||
>
|
>
|
||||||
<span>Commands</span>
|
<span>Commands</span>
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { Combobox } from '@headlessui/react'
|
import { Combobox } from '@headlessui/react'
|
||||||
import Fuse from 'fuse.js'
|
import Fuse from 'fuse.js'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { Command } from 'lib/commandTypes'
|
import { Command } from 'lib/commandTypes'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { getActorNextEvents } from 'lib/utils'
|
import { getActorNextEvents } from 'lib/utils'
|
||||||
import { sortCommands } from 'lib/commandUtils'
|
import { sortCommands } from 'lib/commandUtils'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
function CommandComboBox({
|
function CommandComboBox({
|
||||||
options,
|
options,
|
||||||
@ -14,7 +14,6 @@ function CommandComboBox({
|
|||||||
options: Command[]
|
options: Command[]
|
||||||
placeholder?: string
|
placeholder?: string
|
||||||
}) {
|
}) {
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
const [filteredOptions, setFilteredOptions] = useState<typeof options>()
|
||||||
|
|
||||||
@ -41,7 +40,7 @@ function CommandComboBox({
|
|||||||
}, [query])
|
}, [query])
|
||||||
|
|
||||||
function handleSelection(command: Command) {
|
function handleSelection(command: Command) {
|
||||||
commandBarSend({ type: 'Select command', data: { command } })
|
commandBarActor.send({ type: 'Select command', data: { command } })
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,6 +51,7 @@ function CommandComboBox({
|
|||||||
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
|
className="w-5 h-5 bg-primary/10 dark:bg-primary text-primary dark:text-inherit"
|
||||||
/>
|
/>
|
||||||
<Combobox.Input
|
<Combobox.Input
|
||||||
|
data-testid="cmd-bar-search"
|
||||||
onChange={(event) => setQuery(event.target.value)}
|
onChange={(event) => setQuery(event.target.value)}
|
||||||
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
className="w-full bg-transparent focus:outline-none selection:bg-primary/20 dark:selection:bg-primary/40 dark:focus:outline-none"
|
||||||
onKeyDown={(event) => {
|
onKeyDown={(event) => {
|
||||||
@ -60,7 +60,7 @@ function CommandComboBox({
|
|||||||
(event.key === 'Backspace' && !event.currentTarget.value)
|
(event.key === 'Backspace' && !event.currentTarget.value)
|
||||||
) {
|
) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
placeholder={
|
placeholder={
|
||||||
@ -85,6 +85,7 @@ function CommandComboBox({
|
|||||||
value={option}
|
value={option}
|
||||||
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
className="flex items-center gap-4 px-4 py-1.5 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90 ui-disabled:!text-chalkboard-50"
|
||||||
disabled={optionIsDisabled(option)}
|
disabled={optionIsDisabled(option)}
|
||||||
|
data-testid={`cmd-bar-option`}
|
||||||
>
|
>
|
||||||
{'icon' in option && option.icon && (
|
{'icon' in option && option.icon && (
|
||||||
<CustomIcon name={option.icon} className="w-5 h-5" />
|
<CustomIcon name={option.icon} className="w-5 h-5" />
|
||||||
|
|||||||
@ -1,24 +1,21 @@
|
|||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
import {
|
import { expandPlane, PlaneArtifactRich } from 'lang/std/artifactGraph'
|
||||||
ArtifactGraph,
|
import { ArtifactGraph } from 'lang/wasm'
|
||||||
expandPlane,
|
|
||||||
PlaneArtifactRich,
|
|
||||||
} from 'lang/std/artifactGraph'
|
|
||||||
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
|
import { DebugDisplayArray, GenericObj } from './DebugDisplayObj'
|
||||||
|
|
||||||
export function DebugFeatureTree() {
|
export function DebugArtifactGraph() {
|
||||||
const featureTree = useMemo(() => {
|
const artifactGraphTree = useMemo(() => {
|
||||||
return computeTree(engineCommandManager.artifactGraph)
|
return computeTree(engineCommandManager.artifactGraph)
|
||||||
}, [engineCommandManager.artifactGraph])
|
}, [engineCommandManager.artifactGraph])
|
||||||
|
|
||||||
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
|
const filterKeys: string[] = ['__meta', 'codeRef', 'pathToNode']
|
||||||
return (
|
return (
|
||||||
<details data-testid="debug-feature-tree" className="relative">
|
<details data-testid="debug-feature-tree" className="relative">
|
||||||
<summary>Feature Tree</summary>
|
<summary>Artifact Graph</summary>
|
||||||
{featureTree.length > 0 ? (
|
{artifactGraphTree.length > 0 ? (
|
||||||
<pre className="text-xs">
|
<pre className="text-xs">
|
||||||
<DebugDisplayArray arr={featureTree} filterKeys={filterKeys} />
|
<DebugDisplayArray arr={artifactGraphTree} filterKeys={filterKeys} />
|
||||||
</pre>
|
</pre>
|
||||||
) : (
|
) : (
|
||||||
<p>(Empty)</p>
|
<p>(Empty)</p>
|
||||||
@ -12,7 +12,6 @@ import {
|
|||||||
StateFrom,
|
StateFrom,
|
||||||
fromPromise,
|
fromPromise,
|
||||||
} from 'xstate'
|
} from 'xstate'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { fileMachine } from 'machines/fileMachine'
|
import { fileMachine } from 'machines/fileMachine'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import {
|
import {
|
||||||
@ -30,6 +29,7 @@ import {
|
|||||||
} from 'lib/getKclSamplesManifest'
|
} from 'lib/getKclSamplesManifest'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -47,7 +47,6 @@ export const FileMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
const { project, file } = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
|
||||||
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
|
||||||
@ -90,7 +89,7 @@ export const FileMachineProvider = ({
|
|||||||
navigateToFile: ({ context, event }) => {
|
navigateToFile: ({ context, event }) => {
|
||||||
if (event.type !== 'xstate.done.actor.create-and-open-file') return
|
if (event.type !== 'xstate.done.actor.create-and-open-file') return
|
||||||
if (event.output && 'name' in event.output) {
|
if (event.output && 'name' in event.output) {
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
navigate(
|
navigate(
|
||||||
`..${PATHS.FILE}/${encodeURIComponent(
|
`..${PATHS.FILE}/${encodeURIComponent(
|
||||||
context.selectedDirectory +
|
context.selectedDirectory +
|
||||||
@ -336,15 +335,18 @@ export const FileMachineProvider = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
commandBarSend({ type: 'Add commands', data: { commands: kclCommandMemo } })
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: { commands: kclCommandMemo },
|
||||||
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Remove commands',
|
type: 'Remove commands',
|
||||||
data: { commands: kclCommandMemo },
|
data: { commands: kclCommandMemo },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [commandBarSend, kclCommandMemo])
|
}, [commandBarActor.send, kclCommandMemo])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FileContext.Provider
|
<FileContext.Provider
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { createContext, useEffect, useState } from 'react'
|
import { createContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { engineCommandManager } from 'lib/singletons'
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { components } from 'lib/machine-api'
|
import { components } from 'lib/machine-api'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
export type MachinesListing = Array<
|
export type MachinesListing = Array<
|
||||||
components['schemas']['MachineInfoResponse']
|
components['schemas']['MachineInfoResponse']
|
||||||
@ -42,8 +42,6 @@ export const MachineManagerProvider = ({
|
|||||||
components['schemas']['MachineInfoResponse'] | null
|
components['schemas']['MachineInfoResponse'] | null
|
||||||
>(null)
|
>(null)
|
||||||
|
|
||||||
const commandBarActor = CommandsContext.useActorRef()
|
|
||||||
|
|
||||||
// Get the reason message for why there are no machines.
|
// Get the reason message for why there are no machines.
|
||||||
const noMachinesReason = (): string | undefined => {
|
const noMachinesReason = (): string | undefined => {
|
||||||
if (machines.length > 0) {
|
if (machines.length > 0) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine, useSelector } from '@xstate/react'
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
useEffect,
|
useEffect,
|
||||||
@ -11,6 +11,7 @@ import {
|
|||||||
AnyStateMachine,
|
AnyStateMachine,
|
||||||
ContextFrom,
|
ContextFrom,
|
||||||
Prop,
|
Prop,
|
||||||
|
SnapshotFrom,
|
||||||
StateFrom,
|
StateFrom,
|
||||||
assign,
|
assign,
|
||||||
fromPromise,
|
fromPromise,
|
||||||
@ -78,7 +79,6 @@ import toast from 'react-hot-toast'
|
|||||||
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
|
||||||
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
|
||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import {
|
import {
|
||||||
ExportIntent,
|
ExportIntent,
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
@ -91,6 +91,7 @@ import { IndexLoaderData } from 'lib/types'
|
|||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { promptToEditFlow } from 'lib/promptToEdit'
|
import { promptToEditFlow } from 'lib/promptToEdit'
|
||||||
import { kclEditorActor } from 'machines/kclEditorMachine'
|
import { kclEditorActor } from 'machines/kclEditorMachine'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -102,6 +103,10 @@ export const ModelingMachineContext = createContext(
|
|||||||
{} as MachineContext<typeof modelingMachine>
|
{} as MachineContext<typeof modelingMachine>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const commandBarIsClosedSelector = (
|
||||||
|
state: SnapshotFrom<typeof commandBarActor>
|
||||||
|
) => state.matches('Closed')
|
||||||
|
|
||||||
export const ModelingMachineProvider = ({
|
export const ModelingMachineProvider = ({
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@ -132,8 +137,10 @@ export const ModelingMachineProvider = ({
|
|||||||
let [searchParams] = useSearchParams()
|
let [searchParams] = useSearchParams()
|
||||||
const pool = searchParams.get('pool')
|
const pool = searchParams.get('pool')
|
||||||
|
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
const isCommandBarClosed = useSelector(
|
||||||
|
commandBarActor,
|
||||||
|
commandBarIsClosedSelector
|
||||||
|
)
|
||||||
// Settings machine setup
|
// Settings machine setup
|
||||||
// const retrievedSettings = useRef(
|
// const retrievedSettings = useRef(
|
||||||
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||||
@ -388,7 +395,16 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (setSelections.selectionType === 'completeSelection') {
|
if (setSelections.selectionType === 'completeSelection') {
|
||||||
editorManager.selectRange(setSelections.selection)
|
const codeMirrorSelection = editorManager.createEditorSelection(
|
||||||
|
setSelections.selection
|
||||||
|
)
|
||||||
|
kclEditorActor.send({
|
||||||
|
type: 'setLastSelectionEvent',
|
||||||
|
data: {
|
||||||
|
codeMirrorSelection,
|
||||||
|
scrollIntoView: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return {
|
return {
|
||||||
selectionRanges: setSelections.selection,
|
selectionRanges: setSelections.selection,
|
||||||
@ -529,7 +545,6 @@ export const ModelingMachineProvider = ({
|
|||||||
trimmedPrompt,
|
trimmedPrompt,
|
||||||
fileMachineSend,
|
fileMachineSend,
|
||||||
navigate,
|
navigate,
|
||||||
commandBarSend,
|
|
||||||
context,
|
context,
|
||||||
token,
|
token,
|
||||||
settings: {
|
settings: {
|
||||||
@ -543,7 +558,7 @@ export const ModelingMachineProvider = ({
|
|||||||
'has valid selection for deletion': ({
|
'has valid selection for deletion': ({
|
||||||
context: { selectionRanges },
|
context: { selectionRanges },
|
||||||
}) => {
|
}) => {
|
||||||
if (!commandBarState.matches('Closed')) return false
|
if (!isCommandBarClosed) return false
|
||||||
if (selectionRanges.graphSelections.length <= 0) return false
|
if (selectionRanges.graphSelections.length <= 0) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { DebugFeatureTree } from 'components/DebugFeatureTree'
|
import { DebugArtifactGraph } from 'components/DebugArtifactGraph'
|
||||||
import { AstExplorer } from '../../AstExplorer'
|
import { AstExplorer } from '../../AstExplorer'
|
||||||
import { EngineCommands } from '../../EngineCommands'
|
import { EngineCommands } from '../../EngineCommands'
|
||||||
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
|
import { CamDebugSettings } from 'clientSideScene/ClientSideSceneComp'
|
||||||
@ -14,7 +14,7 @@ export const DebugPane = () => {
|
|||||||
<EngineCommands />
|
<EngineCommands />
|
||||||
<CamDebugSettings />
|
<CamDebugSettings />
|
||||||
<AstExplorer />
|
<AstExplorer />
|
||||||
<DebugFeatureTree />
|
<DebugArtifactGraph />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
@apply font-mono !no-underline text-xs font-bold select-none text-chalkboard-90;
|
||||||
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
@apply ui-active:bg-primary/10 ui-active:text-primary ui-active:text-inherit;
|
||||||
@apply transition-colors ease-out;
|
@apply transition-colors ease-out;
|
||||||
|
@apply m-0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.dark) .button {
|
:global(.dark) .button {
|
||||||
|
|||||||
@ -9,12 +9,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
|||||||
import { kclManager } from 'lib/singletons'
|
import { kclManager } from 'lib/singletons'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
||||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu>
|
<Menu>
|
||||||
@ -85,7 +84,7 @@ export const KclEditorMenu = ({ children }: PropsWithChildren) => {
|
|||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'code',
|
groupId: 'code',
|
||||||
|
|||||||
@ -95,9 +95,11 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
) {
|
) {
|
||||||
const sk = sketchFromKclValueOptional(val, key)
|
const sk = sketchFromKclValueOptional(val, key)
|
||||||
if (val.type === 'Solid') {
|
if (val.type === 'Solid') {
|
||||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
processedMemory[key] = val.value.value.map(
|
||||||
return rest
|
({ ...rest }: ExtrudeSurface) => {
|
||||||
})
|
return rest
|
||||||
|
}
|
||||||
|
)
|
||||||
} else if (!(sk instanceof Reason)) {
|
} else if (!(sk instanceof Reason)) {
|
||||||
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
|
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
return rest
|
return rest
|
||||||
|
|||||||
@ -15,12 +15,12 @@ import { ModelingPane } from './ModelingPane'
|
|||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
import { onboardingPaths } from 'routes/Onboarding/paths'
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -37,7 +37,6 @@ function getPlatformString(): 'web' | 'desktop' {
|
|||||||
|
|
||||||
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const kclContext = useKclContext()
|
const kclContext = useKclContext()
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
@ -66,7 +65,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
icon: 'floppyDiskArrow',
|
icon: 'floppyDiskArrow',
|
||||||
keybinding: 'Ctrl + Shift + E',
|
keybinding: 'Ctrl + Shift + E',
|
||||||
action: () =>
|
action: () =>
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Export', groupId: 'modeling' },
|
data: { name: 'Export', groupId: 'modeling' },
|
||||||
}),
|
}),
|
||||||
@ -79,7 +78,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
keybinding: 'Ctrl + Shift + M',
|
keybinding: 'Ctrl + Shift + M',
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
action: async () => {
|
action: async () => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: { name: 'Make', groupId: 'modeling' },
|
data: { name: 'Make', groupId: 'modeling' },
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
|
||||||
import {
|
import {
|
||||||
NETWORK_HEALTH_TEXT,
|
NETWORK_HEALTH_TEXT,
|
||||||
NetworkHealthIndicator,
|
NetworkHealthIndicator,
|
||||||
@ -12,9 +11,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
|||||||
// wrap in router and xState context
|
// wrap in router and xState context
|
||||||
return (
|
return (
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
|
||||||
</CommandBarProvider>
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { render, screen } from '@testing-library/react'
|
|||||||
import { BrowserRouter } from 'react-router-dom'
|
import { BrowserRouter } from 'react-router-dom'
|
||||||
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
import ProjectSidebarMenu from './ProjectSidebarMenu'
|
||||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
|
||||||
import { Project } from 'lib/project'
|
import { Project } from 'lib/project'
|
||||||
|
|
||||||
const now = new Date()
|
const now = new Date()
|
||||||
@ -33,11 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
|
|||||||
test('Disables popover menu by default', () => {
|
test('Disables popover menu by default', () => {
|
||||||
render(
|
render(
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<CommandBarProvider>
|
<SettingsAuthProviderJest>
|
||||||
<SettingsAuthProviderJest>
|
<ProjectSidebarMenu project={projectWellFormed} />
|
||||||
<ProjectSidebarMenu project={projectWellFormed} />
|
</SettingsAuthProviderJest>
|
||||||
</SettingsAuthProviderJest>
|
|
||||||
</CommandBarProvider>
|
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { Link, useLocation, useNavigate } from 'react-router-dom'
|
|||||||
import { Fragment, useMemo, useContext } from 'react'
|
import { Fragment, useMemo, useContext } from 'react'
|
||||||
import { Logo } from './Logo'
|
import { Logo } from './Logo'
|
||||||
import { APP_NAME } from 'lib/constants'
|
import { APP_NAME } from 'lib/constants'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { useLspContext } from './LspProvider'
|
import { useLspContext } from './LspProvider'
|
||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
import { engineCommandManager, kclManager } from 'lib/singletons'
|
||||||
@ -15,6 +14,9 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
|
|||||||
import usePlatform from 'hooks/usePlatform'
|
import usePlatform from 'hooks/usePlatform'
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
import Tooltip from './Tooltip'
|
import Tooltip from './Tooltip'
|
||||||
|
import { SnapshotFrom } from 'xstate'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
|
||||||
const ProjectSidebarMenu = ({
|
const ProjectSidebarMenu = ({
|
||||||
project,
|
project,
|
||||||
@ -84,6 +86,9 @@ function AppLogoLink({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const commandsSelector = (state: SnapshotFrom<typeof commandBarActor>) =>
|
||||||
|
state.context.commands
|
||||||
|
|
||||||
function ProjectMenuPopover({
|
function ProjectMenuPopover({
|
||||||
project,
|
project,
|
||||||
file,
|
file,
|
||||||
@ -96,16 +101,14 @@ function ProjectMenuPopover({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const machineManager = useContext(MachineManagerContext)
|
const machineManager = useContext(MachineManagerContext)
|
||||||
|
const commands = useSelector(commandBarActor, commandsSelector)
|
||||||
|
|
||||||
const { commandBarState, commandBarSend } = useCommandsContext()
|
|
||||||
const { onProjectClose } = useLspContext()
|
const { onProjectClose } = useLspContext()
|
||||||
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
const exportCommandInfo = { name: 'Export', groupId: 'modeling' }
|
||||||
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
const makeCommandInfo = { name: 'Make', groupId: 'modeling' }
|
||||||
const findCommand = (obj: { name: string; groupId: string }) =>
|
const findCommand = (obj: { name: string; groupId: string }) =>
|
||||||
Boolean(
|
Boolean(
|
||||||
commandBarState.context.commands.find(
|
commands.find((c) => c.name === obj.name && c.groupId === obj.groupId)
|
||||||
(c) => c.name === obj.name && c.groupId === obj.groupId
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
const machineCount = machineManager.machines.length
|
const machineCount = machineManager.machines.length
|
||||||
|
|
||||||
@ -150,7 +153,7 @@ function ProjectMenuPopover({
|
|||||||
),
|
),
|
||||||
disabled: !findCommand(exportCommandInfo),
|
disabled: !findCommand(exportCommandInfo),
|
||||||
onClick: () =>
|
onClick: () =>
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: exportCommandInfo,
|
data: exportCommandInfo,
|
||||||
}),
|
}),
|
||||||
@ -175,7 +178,7 @@ function ProjectMenuPopover({
|
|||||||
),
|
),
|
||||||
disabled: !findCommand(makeCommandInfo) || machineCount === 0,
|
disabled: !findCommand(makeCommandInfo) || machineCount === 0,
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: makeCommandInfo,
|
data: makeCommandInfo,
|
||||||
})
|
})
|
||||||
@ -200,7 +203,7 @@ function ProjectMenuPopover({
|
|||||||
[
|
[
|
||||||
platform,
|
platform,
|
||||||
findCommand,
|
findCommand,
|
||||||
commandBarSend,
|
commandBarActor.send,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
onProjectClose,
|
onProjectClose,
|
||||||
isDesktop,
|
isDesktop,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useMachine } from '@xstate/react'
|
import { useMachine } from '@xstate/react'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||||
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
import { useProjectsLoader } from 'hooks/useProjectsLoader'
|
||||||
import { projectsMachine } from 'machines/projectsMachine'
|
import { projectsMachine } from 'machines/projectsMachine'
|
||||||
@ -18,11 +17,13 @@ import {
|
|||||||
getNextProjectIndex,
|
getNextProjectIndex,
|
||||||
interpolateProjectNameWithIndex,
|
interpolateProjectNameWithIndex,
|
||||||
doesProjectNameNeedInterpolated,
|
doesProjectNameNeedInterpolated,
|
||||||
|
getUniqueProjectName,
|
||||||
} from 'lib/desktopFS'
|
} from 'lib/desktopFS'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
import useStateMachineCommands from 'hooks/useStateMachineCommands'
|
||||||
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
|
import { projectsCommandBarConfig } from 'lib/commandBarConfigs/projectsCommandConfig'
|
||||||
import { isDesktop } from 'lib/isDesktop'
|
import { isDesktop } from 'lib/isDesktop'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state?: StateFrom<T>
|
state?: StateFrom<T>
|
||||||
@ -72,7 +73,6 @@ const ProjectsContextDesktop = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const { onProjectOpen } = useLspContext()
|
const { onProjectOpen } = useLspContext()
|
||||||
const {
|
const {
|
||||||
settings: { context: settings },
|
settings: { context: settings },
|
||||||
@ -125,7 +125,7 @@ const ProjectsContextDesktop = ({
|
|||||||
},
|
},
|
||||||
null
|
null
|
||||||
)
|
)
|
||||||
commandBarSend({ type: 'Close' })
|
commandBarActor.send({ type: 'Close' })
|
||||||
const newPathName = `${PATHS.FILE}/${encodeURIComponent(
|
const newPathName = `${PATHS.FILE}/${encodeURIComponent(
|
||||||
projectPath
|
projectPath
|
||||||
)}`
|
)}`
|
||||||
@ -195,16 +195,12 @@ const ProjectsContextDesktop = ({
|
|||||||
: settings.projects.defaultProjectName.current
|
: settings.projects.defaultProjectName.current
|
||||||
).trim()
|
).trim()
|
||||||
|
|
||||||
if (doesProjectNameNeedInterpolated(name)) {
|
const uniqueName = getUniqueProjectName(name, input.projects)
|
||||||
const nextIndex = getNextProjectIndex(name, input.projects)
|
await createNewProjectDirectory(uniqueName)
|
||||||
name = interpolateProjectNameWithIndex(name, nextIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
await createNewProjectDirectory(name)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: `Successfully created "${name}"`,
|
message: `Successfully created "${uniqueName}"`,
|
||||||
name,
|
name: uniqueName,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
renameProject: fromPromise(async ({ input }) => {
|
renameProject: fromPromise(async ({ input }) => {
|
||||||
|
|||||||
@ -29,7 +29,6 @@ import {
|
|||||||
createSettingsCommand,
|
createSettingsCommand,
|
||||||
settingsWithCommandConfigs,
|
settingsWithCommandConfigs,
|
||||||
} from 'lib/commandBarConfigs/settingsCommandConfig'
|
} from 'lib/commandBarConfigs/settingsCommandConfig'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { Command } from 'lib/commandTypes'
|
import { Command } from 'lib/commandTypes'
|
||||||
import { BaseUnit } from 'lib/settings/settingsTypes'
|
import { BaseUnit } from 'lib/settings/settingsTypes'
|
||||||
import {
|
import {
|
||||||
@ -42,6 +41,7 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||||
import { codeManager } from 'lib/singletons'
|
import { codeManager } from 'lib/singletons'
|
||||||
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
|
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -109,7 +109,6 @@ export const SettingsAuthProviderBase = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const [settingsPath, setSettingsPath] = useState<string | undefined>(
|
const [settingsPath, setSettingsPath] = useState<string | undefined>(
|
||||||
undefined
|
undefined
|
||||||
)
|
)
|
||||||
@ -278,10 +277,10 @@ export const SettingsAuthProviderBase = ({
|
|||||||
)
|
)
|
||||||
.filter((c) => c !== null) as Command[]
|
.filter((c) => c !== null) as Command[]
|
||||||
|
|
||||||
commandBarSend({ type: 'Add commands', data: { commands: commands } })
|
commandBarActor.send({ type: 'Add commands', data: { commands: commands } })
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Remove commands',
|
type: 'Remove commands',
|
||||||
data: { commands },
|
data: { commands },
|
||||||
})
|
})
|
||||||
@ -290,7 +289,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
settingsState,
|
settingsState,
|
||||||
settingsSend,
|
settingsSend,
|
||||||
settingsActor,
|
settingsActor,
|
||||||
commandBarSend,
|
commandBarActor.send,
|
||||||
settingsWithCommandConfigs,
|
settingsWithCommandConfigs,
|
||||||
])
|
])
|
||||||
|
|
||||||
@ -303,7 +302,7 @@ export const SettingsAuthProviderBase = ({
|
|||||||
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
|
encodeURIComponent(loadedProject?.file?.path || BROWSER_PATH)
|
||||||
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
|
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
|
||||||
createRouteCommands(navigate, location, filePath)
|
createRouteCommands(navigate, location, filePath)
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Remove commands',
|
type: 'Remove commands',
|
||||||
data: {
|
data: {
|
||||||
commands: [
|
commands: [
|
||||||
@ -314,12 +313,12 @@ export const SettingsAuthProviderBase = ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (location.pathname === PATHS.HOME) {
|
if (location.pathname === PATHS.HOME) {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Add commands',
|
type: 'Add commands',
|
||||||
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
|
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
|
||||||
})
|
})
|
||||||
} else if (location.pathname.includes(PATHS.FILE)) {
|
} else if (location.pathname.includes(PATHS.FILE)) {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Add commands',
|
type: 'Add commands',
|
||||||
data: {
|
data: {
|
||||||
commands: [
|
commands: [
|
||||||
|
|||||||
@ -17,10 +17,11 @@ import {
|
|||||||
import { useRouteLoaderData } from 'react-router-dom'
|
import { useRouteLoaderData } from 'react-router-dom'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
import { IndexLoaderData } from 'lib/types'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
|
||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
import { ViewControlContextMenu } from './ViewControlMenu'
|
import { ViewControlContextMenu } from './ViewControlMenu'
|
||||||
|
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||||
|
import { useSelector } from '@xstate/react'
|
||||||
|
|
||||||
enum StreamState {
|
enum StreamState {
|
||||||
Playing = 'playing',
|
Playing = 'playing',
|
||||||
@ -35,7 +36,7 @@ export const Stream = () => {
|
|||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
const { commandBarState } = useCommandsContext()
|
const commandBarState = useCommandBarState()
|
||||||
const { mediaStream } = useAppStream()
|
const { mediaStream } = useAppStream()
|
||||||
const { overallState, immediateState } = useNetworkContext()
|
const { overallState, immediateState } = useNetworkContext()
|
||||||
const [streamState, setStreamState] = useState(StreamState.Unset)
|
const [streamState, setStreamState] = useState(StreamState.Unset)
|
||||||
@ -301,7 +302,7 @@ export const Stream = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const path = getArtifactOfTypes(
|
const path = getArtifactOfTypes(
|
||||||
{ key: entity_id, types: ['path', 'solid2D', 'segment'] },
|
{ key: entity_id, types: ['path', 'solid2d', 'segment'] },
|
||||||
engineCommandManager.artifactGraph
|
engineCommandManager.artifactGraph
|
||||||
)
|
)
|
||||||
if (err(path)) {
|
if (err(path)) {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import { base64Decode } from 'lang/wasm'
|
|||||||
import { sendTelemetry } from 'lib/textToCad'
|
import { sendTelemetry } from 'lib/textToCad'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { ActionButton } from './ActionButton'
|
import { ActionButton } from './ActionButton'
|
||||||
import { commandBarMachine } from 'machines/commandBarMachine'
|
import { commandBarActor, commandBarMachine } from 'machines/commandBarMachine'
|
||||||
import { EventFrom } from 'xstate'
|
import { EventFrom } from 'xstate'
|
||||||
import { fileMachine } from 'machines/fileMachine'
|
import { fileMachine } from 'machines/fileMachine'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
@ -43,15 +43,10 @@ export function ToastTextToCadError({
|
|||||||
toastId,
|
toastId,
|
||||||
message,
|
message,
|
||||||
prompt,
|
prompt,
|
||||||
commandBarSend,
|
|
||||||
}: {
|
}: {
|
||||||
toastId: string
|
toastId: string
|
||||||
message: string
|
message: string
|
||||||
prompt: string
|
prompt: string
|
||||||
commandBarSend: (
|
|
||||||
event: EventFrom<typeof commandBarMachine>,
|
|
||||||
data?: unknown
|
|
||||||
) => void
|
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col justify-between gap-6">
|
<div className="flex flex-col justify-between gap-6">
|
||||||
@ -81,7 +76,7 @@ export function ToastTextToCadError({
|
|||||||
}}
|
}}
|
||||||
name="Edit prompt"
|
name="Edit prompt"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Find and select command',
|
type: 'Find and select command',
|
||||||
data: {
|
data: {
|
||||||
groupId: 'modeling',
|
groupId: 'modeling',
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { toolTips } from 'lang/langHelpers'
|
import { toolTips } from 'lang/langHelpers'
|
||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import { PathToNode, Program, Expr } from '../../lang/wasm'
|
import { PathToNode, Program, Expr, topLevelRange } from '../../lang/wasm'
|
||||||
import { getNodeFromPath } from '../../lang/queryAst'
|
import { getNodeFromPath } from '../../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
PathToNodeMap,
|
PathToNodeMap,
|
||||||
@ -41,7 +41,7 @@ export function removeConstrainingValuesInfo({
|
|||||||
graphSelections: nodes.map(
|
graphSelections: nodes.map(
|
||||||
(node): Selection => ({
|
(node): Selection => ({
|
||||||
codeRef: codeRefFromRange(
|
codeRef: codeRefFromRange(
|
||||||
[node.start, node.end, true],
|
topLevelRange(node.start, node.end),
|
||||||
kclManager.ast
|
kclManager.ast
|
||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import {
|
|||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
|
||||||
import { CommandBarProvider } from './CommandBar/CommandBarProvider'
|
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
@ -124,9 +123,7 @@ function TestWrap({ children }: { children: React.ReactNode }) {
|
|||||||
<Route
|
<Route
|
||||||
path="/file/:id"
|
path="/file/:id"
|
||||||
element={
|
element={
|
||||||
<CommandBarProvider>
|
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
||||||
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
|
|
||||||
</CommandBarProvider>
|
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { engineCommandManager, kclManager } from 'lib/singletons'
|
|||||||
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
|
import { modelingMachine, ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
|
import { Selections, Selection, processCodeMirrorRanges } from 'lib/selections'
|
||||||
import { undo, redo } from '@codemirror/commands'
|
import { undo, redo } from '@codemirror/commands'
|
||||||
import { CommandBarMachineEvent } from 'machines/commandBarMachine'
|
|
||||||
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
|
import { addLineHighlight, addLineHighlightEvent } from './highlightextension'
|
||||||
import {
|
import {
|
||||||
Diagnostic,
|
Diagnostic,
|
||||||
@ -52,9 +51,6 @@ export default class EditorManager {
|
|||||||
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
private _modelingSend: (eventInfo: ModelingMachineEvent) => void = () => {}
|
||||||
private _modelingState: StateFrom<typeof modelingMachine> | null = null
|
private _modelingState: StateFrom<typeof modelingMachine> | null = null
|
||||||
|
|
||||||
private _commandBarSend: (eventInfo: CommandBarMachineEvent) => void =
|
|
||||||
() => {}
|
|
||||||
|
|
||||||
private _convertToVariableEnabled: boolean = false
|
private _convertToVariableEnabled: boolean = false
|
||||||
private _convertToVariableCallback: () => void = () => {}
|
private _convertToVariableCallback: () => void = () => {}
|
||||||
|
|
||||||
@ -161,14 +157,6 @@ export default class EditorManager {
|
|||||||
this._modelingState = state
|
this._modelingState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
setCommandBarSend(send: (eventInfo: CommandBarMachineEvent) => void) {
|
|
||||||
this._commandBarSend = send
|
|
||||||
}
|
|
||||||
|
|
||||||
commandBarSend(eventInfo: CommandBarMachineEvent): void {
|
|
||||||
return this._commandBarSend(eventInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
get highlightRange(): Array<[number, number]> {
|
get highlightRange(): Array<[number, number]> {
|
||||||
return this._highlightRange
|
return this._highlightRange
|
||||||
}
|
}
|
||||||
@ -315,6 +303,21 @@ export default class EditorManager {
|
|||||||
if (selections?.graphSelections?.length === 0) {
|
if (selections?.graphSelections?.length === 0) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this._editorView) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const codeBaseSelections = this.createEditorSelection(selections)
|
||||||
|
this._editorView.dispatch({
|
||||||
|
selection: codeBaseSelections,
|
||||||
|
annotations: [
|
||||||
|
updateOutsideEditorEvent,
|
||||||
|
Transaction.addToHistory.of(false),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createEditorSelection(selections: Selections) {
|
||||||
let codeBasedSelections = []
|
let codeBasedSelections = []
|
||||||
for (const selection of selections.graphSelections) {
|
for (const selection of selections.graphSelections) {
|
||||||
const safeEnd = Math.min(
|
const safeEnd = Math.min(
|
||||||
@ -331,18 +334,7 @@ export default class EditorManager {
|
|||||||
.range[1]
|
.range[1]
|
||||||
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
|
const safeEnd = Math.min(end, this._editorView?.state.doc.length || end)
|
||||||
codeBasedSelections.push(EditorSelection.cursor(safeEnd))
|
codeBasedSelections.push(EditorSelection.cursor(safeEnd))
|
||||||
|
return EditorSelection.create(codeBasedSelections, 1)
|
||||||
if (!this._editorView) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this._editorView.dispatch({
|
|
||||||
selection: EditorSelection.create(codeBasedSelections, 1),
|
|
||||||
annotations: [
|
|
||||||
updateOutsideEditorEvent,
|
|
||||||
Transaction.addToHistory.of(false),
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will ONLY get here if the user called a select event.
|
// We will ONLY get here if the user called a select event.
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
|
|
||||||
|
|
||||||
export const useCommandsContext = () => {
|
|
||||||
const commandBarActor = CommandsContext.useActorRef()
|
|
||||||
const commandBarState = CommandsContext.useSelector((state) => state)
|
|
||||||
return {
|
|
||||||
commandBarSend: commandBarActor.send,
|
|
||||||
commandBarState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,7 +1,6 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
|
import { AnyStateMachine, Actor, StateFrom, EventFrom } from 'xstate'
|
||||||
import { createMachineCommand } from '../lib/createMachineCommand'
|
import { createMachineCommand } from '../lib/createMachineCommand'
|
||||||
import { useCommandsContext } from './useCommandsContext'
|
|
||||||
import { modelingMachine } from 'machines/modelingMachine'
|
import { modelingMachine } from 'machines/modelingMachine'
|
||||||
import { authMachine } from 'machines/authMachine'
|
import { authMachine } from 'machines/authMachine'
|
||||||
import { settingsMachine } from 'machines/settingsMachine'
|
import { settingsMachine } from 'machines/settingsMachine'
|
||||||
@ -15,6 +14,7 @@ import { useKclContext } from 'lang/KclProvider'
|
|||||||
import { useNetworkContext } from 'hooks/useNetworkContext'
|
import { useNetworkContext } from 'hooks/useNetworkContext'
|
||||||
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
import { NetworkHealthState } from 'hooks/useNetworkStatus'
|
||||||
import { useAppState } from 'AppState'
|
import { useAppState } from 'AppState'
|
||||||
|
import { commandBarActor } from 'machines/commandBarMachine'
|
||||||
|
|
||||||
// This might not be necessary, AnyStateMachine from xstate is working
|
// This might not be necessary, AnyStateMachine from xstate is working
|
||||||
export type AllMachines =
|
export type AllMachines =
|
||||||
@ -48,7 +48,6 @@ export default function useStateMachineCommands<
|
|||||||
allCommandsRequireNetwork = false,
|
allCommandsRequireNetwork = false,
|
||||||
onCancel,
|
onCancel,
|
||||||
}: UseStateMachineCommandsArgs<T, S>) {
|
}: UseStateMachineCommandsArgs<T, S>) {
|
||||||
const { commandBarSend } = useCommandsContext()
|
|
||||||
const { overallState } = useNetworkContext()
|
const { overallState } = useNetworkContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { isStreamReady } = useAppState()
|
const { isStreamReady } = useAppState()
|
||||||
@ -76,10 +75,13 @@ export default function useStateMachineCommands<
|
|||||||
})
|
})
|
||||||
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
.filter((c) => c !== null) as Command[] // TS isn't smart enough to know this filter removes nulls
|
||||||
|
|
||||||
commandBarSend({ type: 'Add commands', data: { commands: newCommands } })
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: { commands: newCommands },
|
||||||
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
commandBarSend({
|
commandBarActor.send({
|
||||||
type: 'Remove commands',
|
type: 'Remove commands',
|
||||||
data: { commands: newCommands },
|
data: { commands: newCommands },
|
||||||
})
|
})
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
recast,
|
recast,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
|
||||||
@ -376,11 +377,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(
|
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
|
||||||
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' })
|
||||||
@ -473,7 +470,7 @@ export class KclManager {
|
|||||||
...artifact,
|
...artifact,
|
||||||
codeRef: {
|
codeRef: {
|
||||||
...artifact.codeRef,
|
...artifact.codeRef,
|
||||||
range: [node.start, node.end, true],
|
range: topLevelRange(node.start, node.end),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -594,7 +591,7 @@ export class KclManager {
|
|||||||
if (start && end) {
|
if (start && end) {
|
||||||
returnVal.graphSelections.push({
|
returnVal.graphSelections.push({
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [start, end, true],
|
range: topLevelRange(start, end),
|
||||||
pathToNode: path,
|
pathToNode: path,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -24,7 +24,10 @@ describe('testing AST', () => {
|
|||||||
type: 'Literal',
|
type: 'Literal',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 1,
|
end: 1,
|
||||||
value: 5,
|
value: {
|
||||||
|
suffix: 'None',
|
||||||
|
value: 5,
|
||||||
|
},
|
||||||
raw: '5',
|
raw: '5',
|
||||||
},
|
},
|
||||||
operator: '+',
|
operator: '+',
|
||||||
@ -32,7 +35,10 @@ describe('testing AST', () => {
|
|||||||
type: 'Literal',
|
type: 'Literal',
|
||||||
start: 3,
|
start: 3,
|
||||||
end: 4,
|
end: 4,
|
||||||
value: 6,
|
value: {
|
||||||
|
suffix: 'None',
|
||||||
|
value: 6,
|
||||||
|
},
|
||||||
raw: '6',
|
raw: '6',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -54,6 +54,9 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
__meta: [{ sourceRange: [46, 71, 0] }],
|
__meta: [{ sourceRange: [46, 71, 0] }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -72,56 +75,65 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
const sketch001 = execState.memory.get('mySketch001')
|
const sketch001 = execState.memory.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'Solid',
|
type: 'Solid',
|
||||||
id: expect.any(String),
|
value: {
|
||||||
value: [
|
type: 'Solid',
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [77, 102, 0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [108, 132, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sketch: {
|
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: expect.any(Array),
|
value: [
|
||||||
on: expect.any(Object),
|
|
||||||
start: expect.any(Object),
|
|
||||||
type: 'Sketch',
|
|
||||||
paths: [
|
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [0, 0],
|
faceId: expect.any(String),
|
||||||
to: [-1.59, -1.54],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [77, 102, 0],
|
||||||
sourceRange: [77, 102, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [-1.59, -1.54],
|
faceId: expect.any(String),
|
||||||
to: [0.46, -5.82],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [108, 132, 0],
|
||||||
sourceRange: [108, 132, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sketch: {
|
||||||
|
id: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
|
__meta: expect.any(Array),
|
||||||
|
on: expect.any(Object),
|
||||||
|
start: expect.any(Object),
|
||||||
|
type: 'Sketch',
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-1.59, -1.54],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [77, 102, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-1.59, -1.54],
|
||||||
|
to: [0.46, -5.82],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [108, 132, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
height: 2,
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
|
__meta: [{ sourceRange: [46, 71, 0] }],
|
||||||
},
|
},
|
||||||
height: 2,
|
|
||||||
startCapId: expect.any(String),
|
|
||||||
endCapId: expect.any(String),
|
|
||||||
__meta: [{ sourceRange: [46, 71, 0] }],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('sketch extrude and sketch on one of the faces', async () => {
|
test('sketch extrude and sketch on one of the faces', async () => {
|
||||||
@ -154,187 +166,205 @@ const sk2 = startSketchOn('XY')
|
|||||||
expect(geos).toEqual([
|
expect(geos).toEqual([
|
||||||
{
|
{
|
||||||
type: 'Solid',
|
type: 'Solid',
|
||||||
id: expect.any(String),
|
value: {
|
||||||
value: [
|
type: 'Solid',
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [69, 89, 0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: {
|
|
||||||
end: 116,
|
|
||||||
start: 114,
|
|
||||||
type: 'TagDeclarator',
|
|
||||||
value: 'p',
|
|
||||||
},
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [95, 117, 0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [123, 142, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sketch: {
|
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: expect.any(Array),
|
value: [
|
||||||
on: expect.any(Object),
|
|
||||||
start: expect.any(Object),
|
|
||||||
type: 'Sketch',
|
|
||||||
tags: {
|
|
||||||
p: {
|
|
||||||
__meta: [
|
|
||||||
{
|
|
||||||
sourceRange: [114, 116, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: 'TagIdentifier',
|
|
||||||
value: 'p',
|
|
||||||
info: expect.any(Object),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
paths: [
|
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [0, 0],
|
faceId: expect.any(String),
|
||||||
to: [-2.5, 0],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [69, 89, 0],
|
||||||
sourceRange: [69, 89, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [-2.5, 0],
|
faceId: expect.any(String),
|
||||||
to: [0, 10],
|
|
||||||
tag: {
|
tag: {
|
||||||
end: 116,
|
end: 116,
|
||||||
start: 114,
|
start: 114,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'p',
|
value: 'p',
|
||||||
},
|
},
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [95, 117, 0],
|
||||||
sourceRange: [95, 117, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [0, 10],
|
faceId: expect.any(String),
|
||||||
to: [2.5, 0],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [123, 142, 0],
|
||||||
sourceRange: [123, 142, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sketch: {
|
||||||
|
id: expect.any(String),
|
||||||
|
__meta: expect.any(Array),
|
||||||
|
on: expect.any(Object),
|
||||||
|
start: expect.any(Object),
|
||||||
|
type: 'Sketch',
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
p: {
|
||||||
|
__meta: [
|
||||||
|
{
|
||||||
|
sourceRange: [114, 116, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'TagIdentifier',
|
||||||
|
value: 'p',
|
||||||
|
info: expect.any(Object),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-2.5, 0],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [69, 89, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-2.5, 0],
|
||||||
|
to: [0, 10],
|
||||||
|
tag: {
|
||||||
|
end: 116,
|
||||||
|
start: 114,
|
||||||
|
type: 'TagDeclarator',
|
||||||
|
value: 'p',
|
||||||
|
},
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [95, 117, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 10],
|
||||||
|
to: [2.5, 0],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [123, 142, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
height: 2,
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
|
__meta: [{ sourceRange: [38, 63, 0] }],
|
||||||
},
|
},
|
||||||
height: 2,
|
|
||||||
startCapId: expect.any(String),
|
|
||||||
endCapId: expect.any(String),
|
|
||||||
__meta: [{ sourceRange: [38, 63, 0] }],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'Solid',
|
type: 'Solid',
|
||||||
id: expect.any(String),
|
value: {
|
||||||
value: [
|
type: 'Solid',
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [373, 393, 0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: {
|
|
||||||
end: 419,
|
|
||||||
start: 417,
|
|
||||||
type: 'TagDeclarator',
|
|
||||||
value: 'o',
|
|
||||||
},
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [399, 420, 0],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'extrudePlane',
|
|
||||||
faceId: expect.any(String),
|
|
||||||
tag: null,
|
|
||||||
id: expect.any(String),
|
|
||||||
sourceRange: [426, 445, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
sketch: {
|
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: expect.any(Array),
|
value: [
|
||||||
on: expect.any(Object),
|
|
||||||
start: expect.any(Object),
|
|
||||||
type: 'Sketch',
|
|
||||||
tags: {
|
|
||||||
o: {
|
|
||||||
__meta: [
|
|
||||||
{
|
|
||||||
sourceRange: [417, 419, 0],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
type: 'TagIdentifier',
|
|
||||||
value: 'o',
|
|
||||||
info: expect.any(Object),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
paths: [
|
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [0, 0],
|
faceId: expect.any(String),
|
||||||
to: [-2.5, 0],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [373, 393, 0],
|
||||||
sourceRange: [373, 393, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [-2.5, 0],
|
faceId: expect.any(String),
|
||||||
to: [0, 3],
|
|
||||||
tag: {
|
tag: {
|
||||||
end: 419,
|
end: 419,
|
||||||
start: 417,
|
start: 417,
|
||||||
type: 'TagDeclarator',
|
type: 'TagDeclarator',
|
||||||
value: 'o',
|
value: 'o',
|
||||||
},
|
},
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [399, 420, 0],
|
||||||
sourceRange: [399, 420, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'extrudePlane',
|
||||||
from: [0, 3],
|
faceId: expect.any(String),
|
||||||
to: [2.5, 0],
|
|
||||||
tag: null,
|
tag: null,
|
||||||
__geoMeta: {
|
id: expect.any(String),
|
||||||
id: expect.any(String),
|
sourceRange: [426, 445, 0],
|
||||||
sourceRange: [426, 445, 0],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
sketch: {
|
||||||
|
id: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
|
__meta: expect.any(Array),
|
||||||
|
on: expect.any(Object),
|
||||||
|
start: expect.any(Object),
|
||||||
|
type: 'Sketch',
|
||||||
|
tags: {
|
||||||
|
o: {
|
||||||
|
__meta: [
|
||||||
|
{
|
||||||
|
sourceRange: [417, 419, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'TagIdentifier',
|
||||||
|
value: 'o',
|
||||||
|
info: expect.any(Object),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
paths: [
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 0],
|
||||||
|
to: [-2.5, 0],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [373, 393, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [-2.5, 0],
|
||||||
|
to: [0, 3],
|
||||||
|
tag: {
|
||||||
|
end: 419,
|
||||||
|
start: 417,
|
||||||
|
type: 'TagDeclarator',
|
||||||
|
value: 'o',
|
||||||
|
},
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [399, 420, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ToPoint',
|
||||||
|
from: [0, 3],
|
||||||
|
to: [2.5, 0],
|
||||||
|
tag: null,
|
||||||
|
__geoMeta: {
|
||||||
|
id: expect.any(String),
|
||||||
|
sourceRange: [426, 445, 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
height: 2,
|
||||||
|
startCapId: expect.any(String),
|
||||||
|
endCapId: expect.any(String),
|
||||||
|
__meta: [{ sourceRange: [342, 367, 0] }],
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
height: 2,
|
|
||||||
startCapId: expect.any(String),
|
|
||||||
endCapId: expect.any(String),
|
|
||||||
__meta: [{ sourceRange: [342, 367, 0] }],
|
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { kclErrorsToDiagnostics, KCLError } from './errors'
|
import { kclErrorsToDiagnostics, KCLError } from './errors'
|
||||||
|
import { defaultArtifactGraph, topLevelRange } from 'lang/wasm'
|
||||||
|
|
||||||
describe('test kclErrToDiagnostic', () => {
|
describe('test kclErrToDiagnostic', () => {
|
||||||
it('converts KCL errors to CodeMirror diagnostics', () => {
|
it('converts KCL errors to CodeMirror diagnostics', () => {
|
||||||
@ -8,18 +9,20 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
message: '',
|
message: '',
|
||||||
kind: 'semantic',
|
kind: 'semantic',
|
||||||
msg: 'Semantic error',
|
msg: 'Semantic error',
|
||||||
sourceRange: [0, 1, true],
|
sourceRange: topLevelRange(0, 1),
|
||||||
operations: [],
|
operations: [],
|
||||||
artifactCommands: [],
|
artifactCommands: [],
|
||||||
|
artifactGraph: defaultArtifactGraph(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
message: '',
|
message: '',
|
||||||
kind: 'type',
|
kind: 'type',
|
||||||
msg: 'Type error',
|
msg: 'Type error',
|
||||||
sourceRange: [4, 5, true],
|
sourceRange: topLevelRange(4, 5),
|
||||||
operations: [],
|
operations: [],
|
||||||
artifactCommands: [],
|
artifactCommands: [],
|
||||||
|
artifactGraph: defaultArtifactGraph(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
const diagnostics = kclErrorsToDiagnostics(errors)
|
||||||
|
|||||||
@ -5,7 +5,13 @@ 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 {
|
||||||
|
ArtifactCommand,
|
||||||
|
ArtifactGraph,
|
||||||
|
defaultArtifactGraph,
|
||||||
|
isTopLevelModule,
|
||||||
|
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
|
||||||
@ -15,13 +21,15 @@ export class KCLError extends Error {
|
|||||||
msg: string
|
msg: string
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[]
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kind: ExtractKind<RustKclError> | 'name',
|
kind: ExtractKind<RustKclError> | 'name',
|
||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.kind = kind
|
this.kind = kind
|
||||||
@ -29,6 +37,7 @@ export class KCLError extends Error {
|
|||||||
this.sourceRange = sourceRange
|
this.sourceRange = sourceRange
|
||||||
this.operations = operations
|
this.operations = operations
|
||||||
this.artifactCommands = artifactCommands
|
this.artifactCommands = artifactCommands
|
||||||
|
this.artifactGraph = artifactGraph
|
||||||
Object.setPrototypeOf(this, KCLError.prototype)
|
Object.setPrototypeOf(this, KCLError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -38,9 +47,17 @@ export class KCLLexicalError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('lexical', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'lexical',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,9 +67,17 @@ export class KCLInternalError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('internal', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'internal',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,9 +87,17 @@ export class KCLSyntaxError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('syntax', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'syntax',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,9 +107,17 @@ export class KCLSemanticError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('semantic', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'semantic',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,9 +127,10 @@ export class KCLTypeError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('type', msg, sourceRange, operations, artifactCommands)
|
super('type', msg, sourceRange, operations, artifactCommands, artifactGraph)
|
||||||
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -98,9 +140,17 @@ export class KCLUnimplementedError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('unimplemented', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'unimplemented',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,9 +160,17 @@ export class KCLUnexpectedError extends KCLError {
|
|||||||
msg: string,
|
msg: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super('unexpected', msg, sourceRange, operations, artifactCommands)
|
super(
|
||||||
|
'unexpected',
|
||||||
|
msg,
|
||||||
|
sourceRange,
|
||||||
|
operations,
|
||||||
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
|
)
|
||||||
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,14 +180,16 @@ export class KCLValueAlreadyDefined extends KCLError {
|
|||||||
key: string,
|
key: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
'name',
|
'name',
|
||||||
`Key ${key} was already defined elsewhere`,
|
`Key ${key} was already defined elsewhere`,
|
||||||
sourceRange,
|
sourceRange,
|
||||||
operations,
|
operations,
|
||||||
artifactCommands
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
)
|
)
|
||||||
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||||
}
|
}
|
||||||
@ -140,14 +200,16 @@ export class KCLUndefinedValueError extends KCLError {
|
|||||||
key: string,
|
key: string,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
operations: Operation[],
|
operations: Operation[],
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[],
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
'name',
|
'name',
|
||||||
`Key ${key} has not been defined`,
|
`Key ${key} has not been defined`,
|
||||||
sourceRange,
|
sourceRange,
|
||||||
operations,
|
operations,
|
||||||
artifactCommands
|
artifactCommands,
|
||||||
|
artifactGraph
|
||||||
)
|
)
|
||||||
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||||
}
|
}
|
||||||
@ -167,9 +229,10 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
new KCLError(
|
new KCLError(
|
||||||
'unexpected',
|
'unexpected',
|
||||||
message,
|
message,
|
||||||
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, true],
|
[posToOffset(doc, range.start)!, posToOffset(doc, range.end)!, 0],
|
||||||
[],
|
[],
|
||||||
[]
|
[],
|
||||||
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
@ -193,7 +256,7 @@ export function kclErrorsToDiagnostics(
|
|||||||
errors: KCLError[]
|
errors: KCLError[]
|
||||||
): CodeMirrorDiagnostic[] {
|
): CodeMirrorDiagnostic[] {
|
||||||
return errors
|
return errors
|
||||||
?.filter((err) => err.sourceRange[2])
|
?.filter((err) => isTopLevelModule(err.sourceRange))
|
||||||
.map((err) => {
|
.map((err) => {
|
||||||
return {
|
return {
|
||||||
from: err.sourceRange[0],
|
from: err.sourceRange[0],
|
||||||
@ -208,7 +271,7 @@ export function complilationErrorsToDiagnostics(
|
|||||||
errors: CompilationError[]
|
errors: CompilationError[]
|
||||||
): CodeMirrorDiagnostic[] {
|
): CodeMirrorDiagnostic[] {
|
||||||
return errors
|
return errors
|
||||||
?.filter((err) => err.sourceRange[2] === 0)
|
?.filter((err) => isTopLevelModule(err.sourceRange))
|
||||||
.map((err) => {
|
.map((err) => {
|
||||||
let severity: any = 'error'
|
let severity: any = 'error'
|
||||||
if (err.severity === 'Warning') {
|
if (err.severity === 'Warning') {
|
||||||
|
|||||||
@ -6,6 +6,8 @@ import {
|
|||||||
Sketch,
|
Sketch,
|
||||||
initPromise,
|
initPromise,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
|
defaultArtifactGraph,
|
||||||
|
topLevelRange,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import { KCLError } from './errors'
|
import { KCLError } from './errors'
|
||||||
@ -219,6 +221,9 @@ const newVar = myVar + 1`
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
|
units: {
|
||||||
|
type: 'Mm',
|
||||||
|
},
|
||||||
__meta: [{ sourceRange: [39, 63, 0] }],
|
__meta: [{ sourceRange: [39, 63, 0] }],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -480,9 +485,10 @@ const theExtrude = startSketchOn('XY')
|
|||||||
new KCLError(
|
new KCLError(
|
||||||
'undefined_value',
|
'undefined_value',
|
||||||
'memory item key `myVarZ` is not defined',
|
'memory item key `myVarZ` is not defined',
|
||||||
[129, 135, true],
|
topLevelRange(129, 135),
|
||||||
[],
|
[],
|
||||||
[]
|
[],
|
||||||
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -1,5 +1,12 @@
|
|||||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||||
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
|
import {
|
||||||
|
Identifier,
|
||||||
|
assertParse,
|
||||||
|
initPromise,
|
||||||
|
Parameter,
|
||||||
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
|
} from './wasm'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -17,11 +24,10 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
`
|
`
|
||||||
const subStr = 'lineTo([3, 4], %, $yo)'
|
const subStr = 'lineTo([3, 4], %, $yo)'
|
||||||
const lineToSubstringIndex = code.indexOf(subStr)
|
const lineToSubstringIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number, boolean] = [
|
const sourceRange = topLevelRange(
|
||||||
lineToSubstringIndex,
|
lineToSubstringIndex,
|
||||||
lineToSubstringIndex + subStr.length,
|
lineToSubstringIndex + subStr.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
|
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
@ -29,7 +35,7 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
if (err(_node)) throw _node
|
if (err(_node)) throw _node
|
||||||
const { node } = _node
|
const { node } = _node
|
||||||
|
|
||||||
expect([node.start, node.end, true]).toEqual(sourceRange)
|
expect(topLevelRange(node.start, node.end)).toEqual(sourceRange)
|
||||||
expect(node.type).toBe('CallExpression')
|
expect(node.type).toBe('CallExpression')
|
||||||
})
|
})
|
||||||
it('gets path right for function definition params', () => {
|
it('gets path right for function definition params', () => {
|
||||||
@ -45,11 +51,7 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
const b1 = cube([0,0], 10)`
|
const b1 = cube([0,0], 10)`
|
||||||
const subStr = 'pos, scale'
|
const subStr = 'pos, scale'
|
||||||
const subStrIndex = code.indexOf(subStr)
|
const subStrIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number, boolean] = [
|
const sourceRange = topLevelRange(subStrIndex, subStrIndex + 'pos'.length)
|
||||||
subStrIndex,
|
|
||||||
subStrIndex + 'pos'.length,
|
|
||||||
true,
|
|
||||||
]
|
|
||||||
|
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
@ -81,11 +83,7 @@ const b1 = cube([0,0], 10)`
|
|||||||
const b1 = cube([0,0], 10)`
|
const b1 = cube([0,0], 10)`
|
||||||
const subStr = 'scale, 0'
|
const subStr = 'scale, 0'
|
||||||
const subStrIndex = code.indexOf(subStr)
|
const subStrIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number, boolean] = [
|
const sourceRange = topLevelRange(subStrIndex, subStrIndex + 'scale'.length)
|
||||||
subStrIndex,
|
|
||||||
subStrIndex + 'scale'.length,
|
|
||||||
true,
|
|
||||||
]
|
|
||||||
|
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { assertParse, recast, initPromise, Identifier } from './wasm'
|
import {
|
||||||
|
assertParse,
|
||||||
|
recast,
|
||||||
|
initPromise,
|
||||||
|
Identifier,
|
||||||
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
createLiteral,
|
createLiteral,
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
@ -32,7 +39,7 @@ describe('Testing createLiteral', () => {
|
|||||||
it('should create a literal', () => {
|
it('should create a literal', () => {
|
||||||
const result = createLiteral(5)
|
const result = createLiteral(5)
|
||||||
expect(result.type).toBe('Literal')
|
expect(result.type).toBe('Literal')
|
||||||
expect(result.value).toBe(5)
|
expect((result as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Testing createIdentifier', () => {
|
describe('Testing createIdentifier', () => {
|
||||||
@ -49,7 +56,7 @@ describe('Testing createCallExpression', () => {
|
|||||||
expect(result.callee.type).toBe('Identifier')
|
expect(result.callee.type).toBe('Identifier')
|
||||||
expect(result.callee.name).toBe('myFunc')
|
expect(result.callee.name).toBe('myFunc')
|
||||||
expect(result.arguments[0].type).toBe('Literal')
|
expect(result.arguments[0].type).toBe('Literal')
|
||||||
expect((result.arguments[0] as any).value).toBe(5)
|
expect((result.arguments[0] as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Testing createObjectExpression', () => {
|
describe('Testing createObjectExpression', () => {
|
||||||
@ -61,7 +68,7 @@ describe('Testing createObjectExpression', () => {
|
|||||||
expect(result.properties[0].type).toBe('ObjectProperty')
|
expect(result.properties[0].type).toBe('ObjectProperty')
|
||||||
expect(result.properties[0].key.name).toBe('myProp')
|
expect(result.properties[0].key.name).toBe('myProp')
|
||||||
expect(result.properties[0].value.type).toBe('Literal')
|
expect(result.properties[0].value.type).toBe('Literal')
|
||||||
expect((result.properties[0].value as any).value).toBe(5)
|
expect((result.properties[0].value as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Testing createArrayExpression', () => {
|
describe('Testing createArrayExpression', () => {
|
||||||
@ -69,7 +76,7 @@ describe('Testing createArrayExpression', () => {
|
|||||||
const result = createArrayExpression([createLiteral(5)])
|
const result = createArrayExpression([createLiteral(5)])
|
||||||
expect(result.type).toBe('ArrayExpression')
|
expect(result.type).toBe('ArrayExpression')
|
||||||
expect(result.elements[0].type).toBe('Literal')
|
expect(result.elements[0].type).toBe('Literal')
|
||||||
expect((result.elements[0] as any).value).toBe(5)
|
expect((result.elements[0] as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Testing createPipeSubstitution', () => {
|
describe('Testing createPipeSubstitution', () => {
|
||||||
@ -86,7 +93,7 @@ describe('Testing createVariableDeclaration', () => {
|
|||||||
expect(result.declaration.id.type).toBe('Identifier')
|
expect(result.declaration.id.type).toBe('Identifier')
|
||||||
expect(result.declaration.id.name).toBe('myVar')
|
expect(result.declaration.id.name).toBe('myVar')
|
||||||
expect(result.declaration.init.type).toBe('Literal')
|
expect(result.declaration.init.type).toBe('Literal')
|
||||||
expect((result.declaration.init as any).value).toBe(5)
|
expect((result.declaration.init as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
describe('Testing createPipeExpression', () => {
|
describe('Testing createPipeExpression', () => {
|
||||||
@ -94,7 +101,7 @@ describe('Testing createPipeExpression', () => {
|
|||||||
const result = createPipeExpression([createLiteral(5)])
|
const result = createPipeExpression([createLiteral(5)])
|
||||||
expect(result.type).toBe('PipeExpression')
|
expect(result.type).toBe('PipeExpression')
|
||||||
expect(result.body[0].type).toBe('Literal')
|
expect(result.body[0].type).toBe('Literal')
|
||||||
expect((result.body[0] as any).value).toBe(5)
|
expect((result.body[0] as any).value.value).toBe(5)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -148,11 +155,7 @@ function giveSketchFnCallTagTestHelper(
|
|||||||
// making it more of an integration test, but easier to read the test intention is the goal
|
// making it more of an integration test, but easier to read the test intention is the goal
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const start = code.indexOf(searchStr)
|
const start = code.indexOf(searchStr)
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(start, start + searchStr.length)
|
||||||
start,
|
|
||||||
start + searchStr.length,
|
|
||||||
true,
|
|
||||||
]
|
|
||||||
const sketchRes = giveSketchFnCallTag(ast, range)
|
const sketchRes = giveSketchFnCallTag(ast, range)
|
||||||
if (err(sketchRes)) throw sketchRes
|
if (err(sketchRes)) throw sketchRes
|
||||||
const { modifiedAst, tag, isTagExisting } = sketchRes
|
const { modifiedAst, tag, isTagExisting } = sketchRes
|
||||||
@ -230,7 +233,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[startIndex, startIndex, true],
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
@ -244,7 +247,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[startIndex, startIndex, true],
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
@ -258,7 +261,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[startIndex, startIndex, true],
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
@ -272,7 +275,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[startIndex, startIndex, true],
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
@ -286,7 +289,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const { modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[startIndex, startIndex, true],
|
topLevelRange(startIndex, startIndex),
|
||||||
'newVar'
|
'newVar'
|
||||||
)
|
)
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
@ -306,18 +309,16 @@ describe('testing sketchOnExtrudedFace', () => {
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const segmentSnippet = `line([9.7, 9.19], %)`
|
const segmentSnippet = `line([9.7, 9.19], %)`
|
||||||
const segmentRange: [number, number, boolean] = [
|
const segmentRange = topLevelRange(
|
||||||
code.indexOf(segmentSnippet),
|
code.indexOf(segmentSnippet),
|
||||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||||
const extrudeRange: [number, number, boolean] = [
|
const extrudeRange = topLevelRange(
|
||||||
code.indexOf(extrudeSnippet),
|
code.indexOf(extrudeSnippet),
|
||||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
|
|
||||||
const extruded = sketchOnExtrudedFace(
|
const extruded = sketchOnExtrudedFace(
|
||||||
@ -346,18 +347,16 @@ sketch001 = startSketchOn(part001, seg01)`)
|
|||||||
|> extrude(5 + 7, %)`
|
|> extrude(5 + 7, %)`
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const segmentSnippet = `close(%)`
|
const segmentSnippet = `close(%)`
|
||||||
const segmentRange: [number, number, boolean] = [
|
const segmentRange = topLevelRange(
|
||||||
code.indexOf(segmentSnippet),
|
code.indexOf(segmentSnippet),
|
||||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||||
const extrudeRange: [number, number, boolean] = [
|
const extrudeRange = topLevelRange(
|
||||||
code.indexOf(extrudeSnippet),
|
code.indexOf(extrudeSnippet),
|
||||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
|
|
||||||
const extruded = sketchOnExtrudedFace(
|
const extruded = sketchOnExtrudedFace(
|
||||||
@ -386,18 +385,16 @@ sketch001 = startSketchOn(part001, seg01)`)
|
|||||||
|> extrude(5 + 7, %)`
|
|> extrude(5 + 7, %)`
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
const sketchSnippet = `startProfileAt([3.58, 2.06], %)`
|
||||||
const sketchRange: [number, number, boolean] = [
|
const sketchRange = topLevelRange(
|
||||||
code.indexOf(sketchSnippet),
|
code.indexOf(sketchSnippet),
|
||||||
code.indexOf(sketchSnippet) + sketchSnippet.length,
|
code.indexOf(sketchSnippet) + sketchSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
|
||||||
const extrudeSnippet = `extrude(5 + 7, %)`
|
const extrudeSnippet = `extrude(5 + 7, %)`
|
||||||
const extrudeRange: [number, number, boolean] = [
|
const extrudeRange = topLevelRange(
|
||||||
code.indexOf(extrudeSnippet),
|
code.indexOf(extrudeSnippet),
|
||||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
|
|
||||||
const extruded = sketchOnExtrudedFace(
|
const extruded = sketchOnExtrudedFace(
|
||||||
@ -435,18 +432,16 @@ sketch001 = startSketchOn(part001, 'END')`)
|
|||||||
part001 = extrude(5 + 7, sketch001)`
|
part001 = extrude(5 + 7, sketch001)`
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const segmentSnippet = `line([4.99, -0.46], %)`
|
const segmentSnippet = `line([4.99, -0.46], %)`
|
||||||
const segmentRange: [number, number, boolean] = [
|
const segmentRange = topLevelRange(
|
||||||
code.indexOf(segmentSnippet),
|
code.indexOf(segmentSnippet),
|
||||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
const segmentPathToNode = getNodePathFromSourceRange(ast, segmentRange)
|
||||||
const extrudeSnippet = `extrude(5 + 7, sketch001)`
|
const extrudeSnippet = `extrude(5 + 7, sketch001)`
|
||||||
const extrudeRange: [number, number, boolean] = [
|
const extrudeRange = topLevelRange(
|
||||||
code.indexOf(extrudeSnippet),
|
code.indexOf(extrudeSnippet),
|
||||||
code.indexOf(extrudeSnippet) + extrudeSnippet.length,
|
code.indexOf(extrudeSnippet) + extrudeSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
const extrudePathToNode = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
|
|
||||||
const updatedAst = sketchOnExtrudedFace(
|
const updatedAst = sketchOnExtrudedFace(
|
||||||
@ -471,11 +466,10 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
|
const lineOfInterest = 'line([306.21, 198.85], %, $a)'
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest),
|
code.indexOf(lineOfInterest),
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||||
[],
|
[],
|
||||||
@ -549,11 +543,10 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
const lineOfInterest = line
|
const lineOfInterest = line
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest),
|
code.indexOf(lineOfInterest),
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
|
const dependentSegments = findUsesOfTagInPipe(ast, pathToNode)
|
||||||
const modifiedAst = deleteSegmentFromPipeExpression(
|
const modifiedAst = deleteSegmentFromPipeExpression(
|
||||||
@ -638,11 +631,10 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
|
|
||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest) + 1,
|
code.indexOf(lineOfInterest) + 1,
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
let argPosition: SimplifiedArgDetails
|
let argPosition: SimplifiedArgDetails
|
||||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
if (key === 'arrayIndex' && typeof value === 'number') {
|
||||||
@ -692,11 +684,10 @@ describe('Testing removeSingleConstraintInfo', () => {
|
|||||||
|
|
||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
const lineOfInterest = expectedFinish.split('(')[0] + '('
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest) + 1,
|
code.indexOf(lineOfInterest) + 1,
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
let argPosition: SimplifiedArgDetails
|
let argPosition: SimplifiedArgDetails
|
||||||
if (key === 'arrayIndex' && typeof value === 'number') {
|
if (key === 'arrayIndex' && typeof value === 'number') {
|
||||||
argPosition = {
|
argPosition = {
|
||||||
@ -889,11 +880,10 @@ sketch002 = startSketchOn({
|
|||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
|
|
||||||
// deleteFromSelection
|
// deleteFromSelection
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
codeBefore.indexOf(lineOfInterest),
|
codeBefore.indexOf(lineOfInterest),
|
||||||
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length,
|
codeBefore.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const artifact = { type } as Artifact
|
const artifact = { type } as Artifact
|
||||||
const newAst = await deleteFromSelection(
|
const newAst = await deleteFromSelection(
|
||||||
ast,
|
ast,
|
||||||
|
|||||||
@ -743,14 +743,18 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
|
|||||||
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLiteral(value: LiteralValue): Node<Literal> {
|
export function createLiteral(value: LiteralValue | number): Node<Literal> {
|
||||||
|
const raw = `${value}`
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
value = { value, suffix: 'None' }
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
type: 'Literal',
|
type: 'Literal',
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 0,
|
end: 0,
|
||||||
moduleId: 0,
|
moduleId: 0,
|
||||||
value,
|
value,
|
||||||
raw: `${value}`,
|
raw,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,8 @@ import {
|
|||||||
makeDefaultPlanes,
|
makeDefaultPlanes,
|
||||||
PipeExpression,
|
PipeExpression,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
EdgeTreatmentType,
|
EdgeTreatmentType,
|
||||||
@ -77,11 +79,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
code: string,
|
code: string,
|
||||||
expectedExtrudeSnippet: string
|
expectedExtrudeSnippet: string
|
||||||
): CallExpression | PipeExpression | Error {
|
): CallExpression | PipeExpression | Error {
|
||||||
const extrudeRange: [number, number, boolean] = [
|
const extrudeRange = topLevelRange(
|
||||||
code.indexOf(expectedExtrudeSnippet),
|
code.indexOf(expectedExtrudeSnippet),
|
||||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
const expectedExtrudeNodeResult = getNodeFromPath<
|
const expectedExtrudeNodeResult = getNodeFromPath<
|
||||||
VariableDeclarator | CallExpression
|
VariableDeclarator | CallExpression
|
||||||
@ -112,11 +113,10 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
const segmentRange: [number, number, boolean] = [
|
const segmentRange = topLevelRange(
|
||||||
code.indexOf(selectedSegmentSnippet),
|
code.indexOf(selectedSegmentSnippet),
|
||||||
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length,
|
code.indexOf(selectedSegmentSnippet) + selectedSegmentSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const selection: Selection = {
|
const selection: Selection = {
|
||||||
codeRef: codeRefFromRange(segmentRange, ast),
|
codeRef: codeRefFromRange(segmentRange, ast),
|
||||||
}
|
}
|
||||||
@ -260,12 +260,12 @@ const runModifyAstCloneWithEdgeTreatmentAndTag = async (
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
// selection
|
// selection
|
||||||
const segmentRanges: Array<[number, number, boolean]> = selectionSnippets.map(
|
const segmentRanges: Array<SourceRange> = selectionSnippets.map(
|
||||||
(selectionSnippet) => [
|
(selectionSnippet) =>
|
||||||
code.indexOf(selectionSnippet),
|
topLevelRange(
|
||||||
code.indexOf(selectionSnippet) + selectionSnippet.length,
|
code.indexOf(selectionSnippet),
|
||||||
true,
|
code.indexOf(selectionSnippet) + selectionSnippet.length
|
||||||
]
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// executeAst
|
// executeAst
|
||||||
@ -596,11 +596,10 @@ extrude001 = extrude(-5, sketch001)
|
|||||||
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
it('should correctly identify getOppositeEdge and baseEdge edges', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
const lineOfInterest = `line([7.11, 3.48], %, $seg01)`
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest),
|
code.indexOf(lineOfInterest),
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
if (err(pathToNode)) return
|
if (err(pathToNode)) return
|
||||||
const callExp = getNodeFromPath<CallExpression>(
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
@ -615,11 +614,10 @@ extrude001 = extrude(-5, sketch001)
|
|||||||
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
it('should correctly identify getPreviousAdjacentEdge edges', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
const lineOfInterest = `line([-6.37, 3.88], %, $seg02)`
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest),
|
code.indexOf(lineOfInterest),
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
if (err(pathToNode)) return
|
if (err(pathToNode)) return
|
||||||
const callExp = getNodeFromPath<CallExpression>(
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
@ -634,11 +632,10 @@ extrude001 = extrude(-5, sketch001)
|
|||||||
it('should correctly identify no edges', () => {
|
it('should correctly identify no edges', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const lineOfInterest = `line([-3.29, -13.85], %)`
|
const lineOfInterest = `line([-3.29, -13.85], %)`
|
||||||
const range: [number, number, boolean] = [
|
const range = topLevelRange(
|
||||||
code.indexOf(lineOfInterest),
|
code.indexOf(lineOfInterest),
|
||||||
code.indexOf(lineOfInterest) + lineOfInterest.length,
|
code.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
const pathToNode = getNodePathFromSourceRange(ast, range)
|
||||||
if (err(pathToNode)) return
|
if (err(pathToNode)) return
|
||||||
const callExp = getNodeFromPath<CallExpression>(
|
const callExp = getNodeFromPath<CallExpression>(
|
||||||
@ -660,13 +657,12 @@ describe('Testing button states', () => {
|
|||||||
) => {
|
) => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const range: [number, number, boolean] = segmentSnippet
|
const range = segmentSnippet
|
||||||
? [
|
? topLevelRange(
|
||||||
code.indexOf(segmentSnippet),
|
code.indexOf(segmentSnippet),
|
||||||
code.indexOf(segmentSnippet) + segmentSnippet.length,
|
code.indexOf(segmentSnippet) + segmentSnippet.length
|
||||||
true,
|
)
|
||||||
]
|
: topLevelRange(ast.end, ast.end) // empty line in the end of the code
|
||||||
: [ast.end, ast.end, true] // empty line in the end of the code
|
|
||||||
|
|
||||||
const selectionRanges: Selections = {
|
const selectionRanges: Selections = {
|
||||||
graphSelections: [
|
graphSelections: [
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
|
ArtifactGraph,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
Expr,
|
Expr,
|
||||||
Identifier,
|
Identifier,
|
||||||
@ -31,11 +32,7 @@ import {
|
|||||||
import { err, trap } from 'lib/trap'
|
import { err, trap } from 'lib/trap'
|
||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import { KclCommandValue } from 'lib/commandTypes'
|
import { KclCommandValue } from 'lib/commandTypes'
|
||||||
import {
|
import { Artifact, getSweepFromSuspectedPath } from 'lang/std/artifactGraph'
|
||||||
Artifact,
|
|
||||||
ArtifactGraph,
|
|
||||||
getSweepFromSuspectedPath,
|
|
||||||
} from 'lang/std/artifactGraph'
|
|
||||||
import {
|
import {
|
||||||
kclManager,
|
kclManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { ArtifactGraph } from 'lang/std/artifactGraph'
|
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
import { Expr } from 'wasm-lib/kcl/bindings/Expr'
|
||||||
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
import { Program } from 'wasm-lib/kcl/bindings/Program'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { PathToNode, VariableDeclarator } from 'lang/wasm'
|
import { ArtifactGraph, PathToNode, VariableDeclarator } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
getPathToExtrudeForSegmentSelection,
|
getPathToExtrudeForSegmentSelection,
|
||||||
mutateAstWithTagForSketchSegment,
|
mutateAstWithTagForSketchSegment,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
initPromise,
|
initPromise,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Identifier,
|
Identifier,
|
||||||
|
topLevelRange,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
@ -57,7 +58,7 @@ variableBelowShouldNotBeIncluded = 3
|
|||||||
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
|
||||||
ast,
|
ast,
|
||||||
execState.memory,
|
execState.memory,
|
||||||
[rangeStart, rangeStart, true]
|
topLevelRange(rangeStart, rangeStart)
|
||||||
)
|
)
|
||||||
expect(variables).toEqual([
|
expect(variables).toEqual([
|
||||||
{ key: 'baseThick', value: 1 },
|
{ key: 'baseThick', value: 1 },
|
||||||
@ -87,7 +88,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find a safe binaryExpression', () => {
|
it('find a safe binaryExpression', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('100 + 100') + 2
|
const rangeStart = code.indexOf('100 + 100') + 2
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
expect(result.value?.type).toBe('BinaryExpression')
|
expect(result.value?.type).toBe('BinaryExpression')
|
||||||
@ -100,7 +104,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find a safe Identifier', () => {
|
it('find a safe Identifier', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('abc')
|
const rangeStart = code.indexOf('abc')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
expect(result.value?.type).toBe('Identifier')
|
expect(result.value?.type).toBe('Identifier')
|
||||||
@ -109,7 +116,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find a safe CallExpression', () => {
|
it('find a safe CallExpression', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('def')
|
const rangeStart = code.indexOf('def')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
expect(result.value?.type).toBe('CallExpression')
|
expect(result.value?.type).toBe('CallExpression')
|
||||||
@ -122,7 +132,7 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
it('find an UNsafe CallExpression, as it has a PipeSubstitution', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('ghi')
|
const rangeStart = code.indexOf('ghi')
|
||||||
const range: [number, number, boolean] = [rangeStart, rangeStart, true]
|
const range = topLevelRange(rangeStart, rangeStart)
|
||||||
const result = isNodeSafeToReplace(ast, range)
|
const result = isNodeSafeToReplace(ast, range)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(false)
|
expect(result.isSafe).toBe(false)
|
||||||
@ -132,7 +142,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find an UNsafe Identifier, as it is a callee', () => {
|
it('find an UNsafe Identifier, as it is a callee', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('ine([2.8,')
|
const rangeStart = code.indexOf('ine([2.8,')
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(false)
|
expect(result.isSafe).toBe(false)
|
||||||
expect(result.value?.type).toBe('CallExpression')
|
expect(result.value?.type).toBe('CallExpression')
|
||||||
@ -143,7 +156,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
it("find a safe BinaryExpression that's assigned to a variable", () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('5 + 6') + 1
|
const rangeStart = code.indexOf('5 + 6') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
expect(result.value?.type).toBe('BinaryExpression')
|
expect(result.value?.type).toBe('BinaryExpression')
|
||||||
@ -156,7 +172,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
it('find a safe BinaryExpression that has a CallExpression within', () => {
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
const rangeStart = code.indexOf('jkl') + 1
|
const rangeStart = code.indexOf('jkl') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
expect(result.value?.type).toBe('BinaryExpression')
|
expect(result.value?.type).toBe('BinaryExpression')
|
||||||
@ -173,7 +192,10 @@ yo2 = hmm([identifierGuy + 5])`
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const rangeStart = code.indexOf('identifierGuy') + 1
|
const rangeStart = code.indexOf('identifierGuy') + 1
|
||||||
const result = isNodeSafeToReplace(ast, [rangeStart, rangeStart, true])
|
const result = isNodeSafeToReplace(
|
||||||
|
ast,
|
||||||
|
topLevelRange(rangeStart, rangeStart)
|
||||||
|
)
|
||||||
if (err(result)) throw result
|
if (err(result)) throw result
|
||||||
|
|
||||||
expect(result.isSafe).toBe(true)
|
expect(result.isSafe).toBe(true)
|
||||||
@ -222,11 +244,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const result = getNodePathFromSourceRange(ast, [
|
const result = getNodePathFromSourceRange(
|
||||||
sourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(sourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
@ -241,11 +262,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
const sourceIndex = code.indexOf(searchLn) + searchLn.length
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const result = getNodePathFromSourceRange(ast, [
|
const result = getNodePathFromSourceRange(
|
||||||
sourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(sourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
const expected = [
|
const expected = [
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
@ -257,18 +277,16 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
expect(result).toEqual(expected)
|
expect(result).toEqual(expected)
|
||||||
// expect similar result for start of line
|
// expect similar result for start of line
|
||||||
const startSourceIndex = code.indexOf(searchLn)
|
const startSourceIndex = code.indexOf(searchLn)
|
||||||
const startResult = getNodePathFromSourceRange(ast, [
|
const startResult = getNodePathFromSourceRange(
|
||||||
startSourceIndex,
|
ast,
|
||||||
startSourceIndex,
|
topLevelRange(startSourceIndex, startSourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
|
expect(startResult).toEqual([...expected, ['callee', 'CallExpression']])
|
||||||
// expect similar result when whole line is selected
|
// expect similar result when whole line is selected
|
||||||
const selectWholeThing = getNodePathFromSourceRange(ast, [
|
const selectWholeThing = getNodePathFromSourceRange(
|
||||||
startSourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(startSourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(selectWholeThing).toEqual(expected)
|
expect(selectWholeThing).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -283,11 +301,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const sourceIndex = code.indexOf(searchLn)
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const result = getNodePathFromSourceRange(ast, [
|
const result = getNodePathFromSourceRange(
|
||||||
sourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(sourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[1, 'index'],
|
[1, 'index'],
|
||||||
@ -313,11 +330,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const sourceIndex = code.indexOf(searchLn)
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const result = getNodePathFromSourceRange(ast, [
|
const result = getNodePathFromSourceRange(
|
||||||
sourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(sourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[1, 'index'],
|
[1, 'index'],
|
||||||
@ -341,11 +357,10 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
const sourceIndex = code.indexOf(searchLn)
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const result = getNodePathFromSourceRange(ast, [
|
const result = getNodePathFromSourceRange(
|
||||||
sourceIndex,
|
ast,
|
||||||
sourceIndex,
|
topLevelRange(sourceIndex, sourceIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
['body', ''],
|
['body', ''],
|
||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
@ -375,7 +390,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
const result = hasExtrudeSketch({
|
const result = hasExtrudeSketch({
|
||||||
ast,
|
ast,
|
||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
programMemory: execState.memory,
|
||||||
})
|
})
|
||||||
@ -395,7 +410,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
const result = hasExtrudeSketch({
|
const result = hasExtrudeSketch({
|
||||||
ast,
|
ast,
|
||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange([100, 101, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
programMemory: execState.memory,
|
||||||
})
|
})
|
||||||
@ -409,7 +424,7 @@ part001 = startSketchAt([-1.41, 3.46])
|
|||||||
const result = hasExtrudeSketch({
|
const result = hasExtrudeSketch({
|
||||||
ast,
|
ast,
|
||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange([10, 11, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
|
||||||
},
|
},
|
||||||
programMemory: execState.memory,
|
programMemory: execState.memory,
|
||||||
})
|
})
|
||||||
@ -431,11 +446,10 @@ describe('Testing findUsesOfTagInPipe', () => {
|
|||||||
const lineOfInterest = `198.85], %, $seg01`
|
const lineOfInterest = `198.85], %, $seg01`
|
||||||
const characterIndex =
|
const characterIndex =
|
||||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
characterIndex,
|
ast,
|
||||||
characterIndex,
|
topLevelRange(characterIndex, characterIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||||
expect(result).toHaveLength(2)
|
expect(result).toHaveLength(2)
|
||||||
result.forEach((range) => {
|
result.forEach((range) => {
|
||||||
@ -448,11 +462,10 @@ describe('Testing findUsesOfTagInPipe', () => {
|
|||||||
const lineOfInterest = `line([306.21, 198.82], %)`
|
const lineOfInterest = `line([306.21, 198.82], %)`
|
||||||
const characterIndex =
|
const characterIndex =
|
||||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, [
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
characterIndex,
|
ast,
|
||||||
characterIndex,
|
topLevelRange(characterIndex, characterIndex)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
const result = findUsesOfTagInPipe(ast, pathToNode)
|
const result = findUsesOfTagInPipe(ast, pathToNode)
|
||||||
expect(result).toHaveLength(0)
|
expect(result).toHaveLength(0)
|
||||||
})
|
})
|
||||||
@ -498,7 +511,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
|||||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
const extruded = hasSketchPipeBeenExtruded(
|
const extruded = hasSketchPipeBeenExtruded(
|
||||||
{
|
{
|
||||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
codeRef: codeRefFromRange(
|
||||||
|
topLevelRange(characterIndex, characterIndex),
|
||||||
|
ast
|
||||||
|
),
|
||||||
},
|
},
|
||||||
ast
|
ast
|
||||||
)
|
)
|
||||||
@ -511,7 +527,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
|||||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
const extruded = hasSketchPipeBeenExtruded(
|
const extruded = hasSketchPipeBeenExtruded(
|
||||||
{
|
{
|
||||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
codeRef: codeRefFromRange(
|
||||||
|
topLevelRange(characterIndex, characterIndex),
|
||||||
|
ast
|
||||||
|
),
|
||||||
},
|
},
|
||||||
ast
|
ast
|
||||||
)
|
)
|
||||||
@ -524,7 +543,10 @@ sketch003 = startSketchOn(extrude001, 'END')
|
|||||||
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
const extruded = hasSketchPipeBeenExtruded(
|
const extruded = hasSketchPipeBeenExtruded(
|
||||||
{
|
{
|
||||||
codeRef: codeRefFromRange([characterIndex, characterIndex, true], ast),
|
codeRef: codeRefFromRange(
|
||||||
|
topLevelRange(characterIndex, characterIndex),
|
||||||
|
ast
|
||||||
|
),
|
||||||
},
|
},
|
||||||
ast
|
ast
|
||||||
)
|
)
|
||||||
@ -638,7 +660,7 @@ myNestedVar = [
|
|||||||
enter: (node, path) => {
|
enter: (node, path) => {
|
||||||
if (
|
if (
|
||||||
node.type === 'Literal' &&
|
node.type === 'Literal' &&
|
||||||
String(node.value) === literalOfInterest
|
String((node as any).value.value) === literalOfInterest
|
||||||
) {
|
) {
|
||||||
pathToNode = path
|
pathToNode = path
|
||||||
} else if (
|
} else if (
|
||||||
@ -651,11 +673,10 @@ myNestedVar = [
|
|||||||
})
|
})
|
||||||
|
|
||||||
const literalIndex = code.indexOf(literalOfInterest)
|
const literalIndex = code.indexOf(literalOfInterest)
|
||||||
const pathToNode2 = getNodePathFromSourceRange(ast, [
|
const pathToNode2 = getNodePathFromSourceRange(
|
||||||
literalIndex + 2,
|
ast,
|
||||||
literalIndex + 2,
|
topLevelRange(literalIndex + 2, literalIndex + 2)
|
||||||
true,
|
)
|
||||||
])
|
|
||||||
expect(pathToNode).toEqual(pathToNode2)
|
expect(pathToNode).toEqual(pathToNode2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { ToolTip } from 'lang/langHelpers'
|
|||||||
import { Selection, Selections } from 'lib/selections'
|
import { Selection, Selections } from 'lib/selections'
|
||||||
import {
|
import {
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
|
ArtifactGraph,
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
Expr,
|
Expr,
|
||||||
@ -16,8 +17,8 @@ import {
|
|||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
sketchFromKclValueOptional,
|
sketchFromKclValueOptional,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
sourceRangeFromRust,
|
|
||||||
SyntaxType,
|
SyntaxType,
|
||||||
|
topLevelRange,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
@ -32,7 +33,7 @@ import {
|
|||||||
import { err, Reason } from 'lib/trap'
|
import { err, Reason } from 'lib/trap'
|
||||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { ArtifactGraph, codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange } from './std/artifactGraph'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
* Retrieves a node from a given path within a Program node structure, optionally stopping at a specified node type.
|
||||||
@ -716,16 +717,6 @@ function isTypeInArrayExp(
|
|||||||
return node.elements.some((el) => isTypeInValue(el, syntaxType))
|
return node.elements.some((el) => isTypeInValue(el, syntaxType))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isValueZero(val?: Expr): boolean {
|
|
||||||
return (
|
|
||||||
(val?.type === 'Literal' && Number(val.value) === 0) ||
|
|
||||||
(val?.type === 'UnaryExpression' &&
|
|
||||||
val.operator === '-' &&
|
|
||||||
val.argument.type === 'Literal' &&
|
|
||||||
Number(val.argument.value) === 0)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isLinesParallelAndConstrained(
|
export function isLinesParallelAndConstrained(
|
||||||
ast: Program,
|
ast: Program,
|
||||||
artifactGraph: ArtifactGraph,
|
artifactGraph: ArtifactGraph,
|
||||||
@ -819,7 +810,7 @@ export function isLinesParallelAndConstrained(
|
|||||||
return {
|
return {
|
||||||
isParallelAndConstrained,
|
isParallelAndConstrained,
|
||||||
selection: {
|
selection: {
|
||||||
codeRef: codeRefFromRange(sourceRangeFromRust(prevSourceRange), ast),
|
codeRef: codeRefFromRange(prevSourceRange, ast),
|
||||||
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
artifact: artifactGraph.get(prevSegment.__geoMeta.id),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -937,7 +928,7 @@ export function findUsesOfTagInPipe(
|
|||||||
const tagArgValue =
|
const tagArgValue =
|
||||||
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
|
tagArg.type === 'TagDeclarator' ? String(tagArg.value) : tagArg.name
|
||||||
if (tagArgValue === tag)
|
if (tagArgValue === tag)
|
||||||
dependentRanges.push([node.start, node.end, true])
|
dependentRanges.push(topLevelRange(node.start, node.end))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return dependentRanges
|
return dependentRanges
|
||||||
|
|||||||
@ -1,559 +0,0 @@
|
|||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
||||||
|
|
||||||
exports[`testing createArtifactGraph > code with an extrusion, fillet and sketch of face: > snapshot of the artifactGraph 1`] = `
|
|
||||||
Map {
|
|
||||||
"UUID-0" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
12,
|
|
||||||
31,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"type": "plane",
|
|
||||||
},
|
|
||||||
"UUID-1" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
37,
|
|
||||||
64,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"id": "UUID",
|
|
||||||
"planeId": "UUID",
|
|
||||||
"segIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"solid2dId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "path",
|
|
||||||
},
|
|
||||||
"UUID-2" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
70,
|
|
||||||
86,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-3" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
92,
|
|
||||||
119,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeCutId": "UUID",
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-4" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
125,
|
|
||||||
150,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-5" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
156,
|
|
||||||
203,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-6" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
209,
|
|
||||||
217,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-7" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"type": "solid2D",
|
|
||||||
},
|
|
||||||
"UUID-8" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
231,
|
|
||||||
254,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"subType": "extrusion",
|
|
||||||
"surfaceIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"type": "sweep",
|
|
||||||
},
|
|
||||||
"UUID-9" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-10" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-11" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-12" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-13" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"subType": "start",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "cap",
|
|
||||||
},
|
|
||||||
"UUID-14" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"subType": "end",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "cap",
|
|
||||||
},
|
|
||||||
"UUID-15" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-16" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-17" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-18" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-19" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-20" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-21" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-22" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-23" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
260,
|
|
||||||
299,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"consumedEdgeId": "UUID",
|
|
||||||
"edgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"subType": "fillet",
|
|
||||||
"type": "edgeCut",
|
|
||||||
},
|
|
||||||
"UUID-24" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
350,
|
|
||||||
377,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"id": "UUID",
|
|
||||||
"planeId": "UUID",
|
|
||||||
"segIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"solid2dId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "path",
|
|
||||||
},
|
|
||||||
"UUID-25" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
383,
|
|
||||||
398,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-26" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
404,
|
|
||||||
420,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-27" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
426,
|
|
||||||
473,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"surfaceId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-28" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
479,
|
|
||||||
487,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"type": "segment",
|
|
||||||
},
|
|
||||||
"UUID-29" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"type": "solid2D",
|
|
||||||
},
|
|
||||||
"UUID-30" => {
|
|
||||||
"codeRef": {
|
|
||||||
"pathToNode": [
|
|
||||||
[
|
|
||||||
"body",
|
|
||||||
"",
|
|
||||||
],
|
|
||||||
],
|
|
||||||
"range": [
|
|
||||||
501,
|
|
||||||
522,
|
|
||||||
true,
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"edgeIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathId": "UUID",
|
|
||||||
"subType": "extrusion",
|
|
||||||
"surfaceIds": [
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
"UUID",
|
|
||||||
],
|
|
||||||
"type": "sweep",
|
|
||||||
},
|
|
||||||
"UUID-31" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-32" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-33" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"segId": "UUID",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "wall",
|
|
||||||
},
|
|
||||||
"UUID-34" => {
|
|
||||||
"edgeCutEdgeIds": [],
|
|
||||||
"id": "UUID",
|
|
||||||
"pathIds": [],
|
|
||||||
"subType": "end",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "cap",
|
|
||||||
},
|
|
||||||
"UUID-35" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-36" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-37" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-38" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-39" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "opposite",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
"UUID-40" => {
|
|
||||||
"id": "UUID",
|
|
||||||
"segId": "UUID",
|
|
||||||
"subType": "adjacent",
|
|
||||||
"sweepId": "UUID",
|
|
||||||
"type": "sweepEdge",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
@ -1,960 +0,0 @@
|
|||||||
import {
|
|
||||||
makeDefaultPlanes,
|
|
||||||
assertParse,
|
|
||||||
initPromise,
|
|
||||||
Program,
|
|
||||||
ArtifactCommand,
|
|
||||||
ExecState,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { Models } from '@kittycad/lib'
|
|
||||||
import {
|
|
||||||
ResponseMap,
|
|
||||||
createArtifactGraph,
|
|
||||||
filterArtifacts,
|
|
||||||
expandPlane,
|
|
||||||
expandPath,
|
|
||||||
expandSweep,
|
|
||||||
ArtifactGraph,
|
|
||||||
expandSegment,
|
|
||||||
getArtifactsToUpdate,
|
|
||||||
} from './artifactGraph'
|
|
||||||
import { err } from 'lib/trap'
|
|
||||||
import { engineCommandManager, kclManager } from 'lib/singletons'
|
|
||||||
import { VITE_KC_DEV_TOKEN } from 'env'
|
|
||||||
import fsp from 'fs/promises'
|
|
||||||
import fs from 'fs'
|
|
||||||
import { chromium } from 'playwright'
|
|
||||||
import * as d3 from 'd3-force'
|
|
||||||
import path from 'path'
|
|
||||||
import pixelmatch from 'pixelmatch'
|
|
||||||
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.
|
|
||||||
It's needed for testing the artifactGraph, as it is tied to the websocket commands.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const pathStart = 'src/lang/std/artifactMapCache'
|
|
||||||
const fullPath = `${pathStart}/artifactMapCache.json`
|
|
||||||
|
|
||||||
const exampleCode1 = `sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-5, -5], %)
|
|
||||||
|> line([0, 10], %)
|
|
||||||
|> line([10.55, 0], %, $seg01)
|
|
||||||
|> line([0, -10], %, $seg02)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude001 = extrude(-10, sketch001)
|
|
||||||
|> fillet({ radius: 5, tags: [seg01] }, %)
|
|
||||||
sketch002 = startSketchOn(extrude001, seg02)
|
|
||||||
|> startProfileAt([-2, -6], %)
|
|
||||||
|> line([2, 3], %)
|
|
||||||
|> line([2, -3], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude002 = extrude(5, sketch002)
|
|
||||||
`
|
|
||||||
|
|
||||||
const exampleCodeNo3D = `sketch003 = startSketchOn('YZ')
|
|
||||||
|> startProfileAt([5.82, 0], %)
|
|
||||||
|> angledLine([180, 11.54], %, $rectangleSegmentA001)
|
|
||||||
|> angledLine([
|
|
||||||
segAng(rectangleSegmentA001) - 90,
|
|
||||||
8.21
|
|
||||||
], %, $rectangleSegmentB001)
|
|
||||||
|> angledLine([
|
|
||||||
segAng(rectangleSegmentA001),
|
|
||||||
-segLen(rectangleSegmentA001)
|
|
||||||
], %, $rectangleSegmentC001)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
sketch004 = startSketchOn('-XZ')
|
|
||||||
|> startProfileAt([0, 14.36], %)
|
|
||||||
|> line([15.49, 0.05], %)
|
|
||||||
|> tangentialArcTo([0, 0], %)
|
|
||||||
|> tangentialArcTo([-6.8, 8.17], %)
|
|
||||||
`
|
|
||||||
|
|
||||||
const sketchOnFaceOnFaceEtc = `sketch001 = startSketchOn('XZ')
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> line([4, 8], %)
|
|
||||||
|> line([5, -8], %, $seg01)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude001 = extrude(6, sketch001)
|
|
||||||
sketch002 = startSketchOn(extrude001, seg01)
|
|
||||||
|> startProfileAt([-0.5, 0.5], %)
|
|
||||||
|> line([2, 5], %)
|
|
||||||
|> line([2, -5], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude002 = extrude(5, sketch002)
|
|
||||||
sketch003 = startSketchOn(extrude002, 'END')
|
|
||||||
|> startProfileAt([1, 1.5], %)
|
|
||||||
|> line([0.5, 2], %, $seg02)
|
|
||||||
|> line([1, -2], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude003 = extrude(4, sketch003)
|
|
||||||
sketch004 = startSketchOn(extrude003, seg02)
|
|
||||||
|> startProfileAt([-3, 14], %)
|
|
||||||
|> line([0.5, 1], %)
|
|
||||||
|> line([0.5, -2], %)
|
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|
||||||
|> close(%)
|
|
||||||
extrude004 = extrude(3, sketch004)
|
|
||||||
`
|
|
||||||
const exampleCodeOffsetPlanes = `
|
|
||||||
offsetPlane001 = offsetPlane("XY", 20)
|
|
||||||
offsetPlane002 = offsetPlane("XZ", -50)
|
|
||||||
offsetPlane003 = offsetPlane("YZ", 10)
|
|
||||||
|
|
||||||
sketch002 = startSketchOn(offsetPlane001)
|
|
||||||
|> startProfileAt([0, 0], %)
|
|
||||||
|> line([6.78, 15.01], %)
|
|
||||||
`
|
|
||||||
|
|
||||||
// add more code snippets here and use `getCommands` to get the artifactCommands and responseMap for more tests
|
|
||||||
const codeToWriteCacheFor = {
|
|
||||||
exampleCode1,
|
|
||||||
sketchOnFaceOnFaceEtc,
|
|
||||||
exampleCodeNo3D,
|
|
||||||
exampleCodeOffsetPlanes,
|
|
||||||
} as const
|
|
||||||
|
|
||||||
type CodeKey = keyof typeof codeToWriteCacheFor
|
|
||||||
|
|
||||||
type CacheShape = {
|
|
||||||
[key in CodeKey]: {
|
|
||||||
artifactCommands: ArtifactCommand[]
|
|
||||||
responseMap: ResponseMap
|
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
await initPromise
|
|
||||||
|
|
||||||
// THESE TEST WILL FAIL without VITE_KC_DEV_TOKEN set in .env.development.local
|
|
||||||
await new Promise((resolve) => {
|
|
||||||
engineCommandManager.start({
|
|
||||||
// disableWebRTC: true,
|
|
||||||
token: VITE_KC_DEV_TOKEN,
|
|
||||||
// there does seem to be a minimum resolution, not sure what it is but 256 works ok.
|
|
||||||
width: 256,
|
|
||||||
height: 256,
|
|
||||||
makeDefaultPlanes: () => makeDefaultPlanes(engineCommandManager),
|
|
||||||
setMediaStream: () => {},
|
|
||||||
setIsStreamReady: () => {},
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
||||||
callbackOnEngineLiteConnect: async () => {
|
|
||||||
const cacheEntries = Object.entries(codeToWriteCacheFor) as [
|
|
||||||
CodeKey,
|
|
||||||
string
|
|
||||||
][]
|
|
||||||
const cacheToWriteToFileTemp: Partial<CacheShape> = {}
|
|
||||||
for (const [codeKey, code] of cacheEntries) {
|
|
||||||
const ast = assertParse(code)
|
|
||||||
await kclManager.executeAst({ ast })
|
|
||||||
|
|
||||||
cacheToWriteToFileTemp[codeKey] = {
|
|
||||||
artifactCommands: kclManager.execState.artifactCommands,
|
|
||||||
responseMap: engineCommandManager.responseMap,
|
|
||||||
execStateArtifacts: kclManager.execState.artifacts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const cache = JSON.stringify(cacheToWriteToFileTemp)
|
|
||||||
|
|
||||||
await fsp.mkdir(pathStart, { recursive: true })
|
|
||||||
await fsp.writeFile(fullPath, cache)
|
|
||||||
resolve(true)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}, 20_000)
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
engineCommandManager.tearDown()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('testing createArtifactGraph', () => {
|
|
||||||
describe('code with offset planes and a sketch:', () => {
|
|
||||||
let ast: Node<Program>
|
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
|
||||||
|
|
||||||
it('setup', () => {
|
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
|
||||||
const {
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast: _ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCodeOffsetPlanes')
|
|
||||||
ast = _ast
|
|
||||||
theMap = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`there should be one sketch`, () => {
|
|
||||||
const sketches = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
|
||||||
(path) => expandPath(path[1], theMap)
|
|
||||||
)
|
|
||||||
expect(sketches).toHaveLength(1)
|
|
||||||
sketches.forEach((path) => {
|
|
||||||
if (err(path)) throw path
|
|
||||||
expect(path.type).toBe('path')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`there should be three offsetPlanes`, () => {
|
|
||||||
const offsetPlanes = [
|
|
||||||
...filterArtifacts({ types: ['plane'] }, theMap),
|
|
||||||
].map((plane) => expandPlane(plane[1], theMap))
|
|
||||||
expect(offsetPlanes).toHaveLength(3)
|
|
||||||
offsetPlanes.forEach((path) => {
|
|
||||||
expect(path.type).toBe('plane')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`Only one offset plane should have a path`, () => {
|
|
||||||
const offsetPlanes = [
|
|
||||||
...filterArtifacts({ types: ['plane'] }, theMap),
|
|
||||||
].map((plane) => expandPlane(plane[1], theMap))
|
|
||||||
const offsetPlaneWithPaths = offsetPlanes.filter(
|
|
||||||
(plane) => plane.paths.length
|
|
||||||
)
|
|
||||||
expect(offsetPlaneWithPaths).toHaveLength(1)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
|
||||||
let ast: Node<Program>
|
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
|
||||||
it('setup', () => {
|
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
|
||||||
const {
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast: _ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCode1')
|
|
||||||
ast = _ast
|
|
||||||
theMap = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be two planes for the extrusion and the sketch on face', () => {
|
|
||||||
const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map(
|
|
||||||
(plane) => expandPlane(plane[1], theMap)
|
|
||||||
)
|
|
||||||
expect(planes).toHaveLength(1)
|
|
||||||
planes.forEach((path) => {
|
|
||||||
expect(path.type).toBe('plane')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('there should be two paths for the extrusion and the sketch on face', () => {
|
|
||||||
const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
|
||||||
(path) => expandPath(path[1], theMap)
|
|
||||||
)
|
|
||||||
expect(paths).toHaveLength(2)
|
|
||||||
paths.forEach((path) => {
|
|
||||||
if (err(path)) throw path
|
|
||||||
expect(path.type).toBe('path')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be two extrusions, for the original and the sketchOnFace, the first extrusion should have 6 sides of the cube', () => {
|
|
||||||
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
|
|
||||||
(extrusion) => expandSweep(extrusion[1], theMap)
|
|
||||||
)
|
|
||||||
expect(extrusions).toHaveLength(2)
|
|
||||||
extrusions.forEach((extrusion, index) => {
|
|
||||||
if (err(extrusion)) throw extrusion
|
|
||||||
expect(extrusion.type).toBe('sweep')
|
|
||||||
const firstExtrusionIsACubeIE6Sides = 6
|
|
||||||
// Each face of the triangular prism (5), but without the bottom cap.
|
|
||||||
// The engine doesn't generate that.
|
|
||||||
const secondExtrusionIsATriangularPrism = 4
|
|
||||||
expect(extrusion.surfaces.length).toBe(
|
|
||||||
!index
|
|
||||||
? firstExtrusionIsACubeIE6Sides
|
|
||||||
: secondExtrusionIsATriangularPrism
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be 5 + 4 segments, 4 (+close) from the first extrusion and 3 (+close) from the second', () => {
|
|
||||||
const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map(
|
|
||||||
(segment) => expandSegment(segment[1], theMap)
|
|
||||||
)
|
|
||||||
expect(segments).toHaveLength(9)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('snapshot of the artifactGraph', () => {
|
|
||||||
const stableMap = new Map(
|
|
||||||
[...theMap].map(([, artifact], index): [string, any] => {
|
|
||||||
const stableValue: any = {}
|
|
||||||
Object.entries(artifact).forEach(([propName, value]) => {
|
|
||||||
if (
|
|
||||||
propName === 'type' ||
|
|
||||||
propName === 'codeRef' ||
|
|
||||||
propName === 'subType'
|
|
||||||
) {
|
|
||||||
stableValue[propName] = value
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (Array.isArray(value))
|
|
||||||
stableValue[propName] = value.map(() => 'UUID')
|
|
||||||
if (typeof value === 'string' && value)
|
|
||||||
stableValue[propName] = 'UUID'
|
|
||||||
})
|
|
||||||
return [`UUID-${index}`, stableValue]
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(stableMap).toMatchSnapshot()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('screenshot graph', async () => {
|
|
||||||
// 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
|
|
||||||
// of the edges refers to a non-existent node, the graph will throw.
|
|
||||||
// further more we can check that each edge is bi-directional, if it's not
|
|
||||||
// by checking the arrow heads going both ways, on the graph.
|
|
||||||
await GraphTheGraph(theMap, 2000, 2000, 'exampleCode1.png')
|
|
||||||
}, 20000)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe(`code with sketches but no extrusions or other 3D elements`, () => {
|
|
||||||
let ast: Node<Program>
|
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
|
||||||
it(`setup`, () => {
|
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
|
||||||
const {
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast: _ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('exampleCodeNo3D')
|
|
||||||
ast = _ast
|
|
||||||
theMap = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be two planes, one for each sketch path', () => {
|
|
||||||
const planes = [...filterArtifacts({ types: ['plane'] }, theMap)].map(
|
|
||||||
(plane) => expandPlane(plane[1], theMap)
|
|
||||||
)
|
|
||||||
expect(planes).toHaveLength(2)
|
|
||||||
planes.forEach((path) => {
|
|
||||||
expect(path.type).toBe('plane')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('there should be two paths, one on each plane', () => {
|
|
||||||
const paths = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
|
||||||
(path) => expandPath(path[1], theMap)
|
|
||||||
)
|
|
||||||
expect(paths).toHaveLength(2)
|
|
||||||
paths.forEach((path) => {
|
|
||||||
if (err(path)) throw path
|
|
||||||
expect(path.type).toBe('path')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`there should be 1 solid2D, just for the first closed path`, () => {
|
|
||||||
const solid2Ds = [...filterArtifacts({ types: ['solid2D'] }, theMap)]
|
|
||||||
expect(solid2Ds).toHaveLength(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be no extrusions', () => {
|
|
||||||
const extrusions = [...filterArtifacts({ types: ['sweep'] }, theMap)].map(
|
|
||||||
(extrusion) => expandSweep(extrusion[1], theMap)
|
|
||||||
)
|
|
||||||
expect(extrusions).toHaveLength(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('there should be 8 segments, 4 + 1 (close) from the first sketch and 3 from the second', () => {
|
|
||||||
const segments = [...filterArtifacts({ types: ['segment'] }, theMap)].map(
|
|
||||||
(segment) => expandSegment(segment[1], theMap)
|
|
||||||
)
|
|
||||||
expect(segments).toHaveLength(8)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('screenshot graph', async () => {
|
|
||||||
// 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
|
|
||||||
// of the edges refers to a non-existent node, the graph will throw.
|
|
||||||
// further more we can check that each edge is bi-directional, if it's not
|
|
||||||
// by checking the arrow heads going both ways, on the graph.
|
|
||||||
await GraphTheGraph(theMap, 2000, 2000, 'exampleCodeNo3D.png')
|
|
||||||
}, 20000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('capture graph of sketchOnFaceOnFace...', () => {
|
|
||||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
|
||||||
let ast: Node<Program>
|
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
|
||||||
it('setup', async () => {
|
|
||||||
// putting this logic in here because describe blocks runs before beforeAll has finished
|
|
||||||
const {
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast: _ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
} = getCommands('sketchOnFaceOnFaceEtc')
|
|
||||||
ast = _ast
|
|
||||||
theMap = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 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
|
|
||||||
// of the edges refers to a non-existent node, the graph will throw.
|
|
||||||
// further more we can check that each edge is bi-directional, if it's not
|
|
||||||
// by checking the arrow heads going both ways, on the graph.
|
|
||||||
await GraphTheGraph(theMap, 3000, 3000, 'sketchOnFaceOnFaceEtc.png')
|
|
||||||
}, 20000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function getCommands(
|
|
||||||
codeKey: CodeKey
|
|
||||||
): CacheShape[CodeKey] & { ast: Node<Program> } {
|
|
||||||
const ast = assertParse(codeKey)
|
|
||||||
const file = fs.readFileSync(fullPath, 'utf-8')
|
|
||||||
const parsed: CacheShape = JSON.parse(file)
|
|
||||||
// these either already exist from the last run, or were created in
|
|
||||||
const artifactCommands = parsed[codeKey].artifactCommands
|
|
||||||
const responseMap = parsed[codeKey].responseMap
|
|
||||||
const execStateArtifacts = parsed[codeKey].execStateArtifacts
|
|
||||||
return {
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function GraphTheGraph(
|
|
||||||
theMap: ArtifactGraph,
|
|
||||||
sizeX: number,
|
|
||||||
sizeY: number,
|
|
||||||
imageName: string
|
|
||||||
) {
|
|
||||||
const nodes: Array<{ id: string; label: string }> = []
|
|
||||||
const edges: Array<{ source: string; target: string; label: string }> = []
|
|
||||||
let index = 0
|
|
||||||
for (const [commandId, artifact] of theMap) {
|
|
||||||
nodes.push({
|
|
||||||
id: commandId,
|
|
||||||
label: `${artifact.type}-${index++}`,
|
|
||||||
})
|
|
||||||
Object.entries(artifact).forEach(([propName, value]) => {
|
|
||||||
if (
|
|
||||||
propName === 'type' ||
|
|
||||||
propName === 'codeRef' ||
|
|
||||||
propName === 'subType' ||
|
|
||||||
propName === 'id'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
if (Array.isArray(value))
|
|
||||||
value.forEach((v) => {
|
|
||||||
v && edges.push({ source: commandId, target: v, label: propName })
|
|
||||||
})
|
|
||||||
if (typeof value === 'string' && value)
|
|
||||||
edges.push({ source: commandId, target: value, label: propName })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a force simulation to calculate node positions
|
|
||||||
const simulation = d3
|
|
||||||
.forceSimulation(nodes as any)
|
|
||||||
.force(
|
|
||||||
'link',
|
|
||||||
d3
|
|
||||||
.forceLink(edges)
|
|
||||||
.id((d: any) => d.id)
|
|
||||||
.distance(100)
|
|
||||||
)
|
|
||||||
.force('charge', d3.forceManyBody().strength(-300))
|
|
||||||
.force('center', d3.forceCenter(300, 200))
|
|
||||||
.stop()
|
|
||||||
|
|
||||||
// Run the simulation
|
|
||||||
for (let i = 0; i < 300; ++i) simulation.tick()
|
|
||||||
|
|
||||||
// Create traces for Plotly
|
|
||||||
const nodeTrace = {
|
|
||||||
x: nodes.map((node: any) => node.x),
|
|
||||||
y: nodes.map((node: any) => node.y),
|
|
||||||
text: nodes.map((node) => node.label), // Use the custom label
|
|
||||||
mode: 'markers+text',
|
|
||||||
type: 'scatter',
|
|
||||||
marker: { size: 20, color: 'gray' }, // Nodes in gray
|
|
||||||
textfont: { size: 14, color: 'black' }, // Labels in black
|
|
||||||
textposition: 'top center', // Position text on top
|
|
||||||
}
|
|
||||||
|
|
||||||
const edgeTrace = {
|
|
||||||
x: [],
|
|
||||||
y: [],
|
|
||||||
mode: 'lines',
|
|
||||||
type: 'scatter',
|
|
||||||
line: { width: 2, color: 'lightgray' }, // Edges in light gray
|
|
||||||
}
|
|
||||||
|
|
||||||
const annotations: any[] = []
|
|
||||||
|
|
||||||
edges.forEach((edge) => {
|
|
||||||
const sourceNode = nodes.find(
|
|
||||||
(node: any) => node.id === (edge as any).source.id
|
|
||||||
)
|
|
||||||
const targetNode = nodes.find(
|
|
||||||
(node: any) => node.id === (edge as any).target.id
|
|
||||||
)
|
|
||||||
|
|
||||||
// Check if nodes are found
|
|
||||||
if (!sourceNode || !targetNode) {
|
|
||||||
throw new Error(
|
|
||||||
// @ts-ignore
|
|
||||||
`Node not found: ${!sourceNode ? edge.source.id : edge.target.id}`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
edgeTrace.x.push(sourceNode.x, targetNode.x, null)
|
|
||||||
// @ts-ignore
|
|
||||||
edgeTrace.y.push(sourceNode.y, targetNode.y, null)
|
|
||||||
|
|
||||||
// Calculate offset for arrowhead
|
|
||||||
const offsetFactor = 0.9 // Adjust this factor to control the offset distance
|
|
||||||
// @ts-ignore
|
|
||||||
const offsetX = (targetNode.x - sourceNode.x) * offsetFactor
|
|
||||||
// @ts-ignore
|
|
||||||
const offsetY = (targetNode.y - sourceNode.y) * offsetFactor
|
|
||||||
|
|
||||||
// Add arrowhead annotation with offset
|
|
||||||
annotations.push({
|
|
||||||
// @ts-ignore
|
|
||||||
ax: sourceNode.x,
|
|
||||||
// @ts-ignore
|
|
||||||
ay: sourceNode.y,
|
|
||||||
// @ts-ignore
|
|
||||||
x: targetNode.x - offsetX,
|
|
||||||
// @ts-ignore
|
|
||||||
y: targetNode.y - offsetY,
|
|
||||||
xref: 'x',
|
|
||||||
yref: 'y',
|
|
||||||
axref: 'x',
|
|
||||||
ayref: 'y',
|
|
||||||
showarrow: true,
|
|
||||||
arrowhead: 2,
|
|
||||||
arrowsize: 1,
|
|
||||||
arrowwidth: 2,
|
|
||||||
arrowcolor: 'darkgray', // Arrowheads in dark gray
|
|
||||||
})
|
|
||||||
|
|
||||||
// Add edge label annotation closer to the edge tail (25% of the length)
|
|
||||||
// @ts-ignore
|
|
||||||
const labelX = sourceNode.x * 0.75 + targetNode.x * 0.25
|
|
||||||
// @ts-ignore
|
|
||||||
const labelY = sourceNode.y * 0.75 + targetNode.y * 0.25
|
|
||||||
annotations.push({
|
|
||||||
x: labelX,
|
|
||||||
y: labelY,
|
|
||||||
xref: 'x',
|
|
||||||
yref: 'y',
|
|
||||||
text: edge.label,
|
|
||||||
showarrow: false,
|
|
||||||
font: { size: 12, color: 'black' }, // Edge labels in black
|
|
||||||
align: 'center',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const data = [edgeTrace, nodeTrace]
|
|
||||||
|
|
||||||
const layout = {
|
|
||||||
// title: 'Force-Directed Graph with Nodes and Edges',
|
|
||||||
xaxis: { showgrid: false, zeroline: false, showticklabels: false },
|
|
||||||
yaxis: { showgrid: false, zeroline: false, showticklabels: false },
|
|
||||||
showlegend: false,
|
|
||||||
annotations: annotations,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Export to PNG using Playwright
|
|
||||||
const browser = await chromium.launch()
|
|
||||||
const page = await browser.newPage()
|
|
||||||
await page.setContent(`
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="plotly-graph" style="width:${sizeX}px;height:${sizeY}px;"></div>
|
|
||||||
<script>
|
|
||||||
Plotly.newPlot('plotly-graph', ${JSON.stringify(
|
|
||||||
data
|
|
||||||
)}, ${JSON.stringify(layout)})
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`)
|
|
||||||
await page.waitForSelector('#plotly-graph')
|
|
||||||
const element = await page.$('#plotly-graph')
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
await element.screenshot({
|
|
||||||
path: `./e2e/playwright/temp3.png`,
|
|
||||||
})
|
|
||||||
|
|
||||||
await browser.close()
|
|
||||||
|
|
||||||
const originalImgPath = path.resolve(
|
|
||||||
`./src/lang/std/artifactMapGraphs/${imageName}`
|
|
||||||
)
|
|
||||||
// chop the top 30 pixels off the image
|
|
||||||
const originalImgExists = fs.existsSync(originalImgPath)
|
|
||||||
const originalImg = originalImgExists
|
|
||||||
? PNG.sync.read(fs.readFileSync(originalImgPath))
|
|
||||||
: null
|
|
||||||
// const img1Data = new Uint8Array(img1.data)
|
|
||||||
// const img1DataChopped = img1Data.slice(30 * img1.width * 4)
|
|
||||||
// img1.data = Buffer.from(img1DataChopped)
|
|
||||||
|
|
||||||
const newImagePath = path.resolve('./e2e/playwright/temp3.png')
|
|
||||||
const newImage = PNG.sync.read(fs.readFileSync(newImagePath))
|
|
||||||
const newImageData = new Uint8Array(newImage.data)
|
|
||||||
const newImageDataChopped = newImageData.slice(30 * newImage.width * 4)
|
|
||||||
newImage.data = Buffer.from(newImageDataChopped)
|
|
||||||
|
|
||||||
const { width, height } = originalImg ?? newImage
|
|
||||||
const diff = new PNG({ width, height })
|
|
||||||
|
|
||||||
const imageSizeDifferent = originalImg?.data.length !== newImage.data.length
|
|
||||||
let numDiffPixels = 0
|
|
||||||
if (!imageSizeDifferent) {
|
|
||||||
numDiffPixels = pixelmatch(
|
|
||||||
originalImg.data,
|
|
||||||
newImage.data,
|
|
||||||
diff.data,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
{
|
|
||||||
threshold: 0.1,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numDiffPixels > 10 || imageSizeDifferent) {
|
|
||||||
console.warn('numDiffPixels', numDiffPixels)
|
|
||||||
// write file out to final place
|
|
||||||
fs.writeFileSync(
|
|
||||||
`src/lang/std/artifactMapGraphs/${imageName}`,
|
|
||||||
PNG.sync.write(newImage)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('testing getArtifactsToUpdate', () => {
|
|
||||||
it('should return an array of artifacts to update', () => {
|
|
||||||
const { artifactCommands, responseMap, ast, execStateArtifacts } =
|
|
||||||
getCommands('exampleCode1')
|
|
||||||
const map = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
const getArtifact = (id: string) => map.get(id)
|
|
||||||
const currentPlaneId = 'UUID-1'
|
|
||||||
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({
|
|
||||||
artifactCommand,
|
|
||||||
responseMap,
|
|
||||||
getArtifact,
|
|
||||||
currentPlaneId,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
return artifactsToUpdate.map(({ artifact }) => artifact)
|
|
||||||
}
|
|
||||||
expect(getUpdateObjects('start_path')).toEqual([
|
|
||||||
{
|
|
||||||
type: 'path',
|
|
||||||
segIds: [],
|
|
||||||
id: expect.any(String),
|
|
||||||
planeId: 'UUID-1',
|
|
||||||
sweepId: undefined,
|
|
||||||
codeRef: {
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
range: [37, 64, true],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
expect(getUpdateObjects('extrude')).toEqual([
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
pathId: expect.any(String),
|
|
||||||
id: expect.any(String),
|
|
||||||
surfaceIds: [],
|
|
||||||
edgeIds: [],
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'path',
|
|
||||||
id: expect.any(String),
|
|
||||||
segIds: expect.any(Array),
|
|
||||||
planeId: expect.any(String),
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
codeRef: {
|
|
||||||
range: [37, 64, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
solid2dId: expect.any(String),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
expect(getUpdateObjects('extend_path')).toEqual([
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: undefined,
|
|
||||||
edgeIds: [],
|
|
||||||
codeRef: {
|
|
||||||
range: [70, 86, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'path',
|
|
||||||
id: expect.any(String),
|
|
||||||
segIds: expect.any(Array),
|
|
||||||
planeId: expect.any(String),
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
codeRef: {
|
|
||||||
range: [37, 64, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
solid2dId: expect.any(String),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
expect(getUpdateObjects('solid3d_fillet_edge')).toEqual([
|
|
||||||
{
|
|
||||||
type: 'edgeCut',
|
|
||||||
subType: 'fillet',
|
|
||||||
id: expect.any(String),
|
|
||||||
consumedEdgeId: expect.any(String),
|
|
||||||
edgeIds: [],
|
|
||||||
surfaceId: undefined,
|
|
||||||
codeRef: {
|
|
||||||
range: [260, 299, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: expect.any(String),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [92, 119, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
edgeCutId: expect.any(String),
|
|
||||||
},
|
|
||||||
])
|
|
||||||
expect(getUpdateObjects('solid3d_get_extrusion_face_info')).toEqual([
|
|
||||||
{
|
|
||||||
type: 'wall',
|
|
||||||
id: expect.any(String),
|
|
||||||
segId: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: expect.any(String),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [156, 203, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'wall',
|
|
||||||
id: expect.any(String),
|
|
||||||
segId: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: expect.any(String),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [125, 150, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'wall',
|
|
||||||
id: expect.any(String),
|
|
||||||
segId: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: expect.any(String),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [92, 119, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
edgeCutId: expect.any(String),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'wall',
|
|
||||||
id: expect.any(String),
|
|
||||||
segId: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'segment',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceId: expect.any(String),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [70, 86, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'cap',
|
|
||||||
subType: 'start',
|
|
||||||
id: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'cap',
|
|
||||||
subType: 'end',
|
|
||||||
id: expect.any(String),
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
sweepId: expect.any(String),
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'sweep',
|
|
||||||
subType: 'extrusion',
|
|
||||||
id: expect.any(String),
|
|
||||||
pathId: expect.any(String),
|
|
||||||
surfaceIds: expect.any(Array),
|
|
||||||
edgeIds: expect.any(Array),
|
|
||||||
codeRef: {
|
|
||||||
range: [231, 254, true],
|
|
||||||
pathToNode: [['body', '']],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@ -1,17 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
ArtifactCommand,
|
Artifact,
|
||||||
ExecState,
|
ArtifactGraph,
|
||||||
|
ArtifactId,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Program,
|
Program,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
sourceRangeFromRust,
|
PathArtifact,
|
||||||
|
PlaneArtifact,
|
||||||
|
WallArtifact,
|
||||||
|
SegmentArtifact,
|
||||||
|
Solid2dArtifact as Solid2D,
|
||||||
|
SweepArtifact,
|
||||||
|
SweepEdge,
|
||||||
|
CapArtifact,
|
||||||
|
EdgeCut,
|
||||||
} from 'lang/wasm'
|
} 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 { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||||
|
|
||||||
interface BaseArtifact {
|
interface BaseArtifact {
|
||||||
id: ArtifactId
|
id: ArtifactId
|
||||||
@ -22,30 +30,12 @@ export interface CodeRef {
|
|||||||
pathToNode: PathToNode
|
pathToNode: PathToNode
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlaneArtifact extends BaseArtifact {
|
|
||||||
type: 'plane'
|
|
||||||
pathIds: Array<ArtifactId>
|
|
||||||
codeRef: CodeRef
|
|
||||||
}
|
|
||||||
export interface PlaneArtifactRich extends BaseArtifact {
|
export interface PlaneArtifactRich extends BaseArtifact {
|
||||||
type: 'plane'
|
type: 'plane'
|
||||||
paths: Array<PathArtifact>
|
paths: Array<PathArtifact>
|
||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PathArtifact extends BaseArtifact {
|
|
||||||
type: 'path'
|
|
||||||
planeId: ArtifactId
|
|
||||||
segIds: Array<ArtifactId>
|
|
||||||
sweepId?: ArtifactId
|
|
||||||
solid2dId?: ArtifactId
|
|
||||||
codeRef: CodeRef
|
|
||||||
}
|
|
||||||
|
|
||||||
interface solid2D extends BaseArtifact {
|
|
||||||
type: 'solid2D'
|
|
||||||
pathId: ArtifactId
|
|
||||||
}
|
|
||||||
export interface PathArtifactRich extends BaseArtifact {
|
export interface PathArtifactRich extends BaseArtifact {
|
||||||
type: 'path'
|
type: 'path'
|
||||||
/** A path must always lie on a plane */
|
/** A path must always lie on a plane */
|
||||||
@ -53,18 +43,10 @@ export interface PathArtifactRich extends BaseArtifact {
|
|||||||
/** A path must always contain 0 or more segments */
|
/** A path must always contain 0 or more segments */
|
||||||
segments: Array<SegmentArtifact>
|
segments: Array<SegmentArtifact>
|
||||||
/** A path may not result in a sweep artifact */
|
/** A path may not result in a sweep artifact */
|
||||||
sweep?: SweepArtifact
|
sweep: SweepArtifact | null
|
||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SegmentArtifact extends BaseArtifact {
|
|
||||||
type: 'segment'
|
|
||||||
pathId: ArtifactId
|
|
||||||
surfaceId?: ArtifactId
|
|
||||||
edgeIds: Array<ArtifactId>
|
|
||||||
edgeCutId?: ArtifactId
|
|
||||||
codeRef: CodeRef
|
|
||||||
}
|
|
||||||
interface SegmentArtifactRich extends BaseArtifact {
|
interface SegmentArtifactRich extends BaseArtifact {
|
||||||
type: 'segment'
|
type: 'segment'
|
||||||
path: PathArtifact
|
path: PathArtifact
|
||||||
@ -74,15 +56,6 @@ interface SegmentArtifactRich extends BaseArtifact {
|
|||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A Sweep is a more generic term for extrude, revolve, loft and sweep*/
|
|
||||||
interface SweepArtifact extends BaseArtifact {
|
|
||||||
type: 'sweep'
|
|
||||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
|
||||||
pathId: string
|
|
||||||
surfaceIds: Array<string>
|
|
||||||
edgeIds: Array<string>
|
|
||||||
codeRef: CodeRef
|
|
||||||
}
|
|
||||||
interface SweepArtifactRich extends BaseArtifact {
|
interface SweepArtifactRich extends BaseArtifact {
|
||||||
type: 'sweep'
|
type: 'sweep'
|
||||||
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
subType: 'extrusion' | 'revolve' | 'loft' | 'sweep'
|
||||||
@ -92,58 +65,6 @@ interface SweepArtifactRich extends BaseArtifact {
|
|||||||
codeRef: CodeRef
|
codeRef: CodeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
interface WallArtifact extends BaseArtifact {
|
|
||||||
type: 'wall'
|
|
||||||
segId: ArtifactId
|
|
||||||
edgeCutEdgeIds: Array<ArtifactId>
|
|
||||||
sweepId: ArtifactId
|
|
||||||
pathIds: Array<ArtifactId>
|
|
||||||
}
|
|
||||||
interface CapArtifact extends BaseArtifact {
|
|
||||||
type: 'cap'
|
|
||||||
subType: 'start' | 'end'
|
|
||||||
edgeCutEdgeIds: Array<ArtifactId>
|
|
||||||
sweepId: ArtifactId
|
|
||||||
pathIds: Array<ArtifactId>
|
|
||||||
}
|
|
||||||
|
|
||||||
interface SweepEdge extends BaseArtifact {
|
|
||||||
type: 'sweepEdge'
|
|
||||||
segId: ArtifactId
|
|
||||||
sweepId: ArtifactId
|
|
||||||
subType: 'opposite' | 'adjacent'
|
|
||||||
}
|
|
||||||
|
|
||||||
/** A edgeCut is a more generic term for both fillet or chamfer */
|
|
||||||
interface EdgeCut extends BaseArtifact {
|
|
||||||
type: 'edgeCut'
|
|
||||||
subType: 'fillet' | 'chamfer'
|
|
||||||
consumedEdgeId: ArtifactId
|
|
||||||
edgeIds: Array<ArtifactId>
|
|
||||||
surfaceId?: ArtifactId
|
|
||||||
codeRef: CodeRef
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EdgeCutEdge extends BaseArtifact {
|
|
||||||
type: 'edgeCutEdge'
|
|
||||||
edgeCutId: ArtifactId
|
|
||||||
surfaceId: ArtifactId
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Artifact =
|
|
||||||
| PlaneArtifact
|
|
||||||
| PathArtifact
|
|
||||||
| SegmentArtifact
|
|
||||||
| SweepArtifact
|
|
||||||
| WallArtifact
|
|
||||||
| CapArtifact
|
|
||||||
| SweepEdge
|
|
||||||
| EdgeCut
|
|
||||||
| EdgeCutEdge
|
|
||||||
| solid2D
|
|
||||||
|
|
||||||
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
|
||||||
|
|
||||||
export type EngineCommand = Models['WebSocketRequest_type']
|
export type EngineCommand = Models['WebSocketRequest_type']
|
||||||
|
|
||||||
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
type OkWebSocketResponseData = Models['OkWebSocketResponseData_type']
|
||||||
@ -152,437 +73,6 @@ export interface ResponseMap {
|
|||||||
[commandId: string]: OkWebSocketResponseData
|
[commandId: string]: OkWebSocketResponseData
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 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
|
|
||||||
* should return data on how to update the map, and not do so directly.
|
|
||||||
*/
|
|
||||||
export function createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
}: {
|
|
||||||
artifactCommands: Array<ArtifactCommand>
|
|
||||||
responseMap: ResponseMap
|
|
||||||
ast: Node<Program>
|
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}) {
|
|
||||||
const myMap = new Map<ArtifactId, Artifact>()
|
|
||||||
|
|
||||||
/** see docstring for {@link getArtifactsToUpdate} as to why this is needed */
|
|
||||||
let currentPlaneId = ''
|
|
||||||
|
|
||||||
for (const artifactCommand of artifactCommands) {
|
|
||||||
if (artifactCommand.command.type === 'enable_sketch_mode') {
|
|
||||||
currentPlaneId = artifactCommand.command.entity_id
|
|
||||||
}
|
|
||||||
if (artifactCommand.command.type === 'sketch_mode_disable') {
|
|
||||||
currentPlaneId = ''
|
|
||||||
}
|
|
||||||
const artifactsToUpdate = getArtifactsToUpdate({
|
|
||||||
artifactCommand,
|
|
||||||
responseMap,
|
|
||||||
getArtifact: (id: ArtifactId) => myMap.get(id),
|
|
||||||
currentPlaneId,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
})
|
|
||||||
artifactsToUpdate.forEach(({ id, artifact }) => {
|
|
||||||
const mergedArtifact = mergeArtifacts(myMap.get(id), artifact)
|
|
||||||
myMap.set(id, mergedArtifact)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return myMap
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Merges two artifacts, since our artifacts only contain strings and arrays of string for values we coerce that
|
|
||||||
* but maybe types can be improved here.
|
|
||||||
*/
|
|
||||||
function mergeArtifacts(
|
|
||||||
oldArtifact: Artifact | undefined,
|
|
||||||
newArtifact: Artifact
|
|
||||||
): Artifact {
|
|
||||||
// only has string and array of strings
|
|
||||||
interface GenericArtifact {
|
|
||||||
[key: string]: string | Array<string>
|
|
||||||
}
|
|
||||||
if (!oldArtifact) return newArtifact
|
|
||||||
// merging artifacts of different types should never happen, but if it does, just return the new artifact
|
|
||||||
if (oldArtifact.type !== newArtifact.type) return newArtifact
|
|
||||||
const _oldArtifact = oldArtifact as any as GenericArtifact
|
|
||||||
const mergedArtifact = { ...oldArtifact, ...newArtifact } as GenericArtifact
|
|
||||||
Object.entries(newArtifact as any as GenericArtifact).forEach(
|
|
||||||
([propName, value]) => {
|
|
||||||
const otherValue = _oldArtifact[propName]
|
|
||||||
if (Array.isArray(value) && Array.isArray(otherValue)) {
|
|
||||||
mergedArtifact[propName] = [...new Set([...otherValue, ...value])]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return mergedArtifact as any as Artifact
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Processes a single command and it's response in order to populate the artifact map
|
|
||||||
* It does not mutate the map directly, but returns an array of artifacts to update
|
|
||||||
*
|
|
||||||
* @param currentPlaneId is only needed for `start_path` commands because this command does not have a pathId
|
|
||||||
* instead it relies on the id used with the `enable_sketch_mode` command, so this much be kept track of
|
|
||||||
* outside of this function. It would be good to update the `start_path` command to include the planeId so we
|
|
||||||
* can remove this.
|
|
||||||
*/
|
|
||||||
export function getArtifactsToUpdate({
|
|
||||||
artifactCommand,
|
|
||||||
getArtifact,
|
|
||||||
responseMap,
|
|
||||||
currentPlaneId,
|
|
||||||
ast,
|
|
||||||
execStateArtifacts,
|
|
||||||
}: {
|
|
||||||
artifactCommand: ArtifactCommand
|
|
||||||
responseMap: ResponseMap
|
|
||||||
/** Passing in a getter because we don't wan this function to update the map directly */
|
|
||||||
getArtifact: (id: ArtifactId) => Artifact | undefined
|
|
||||||
currentPlaneId: ArtifactId
|
|
||||||
ast: Node<Program>
|
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
}): Array<{
|
|
||||||
id: ArtifactId
|
|
||||||
artifact: Artifact
|
|
||||||
}> {
|
|
||||||
const range = sourceRangeFromRust(artifactCommand.range)
|
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, range)
|
|
||||||
|
|
||||||
const id = artifactCommand.cmdId
|
|
||||||
const response = responseMap[id]
|
|
||||||
const cmd = artifactCommand.command
|
|
||||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
|
||||||
if (!response) return returnArr
|
|
||||||
if (cmd.type === 'make_plane' && range[1] !== 0) {
|
|
||||||
// If we're calling `make_plane` and the code range doesn't end at `0`
|
|
||||||
// it's not a default plane, but a custom one from the offsetPlane standard library function
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'plane',
|
|
||||||
id,
|
|
||||||
pathIds: [],
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else if (cmd.type === 'enable_sketch_mode') {
|
|
||||||
const plane = getArtifact(currentPlaneId)
|
|
||||||
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
|
||||||
const codeRef =
|
|
||||||
plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode }
|
|
||||||
const existingPlane = getArtifact(currentPlaneId)
|
|
||||||
if (existingPlane?.type === 'wall') {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: currentPlaneId,
|
|
||||||
artifact: {
|
|
||||||
type: 'wall',
|
|
||||||
id: currentPlaneId,
|
|
||||||
segId: existingPlane.segId,
|
|
||||||
edgeCutEdgeIds: existingPlane.edgeCutEdgeIds,
|
|
||||||
sweepId: existingPlane.sweepId,
|
|
||||||
pathIds: existingPlane.pathIds,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: currentPlaneId,
|
|
||||||
artifact: { type: 'plane', id: currentPlaneId, pathIds, codeRef },
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
} else if (cmd.type === 'start_path') {
|
|
||||||
returnArr.push({
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'path',
|
|
||||||
id,
|
|
||||||
segIds: [],
|
|
||||||
planeId: currentPlaneId,
|
|
||||||
sweepId: undefined,
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const plane = getArtifact(currentPlaneId)
|
|
||||||
const codeRef =
|
|
||||||
plane?.type === 'plane' ? plane?.codeRef : { range, pathToNode }
|
|
||||||
if (plane?.type === 'plane') {
|
|
||||||
returnArr.push({
|
|
||||||
id: currentPlaneId,
|
|
||||||
artifact: { type: 'plane', id: currentPlaneId, pathIds: [id], codeRef },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (plane?.type === 'wall') {
|
|
||||||
returnArr.push({
|
|
||||||
id: currentPlaneId,
|
|
||||||
artifact: {
|
|
||||||
type: 'wall',
|
|
||||||
id: currentPlaneId,
|
|
||||||
segId: plane.segId,
|
|
||||||
edgeCutEdgeIds: plane.edgeCutEdgeIds,
|
|
||||||
sweepId: plane.sweepId,
|
|
||||||
pathIds: [id],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return returnArr
|
|
||||||
} else if (cmd.type === 'extend_path' || cmd.type === 'close_path') {
|
|
||||||
const pathId = cmd.type === 'extend_path' ? cmd.path : cmd.path_id
|
|
||||||
returnArr.push({
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'segment',
|
|
||||||
id,
|
|
||||||
pathId,
|
|
||||||
surfaceId: undefined,
|
|
||||||
edgeIds: [],
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const path = getArtifact(pathId)
|
|
||||||
if (path?.type === 'path')
|
|
||||||
returnArr.push({
|
|
||||||
id: pathId,
|
|
||||||
artifact: { ...path, segIds: [id] },
|
|
||||||
})
|
|
||||||
if (
|
|
||||||
response?.type === 'modeling' &&
|
|
||||||
response.data.modeling_response.type === 'close_path'
|
|
||||||
) {
|
|
||||||
returnArr.push({
|
|
||||||
id: response.data.modeling_response.data.face_id,
|
|
||||||
artifact: {
|
|
||||||
type: 'solid2D',
|
|
||||||
id: response.data.modeling_response.data.face_id,
|
|
||||||
pathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const path = getArtifact(pathId)
|
|
||||||
if (path?.type === 'path')
|
|
||||||
returnArr.push({
|
|
||||||
id: pathId,
|
|
||||||
artifact: {
|
|
||||||
...path,
|
|
||||||
solid2dId: response.data.modeling_response.data.face_id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return returnArr
|
|
||||||
} else if (
|
|
||||||
cmd.type === 'extrude' ||
|
|
||||||
cmd.type === 'revolve' ||
|
|
||||||
cmd.type === 'sweep'
|
|
||||||
) {
|
|
||||||
const subType = cmd.type === 'extrude' ? 'extrusion' : cmd.type
|
|
||||||
returnArr.push({
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'sweep',
|
|
||||||
subType: subType,
|
|
||||||
id,
|
|
||||||
pathId: cmd.target,
|
|
||||||
surfaceIds: [],
|
|
||||||
edgeIds: [],
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const path = getArtifact(cmd.target)
|
|
||||||
if (path?.type === 'path')
|
|
||||||
returnArr.push({
|
|
||||||
id: cmd.target,
|
|
||||||
artifact: { ...path, sweepId: id },
|
|
||||||
})
|
|
||||||
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 (
|
|
||||||
cmd.type === 'solid3d_get_extrusion_face_info' &&
|
|
||||||
response?.type === 'modeling' &&
|
|
||||||
response.data.modeling_response.type === 'solid3d_get_extrusion_face_info'
|
|
||||||
) {
|
|
||||||
let lastPath: PathArtifact
|
|
||||||
response.data.modeling_response.data.faces.forEach(
|
|
||||||
({ curve_id, cap, face_id }) => {
|
|
||||||
if (cap === 'none' && curve_id && face_id) {
|
|
||||||
const seg = getArtifact(curve_id)
|
|
||||||
if (seg?.type !== 'segment') return
|
|
||||||
const path = getArtifact(seg.pathId)
|
|
||||||
if (path?.type === 'path' && seg?.type === 'segment') {
|
|
||||||
lastPath = path
|
|
||||||
returnArr.push({
|
|
||||||
id: face_id,
|
|
||||||
artifact: {
|
|
||||||
type: 'wall',
|
|
||||||
id: face_id,
|
|
||||||
segId: curve_id,
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
// TODO: Add explicit check for sweepId. Should never use ''
|
|
||||||
sweepId: path.sweepId ?? '',
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
returnArr.push({
|
|
||||||
id: curve_id,
|
|
||||||
artifact: { ...seg, surfaceId: face_id },
|
|
||||||
})
|
|
||||||
if (path.sweepId) {
|
|
||||||
const sweep = getArtifact(path.sweepId)
|
|
||||||
if (sweep?.type === 'sweep') {
|
|
||||||
returnArr.push({
|
|
||||||
id: path.sweepId,
|
|
||||||
artifact: {
|
|
||||||
...sweep,
|
|
||||||
surfaceIds: [face_id],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
response.data.modeling_response.data.faces.forEach(({ cap, face_id }) => {
|
|
||||||
if ((cap === 'top' || cap === 'bottom') && face_id) {
|
|
||||||
const path = lastPath
|
|
||||||
if (path?.type === 'path') {
|
|
||||||
returnArr.push({
|
|
||||||
id: face_id,
|
|
||||||
artifact: {
|
|
||||||
type: 'cap',
|
|
||||||
id: face_id,
|
|
||||||
subType: cap === 'bottom' ? 'start' : 'end',
|
|
||||||
edgeCutEdgeIds: [],
|
|
||||||
// TODO: Add explicit check for sweepId. Should never use ''
|
|
||||||
sweepId: path.sweepId ?? '',
|
|
||||||
pathIds: [],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (path.sweepId) {
|
|
||||||
const sweep = getArtifact(path.sweepId)
|
|
||||||
if (sweep?.type !== 'sweep') return
|
|
||||||
returnArr.push({
|
|
||||||
id: path.sweepId,
|
|
||||||
artifact: {
|
|
||||||
...sweep,
|
|
||||||
surfaceIds: [face_id],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return returnArr
|
|
||||||
} else if (
|
|
||||||
// is opposite edge
|
|
||||||
(cmd.type === 'solid3d_get_opposite_edge' &&
|
|
||||||
response.type === 'modeling' &&
|
|
||||||
response.data.modeling_response.type === 'solid3d_get_opposite_edge' &&
|
|
||||||
response.data.modeling_response.data.edge) ||
|
|
||||||
// or is adjacent edge
|
|
||||||
(cmd.type === 'solid3d_get_next_adjacent_edge' &&
|
|
||||||
response.type === 'modeling' &&
|
|
||||||
response.data.modeling_response.type ===
|
|
||||||
'solid3d_get_next_adjacent_edge' &&
|
|
||||||
response.data.modeling_response.data.edge)
|
|
||||||
) {
|
|
||||||
const wall = getArtifact(cmd.face_id)
|
|
||||||
if (wall?.type !== 'wall') return returnArr
|
|
||||||
const sweep = getArtifact(wall.sweepId)
|
|
||||||
if (sweep?.type !== 'sweep') return returnArr
|
|
||||||
const path = getArtifact(sweep.pathId)
|
|
||||||
if (path?.type !== 'path') return returnArr
|
|
||||||
const segment = getArtifact(cmd.edge_id)
|
|
||||||
if (segment?.type !== 'segment') return returnArr
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
id: response.data.modeling_response.data.edge,
|
|
||||||
artifact: {
|
|
||||||
type: 'sweepEdge',
|
|
||||||
id: response.data.modeling_response.data.edge,
|
|
||||||
subType:
|
|
||||||
cmd.type === 'solid3d_get_next_adjacent_edge'
|
|
||||||
? 'adjacent'
|
|
||||||
: 'opposite',
|
|
||||||
segId: cmd.edge_id,
|
|
||||||
// TODO: Add explicit check for sweepId. Should never use ''
|
|
||||||
sweepId: path.sweepId ?? '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: cmd.edge_id,
|
|
||||||
artifact: {
|
|
||||||
...segment,
|
|
||||||
edgeIds: [response.data.modeling_response.data.edge],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: sweep.id,
|
|
||||||
artifact: {
|
|
||||||
...sweep,
|
|
||||||
edgeIds: [response.data.modeling_response.data.edge],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else if (cmd.type === 'solid3d_fillet_edge') {
|
|
||||||
returnArr.push({
|
|
||||||
id,
|
|
||||||
artifact: {
|
|
||||||
type: 'edgeCut',
|
|
||||||
id,
|
|
||||||
subType: cmd.cut_type,
|
|
||||||
consumedEdgeId: cmd.edge_id,
|
|
||||||
edgeIds: [],
|
|
||||||
surfaceId: undefined,
|
|
||||||
codeRef: { range, pathToNode },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const consumedEdge = getArtifact(cmd.edge_id)
|
|
||||||
if (consumedEdge?.type === 'segment') {
|
|
||||||
returnArr.push({
|
|
||||||
id: cmd.edge_id,
|
|
||||||
artifact: { ...consumedEdge, edgeCutId: id },
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return returnArr
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
/** filter map items of a specific type */
|
/** filter map items of a specific type */
|
||||||
export function filterArtifacts<T extends Artifact['type'][]>(
|
export function filterArtifacts<T extends Artifact['type'][]>(
|
||||||
{
|
{
|
||||||
@ -676,7 +166,7 @@ export function expandPath(
|
|||||||
},
|
},
|
||||||
artifactGraph
|
artifactGraph
|
||||||
)
|
)
|
||||||
: undefined
|
: null
|
||||||
const plane = getArtifactOfTypes(
|
const plane = getArtifactOfTypes(
|
||||||
{ key: path.planeId, types: ['plane', 'wall'] },
|
{ key: path.planeId, types: ['plane', 'wall'] },
|
||||||
artifactGraph
|
artifactGraph
|
||||||
@ -778,11 +268,11 @@ export function getCapCodeRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getSolid2dCodeRef(
|
export function getSolid2dCodeRef(
|
||||||
solid2D: solid2D,
|
solid2d: Solid2D,
|
||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
): CodeRef | Error {
|
): CodeRef | Error {
|
||||||
const path = getArtifactOfTypes(
|
const path = getArtifactOfTypes(
|
||||||
{ key: solid2D.pathId, types: ['path'] },
|
{ key: solid2d.pathId, types: ['path'] },
|
||||||
artifactGraph
|
artifactGraph
|
||||||
)
|
)
|
||||||
if (err(path)) return path
|
if (err(path)) return path
|
||||||
@ -881,7 +371,7 @@ export function getCodeRefsByArtifactId(
|
|||||||
artifactGraph: ArtifactGraph
|
artifactGraph: ArtifactGraph
|
||||||
): Array<CodeRef> | null {
|
): Array<CodeRef> | null {
|
||||||
const artifact = artifactGraph.get(id)
|
const artifact = artifactGraph.get(id)
|
||||||
if (artifact?.type === 'solid2D') {
|
if (artifact?.type === 'solid2d') {
|
||||||
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
|
const codeRef = getSolid2dCodeRef(artifact, artifactGraph)
|
||||||
if (err(codeRef)) return null
|
if (err(codeRef)) return null
|
||||||
return [codeRef]
|
return [codeRef]
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
ArtifactCommand,
|
ArtifactGraph,
|
||||||
defaultRustSourceRange,
|
defaultSourceRange,
|
||||||
ExecState,
|
ExecState,
|
||||||
Program,
|
|
||||||
RustSourceRange,
|
|
||||||
SourceRange,
|
SourceRange,
|
||||||
} 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'
|
||||||
@ -17,12 +15,7 @@ import {
|
|||||||
darkModeMatcher,
|
darkModeMatcher,
|
||||||
} from 'lib/theme'
|
} from 'lib/theme'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import {
|
import { EngineCommand, ResponseMap } from 'lang/std/artifactGraph'
|
||||||
ArtifactGraph,
|
|
||||||
EngineCommand,
|
|
||||||
ResponseMap,
|
|
||||||
createArtifactGraph,
|
|
||||||
} from 'lang/std/artifactGraph'
|
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { exportMake } from 'lib/exportMake'
|
import { exportMake } from 'lib/exportMake'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
@ -36,7 +29,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
|
||||||
@ -1022,6 +1014,11 @@ class EngineConnection extends EventTarget {
|
|||||||
this.pingPongSpan.pong = new Date()
|
this.pingPongSpan.pong = new Date()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'modeling_session_data':
|
||||||
|
let api_call_id = resp.data?.session?.api_call_id
|
||||||
|
console.log(`API Call ID: ${api_call_id}`)
|
||||||
|
break
|
||||||
|
|
||||||
// Only fires on successful authentication.
|
// Only fires on successful authentication.
|
||||||
case 'ice_server_info':
|
case 'ice_server_info':
|
||||||
let ice_servers = resp.data?.ice_servers
|
let ice_servers = resp.data?.ice_servers
|
||||||
@ -1309,8 +1306,8 @@ export enum EngineCommandManagerEvents {
|
|||||||
|
|
||||||
interface PendingMessage {
|
interface PendingMessage {
|
||||||
command: EngineCommand
|
command: EngineCommand
|
||||||
range: RustSourceRange
|
range: SourceRange
|
||||||
idToRangeMap: { [key: string]: RustSourceRange }
|
idToRangeMap: { [key: string]: SourceRange }
|
||||||
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
resolve: (data: [Models['WebSocketResponse_type']]) => void
|
||||||
reject: (reason: string) => void
|
reject: (reason: string) => void
|
||||||
promise: Promise<[Models['WebSocketResponse_type']]>
|
promise: Promise<[Models['WebSocketResponse_type']]>
|
||||||
@ -1994,7 +1991,7 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
{
|
{
|
||||||
command,
|
command,
|
||||||
idToRangeMap: {},
|
idToRangeMap: {},
|
||||||
range: defaultRustSourceRange(),
|
range: defaultSourceRange(),
|
||||||
},
|
},
|
||||||
true // isSceneCommand
|
true // isSceneCommand
|
||||||
)
|
)
|
||||||
@ -2025,9 +2022,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
return Promise.reject(new Error('rangeStr is undefined'))
|
return Promise.reject(new Error('rangeStr is undefined'))
|
||||||
if (commandStr === undefined)
|
if (commandStr === undefined)
|
||||||
return Promise.reject(new Error('commandStr is undefined'))
|
return Promise.reject(new Error('commandStr is undefined'))
|
||||||
const range: RustSourceRange = JSON.parse(rangeStr)
|
const range: SourceRange = JSON.parse(rangeStr)
|
||||||
const command: EngineCommand = JSON.parse(commandStr)
|
const command: EngineCommand = JSON.parse(commandStr)
|
||||||
const idToRangeMap: { [key: string]: RustSourceRange } =
|
const idToRangeMap: { [key: string]: SourceRange } =
|
||||||
JSON.parse(idToRangeStr)
|
JSON.parse(idToRangeStr)
|
||||||
|
|
||||||
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
// Current executeAst is stale, going to interrupt, a new executeAst will trigger
|
||||||
@ -2087,17 +2084,8 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
Object.values(this.pendingCommands).map((a) => a.promise)
|
Object.values(this.pendingCommands).map((a) => a.promise)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
updateArtifactGraph(
|
updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) {
|
||||||
ast: Node<Program>,
|
this.artifactGraph = execStateArtifactGraph
|
||||||
artifactCommands: ArtifactCommand[],
|
|
||||||
execStateArtifacts: ExecState['artifacts']
|
|
||||||
) {
|
|
||||||
this.artifactGraph = createArtifactGraph({
|
|
||||||
artifactCommands,
|
|
||||||
responseMap: this.responseMap,
|
|
||||||
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) {
|
||||||
this.deferredArtifactEmptied(null)
|
this.deferredArtifactEmptied(null)
|
||||||
|
|||||||
@ -11,8 +11,8 @@ import {
|
|||||||
assertParse,
|
assertParse,
|
||||||
recast,
|
recast,
|
||||||
initPromise,
|
initPromise,
|
||||||
SourceRange,
|
|
||||||
CallExpression,
|
CallExpression,
|
||||||
|
topLevelRange,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from '../queryAst'
|
||||||
import { enginelessExecutor } from '../../lib/testHelpers'
|
import { enginelessExecutor } from '../../lib/testHelpers'
|
||||||
@ -124,7 +124,10 @@ describe('testing changeSketchArguments', () => {
|
|||||||
execState.memory,
|
execState.memory,
|
||||||
{
|
{
|
||||||
type: 'sourceRange',
|
type: 'sourceRange',
|
||||||
sourceRange: [sourceStart, sourceStart + lineToChange.length, true],
|
sourceRange: topLevelRange(
|
||||||
|
sourceStart,
|
||||||
|
sourceStart + lineToChange.length
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'straight-segment',
|
type: 'straight-segment',
|
||||||
@ -219,11 +222,10 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
const ast = assertParse(code)
|
const ast = assertParse(code)
|
||||||
await enginelessExecutor(ast)
|
await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(originalLine)
|
const sourceStart = code.indexOf(originalLine)
|
||||||
const sourceRange: [number, number, boolean] = [
|
const sourceRange = topLevelRange(
|
||||||
sourceStart,
|
sourceStart,
|
||||||
sourceStart + originalLine.length,
|
sourceStart + originalLine.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
if (err(ast)) return ast
|
if (err(ast)) return ast
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
const sketchOnFaceRetVal = addTagForSketchOnFace(
|
||||||
@ -292,11 +294,10 @@ ${insertCode}
|
|||||||
await enginelessExecutor(ast)
|
await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(originalChamfer)
|
const sourceStart = code.indexOf(originalChamfer)
|
||||||
const extraChars = originalChamfer.indexOf('chamfer')
|
const extraChars = originalChamfer.indexOf('chamfer')
|
||||||
const sourceRange: [number, number, boolean] = [
|
const sourceRange = topLevelRange(
|
||||||
sourceStart + extraChars,
|
sourceStart + extraChars,
|
||||||
sourceStart + originalChamfer.length - extraChars,
|
sourceStart + originalChamfer.length - extraChars
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
|
|
||||||
if (err(ast)) throw ast
|
if (err(ast)) throw ast
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
@ -357,7 +358,6 @@ describe('testing getConstraintInfo', () => {
|
|||||||
offset = 0
|
offset = 0
|
||||||
}, %)
|
}, %)
|
||||||
|> tangentialArcTo([3.14, 13.14], %)`
|
|> tangentialArcTo([3.14, 13.14], %)`
|
||||||
const ast = assertParse(code)
|
|
||||||
test.each([
|
test.each([
|
||||||
[
|
[
|
||||||
'line',
|
'line',
|
||||||
@ -366,7 +366,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3',
|
value: '3',
|
||||||
sourceRange: [78, 79, true],
|
sourceRange: topLevelRange(78, 79),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'line',
|
stdLibFnName: 'line',
|
||||||
@ -375,7 +375,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '4',
|
value: '4',
|
||||||
sourceRange: [81, 82, true],
|
sourceRange: topLevelRange(81, 82),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'line',
|
stdLibFnName: 'line',
|
||||||
@ -389,7 +389,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [118, 122, true],
|
sourceRange: topLevelRange(118, 122),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -398,7 +398,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'length',
|
type: 'length',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [137, 141, true],
|
sourceRange: topLevelRange(137, 141),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -412,7 +412,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '6.14',
|
value: '6.14',
|
||||||
sourceRange: [164, 168, true],
|
sourceRange: topLevelRange(164, 168),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'lineTo',
|
stdLibFnName: 'lineTo',
|
||||||
@ -421,7 +421,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [170, 174, true],
|
sourceRange: topLevelRange(170, 174),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'lineTo',
|
stdLibFnName: 'lineTo',
|
||||||
@ -435,7 +435,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'horizontal',
|
type: 'horizontal',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'xLineTo',
|
value: 'xLineTo',
|
||||||
sourceRange: [185, 192, true],
|
sourceRange: topLevelRange(185, 192),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLineTo',
|
stdLibFnName: 'xLineTo',
|
||||||
@ -444,7 +444,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '8',
|
value: '8',
|
||||||
sourceRange: [193, 194, true],
|
sourceRange: topLevelRange(193, 194),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLineTo',
|
stdLibFnName: 'xLineTo',
|
||||||
@ -458,7 +458,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'yLineTo',
|
value: 'yLineTo',
|
||||||
sourceRange: [204, 211, true],
|
sourceRange: topLevelRange(204, 211),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLineTo',
|
stdLibFnName: 'yLineTo',
|
||||||
@ -467,7 +467,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '5',
|
value: '5',
|
||||||
sourceRange: [212, 213, true],
|
sourceRange: topLevelRange(212, 213),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLineTo',
|
stdLibFnName: 'yLineTo',
|
||||||
@ -481,7 +481,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'yLine',
|
value: 'yLine',
|
||||||
sourceRange: [223, 228, true],
|
sourceRange: topLevelRange(223, 228),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLine',
|
stdLibFnName: 'yLine',
|
||||||
@ -490,7 +490,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [229, 233, true],
|
sourceRange: topLevelRange(229, 233),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLine',
|
stdLibFnName: 'yLine',
|
||||||
@ -504,7 +504,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'horizontal',
|
type: 'horizontal',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'xLine',
|
value: 'xLine',
|
||||||
sourceRange: [247, 252, true],
|
sourceRange: topLevelRange(247, 252),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLine',
|
stdLibFnName: 'xLine',
|
||||||
@ -513,7 +513,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [253, 257, true],
|
sourceRange: topLevelRange(253, 257),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLine',
|
stdLibFnName: 'xLine',
|
||||||
@ -527,7 +527,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [301, 305, true],
|
sourceRange: topLevelRange(301, 305),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -536,7 +536,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [320, 324, true],
|
sourceRange: topLevelRange(320, 324),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -550,7 +550,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '30',
|
value: '30',
|
||||||
sourceRange: [373, 375, true],
|
sourceRange: topLevelRange(373, 375),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -559,7 +559,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3',
|
value: '3',
|
||||||
sourceRange: [390, 391, true],
|
sourceRange: topLevelRange(390, 391),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -573,7 +573,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '12.14',
|
value: '12.14',
|
||||||
sourceRange: [434, 439, true],
|
sourceRange: topLevelRange(434, 439),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -582,7 +582,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '12',
|
value: '12',
|
||||||
sourceRange: [450, 452, true],
|
sourceRange: topLevelRange(450, 452),
|
||||||
argPosition: { type: 'objectProperty', key: 'to' },
|
argPosition: { type: 'objectProperty', key: 'to' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -596,7 +596,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '30',
|
value: '30',
|
||||||
sourceRange: [495, 497, true],
|
sourceRange: topLevelRange(495, 497),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -605,7 +605,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '10.14',
|
value: '10.14',
|
||||||
sourceRange: [508, 513, true],
|
sourceRange: topLevelRange(508, 513),
|
||||||
argPosition: { type: 'objectProperty', key: 'to' },
|
argPosition: { type: 'objectProperty', key: 'to' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -619,7 +619,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [567, 571, true],
|
sourceRange: topLevelRange(567, 571),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineThatIntersects',
|
stdLibFnName: 'angledLineThatIntersects',
|
||||||
@ -628,7 +628,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'intersectionOffset',
|
type: 'intersectionOffset',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '0',
|
value: '0',
|
||||||
sourceRange: [608, 609, true],
|
sourceRange: topLevelRange(608, 609),
|
||||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineThatIntersects',
|
stdLibFnName: 'angledLineThatIntersects',
|
||||||
@ -637,7 +637,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'intersectionTag',
|
type: 'intersectionTag',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: 'a',
|
value: 'a',
|
||||||
sourceRange: [592, 593, true],
|
sourceRange: topLevelRange(592, 593),
|
||||||
argPosition: {
|
argPosition: {
|
||||||
key: 'intersectTag',
|
key: 'intersectTag',
|
||||||
type: 'objectProperty',
|
type: 'objectProperty',
|
||||||
@ -654,7 +654,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'tangentialWithPrevious',
|
type: 'tangentialWithPrevious',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'tangentialArcTo',
|
value: 'tangentialArcTo',
|
||||||
sourceRange: [623, 638, true],
|
sourceRange: topLevelRange(623, 638),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -663,7 +663,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [640, 644, true],
|
sourceRange: topLevelRange(640, 644),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -672,7 +672,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '13.14',
|
value: '13.14',
|
||||||
sourceRange: [646, 651, true],
|
sourceRange: topLevelRange(646, 651),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -680,11 +680,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||||
const sourceRange: SourceRange = [
|
const ast = assertParse(code)
|
||||||
|
const sourceRange = topLevelRange(
|
||||||
code.indexOf(functionName),
|
code.indexOf(functionName),
|
||||||
code.indexOf(functionName) + functionName.length,
|
code.indexOf(functionName) + functionName.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
if (err(ast)) return ast
|
if (err(ast)) return ast
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||||
@ -717,7 +717,6 @@ describe('testing getConstraintInfo', () => {
|
|||||||
offset = 0
|
offset = 0
|
||||||
}, %)
|
}, %)
|
||||||
|> tangentialArcTo([3.14, 13.14], %)`
|
|> tangentialArcTo([3.14, 13.14], %)`
|
||||||
const ast = assertParse(code)
|
|
||||||
test.each([
|
test.each([
|
||||||
[
|
[
|
||||||
`angledLine(`,
|
`angledLine(`,
|
||||||
@ -726,7 +725,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [112, 116, true],
|
sourceRange: topLevelRange(112, 116),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -735,7 +734,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'length',
|
type: 'length',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [118, 122, true],
|
sourceRange: topLevelRange(118, 122),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -749,7 +748,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [277, 281, true],
|
sourceRange: topLevelRange(277, 281),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -758,7 +757,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3.14',
|
value: '3.14',
|
||||||
sourceRange: [283, 287, true],
|
sourceRange: topLevelRange(283, 287),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -772,7 +771,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '30',
|
value: '30',
|
||||||
sourceRange: [321, 323, true],
|
sourceRange: topLevelRange(321, 323),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -781,7 +780,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '3',
|
value: '3',
|
||||||
sourceRange: [325, 326, true],
|
sourceRange: topLevelRange(325, 326),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -795,7 +794,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '12',
|
value: '12',
|
||||||
sourceRange: [354, 356, true],
|
sourceRange: topLevelRange(354, 356),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -804,7 +803,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '12',
|
value: '12',
|
||||||
sourceRange: [358, 360, true],
|
sourceRange: topLevelRange(358, 360),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -818,7 +817,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '30',
|
value: '30',
|
||||||
sourceRange: [388, 390, true],
|
sourceRange: topLevelRange(388, 390),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -827,7 +826,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: '10',
|
value: '10',
|
||||||
sourceRange: [392, 394, true],
|
sourceRange: topLevelRange(392, 394),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -835,11 +834,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||||
const sourceRange: SourceRange = [
|
const ast = assertParse(code)
|
||||||
|
const sourceRange = topLevelRange(
|
||||||
code.indexOf(functionName),
|
code.indexOf(functionName),
|
||||||
code.indexOf(functionName) + functionName.length,
|
code.indexOf(functionName) + functionName.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
if (err(ast)) return ast
|
if (err(ast)) return ast
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||||
@ -872,7 +871,6 @@ describe('testing getConstraintInfo', () => {
|
|||||||
offset = 0 + 0
|
offset = 0 + 0
|
||||||
}, %)
|
}, %)
|
||||||
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
|
|> tangentialArcTo([3.14 + 0, 13.14 + 0], %)`
|
||||||
const ast = assertParse(code)
|
|
||||||
test.each([
|
test.each([
|
||||||
[
|
[
|
||||||
'line',
|
'line',
|
||||||
@ -881,7 +879,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3 + 0',
|
value: '3 + 0',
|
||||||
sourceRange: [83, 88, true],
|
sourceRange: topLevelRange(83, 88),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'line',
|
stdLibFnName: 'line',
|
||||||
@ -890,7 +888,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '4 + 0',
|
value: '4 + 0',
|
||||||
sourceRange: [90, 95, true],
|
sourceRange: topLevelRange(90, 95),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'line',
|
stdLibFnName: 'line',
|
||||||
@ -904,7 +902,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [129, 137, true],
|
sourceRange: topLevelRange(129, 137),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -913,7 +911,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'length',
|
type: 'length',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [148, 156, true],
|
sourceRange: topLevelRange(148, 156),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
@ -927,7 +925,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '6.14 + 0',
|
value: '6.14 + 0',
|
||||||
sourceRange: [178, 186, true],
|
sourceRange: topLevelRange(178, 186),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'lineTo',
|
stdLibFnName: 'lineTo',
|
||||||
@ -936,7 +934,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [188, 196, true],
|
sourceRange: topLevelRange(188, 196),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'lineTo',
|
stdLibFnName: 'lineTo',
|
||||||
@ -950,7 +948,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'horizontal',
|
type: 'horizontal',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'xLineTo',
|
value: 'xLineTo',
|
||||||
sourceRange: [209, 216, true],
|
sourceRange: topLevelRange(209, 216),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLineTo',
|
stdLibFnName: 'xLineTo',
|
||||||
@ -959,7 +957,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '8 + 0',
|
value: '8 + 0',
|
||||||
sourceRange: [217, 222, true],
|
sourceRange: topLevelRange(217, 222),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLineTo',
|
stdLibFnName: 'xLineTo',
|
||||||
@ -973,7 +971,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'yLineTo',
|
value: 'yLineTo',
|
||||||
sourceRange: [234, 241, true],
|
sourceRange: topLevelRange(234, 241),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLineTo',
|
stdLibFnName: 'yLineTo',
|
||||||
@ -982,7 +980,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '5 + 0',
|
value: '5 + 0',
|
||||||
sourceRange: [242, 247, true],
|
sourceRange: topLevelRange(242, 247),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLineTo',
|
stdLibFnName: 'yLineTo',
|
||||||
@ -996,7 +994,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'vertical',
|
type: 'vertical',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'yLine',
|
value: 'yLine',
|
||||||
sourceRange: [259, 264, true],
|
sourceRange: topLevelRange(259, 264),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLine',
|
stdLibFnName: 'yLine',
|
||||||
@ -1005,7 +1003,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [265, 273, true],
|
sourceRange: topLevelRange(265, 273),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'yLine',
|
stdLibFnName: 'yLine',
|
||||||
@ -1019,7 +1017,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'horizontal',
|
type: 'horizontal',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'xLine',
|
value: 'xLine',
|
||||||
sourceRange: [289, 294, true],
|
sourceRange: topLevelRange(289, 294),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLine',
|
stdLibFnName: 'xLine',
|
||||||
@ -1028,7 +1026,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [295, 303, true],
|
sourceRange: topLevelRange(295, 303),
|
||||||
argPosition: { type: 'singleValue' },
|
argPosition: { type: 'singleValue' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'xLine',
|
stdLibFnName: 'xLine',
|
||||||
@ -1042,7 +1040,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [345, 353, true],
|
sourceRange: topLevelRange(345, 353),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -1051,7 +1049,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xRelative',
|
type: 'xRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [364, 372, true],
|
sourceRange: topLevelRange(364, 372),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
@ -1065,7 +1063,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '30 + 0',
|
value: '30 + 0',
|
||||||
sourceRange: [416, 422, true],
|
sourceRange: topLevelRange(416, 422),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -1074,7 +1072,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yRelative',
|
type: 'yRelative',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3 + 0',
|
value: '3 + 0',
|
||||||
sourceRange: [433, 438, true],
|
sourceRange: topLevelRange(433, 438),
|
||||||
argPosition: { type: 'objectProperty', key: 'length' },
|
argPosition: { type: 'objectProperty', key: 'length' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
@ -1088,7 +1086,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '12.14 + 0',
|
value: '12.14 + 0',
|
||||||
sourceRange: [476, 485, true],
|
sourceRange: topLevelRange(476, 485),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -1097,7 +1095,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '12 + 0',
|
value: '12 + 0',
|
||||||
sourceRange: [492, 498, true],
|
sourceRange: topLevelRange(492, 498),
|
||||||
argPosition: { type: 'objectProperty', key: 'to' },
|
argPosition: { type: 'objectProperty', key: 'to' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
@ -1111,7 +1109,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '30 + 0',
|
value: '30 + 0',
|
||||||
sourceRange: [536, 542, true],
|
sourceRange: topLevelRange(536, 542),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -1120,7 +1118,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '10.14 + 0',
|
value: '10.14 + 0',
|
||||||
sourceRange: [549, 558, true],
|
sourceRange: topLevelRange(549, 558),
|
||||||
argPosition: { type: 'objectProperty', key: 'to' },
|
argPosition: { type: 'objectProperty', key: 'to' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
@ -1134,7 +1132,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'angle',
|
type: 'angle',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [616, 624, true],
|
sourceRange: topLevelRange(616, 624),
|
||||||
argPosition: { type: 'objectProperty', key: 'angle' },
|
argPosition: { type: 'objectProperty', key: 'angle' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineThatIntersects',
|
stdLibFnName: 'angledLineThatIntersects',
|
||||||
@ -1143,7 +1141,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'intersectionOffset',
|
type: 'intersectionOffset',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '0 + 0',
|
value: '0 + 0',
|
||||||
sourceRange: [671, 676, true],
|
sourceRange: topLevelRange(671, 676),
|
||||||
argPosition: { type: 'objectProperty', key: 'offset' },
|
argPosition: { type: 'objectProperty', key: 'offset' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineThatIntersects',
|
stdLibFnName: 'angledLineThatIntersects',
|
||||||
@ -1152,7 +1150,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'intersectionTag',
|
type: 'intersectionTag',
|
||||||
isConstrained: false,
|
isConstrained: false,
|
||||||
value: 'a',
|
value: 'a',
|
||||||
sourceRange: [650, 651, true],
|
sourceRange: topLevelRange(650, 651),
|
||||||
argPosition: { key: 'intersectTag', type: 'objectProperty' },
|
argPosition: { key: 'intersectTag', type: 'objectProperty' },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'angledLineThatIntersects',
|
stdLibFnName: 'angledLineThatIntersects',
|
||||||
@ -1166,7 +1164,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'tangentialWithPrevious',
|
type: 'tangentialWithPrevious',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: 'tangentialArcTo',
|
value: 'tangentialArcTo',
|
||||||
sourceRange: [697, 712, true],
|
sourceRange: topLevelRange(697, 712),
|
||||||
argPosition: undefined,
|
argPosition: undefined,
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -1175,7 +1173,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '3.14 + 0',
|
value: '3.14 + 0',
|
||||||
sourceRange: [714, 722, true],
|
sourceRange: topLevelRange(714, 722),
|
||||||
argPosition: { type: 'arrayItem', index: 0 },
|
argPosition: { type: 'arrayItem', index: 0 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -1184,7 +1182,7 @@ describe('testing getConstraintInfo', () => {
|
|||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: true,
|
isConstrained: true,
|
||||||
value: '13.14 + 0',
|
value: '13.14 + 0',
|
||||||
sourceRange: [724, 733, true],
|
sourceRange: topLevelRange(724, 733),
|
||||||
argPosition: { type: 'arrayItem', index: 1 },
|
argPosition: { type: 'arrayItem', index: 1 },
|
||||||
pathToNode: expect.any(Array),
|
pathToNode: expect.any(Array),
|
||||||
stdLibFnName: 'tangentialArcTo',
|
stdLibFnName: 'tangentialArcTo',
|
||||||
@ -1192,11 +1190,11 @@ describe('testing getConstraintInfo', () => {
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
])('testing %s when inputs are unconstrained', (functionName, expected) => {
|
||||||
const sourceRange: SourceRange = [
|
const ast = assertParse(code)
|
||||||
|
const sourceRange = topLevelRange(
|
||||||
code.indexOf(functionName),
|
code.indexOf(functionName),
|
||||||
code.indexOf(functionName) + functionName.length,
|
code.indexOf(functionName) + functionName.length
|
||||||
true,
|
)
|
||||||
]
|
|
||||||
if (err(ast)) return ast
|
if (err(ast)) return ast
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
const pathToNode = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const callExp = getNodeFromPath<Node<CallExpression>>(
|
const callExp = getNodeFromPath<Node<CallExpression>>(
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
Identifier,
|
Identifier,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -222,7 +223,7 @@ const commonConstraintInfoHelper = (
|
|||||||
code.slice(input1.start, input1.end),
|
code.slice(input1.start, input1.end),
|
||||||
stdLibFnName,
|
stdLibFnName,
|
||||||
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
|
isArr ? abbreviatedInputs[0].arrayInput : abbreviatedInputs[0].objInput,
|
||||||
[input1.start, input1.end, true],
|
topLevelRange(input1.start, input1.end),
|
||||||
pathToFirstArg
|
pathToFirstArg
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -234,7 +235,7 @@ const commonConstraintInfoHelper = (
|
|||||||
code.slice(input2.start, input2.end),
|
code.slice(input2.start, input2.end),
|
||||||
stdLibFnName,
|
stdLibFnName,
|
||||||
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
|
isArr ? abbreviatedInputs[1].arrayInput : abbreviatedInputs[1].objInput,
|
||||||
[input2.start, input2.end, true],
|
topLevelRange(input2.start, input2.end),
|
||||||
pathToSecondArg
|
pathToSecondArg
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -266,7 +267,7 @@ const horzVertConstraintInfoHelper = (
|
|||||||
callee.name,
|
callee.name,
|
||||||
stdLibFnName,
|
stdLibFnName,
|
||||||
undefined,
|
undefined,
|
||||||
[callee.start, callee.end, true],
|
topLevelRange(callee.start, callee.end),
|
||||||
pathToCallee
|
pathToCallee
|
||||||
),
|
),
|
||||||
constrainInfo(
|
constrainInfo(
|
||||||
@ -275,7 +276,7 @@ const horzVertConstraintInfoHelper = (
|
|||||||
code.slice(firstArg.start, firstArg.end),
|
code.slice(firstArg.start, firstArg.end),
|
||||||
stdLibFnName,
|
stdLibFnName,
|
||||||
abbreviatedInput,
|
abbreviatedInput,
|
||||||
[firstArg.start, firstArg.end, true],
|
topLevelRange(firstArg.start, firstArg.end),
|
||||||
pathToFirstArg
|
pathToFirstArg
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -905,7 +906,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
|||||||
callee.name,
|
callee.name,
|
||||||
'tangentialArcTo',
|
'tangentialArcTo',
|
||||||
undefined,
|
undefined,
|
||||||
[callee.start, callee.end, true],
|
topLevelRange(callee.start, callee.end),
|
||||||
pathToCallee
|
pathToCallee
|
||||||
),
|
),
|
||||||
constrainInfo(
|
constrainInfo(
|
||||||
@ -914,7 +915,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
|||||||
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
|
code.slice(firstArg.elements[0].start, firstArg.elements[0].end),
|
||||||
'tangentialArcTo',
|
'tangentialArcTo',
|
||||||
0,
|
0,
|
||||||
[firstArg.elements[0].start, firstArg.elements[0].end, true],
|
topLevelRange(firstArg.elements[0].start, firstArg.elements[0].end),
|
||||||
pathToFirstArg
|
pathToFirstArg
|
||||||
),
|
),
|
||||||
constrainInfo(
|
constrainInfo(
|
||||||
@ -923,7 +924,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
|||||||
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
|
code.slice(firstArg.elements[1].start, firstArg.elements[1].end),
|
||||||
'tangentialArcTo',
|
'tangentialArcTo',
|
||||||
1,
|
1,
|
||||||
[firstArg.elements[1].start, firstArg.elements[1].end, true],
|
topLevelRange(firstArg.elements[1].start, firstArg.elements[1].end),
|
||||||
pathToSecondArg
|
pathToSecondArg
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@ -1052,7 +1053,7 @@ export const circle: SketchLineHelper = {
|
|||||||
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
|
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
|
||||||
'circle',
|
'circle',
|
||||||
'radius',
|
'radius',
|
||||||
[radiusDetails.expr.start, radiusDetails.expr.end, true],
|
topLevelRange(radiusDetails.expr.start, radiusDetails.expr.end),
|
||||||
pathToRadiusLiteral
|
pathToRadiusLiteral
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
@ -1061,11 +1062,10 @@ export const circle: SketchLineHelper = {
|
|||||||
isConstrained: isNotLiteralArrayOrStatic(
|
isConstrained: isNotLiteralArrayOrStatic(
|
||||||
centerDetails.expr.elements[0]
|
centerDetails.expr.elements[0]
|
||||||
),
|
),
|
||||||
sourceRange: [
|
sourceRange: topLevelRange(
|
||||||
centerDetails.expr.elements[0].start,
|
centerDetails.expr.elements[0].start,
|
||||||
centerDetails.expr.elements[0].end,
|
centerDetails.expr.elements[0].end
|
||||||
true,
|
),
|
||||||
],
|
|
||||||
pathToNode: pathToXArg,
|
pathToNode: pathToXArg,
|
||||||
value: code.slice(
|
value: code.slice(
|
||||||
centerDetails.expr.elements[0].start,
|
centerDetails.expr.elements[0].start,
|
||||||
@ -1083,11 +1083,10 @@ export const circle: SketchLineHelper = {
|
|||||||
isConstrained: isNotLiteralArrayOrStatic(
|
isConstrained: isNotLiteralArrayOrStatic(
|
||||||
centerDetails.expr.elements[1]
|
centerDetails.expr.elements[1]
|
||||||
),
|
),
|
||||||
sourceRange: [
|
sourceRange: topLevelRange(
|
||||||
centerDetails.expr.elements[1].start,
|
centerDetails.expr.elements[1].start,
|
||||||
centerDetails.expr.elements[1].end,
|
centerDetails.expr.elements[1].end
|
||||||
true,
|
),
|
||||||
],
|
|
||||||
pathToNode: pathToYArg,
|
pathToNode: pathToYArg,
|
||||||
value: code.slice(
|
value: code.slice(
|
||||||
centerDetails.expr.elements[1].start,
|
centerDetails.expr.elements[1].start,
|
||||||
@ -1763,7 +1762,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
code.slice(angle.start, angle.end),
|
code.slice(angle.start, angle.end),
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
'angle',
|
'angle',
|
||||||
[angle.start, angle.end, true],
|
topLevelRange(angle.start, angle.end),
|
||||||
pathToAngleProp
|
pathToAngleProp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1782,7 +1781,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
code.slice(offset.start, offset.end),
|
code.slice(offset.start, offset.end),
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
'offset',
|
'offset',
|
||||||
[offset.start, offset.end, true],
|
topLevelRange(offset.start, offset.end),
|
||||||
pathToOffsetProp
|
pathToOffsetProp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -1801,7 +1800,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
|
|||||||
code.slice(tag.start, tag.end),
|
code.slice(tag.start, tag.end),
|
||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
'intersectTag',
|
'intersectTag',
|
||||||
[tag.start, tag.end, true],
|
topLevelRange(tag.start, tag.end),
|
||||||
pathToTagProp
|
pathToTagProp
|
||||||
)
|
)
|
||||||
returnVal.push(info)
|
returnVal.push(info)
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import {
|
|||||||
initPromise,
|
initPromise,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
topLevelRange,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
ConstraintType,
|
ConstraintType,
|
||||||
@ -31,10 +32,10 @@ async function testingSwapSketchFnCall({
|
|||||||
constraintType: ConstraintType
|
constraintType: ConstraintType
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
newCode: string
|
newCode: string
|
||||||
originalRange: [number, number, boolean]
|
originalRange: SourceRange
|
||||||
}> {
|
}> {
|
||||||
const startIndex = inputCode.indexOf(callToSwap)
|
const startIndex = inputCode.indexOf(callToSwap)
|
||||||
const range: SourceRange = [startIndex, startIndex + callToSwap.length, true]
|
const range = topLevelRange(startIndex, startIndex + callToSwap.length)
|
||||||
const ast = assertParse(inputCode)
|
const ast = assertParse(inputCode)
|
||||||
|
|
||||||
const execState = await enginelessExecutor(ast)
|
const execState = await enginelessExecutor(ast)
|
||||||
@ -375,7 +376,10 @@ part001 = startSketchOn('XY')
|
|||||||
execState.memory.get('part001'),
|
execState.memory.get('part001'),
|
||||||
'part001'
|
'part001'
|
||||||
) as Sketch
|
) as Sketch
|
||||||
const _segment = getSketchSegmentFromSourceRange(sg, [index, index, true])
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
|
sg,
|
||||||
|
topLevelRange(index, index)
|
||||||
|
)
|
||||||
if (err(_segment)) throw _segment
|
if (err(_segment)) throw _segment
|
||||||
const { __geoMeta, ...segment } = _segment.segment
|
const { __geoMeta, ...segment } = _segment.segment
|
||||||
expect(segment).toEqual({
|
expect(segment).toEqual({
|
||||||
@ -390,7 +394,7 @@ part001 = startSketchOn('XY')
|
|||||||
const index = code.indexOf('// segment-in-start') - 7
|
const index = code.indexOf('// segment-in-start') - 7
|
||||||
const _segment = getSketchSegmentFromSourceRange(
|
const _segment = getSketchSegmentFromSourceRange(
|
||||||
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
sketchFromKclValue(execState.memory.get('part001'), 'part001') as Sketch,
|
||||||
[index, index, true]
|
topLevelRange(index, index)
|
||||||
)
|
)
|
||||||
if (err(_segment)) throw _segment
|
if (err(_segment)) throw _segment
|
||||||
const { __geoMeta, ...segment } = _segment.segment
|
const { __geoMeta, ...segment } = _segment.segment
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
Path,
|
Path,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Expr,
|
Expr,
|
||||||
|
topLevelRange,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
@ -31,7 +32,7 @@ export function getSketchSegmentFromPathToNode(
|
|||||||
const node = nodeMeta.node
|
const node = nodeMeta.node
|
||||||
if (!node || typeof node.start !== 'number' || !node.end)
|
if (!node || typeof node.start !== 'number' || !node.end)
|
||||||
return new Error('no node found')
|
return new Error('no node found')
|
||||||
const sourceRange: SourceRange = [node.start, node.end, true]
|
const sourceRange = topLevelRange(node.start, node.end)
|
||||||
return getSketchSegmentFromSourceRange(sketch, sourceRange)
|
return getSketchSegmentFromSourceRange(sketch, sourceRange)
|
||||||
}
|
}
|
||||||
export function getSketchSegmentFromSourceRange(
|
export function getSketchSegmentFromSourceRange(
|
||||||
|
|||||||
@ -1,4 +1,11 @@
|
|||||||
import { assertParse, Expr, recast, initPromise, Program } from '../wasm'
|
import {
|
||||||
|
assertParse,
|
||||||
|
Expr,
|
||||||
|
recast,
|
||||||
|
initPromise,
|
||||||
|
Program,
|
||||||
|
topLevelRange,
|
||||||
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
getConstraintType,
|
getConstraintType,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
@ -125,7 +132,7 @@ describe('testing transformAstForSketchLines for equal length constraint', () =>
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const start = codeBeforeLine + line.indexOf('|> ' + 5)
|
const start = codeBeforeLine + line.indexOf('|> ' + 5)
|
||||||
const range: [number, number, boolean] = [start, start, true]
|
const range = topLevelRange(start, start)
|
||||||
return {
|
return {
|
||||||
codeRef: codeRefFromRange(range, ast),
|
codeRef: codeRefFromRange(range, ast),
|
||||||
}
|
}
|
||||||
@ -297,7 +304,7 @@ part001 = startSketchOn('XY')
|
|||||||
const comment = ln.split('//')[1]
|
const comment = ln.split('//')[1]
|
||||||
const start = inputScript.indexOf('//' + comment) - 7
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
return {
|
return {
|
||||||
codeRef: codeRefFromRange([start, start, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -386,7 +393,7 @@ part001 = startSketchOn('XY')
|
|||||||
const comment = ln.split('//')[1]
|
const comment = ln.split('//')[1]
|
||||||
const start = inputScript.indexOf('//' + comment) - 7
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
return {
|
return {
|
||||||
codeRef: codeRefFromRange([start, start, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -446,7 +453,7 @@ part001 = startSketchOn('XY')
|
|||||||
const comment = ln.split('//')[1]
|
const comment = ln.split('//')[1]
|
||||||
const start = inputScript.indexOf('//' + comment) - 7
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
return {
|
return {
|
||||||
codeRef: codeRefFromRange([start, start, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -541,7 +548,7 @@ async function helperThing(
|
|||||||
const comment = ln.split('//')[1]
|
const comment = ln.split('//')[1]
|
||||||
const start = inputScript.indexOf('//' + comment) - 7
|
const start = inputScript.indexOf('//' + comment) - 7
|
||||||
return {
|
return {
|
||||||
codeRef: codeRefFromRange([start, start, true], ast),
|
codeRef: codeRefFromRange(topLevelRange(start, start), ast),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -610,7 +617,7 @@ part001 = startSketchOn('XY')
|
|||||||
}
|
}
|
||||||
const offsetIndex = index - 7
|
const offsetIndex = index - 7
|
||||||
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
const expectedConstraintLevel = getConstraintLevelFromSourceRange(
|
||||||
[offsetIndex, offsetIndex, true],
|
topLevelRange(offsetIndex, offsetIndex),
|
||||||
ast
|
ast
|
||||||
)
|
)
|
||||||
if (err(expectedConstraintLevel)) {
|
if (err(expectedConstraintLevel)) {
|
||||||
|
|||||||
@ -20,12 +20,12 @@ import {
|
|||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
Literal,
|
Literal,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
LiteralValue,
|
||||||
} from '../wasm'
|
} from '../wasm'
|
||||||
import {
|
import {
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
isValueZero,
|
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
import {
|
import {
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
@ -79,11 +79,32 @@ export type ConstraintType =
|
|||||||
| 'setAngleBetween'
|
| 'setAngleBetween'
|
||||||
|
|
||||||
const REF_NUM_ERR = new Error('Referenced segment does not have a to value')
|
const REF_NUM_ERR = new Error('Referenced segment does not have a to value')
|
||||||
|
|
||||||
|
function asNum(val: LiteralValue): number | Error {
|
||||||
|
if (typeof val === 'object') return val.value
|
||||||
|
return REF_NUM_ERR
|
||||||
|
}
|
||||||
|
|
||||||
|
function forceNum(arg: Literal): number {
|
||||||
|
if (typeof arg.value === 'boolean' || typeof arg.value === 'string') {
|
||||||
|
return Number(arg.value)
|
||||||
|
} else {
|
||||||
|
return arg.value.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isUndef(val: any): val is undefined {
|
function isUndef(val: any): val is undefined {
|
||||||
return typeof val === 'undefined'
|
return typeof val === 'undefined'
|
||||||
}
|
}
|
||||||
function isNum(val: any): val is number {
|
|
||||||
return typeof val === 'number'
|
function isValueZero(val?: Expr): boolean {
|
||||||
|
return (
|
||||||
|
(val?.type === 'Literal' && forceNum(val) === 0) ||
|
||||||
|
(val?.type === 'UnaryExpression' &&
|
||||||
|
val.operator === '-' &&
|
||||||
|
val.argument.type === 'Literal' &&
|
||||||
|
Number(val.argument.value) === 0)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCallWrapper(
|
function createCallWrapper(
|
||||||
@ -190,7 +211,7 @@ const xyLineSetLength =
|
|||||||
: referenceSeg
|
: referenceSeg
|
||||||
? segRef
|
? segRef
|
||||||
: args[0].expr
|
: args[0].expr
|
||||||
const literalARg = getArgLiteralVal(args[0].expr)
|
const literalARg = asNum(args[0].expr.value)
|
||||||
if (err(literalARg)) return literalARg
|
if (err(literalARg)) return literalARg
|
||||||
return createCallWrapper(xOrY, lineVal, tag, literalARg)
|
return createCallWrapper(xOrY, lineVal, tag, literalARg)
|
||||||
}
|
}
|
||||||
@ -211,13 +232,14 @@ const basicAngledLineCreateNode =
|
|||||||
referencedSegment: path,
|
referencedSegment: path,
|
||||||
}) => {
|
}) => {
|
||||||
const refAng = path ? getAngle(path?.from, path?.to) : 0
|
const refAng = path ? getAngle(path?.from, path?.to) : 0
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const argValue = asNum(args[0].expr.value)
|
||||||
|
if (err(argValue)) return argValue
|
||||||
const nonForcedAng =
|
const nonForcedAng =
|
||||||
varValToUse === 'ang'
|
varValToUse === 'ang'
|
||||||
? inputs[0].expr
|
? inputs[0].expr
|
||||||
: referenceSeg === 'ang'
|
: referenceSeg === 'ang'
|
||||||
? getClosesAngleDirection(
|
? getClosesAngleDirection(
|
||||||
args[0].expr.value,
|
argValue,
|
||||||
refAng,
|
refAng,
|
||||||
createSegAngle(referenceSegName)
|
createSegAngle(referenceSegName)
|
||||||
)
|
)
|
||||||
@ -230,8 +252,8 @@ const basicAngledLineCreateNode =
|
|||||||
: args[1].expr
|
: args[1].expr
|
||||||
const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform
|
const shouldForceAng = valToForce === 'ang' && forceValueUsedInTransform
|
||||||
const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform
|
const shouldForceLen = valToForce === 'len' && forceValueUsedInTransform
|
||||||
const literalArg = getArgLiteralVal(
|
const literalArg = asNum(
|
||||||
valToForce === 'ang' ? args[0].expr : args[1].expr
|
valToForce === 'ang' ? args[0].expr.value : args[1].expr.value
|
||||||
)
|
)
|
||||||
if (err(literalArg)) return literalArg
|
if (err(literalArg)) return literalArg
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
@ -283,7 +305,7 @@ const getMinAndSegAngVals = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) =>
|
const getSignedLeg = (arg: Literal, legLenVal: BinaryPart) =>
|
||||||
Number(arg.value) < 0 ? createUnaryExpression(legLenVal) : legLenVal
|
forceNum(arg) < 0 ? createUnaryExpression(legLenVal) : legLenVal
|
||||||
|
|
||||||
const getLegAng = (ang: number, legAngleVal: BinaryPart) => {
|
const getLegAng = (ang: number, legAngleVal: BinaryPart) => {
|
||||||
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
|
const normalisedAngle = ((ang % 360) + 360) % 360 // between 0 and 360
|
||||||
@ -322,8 +344,7 @@ const setHorzVertDistanceCreateNode =
|
|||||||
referencedSegment,
|
referencedSegment,
|
||||||
}) => {
|
}) => {
|
||||||
const refNum = referencedSegment?.to?.[index]
|
const refNum = referencedSegment?.to?.[index]
|
||||||
const literalArg = getArgLiteralVal(args?.[index].expr)
|
const literalArg = asNum(args?.[index].expr.value)
|
||||||
if (err(literalArg)) return literalArg
|
|
||||||
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
||||||
|
|
||||||
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
||||||
@ -352,7 +373,7 @@ const setHorzVertDistanceForAngleLineCreateNode =
|
|||||||
referencedSegment,
|
referencedSegment,
|
||||||
}) => {
|
}) => {
|
||||||
const refNum = referencedSegment?.to?.[index]
|
const refNum = referencedSegment?.to?.[index]
|
||||||
const literalArg = getArgLiteralVal(args?.[1].expr)
|
const literalArg = asNum(args?.[1].expr.value)
|
||||||
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
||||||
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
||||||
const binExp = createBinaryExpressionWithUnary([
|
const binExp = createBinaryExpressionWithUnary([
|
||||||
@ -374,8 +395,8 @@ const setAbsDistanceCreateNode =
|
|||||||
index = xOrY === 'x' ? 0 : 1
|
index = xOrY === 'x' ? 0 : 1
|
||||||
): CreateStdLibSketchCallExpr =>
|
): CreateStdLibSketchCallExpr =>
|
||||||
({ tag, forceValueUsedInTransform, rawArgs: args }) => {
|
({ tag, forceValueUsedInTransform, rawArgs: args }) => {
|
||||||
const literalArg = getArgLiteralVal(args?.[index].expr)
|
const literalArg = asNum(args?.[index].expr.value)
|
||||||
if (err(literalArg)) return REF_NUM_ERR
|
if (err(literalArg)) return literalArg
|
||||||
const valueUsedInTransform = roundOff(literalArg, 2)
|
const valueUsedInTransform = roundOff(literalArg, 2)
|
||||||
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
|
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
|
||||||
if (isXOrYLine) {
|
if (isXOrYLine) {
|
||||||
@ -396,8 +417,8 @@ const setAbsDistanceCreateNode =
|
|||||||
const setAbsDistanceForAngleLineCreateNode =
|
const setAbsDistanceForAngleLineCreateNode =
|
||||||
(xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr =>
|
(xOrY: 'x' | 'y'): CreateStdLibSketchCallExpr =>
|
||||||
({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => {
|
({ tag, forceValueUsedInTransform, inputs, rawArgs: args }) => {
|
||||||
const literalArg = getArgLiteralVal(args?.[1].expr)
|
const literalArg = asNum(args?.[1].expr.value)
|
||||||
if (err(literalArg)) return REF_NUM_ERR
|
if (err(literalArg)) return literalArg
|
||||||
const valueUsedInTransform = roundOff(literalArg, 2)
|
const valueUsedInTransform = roundOff(literalArg, 2)
|
||||||
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
|
const val = forceValueUsedInTransform || createLiteral(valueUsedInTransform)
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
@ -419,7 +440,7 @@ const setHorVertDistanceForXYLines =
|
|||||||
}) => {
|
}) => {
|
||||||
const index = xOrY === 'x' ? 0 : 1
|
const index = xOrY === 'x' ? 0 : 1
|
||||||
const refNum = referencedSegment?.to?.[index]
|
const refNum = referencedSegment?.to?.[index]
|
||||||
const literalArg = getArgLiteralVal(args?.[index].expr)
|
const literalArg = asNum(args?.[index].expr.value)
|
||||||
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
if (isUndef(refNum) || err(literalArg)) return REF_NUM_ERR
|
||||||
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
const valueUsedInTransform = roundOff(literalArg - refNum, 2)
|
||||||
const makeBinExp = createBinaryExpressionWithUnary([
|
const makeBinExp = createBinaryExpressionWithUnary([
|
||||||
@ -445,9 +466,9 @@ const setHorzVertDistanceConstraintLineCreateNode =
|
|||||||
])
|
])
|
||||||
|
|
||||||
const makeBinExp = (index: 0 | 1) => {
|
const makeBinExp = (index: 0 | 1) => {
|
||||||
const arg = getArgLiteralVal(args?.[index].expr)
|
const arg = asNum(args?.[index].expr.value)
|
||||||
const refNum = referencedSegment?.to?.[index]
|
const refNum = referencedSegment?.to?.[index]
|
||||||
if (err(arg) || !isNum(refNum)) return REF_NUM_ERR
|
if (err(arg) || isUndef(refNum)) return REF_NUM_ERR
|
||||||
return createBinaryExpressionWithUnary([
|
return createBinaryExpressionWithUnary([
|
||||||
createSegEnd(referenceSegName, isX),
|
createSegEnd(referenceSegName, isX),
|
||||||
createLiteral(roundOff(arg - refNum, 2)),
|
createLiteral(roundOff(arg - refNum, 2)),
|
||||||
@ -468,9 +489,9 @@ const setAngledIntersectLineForLines: CreateStdLibSketchCallExpr = ({
|
|||||||
forceValueUsedInTransform,
|
forceValueUsedInTransform,
|
||||||
rawArgs: args,
|
rawArgs: args,
|
||||||
}) => {
|
}) => {
|
||||||
const val = args[1].expr.value,
|
const val = asNum(args[1].expr.value),
|
||||||
angle = args[0].expr.value
|
angle = asNum(args[0].expr.value)
|
||||||
if (!isNum(val) || !isNum(angle)) return REF_NUM_ERR
|
if (err(val) || err(angle)) return REF_NUM_ERR
|
||||||
const valueUsedInTransform = roundOff(val, 2)
|
const valueUsedInTransform = roundOff(val, 2)
|
||||||
const varNamMap: { [key: number]: string } = {
|
const varNamMap: { [key: number]: string } = {
|
||||||
0: 'ZERO',
|
0: 'ZERO',
|
||||||
@ -498,8 +519,8 @@ const setAngledIntersectForAngledLines: CreateStdLibSketchCallExpr = ({
|
|||||||
inputs,
|
inputs,
|
||||||
rawArgs: args,
|
rawArgs: args,
|
||||||
}) => {
|
}) => {
|
||||||
const val = args[1].expr.value
|
const val = asNum(args[1].expr.value)
|
||||||
if (!isNum(val)) return REF_NUM_ERR
|
if (err(val)) return val
|
||||||
const valueUsedInTransform = roundOff(val, 2)
|
const valueUsedInTransform = roundOff(val, 2)
|
||||||
return intersectCallWrapper({
|
return intersectCallWrapper({
|
||||||
fnName: 'angledLineThatIntersects',
|
fnName: 'angledLineThatIntersects',
|
||||||
@ -524,8 +545,8 @@ const setAngleBetweenCreateNode =
|
|||||||
const refAngle = referencedSegment
|
const refAngle = referencedSegment
|
||||||
? getAngle(referencedSegment?.from, referencedSegment?.to)
|
? getAngle(referencedSegment?.from, referencedSegment?.to)
|
||||||
: 0
|
: 0
|
||||||
const val = args[0].expr.value
|
const val = asNum(args[0].expr.value)
|
||||||
if (!isNum(val)) return REF_NUM_ERR
|
if (err(val)) return val
|
||||||
let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle))
|
let valueUsedInTransform = roundOff(normaliseAngle(val - refAngle))
|
||||||
let firstHalfValue = createSegAngle(referenceSegName)
|
let firstHalfValue = createSegAngle(referenceSegName)
|
||||||
if (Math.abs(valueUsedInTransform) > 90) {
|
if (Math.abs(valueUsedInTransform) > 90) {
|
||||||
@ -706,13 +727,11 @@ const transformMap: TransformMap = {
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineToX',
|
'angledLineToX',
|
||||||
[
|
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[0].expr],
|
||||||
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
|
|
||||||
inputs[0].expr,
|
|
||||||
],
|
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -739,13 +758,11 @@ const transformMap: TransformMap = {
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineToY',
|
'angledLineToY',
|
||||||
[
|
[getAngleLengthSign(val, angleToMatchLengthYCall), inputs[1].expr],
|
||||||
getAngleLengthSign(args[0].expr.value, angleToMatchLengthYCall),
|
|
||||||
inputs[1].expr,
|
|
||||||
],
|
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -763,7 +780,7 @@ const transformMap: TransformMap = {
|
|||||||
forceValueUsedInTransform,
|
forceValueUsedInTransform,
|
||||||
rawArgs: args,
|
rawArgs: args,
|
||||||
}) => {
|
}) => {
|
||||||
const val = getArgLiteralVal(args[0].expr)
|
const val = asNum(args[0].expr.value)
|
||||||
if (err(val)) return val
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineToY',
|
'angledLineToY',
|
||||||
@ -844,7 +861,7 @@ const transformMap: TransformMap = {
|
|||||||
tooltip: 'yLine',
|
tooltip: 'yLine',
|
||||||
createNode: ({ inputs, tag, rawArgs: args }) => {
|
createNode: ({ inputs, tag, rawArgs: args }) => {
|
||||||
const expr = inputs[1].expr
|
const expr = inputs[1].expr
|
||||||
if (Number(args[0].expr.value) >= 0)
|
if (forceNum(args[0].expr) >= 0)
|
||||||
return createCallWrapper('yLine', expr, tag)
|
return createCallWrapper('yLine', expr, tag)
|
||||||
if (isExprBinaryPart(expr))
|
if (isExprBinaryPart(expr))
|
||||||
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
|
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
|
||||||
@ -856,7 +873,7 @@ const transformMap: TransformMap = {
|
|||||||
tooltip: 'xLine',
|
tooltip: 'xLine',
|
||||||
createNode: ({ inputs, tag, rawArgs: args }) => {
|
createNode: ({ inputs, tag, rawArgs: args }) => {
|
||||||
const expr = inputs[1].expr
|
const expr = inputs[1].expr
|
||||||
if (Number(args[0].expr.value) >= 0)
|
if (forceNum(args[0].expr) >= 0)
|
||||||
return createCallWrapper('xLine', expr, tag)
|
return createCallWrapper('xLine', expr, tag)
|
||||||
if (isExprBinaryPart(expr))
|
if (isExprBinaryPart(expr))
|
||||||
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
|
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
|
||||||
@ -900,10 +917,11 @@ const transformMap: TransformMap = {
|
|||||||
referenceSegName,
|
referenceSegName,
|
||||||
getInputOfType(inputs, 'xRelative').expr
|
getInputOfType(inputs, 'xRelative').expr
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineOfXLength',
|
'angledLineOfXLength',
|
||||||
[getLegAng(args[0].expr.value, legAngle), minVal],
|
[getLegAng(val, legAngle), minVal],
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -912,7 +930,7 @@ const transformMap: TransformMap = {
|
|||||||
tooltip: 'xLine',
|
tooltip: 'xLine',
|
||||||
createNode: ({ inputs, tag, rawArgs: args }) => {
|
createNode: ({ inputs, tag, rawArgs: args }) => {
|
||||||
const expr = inputs[1].expr
|
const expr = inputs[1].expr
|
||||||
if (Number(args[0].expr.value) >= 0)
|
if (forceNum(args[0].expr) >= 0)
|
||||||
return createCallWrapper('xLine', expr, tag)
|
return createCallWrapper('xLine', expr, tag)
|
||||||
if (isExprBinaryPart(expr))
|
if (isExprBinaryPart(expr))
|
||||||
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
|
return createCallWrapper('xLine', createUnaryExpression(expr), tag)
|
||||||
@ -953,10 +971,11 @@ const transformMap: TransformMap = {
|
|||||||
inputs[1].expr,
|
inputs[1].expr,
|
||||||
'legAngY'
|
'legAngY'
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineOfXLength',
|
'angledLineOfXLength',
|
||||||
[getLegAng(args[0].expr.value, legAngle), minVal],
|
[getLegAng(val, legAngle), minVal],
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -965,7 +984,7 @@ const transformMap: TransformMap = {
|
|||||||
tooltip: 'yLine',
|
tooltip: 'yLine',
|
||||||
createNode: ({ inputs, tag, rawArgs: args }) => {
|
createNode: ({ inputs, tag, rawArgs: args }) => {
|
||||||
const expr = inputs[1].expr
|
const expr = inputs[1].expr
|
||||||
if (Number(args[0].expr.value) >= 0)
|
if (forceNum(args[0].expr) >= 0)
|
||||||
return createCallWrapper('yLine', expr, tag)
|
return createCallWrapper('yLine', expr, tag)
|
||||||
if (isExprBinaryPart(expr))
|
if (isExprBinaryPart(expr))
|
||||||
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
|
return createCallWrapper('yLine', createUnaryExpression(expr), tag)
|
||||||
@ -1005,13 +1024,11 @@ const transformMap: TransformMap = {
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineToX',
|
'angledLineToX',
|
||||||
[
|
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
|
||||||
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
|
|
||||||
inputs[1].expr,
|
|
||||||
],
|
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1057,13 +1074,11 @@ const transformMap: TransformMap = {
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
if (!isNum(args[0].expr.value)) return REF_NUM_ERR
|
const val = asNum(args[0].expr.value)
|
||||||
|
if (err(val)) return val
|
||||||
return createCallWrapper(
|
return createCallWrapper(
|
||||||
'angledLineToY',
|
'angledLineToY',
|
||||||
[
|
[getAngleLengthSign(val, angleToMatchLengthXCall), inputs[1].expr],
|
||||||
getAngleLengthSign(args[0].expr.value, angleToMatchLengthXCall),
|
|
||||||
inputs[1].expr,
|
|
||||||
],
|
|
||||||
tag
|
tag
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@ -1080,7 +1095,7 @@ const transformMap: TransformMap = {
|
|||||||
equalLength: {
|
equalLength: {
|
||||||
tooltip: 'xLine',
|
tooltip: 'xLine',
|
||||||
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
|
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
|
||||||
const argVal = getArgLiteralVal(args[0].expr)
|
const argVal = asNum(args[0].expr.value)
|
||||||
if (err(argVal)) return argVal
|
if (err(argVal)) return argVal
|
||||||
const segLen = createSegLen(referenceSegName)
|
const segLen = createSegLen(referenceSegName)
|
||||||
if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal)
|
if (argVal > 0) return createCallWrapper('xLine', segLen, tag, argVal)
|
||||||
@ -1118,7 +1133,7 @@ const transformMap: TransformMap = {
|
|||||||
equalLength: {
|
equalLength: {
|
||||||
tooltip: 'yLine',
|
tooltip: 'yLine',
|
||||||
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
|
createNode: ({ referenceSegName, tag, rawArgs: args }) => {
|
||||||
const argVal = getArgLiteralVal(args[0].expr)
|
const argVal = asNum(args[0].expr.value)
|
||||||
if (err(argVal)) return argVal
|
if (err(argVal)) return argVal
|
||||||
let segLen = createSegLen(referenceSegName)
|
let segLen = createSegLen(referenceSegName)
|
||||||
if (argVal < 0) segLen = createUnaryExpression(segLen)
|
if (argVal < 0) segLen = createUnaryExpression(segLen)
|
||||||
@ -1714,7 +1729,7 @@ export function transformAstSketchLines({
|
|||||||
let kclVal = programMemory.get(varName)
|
let kclVal = programMemory.get(varName)
|
||||||
let sketch
|
let sketch
|
||||||
if (kclVal?.type === 'Solid') {
|
if (kclVal?.type === 'Solid') {
|
||||||
sketch = kclVal.sketch
|
sketch = kclVal.value.sketch
|
||||||
} else {
|
} else {
|
||||||
sketch = sketchFromKclValue(kclVal, varName)
|
sketch = sketchFromKclValue(kclVal, varName)
|
||||||
if (err(sketch)) {
|
if (err(sketch)) {
|
||||||
@ -1823,11 +1838,6 @@ function createLastSeg(isX: boolean): Node<CallExpression> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArgLiteralVal(arg: Literal): number | Error {
|
|
||||||
if (!isNum(arg.value)) return REF_NUM_ERR
|
|
||||||
return arg.value
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConstraintLevel = 'free' | 'partial' | 'full'
|
export type ConstraintLevel = 'free' | 'partial' | 'full'
|
||||||
|
|
||||||
export function getConstraintLevelFromSourceRange(
|
export function getConstraintLevelFromSourceRange(
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import {
|
|||||||
Literal,
|
Literal,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
BinaryExpression,
|
BinaryExpression,
|
||||||
|
ArtifactGraph,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
import { filterArtifacts } from 'lang/std/artifactGraph'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
|
|
||||||
export function updatePathToNodeFromMap(
|
export function updatePathToNodeFromMap(
|
||||||
|
|||||||
111
src/lang/wasm.ts
111
src/lang/wasm.ts
@ -44,17 +44,30 @@ import { EnvironmentRef } from '../wasm-lib/kcl/bindings/EnvironmentRef'
|
|||||||
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
import { Environment } from '../wasm-lib/kcl/bindings/Environment'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
||||||
import { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
import { SourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||||
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 { Artifact as RustArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
import { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
import { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
import { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
import { ArtifactGraph as RustArtifactGraph } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
import { Artifact } from './std/artifactGraph'
|
||||||
|
import { getNodePathFromSourceRange } from './queryAst'
|
||||||
|
|
||||||
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
export type { Artifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/ArtifactCommand'
|
export type { ArtifactCommand } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
export type { ArtifactId } from 'wasm-lib/kcl/bindings/ArtifactId'
|
export type { ArtifactId } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Cap as CapArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { CodeRef } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { EdgeCut } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Path as PathArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Plane as PlaneArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Segment as SegmentArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Solid2d as Solid2dArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Sweep as SweepArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { SweepEdge } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
export type { Wall as WallArtifact } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
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'
|
||||||
@ -76,7 +89,7 @@ export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
|||||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||||
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||||
export type { SourceRange as RustSourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
export type { SourceRange } from 'wasm-lib/kcl/bindings/SourceRange'
|
||||||
|
|
||||||
export type SyntaxType =
|
export type SyntaxType =
|
||||||
| 'Program'
|
| 'Program'
|
||||||
@ -105,35 +118,36 @@ export type { Solid } from '../wasm-lib/kcl/bindings/Solid'
|
|||||||
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
|
export type { KclValue } from '../wasm-lib/kcl/bindings/KclValue'
|
||||||
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
export type { ExtrudeSurface } from '../wasm-lib/kcl/bindings/ExtrudeSurface'
|
||||||
|
|
||||||
/**
|
|
||||||
* The first two items are the start and end points (byte offsets from the start of the file).
|
|
||||||
* The third item is whether the source range belongs to the 'main' file, i.e., the file currently
|
|
||||||
* being rendered/displayed in the editor (TODO we need to handle modules better in the frontend).
|
|
||||||
*/
|
|
||||||
export type SourceRange = [number, number, boolean]
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
|
* Convert a SourceRange as used inside the KCL interpreter into the above one for use in the
|
||||||
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
|
* frontend (essentially we're eagerly checking whether the frontend should care about the SourceRange
|
||||||
* so as not to expose details of the interpreter's current representation of module ids throughout
|
* so as not to expose details of the interpreter's current representation of module ids throughout
|
||||||
* the frontend).
|
* the frontend).
|
||||||
*/
|
*/
|
||||||
export function sourceRangeFromRust(s: RustSourceRange): SourceRange {
|
export function sourceRangeFromRust(s: SourceRange): SourceRange {
|
||||||
return [s[0], s[1], s[2] === 0]
|
return [s[0], s[1], s[2]]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a default SourceRange for testing or as a placeholder.
|
* Create a default SourceRange for testing or as a placeholder.
|
||||||
*/
|
*/
|
||||||
export function defaultSourceRange(): SourceRange {
|
export function defaultSourceRange(): SourceRange {
|
||||||
return [0, 0, true]
|
return [0, 0, 0]
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a default RustSourceRange for testing or as a placeholder.
|
* Create a SourceRange for the top-level module.
|
||||||
*/
|
*/
|
||||||
export function defaultRustSourceRange(): RustSourceRange {
|
export function topLevelRange(start: number, end: number): SourceRange {
|
||||||
return [0, 0, 0]
|
return [start, end, 0]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this source range is from the file being executed. Returns
|
||||||
|
* false if it's from a file that was imported.
|
||||||
|
*/
|
||||||
|
export function isTopLevelModule(range: SourceRange): boolean {
|
||||||
|
return range[2] === 0
|
||||||
}
|
}
|
||||||
|
|
||||||
export const wasmUrl = () => {
|
export const wasmUrl = () => {
|
||||||
@ -234,7 +248,8 @@ export const parse = (code: string | Error): ParseResult | Error => {
|
|||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[]
|
[],
|
||||||
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -258,8 +273,9 @@ export const isPathToNodeNumber = (
|
|||||||
export interface ExecState {
|
export interface ExecState {
|
||||||
memory: ProgramMemory
|
memory: ProgramMemory
|
||||||
operations: Operation[]
|
operations: Operation[]
|
||||||
artifacts: { [key in ArtifactId]?: Artifact }
|
artifacts: { [key in ArtifactId]?: RustArtifact }
|
||||||
artifactCommands: ArtifactCommand[]
|
artifactCommands: ArtifactCommand[]
|
||||||
|
artifactGraph: ArtifactGraph
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -272,18 +288,53 @@ export function emptyExecState(): ExecState {
|
|||||||
operations: [],
|
operations: [],
|
||||||
artifacts: {},
|
artifacts: {},
|
||||||
artifactCommands: [],
|
artifactCommands: [],
|
||||||
|
artifactGraph: defaultArtifactGraph(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function execStateFromRust(execOutcome: RustExecOutcome): ExecState {
|
function execStateFromRust(
|
||||||
|
execOutcome: RustExecOutcome,
|
||||||
|
program: Node<Program>
|
||||||
|
): ExecState {
|
||||||
|
const artifactGraph = rustArtifactGraphToMap(execOutcome.artifactGraph)
|
||||||
|
// We haven't ported pathToNode logic to Rust yet, so we need to fill it in.
|
||||||
|
for (const [id, artifact] of artifactGraph) {
|
||||||
|
if (!artifact) continue
|
||||||
|
if (!('codeRef' in artifact)) continue
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
program,
|
||||||
|
sourceRangeFromRust(artifact.codeRef.range)
|
||||||
|
)
|
||||||
|
artifact.codeRef.pathToNode = pathToNode
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
memory: ProgramMemory.fromRaw(execOutcome.memory),
|
||||||
operations: execOutcome.operations,
|
operations: execOutcome.operations,
|
||||||
artifacts: execOutcome.artifacts,
|
artifacts: execOutcome.artifacts,
|
||||||
artifactCommands: execOutcome.artifactCommands,
|
artifactCommands: execOutcome.artifactCommands,
|
||||||
|
artifactGraph,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ArtifactGraph = Map<ArtifactId, Artifact>
|
||||||
|
|
||||||
|
function rustArtifactGraphToMap(
|
||||||
|
rustArtifactGraph: RustArtifactGraph
|
||||||
|
): ArtifactGraph {
|
||||||
|
const map = new Map<ArtifactId, Artifact>()
|
||||||
|
for (const [id, artifact] of Object.entries(rustArtifactGraph.map)) {
|
||||||
|
if (!artifact) continue
|
||||||
|
map.set(id, artifact)
|
||||||
|
}
|
||||||
|
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
export function defaultArtifactGraph(): ArtifactGraph {
|
||||||
|
return new Map()
|
||||||
|
}
|
||||||
|
|
||||||
interface Memory {
|
interface Memory {
|
||||||
[key: string]: KclValue | undefined
|
[key: string]: KclValue | undefined
|
||||||
}
|
}
|
||||||
@ -488,7 +539,8 @@ export function sketchFromKclValueOptional(
|
|||||||
): Sketch | Reason {
|
): Sketch | Reason {
|
||||||
if (obj?.value?.type === 'Sketch') return obj.value
|
if (obj?.value?.type === 'Sketch') return obj.value
|
||||||
if (obj?.value?.type === 'Solid') return obj.value.sketch
|
if (obj?.value?.type === 'Solid') return obj.value.sketch
|
||||||
if (obj?.type === 'Solid') return obj.sketch
|
if (obj?.type === 'Sketch') return obj.value
|
||||||
|
if (obj?.type === 'Solid') return obj.value.sketch
|
||||||
if (!varName) {
|
if (!varName) {
|
||||||
varName = 'a KCL value'
|
varName = 'a KCL value'
|
||||||
}
|
}
|
||||||
@ -543,7 +595,7 @@ export const executor = async (
|
|||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
fileSystemManager
|
fileSystemManager
|
||||||
)
|
)
|
||||||
return execStateFromRust(execOutcome)
|
return execStateFromRust(execOutcome, node)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
const parsed: KclErrorWithOutputs = JSON.parse(e.toString())
|
||||||
@ -552,7 +604,8 @@ export const executor = async (
|
|||||||
parsed.error.msg,
|
parsed.error.msg,
|
||||||
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
sourceRangeFromRust(parsed.error.sourceRanges[0]),
|
||||||
parsed.operations,
|
parsed.operations,
|
||||||
parsed.artifactCommands
|
parsed.artifactCommands,
|
||||||
|
rustArtifactGraphToMap(parsed.artifactGraph)
|
||||||
)
|
)
|
||||||
|
|
||||||
return Promise.reject(kclError)
|
return Promise.reject(kclError)
|
||||||
@ -613,7 +666,8 @@ export const modifyAstForSketch = async (
|
|||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[]
|
[],
|
||||||
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
|
|
||||||
console.log(kclError)
|
console.log(kclError)
|
||||||
@ -683,7 +737,8 @@ export function programMemoryInit(): ProgramMemory | Error {
|
|||||||
parsed.msg,
|
parsed.msg,
|
||||||
sourceRangeFromRust(parsed.sourceRanges[0]),
|
sourceRangeFromRust(parsed.sourceRanges[0]),
|
||||||
[],
|
[],
|
||||||
[]
|
[],
|
||||||
|
defaultArtifactGraph()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,12 @@ import { Selections } from 'lib/selections'
|
|||||||
import { kclManager } from 'lib/singletons'
|
import { kclManager } from 'lib/singletons'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
import { modelingMachine, SketchTool } from 'machines/modelingMachine'
|
||||||
import { loftValidator, revolveAxisValidator } from './validators'
|
import {
|
||||||
|
loftValidator,
|
||||||
|
revolveAxisValidator,
|
||||||
|
shellValidator,
|
||||||
|
sweepValidator,
|
||||||
|
} from './validators'
|
||||||
|
|
||||||
type OutputFormat = Models['OutputFormat_type']
|
type OutputFormat = Models['OutputFormat_type']
|
||||||
type OutputTypeKey = OutputFormat['type']
|
type OutputTypeKey = OutputFormat['type']
|
||||||
@ -38,8 +43,8 @@ export type ModelingCommandSchema = {
|
|||||||
distance: KclCommandValue
|
distance: KclCommandValue
|
||||||
}
|
}
|
||||||
Sweep: {
|
Sweep: {
|
||||||
path: Selections
|
target: Selections
|
||||||
profile: Selections
|
trajectory: Selections
|
||||||
}
|
}
|
||||||
Loft: {
|
Loft: {
|
||||||
selection: Selections
|
selection: Selections
|
||||||
@ -276,7 +281,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['solid2D', 'segment'],
|
selectionTypes: ['solid2d', 'segment'],
|
||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
@ -304,25 +309,24 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
'Create a 3D body by moving a sketch region along an arbitrary path.',
|
||||||
icon: 'sweep',
|
icon: 'sweep',
|
||||||
status: 'development',
|
status: 'development',
|
||||||
needsReview: true,
|
needsReview: false,
|
||||||
args: {
|
args: {
|
||||||
profile: {
|
target: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['solid2D'],
|
selectionTypes: ['solid2d'],
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
// TODO: add dry-run validation
|
|
||||||
warningMessage:
|
warningMessage:
|
||||||
'The sweep workflow is new and under tested. Please break it and report issues.',
|
'The sweep workflow is new and under tested. Please break it and report issues.',
|
||||||
},
|
},
|
||||||
path: {
|
trajectory: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['segment', 'path'],
|
selectionTypes: ['segment', 'path'],
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: false,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
// TODO: add dry-run validation
|
validation: sweepValidator,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -333,7 +337,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['solid2D'],
|
selectionTypes: ['solid2d'],
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: true,
|
required: true,
|
||||||
skip: false,
|
skip: false,
|
||||||
@ -351,12 +355,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
selectionTypes: ['cap', 'wall'],
|
selectionTypes: ['cap', 'wall'],
|
||||||
multiple: true,
|
multiple: true,
|
||||||
required: true,
|
required: true,
|
||||||
skip: false,
|
validation: shellValidator,
|
||||||
},
|
},
|
||||||
thickness: {
|
thickness: {
|
||||||
inputType: 'kcl',
|
inputType: 'kcl',
|
||||||
defaultValue: KCL_DEFAULT_LENGTH,
|
defaultValue: KCL_DEFAULT_LENGTH,
|
||||||
required: true,
|
required: true,
|
||||||
|
// TODO: add dry-run validation on thickness param
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -368,7 +373,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
args: {
|
args: {
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: ['solid2D', 'segment'],
|
selectionTypes: ['solid2d', 'segment'],
|
||||||
multiple: false, // TODO: multiple selection
|
multiple: false, // TODO: multiple selection
|
||||||
required: true,
|
required: true,
|
||||||
skip: true,
|
skip: true,
|
||||||
@ -573,7 +578,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
selectionTypes: [
|
selectionTypes: [
|
||||||
'solid2D',
|
'solid2d',
|
||||||
'segment',
|
'segment',
|
||||||
'sweepEdge',
|
'sweepEdge',
|
||||||
'cap',
|
'cap',
|
||||||
|
|||||||
@ -116,16 +116,16 @@ export const loftValidator = async ({
|
|||||||
}
|
}
|
||||||
const { selection } = data
|
const { selection } = data
|
||||||
|
|
||||||
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2D')) {
|
if (selection.graphSelections.some((s) => s.artifact?.type !== 'solid2d')) {
|
||||||
return 'Unable to loft, some selection are not solid2Ds'
|
return 'Unable to loft, some selection are not solid2ds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const sectionIds = data.selection.graphSelections.flatMap((s) =>
|
const sectionIds = data.selection.graphSelections.flatMap((s) =>
|
||||||
s.artifact?.type === 'solid2D' ? s.artifact.pathId : []
|
s.artifact?.type === 'solid2d' ? s.artifact.pathId : []
|
||||||
)
|
)
|
||||||
|
|
||||||
if (sectionIds.length < 2) {
|
if (sectionIds.length < 2) {
|
||||||
return 'Unable to loft, selection contains less than two solid2Ds'
|
return 'Unable to loft, selection contains less than two solid2ds'
|
||||||
}
|
}
|
||||||
|
|
||||||
const loftCommand = async () => {
|
const loftCommand = async () => {
|
||||||
@ -153,3 +153,118 @@ export const loftValidator = async ({
|
|||||||
return 'Unable to loft with selected sketches'
|
return 'Unable to loft with selected sketches'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const shellValidator = async ({
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
data: { selection: Selections }
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(data.selection)) {
|
||||||
|
return 'Unable to shell, selections are missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
// No validation on the faces, filtering is done upstream and we have the dry run validation just below
|
||||||
|
const face_ids = data.selection.graphSelections.flatMap((s) =>
|
||||||
|
s.artifact ? s.artifact.id : []
|
||||||
|
)
|
||||||
|
|
||||||
|
// We don't have the concept of solid3ds in TS yet.
|
||||||
|
// So we're listing out the sweeps as if they were solids and taking the first one, just like in Rust for Shell:
|
||||||
|
// https://github.com/KittyCAD/modeling-app/blob/e61fff115b9fa94aaace6307b1842cc15d41655e/src/wasm-lib/kcl/src/std/shell.rs#L237-L238
|
||||||
|
// TODO: This is one cheap way to make sketch-on-face supported now but will likely fail multiple solids
|
||||||
|
const object_id = engineCommandManager.artifactGraph
|
||||||
|
.values()
|
||||||
|
.find((v) => v.type === 'sweep')?.pathId
|
||||||
|
|
||||||
|
if (!object_id) {
|
||||||
|
return "Unable to shell, couldn't find the solid"
|
||||||
|
}
|
||||||
|
|
||||||
|
const shellCommand = async () => {
|
||||||
|
// TODO: figure out something better than an arbitrarily small value
|
||||||
|
const DEFAULT_THICKNESS: Models['LengthUnit_type'] = 1e-9
|
||||||
|
const DEFAULT_HOLLOW = false
|
||||||
|
const cmdArgs = {
|
||||||
|
face_ids,
|
||||||
|
object_id,
|
||||||
|
hollow: DEFAULT_HOLLOW,
|
||||||
|
shell_thickness: DEFAULT_THICKNESS,
|
||||||
|
}
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'solid3d_shell_face',
|
||||||
|
...cmdArgs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const attemptShell = await dryRunWrapper(shellCommand)
|
||||||
|
if (attemptShell?.success) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unable to shell with the provided selection'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const sweepValidator = async ({
|
||||||
|
context,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
context: CommandBarContext
|
||||||
|
data: { trajectory: Selections }
|
||||||
|
}): Promise<boolean | string> => {
|
||||||
|
if (!isSelections(data.trajectory)) {
|
||||||
|
console.log('Unable to sweep, selections are missing')
|
||||||
|
return 'Unable to sweep, selections are missing'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the parent path from the segment selection directly
|
||||||
|
const trajectoryArtifact = data.trajectory.graphSelections[0].artifact
|
||||||
|
if (!trajectoryArtifact) {
|
||||||
|
return "Unable to sweep, couldn't find the trajectory artifact"
|
||||||
|
}
|
||||||
|
if (trajectoryArtifact.type !== 'segment') {
|
||||||
|
return "Unable to sweep, couldn't find the target from a non-segment selection"
|
||||||
|
}
|
||||||
|
const trajectory = trajectoryArtifact.pathId
|
||||||
|
|
||||||
|
// Get the former arg in the command bar flow, and retrieve the path from the solid2d directly
|
||||||
|
const targetArg = context.argumentsToSubmit['target'] as Selections
|
||||||
|
const targetArtifact = targetArg.graphSelections[0].artifact
|
||||||
|
if (!targetArtifact) {
|
||||||
|
return "Unable to sweep, couldn't find the profile artifact"
|
||||||
|
}
|
||||||
|
if (targetArtifact.type !== 'solid2d') {
|
||||||
|
return "Unable to sweep, couldn't find the target from a non-solid2d selection"
|
||||||
|
}
|
||||||
|
const target = targetArtifact.pathId
|
||||||
|
|
||||||
|
const sweepCommand = async () => {
|
||||||
|
// TODO: second look on defaults here
|
||||||
|
const DEFAULT_TOLERANCE: Models['LengthUnit_type'] = 1e-7
|
||||||
|
const DEFAULT_SECTIONAL = false
|
||||||
|
const cmdArgs = {
|
||||||
|
target,
|
||||||
|
trajectory,
|
||||||
|
sectional: DEFAULT_SECTIONAL,
|
||||||
|
tolerance: DEFAULT_TOLERANCE,
|
||||||
|
}
|
||||||
|
return await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'sweep',
|
||||||
|
...cmdArgs,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const attemptSweep = await dryRunWrapper(sweepCommand)
|
||||||
|
if (attemptSweep?.success) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unable to sweep with the provided selection'
|
||||||
|
}
|
||||||
|
|||||||
58
src/lib/desktopFS.test.ts
Normal file
58
src/lib/desktopFS.test.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import { getUniqueProjectName } from './desktopFS'
|
||||||
|
import { FileEntry } from './project'
|
||||||
|
|
||||||
|
/** Create a dummy project */
|
||||||
|
function project(name: string, children?: FileEntry[]): FileEntry {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
children: children || [
|
||||||
|
{ name: 'main.kcl', children: null, path: 'main.kcl' },
|
||||||
|
],
|
||||||
|
path: `/projects/${name}`,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe(`Getting unique project names`, () => {
|
||||||
|
it(`should return the same name if no conflicts`, () => {
|
||||||
|
const projectName = 'new-project'
|
||||||
|
const projects = [project('existing-project'), project('another-project')]
|
||||||
|
const result = getUniqueProjectName(projectName, projects)
|
||||||
|
expect(result).toBe(projectName)
|
||||||
|
})
|
||||||
|
it(`should return a unique name if there is a conflict`, () => {
|
||||||
|
const projectName = 'existing-project'
|
||||||
|
const projects = [project('existing-project'), project('another-project')]
|
||||||
|
const result = getUniqueProjectName(projectName, projects)
|
||||||
|
expect(result).toBe('existing-project-1')
|
||||||
|
})
|
||||||
|
it(`should increment an ending index until a unique one is found`, () => {
|
||||||
|
const projectName = 'existing-project-1'
|
||||||
|
const projects = [
|
||||||
|
project('existing-project'),
|
||||||
|
project('existing-project-1'),
|
||||||
|
project('existing-project-2'),
|
||||||
|
]
|
||||||
|
const result = getUniqueProjectName(projectName, projects)
|
||||||
|
expect(result).toBe('existing-project-3')
|
||||||
|
})
|
||||||
|
it(`should prefer the formatting of the index identifier if present`, () => {
|
||||||
|
const projectName = 'existing-project-$nn'
|
||||||
|
const projects = [
|
||||||
|
project('existing-project'),
|
||||||
|
project('existing-project-1'),
|
||||||
|
project('existing-project-2'),
|
||||||
|
]
|
||||||
|
const result = getUniqueProjectName(projectName, projects)
|
||||||
|
expect(result).toBe('existing-project-03')
|
||||||
|
})
|
||||||
|
it(`be able to get an incrementing index regardless of padding zeroes`, () => {
|
||||||
|
const projectName = 'existing-project-$nn'
|
||||||
|
const projects = [
|
||||||
|
project('existing-project'),
|
||||||
|
project('existing-project-01'),
|
||||||
|
project('existing-project-2'),
|
||||||
|
]
|
||||||
|
const result = getUniqueProjectName(projectName, projects)
|
||||||
|
expect(result).toBe('existing-project-03')
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -54,8 +54,10 @@ export function getNextProjectIndex(
|
|||||||
const matches = projects.map((project) => project.name?.match(regex))
|
const matches = projects.map((project) => project.name?.match(regex))
|
||||||
const indices = matches
|
const indices = matches
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
.map((match) => match![1])
|
.map((match) => (match !== null ? match[1] : '-1'))
|
||||||
.map(Number)
|
.map((maybeMatchIndex) => {
|
||||||
|
return parseInt(maybeMatchIndex || '0', 10)
|
||||||
|
})
|
||||||
const maxIndex = Math.max(...indices, -1)
|
const maxIndex = Math.max(...indices, -1)
|
||||||
return maxIndex + 1
|
return maxIndex + 1
|
||||||
}
|
}
|
||||||
@ -83,6 +85,33 @@ export function doesProjectNameNeedInterpolated(projectName: string) {
|
|||||||
return projectName.includes(INDEX_IDENTIFIER)
|
return projectName.includes(INDEX_IDENTIFIER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a target name, which may include our magic index interpolation string,
|
||||||
|
* and a list of projects, return a unique name that doesn't conflict with any
|
||||||
|
* of the existing projects, incrementing any ending number if necessary.
|
||||||
|
* @param name
|
||||||
|
* @param projects
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function getUniqueProjectName(name: string, projects: FileEntry[]) {
|
||||||
|
// The name may have our magic index interpolation string in it
|
||||||
|
const needsInterpolation = doesProjectNameNeedInterpolated(name)
|
||||||
|
|
||||||
|
if (needsInterpolation) {
|
||||||
|
const nextIndex = getNextProjectIndex(name, projects)
|
||||||
|
return interpolateProjectNameWithIndex(name, nextIndex)
|
||||||
|
} else {
|
||||||
|
let newName = name
|
||||||
|
while (projects.some((project) => project.name === newName)) {
|
||||||
|
const nameEndsWithNumber = newName.match(/\d+$/)
|
||||||
|
newName = nameEndsWithNumber
|
||||||
|
? newName.replace(/\d+$/, (num) => `${parseInt(num, 10) + 1}`)
|
||||||
|
: `${name}-1`
|
||||||
|
}
|
||||||
|
return newName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function escapeRegExpChars(string: string) {
|
function escapeRegExpChars(string: string) {
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { defaultRustSourceRange } from 'lang/wasm'
|
import { defaultSourceRange } from 'lang/wasm'
|
||||||
import { filterOperations } from './operations'
|
import { filterOperations } from './operations'
|
||||||
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
|
||||||
|
|
||||||
@ -8,7 +8,7 @@ function stdlib(name: string): Operation {
|
|||||||
name,
|
name,
|
||||||
unlabeledArg: null,
|
unlabeledArg: null,
|
||||||
labeledArgs: {},
|
labeledArgs: {},
|
||||||
sourceRange: defaultRustSourceRange(),
|
sourceRange: defaultSourceRange(),
|
||||||
isError: false,
|
isError: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -17,10 +17,10 @@ function userCall(name: string): Operation {
|
|||||||
return {
|
return {
|
||||||
type: 'UserDefinedFunctionCall',
|
type: 'UserDefinedFunctionCall',
|
||||||
name,
|
name,
|
||||||
functionSourceRange: defaultRustSourceRange(),
|
functionSourceRange: defaultSourceRange(),
|
||||||
unlabeledArg: null,
|
unlabeledArg: null,
|
||||||
labeledArgs: {},
|
labeledArgs: {},
|
||||||
sourceRange: defaultRustSourceRange(),
|
sourceRange: defaultSourceRange(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function userReturn(): Operation {
|
function userReturn(): Operation {
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import { VITE_KC_API_BASE_URL } from 'env'
|
|||||||
import crossPlatformFetch from './crossPlatformFetch'
|
import crossPlatformFetch from './crossPlatformFetch'
|
||||||
import { err, reportRejection } from './trap'
|
import { err, reportRejection } from './trap'
|
||||||
import { Selections } from './selections'
|
import { Selections } from './selections'
|
||||||
import { ArtifactGraph, getArtifactOfTypes } from 'lang/std/artifactGraph'
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
import { SourceRange } from 'lang/wasm'
|
import { ArtifactGraph, SourceRange, topLevelRange } from 'lang/wasm'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { codeManager, editorManager, kclManager } from './singletons'
|
import { codeManager, editorManager, kclManager } from './singletons'
|
||||||
import { ToastPromptToEditCadSuccess } from 'components/ToastTextToCad'
|
import { ToastPromptToEditCadSuccess } from 'components/ToastTextToCad'
|
||||||
@ -334,7 +334,7 @@ const reBuildNewCodeWithRanges = (
|
|||||||
} else if (change.added && !change.removed) {
|
} else if (change.added && !change.removed) {
|
||||||
const start = newCodeWithRanges.length
|
const start = newCodeWithRanges.length
|
||||||
const end = start + change.value.length
|
const end = start + change.value.length
|
||||||
insertRanges.push([start, end, true])
|
insertRanges.push(topLevelRange(start, end))
|
||||||
newCodeWithRanges += change.value
|
newCodeWithRanges += change.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import {
|
|||||||
SourceRange,
|
SourceRange,
|
||||||
Expr,
|
Expr,
|
||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
|
topLevelRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||||
import { isNonNullable, uuidv4 } from 'lib/utils'
|
import { isNonNullable, uuidv4 } from 'lib/utils'
|
||||||
@ -63,7 +64,7 @@ type Selection__old =
|
|||||||
| 'line-end'
|
| 'line-end'
|
||||||
| 'line-mid'
|
| 'line-mid'
|
||||||
| 'extrude-wall'
|
| 'extrude-wall'
|
||||||
| 'solid2D'
|
| 'solid2d'
|
||||||
| 'start-cap'
|
| 'start-cap'
|
||||||
| 'end-cap'
|
| 'end-cap'
|
||||||
| 'point'
|
| 'point'
|
||||||
@ -103,13 +104,13 @@ function convertSelectionToOld(selection: Selection): Selection__old | null {
|
|||||||
// return {} as Selection__old
|
// return {} as Selection__old
|
||||||
// TODO implementation
|
// TODO implementation
|
||||||
const _artifact = selection.artifact
|
const _artifact = selection.artifact
|
||||||
if (_artifact?.type === 'solid2D') {
|
if (_artifact?.type === 'solid2d') {
|
||||||
const codeRef = getSolid2dCodeRef(
|
const codeRef = getSolid2dCodeRef(
|
||||||
_artifact,
|
_artifact,
|
||||||
engineCommandManager.artifactGraph
|
engineCommandManager.artifactGraph
|
||||||
)
|
)
|
||||||
if (err(codeRef)) return null
|
if (err(codeRef)) return null
|
||||||
return { range: codeRef.range, type: 'solid2D' }
|
return { range: codeRef.range, type: 'solid2d' }
|
||||||
}
|
}
|
||||||
if (_artifact?.type === 'cap') {
|
if (_artifact?.type === 'cap') {
|
||||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||||
@ -269,7 +270,7 @@ export function getEventForSegmentSelection(
|
|||||||
selectionType: 'singleCodeCursor',
|
selectionType: 'singleCodeCursor',
|
||||||
selection: {
|
selection: {
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [node.node.start, node.node.end, true],
|
range: topLevelRange(node.node.start, node.node.end),
|
||||||
pathToNode: group.userData.pathToNode,
|
pathToNode: group.userData.pathToNode,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -381,10 +382,13 @@ export function processCodeMirrorRanges({
|
|||||||
if (!isChange) return null
|
if (!isChange) return null
|
||||||
const codeBasedSelections: Selections['graphSelections'] =
|
const codeBasedSelections: Selections['graphSelections'] =
|
||||||
codeMirrorRanges.map(({ from, to }) => {
|
codeMirrorRanges.map(({ from, to }) => {
|
||||||
const pathToNode = getNodePathFromSourceRange(ast, [from, to, true])
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
ast,
|
||||||
|
topLevelRange(from, to)
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [from, to, true],
|
range: topLevelRange(from, to),
|
||||||
pathToNode,
|
pathToNode,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -447,7 +451,10 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
|
|||||||
if (err(nodeMeta)) return
|
if (err(nodeMeta)) return
|
||||||
const node = nodeMeta.node
|
const node = nodeMeta.node
|
||||||
const groupHasCursor = codeBasedSelections.some((selection) => {
|
const groupHasCursor = codeBasedSelections.some((selection) => {
|
||||||
return isOverlap(selection?.codeRef?.range, [node.start, node.end, true])
|
return isOverlap(
|
||||||
|
selection?.codeRef?.range,
|
||||||
|
topLevelRange(node.start, node.end)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const color = groupHasCursor
|
const color = groupHasCursor
|
||||||
@ -575,7 +582,7 @@ export function getSelectionTypeDisplayText(
|
|||||||
([type, count]) =>
|
([type, count]) =>
|
||||||
`${count} ${type
|
`${count} ${type
|
||||||
.replace('wall', 'face')
|
.replace('wall', 'face')
|
||||||
.replace('solid2D', 'face')
|
.replace('solid2d', 'face')
|
||||||
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
.replace('segment', 'face')}${count > 1 ? 's' : ''}`
|
||||||
)
|
)
|
||||||
.toArray()
|
.toArray()
|
||||||
@ -650,7 +657,7 @@ export function codeToIdSelections(
|
|||||||
const artifact = engineCommandManager.artifactGraph.get(
|
const artifact = engineCommandManager.artifactGraph.get(
|
||||||
entry.artifact.solid2dId || ''
|
entry.artifact.solid2dId || ''
|
||||||
)
|
)
|
||||||
if (artifact?.type !== 'solid2D') {
|
if (artifact?.type !== 'solid2d') {
|
||||||
bestCandidate = {
|
bestCandidate = {
|
||||||
artifact: entry.artifact,
|
artifact: entry.artifact,
|
||||||
selection,
|
selection,
|
||||||
@ -873,7 +880,7 @@ export function updateSelections(
|
|||||||
return {
|
return {
|
||||||
artifact: artifact,
|
artifact: artifact,
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [node.start, node.end, true],
|
range: topLevelRange(node.start, node.end),
|
||||||
pathToNode: pathToNode,
|
pathToNode: pathToNode,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -887,7 +894,7 @@ export function updateSelections(
|
|||||||
if (err(node)) return node
|
if (err(node)) return node
|
||||||
pathToNodeBasedSelections.push({
|
pathToNodeBasedSelections.push({
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [node.node.start, node.node.end, true],
|
range: topLevelRange(node.node.start, node.node.end),
|
||||||
pathToNode: pathToNode,
|
pathToNode: pathToNode,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -68,10 +68,6 @@ interface TextToKclProps {
|
|||||||
data?: unknown
|
data?: unknown
|
||||||
) => unknown
|
) => unknown
|
||||||
navigate: NavigateFunction
|
navigate: NavigateFunction
|
||||||
commandBarSend: (
|
|
||||||
type: EventFrom<typeof commandBarMachine>,
|
|
||||||
data?: unknown
|
|
||||||
) => unknown
|
|
||||||
context: ContextFrom<typeof fileMachine>
|
context: ContextFrom<typeof fileMachine>
|
||||||
token?: string
|
token?: string
|
||||||
settings: {
|
settings: {
|
||||||
@ -84,7 +80,6 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
trimmedPrompt,
|
trimmedPrompt,
|
||||||
fileMachineSend,
|
fileMachineSend,
|
||||||
navigate,
|
navigate,
|
||||||
commandBarSend,
|
|
||||||
context,
|
context,
|
||||||
token,
|
token,
|
||||||
settings,
|
settings,
|
||||||
@ -96,7 +91,6 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
ToastTextToCadError({
|
ToastTextToCadError({
|
||||||
toastId,
|
toastId,
|
||||||
message,
|
message,
|
||||||
commandBarSend,
|
|
||||||
prompt: trimmedPrompt,
|
prompt: trimmedPrompt,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
@ -195,7 +189,7 @@ export async function submitAndAwaitTextToKcl({
|
|||||||
.toLowerCase()}${FILE_EXT}`
|
.toLowerCase()}${FILE_EXT}`
|
||||||
|
|
||||||
if (isDesktop()) {
|
if (isDesktop()) {
|
||||||
// We have to pre-emptively run our unique file name logic,
|
// We have to preemptively run our unique file name logic,
|
||||||
// so that we can pass the unique file name to the toast,
|
// so that we can pass the unique file name to the toast,
|
||||||
// and by extension the file-deletion-on-reject logic.
|
// and by extension the file-deletion-on-reject logic.
|
||||||
newFileName = getNextFileName({
|
newFileName = getNextFileName({
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user