Compare commits
25 Commits
v0.9.5
...
achalmers/
| Author | SHA1 | Date | |
|---|---|---|---|
| f3cb24cad7 | |||
| 4631b1e74d | |||
| 06d0fa1da5 | |||
| 6427b9da48 | |||
| 6c15a743a2 | |||
| d0930477ad | |||
| e5e30d231b | |||
| 9822576077 | |||
| 629f326f4c | |||
| 89b880d9ae | |||
| f6de0de1bf | |||
| 65ebb86b67 | |||
| cce8274902 | |||
| c515bef8e4 | |||
| b17e61d963 | |||
| d31d07d9c8 | |||
| 7aa2d63c21 | |||
| e1081b0ee6 | |||
| 59223279b7 | |||
| 8a4e717565 | |||
| 80b542ca18 | |||
| e4bfc863ea | |||
| 77ef255de4 | |||
| 64c3841079 | |||
| c7bb6bc845 |
3
.github/workflows/cargo-build.yml
vendored
3
.github/workflows/cargo-build.yml
vendored
@ -15,6 +15,9 @@ on:
|
|||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
- .github/workflows/cargo-build.yml
|
- .github/workflows/cargo-build.yml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo build
|
name: cargo build
|
||||||
jobs:
|
jobs:
|
||||||
cargobuild:
|
cargobuild:
|
||||||
|
|||||||
3
.github/workflows/cargo-clippy.yml
vendored
3
.github/workflows/cargo-clippy.yml
vendored
@ -15,6 +15,9 @@ on:
|
|||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
- '**.rs'
|
- '**.rs'
|
||||||
- .github/workflows/cargo-build.yml
|
- .github/workflows/cargo-build.yml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo clippy
|
name: cargo clippy
|
||||||
jobs:
|
jobs:
|
||||||
cargoclippy:
|
cargoclippy:
|
||||||
|
|||||||
3
.github/workflows/cargo-criterion.yml
vendored
3
.github/workflows/cargo-criterion.yml
vendored
@ -17,6 +17,9 @@ on:
|
|||||||
- .github/workflows/cargo-criterion.yml
|
- .github/workflows/cargo-criterion.yml
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo criterion
|
name: cargo criterion
|
||||||
jobs:
|
jobs:
|
||||||
cargocriterion:
|
cargocriterion:
|
||||||
|
|||||||
3
.github/workflows/cargo-fmt.yml
vendored
3
.github/workflows/cargo-fmt.yml
vendored
@ -18,6 +18,9 @@ on:
|
|||||||
permissions:
|
permissions:
|
||||||
packages: read
|
packages: read
|
||||||
contents: read
|
contents: read
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo fmt
|
name: cargo fmt
|
||||||
jobs:
|
jobs:
|
||||||
cargofmt:
|
cargofmt:
|
||||||
|
|||||||
3
.github/workflows/cargo-test.yml
vendored
3
.github/workflows/cargo-test.yml
vendored
@ -17,6 +17,9 @@ on:
|
|||||||
- .github/workflows/cargo-test.yml
|
- .github/workflows/cargo-test.yml
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
name: cargo test
|
name: cargo test
|
||||||
jobs:
|
jobs:
|
||||||
cargotest:
|
cargotest:
|
||||||
|
|||||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -8,6 +8,9 @@ on:
|
|||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||||
|
cancel-in-progress: true
|
||||||
jobs:
|
jobs:
|
||||||
check-format:
|
check-format:
|
||||||
runs-on: 'ubuntu-20.04'
|
runs-on: 'ubuntu-20.04'
|
||||||
|
|||||||
@ -7,3 +7,6 @@ coverage
|
|||||||
target
|
target
|
||||||
src/wasm-lib/pkg
|
src/wasm-lib/pkg
|
||||||
src/wasm-lib/kcl/bindings
|
src/wasm-lib/kcl/bindings
|
||||||
|
|
||||||
|
# XState generated files
|
||||||
|
src/machines/modelingMachine.typegen.ts
|
||||||
|
|||||||
@ -88,6 +88,13 @@ yarn test
|
|||||||
|
|
||||||
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testing Library E2E](https://testing-library.com/docs/react-testing-library/intro/) tests, in interactive mode by default.
|
||||||
|
|
||||||
|
For running the rust (not tauri rust though) only, you can
|
||||||
|
```bash
|
||||||
|
cd src/wasm-lib
|
||||||
|
cargo test
|
||||||
|
```
|
||||||
|
but you will need to have install ffmpeg prior to.
|
||||||
|
|
||||||
## Tauri
|
## Tauri
|
||||||
|
|
||||||
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
To spin up up tauri dev, `yarn install` and `yarn build:wasm` need to have been done before hand then
|
||||||
|
|||||||
1262
docs/kcl/std.json
1262
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
314
docs/kcl/std.md
314
docs/kcl/std.md
@ -48,7 +48,9 @@
|
|||||||
* [`show`](#show)
|
* [`show`](#show)
|
||||||
* [`sin`](#sin)
|
* [`sin`](#sin)
|
||||||
* [`sqrt`](#sqrt)
|
* [`sqrt`](#sqrt)
|
||||||
|
* [`startProfileAt`](#startProfileAt)
|
||||||
* [`startSketchAt`](#startSketchAt)
|
* [`startSketchAt`](#startSketchAt)
|
||||||
|
* [`startSketchOn`](#startSketchOn)
|
||||||
* [`tan`](#tan)
|
* [`tan`](#tan)
|
||||||
* [`tangentalArc`](#tangentalArc)
|
* [`tangentalArc`](#tangentalArc)
|
||||||
* [`tangentalArcTo`](#tangentalArcTo)
|
* [`tangentalArcTo`](#tangentalArcTo)
|
||||||
@ -120,6 +122,8 @@ angleToMatchLengthX(segment_name: string, to: number, sketch_group: SketchGroup)
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -204,6 +208,8 @@ angleToMatchLengthY(segment_name: string, to: number, sketch_group: SketchGroup)
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -298,6 +304,8 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -364,6 +372,8 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -454,6 +464,8 @@ angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -520,6 +532,8 @@ angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -610,6 +624,8 @@ angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -676,6 +692,8 @@ angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -767,6 +785,8 @@ angledLineThatIntersects(data: AngeledLineThatIntersectsData, sketch_group: Sket
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -833,6 +853,8 @@ angledLineThatIntersects(data: AngeledLineThatIntersectsData, sketch_group: Sket
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -923,6 +945,8 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -989,6 +1013,8 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1079,6 +1105,8 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1145,6 +1173,8 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1262,6 +1292,8 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1328,6 +1360,8 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1467,6 +1501,8 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1533,6 +1569,8 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1631,6 +1669,8 @@ close(sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1697,6 +1737,8 @@ close(sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1815,6 +1857,8 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -1996,6 +2040,8 @@ lastSegX(sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2078,6 +2124,8 @@ lastSegY(sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2233,6 +2281,8 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2299,6 +2349,8 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2387,6 +2439,8 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2453,6 +2507,8 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2693,6 +2749,8 @@ segAng(segment_name: string, sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2776,6 +2834,8 @@ segEndX(segment_name: string, sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2859,6 +2919,8 @@ segEndY(segment_name: string, sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -2942,6 +3004,8 @@ segLen(segment_name: string, sketch_group: SketchGroup) -> number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3024,6 +3088,8 @@ show(sketch: SketchGroup)
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3125,14 +3191,14 @@ sqrt(num: number) -> number
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
### startSketchAt
|
### startProfileAt
|
||||||
|
|
||||||
Start a sketch at a given point.
|
Start a profile at a given point.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
startSketchAt(data: LineData) -> SketchGroup
|
startProfileAt(data: LineData, plane: Plane) -> SketchGroup
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Arguments
|
#### Arguments
|
||||||
@ -3147,6 +3213,40 @@ startSketchAt(data: LineData) -> SketchGroup
|
|||||||
} |
|
} |
|
||||||
[number]
|
[number]
|
||||||
```
|
```
|
||||||
|
* `plane`: `Plane` - A plane.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The id of the plane.
|
||||||
|
id: uuid,
|
||||||
|
// Origin of the plane.
|
||||||
|
origin: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// Type for a plane.
|
||||||
|
value: string |
|
||||||
|
string,
|
||||||
|
// What should the plane’s X axis be?
|
||||||
|
xAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// What should the plane’s Y axis be?
|
||||||
|
yAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// The z-axis (normal).
|
||||||
|
zAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
@ -3155,6 +3255,8 @@ startSketchAt(data: LineData) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3216,6 +3318,188 @@ startSketchAt(data: LineData) -> SketchGroup
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### startSketchAt
|
||||||
|
|
||||||
|
Start a sketch at a given point on the 'XY' plane.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
startSketchAt(data: LineData) -> SketchGroup
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `data`: `LineData` - Data to draw a line.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The tag.
|
||||||
|
tag: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
} |
|
||||||
|
[number]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `SketchGroup` - A sketch group is a collection of paths.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The id of the sketch group.
|
||||||
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
|
// The position of the sketch group.
|
||||||
|
position: [number],
|
||||||
|
// The rotation of the sketch group.
|
||||||
|
rotation: [number],
|
||||||
|
// The starting path.
|
||||||
|
start: {
|
||||||
|
// The from point.
|
||||||
|
from: [number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
},
|
||||||
|
// The paths in the sketch group.
|
||||||
|
value: [{
|
||||||
|
// The from point.
|
||||||
|
from: [number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
type: string,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
type: string,
|
||||||
|
// The x coordinate.
|
||||||
|
x: number,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
type: string,
|
||||||
|
// The x coordinate.
|
||||||
|
x: number,
|
||||||
|
// The y coordinate.
|
||||||
|
y: number,
|
||||||
|
} |
|
||||||
|
{
|
||||||
|
// The from point.
|
||||||
|
from: [number],
|
||||||
|
// The name of the path.
|
||||||
|
name: string,
|
||||||
|
// The to point.
|
||||||
|
to: [number],
|
||||||
|
type: string,
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### startSketchOn
|
||||||
|
|
||||||
|
Start a sketch at a given point.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
startSketchOn(data: PlaneData) -> Plane
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Arguments
|
||||||
|
|
||||||
|
* `data`: `PlaneData` - Data for a plane.
|
||||||
|
```
|
||||||
|
string |
|
||||||
|
string |
|
||||||
|
string |
|
||||||
|
string |
|
||||||
|
string |
|
||||||
|
string |
|
||||||
|
{
|
||||||
|
plane: {
|
||||||
|
// Origin of the plane.
|
||||||
|
origin: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// What should the plane’s X axis be?
|
||||||
|
x_axis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// What should the plane’s Y axis be?
|
||||||
|
y_axis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// The z-axis (normal).
|
||||||
|
z_axis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
* `Plane` - A plane.
|
||||||
|
```
|
||||||
|
{
|
||||||
|
// The id of the plane.
|
||||||
|
id: uuid,
|
||||||
|
// Origin of the plane.
|
||||||
|
origin: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// Type for a plane.
|
||||||
|
value: string |
|
||||||
|
string,
|
||||||
|
// What should the plane’s X axis be?
|
||||||
|
xAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// What should the plane’s Y axis be?
|
||||||
|
yAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
// The z-axis (normal).
|
||||||
|
zAxis: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
z: number,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### tan
|
### tan
|
||||||
|
|
||||||
Computes the tangent of a number (in radians).
|
Computes the tangent of a number (in radians).
|
||||||
@ -3269,6 +3553,8 @@ tangentalArc(data: TangentalArcData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3335,6 +3621,8 @@ tangentalArc(data: TangentalArcData, sketch_group: SketchGroup) -> SketchGroup
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3423,6 +3711,8 @@ tangentalArcTo(data: TangentalArcToData, sketch_group: SketchGroup) -> SketchGro
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3489,6 +3779,8 @@ tangentalArcTo(data: TangentalArcToData, sketch_group: SketchGroup) -> SketchGro
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3596,6 +3888,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3662,6 +3956,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3750,6 +4046,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3816,6 +4114,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3904,6 +4204,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -3970,6 +4272,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -4058,6 +4362,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
@ -4124,6 +4430,8 @@ number
|
|||||||
{
|
{
|
||||||
// The id of the sketch group.
|
// The id of the sketch group.
|
||||||
id: uuid,
|
id: uuid,
|
||||||
|
// The plane id of the sketch group.
|
||||||
|
planeId: uuid,
|
||||||
// The position of the sketch group.
|
// The position of the sketch group.
|
||||||
position: [number],
|
position: [number],
|
||||||
// The rotation of the sketch group.
|
// The rotation of the sketch group.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "untitled-app",
|
"name": "untitled-app",
|
||||||
"version": "0.9.5",
|
"version": "0.10.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/autocomplete": "^6.9.0",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||||
"@headlessui/react": "^1.7.13",
|
"@headlessui/react": "^1.7.13",
|
||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@kittycad/lib": "^0.0.40",
|
"@kittycad/lib": "^0.0.43",
|
||||||
"@lezer/javascript": "^1.4.7",
|
"@lezer/javascript": "^1.4.7",
|
||||||
"@open-rpc/client-js": "^1.8.1",
|
"@open-rpc/client-js": "^1.8.1",
|
||||||
"@react-hook/resize-observer": "^1.2.6",
|
"@react-hook/resize-observer": "^1.2.6",
|
||||||
@ -25,6 +25,7 @@
|
|||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"@uiw/react-codemirror": "^4.21.13",
|
"@uiw/react-codemirror": "^4.21.13",
|
||||||
|
"@xstate/inspect": "^0.8.0",
|
||||||
"@xstate/react": "^3.2.2",
|
"@xstate/react": "^3.2.2",
|
||||||
"crypto-js": "^4.1.1",
|
"crypto-js": "^4.1.1",
|
||||||
"debounce-promise": "^3.1.2",
|
"debounce-promise": "^3.1.2",
|
||||||
@ -32,7 +33,7 @@
|
|||||||
"fuse.js": "^6.6.2",
|
"fuse.js": "^6.6.2",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"json-rpc-2.0": "^1.6.0",
|
"json-rpc-2.0": "^1.6.0",
|
||||||
"re-resizable": "^6.9.9",
|
"re-resizable": "^6.9.11",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
|
|||||||
10
src-tauri/Cargo.lock
generated
10
src-tauri/Cargo.lock
generated
@ -1658,9 +1658,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.28"
|
version = "0.2.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35b2f9302648dbb06fd7121687f9505fc3179eba84111a06d76b246e3158f5dc"
|
checksum = "539b323537b877fc8dd130362b8f1af9af8051c19208bb8bfd816ab7c330f2bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -3712,9 +3712,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri"
|
name = "tauri"
|
||||||
version = "1.5.0"
|
version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72aee3277d0a0df01472cc704ab5934a51a1f25348838df17bfb3c5cb727880c"
|
checksum = "0238c5063bf9613054149a1b6bce4935922e532b7d8211f36989a490a79806be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64 0.21.2",
|
"base64 0.21.2",
|
||||||
@ -3828,7 +3828,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-plugin-fs-extra"
|
name = "tauri-plugin-fs-extra"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#9b96996b5a90a6a57d587ce4312975f13a4d8bc2"
|
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#9f27e6e4415ddf6c40f846d50c0d95c768cded77"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
|
|||||||
@ -16,11 +16,11 @@ tauri-build = { version = "1.5.0", features = [] }
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
kittycad = "0.2.28"
|
kittycad = "0.2.31"
|
||||||
oauth2 = "4.4.2"
|
oauth2 = "4.4.2"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tauri = { version = "1.5.0", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
tauri = { version = "1.5.1", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "updater", "devtools"] }
|
||||||
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||||
tokio = { version = "1.32.0", features = ["time"] }
|
tokio = { version = "1.32.0", features = ["time"] }
|
||||||
toml = "0.8.2"
|
toml = "0.8.2"
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "kittycad-modeling",
|
"productName": "kittycad-modeling",
|
||||||
"version": "0.9.5"
|
"version": "0.10.0"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|||||||
@ -1,9 +1,16 @@
|
|||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import { App } from './App'
|
import { App } from './App'
|
||||||
import { describe, test, vi } from 'vitest'
|
import { describe, test, vi } from 'vitest'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import {
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
createMemoryRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
} from 'react-router-dom'
|
||||||
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
import { GlobalStateProvider } from './components/GlobalStateProvider'
|
||||||
import CommandBarProvider from 'components/CommandBar'
|
import CommandBarProvider from 'components/CommandBar'
|
||||||
|
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||||
|
import { BROWSER_FILE_NAME } from 'Router'
|
||||||
|
|
||||||
let listener: ((rect: any) => void) | undefined = undefined
|
let listener: ((rect: any) => void) | undefined = undefined
|
||||||
;(global as any).ResizeObserver = class ResizeObserver {
|
;(global as any).ResizeObserver = class ResizeObserver {
|
||||||
@ -24,7 +31,7 @@ describe('App tests', () => {
|
|||||||
>
|
>
|
||||||
return {
|
return {
|
||||||
...actual,
|
...actual,
|
||||||
useParams: () => ({ id: 'new' }),
|
useParams: () => ({ id: BROWSER_FILE_NAME }),
|
||||||
useLoaderData: () => ({ code: null }),
|
useLoaderData: () => ({ code: null }),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -41,12 +48,26 @@ describe('App tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
// wrap in router and xState context
|
// We have to use a memory router in the testing environment,
|
||||||
return (
|
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
||||||
<BrowserRouter>
|
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
||||||
<CommandBarProvider>
|
const router = createMemoryRouter(
|
||||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
createRoutesFromElements(
|
||||||
</CommandBarProvider>
|
<Route
|
||||||
</BrowserRouter>
|
path="/file/:id"
|
||||||
|
element={
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>
|
||||||
|
<ModelingMachineProvider>{children}</ModelingMachineProvider>
|
||||||
|
</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
initialEntries: ['/file/new'],
|
||||||
|
initialIndex: 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
return <RouterProvider router={router} />
|
||||||
}
|
}
|
||||||
|
|||||||
88
src/App.tsx
88
src/App.tsx
@ -1,4 +1,4 @@
|
|||||||
import { useRef, useEffect, useCallback, MouseEventHandler } from 'react'
|
import { useEffect, useCallback, MouseEventHandler } from 'react'
|
||||||
import { DebugPanel } from './components/DebugPanel'
|
import { DebugPanel } from './components/DebugPanel'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { PaneType, useStore } from './useStore'
|
import { PaneType, useStore } from './useStore'
|
||||||
@ -29,45 +29,33 @@ import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/mod
|
|||||||
import { CodeMenu } from 'components/CodeMenu'
|
import { CodeMenu } from 'components/CodeMenu'
|
||||||
import { TextEditor } from 'components/TextEditor'
|
import { TextEditor } from 'components/TextEditor'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
|
||||||
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
|
||||||
import { engineCommandManager } from './lang/std/engineConnection'
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
export function App() {
|
export function App() {
|
||||||
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
const { code: loadedCode, project } = useLoaderData() as IndexLoaderData
|
||||||
|
|
||||||
const streamRef = useRef<HTMLDivElement>(null)
|
|
||||||
useHotKeyListener()
|
useHotKeyListener()
|
||||||
const {
|
const {
|
||||||
setCode,
|
|
||||||
buttonDownInStream,
|
buttonDownInStream,
|
||||||
openPanes,
|
openPanes,
|
||||||
setOpenPanes,
|
setOpenPanes,
|
||||||
didDragInStream,
|
didDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
guiMode,
|
|
||||||
setGuiMode,
|
|
||||||
executeAst,
|
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
guiMode: s.guiMode,
|
|
||||||
setGuiMode: s.setGuiMode,
|
|
||||||
setCode: s.setCode,
|
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
buttonDownInStream: s.buttonDownInStream,
|
||||||
openPanes: s.openPanes,
|
openPanes: s.openPanes,
|
||||||
setOpenPanes: s.setOpenPanes,
|
setOpenPanes: s.setOpenPanes,
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
executeAst: s.executeAst,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const {
|
const { settings } = useGlobalStateContext()
|
||||||
auth: {
|
const { showDebugPanel, onboardingStatus, cameraControls, theme } =
|
||||||
context: { token },
|
settings?.context || {}
|
||||||
},
|
const { state, send } = useModelingContext()
|
||||||
settings: {
|
|
||||||
context: { showDebugPanel, onboardingStatus, cameraControls, theme },
|
|
||||||
},
|
|
||||||
} = useGlobalStateContext()
|
|
||||||
|
|
||||||
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
const editorTheme = theme === Themes.System ? getSystemTheme() : theme
|
||||||
|
|
||||||
@ -84,50 +72,7 @@ export function App() {
|
|||||||
useHotkeys('shift + l', () => togglePane('logs'))
|
useHotkeys('shift + l', () => togglePane('logs'))
|
||||||
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
useHotkeys('shift + e', () => togglePane('kclErrors'))
|
||||||
useHotkeys('shift + d', () => togglePane('debug'))
|
useHotkeys('shift + d', () => togglePane('debug'))
|
||||||
useHotkeys('esc', () => {
|
useHotkeys('esc', () => send('Cancel'))
|
||||||
if (guiMode.mode === 'sketch') {
|
|
||||||
if (guiMode.sketchMode === 'selectFace') return
|
|
||||||
if (guiMode.sketchMode === 'sketchEdit') {
|
|
||||||
// TODO: share this with Toolbar's "Exit sketch" button
|
|
||||||
// exiting sketch should be done consistently across all exits
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'edit_mode_exit' },
|
|
||||||
})
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
|
||||||
})
|
|
||||||
setGuiMode({ mode: 'default' })
|
|
||||||
// this is necessary to get the UI back into a consistent
|
|
||||||
// state right now, hopefully won't need to rerender
|
|
||||||
// when exiting sketch mode in the future
|
|
||||||
executeAst()
|
|
||||||
} else {
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'set_tool',
|
|
||||||
tool: 'select',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
rotation: guiMode.rotation,
|
|
||||||
position: guiMode.position,
|
|
||||||
pathToNode: guiMode.pathToNode,
|
|
||||||
pathId: guiMode.pathId,
|
|
||||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setGuiMode({ mode: 'default' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
const paneOpacity = [onboardingPaths.CAMERA, onboardingPaths.STREAMING].some(
|
||||||
(p) => p === onboardingStatus
|
(p) => p === onboardingStatus
|
||||||
@ -141,17 +86,16 @@ export function App() {
|
|||||||
// on mount, and overwrite any locally-stored code
|
// on mount, and overwrite any locally-stored code
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isTauri() && loadedCode !== null) {
|
if (isTauri() && loadedCode !== null) {
|
||||||
setCode(loadedCode)
|
kclManager.setCode(loadedCode)
|
||||||
}
|
}
|
||||||
return () => {
|
return () => {
|
||||||
// Clear code on unmount if in desktop app
|
// Clear code on unmount if in desktop app
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
setCode('')
|
kclManager.setCode('')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [loadedCode, setCode])
|
}, [loadedCode])
|
||||||
|
|
||||||
useSetupEngineManager(streamRef, token)
|
|
||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
const debounceSocketSend = throttle<EngineCommand>((message) => {
|
||||||
@ -169,10 +113,7 @@ export function App() {
|
|||||||
|
|
||||||
const newCmdId = uuidv4()
|
const newCmdId = uuidv4()
|
||||||
if (buttonDownInStream === undefined) {
|
if (buttonDownInStream === undefined) {
|
||||||
if (
|
if (state.matches('Sketch.Line Tool')) {
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any)
|
|
||||||
) {
|
|
||||||
debounceSocketSend({
|
debounceSocketSend({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
@ -192,7 +133,7 @@ export function App() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
if (state.matches('Sketch.Move Tool')) {
|
||||||
debounceSocketSend({
|
debounceSocketSend({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
@ -232,9 +173,8 @@ export function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="h-screen overflow-hidden relative flex flex-col cursor-pointer select-none"
|
className="relative h-full flex flex-col"
|
||||||
onMouseMove={handleMouseMove}
|
onMouseMove={handleMouseMove}
|
||||||
ref={streamRef}
|
|
||||||
>
|
>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
className={
|
className={
|
||||||
|
|||||||
@ -3,10 +3,8 @@ import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
|||||||
|
|
||||||
// Wrapper around protected routes, used in src/Router.tsx
|
// Wrapper around protected routes, used in src/Router.tsx
|
||||||
export const Auth = ({ children }: React.PropsWithChildren) => {
|
export const Auth = ({ children }: React.PropsWithChildren) => {
|
||||||
const {
|
const { auth } = useGlobalStateContext()
|
||||||
auth: { state },
|
const isLoggingIn = auth?.state.matches('checkIfLoggedIn')
|
||||||
} = useGlobalStateContext()
|
|
||||||
const isLoggingIn = state.matches('checkIfLoggedIn')
|
|
||||||
|
|
||||||
return isLoggingIn ? (
|
return isLoggingIn ? (
|
||||||
<Loading>Loading KittyCAD Modeling App...</Loading>
|
<Loading>Loading KittyCAD Modeling App...</Loading>
|
||||||
|
|||||||
@ -40,6 +40,8 @@ import { ContextFrom } from 'xstate'
|
|||||||
import CommandBarProvider from 'components/CommandBar'
|
import CommandBarProvider from 'components/CommandBar'
|
||||||
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
import { TEST, VITE_KC_SENTRY_DSN } from './env'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
|
import ModelingMachineProvider from 'components/ModelingMachineProvider'
|
||||||
|
import { KclContextProvider } from 'lang/KclSinglton'
|
||||||
|
|
||||||
if (VITE_KC_SENTRY_DSN && !TEST) {
|
if (VITE_KC_SENTRY_DSN && !TEST) {
|
||||||
Sentry.init({
|
Sentry.init({
|
||||||
@ -94,6 +96,8 @@ export const paths = {
|
|||||||
) as typeof onboardingPaths,
|
) as typeof onboardingPaths,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const BROWSER_FILE_NAME = 'new'
|
||||||
|
|
||||||
export type IndexLoaderData = {
|
export type IndexLoaderData = {
|
||||||
code: string | null
|
code: string | null
|
||||||
project?: ProjectWithEntryPointMetadata
|
project?: ProjectWithEntryPointMetadata
|
||||||
@ -129,7 +133,9 @@ const router = createBrowserRouter(
|
|||||||
{
|
{
|
||||||
path: paths.INDEX,
|
path: paths.INDEX,
|
||||||
loader: () =>
|
loader: () =>
|
||||||
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
|
isTauri()
|
||||||
|
? redirect(paths.HOME)
|
||||||
|
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
|
||||||
errorElement: <ErrorPage />,
|
errorElement: <ErrorPage />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -137,7 +143,11 @@ const router = createBrowserRouter(
|
|||||||
element: (
|
element: (
|
||||||
<Auth>
|
<Auth>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<App />
|
<KclContextProvider>
|
||||||
|
<ModelingMachineProvider>
|
||||||
|
<App />
|
||||||
|
</ModelingMachineProvider>
|
||||||
|
</KclContextProvider>
|
||||||
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
{!isTauri() && import.meta.env.PROD && <DownloadAppBanner />}
|
||||||
</Auth>
|
</Auth>
|
||||||
),
|
),
|
||||||
@ -167,7 +177,7 @@ const router = createBrowserRouter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.id && params.id !== 'new') {
|
if (params.id && params.id !== BROWSER_FILE_NAME) {
|
||||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||||
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
|
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
|
||||||
const entrypoint_metadata = await metadata(
|
const entrypoint_metadata = await metadata(
|
||||||
@ -212,7 +222,7 @@ const router = createBrowserRouter(
|
|||||||
),
|
),
|
||||||
loader: async () => {
|
loader: async () => {
|
||||||
if (!isTauri()) {
|
if (!isTauri()) {
|
||||||
return redirect(paths.FILE + '/new')
|
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
|
||||||
}
|
}
|
||||||
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
|
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
|
||||||
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<
|
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<
|
||||||
|
|||||||
322
src/Toolbar.tsx
322
src/Toolbar.tsx
@ -1,24 +1,26 @@
|
|||||||
import { useStore, toolTips, ToolTip } from './useStore'
|
import { useStore, toolTips, ToolTip } from './useStore'
|
||||||
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
import { extrudeSketch, sketchOnExtrudedFace } from './lang/modifyAst'
|
||||||
import { getNodePathFromSourceRange } from './lang/queryAst'
|
import { getNodePathFromSourceRange } from './lang/queryAst'
|
||||||
import { HorzVert } from './components/Toolbar/HorzVert'
|
// import { HorzVert } from './components/Toolbar/HorzVert'
|
||||||
import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
|
// import { RemoveConstrainingValues } from './components/Toolbar/RemoveConstrainingValues'
|
||||||
import { EqualLength } from './components/Toolbar/EqualLength'
|
// import { EqualLength } from './components/Toolbar/EqualLength'
|
||||||
import { EqualAngle } from './components/Toolbar/EqualAngle'
|
// import { EqualAngle } from './components/Toolbar/EqualAngle'
|
||||||
import { Intersect } from './components/Toolbar/Intersect'
|
// import { Intersect } from './components/Toolbar/Intersect'
|
||||||
import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
|
// import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
|
||||||
import { SetAngleLength } from './components/Toolbar/setAngleLength'
|
// import { SetAngleLength } from './components/Toolbar/setAngleLength'
|
||||||
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
// import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
|
||||||
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
// import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
|
||||||
import { Fragment, useEffect } from 'react'
|
import { Fragment, WheelEvent, useRef, useMemo } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { Popover, Transition } from '@headlessui/react'
|
import { Popover, Transition } from '@headlessui/react'
|
||||||
import styles from './Toolbar.module.css'
|
import styles from './Toolbar.module.css'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useAppMode } from 'hooks/useAppMode'
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { engineCommandManager } from './lang/std/engineConnection'
|
import { engineCommandManager } from './lang/std/engineConnection'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export const sketchButtonClassnames = {
|
export const sketchButtonClassnames = {
|
||||||
background:
|
background:
|
||||||
@ -44,216 +46,140 @@ const sketchFnLabels: Record<ToolTip | 'sketch_line' | 'move', string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
const {
|
const { state, send, context } = useModelingContext()
|
||||||
setGuiMode,
|
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
|
||||||
guiMode,
|
const pathId = useMemo(
|
||||||
selectionRanges,
|
() =>
|
||||||
ast,
|
isCursorInSketchCommandRange(
|
||||||
updateAst,
|
engineCommandManager.artifactMap,
|
||||||
programMemory,
|
context.selectionRanges
|
||||||
executeAst,
|
),
|
||||||
} = useStore((s) => ({
|
[engineCommandManager.artifactMap, context.selectionRanges]
|
||||||
guiMode: s.guiMode,
|
)
|
||||||
setGuiMode: s.setGuiMode,
|
|
||||||
selectionRanges: s.selectionRanges,
|
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
|
||||||
ast: s.ast,
|
const span = toolbarButtonsRef.current
|
||||||
updateAst: s.updateAst,
|
if (!span) {
|
||||||
programMemory: s.programMemory,
|
return
|
||||||
executeAst: s.executeAst,
|
}
|
||||||
}))
|
|
||||||
useAppMode()
|
span.scrollLeft = span.scrollLeft += ev.deltaY
|
||||||
|
}
|
||||||
|
|
||||||
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
|
||||||
return (
|
return (
|
||||||
<span className={styles.toolbarButtons + ' ' + className}>
|
<span
|
||||||
{guiMode.mode === 'default' && (
|
ref={toolbarButtonsRef}
|
||||||
|
onWheel={handleToolbarButtonsWheelEvent}
|
||||||
|
className={styles.toolbarButtons + ' ' + className}
|
||||||
|
>
|
||||||
|
{state.nextEvents.includes('Enter sketch') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => send({ type: 'Enter sketch' })}
|
||||||
setGuiMode({
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'selectFace',
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
className="group"
|
className="group"
|
||||||
>
|
>
|
||||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Start Sketch
|
Start Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{guiMode.mode === 'canEditExtrude' && (
|
{state.nextEvents.includes('Enter sketch') && pathId && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => send({ type: 'Enter sketch' })}
|
||||||
if (!ast) return
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selectionRanges.codeBasedSelections[0].range
|
|
||||||
)
|
|
||||||
const { modifiedAst } = sketchOnExtrudedFace(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
programMemory
|
|
||||||
)
|
|
||||||
updateAst(modifiedAst, true)
|
|
||||||
}}
|
|
||||||
className="group"
|
|
||||||
>
|
|
||||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
|
||||||
Sketch on Face
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
{guiMode.mode === 'canEditSketch' && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selectionRanges.codeBasedSelections[0].range
|
|
||||||
)
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'enterSketchEdit',
|
|
||||||
pathToNode: pathToNode,
|
|
||||||
rotation: [0, 0, 0, 1],
|
|
||||||
position: [0, 0, 0],
|
|
||||||
pathId: guiMode.pathId,
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
className="group"
|
className="group"
|
||||||
>
|
>
|
||||||
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
<ActionIcon icon="sketch" className="!p-0.5" size="md" />
|
||||||
Edit Sketch
|
Edit Sketch
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{guiMode.mode === 'canEditSketch' && (
|
{state.nextEvents.includes('Cancel') && !state.matches('idle') && (
|
||||||
<>
|
<button onClick={() => send({ type: 'Cancel' })} className="group">
|
||||||
<button
|
<ActionIcon icon="exit" className="!p-0.5" size="md" />
|
||||||
onClick={() => {
|
Exit Sketch
|
||||||
if (!ast) return
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selectionRanges.codeBasedSelections[0].range
|
|
||||||
)
|
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
|
||||||
ast,
|
|
||||||
pathToNode
|
|
||||||
)
|
|
||||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
|
||||||
}}
|
|
||||||
className="group"
|
|
||||||
>
|
|
||||||
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
|
||||||
Extrude
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
if (!ast) return
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
|
||||||
ast,
|
|
||||||
selectionRanges.codeBasedSelections[0].range
|
|
||||||
)
|
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
updateAst(modifiedAst, true, { focusPath: pathToExtrudeArg })
|
|
||||||
}}
|
|
||||||
className="group"
|
|
||||||
>
|
|
||||||
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
|
||||||
Extrude as new
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{guiMode.mode === 'sketch' && (
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'edit_mode_exit' },
|
|
||||||
})
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
|
||||||
})
|
|
||||||
|
|
||||||
setGuiMode({ mode: 'default' })
|
|
||||||
executeAst()
|
|
||||||
}}
|
|
||||||
className="group"
|
|
||||||
>
|
|
||||||
<ActionIcon
|
|
||||||
icon="exit"
|
|
||||||
className="!p-0.5"
|
|
||||||
bgClassName={sketchButtonClassnames.background}
|
|
||||||
iconClassName={sketchButtonClassnames.icon}
|
|
||||||
size="md"
|
|
||||||
/>
|
|
||||||
Exit sketch
|
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{toolTips
|
{state.matches('Sketch') && !state.matches('idle') && (
|
||||||
.filter(
|
<button
|
||||||
// (sketchFnName) => !['angledLineThatIntersects'].includes(sketchFnName)
|
onClick={() =>
|
||||||
(sketchFnName) => ['sketch_line', 'move'].includes(sketchFnName)
|
state.matches('Sketch.Line Tool')
|
||||||
)
|
? send('CancelSketch')
|
||||||
.map((sketchFnName) => {
|
: send('Equip tool')
|
||||||
if (
|
}
|
||||||
guiMode.mode !== 'sketch' ||
|
className={
|
||||||
!('isTooltip' in guiMode || guiMode.sketchMode === 'sketchEdit')
|
'group ' +
|
||||||
|
(state.matches('Sketch.Line Tool')
|
||||||
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ActionIcon icon="line" className="!p-0.5" size="md" />
|
||||||
|
Line
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{state.matches('Sketch') && (
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
state.matches('Sketch.Move Tool')
|
||||||
|
? send('CancelSketch')
|
||||||
|
: send('Equip move tool')
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
'group ' +
|
||||||
|
(state.matches('Sketch.Move Tool')
|
||||||
|
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
||||||
|
: '')
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ActionIcon icon="move" className="!p-0.5" size="md" />
|
||||||
|
Move
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
{state.matches('Sketch.SketchIdle') &&
|
||||||
|
state.nextEvents
|
||||||
|
.filter(
|
||||||
|
(eventName) =>
|
||||||
|
eventName.includes('Make segment') ||
|
||||||
|
eventName.includes('Constrain')
|
||||||
)
|
)
|
||||||
return null
|
.map((eventName) => (
|
||||||
return (
|
|
||||||
<button
|
<button
|
||||||
key={sketchFnName}
|
key={eventName}
|
||||||
onClick={() => {
|
onClick={() => send(eventName)}
|
||||||
engineCommandManager.sendSceneCommand({
|
className="group"
|
||||||
type: 'modeling_cmd_req',
|
disabled={
|
||||||
cmd_id: uuidv4(),
|
!state.nextEvents
|
||||||
cmd: {
|
.filter((event) => state.can(event as any))
|
||||||
type: 'set_tool',
|
.includes(eventName)
|
||||||
tool:
|
|
||||||
guiMode.sketchMode === sketchFnName
|
|
||||||
? 'select'
|
|
||||||
: (sketchFnName as any),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setGuiMode({
|
|
||||||
...guiMode,
|
|
||||||
...(guiMode.sketchMode === sketchFnName
|
|
||||||
? {
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
// todo: ...guiMod is adding isTooltip: true, will probably just fix with xstate migtaion
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
sketchMode: sketchFnName,
|
|
||||||
waitingFirstClick: true,
|
|
||||||
isTooltip: true,
|
|
||||||
pathId: guiMode.pathId,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
}}
|
|
||||||
className={
|
|
||||||
'group ' +
|
|
||||||
(guiMode.sketchMode === sketchFnName
|
|
||||||
? '!text-fern-70 !bg-fern-10 !dark:text-fern-20 !border-fern-50'
|
|
||||||
: '')
|
|
||||||
}
|
}
|
||||||
|
title={eventName}
|
||||||
>
|
>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
icon={sketchFnName.includes('line') ? 'line' : 'move'}
|
icon={'line'} // TODO
|
||||||
className="!p-0.5"
|
|
||||||
bgClassName={sketchButtonClassnames.background}
|
bgClassName={sketchButtonClassnames.background}
|
||||||
iconClassName={sketchButtonClassnames.icon}
|
iconClassName={sketchButtonClassnames.icon}
|
||||||
size="md"
|
size="md"
|
||||||
/>
|
/>
|
||||||
{sketchFnLabels[sketchFnName]}
|
{eventName
|
||||||
|
.replace('Make segment ', '')
|
||||||
|
.replace('Constrain ', '')}
|
||||||
</button>
|
</button>
|
||||||
)
|
))}
|
||||||
})}
|
{state.matches('idle') && (
|
||||||
<HorzVert horOrVert="horizontal" />
|
<button
|
||||||
|
onClick={() => send('extrude intent')}
|
||||||
|
disabled={!state.can('extrude intent')}
|
||||||
|
className="group"
|
||||||
|
title={
|
||||||
|
state.can('extrude intent')
|
||||||
|
? 'extrude'
|
||||||
|
: 'sketches need to be closed, or not already extruded'
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ActionIcon icon="extrude" className="!p-0.5" size="md" />
|
||||||
|
Extrude
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <HorzVert horOrVert="horizontal" />
|
||||||
<HorzVert horOrVert="vertical" />
|
<HorzVert horOrVert="vertical" />
|
||||||
<EqualLength />
|
<EqualLength />
|
||||||
<EqualAngle />
|
<EqualAngle />
|
||||||
@ -269,16 +195,20 @@ export const Toolbar = () => {
|
|||||||
<SetAngleLength angleOrLength="setLength" />
|
<SetAngleLength angleOrLength="setLength" />
|
||||||
<Intersect />
|
<Intersect />
|
||||||
<RemoveConstrainingValues />
|
<RemoveConstrainingValues />
|
||||||
<SetAngleBetween />
|
<SetAngleBetween /> */}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover className={styles.toolbarWrapper + ' ' + guiMode.mode}>
|
<Popover
|
||||||
|
className={
|
||||||
|
styles.toolbarWrapper + state.matches('Sketch') ? ' sketch' : ''
|
||||||
|
}
|
||||||
|
>
|
||||||
<div className={styles.toolbar}>
|
<div className={styles.toolbar}>
|
||||||
<span className={styles.toolbarCap + ' ' + styles.label}>
|
<span className={styles.toolbarCap + ' ' + styles.label}>
|
||||||
{guiMode.mode === 'sketch' ? '2D' : '3D'}
|
{state.matches('Sketch') ? '2D' : '3D'}
|
||||||
</span>
|
</span>
|
||||||
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
<menu className="flex-1 gap-2 py-0.5 overflow-hidden whitespace-nowrap">
|
||||||
<ToolbarButtons />
|
<ToolbarButtons />
|
||||||
@ -314,7 +244,7 @@ export const Toolbar = () => {
|
|||||||
<p
|
<p
|
||||||
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
|
className={`${styles.toolbarCap} ${styles.label} !self-center rounded-r-full w-fit`}
|
||||||
>
|
>
|
||||||
You're in {guiMode.mode === 'sketch' ? '2D' : '3D'}
|
You're in {state.matches('Sketch') ? '2D' : '3D'}
|
||||||
</p>
|
</p>
|
||||||
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
|
<Popover.Button className="p-2 flex items-center justify-center rounded-sm bg-chalkboard-20 text-chalkboard-110 dark:bg-chalkboard-70 dark:text-chalkboard-20 border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-60">
|
||||||
<FontAwesomeIcon icon={faX} className="w-4 h-4" />
|
<FontAwesomeIcon icon={faX} className="w-4 h-4" />
|
||||||
|
|||||||
@ -20,11 +20,8 @@ export const AppHeader = ({
|
|||||||
className = '',
|
className = '',
|
||||||
enableMenu = false,
|
enableMenu = false,
|
||||||
}: AppHeaderProps) => {
|
}: AppHeaderProps) => {
|
||||||
const {
|
const { auth } = useGlobalStateContext()
|
||||||
auth: {
|
const user = auth?.context?.user
|
||||||
context: { user },
|
|
||||||
},
|
|
||||||
} = useGlobalStateContext()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header
|
<header
|
||||||
|
|||||||
@ -1,18 +1,18 @@
|
|||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
|
|
||||||
export function AstExplorer() {
|
export function AstExplorer() {
|
||||||
const { ast, setHighlightRange, selectionRanges } = useStore((s) => ({
|
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
||||||
ast: s.ast,
|
const { context } = useModelingContext()
|
||||||
setHighlightRange: s.setHighlightRange,
|
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
}))
|
|
||||||
const pathToNode = getNodePathFromSourceRange(
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
ast,
|
// TODO maybe need to have callback to make sure it stays in sync
|
||||||
selectionRanges.codeBasedSelections?.[0]?.range
|
kclManager.ast,
|
||||||
|
context.selectionRanges.codeBasedSelections?.[0]?.range
|
||||||
)
|
)
|
||||||
const node = getNodeFromPath(ast, pathToNode).node
|
const node = getNodeFromPath(kclManager.ast, pathToNode).node
|
||||||
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
const [filterKeys, setFilterKeys] = useState<string[]>(['start', 'end'])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -46,7 +46,11 @@ export function AstExplorer() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
|
<pre className=" text-xs overflow-y-auto" style={{ width: '300px' }}>
|
||||||
<DisplayObj obj={ast} filterKeys={filterKeys} node={node} />
|
<DisplayObj
|
||||||
|
obj={kclManager.ast}
|
||||||
|
filterKeys={filterKeys}
|
||||||
|
node={node}
|
||||||
|
/>
|
||||||
</pre>
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -84,10 +88,8 @@ function DisplayObj({
|
|||||||
filterKeys: string[]
|
filterKeys: string[]
|
||||||
node: any
|
node: any
|
||||||
}) {
|
}) {
|
||||||
const { setHighlightRange, setCursor2 } = useStore((s) => ({
|
const setHighlightRange = useStore((s) => s.setHighlightRange)
|
||||||
setHighlightRange: s.setHighlightRange,
|
const { send } = useModelingContext()
|
||||||
setCursor2: s.setCursor2,
|
|
||||||
}))
|
|
||||||
const ref = useRef<HTMLPreElement>(null)
|
const ref = useRef<HTMLPreElement>(null)
|
||||||
const [hasCursor, setHasCursor] = useState(false)
|
const [hasCursor, setHasCursor] = useState(false)
|
||||||
const [isCollapsed, setIsCollapsed] = useState(false)
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
||||||
@ -118,7 +120,16 @@ function DisplayObj({
|
|||||||
setHighlightRange([obj?.start || 0, obj.end])
|
setHighlightRange([obj?.start || 0, obj.end])
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
setCursor2({ type: 'default', range: [obj?.start || 0, obj.end || 0] })
|
send({
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: {
|
||||||
|
type: 'default',
|
||||||
|
range: [obj?.start || 0, obj.end || 0],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -7,8 +7,9 @@ import {
|
|||||||
findUniqueName,
|
findUniqueName,
|
||||||
} from '../lang/modifyAst'
|
} from '../lang/modifyAst'
|
||||||
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
import { findAllPreviousVariables, PrevVariable } from '../lang/queryAst'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
export const AvailableVars = ({
|
export const AvailableVars = ({
|
||||||
onVarClick,
|
onVarClick,
|
||||||
@ -91,11 +92,9 @@ export function useCalc({
|
|||||||
newVariableInsertIndex: number
|
newVariableInsertIndex: number
|
||||||
setNewVariableName: (a: string) => void
|
setNewVariableName: (a: string) => void
|
||||||
} {
|
} {
|
||||||
const { ast, programMemory, selectionRange } = useStore((s) => ({
|
const { programMemory } = useKclContext()
|
||||||
ast: s.ast,
|
const { context } = useModelingContext()
|
||||||
programMemory: s.programMemory,
|
const selectionRange = context.selectionRanges.codeBasedSelections[0].range
|
||||||
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
|
|
||||||
}))
|
|
||||||
const inputRef = useRef<HTMLInputElement>(null)
|
const inputRef = useRef<HTMLInputElement>(null)
|
||||||
const [availableVarInfo, setAvailableVarInfo] = useState<
|
const [availableVarInfo, setAvailableVarInfo] = useState<
|
||||||
ReturnType<typeof findAllPreviousVariables>
|
ReturnType<typeof findAllPreviousVariables>
|
||||||
@ -115,9 +114,7 @@ export function useCalc({
|
|||||||
inputRef.current &&
|
inputRef.current &&
|
||||||
inputRef.current.setSelectionRange(0, String(value).length)
|
inputRef.current.setSelectionRange(0, String(value).length)
|
||||||
}, 100)
|
}, 100)
|
||||||
if (ast) {
|
setNewVariableName(findUniqueName(kclManager.ast, valueName))
|
||||||
setNewVariableName(findUniqueName(ast, valueName))
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -130,10 +127,14 @@ export function useCalc({
|
|||||||
}, [newVariableName])
|
}, [newVariableName])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast || !programMemory || !selectionRange) return
|
if (!programMemory || !selectionRange) return
|
||||||
const varInfo = findAllPreviousVariables(ast, programMemory, selectionRange)
|
const varInfo = findAllPreviousVariables(
|
||||||
|
kclManager.ast,
|
||||||
|
programMemory,
|
||||||
|
selectionRange
|
||||||
|
)
|
||||||
setAvailableVarInfo(varInfo)
|
setAvailableVarInfo(varInfo)
|
||||||
}, [ast, programMemory, selectionRange])
|
}, [kclManager.ast, programMemory, selectionRange])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
@ -143,7 +144,13 @@ export function useCalc({
|
|||||||
availableVarInfo.variables.forEach(({ key, value }) => {
|
availableVarInfo.variables.forEach(({ key, value }) => {
|
||||||
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
|
||||||
})
|
})
|
||||||
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
|
|
||||||
|
executor(
|
||||||
|
ast,
|
||||||
|
_programMem,
|
||||||
|
engineCommandManager,
|
||||||
|
kclManager.defaultPlanes
|
||||||
|
).then((programMemory) => {
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
a.type === 'VariableDeclaration' &&
|
a.type === 'VariableDeclaration' &&
|
||||||
|
|||||||
@ -5,16 +5,13 @@ import {
|
|||||||
faEllipsis,
|
faEllipsis,
|
||||||
} from '@fortawesome/free-solid-svg-icons'
|
} from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionIcon } from './ActionIcon'
|
import { ActionIcon } from './ActionIcon'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
import styles from './CodeMenu.module.css'
|
import styles from './CodeMenu.module.css'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { editorShortcutMeta } from './TextEditor'
|
import { editorShortcutMeta } from './TextEditor'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
export const CodeMenu = ({ children }: PropsWithChildren) => {
|
||||||
const { formatCode } = useStore((s) => ({
|
|
||||||
formatCode: s.formatCode,
|
|
||||||
}))
|
|
||||||
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
const { enable: convertToVarEnabled, handleClick: handleConvertToVarClick } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
|
|
||||||
@ -41,7 +38,10 @@ export const CodeMenu = ({ children }: PropsWithChildren) => {
|
|||||||
</Menu.Button>
|
</Menu.Button>
|
||||||
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
<Menu.Items className="absolute right-0 left-auto w-72 flex flex-col gap-1 divide-y divide-chalkboard-20 dark:divide-chalkboard-70 align-stretch px-0 py-1 bg-chalkboard-10 dark:bg-chalkboard-90 rounded-sm shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50">
|
||||||
<Menu.Item>
|
<Menu.Item>
|
||||||
<button onClick={() => formatCode()} className={styles.button}>
|
<button
|
||||||
|
onClick={() => kclManager.format()}
|
||||||
|
className={styles.button}
|
||||||
|
>
|
||||||
<span>Format code</span>
|
<span>Format code</span>
|
||||||
<small>{editorShortcutMeta.formatCode.display}</small>
|
<small>{editorShortcutMeta.formatCode.display}</small>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -47,11 +47,9 @@ export const ErrorPage = () => {
|
|||||||
Clear storage
|
Clear storage
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
Element="link"
|
Element="externalLink"
|
||||||
icon={{ icon: faBug }}
|
icon={{ icon: faBug }}
|
||||||
target="_blank"
|
to="https://github.com/KittyCAD/modeling-app/issues/new"
|
||||||
rel="noopener noreferrer"
|
|
||||||
to="https://discord.com/channels/915388055236509727/1138967922614743060"
|
|
||||||
>
|
>
|
||||||
Report Bug
|
Report Bug
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import ReactJson from 'react-json-view'
|
import ReactJson from 'react-json-view'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
|
import { useKclContext } from 'lang/KclSinglton'
|
||||||
|
|
||||||
const ReactJsonTypeHack = ReactJson as any
|
const ReactJsonTypeHack = ReactJson as any
|
||||||
|
|
||||||
@ -11,9 +11,7 @@ interface LogPanelProps extends CollapsiblePanelProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
|
export const Logs = ({ theme = Themes.Light, ...props }: LogPanelProps) => {
|
||||||
const { logs } = useStore(({ logs }) => ({
|
const { logs } = useKclContext()
|
||||||
logs,
|
|
||||||
}))
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = document.querySelector('.console-tile')
|
const element = document.querySelector('.console-tile')
|
||||||
if (element) {
|
if (element) {
|
||||||
@ -47,21 +45,19 @@ export const KCLErrors = ({
|
|||||||
theme = Themes.Light,
|
theme = Themes.Light,
|
||||||
...props
|
...props
|
||||||
}: LogPanelProps) => {
|
}: LogPanelProps) => {
|
||||||
const { kclErrors } = useStore(({ kclErrors }) => ({
|
const { errors } = useKclContext()
|
||||||
kclErrors,
|
|
||||||
}))
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = document.querySelector('.console-tile')
|
const element = document.querySelector('.console-tile')
|
||||||
if (element) {
|
if (element) {
|
||||||
element.scrollTop = element.scrollHeight - element.clientHeight
|
element.scrollTop = element.scrollHeight - element.clientHeight
|
||||||
}
|
}
|
||||||
}, [kclErrors])
|
}, [errors])
|
||||||
return (
|
return (
|
||||||
<CollapsiblePanel {...props}>
|
<CollapsiblePanel {...props}>
|
||||||
<div className="h-full relative">
|
<div className="h-full relative">
|
||||||
<div className="absolute inset-0 flex flex-col">
|
<div className="absolute inset-0 flex flex-col">
|
||||||
<ReactJsonTypeHack
|
<ReactJsonTypeHack
|
||||||
src={kclErrors}
|
src={errors}
|
||||||
collapsed={1}
|
collapsed={1}
|
||||||
collapseStringsAfterLength={60}
|
collapseStringsAfterLength={60}
|
||||||
enableClipboard={false}
|
enableClipboard={false}
|
||||||
|
|||||||
@ -14,12 +14,14 @@ describe('processMemory', () => {
|
|||||||
}
|
}
|
||||||
const otherVar = myFn(5)
|
const otherVar = myFn(5)
|
||||||
|
|
||||||
const theExtrude = startSketchAt([0, 0])
|
const theExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.4, myVar], %)
|
|> lineTo([-2.4, myVar], %)
|
||||||
|> lineTo([-0.76, otherVar], %)
|
|> lineTo([-0.76, otherVar], %)
|
||||||
|> extrude(4, %)
|
|> extrude(4, %)
|
||||||
|
|
||||||
const theSketch = startSketchAt([0, 0])
|
const theSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-3.35, 0.17], %)
|
|> lineTo([-3.35, 0.17], %)
|
||||||
|> lineTo([0.98, 5.16], %)
|
|> lineTo([0.98, 5.16], %)
|
||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import ReactJson from 'react-json-view'
|
import ReactJson from 'react-json-view'
|
||||||
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
import { CollapsiblePanel, CollapsiblePanelProps } from './CollapsiblePanel'
|
||||||
import { useStore } from '../useStore'
|
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
import { ProgramMemory, Path, ExtrudeSurface } from '../lang/wasm'
|
||||||
import { Themes } from '../lib/theme'
|
import { Themes } from '../lib/theme'
|
||||||
|
import { useKclContext } from 'lang/KclSinglton'
|
||||||
|
|
||||||
interface MemoryPanelProps extends CollapsiblePanelProps {
|
interface MemoryPanelProps extends CollapsiblePanelProps {
|
||||||
theme?: Exclude<Themes, Themes.System>
|
theme?: Exclude<Themes, Themes.System>
|
||||||
@ -13,9 +13,7 @@ export const MemoryPanel = ({
|
|||||||
theme = Themes.Light,
|
theme = Themes.Light,
|
||||||
...props
|
...props
|
||||||
}: MemoryPanelProps) => {
|
}: MemoryPanelProps) => {
|
||||||
const { programMemory } = useStore((s) => ({
|
const { programMemory } = useKclContext()
|
||||||
programMemory: s.programMemory,
|
|
||||||
}))
|
|
||||||
const ProcessedMemory = useMemo(
|
const ProcessedMemory = useMemo(
|
||||||
() => processMemory(programMemory),
|
() => processMemory(programMemory),
|
||||||
[programMemory]
|
[programMemory]
|
||||||
|
|||||||
355
src/components/ModelingMachineProvider.tsx
Normal file
355
src/components/ModelingMachineProvider.tsx
Normal file
@ -0,0 +1,355 @@
|
|||||||
|
import { useMachine } from '@xstate/react'
|
||||||
|
import React, { createContext, useEffect, useRef } from 'react'
|
||||||
|
import {
|
||||||
|
AnyStateMachine,
|
||||||
|
ContextFrom,
|
||||||
|
InterpreterFrom,
|
||||||
|
Prop,
|
||||||
|
StateFrom,
|
||||||
|
assign,
|
||||||
|
} from 'xstate'
|
||||||
|
import { SetSelections, modelingMachine } from 'machines/modelingMachine'
|
||||||
|
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
|
||||||
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { addStartSketch } from 'lang/modifyAst'
|
||||||
|
import { roundOff } from 'lib/utils'
|
||||||
|
import { recast, parse, Program, VariableDeclarator } from 'lang/wasm'
|
||||||
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
|
import {
|
||||||
|
addCloseToPipe,
|
||||||
|
addNewSketchLn,
|
||||||
|
compareVec2Epsilon,
|
||||||
|
} from 'lang/std/sketch'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
import { applyConstraintHorzVertDistance } from './Toolbar/SetHorzVertDistance'
|
||||||
|
import { applyConstraintAngleBetween } from './Toolbar/SetAngleBetween'
|
||||||
|
import { applyConstraintAngleLength } from './Toolbar/setAngleLength'
|
||||||
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { pathMapToSelections } from 'lang/util'
|
||||||
|
import {
|
||||||
|
dispatchCodeMirrorCursor,
|
||||||
|
setCodeMirrorCursor,
|
||||||
|
useStore,
|
||||||
|
} from 'useStore'
|
||||||
|
|
||||||
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
|
state: StateFrom<T>
|
||||||
|
context: ContextFrom<T>
|
||||||
|
send: Prop<InterpreterFrom<T>, 'send'>
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ModelingMachineContext = createContext(
|
||||||
|
{} as MachineContext<typeof modelingMachine>
|
||||||
|
)
|
||||||
|
|
||||||
|
export const ModelingMachineProvider = ({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) => {
|
||||||
|
const { auth } = useGlobalStateContext()
|
||||||
|
const token = auth?.context?.token
|
||||||
|
const streamRef = useRef<HTMLDivElement>(null)
|
||||||
|
useSetupEngineManager(streamRef, token)
|
||||||
|
|
||||||
|
const { isShiftDown, editorView } = useStore((s) => ({
|
||||||
|
isShiftDown: s.isShiftDown,
|
||||||
|
editorView: s.editorView,
|
||||||
|
}))
|
||||||
|
|
||||||
|
// const { commands } = useCommandsContext()
|
||||||
|
|
||||||
|
// Settings machine setup
|
||||||
|
// const retrievedSettings = useRef(
|
||||||
|
// localStorage?.getItem(MODELING_PERSIST_KEY) || '{}'
|
||||||
|
// )
|
||||||
|
|
||||||
|
// What should we persist from modeling state? Nothing?
|
||||||
|
// const persistedSettings = Object.assign(
|
||||||
|
// settingsMachine.initialState.context,
|
||||||
|
// JSON.parse(retrievedSettings.current) as Partial<
|
||||||
|
// (typeof settingsMachine)['context']
|
||||||
|
// >
|
||||||
|
// )
|
||||||
|
|
||||||
|
const [modelingState, modelingSend] = useMachine(modelingMachine, {
|
||||||
|
// context: persistedSettings,
|
||||||
|
actions: {
|
||||||
|
'Modify AST': () => {},
|
||||||
|
'Update code selection cursors': () => {},
|
||||||
|
'show default planes': () => {
|
||||||
|
kclManager.showPlanes()
|
||||||
|
},
|
||||||
|
'create path': async () => {
|
||||||
|
const sketchUuid = uuidv4()
|
||||||
|
const proms = [
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: sketchUuid,
|
||||||
|
cmd: {
|
||||||
|
type: 'start_path',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: sketchUuid,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
await Promise.all(proms)
|
||||||
|
},
|
||||||
|
'AST start new sketch': assign((_, { data: { coords, axis } }) => {
|
||||||
|
// Something really weird must have happened for this to happen.
|
||||||
|
if (!axis) {
|
||||||
|
console.error('axis is undefined for starting a new sketch')
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _addStartSketch = addStartSketch(
|
||||||
|
kclManager.ast,
|
||||||
|
axis,
|
||||||
|
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
||||||
|
[
|
||||||
|
roundOff(coords[1].x - coords[0].x),
|
||||||
|
roundOff(coords[1].y - coords[0].y),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
const _modifiedAst = _addStartSketch.modifiedAst
|
||||||
|
const _pathToNode = _addStartSketch.pathToNode
|
||||||
|
const newCode = recast(_modifiedAst)
|
||||||
|
const astWithUpdatedSource = parse(newCode)
|
||||||
|
|
||||||
|
kclManager.executeAstMock(astWithUpdatedSource, true)
|
||||||
|
|
||||||
|
return {
|
||||||
|
sketchPathToNode: _pathToNode,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'AST add line segment': ({ sketchPathToNode }, { data: { coords } }) => {
|
||||||
|
if (!sketchPathToNode) return
|
||||||
|
const lastCoord = coords[coords.length - 1]
|
||||||
|
|
||||||
|
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
||||||
|
kclManager.ast,
|
||||||
|
sketchPathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)
|
||||||
|
const variableName = varDec.id.name
|
||||||
|
const sketchGroup = kclManager.programMemory.root[variableName]
|
||||||
|
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
|
||||||
|
const initialCoords = sketchGroup.value[0].from
|
||||||
|
|
||||||
|
const isClose = compareVec2Epsilon(initialCoords, [
|
||||||
|
lastCoord.x,
|
||||||
|
lastCoord.y,
|
||||||
|
])
|
||||||
|
|
||||||
|
let _modifiedAst: Program
|
||||||
|
if (!isClose) {
|
||||||
|
_modifiedAst = addNewSketchLn({
|
||||||
|
node: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
to: [lastCoord.x, lastCoord.y],
|
||||||
|
fnName: 'line',
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
}).modifiedAst
|
||||||
|
kclManager.executeAstMock(_modifiedAst, true)
|
||||||
|
// kclManager.updateAst(_modifiedAst, false)
|
||||||
|
} else {
|
||||||
|
_modifiedAst = addCloseToPipe({
|
||||||
|
node: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
pathToNode: sketchPathToNode,
|
||||||
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
})
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
})
|
||||||
|
kclManager.executeAstMock(_modifiedAst, true)
|
||||||
|
// updateAst(_modifiedAst, true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'sketch exit execute': () => {
|
||||||
|
kclManager.executeAst()
|
||||||
|
},
|
||||||
|
'set tool': () => {}, // TODO
|
||||||
|
'toast extrude failed': () => {
|
||||||
|
toast.error(
|
||||||
|
'Extrude failed, sketches need to be closed, or not already extruded'
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'Set selection': assign(({ selectionRanges }, event) => {
|
||||||
|
if (event.type !== 'Set selection') return {} // this was needed for ts after adding 'Set selection' action to on done modal events
|
||||||
|
const setSelections = event.data
|
||||||
|
if (setSelections.selectionType === 'mirrorCodeMirrorSelections')
|
||||||
|
return { selectionRanges: setSelections.selection }
|
||||||
|
else if (setSelections.selectionType === 'otherSelection')
|
||||||
|
return {
|
||||||
|
selectionRanges: {
|
||||||
|
...selectionRanges,
|
||||||
|
otherSelections: [setSelections.selection],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
else if (!editorView) return {}
|
||||||
|
else if (setSelections.selectionType === 'singleCodeCursor') {
|
||||||
|
// This DOES NOT set the `selectionRanges` in xstate context
|
||||||
|
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
||||||
|
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||||
|
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||||
|
// because we want to respect the user manually placing the cursor too.
|
||||||
|
const selectionRangeTypeMap = setCodeMirrorCursor({
|
||||||
|
codeSelection: setSelections.selection,
|
||||||
|
currestSelections: selectionRanges,
|
||||||
|
editorView,
|
||||||
|
isShiftDown,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
selectionRangeTypeMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// This DOES NOT set the `selectionRanges` in xstate context
|
||||||
|
// same as comment above
|
||||||
|
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
|
||||||
|
selections: setSelections.selection,
|
||||||
|
editorView,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
selectionRangeTypeMap,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
guards: {
|
||||||
|
'Selection contains axis': () => true,
|
||||||
|
'Selection contains edge': () => true,
|
||||||
|
'Selection contains face': () => true,
|
||||||
|
'Selection contains line': () => true,
|
||||||
|
'Selection contains point': () => true,
|
||||||
|
'Selection is not empty': () => true,
|
||||||
|
'Selection is one face': ({ selectionRanges }) => {
|
||||||
|
return !!isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
'Get horizontal info': async ({
|
||||||
|
selectionRanges,
|
||||||
|
}): Promise<SetSelections> => {
|
||||||
|
const { modifiedAst, pathToNodeMap } =
|
||||||
|
await applyConstraintHorzVertDistance({
|
||||||
|
constraint: 'setHorzDistance',
|
||||||
|
selectionRanges,
|
||||||
|
})
|
||||||
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
return {
|
||||||
|
selectionType: 'completeSelection',
|
||||||
|
selection: pathMapToSelections(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
pathToNodeMap
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Get vertical info': async ({
|
||||||
|
selectionRanges,
|
||||||
|
}): Promise<SetSelections> => {
|
||||||
|
const { modifiedAst, pathToNodeMap } =
|
||||||
|
await applyConstraintHorzVertDistance({
|
||||||
|
constraint: 'setVertDistance',
|
||||||
|
selectionRanges,
|
||||||
|
})
|
||||||
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
return {
|
||||||
|
selectionType: 'completeSelection',
|
||||||
|
selection: pathMapToSelections(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
pathToNodeMap
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Get angle info': async ({ selectionRanges }): Promise<SetSelections> => {
|
||||||
|
const { modifiedAst, pathToNodeMap } =
|
||||||
|
await applyConstraintAngleBetween({
|
||||||
|
selectionRanges,
|
||||||
|
})
|
||||||
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
return {
|
||||||
|
selectionType: 'completeSelection',
|
||||||
|
selection: pathMapToSelections(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
pathToNodeMap
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Get length info': async ({
|
||||||
|
selectionRanges,
|
||||||
|
}): Promise<SetSelections> => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = await applyConstraintAngleLength(
|
||||||
|
{ selectionRanges }
|
||||||
|
)
|
||||||
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
return {
|
||||||
|
selectionType: 'completeSelection',
|
||||||
|
selection: pathMapToSelections(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
pathToNodeMap
|
||||||
|
),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
devTools: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
engineCommandManager.onPlaneSelected((plane_id: string) => {
|
||||||
|
if (modelingState.nextEvents.includes('Select default plane')) {
|
||||||
|
modelingSend({
|
||||||
|
type: 'Select default plane',
|
||||||
|
data: { planeId: plane_id },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [kclManager.defaultPlanes, modelingSend, modelingState.nextEvents])
|
||||||
|
|
||||||
|
// useStateMachineCommands({
|
||||||
|
// state: settingsState,
|
||||||
|
// send: settingsSend,
|
||||||
|
// commands,
|
||||||
|
// owner: 'settings',
|
||||||
|
// commandBarMeta: settingsCommandBarMeta,
|
||||||
|
// })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModelingMachineContext.Provider
|
||||||
|
value={{
|
||||||
|
state: modelingState,
|
||||||
|
context: modelingState.context,
|
||||||
|
send: modelingSend,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
|
||||||
|
since realistically it won't ever have generic children that isn't app.tsx */}
|
||||||
|
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</ModelingMachineContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModelingMachineProvider
|
||||||
@ -7,28 +7,17 @@ import {
|
|||||||
} from 'react'
|
} from 'react'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { getNormalisedCoordinates, roundOff } from '../lib/utils'
|
import { getNormalisedCoordinates } from '../lib/utils'
|
||||||
import Loading from './Loading'
|
import Loading from './Loading'
|
||||||
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
import { cameraMouseDragGuards } from 'lib/cameraControls'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
import { CameraDragInteractionType_type } from '@kittycad/lib/dist/types/src/models'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { addStartSketch } from 'lang/modifyAst'
|
|
||||||
import {
|
|
||||||
addCloseToPipe,
|
|
||||||
addNewSketchLn,
|
|
||||||
compareVec2Epsilon,
|
|
||||||
} from 'lang/std/sketch'
|
|
||||||
import { getNodeFromPath } from 'lang/queryAst'
|
import { getNodeFromPath } from 'lang/queryAst'
|
||||||
import {
|
import { Program, VariableDeclarator, modifyAstForSketch } from 'lang/wasm'
|
||||||
Program,
|
|
||||||
VariableDeclarator,
|
|
||||||
rangeTypeFix,
|
|
||||||
modifyAstForSketch,
|
|
||||||
} from 'lang/wasm'
|
|
||||||
import { KCLError } from 'lang/errors'
|
|
||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export const Stream = ({ className = '' }) => {
|
export const Stream = ({ className = '' }) => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
@ -40,31 +29,17 @@ export const Stream = ({ className = '' }) => {
|
|||||||
didDragInStream,
|
didDragInStream,
|
||||||
setDidDragInStream,
|
setDidDragInStream,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
isExecuting,
|
|
||||||
guiMode,
|
|
||||||
ast,
|
|
||||||
updateAst,
|
|
||||||
setGuiMode,
|
|
||||||
programMemory,
|
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
mediaStream: s.mediaStream,
|
mediaStream: s.mediaStream,
|
||||||
setButtonDownInStream: s.setButtonDownInStream,
|
setButtonDownInStream: s.setButtonDownInStream,
|
||||||
fileId: s.fileId,
|
|
||||||
didDragInStream: s.didDragInStream,
|
didDragInStream: s.didDragInStream,
|
||||||
setDidDragInStream: s.setDidDragInStream,
|
setDidDragInStream: s.setDidDragInStream,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
isExecuting: s.isExecuting,
|
|
||||||
guiMode: s.guiMode,
|
|
||||||
ast: s.ast,
|
|
||||||
updateAst: s.updateAst,
|
|
||||||
setGuiMode: s.setGuiMode,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
}))
|
}))
|
||||||
const {
|
const { settings } = useGlobalStateContext()
|
||||||
settings: {
|
const cameraControls = settings?.context?.cameraControls
|
||||||
context: { cameraControls },
|
const { send, state, context } = useModelingContext()
|
||||||
},
|
const { isExecuting } = useKclContext()
|
||||||
} = useGlobalStateContext()
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -108,7 +83,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
interaction = 'zoom'
|
interaction = 'zoom'
|
||||||
}
|
}
|
||||||
|
|
||||||
if (guiMode.mode === 'sketch' && guiMode.sketchMode === ('move' as any)) {
|
if (state.matches('Sketch.Move Tool')) {
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -117,12 +92,7 @@ export const Stream = ({ className = '' }) => {
|
|||||||
},
|
},
|
||||||
cmd_id: newId,
|
cmd_id: newId,
|
||||||
})
|
})
|
||||||
} else if (
|
} else if (!state.matches('Sketch.Line Tool')) {
|
||||||
!(
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
engineCommandManager.sendSceneCommand({
|
engineCommandManager.sendSceneCommand({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd: {
|
cmd: {
|
||||||
@ -178,200 +148,141 @@ export const Stream = ({ className = '' }) => {
|
|||||||
cmd_id: newCmdId,
|
cmd_id: newCmdId,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!didDragInStream) {
|
if (!didDragInStream && state.matches('Sketch no face')) {
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'select_with_point',
|
|
||||||
selection_type: 'add',
|
|
||||||
selected_at_window: { x, y },
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!didDragInStream && guiMode.mode === 'default') {
|
|
||||||
command.cmd = {
|
command.cmd = {
|
||||||
type: 'select_with_point',
|
type: 'select_with_point',
|
||||||
selection_type: 'add',
|
selection_type: 'add',
|
||||||
selected_at_window: { x, y },
|
selected_at_window: { x, y },
|
||||||
}
|
}
|
||||||
} else if (
|
engineCommandManager.sendSceneCommand(command)
|
||||||
(!didDragInStream &&
|
} else if (!didDragInStream && state.matches('Sketch.Line Tool')) {
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
['move', 'select'].includes(guiMode.sketchMode)) ||
|
|
||||||
(guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === ('sketch_line' as any))
|
|
||||||
) {
|
|
||||||
command.cmd = {
|
command.cmd = {
|
||||||
type: 'mouse_click',
|
type: 'mouse_click',
|
||||||
window: { x, y },
|
window: { x, y },
|
||||||
}
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
|
||||||
|
const entities_modified = resp?.data?.data?.entities_modified
|
||||||
|
if (!entities_modified) return
|
||||||
|
if (state.matches('Sketch.Line Tool.No Points')) {
|
||||||
|
send('Add point')
|
||||||
|
} else if (state.matches('Sketch.Line Tool.Point Added')) {
|
||||||
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'curve_get_control_points',
|
||||||
|
curve_id: entities_modified[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const coords: { x: number; y: number }[] =
|
||||||
|
curve.data.data.control_points
|
||||||
|
// We need the normal for the plane we are on.
|
||||||
|
const plane = await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'get_sketch_mode_plane',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const z_axis = plane.data.data.z_axis
|
||||||
|
|
||||||
|
// Get the current axis.
|
||||||
|
let currentAxis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null =
|
||||||
|
null
|
||||||
|
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
|
||||||
|
if (z_axis.z === -1) {
|
||||||
|
currentAxis = '-xy'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'xy'
|
||||||
|
}
|
||||||
|
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
|
||||||
|
if (z_axis.x === -1) {
|
||||||
|
currentAxis = '-yz'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'yz'
|
||||||
|
}
|
||||||
|
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
|
||||||
|
if (z_axis.y === -1) {
|
||||||
|
currentAxis = '-xz'
|
||||||
|
} else {
|
||||||
|
currentAxis = 'xz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
send({ type: 'Add point', data: { coords, axis: currentAxis } })
|
||||||
|
} else if (state.matches('Sketch.Line Tool.Segment Added')) {
|
||||||
|
const curve = await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'curve_get_control_points',
|
||||||
|
curve_id: entities_modified[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const coords: { x: number; y: number }[] =
|
||||||
|
curve.data.data.control_points
|
||||||
|
send({ type: 'Add point', data: { coords, axis: null } })
|
||||||
|
}
|
||||||
|
})
|
||||||
} else if (
|
} else if (
|
||||||
guiMode.mode === 'sketch' &&
|
!didDragInStream &&
|
||||||
guiMode.sketchMode === ('move' as any)
|
(state.matches('Sketch.SketchIdle') ||
|
||||||
|
state.matches('idle') ||
|
||||||
|
state.matches('awaiting selection'))
|
||||||
) {
|
) {
|
||||||
|
command.cmd = {
|
||||||
|
type: 'select_with_point',
|
||||||
|
selected_at_window: { x, y },
|
||||||
|
selection_type: 'add',
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(command)
|
||||||
|
} else if (!didDragInStream && state.matches('Sketch.Move Tool')) {
|
||||||
|
command.cmd = {
|
||||||
|
type: 'select_with_point',
|
||||||
|
selected_at_window: { x, y },
|
||||||
|
selection_type: 'add',
|
||||||
|
}
|
||||||
|
engineCommandManager.sendSceneCommand(command)
|
||||||
|
} else if (didDragInStream && state.matches('Sketch.Move Tool')) {
|
||||||
command.cmd = {
|
command.cmd = {
|
||||||
type: 'handle_mouse_drag_end',
|
type: 'handle_mouse_drag_end',
|
||||||
window: { x, y },
|
window: { x, y },
|
||||||
}
|
}
|
||||||
}
|
engineCommandManager.sendSceneCommand(command).then(async () => {
|
||||||
engineCommandManager.sendSceneCommand(command).then(async (resp) => {
|
if (!context.sketchPathToNode) return
|
||||||
if (!(guiMode.mode === 'sketch')) return
|
const varDec = getNodeFromPath<VariableDeclarator>(
|
||||||
|
kclManager.ast,
|
||||||
|
context.sketchPathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
).node
|
||||||
|
const variableName = varDec?.id?.name
|
||||||
|
|
||||||
if (guiMode.sketchMode === 'selectFace') return
|
// Get the current plane string for plane we are on.
|
||||||
|
let currentPlaneString = ''
|
||||||
|
if (context.sketchPlaneId === kclManager.getPlaneId('xy')) {
|
||||||
|
currentPlaneString = 'XY'
|
||||||
|
} else if (context.sketchPlaneId === kclManager.getPlaneId('yz')) {
|
||||||
|
currentPlaneString = 'YZ'
|
||||||
|
} else if (context.sketchPlaneId === kclManager.getPlaneId('xz')) {
|
||||||
|
currentPlaneString = 'XZ'
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the sketch group already exists.
|
// Do not supporting editing/moving lines on a non-default plane.
|
||||||
const varDec = getNodeFromPath<VariableDeclarator>(
|
// Eventually we can support this but for now we will just throw an
|
||||||
ast,
|
// error.
|
||||||
guiMode.pathToNode,
|
if (currentPlaneString === '') return
|
||||||
'VariableDeclarator'
|
|
||||||
).node
|
|
||||||
const variableName = varDec?.id?.name
|
|
||||||
const sketchGroup = programMemory.root[variableName]
|
|
||||||
const isEditingExistingSketch =
|
|
||||||
sketchGroup?.type === 'SketchGroup' && sketchGroup.value.length
|
|
||||||
let sketchGroupId = ''
|
|
||||||
if (sketchGroup && sketchGroup.type === 'SketchGroup') {
|
|
||||||
sketchGroupId = sketchGroup.id
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
guiMode.sketchMode === ('move' as any as 'line') &&
|
|
||||||
command.cmd.type === 'handle_mouse_drag_end'
|
|
||||||
) {
|
|
||||||
// Let's get the updated ast.
|
|
||||||
if (sketchGroupId === '') return
|
|
||||||
// We have a problem if we do not have an id for the sketch group.
|
|
||||||
if (
|
|
||||||
guiMode.pathId === undefined ||
|
|
||||||
guiMode.pathId === null ||
|
|
||||||
guiMode.pathId === ''
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
let engineId = guiMode.pathId
|
|
||||||
|
|
||||||
const updatedAst: Program = await modifyAstForSketch(
|
const updatedAst: Program = await modifyAstForSketch(
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
ast,
|
kclManager.ast,
|
||||||
variableName,
|
variableName,
|
||||||
engineId
|
currentPlaneString,
|
||||||
|
context.sketchEnginePathId
|
||||||
)
|
)
|
||||||
|
kclManager.executeAstMock(updatedAst, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
updateAst(updatedAst, false)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command?.cmd?.type !== 'mouse_click' || !ast) return
|
|
||||||
|
|
||||||
if (!(guiMode.sketchMode === ('sketch_line' as any as 'line'))) return
|
|
||||||
|
|
||||||
if (
|
|
||||||
resp?.data?.data?.entities_modified?.length &&
|
|
||||||
guiMode.waitingFirstClick &&
|
|
||||||
!isEditingExistingSketch
|
|
||||||
) {
|
|
||||||
const curve = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: resp?.data?.data?.entities_modified[0],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const coords: { x: number; y: number }[] =
|
|
||||||
curve.data.data.control_points
|
|
||||||
const _addStartSketch = addStartSketch(
|
|
||||||
ast,
|
|
||||||
[roundOff(coords[0].x), roundOff(coords[0].y)],
|
|
||||||
[
|
|
||||||
roundOff(coords[1].x - coords[0].x),
|
|
||||||
roundOff(coords[1].y - coords[0].y),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
const _modifiedAst = _addStartSketch.modifiedAst
|
|
||||||
const _pathToNode = _addStartSketch.pathToNode
|
|
||||||
|
|
||||||
// We need to update the guiMode with the right pathId so that we can
|
|
||||||
// move lines later and send the right sketch id to the engine.
|
|
||||||
for (const [id, artifact] of Object.entries(
|
|
||||||
engineCommandManager.artifactMap
|
|
||||||
)) {
|
|
||||||
if (artifact.commandType === 'start_path') {
|
|
||||||
guiMode.pathId = id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setGuiMode({
|
|
||||||
...guiMode,
|
|
||||||
pathToNode: _pathToNode,
|
|
||||||
waitingFirstClick: false,
|
|
||||||
})
|
|
||||||
updateAst(_modifiedAst, false)
|
|
||||||
} else if (
|
|
||||||
resp?.data?.data?.entities_modified?.length &&
|
|
||||||
(!guiMode.waitingFirstClick || isEditingExistingSketch)
|
|
||||||
) {
|
|
||||||
const curve = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'curve_get_control_points',
|
|
||||||
curve_id: resp?.data?.data?.entities_modified[0],
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const coords: { x: number; y: number }[] =
|
|
||||||
curve.data.data.control_points
|
|
||||||
|
|
||||||
const { node: varDec } = getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
guiMode.pathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)
|
|
||||||
const variableName = varDec.id.name
|
|
||||||
const sketchGroup = programMemory.root[variableName]
|
|
||||||
if (!sketchGroup || sketchGroup.type !== 'SketchGroup') return
|
|
||||||
const initialCoords = sketchGroup.value[0].from
|
|
||||||
|
|
||||||
const isClose = compareVec2Epsilon(initialCoords, [
|
|
||||||
coords[1].x,
|
|
||||||
coords[1].y,
|
|
||||||
])
|
|
||||||
|
|
||||||
let _modifiedAst: Program
|
|
||||||
if (!isClose) {
|
|
||||||
_modifiedAst = addNewSketchLn({
|
|
||||||
node: ast,
|
|
||||||
programMemory,
|
|
||||||
to: [coords[1].x, coords[1].y],
|
|
||||||
fnName: 'line',
|
|
||||||
pathToNode: guiMode.pathToNode,
|
|
||||||
}).modifiedAst
|
|
||||||
updateAst(_modifiedAst, false)
|
|
||||||
} else {
|
|
||||||
_modifiedAst = addCloseToPipe({
|
|
||||||
node: ast,
|
|
||||||
programMemory,
|
|
||||||
pathToNode: guiMode.pathToNode,
|
|
||||||
})
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'default',
|
|
||||||
})
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'edit_mode_exit' },
|
|
||||||
})
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: { type: 'default_camera_disable_sketch_mode' },
|
|
||||||
})
|
|
||||||
updateAst(_modifiedAst, true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setDidDragInStream(false)
|
setDidDragInStream(false)
|
||||||
setClickCoords(undefined)
|
setClickCoords(undefined)
|
||||||
}
|
}
|
||||||
@ -402,8 +313,8 @@ export const Stream = ({ className = '' }) => {
|
|||||||
onWheel={handleScroll}
|
onWheel={handleScroll}
|
||||||
onPlay={() => setIsLoading(false)}
|
onPlay={() => setIsLoading(false)}
|
||||||
onMouseMoveCapture={handleMouseMove}
|
onMouseMoveCapture={handleMouseMove}
|
||||||
|
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}
|
||||||
disablePictureInPicture
|
disablePictureInPicture
|
||||||
className={`w-full h-full ${isExecuting && 'blur-md'}`}
|
|
||||||
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
|
||||||
/>
|
/>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
import { useConvertToVariable } from 'hooks/useToolbarGuards'
|
||||||
import { Themes } from 'lib/theme'
|
import { Themes } from 'lib/theme'
|
||||||
import { useMemo } from 'react'
|
import { useMemo, useState } from 'react'
|
||||||
import { linter, lintGutter } from '@codemirror/lint'
|
import { linter, lintGutter } from '@codemirror/lint'
|
||||||
import { Selections, useStore } from 'useStore'
|
import { Selections, useStore } from 'useStore'
|
||||||
import { LanguageServerClient } from 'editor/lsp'
|
import { LanguageServerClient } from 'editor/lsp'
|
||||||
@ -29,8 +29,10 @@ import {
|
|||||||
import { isOverlap, roundOff } from 'lib/utils'
|
import { isOverlap, roundOff } from 'lib/utils'
|
||||||
import { kclErrToDiagnostic } from 'lang/errors'
|
import { kclErrToDiagnostic } from 'lang/errors'
|
||||||
import { CSSRuleObject } from 'tailwindcss/types/config'
|
import { CSSRuleObject } from 'tailwindcss/types/config'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import interact from '@replit/codemirror-interact'
|
import interact from '@replit/codemirror-interact'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { kclManager, useKclContext } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -49,35 +51,22 @@ export const TextEditor = ({
|
|||||||
theme: Themes.Light | Themes.Dark
|
theme: Themes.Light | Themes.Dark
|
||||||
}) => {
|
}) => {
|
||||||
const pathParams = useParams()
|
const pathParams = useParams()
|
||||||
const {
|
const { editorView, isLSPServerReady, setEditorView, setIsLSPServerReady } =
|
||||||
code,
|
useStore((s) => ({
|
||||||
deferredSetCode,
|
editorView: s.editorView,
|
||||||
editorView,
|
isLSPServerReady: s.isLSPServerReady,
|
||||||
formatCode,
|
setEditorView: s.setEditorView,
|
||||||
isLSPServerReady,
|
setIsLSPServerReady: s.setIsLSPServerReady,
|
||||||
selectionRanges,
|
}))
|
||||||
selectionRangeTypeMap,
|
const { code, errors } = useKclContext()
|
||||||
setEditorView,
|
|
||||||
setIsLSPServerReady,
|
|
||||||
setSelectionRanges,
|
|
||||||
} = useStore((s) => ({
|
|
||||||
code: s.code,
|
|
||||||
deferredSetCode: s.deferredSetCode,
|
|
||||||
editorView: s.editorView,
|
|
||||||
formatCode: s.formatCode,
|
|
||||||
isLSPServerReady: s.isLSPServerReady,
|
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
|
||||||
setEditorView: s.setEditorView,
|
|
||||||
setIsLSPServerReady: s.setIsLSPServerReady,
|
|
||||||
setSelectionRanges: s.setSelectionRanges,
|
|
||||||
}))
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
settings: {
|
context: { selectionRanges, selectionRangeTypeMap },
|
||||||
context: { textWrapping },
|
send,
|
||||||
},
|
} = useModelingContext()
|
||||||
} = useGlobalStateContext()
|
|
||||||
|
const { settings: { context: { textWrapping } = {} } = {} } =
|
||||||
|
useGlobalStateContext()
|
||||||
const { setCommandBarOpen } = useCommandsContext()
|
const { setCommandBarOpen } = useCommandsContext()
|
||||||
const { enable: convertEnabled, handleClick: convertCallback } =
|
const { enable: convertEnabled, handleClick: convertCallback } =
|
||||||
useConvertToVariable()
|
useConvertToVariable()
|
||||||
@ -122,12 +111,12 @@ export const TextEditor = ({
|
|||||||
}, [lspClient, isLSPServerReady])
|
}, [lspClient, isLSPServerReady])
|
||||||
|
|
||||||
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
// const onChange = React.useCallback((value: string, viewUpdate: ViewUpdate) => {
|
||||||
const onChange = (value: string, viewUpdate: ViewUpdate) => {
|
const onChange = (newCode: string, viewUpdate: ViewUpdate) => {
|
||||||
deferredSetCode(value)
|
kclManager.setCodeAndExecute(newCode)
|
||||||
if (isTauri() && pathParams.id) {
|
if (isTauri() && pathParams.id) {
|
||||||
// Save the file to disk
|
// Save the file to disk
|
||||||
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
|
||||||
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, value).catch(
|
writeTextFile(pathParams.id + '/' + PROJECT_ENTRYPOINT, newCode).catch(
|
||||||
(err) => {
|
(err) => {
|
||||||
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
|
||||||
console.error('error saving file', err)
|
console.error('error saving file', err)
|
||||||
@ -190,10 +179,17 @@ export const TextEditor = ({
|
|||||||
idBasedSelections,
|
idBasedSelections,
|
||||||
})
|
})
|
||||||
|
|
||||||
setSelectionRanges({
|
selectionRanges &&
|
||||||
otherSelections: [],
|
send({
|
||||||
codeBasedSelections,
|
type: 'Set selection',
|
||||||
})
|
data: {
|
||||||
|
selectionType: 'mirrorCodeMirrorSelections',
|
||||||
|
selection: {
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const editorExtensions = useMemo(() => {
|
const editorExtensions = useMemo(() => {
|
||||||
@ -210,7 +206,7 @@ export const TextEditor = ({
|
|||||||
{
|
{
|
||||||
key: editorShortcutMeta.formatCode.codeMirror,
|
key: editorShortcutMeta.formatCode.codeMirror,
|
||||||
run: () => {
|
run: () => {
|
||||||
formatCode()
|
kclManager.format()
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -234,7 +230,7 @@ export const TextEditor = ({
|
|||||||
extensions.push(
|
extensions.push(
|
||||||
lintGutter(),
|
lintGutter(),
|
||||||
linter((_view) => {
|
linter((_view) => {
|
||||||
return kclErrToDiagnostic(useStore.getState().kclErrors)
|
return kclErrToDiagnostic(errors)
|
||||||
}),
|
}),
|
||||||
interact({
|
interact({
|
||||||
rules: [
|
rules: [
|
||||||
|
|||||||
@ -11,34 +11,30 @@ import {
|
|||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { sketchButtonClassnames } from 'Toolbar'
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
|
/*
|
||||||
export const EqualAngle = () => {
|
export const EqualAngle = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enableEqual, setEnableEqual] = useState(false)
|
const [enableEqual, setEnableEqual] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
getNodePathFromSourceRange(ast, range)
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
)
|
)
|
||||||
const nodes = paths.map(
|
const nodes = paths.map(
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
)
|
)
|
||||||
const varDecs = paths.map(
|
const varDecs = paths.map(
|
||||||
(pathToNode) =>
|
(pathToNode) =>
|
||||||
getNodeFromPath<VariableDeclarator>(
|
getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
kclManager.ast,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)?.node
|
)?.node
|
||||||
@ -46,7 +42,7 @@ export const EqualAngle = () => {
|
|||||||
const primaryLine = varDecs[0]
|
const primaryLine = varDecs[0]
|
||||||
const secondaryVarDecs = varDecs.slice(1)
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
isSketchVariablesLinked(secondary, primaryLine, ast)
|
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
|
||||||
)
|
)
|
||||||
const isAllTooltips = nodes.every(
|
const isAllTooltips = nodes.every(
|
||||||
(node) =>
|
(node) =>
|
||||||
@ -59,7 +55,7 @@ export const EqualAngle = () => {
|
|||||||
...selectionRanges,
|
...selectionRanges,
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
||||||
},
|
},
|
||||||
ast,
|
kclManager.ast,
|
||||||
'equalAngle'
|
'equalAngle'
|
||||||
)
|
)
|
||||||
setTransformInfos(theTransforms)
|
setTransformInfos(theTransforms)
|
||||||
@ -76,15 +72,15 @@ export const EqualAngle = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -103,3 +99,4 @@ export const EqualAngle = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { Selections, toolTips, useStore } from '../../useStore'
|
||||||
import { Value, VariableDeclarator } from '../../lang/wasm'
|
import { Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -10,81 +10,41 @@ import {
|
|||||||
TransformInfo,
|
TransformInfo,
|
||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
|
PathToNodeMap,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { sketchButtonClassnames } from 'Toolbar'
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
|
/*
|
||||||
export const EqualLength = () => {
|
export const EqualLength = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enableEqual, setEnableEqual] = useState(false)
|
const [enableEqual, setEnableEqual] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
|
||||||
getNodePathFromSourceRange(ast, range)
|
|
||||||
)
|
|
||||||
const nodes = paths.map(
|
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
|
||||||
)
|
|
||||||
const varDecs = paths.map(
|
|
||||||
(pathToNode) =>
|
|
||||||
getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)?.node
|
|
||||||
)
|
|
||||||
const primaryLine = varDecs[0]
|
|
||||||
const secondaryVarDecs = varDecs.slice(1)
|
|
||||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
|
||||||
isSketchVariablesLinked(secondary, primaryLine, ast)
|
|
||||||
)
|
|
||||||
const isAllTooltips = nodes.every(
|
|
||||||
(node) =>
|
|
||||||
node?.type === 'CallExpression' &&
|
|
||||||
toolTips.includes(node.callee.name as any)
|
|
||||||
)
|
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(
|
setTransformInfos(transforms)
|
||||||
{
|
setEnableEqual(enabled)
|
||||||
...selectionRanges,
|
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
|
||||||
},
|
|
||||||
ast,
|
|
||||||
'equalLength'
|
|
||||||
)
|
|
||||||
setTransformInfos(theTransforms)
|
|
||||||
|
|
||||||
const _enableEqual =
|
|
||||||
!!secondaryVarDecs.length &&
|
|
||||||
isAllTooltips &&
|
|
||||||
isOthersLinkedToPrimary &&
|
|
||||||
theTransforms.every(Boolean)
|
|
||||||
setEnableEqual(_enableEqual)
|
|
||||||
}, [guiMode, selectionRanges])
|
}, [guiMode, selectionRanges])
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, pathToNodeMap } =
|
const { modifiedAst, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -103,3 +63,72 @@ export const EqualLength = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
export function setEqualLengthInfo({
|
||||||
|
selectionRanges,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
}) {
|
||||||
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const varDecs = paths.map(
|
||||||
|
(pathToNode) =>
|
||||||
|
getNodeFromPath<VariableDeclarator>(
|
||||||
|
kclManager.ast,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)?.node
|
||||||
|
)
|
||||||
|
const primaryLine = varDecs[0]
|
||||||
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
|
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
toolTips.includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const transforms = getTransformInfos(
|
||||||
|
{
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
||||||
|
},
|
||||||
|
kclManager.ast,
|
||||||
|
'equalLength'
|
||||||
|
)
|
||||||
|
|
||||||
|
const enabled =
|
||||||
|
!!secondaryVarDecs.length &&
|
||||||
|
isAllTooltips &&
|
||||||
|
isOthersLinkedToPrimary &&
|
||||||
|
transforms.every(Boolean)
|
||||||
|
|
||||||
|
return { enabled, transforms }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyConstraintEqualLength({
|
||||||
|
selectionRanges,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
}): {
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
} {
|
||||||
|
const { enabled, transforms } = setEqualLengthInfo({ selectionRanges })
|
||||||
|
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos: transforms,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
})
|
||||||
|
return { modifiedAst, pathToNodeMap }
|
||||||
|
// kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// // callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|||||||
@ -1,69 +1,53 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/wasm'
|
import { Program, ProgramMemory, Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
} from '../../lang/queryAst'
|
} from '../../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
|
PathToNodeMap,
|
||||||
TransformInfo,
|
TransformInfo,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
import { sketchButtonClassnames } from 'Toolbar'
|
import { sketchButtonClassnames } from 'Toolbar'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
import { Selections } from 'useStore'
|
||||||
|
|
||||||
|
/*
|
||||||
export const HorzVert = ({
|
export const HorzVert = ({
|
||||||
horOrVert,
|
horOrVert,
|
||||||
}: {
|
}: {
|
||||||
horOrVert: 'vertical' | 'horizontal'
|
horOrVert: 'vertical' | 'horizontal'
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enableHorz, setEnableHorz] = useState(false)
|
const [enableHorz, setEnableHorz] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
const { enabled, transforms } = horzVertInfo(selectionRanges, horOrVert)
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
setTransformInfos(transforms)
|
||||||
getNodePathFromSourceRange(ast, range)
|
setEnableHorz(enabled)
|
||||||
)
|
|
||||||
const nodes = paths.map(
|
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
|
||||||
)
|
|
||||||
const isAllTooltips = nodes.every(
|
|
||||||
(node) =>
|
|
||||||
node?.type === 'CallExpression' &&
|
|
||||||
toolTips.includes(node.callee.name as any)
|
|
||||||
)
|
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(selectionRanges, ast, horOrVert)
|
|
||||||
setTransformInfos(theTransforms)
|
|
||||||
|
|
||||||
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
|
|
||||||
setEnableHorz(_enableHorz)
|
|
||||||
}, [guiMode, selectionRanges])
|
}, [guiMode, selectionRanges])
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!transformInfos || !ast) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
|
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -82,3 +66,51 @@ export const HorzVert = ({
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function horzVertInfo(
|
||||||
|
selectionRanges: Selections,
|
||||||
|
horOrVert: 'vertical' | 'horizontal'
|
||||||
|
) {
|
||||||
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
toolTips.includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const theTransforms = getTransformInfos(
|
||||||
|
selectionRanges,
|
||||||
|
kclManager.ast,
|
||||||
|
horOrVert
|
||||||
|
)
|
||||||
|
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
|
||||||
|
return { enabled: _enableHorz, transforms: theTransforms }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyConstraintHorzVert(
|
||||||
|
selectionRanges: Selections,
|
||||||
|
horOrVert: 'vertical' | 'horizontal',
|
||||||
|
ast: Program,
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
): {
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
} {
|
||||||
|
const transformInfos = horzVertInfo(selectionRanges, horOrVert).transforms
|
||||||
|
return transformAstSketchLines({
|
||||||
|
ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory,
|
||||||
|
referenceSegName: '',
|
||||||
|
})
|
||||||
|
// kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|||||||
@ -16,26 +16,22 @@ import {
|
|||||||
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
|
/*
|
||||||
export const Intersect = () => {
|
export const Intersect = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enable, setEnable] = useState(false)
|
const [enable, setEnable] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
const [forecdSelectionRanges, setForcedSelectionRanges] =
|
const [forecdSelectionRanges, setForcedSelectionRanges] =
|
||||||
useState<typeof selectionRanges>()
|
useState<typeof selectionRanges>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
|
||||||
if (selectionRanges.codeBasedSelections.length < 2) {
|
if (selectionRanges.codeBasedSelections.length < 2) {
|
||||||
setEnable(false)
|
setEnable(false)
|
||||||
setForcedSelectionRanges({ ...selectionRanges })
|
setForcedSelectionRanges({ ...selectionRanges })
|
||||||
@ -45,8 +41,8 @@ export const Intersect = () => {
|
|||||||
const previousSegment =
|
const previousSegment =
|
||||||
selectionRanges.codeBasedSelections.length > 1 &&
|
selectionRanges.codeBasedSelections.length > 1 &&
|
||||||
isLinesParallelAndConstrained(
|
isLinesParallelAndConstrained(
|
||||||
ast,
|
kclManager.ast,
|
||||||
programMemory,
|
kclManager.programMemory,
|
||||||
selectionRanges.codeBasedSelections[0],
|
selectionRanges.codeBasedSelections[0],
|
||||||
selectionRanges.codeBasedSelections[1]
|
selectionRanges.codeBasedSelections[1]
|
||||||
)
|
)
|
||||||
@ -70,15 +66,15 @@ export const Intersect = () => {
|
|||||||
setForcedSelectionRanges(_forcedSelectionRanges)
|
setForcedSelectionRanges(_forcedSelectionRanges)
|
||||||
|
|
||||||
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
|
const paths = _forcedSelectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
getNodePathFromSourceRange(ast, range)
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
)
|
)
|
||||||
const nodes = paths.map(
|
const nodes = paths.map(
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
)
|
)
|
||||||
const varDecs = paths.map(
|
const varDecs = paths.map(
|
||||||
(pathToNode) =>
|
(pathToNode) =>
|
||||||
getNodeFromPath<VariableDeclarator>(
|
getNodeFromPath<VariableDeclarator>(
|
||||||
ast,
|
kclManager.ast,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'VariableDeclarator'
|
'VariableDeclarator'
|
||||||
)?.node
|
)?.node
|
||||||
@ -86,7 +82,7 @@ export const Intersect = () => {
|
|||||||
const primaryLine = varDecs[0]
|
const primaryLine = varDecs[0]
|
||||||
const secondaryVarDecs = varDecs.slice(1)
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
isSketchVariablesLinked(secondary, primaryLine, ast)
|
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
|
||||||
)
|
)
|
||||||
const isAllTooltips = nodes.every(
|
const isAllTooltips = nodes.every(
|
||||||
(node) =>
|
(node) =>
|
||||||
@ -103,7 +99,7 @@ export const Intersect = () => {
|
|||||||
codeBasedSelections:
|
codeBasedSelections:
|
||||||
_forcedSelectionRanges.codeBasedSelections.slice(1),
|
_forcedSelectionRanges.codeBasedSelections.slice(1),
|
||||||
},
|
},
|
||||||
ast,
|
kclManager.ast,
|
||||||
'intersect'
|
'intersect'
|
||||||
)
|
)
|
||||||
setTransformInfos(theTransforms)
|
setTransformInfos(theTransforms)
|
||||||
@ -121,13 +117,13 @@ export const Intersect = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast && forecdSelectionRanges)) return
|
if (!(transformInfos && forecdSelectionRanges)) return
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges: forecdSelectionRanges,
|
selectionRanges: forecdSelectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
segName,
|
segName,
|
||||||
@ -150,7 +146,7 @@ export const Intersect = () => {
|
|||||||
initialVariableName: 'offset',
|
initialVariableName: 'offset',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -162,10 +158,10 @@ export const Intersect = () => {
|
|||||||
)
|
)
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges: forecdSelectionRanges,
|
selectionRanges: forecdSelectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -178,7 +174,7 @@ export const Intersect = () => {
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, true, {
|
kclManager.updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -190,3 +186,4 @@ export const Intersect = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@ -10,27 +10,23 @@ import {
|
|||||||
getRemoveConstraintsTransforms,
|
getRemoveConstraintsTransforms,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
|
/*
|
||||||
export const RemoveConstrainingValues = () => {
|
export const RemoveConstrainingValues = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enableHorz, setEnableHorz] = useState(false)
|
const [enableHorz, setEnableHorz] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
getNodePathFromSourceRange(ast, range)
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
)
|
)
|
||||||
const nodes = paths.map(
|
const nodes = paths.map(
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
)
|
)
|
||||||
const isAllTooltips = nodes.every(
|
const isAllTooltips = nodes.every(
|
||||||
(node) =>
|
(node) =>
|
||||||
@ -41,7 +37,7 @@ export const RemoveConstrainingValues = () => {
|
|||||||
try {
|
try {
|
||||||
const theTransforms = getRemoveConstraintsTransforms(
|
const theTransforms = getRemoveConstraintsTransforms(
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
ast,
|
kclManager.ast,
|
||||||
'removeConstrainingValues'
|
'removeConstrainingValues'
|
||||||
)
|
)
|
||||||
setTransformInfos(theTransforms)
|
setTransformInfos(theTransforms)
|
||||||
@ -57,15 +53,15 @@ export const RemoveConstrainingValues = () => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!transformInfos || !ast) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
|
const { modifiedAst, pathToNodeMap } = transformAstSketchLines({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
@ -76,3 +72,4 @@ export const RemoveConstrainingValues = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
createVariableDeclaration,
|
createVariableDeclaration,
|
||||||
} from '../../lang/modifyAst'
|
} from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
@ -31,16 +31,13 @@ const buttonLabels: Record<ButtonType, string> = {
|
|||||||
snapToXAxis: 'Snap To X Axis',
|
snapToXAxis: 'Snap To X Axis',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const disType: ConstraintType =
|
const disType: ConstraintType =
|
||||||
buttonType === 'xAbs' || buttonType === 'yAbs'
|
buttonType === 'xAbs' || buttonType === 'yAbs'
|
||||||
? buttonType
|
? buttonType
|
||||||
@ -50,13 +47,13 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
const [enableAngLen, setEnableAngLen] = useState(false)
|
const [enableAngLen, setEnableAngLen] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
getNodePathFromSourceRange(ast, range)
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
)
|
)
|
||||||
const nodes = paths.map(
|
const nodes = paths.map(
|
||||||
(pathToNode) =>
|
(pathToNode) =>
|
||||||
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
|
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression')
|
||||||
|
.node
|
||||||
)
|
)
|
||||||
const isAllTooltips = nodes.every(
|
const isAllTooltips = nodes.every(
|
||||||
(node) =>
|
(node) =>
|
||||||
@ -64,7 +61,11 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
toolTips.includes(node.callee.name as any)
|
toolTips.includes(node.callee.name as any)
|
||||||
)
|
)
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(selectionRanges, ast, disType)
|
const theTransforms = getTransformInfos(
|
||||||
|
selectionRanges,
|
||||||
|
kclManager.ast,
|
||||||
|
disType
|
||||||
|
)
|
||||||
setTransformInfos(theTransforms)
|
setTransformInfos(theTransforms)
|
||||||
|
|
||||||
const enableY =
|
const enableY =
|
||||||
@ -90,12 +91,12 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { valueUsedInTransform } = transformAstSketchLines({
|
const { valueUsedInTransform } = transformAstSketchLines({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges: selectionRanges,
|
selectionRanges: selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
@ -112,10 +113,10 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
|
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
transformAstSketchLines({
|
transformAstSketchLines({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges: selectionRanges,
|
selectionRanges: selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -129,7 +130,7 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAst(_modifiedAst, true, {
|
kclManager.updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -143,3 +144,4 @@ export const SetAbsDistance = ({ buttonType }: { buttonType: ButtonType }) => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { Selections, toolTips, useStore } from '../../useStore'
|
||||||
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
|
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -11,82 +11,41 @@ import {
|
|||||||
TransformInfo,
|
TransformInfo,
|
||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
|
PathToNodeMap,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createVariableDeclaration } from '../../lang/modifyAst'
|
import { createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
|
/*
|
||||||
export const SetAngleBetween = () => {
|
export const SetAngleBetween = () => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enable, setEnable] = useState(false)
|
const [enable, setEnable] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
const { enabled, transforms } = angleBetweenInfo({ selectionRanges })
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
setTransformInfos(transforms)
|
||||||
getNodePathFromSourceRange(ast, range)
|
setEnable(enabled)
|
||||||
)
|
|
||||||
const nodes = paths.map(
|
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
|
||||||
)
|
|
||||||
const varDecs = paths.map(
|
|
||||||
(pathToNode) =>
|
|
||||||
getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)?.node
|
|
||||||
)
|
|
||||||
const primaryLine = varDecs[0]
|
|
||||||
const secondaryVarDecs = varDecs.slice(1)
|
|
||||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
|
||||||
isSketchVariablesLinked(secondary, primaryLine, ast)
|
|
||||||
)
|
|
||||||
const isAllTooltips = nodes.every(
|
|
||||||
(node) =>
|
|
||||||
node?.type === 'CallExpression' &&
|
|
||||||
toolTips.includes(node.callee.name as any)
|
|
||||||
)
|
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(
|
|
||||||
{
|
|
||||||
...selectionRanges,
|
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
|
||||||
},
|
|
||||||
ast,
|
|
||||||
'setAngleBetween'
|
|
||||||
)
|
|
||||||
setTransformInfos(theTransforms)
|
|
||||||
|
|
||||||
const _enableEqual =
|
|
||||||
secondaryVarDecs.length === 1 &&
|
|
||||||
isAllTooltips &&
|
|
||||||
isOthersLinkedToPrimary &&
|
|
||||||
theTransforms.every(Boolean)
|
|
||||||
setEnable(_enableEqual)
|
|
||||||
}, [guiMode, selectionRanges])
|
}, [guiMode, selectionRanges])
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
segName,
|
segName,
|
||||||
@ -109,7 +68,7 @@ export const SetAngleBetween = () => {
|
|||||||
initialVariableName: 'angle',
|
initialVariableName: 'angle',
|
||||||
} as any)
|
} as any)
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -121,10 +80,10 @@ export const SetAngleBetween = () => {
|
|||||||
// transform again but forcing certain values
|
// transform again but forcing certain values
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -137,7 +96,7 @@ export const SetAngleBetween = () => {
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, true, {
|
kclManager.updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -149,3 +108,135 @@ export const SetAngleBetween = () => {
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function angleBetweenInfo({
|
||||||
|
selectionRanges,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
}) {
|
||||||
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
|
)
|
||||||
|
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const varDecs = paths.map(
|
||||||
|
(pathToNode) =>
|
||||||
|
getNodeFromPath<VariableDeclarator>(
|
||||||
|
kclManager.ast,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)?.node
|
||||||
|
)
|
||||||
|
const primaryLine = varDecs[0]
|
||||||
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
|
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
toolTips.includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const theTransforms = getTransformInfos(
|
||||||
|
{
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
||||||
|
},
|
||||||
|
kclManager.ast,
|
||||||
|
'setAngleBetween'
|
||||||
|
)
|
||||||
|
|
||||||
|
const _enableEqual =
|
||||||
|
secondaryVarDecs.length === 1 &&
|
||||||
|
isAllTooltips &&
|
||||||
|
isOthersLinkedToPrimary &&
|
||||||
|
theTransforms.every(Boolean)
|
||||||
|
return { enabled: _enableEqual, transforms: theTransforms }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyConstraintAngleBetween({
|
||||||
|
selectionRanges,
|
||||||
|
}: // constraint,
|
||||||
|
{
|
||||||
|
selectionRanges: Selections
|
||||||
|
// constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
|
}): Promise<{
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
}> {
|
||||||
|
const transformInfos = angleBetweenInfo({ selectionRanges }).transforms
|
||||||
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
|
transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
segName,
|
||||||
|
value,
|
||||||
|
valueNode,
|
||||||
|
variableName,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
sign,
|
||||||
|
}: {
|
||||||
|
segName: string
|
||||||
|
value: number
|
||||||
|
valueNode: Value
|
||||||
|
variableName?: string
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
sign: number
|
||||||
|
} = await getModalInfo({
|
||||||
|
segName: tagInfo?.tag,
|
||||||
|
isSegNameEditable: !tagInfo?.isTagExisting,
|
||||||
|
value: valueUsedInTransform,
|
||||||
|
initialVariableName: 'angle',
|
||||||
|
} as any)
|
||||||
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
|
return {
|
||||||
|
modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
// kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO handle cursor
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
const finalValue = removeDoubleNegatives(
|
||||||
|
valueNode as BinaryPart,
|
||||||
|
sign,
|
||||||
|
variableName
|
||||||
|
)
|
||||||
|
// transform again but forcing certain values
|
||||||
|
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
|
||||||
|
transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
forceSegName: segName,
|
||||||
|
forceValueUsedInTransform: finalValue,
|
||||||
|
})
|
||||||
|
if (variableName) {
|
||||||
|
const newBody = [..._modifiedAst.body]
|
||||||
|
newBody.splice(
|
||||||
|
newVariableInsertIndex,
|
||||||
|
0,
|
||||||
|
createVariableDeclaration(variableName, valueNode)
|
||||||
|
)
|
||||||
|
_modifiedAst.body = newBody
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _modifiedAst,
|
||||||
|
pathToNodeMap: _pathToNodeMap,
|
||||||
|
}
|
||||||
|
// kclManager.updateAst(_modifiedAst, true, {
|
||||||
|
// TODO handle cursor
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { toolTips, useStore } from '../../useStore'
|
||||||
import { BinaryPart, Value, VariableDeclarator } from '../../lang/wasm'
|
import { BinaryPart, Program, Value, VariableDeclarator } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
@ -12,11 +12,13 @@ import {
|
|||||||
transformSecondarySketchLinesTagFirst,
|
transformSecondarySketchLinesTagFirst,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
ConstraintType,
|
ConstraintType,
|
||||||
|
PathToNodeMap,
|
||||||
} from '../../lang/std/sketchcombos'
|
} from '../../lang/std/sketchcombos'
|
||||||
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
import { GetInfoModal } from '../SetHorVertDistanceModal'
|
||||||
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
import { createLiteral, createVariableDeclaration } from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
import { Selections } from 'useStore'
|
||||||
|
|
||||||
const getModalInfo = create(GetInfoModal as any)
|
const getModalInfo = create(GetInfoModal as any)
|
||||||
|
|
||||||
@ -33,20 +35,17 @@ const buttonLabels: Record<ButtonType, string> = {
|
|||||||
alignEndsVertically: 'Align Ends Vertically',
|
alignEndsVertically: 'Align Ends Vertically',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
export const SetHorzVertDistance = ({
|
export const SetHorzVertDistance = ({
|
||||||
buttonType,
|
buttonType,
|
||||||
}: {
|
}: {
|
||||||
buttonType: ButtonType
|
buttonType: ButtonType
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const constraint: ConstraintType =
|
const constraint: ConstraintType =
|
||||||
buttonType === 'setHorzDistance' || buttonType === 'setVertDistance'
|
buttonType === 'setHorzDistance' || buttonType === 'setVertDistance'
|
||||||
? buttonType
|
? buttonType
|
||||||
@ -56,51 +55,12 @@ export const SetHorzVertDistance = ({
|
|||||||
const [enable, setEnable] = useState(false)
|
const [enable, setEnable] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
const { transforms, enabled } = horzVertDistanceInfo({
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
selectionRanges,
|
||||||
getNodePathFromSourceRange(ast, range)
|
constraint,
|
||||||
)
|
})
|
||||||
const nodes = paths.map(
|
setTransformInfos(transforms)
|
||||||
(pathToNode) => getNodeFromPath<Value>(ast, pathToNode).node
|
setEnable(enabled)
|
||||||
)
|
|
||||||
const varDecs = paths.map(
|
|
||||||
(pathToNode) =>
|
|
||||||
getNodeFromPath<VariableDeclarator>(
|
|
||||||
ast,
|
|
||||||
pathToNode,
|
|
||||||
'VariableDeclarator'
|
|
||||||
)?.node
|
|
||||||
)
|
|
||||||
const primaryLine = varDecs[0]
|
|
||||||
const secondaryVarDecs = varDecs.slice(1)
|
|
||||||
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
|
||||||
isSketchVariablesLinked(secondary, primaryLine, ast)
|
|
||||||
)
|
|
||||||
const isAllTooltips = nodes.every(
|
|
||||||
(node) =>
|
|
||||||
node?.type === 'CallExpression' &&
|
|
||||||
[
|
|
||||||
...toolTips,
|
|
||||||
'startSketchAt', // TODO probably a better place for this to live
|
|
||||||
].includes(node.callee.name as any)
|
|
||||||
)
|
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(
|
|
||||||
{
|
|
||||||
...selectionRanges,
|
|
||||||
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
|
||||||
},
|
|
||||||
ast,
|
|
||||||
constraint
|
|
||||||
)
|
|
||||||
setTransformInfos(theTransforms)
|
|
||||||
|
|
||||||
const _enableEqual =
|
|
||||||
secondaryVarDecs.length === 1 &&
|
|
||||||
isAllTooltips &&
|
|
||||||
isOthersLinkedToPrimary &&
|
|
||||||
theTransforms.every(Boolean)
|
|
||||||
setEnable(_enableEqual)
|
|
||||||
}, [guiMode, selectionRanges])
|
}, [guiMode, selectionRanges])
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
@ -111,13 +71,13 @@ export const SetHorzVertDistance = ({
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
})
|
})
|
||||||
const {
|
const {
|
||||||
segName,
|
segName,
|
||||||
@ -142,7 +102,7 @@ export const SetHorzVertDistance = ({
|
|||||||
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||||
} as any))
|
} as any))
|
||||||
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
updateAst(modifiedAst, true, {
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -152,10 +112,10 @@ export const SetHorzVertDistance = ({
|
|||||||
// transform again but forcing certain values
|
// transform again but forcing certain values
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
transformSecondarySketchLinesTagFirst({
|
transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast: kclManager.ast,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
forceSegName: segName,
|
forceSegName: segName,
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -168,7 +128,7 @@ export const SetHorzVertDistance = ({
|
|||||||
)
|
)
|
||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
updateAst(_modifiedAst, true, {
|
kclManager.updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -180,3 +140,175 @@ export const SetHorzVertDistance = ({
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function horzVertDistanceInfo({
|
||||||
|
selectionRanges,
|
||||||
|
constraint,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
|
}) {
|
||||||
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) => getNodeFromPath<Value>(kclManager.ast, pathToNode).node
|
||||||
|
)
|
||||||
|
const varDecs = paths.map(
|
||||||
|
(pathToNode) =>
|
||||||
|
getNodeFromPath<VariableDeclarator>(
|
||||||
|
kclManager.ast,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclarator'
|
||||||
|
)?.node
|
||||||
|
)
|
||||||
|
const primaryLine = varDecs[0]
|
||||||
|
const secondaryVarDecs = varDecs.slice(1)
|
||||||
|
const isOthersLinkedToPrimary = secondaryVarDecs.every((secondary) =>
|
||||||
|
isSketchVariablesLinked(secondary, primaryLine, kclManager.ast)
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
[
|
||||||
|
...toolTips,
|
||||||
|
'startSketchAt', // TODO probably a better place for this to live
|
||||||
|
].includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const theTransforms = getTransformInfos(
|
||||||
|
{
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections: selectionRanges.codeBasedSelections.slice(1),
|
||||||
|
},
|
||||||
|
kclManager.ast,
|
||||||
|
constraint
|
||||||
|
)
|
||||||
|
const _enableEqual =
|
||||||
|
secondaryVarDecs.length === 1 &&
|
||||||
|
isAllTooltips &&
|
||||||
|
isOthersLinkedToPrimary &&
|
||||||
|
theTransforms.every(Boolean)
|
||||||
|
return { enabled: _enableEqual, transforms: theTransforms }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyConstraintHorzVertDistance({
|
||||||
|
selectionRanges,
|
||||||
|
constraint,
|
||||||
|
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
|
||||||
|
isAlign = false,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
|
isAlign?: boolean
|
||||||
|
}): Promise<{
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
}> {
|
||||||
|
const transformInfos = horzVertDistanceInfo({
|
||||||
|
selectionRanges,
|
||||||
|
constraint,
|
||||||
|
}).transforms
|
||||||
|
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
|
||||||
|
transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
})
|
||||||
|
const {
|
||||||
|
segName,
|
||||||
|
value,
|
||||||
|
valueNode,
|
||||||
|
variableName,
|
||||||
|
newVariableInsertIndex,
|
||||||
|
sign,
|
||||||
|
}: {
|
||||||
|
segName: string
|
||||||
|
value: number
|
||||||
|
valueNode: Value
|
||||||
|
variableName?: string
|
||||||
|
newVariableInsertIndex: number
|
||||||
|
sign: number
|
||||||
|
} = await (!isAlign &&
|
||||||
|
getModalInfo({
|
||||||
|
segName: tagInfo?.tag,
|
||||||
|
isSegNameEditable: !tagInfo?.isTagExisting,
|
||||||
|
value: valueUsedInTransform,
|
||||||
|
initialVariableName: constraint === 'setHorzDistance' ? 'xDis' : 'yDis',
|
||||||
|
} as any))
|
||||||
|
if (segName === tagInfo?.tag && value === valueUsedInTransform) {
|
||||||
|
return {
|
||||||
|
modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
// TODO handle cursor stuff
|
||||||
|
// kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
} else {
|
||||||
|
let finalValue = isAlign
|
||||||
|
? createLiteral(0)
|
||||||
|
: removeDoubleNegatives(valueNode as BinaryPart, sign, variableName)
|
||||||
|
// transform again but forcing certain values
|
||||||
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
|
transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
forceSegName: segName,
|
||||||
|
forceValueUsedInTransform: finalValue,
|
||||||
|
})
|
||||||
|
if (variableName) {
|
||||||
|
const newBody = [..._modifiedAst.body]
|
||||||
|
newBody.splice(
|
||||||
|
newVariableInsertIndex,
|
||||||
|
0,
|
||||||
|
createVariableDeclaration(variableName, valueNode)
|
||||||
|
)
|
||||||
|
_modifiedAst.body = newBody
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
// TODO handle cursor stuff
|
||||||
|
// kclManager.updateAst(_modifiedAst, true, {
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyConstraintHorzVertAlign({
|
||||||
|
selectionRanges,
|
||||||
|
constraint,
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
constraint: 'setHorzDistance' | 'setVertDistance'
|
||||||
|
}): {
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
} {
|
||||||
|
const transformInfos = horzVertDistanceInfo({
|
||||||
|
selectionRanges,
|
||||||
|
constraint,
|
||||||
|
}).transforms
|
||||||
|
let finalValue = createLiteral(0)
|
||||||
|
const { modifiedAst, pathToNodeMap } = transformSecondarySketchLinesTagFirst({
|
||||||
|
ast: kclManager.ast,
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
forceValueUsedInTransform: finalValue,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
modifiedAst: modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
// TODO handle cursor stuff
|
||||||
|
// kclManager.updateAst(_modifiedAst, true, {
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { toolTips, useStore } from '../../useStore'
|
import { Selections, toolTips, useStore } from '../../useStore'
|
||||||
import { Value } from '../../lang/wasm'
|
import { Program, Value } from '../../lang/wasm'
|
||||||
import {
|
import {
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
} from '../../lang/queryAst'
|
} from '../../lang/queryAst'
|
||||||
import {
|
import {
|
||||||
|
PathToNodeMap,
|
||||||
TransformInfo,
|
TransformInfo,
|
||||||
getTransformInfos,
|
getTransformInfos,
|
||||||
transformAstSketchLines,
|
transformAstSketchLines,
|
||||||
@ -19,7 +20,7 @@ import {
|
|||||||
} from '../../lang/modifyAst'
|
} from '../../lang/modifyAst'
|
||||||
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
import { removeDoubleNegatives } from '../AvailableVarsHelpers'
|
||||||
import { normaliseAngle } from '../../lib/utils'
|
import { normaliseAngle } from '../../lib/utils'
|
||||||
import { updateCursors } from '../../lang/util'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
const getModalInfo = create(SetAngleLengthModal as any)
|
const getModalInfo = create(SetAngleLengthModal as any)
|
||||||
|
|
||||||
@ -30,54 +31,39 @@ const buttonLabels: Record<ButtonType, string> = {
|
|||||||
setLength: 'Set Length',
|
setLength: 'Set Length',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
export const SetAngleLength = ({
|
export const SetAngleLength = ({
|
||||||
angleOrLength,
|
angleOrLength,
|
||||||
}: {
|
}: {
|
||||||
angleOrLength: ButtonType
|
angleOrLength: ButtonType
|
||||||
}) => {
|
}) => {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst, setCursor } =
|
const { guiMode, selectionRanges, setCursor } = useStore((s) => ({
|
||||||
useStore((s) => ({
|
guiMode: s.guiMode,
|
||||||
guiMode: s.guiMode,
|
selectionRanges: s.selectionRanges,
|
||||||
ast: s.ast,
|
setCursor: s.setCursor,
|
||||||
updateAst: s.updateAst,
|
}))
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
setCursor: s.setCursor,
|
|
||||||
}))
|
|
||||||
const [enableAngLen, setEnableAngLen] = useState(false)
|
const [enableAngLen, setEnableAngLen] = useState(false)
|
||||||
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
const [transformInfos, setTransformInfos] = useState<TransformInfo[]>()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
const { enabled, transforms } = setAngleLengthInfo({
|
||||||
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
selectionRanges,
|
||||||
getNodePathFromSourceRange(ast, range)
|
angleOrLength,
|
||||||
)
|
})
|
||||||
const nodes = paths.map(
|
|
||||||
(pathToNode) =>
|
|
||||||
getNodeFromPath<Value>(ast, pathToNode, 'CallExpression').node
|
|
||||||
)
|
|
||||||
const isAllTooltips = nodes.every(
|
|
||||||
(node) =>
|
|
||||||
node?.type === 'CallExpression' &&
|
|
||||||
toolTips.includes(node.callee.name as any)
|
|
||||||
)
|
|
||||||
|
|
||||||
const theTransforms = getTransformInfos(selectionRanges, ast, angleOrLength)
|
setTransformInfos(transforms)
|
||||||
setTransformInfos(theTransforms)
|
setEnableAngLen(enabled)
|
||||||
|
|
||||||
const _enableHorz = isAllTooltips && theTransforms.every(Boolean)
|
|
||||||
setEnableAngLen(_enableHorz)
|
|
||||||
}, [guiMode, selectionRanges])
|
}, [guiMode, selectionRanges])
|
||||||
if (guiMode.mode !== 'sketch') return null
|
if (guiMode.mode !== 'sketch') return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
if (!(transformInfos && ast)) return
|
if (!transformInfos) return
|
||||||
const { valueUsedInTransform } = transformAstSketchLines({
|
const { valueUsedInTransform } = transformAstSketchLines({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
})
|
})
|
||||||
try {
|
try {
|
||||||
@ -126,10 +112,10 @@ export const SetAngleLength = ({
|
|||||||
|
|
||||||
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
transformAstSketchLines({
|
transformAstSketchLines({
|
||||||
ast: JSON.parse(JSON.stringify(ast)),
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
transformInfos,
|
transformInfos,
|
||||||
programMemory,
|
programMemory: kclManager.programMemory,
|
||||||
referenceSegName: '',
|
referenceSegName: '',
|
||||||
forceValueUsedInTransform: finalValue,
|
forceValueUsedInTransform: finalValue,
|
||||||
})
|
})
|
||||||
@ -143,7 +129,7 @@ export const SetAngleLength = ({
|
|||||||
_modifiedAst.body = newBody
|
_modifiedAst.body = newBody
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAst(_modifiedAst, true, {
|
kclManager.updateAst(_modifiedAst, true, {
|
||||||
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
})
|
})
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -157,3 +143,119 @@ export const SetAngleLength = ({
|
|||||||
</button>
|
</button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function setAngleLengthInfo({
|
||||||
|
selectionRanges,
|
||||||
|
angleOrLength = 'setLength',
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
angleOrLength?: 'setLength' | 'setAngle'
|
||||||
|
}) {
|
||||||
|
const paths = selectionRanges.codeBasedSelections.map(({ range }) =>
|
||||||
|
getNodePathFromSourceRange(kclManager.ast, range)
|
||||||
|
)
|
||||||
|
const nodes = paths.map(
|
||||||
|
(pathToNode) =>
|
||||||
|
getNodeFromPath<Value>(kclManager.ast, pathToNode, 'CallExpression').node
|
||||||
|
)
|
||||||
|
const isAllTooltips = nodes.every(
|
||||||
|
(node) =>
|
||||||
|
node?.type === 'CallExpression' &&
|
||||||
|
toolTips.includes(node.callee.name as any)
|
||||||
|
)
|
||||||
|
|
||||||
|
const transforms = getTransformInfos(
|
||||||
|
selectionRanges,
|
||||||
|
kclManager.ast,
|
||||||
|
angleOrLength
|
||||||
|
)
|
||||||
|
const enabled = isAllTooltips && transforms.every(Boolean)
|
||||||
|
return { enabled, transforms }
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function applyConstraintAngleLength({
|
||||||
|
selectionRanges,
|
||||||
|
angleOrLength = 'setLength',
|
||||||
|
}: {
|
||||||
|
selectionRanges: Selections
|
||||||
|
angleOrLength?: 'setLength' | 'setAngle'
|
||||||
|
}): Promise<{
|
||||||
|
modifiedAst: Program
|
||||||
|
pathToNodeMap: PathToNodeMap
|
||||||
|
}> {
|
||||||
|
const { transforms } = setAngleLengthInfo({ selectionRanges, angleOrLength })
|
||||||
|
const { valueUsedInTransform } = transformAstSketchLines({
|
||||||
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos: transforms,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
referenceSegName: '',
|
||||||
|
})
|
||||||
|
try {
|
||||||
|
const isReferencingYAxis =
|
||||||
|
selectionRanges.otherSelections.length === 1 &&
|
||||||
|
selectionRanges.otherSelections[0] === 'y-axis'
|
||||||
|
const isReferencingYAxisAngle =
|
||||||
|
isReferencingYAxis && angleOrLength === 'setAngle'
|
||||||
|
|
||||||
|
const isReferencingXAxis =
|
||||||
|
selectionRanges.otherSelections.length === 1 &&
|
||||||
|
selectionRanges.otherSelections[0] === 'x-axis'
|
||||||
|
const isReferencingXAxisAngle =
|
||||||
|
isReferencingXAxis && angleOrLength === 'setAngle'
|
||||||
|
|
||||||
|
let forceVal = valueUsedInTransform || 0
|
||||||
|
let calcIdentifier = createIdentifier('_0')
|
||||||
|
if (isReferencingYAxisAngle) {
|
||||||
|
calcIdentifier = createIdentifier(forceVal < 0 ? '_270' : '_90')
|
||||||
|
forceVal = normaliseAngle(forceVal + (forceVal < 0 ? 90 : -90))
|
||||||
|
} else if (isReferencingXAxisAngle) {
|
||||||
|
calcIdentifier = createIdentifier(Math.abs(forceVal) > 90 ? '_180' : '_0')
|
||||||
|
forceVal =
|
||||||
|
Math.abs(forceVal) > 90 ? normaliseAngle(forceVal - 180) : forceVal
|
||||||
|
}
|
||||||
|
const { valueNode, variableName, newVariableInsertIndex, sign } =
|
||||||
|
await getModalInfo({
|
||||||
|
value: forceVal,
|
||||||
|
valueName: angleOrLength === 'setAngle' ? 'angle' : 'length',
|
||||||
|
shouldCreateVariable: true,
|
||||||
|
} as any)
|
||||||
|
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
|
||||||
|
if (
|
||||||
|
isReferencingYAxisAngle ||
|
||||||
|
(isReferencingXAxisAngle && calcIdentifier.name !== '_0')
|
||||||
|
) {
|
||||||
|
finalValue = createBinaryExpressionWithUnary([calcIdentifier, finalValue])
|
||||||
|
}
|
||||||
|
|
||||||
|
const { modifiedAst: _modifiedAst, pathToNodeMap } =
|
||||||
|
transformAstSketchLines({
|
||||||
|
ast: JSON.parse(JSON.stringify(kclManager.ast)),
|
||||||
|
selectionRanges,
|
||||||
|
transformInfos: transforms,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
referenceSegName: '',
|
||||||
|
forceValueUsedInTransform: finalValue,
|
||||||
|
})
|
||||||
|
if (variableName) {
|
||||||
|
const newBody = [..._modifiedAst.body]
|
||||||
|
newBody.splice(
|
||||||
|
newVariableInsertIndex,
|
||||||
|
0,
|
||||||
|
createVariableDeclaration(variableName, valueNode)
|
||||||
|
)
|
||||||
|
_modifiedAst.body = newBody
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
modifiedAst: _modifiedAst,
|
||||||
|
pathToNodeMap,
|
||||||
|
}
|
||||||
|
// kclManager.updateAst(_modifiedAst, true, {
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
// })
|
||||||
|
} catch (e) {
|
||||||
|
console.log('erorr', e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
import { fireEvent, render, screen } from '@testing-library/react'
|
import { fireEvent, render, screen } from '@testing-library/react'
|
||||||
import UserSidebarMenu from './UserSidebarMenu'
|
import UserSidebarMenu from './UserSidebarMenu'
|
||||||
import { BrowserRouter } from 'react-router-dom'
|
import {
|
||||||
|
Route,
|
||||||
|
RouterProvider,
|
||||||
|
createMemoryRouter,
|
||||||
|
createRoutesFromElements,
|
||||||
|
} from 'react-router-dom'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { GlobalStateProvider } from './GlobalStateProvider'
|
import { GlobalStateProvider } from './GlobalStateProvider'
|
||||||
import CommandBarProvider from './CommandBar'
|
import CommandBarProvider from './CommandBar'
|
||||||
@ -93,11 +98,24 @@ describe('UserSidebarMenu tests', () => {
|
|||||||
|
|
||||||
function TestWrap({ children }: { children: React.ReactNode }) {
|
function TestWrap({ children }: { children: React.ReactNode }) {
|
||||||
// wrap in router and xState context
|
// wrap in router and xState context
|
||||||
return (
|
// We have to use a memory router in the testing environment,
|
||||||
<BrowserRouter>
|
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
|
||||||
<CommandBarProvider>
|
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
|
||||||
<GlobalStateProvider>{children}</GlobalStateProvider>
|
const router = createMemoryRouter(
|
||||||
</CommandBarProvider>
|
createRoutesFromElements(
|
||||||
</BrowserRouter>
|
<Route
|
||||||
|
path="/file/:id"
|
||||||
|
element={
|
||||||
|
<CommandBarProvider>
|
||||||
|
<GlobalStateProvider>{children}</GlobalStateProvider>
|
||||||
|
</CommandBarProvider>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
{
|
||||||
|
initialEntries: ['/file/new'],
|
||||||
|
initialIndex: 0,
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
return <RouterProvider router={router} />
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,20 +10,19 @@ import { faGithub } from '@fortawesome/free-brands-svg-icons'
|
|||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { Fragment, useState } from 'react'
|
import { Fragment, useState } from 'react'
|
||||||
import { paths } from '../Router'
|
import { paths } from '../Router'
|
||||||
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
|
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
|
||||||
type User = Models['User_type']
|
type User = Models['User_type']
|
||||||
|
|
||||||
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
const UserSidebarMenu = ({ user }: { user?: User }) => {
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const filePath = useAbsoluteFilePath()
|
||||||
const displayedName = getDisplayName(user)
|
const displayedName = getDisplayName(user)
|
||||||
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
const [imageLoadFailed, setImageLoadFailed] = useState(false)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const {
|
const send = useGlobalStateContext()?.auth?.send
|
||||||
auth: { send },
|
|
||||||
} = useGlobalStateContext()
|
|
||||||
|
|
||||||
// Fallback logic for displaying user's "name":
|
// Fallback logic for displaying user's "name":
|
||||||
// 1. user.name
|
// 1. user.name
|
||||||
@ -132,11 +131,10 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
|
|||||||
// since /settings is a nested route the sidebar doesn't close
|
// since /settings is a nested route the sidebar doesn't close
|
||||||
// automatically when navigating to it
|
// automatically when navigating to it
|
||||||
close()
|
close()
|
||||||
navigate(
|
const targetPath = location.pathname.includes(paths.FILE)
|
||||||
(location.pathname.endsWith('/')
|
? filePath + paths.SETTINGS
|
||||||
? location.pathname.slice(0, -1)
|
: paths.HOME + paths.SETTINGS
|
||||||
: location.pathname) + paths.SETTINGS
|
navigate(targetPath)
|
||||||
)
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Settings
|
Settings
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
// all web app environment variables are defined here, jest doesn't like import.meta.env so centralising them here
|
// env vars were centralised so they could be mocked in jest
|
||||||
// allows us to mock them in one place, see src/setupTests.ts, it pulls the variable names and valuse from .env.development
|
// but isn't needed anymore with vite, so is now just a convention
|
||||||
// note the exported variable name must match the env var name for the jest mocks to work
|
|
||||||
// i.e. const VITE_MY_VAR = import.meta.env.VITE_MY_VAR
|
|
||||||
// Maybe this file should be generated in a GHA from .env.development?
|
|
||||||
|
|
||||||
export const VITE_KC_API_WS_MODELING_URL = import.meta.env
|
export const VITE_KC_API_WS_MODELING_URL = import.meta.env
|
||||||
.VITE_KC_API_WS_MODELING_URL
|
.VITE_KC_API_WS_MODELING_URL
|
||||||
@ -12,3 +9,4 @@ export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
|
|||||||
.VITE_KC_CONNECTION_TIMEOUT_MS
|
.VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
|
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
|
||||||
export const TEST = import.meta.env.TEST
|
export const TEST = import.meta.env.TEST
|
||||||
|
export const DEV = import.meta.env.DEV
|
||||||
|
|||||||
12
src/hooks/useAbsoluteFilePath.ts
Normal file
12
src/hooks/useAbsoluteFilePath.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { BROWSER_FILE_NAME, IndexLoaderData, paths } from 'Router'
|
||||||
|
import { useRouteLoaderData } from 'react-router-dom'
|
||||||
|
|
||||||
|
export function useAbsoluteFilePath() {
|
||||||
|
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
|
|
||||||
|
return (
|
||||||
|
paths.FILE +
|
||||||
|
'/' +
|
||||||
|
encodeURIComponent(routeData?.project?.path || BROWSER_FILE_NAME)
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,289 +0,0 @@
|
|||||||
// needed somewhere to dump this logic,
|
|
||||||
// Once we have xState this should be removed
|
|
||||||
|
|
||||||
import { useStore, Selections } from 'useStore'
|
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
|
||||||
import { ArtifactMap, EngineCommandManager } from 'lang/std/engineConnection'
|
|
||||||
import { Models } from '@kittycad/lib/dist/types/src'
|
|
||||||
import { isReducedMotion } from 'lang/util'
|
|
||||||
import { isOverlap } from 'lib/utils'
|
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
|
||||||
|
|
||||||
interface DefaultPlanes {
|
|
||||||
xy: string
|
|
||||||
// TODO re-enable
|
|
||||||
// yz: string
|
|
||||||
// xz: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useAppMode() {
|
|
||||||
const { guiMode, setGuiMode, selectionRanges, selectionRangeTypeMap } =
|
|
||||||
useStore((s) => ({
|
|
||||||
guiMode: s.guiMode,
|
|
||||||
setGuiMode: s.setGuiMode,
|
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
selectionRangeTypeMap: s.selectionRangeTypeMap,
|
|
||||||
}))
|
|
||||||
const [defaultPlanes, setDefaultPlanes] = useState<DefaultPlanes | null>(null)
|
|
||||||
useEffect(() => {
|
|
||||||
if (
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === 'selectFace' &&
|
|
||||||
engineCommandManager
|
|
||||||
) {
|
|
||||||
const createAndShowPlanes = async () => {
|
|
||||||
let localDefaultPlanes: DefaultPlanes
|
|
||||||
if (!defaultPlanes) {
|
|
||||||
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
|
||||||
setDefaultPlanes(localDefaultPlanes)
|
|
||||||
} else {
|
|
||||||
localDefaultPlanes = defaultPlanes
|
|
||||||
}
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, false)
|
|
||||||
}
|
|
||||||
createAndShowPlanes()
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
guiMode.mode === 'sketch' &&
|
|
||||||
guiMode.sketchMode === 'enterSketchEdit' &&
|
|
||||||
engineCommandManager
|
|
||||||
) {
|
|
||||||
const enableSketchMode = async () => {
|
|
||||||
let localDefaultPlanes: DefaultPlanes
|
|
||||||
if (!defaultPlanes) {
|
|
||||||
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
|
|
||||||
setDefaultPlanes(localDefaultPlanes)
|
|
||||||
} else {
|
|
||||||
localDefaultPlanes = defaultPlanes
|
|
||||||
}
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, localDefaultPlanes, true)
|
|
||||||
// TODO figure out the plane to use based on the sketch
|
|
||||||
// maybe it's easier to make a new plane than rely on the defaults
|
|
||||||
await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'sketch_mode_enable',
|
|
||||||
plane_id: localDefaultPlanes.xy,
|
|
||||||
ortho: true,
|
|
||||||
animated: !isReducedMotion(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const proms: any[] = []
|
|
||||||
proms.push(
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: guiMode.pathId,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
await Promise.all(proms)
|
|
||||||
}
|
|
||||||
enableSketchMode()
|
|
||||||
setGuiMode({
|
|
||||||
...guiMode,
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (guiMode.mode !== 'sketch' && defaultPlanes) {
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
|
||||||
}
|
|
||||||
if (guiMode.mode === 'default') {
|
|
||||||
const pathId =
|
|
||||||
engineCommandManager &&
|
|
||||||
isCursorInSketchCommandRange(
|
|
||||||
engineCommandManager.artifactMap,
|
|
||||||
selectionRanges
|
|
||||||
)
|
|
||||||
if (pathId) {
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'canEditSketch',
|
|
||||||
rotation: [0, 0, 0, 1],
|
|
||||||
position: [0, 0, 0],
|
|
||||||
pathToNode: [],
|
|
||||||
pathId,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (guiMode.mode === 'canEditSketch') {
|
|
||||||
if (
|
|
||||||
!engineCommandManager ||
|
|
||||||
!isCursorInSketchCommandRange(
|
|
||||||
engineCommandManager.artifactMap,
|
|
||||||
selectionRanges
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'default',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
guiMode,
|
|
||||||
guiMode.mode,
|
|
||||||
engineCommandManager,
|
|
||||||
selectionRanges,
|
|
||||||
selectionRangeTypeMap,
|
|
||||||
])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unSub = engineCommandManager.subscribeTo({
|
|
||||||
event: 'select_with_point',
|
|
||||||
callback: async ({ data }) => {
|
|
||||||
if (!data.entity_id) return
|
|
||||||
if (!defaultPlanes) return
|
|
||||||
if (!Object.values(defaultPlanes || {}).includes(data.entity_id)) {
|
|
||||||
// user clicked something else in the scene
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const sketchModeResponse = await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'sketch_mode_enable',
|
|
||||||
plane_id: data.entity_id,
|
|
||||||
ortho: true,
|
|
||||||
animated: !isReducedMotion(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
setDefaultPlanesHidden(engineCommandManager, defaultPlanes, true)
|
|
||||||
const sketchUuid = uuidv4()
|
|
||||||
const proms: any[] = []
|
|
||||||
proms.push(
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: sketchUuid,
|
|
||||||
cmd: {
|
|
||||||
type: 'start_path',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
proms.push(
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'edit_mode_enter',
|
|
||||||
target: sketchUuid,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
)
|
|
||||||
await Promise.all(proms)
|
|
||||||
setGuiMode({
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
rotation: [0, 0, 0, 1],
|
|
||||||
position: [0, 0, 0],
|
|
||||||
pathToNode: [],
|
|
||||||
pathId: sketchUuid,
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log('sketchModeResponse', sketchModeResponse)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return unSub
|
|
||||||
}, [engineCommandManager, defaultPlanes])
|
|
||||||
}
|
|
||||||
|
|
||||||
async function createPlane(
|
|
||||||
engineCommandManager: EngineCommandManager,
|
|
||||||
{
|
|
||||||
x_axis,
|
|
||||||
y_axis,
|
|
||||||
color,
|
|
||||||
}: {
|
|
||||||
x_axis: Models['Point3d_type']
|
|
||||||
y_axis: Models['Point3d_type']
|
|
||||||
color: Models['Color_type']
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const planeId = uuidv4()
|
|
||||||
await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'make_plane',
|
|
||||||
size: 60,
|
|
||||||
origin: { x: 0, y: 0, z: 0 },
|
|
||||||
x_axis,
|
|
||||||
y_axis,
|
|
||||||
clobber: false,
|
|
||||||
},
|
|
||||||
cmd_id: planeId,
|
|
||||||
})
|
|
||||||
await engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd: {
|
|
||||||
type: 'plane_set_color',
|
|
||||||
plane_id: planeId,
|
|
||||||
color,
|
|
||||||
},
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
})
|
|
||||||
return planeId
|
|
||||||
}
|
|
||||||
|
|
||||||
function setDefaultPlanesHidden(
|
|
||||||
engineCommandManager: EngineCommandManager,
|
|
||||||
defaultPlanes: DefaultPlanes,
|
|
||||||
hidden: boolean
|
|
||||||
) {
|
|
||||||
Object.values(defaultPlanes).forEach((planeId) => {
|
|
||||||
engineCommandManager.sendSceneCommand({
|
|
||||||
type: 'modeling_cmd_req',
|
|
||||||
cmd_id: uuidv4(),
|
|
||||||
cmd: {
|
|
||||||
type: 'object_visible',
|
|
||||||
object_id: planeId,
|
|
||||||
hidden: hidden,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initDefaultPlanes(
|
|
||||||
engineCommandManager: EngineCommandManager
|
|
||||||
): Promise<DefaultPlanes> {
|
|
||||||
const xy = await createPlane(engineCommandManager, {
|
|
||||||
x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
y_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
|
||||||
})
|
|
||||||
// TODO re-enable
|
|
||||||
// const yz = createPlane(engineCommandManager, {
|
|
||||||
// x_axis: { x: 0, y: 1, z: 0 },
|
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
|
||||||
// })
|
|
||||||
// const xz = createPlane(engineCommandManager, {
|
|
||||||
// x_axis: { x: 1, y: 0, z: 0 },
|
|
||||||
// y_axis: { x: 0, y: 0, z: 1 },
|
|
||||||
// color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
|
||||||
// })
|
|
||||||
return { xy }
|
|
||||||
}
|
|
||||||
|
|
||||||
function isCursorInSketchCommandRange(
|
|
||||||
artifactMap: ArtifactMap,
|
|
||||||
selectionRanges: Selections
|
|
||||||
): string | false {
|
|
||||||
const overlapingEntries = Object.entries(artifactMap || {}).filter(
|
|
||||||
([id, artifact]) =>
|
|
||||||
selectionRanges.codeBasedSelections.some(
|
|
||||||
(selection) =>
|
|
||||||
Array.isArray(selection?.range) &&
|
|
||||||
Array.isArray(artifact?.range) &&
|
|
||||||
isOverlap(selection.range, artifact.range) &&
|
|
||||||
(artifact.commandType === 'start_path' ||
|
|
||||||
artifact.commandType === 'extend_path' ||
|
|
||||||
artifact.commandType === 'close_path')
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return overlapingEntries.length && overlapingEntries[0][1].parentId
|
|
||||||
? overlapingEntries[0][1].parentId
|
|
||||||
: overlapingEntries.find(
|
|
||||||
([, artifact]) => artifact.commandType === 'start_path'
|
|
||||||
)?.[0] || false
|
|
||||||
}
|
|
||||||
@ -1,13 +1,14 @@
|
|||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
|
import { useModelingContext } from './useModelingContext'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
const { setCursor2, setHighlightRange, highlightRange } = useStore((s) => ({
|
const { setHighlightRange, highlightRange } = useStore((s) => ({
|
||||||
setCursor2: s.setCursor2,
|
|
||||||
setHighlightRange: s.setHighlightRange,
|
setHighlightRange: s.setHighlightRange,
|
||||||
highlightRange: s.highlightRange,
|
highlightRange: s.highlightRange,
|
||||||
}))
|
}))
|
||||||
|
const { send } = useModelingContext()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!engineCommandManager) return
|
if (!engineCommandManager) return
|
||||||
|
|
||||||
@ -30,16 +31,25 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
event: 'select_with_point',
|
event: 'select_with_point',
|
||||||
callback: ({ data }) => {
|
callback: ({ data }) => {
|
||||||
if (!data?.entity_id) {
|
if (!data?.entity_id) {
|
||||||
setCursor2()
|
send({
|
||||||
|
type: 'Set selection',
|
||||||
|
data: { selectionType: 'singleCodeCursor' },
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
|
const sourceRange = engineCommandManager.sourceRangeMap[data.entity_id]
|
||||||
setCursor2({ range: sourceRange, type: 'default' })
|
send({
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: { range: sourceRange, type: 'default' },
|
||||||
|
},
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
return () => {
|
return () => {
|
||||||
unSubHover()
|
unSubHover()
|
||||||
unSubClick()
|
unSubClick()
|
||||||
}
|
}
|
||||||
}, [engineCommandManager, setCursor2, setHighlightRange, highlightRange])
|
}, [engineCommandManager, setHighlightRange, highlightRange])
|
||||||
}
|
}
|
||||||
|
|||||||
6
src/hooks/useModelingContext.ts
Normal file
6
src/hooks/useModelingContext.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { ModelingMachineContext } from 'components/ModelingMachineProvider'
|
||||||
|
import { useContext } from 'react'
|
||||||
|
|
||||||
|
export const useModelingContext = () => {
|
||||||
|
return useContext(ModelingMachineContext)
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
import { useLayoutEffect, useEffect, useRef } from 'react'
|
import { useLayoutEffect, useEffect, useRef } from 'react'
|
||||||
import { _executor } from '../lang/wasm'
|
import { _executor, parse } from '../lang/wasm'
|
||||||
import { useStore } from '../useStore'
|
import { useStore } from '../useStore'
|
||||||
import { engineCommandManager } from '../lang/std/engineConnection'
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { deferExecution } from 'lib/utils'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export function useSetupEngineManager(
|
export function useSetupEngineManager(
|
||||||
streamRef: React.RefObject<HTMLDivElement>,
|
streamRef: React.RefObject<HTMLDivElement>,
|
||||||
@ -14,13 +14,11 @@ export function useSetupEngineManager(
|
|||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
setStreamDimensions,
|
setStreamDimensions,
|
||||||
streamDimensions,
|
streamDimensions,
|
||||||
executeCode,
|
|
||||||
} = useStore((s) => ({
|
} = useStore((s) => ({
|
||||||
setMediaStream: s.setMediaStream,
|
setMediaStream: s.setMediaStream,
|
||||||
setIsStreamReady: s.setIsStreamReady,
|
setIsStreamReady: s.setIsStreamReady,
|
||||||
setStreamDimensions: s.setStreamDimensions,
|
setStreamDimensions: s.setStreamDimensions,
|
||||||
streamDimensions: s.streamDimensions,
|
streamDimensions: s.streamDimensions,
|
||||||
executeCode: s.executeCode,
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
const streamWidth = streamRef?.current?.offsetWidth
|
const streamWidth = streamRef?.current?.offsetWidth
|
||||||
@ -29,7 +27,7 @@ export function useSetupEngineManager(
|
|||||||
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
const hasSetNonZeroDimensions = useRef<boolean>(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
executeCode()
|
kclManager.executeCode()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
@ -45,7 +43,10 @@ export function useSetupEngineManager(
|
|||||||
setIsStreamReady,
|
setIsStreamReady,
|
||||||
width: quadWidth,
|
width: quadWidth,
|
||||||
height: quadHeight,
|
height: quadHeight,
|
||||||
executeCode,
|
executeCode: (code?: string) => {
|
||||||
|
const _ast = parse(code || kclManager.code)
|
||||||
|
return kclManager.executeAst(_ast, true)
|
||||||
|
},
|
||||||
token,
|
token,
|
||||||
})
|
})
|
||||||
setStreamDimensions({
|
setStreamDimensions({
|
||||||
|
|||||||
@ -1,52 +1,43 @@
|
|||||||
import { SetVarNameModal } from 'components/SetVarNameModal'
|
import { SetVarNameModal } from 'components/SetVarNameModal'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { create } from 'react-modal-promise'
|
import { create } from 'react-modal-promise'
|
||||||
import { useStore } from 'useStore'
|
import { useModelingContext } from './useModelingContext'
|
||||||
|
|
||||||
const getModalInfo = create(SetVarNameModal as any)
|
const getModalInfo = create(SetVarNameModal as any)
|
||||||
|
|
||||||
export function useConvertToVariable() {
|
export function useConvertToVariable() {
|
||||||
const { guiMode, selectionRanges, ast, programMemory, updateAst } = useStore(
|
const { context } = useModelingContext()
|
||||||
(s) => ({
|
|
||||||
guiMode: s.guiMode,
|
|
||||||
ast: s.ast,
|
|
||||||
updateAst: s.updateAst,
|
|
||||||
selectionRanges: s.selectionRanges,
|
|
||||||
programMemory: s.programMemory,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
const [enable, setEnabled] = useState(false)
|
const [enable, setEnabled] = useState(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ast) return
|
|
||||||
|
|
||||||
const { isSafe, value } = isNodeSafeToReplace(
|
const { isSafe, value } = isNodeSafeToReplace(
|
||||||
ast,
|
kclManager.ast,
|
||||||
selectionRanges.codeBasedSelections?.[0]?.range || []
|
context.selectionRanges.codeBasedSelections?.[0]?.range || []
|
||||||
)
|
)
|
||||||
const canReplace = isSafe && value.type !== 'Identifier'
|
const canReplace = isSafe && value.type !== 'Identifier'
|
||||||
const isOnlyOneSelection = selectionRanges.codeBasedSelections.length === 1
|
const isOnlyOneSelection =
|
||||||
|
context.selectionRanges.codeBasedSelections.length === 1
|
||||||
|
|
||||||
const _enableHorz = canReplace && isOnlyOneSelection
|
const _enableHorz = canReplace && isOnlyOneSelection
|
||||||
setEnabled(_enableHorz)
|
setEnabled(_enableHorz)
|
||||||
}, [guiMode, selectionRanges])
|
}, [context.selectionRanges])
|
||||||
|
|
||||||
const handleClick = async () => {
|
const handleClick = async () => {
|
||||||
if (!ast) return
|
|
||||||
try {
|
try {
|
||||||
const { variableName } = await getModalInfo({
|
const { variableName } = await getModalInfo({
|
||||||
valueName: 'var',
|
valueName: 'var',
|
||||||
} as any)
|
} as any)
|
||||||
|
|
||||||
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
|
const { modifiedAst: _modifiedAst } = moveValueIntoNewVariable(
|
||||||
ast,
|
kclManager.ast,
|
||||||
programMemory,
|
kclManager.programMemory,
|
||||||
selectionRanges.codeBasedSelections[0].range,
|
context.selectionRanges.codeBasedSelections[0].range,
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
|
|
||||||
updateAst(_modifiedAst, true)
|
kclManager.updateAst(_modifiedAst, true)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,13 @@ import reportWebVitals from './reportWebVitals'
|
|||||||
import { Toaster } from 'react-hot-toast'
|
import { Toaster } from 'react-hot-toast'
|
||||||
import { Router } from './Router'
|
import { Router } from './Router'
|
||||||
import { HotkeysProvider } from 'react-hotkeys-hook'
|
import { HotkeysProvider } from 'react-hotkeys-hook'
|
||||||
|
import { inspect } from '@xstate/inspect'
|
||||||
|
import { DEV } from 'env'
|
||||||
|
|
||||||
|
if (DEV)
|
||||||
|
inspect({
|
||||||
|
iframe: false,
|
||||||
|
})
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||||
|
|
||||||
|
|||||||
347
src/lang/KclSinglton.tsx
Normal file
347
src/lang/KclSinglton.tsx
Normal file
@ -0,0 +1,347 @@
|
|||||||
|
import { Selections, executeAst, executeCode } from 'useStore'
|
||||||
|
import { KCLError } from './errors'
|
||||||
|
import {
|
||||||
|
EngineCommandManager,
|
||||||
|
engineCommandManager,
|
||||||
|
} from './std/engineConnection'
|
||||||
|
import { deferExecution } from 'lib/utils'
|
||||||
|
import {
|
||||||
|
initPromise,
|
||||||
|
parse,
|
||||||
|
PathToNode,
|
||||||
|
Program,
|
||||||
|
ProgramMemory,
|
||||||
|
recast,
|
||||||
|
} from 'lang/wasm'
|
||||||
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
|
import { getNodeFromPath } from './queryAst'
|
||||||
|
|
||||||
|
const PERSIST_CODE_TOKEN = 'persistCode'
|
||||||
|
|
||||||
|
class KclManager {
|
||||||
|
private _code = bracket
|
||||||
|
private _ast: Program = {
|
||||||
|
body: [],
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
nonCodeMeta: {
|
||||||
|
nonCodeNodes: {},
|
||||||
|
start: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
private _programMemory: ProgramMemory = {
|
||||||
|
root: {},
|
||||||
|
return: null,
|
||||||
|
}
|
||||||
|
private _logs: string[] = []
|
||||||
|
private _kclErrors: KCLError[] = []
|
||||||
|
private _isExecuting = false
|
||||||
|
|
||||||
|
engineCommandManager: EngineCommandManager
|
||||||
|
private _defferer = deferExecution((code: string) => {
|
||||||
|
const ast = parse(code)
|
||||||
|
this.executeAst(ast)
|
||||||
|
}, 600)
|
||||||
|
|
||||||
|
private _isExecutingCallback: (a: boolean) => void = () => {}
|
||||||
|
private _codeCallBack: (arg: string) => void = () => {}
|
||||||
|
private _astCallBack: (arg: Program) => void = () => {}
|
||||||
|
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||||
|
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||||
|
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
|
||||||
|
|
||||||
|
get ast() {
|
||||||
|
return this._ast
|
||||||
|
}
|
||||||
|
set ast(ast) {
|
||||||
|
this._ast = ast
|
||||||
|
this._astCallBack(ast)
|
||||||
|
}
|
||||||
|
|
||||||
|
get code() {
|
||||||
|
return this._code
|
||||||
|
}
|
||||||
|
set code(code) {
|
||||||
|
this._code = code
|
||||||
|
this._codeCallBack(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
get programMemory() {
|
||||||
|
return this._programMemory
|
||||||
|
}
|
||||||
|
set programMemory(programMemory) {
|
||||||
|
this._programMemory = programMemory
|
||||||
|
this._programMemoryCallBack(programMemory)
|
||||||
|
}
|
||||||
|
|
||||||
|
get defaultPlanes() {
|
||||||
|
return this?.engineCommandManager?.defaultPlanes
|
||||||
|
}
|
||||||
|
|
||||||
|
get logs() {
|
||||||
|
return this._logs
|
||||||
|
}
|
||||||
|
set logs(logs) {
|
||||||
|
this._logs = logs
|
||||||
|
this._logsCallBack(logs)
|
||||||
|
}
|
||||||
|
|
||||||
|
get kclErrors() {
|
||||||
|
return this._kclErrors
|
||||||
|
}
|
||||||
|
set kclErrors(kclErrors) {
|
||||||
|
this._kclErrors = kclErrors
|
||||||
|
this._kclErrorsCallBack(kclErrors)
|
||||||
|
}
|
||||||
|
|
||||||
|
get isExecuting() {
|
||||||
|
return this._isExecuting
|
||||||
|
}
|
||||||
|
set isExecuting(isExecuting) {
|
||||||
|
this._isExecuting = isExecuting
|
||||||
|
this._isExecutingCallback(isExecuting)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(engineCommandManager: EngineCommandManager) {
|
||||||
|
this.engineCommandManager = engineCommandManager
|
||||||
|
const storedCode = localStorage.getItem(PERSIST_CODE_TOKEN)
|
||||||
|
// TODO #819 remove zustand persistance logic in a few months
|
||||||
|
// short term migration, shouldn't make a difference for tauri app users
|
||||||
|
// anyway since that's filesystem based.
|
||||||
|
const zustandStore = JSON.parse(localStorage.getItem('store') || '{}')
|
||||||
|
if (storedCode === null && zustandStore?.state?.code) {
|
||||||
|
this.code = zustandStore.state.code
|
||||||
|
localStorage.setItem(PERSIST_CODE_TOKEN, this._code)
|
||||||
|
zustandStore.state.code = ''
|
||||||
|
localStorage.setItem('store', JSON.stringify(zustandStore))
|
||||||
|
} else if (storedCode === null) {
|
||||||
|
this.code = bracket
|
||||||
|
} else {
|
||||||
|
this.code = storedCode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerCallBacks({
|
||||||
|
setCode,
|
||||||
|
setProgramMemory,
|
||||||
|
setAst,
|
||||||
|
setLogs,
|
||||||
|
setKclErrors,
|
||||||
|
setIsExecuting,
|
||||||
|
}: {
|
||||||
|
setCode: (arg: string) => void
|
||||||
|
setProgramMemory: (arg: ProgramMemory) => void
|
||||||
|
setAst: (arg: Program) => void
|
||||||
|
setLogs: (arg: string[]) => void
|
||||||
|
setKclErrors: (arg: KCLError[]) => void
|
||||||
|
setIsExecuting: (arg: boolean) => void
|
||||||
|
}) {
|
||||||
|
this._codeCallBack = setCode
|
||||||
|
this._programMemoryCallBack = setProgramMemory
|
||||||
|
this._astCallBack = setAst
|
||||||
|
this._logsCallBack = setLogs
|
||||||
|
this._kclErrorsCallBack = setKclErrors
|
||||||
|
this._isExecutingCallback = setIsExecuting
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeAst(ast: Program = this._ast, updateCode = false) {
|
||||||
|
this.isExecuting = true
|
||||||
|
await initPromise
|
||||||
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
|
ast,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
defaultPlanes: this.defaultPlanes,
|
||||||
|
})
|
||||||
|
this.isExecuting = false
|
||||||
|
this._logs = logs
|
||||||
|
this._kclErrors = errors
|
||||||
|
this._programMemory = programMemory
|
||||||
|
this._ast = { ...ast }
|
||||||
|
if (updateCode) {
|
||||||
|
this._code = recast(ast)
|
||||||
|
this._codeCallBack(this._code)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async executeAstMock(ast: Program = this._ast, updateCode = false) {
|
||||||
|
await initPromise
|
||||||
|
const newCode = recast(ast)
|
||||||
|
const newAst = parse(newCode)
|
||||||
|
await this?.engineCommandManager?.waitForReady
|
||||||
|
if (updateCode) {
|
||||||
|
this.setCode(recast(ast))
|
||||||
|
}
|
||||||
|
this._ast = { ...newAst }
|
||||||
|
|
||||||
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
|
ast: newAst,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
defaultPlanes: this.defaultPlanes,
|
||||||
|
useFakeExecutor: true,
|
||||||
|
})
|
||||||
|
this._logs = logs
|
||||||
|
this._kclErrors = errors
|
||||||
|
this._programMemory = programMemory
|
||||||
|
}
|
||||||
|
async executeCode(code?: string) {
|
||||||
|
await initPromise
|
||||||
|
await this?.engineCommandManager?.waitForReady
|
||||||
|
const result = await executeCode({
|
||||||
|
engineCommandManager,
|
||||||
|
code: code || this._code,
|
||||||
|
lastAst: this._ast,
|
||||||
|
defaultPlanes: this.defaultPlanes,
|
||||||
|
force: false,
|
||||||
|
})
|
||||||
|
if (!result.isChange) return
|
||||||
|
const { logs, errors, programMemory, ast } = result
|
||||||
|
this.logs = logs
|
||||||
|
this.kclErrors = errors
|
||||||
|
this.programMemory = programMemory
|
||||||
|
this.ast = ast
|
||||||
|
if (code) this.code = code
|
||||||
|
}
|
||||||
|
setCode(code: string) {
|
||||||
|
this._code = code
|
||||||
|
this._codeCallBack(code)
|
||||||
|
localStorage.setItem(PERSIST_CODE_TOKEN, code)
|
||||||
|
}
|
||||||
|
setCodeAndExecute(code: string) {
|
||||||
|
this.setCode(code)
|
||||||
|
if (code.trim()) {
|
||||||
|
this._defferer(code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this._ast = {
|
||||||
|
body: [],
|
||||||
|
start: 0,
|
||||||
|
end: 0,
|
||||||
|
nonCodeMeta: {
|
||||||
|
nonCodeNodes: {},
|
||||||
|
start: null,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this._programMemory = {
|
||||||
|
root: {},
|
||||||
|
return: null,
|
||||||
|
}
|
||||||
|
this.engineCommandManager.endSession()
|
||||||
|
}
|
||||||
|
format() {
|
||||||
|
this.code = recast(parse(kclManager.code))
|
||||||
|
}
|
||||||
|
// There's overlapping resposibility between updateAst and executeAst.
|
||||||
|
// updateAst was added as it was used a lot before xState migration so makes the port easier.
|
||||||
|
// but should probably have think about which of the function to keep
|
||||||
|
async updateAst(
|
||||||
|
ast: Program,
|
||||||
|
execute: boolean,
|
||||||
|
optionalParams?: {
|
||||||
|
focusPath?: PathToNode
|
||||||
|
callBack?: (ast: Program) => void
|
||||||
|
}
|
||||||
|
): Promise<Selections | null> {
|
||||||
|
const newCode = recast(ast)
|
||||||
|
const astWithUpdatedSource = parse(newCode)
|
||||||
|
optionalParams?.callBack?.(astWithUpdatedSource)
|
||||||
|
let returnVal: Selections | null = null
|
||||||
|
|
||||||
|
this.code = newCode
|
||||||
|
if (optionalParams?.focusPath) {
|
||||||
|
const { node } = getNodeFromPath<any>(
|
||||||
|
astWithUpdatedSource,
|
||||||
|
optionalParams?.focusPath
|
||||||
|
)
|
||||||
|
const { start, end } = node
|
||||||
|
if (!start || !end) return null
|
||||||
|
returnVal = {
|
||||||
|
codeBasedSelections: [
|
||||||
|
{
|
||||||
|
type: 'default',
|
||||||
|
range: [start, end],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (execute) {
|
||||||
|
// Call execute on the set ast.
|
||||||
|
await this.executeAst(astWithUpdatedSource)
|
||||||
|
} else {
|
||||||
|
// When we don't re-execute, we still want to update the program
|
||||||
|
// memory with the new ast. So we will hit the mock executor
|
||||||
|
// instead.
|
||||||
|
await this.executeAstMock(astWithUpdatedSource)
|
||||||
|
}
|
||||||
|
return returnVal
|
||||||
|
}
|
||||||
|
|
||||||
|
getPlaneId(axis: 'xy' | 'xz' | 'yz'): string {
|
||||||
|
return this.defaultPlanes[axis]
|
||||||
|
}
|
||||||
|
|
||||||
|
showPlanes() {
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, false)
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, false)
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
hidePlanes() {
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xy, true)
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.yz, true)
|
||||||
|
this.engineCommandManager.setPlaneHidden(this.defaultPlanes.xz, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const kclManager = new KclManager(engineCommandManager)
|
||||||
|
|
||||||
|
const KclContext = createContext({
|
||||||
|
code: kclManager.code,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
ast: kclManager.ast,
|
||||||
|
isExecuting: kclManager.isExecuting,
|
||||||
|
errors: kclManager.kclErrors,
|
||||||
|
logs: kclManager.logs,
|
||||||
|
})
|
||||||
|
|
||||||
|
export function useKclContext() {
|
||||||
|
return useContext(KclContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function KclContextProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [code, setCode] = useState(kclManager.code)
|
||||||
|
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||||
|
const [ast, setAst] = useState(kclManager.ast)
|
||||||
|
const [isExecuting, setIsExecuting] = useState(false)
|
||||||
|
const [errors, setErrors] = useState<KCLError[]>([])
|
||||||
|
const [logs, setLogs] = useState<string[]>([])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
kclManager.registerCallBacks({
|
||||||
|
setCode,
|
||||||
|
setProgramMemory,
|
||||||
|
setAst,
|
||||||
|
setLogs,
|
||||||
|
setKclErrors: setErrors,
|
||||||
|
setIsExecuting,
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
return (
|
||||||
|
<KclContext.Provider
|
||||||
|
value={{
|
||||||
|
code,
|
||||||
|
programMemory,
|
||||||
|
ast,
|
||||||
|
isExecuting,
|
||||||
|
errors,
|
||||||
|
logs,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</KclContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1520,15 +1520,15 @@ const key = 'c'`
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
const { nonCodeMeta } = parse(code)
|
const { nonCodeMeta } = parse(code)
|
||||||
expect(nonCodeMeta.nonCodeNodes[0]).toEqual(nonCodeMetaInstance)
|
expect(nonCodeMeta.nonCodeNodes[0][0]).toEqual(nonCodeMetaInstance)
|
||||||
|
|
||||||
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
// extra whitespace won't change it's position (0) or value (NB the start end would have changed though)
|
||||||
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
const codeWithExtraStartWhitespace = '\n\n\n' + code
|
||||||
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
|
const { nonCodeMeta: nonCodeMeta2 } = parse(codeWithExtraStartWhitespace)
|
||||||
expect(nonCodeMeta2.nonCodeNodes[0].value).toStrictEqual(
|
expect(nonCodeMeta2.nonCodeNodes[0][0].value).toStrictEqual(
|
||||||
nonCodeMetaInstance.value
|
nonCodeMetaInstance.value
|
||||||
)
|
)
|
||||||
expect(nonCodeMeta2.nonCodeNodes[0].start).not.toBe(
|
expect(nonCodeMeta2.nonCodeNodes[0][0].start).not.toBe(
|
||||||
nonCodeMetaInstance.start
|
nonCodeMetaInstance.start
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1545,7 +1545,7 @@ const key = 'c'`
|
|||||||
const { body } = parse(code)
|
const { body } = parse(code)
|
||||||
const indexOfSecondLineToExpression = 2
|
const indexOfSecondLineToExpression = 2
|
||||||
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body as any)[0].declarations[0].init.nonCodeMeta
|
||||||
.nonCodeNodes
|
.nonCodeNodes[0]
|
||||||
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
expect(sketchNonCodeMeta[indexOfSecondLineToExpression]).toEqual({
|
||||||
type: 'NonCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 106,
|
start: 106,
|
||||||
@ -1568,7 +1568,7 @@ const key = 'c'`
|
|||||||
|
|
||||||
const { body } = parse(code)
|
const { body } = parse(code)
|
||||||
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
const sketchNonCodeMeta = (body[0] as any).declarations[0].init.nonCodeMeta
|
||||||
.nonCodeNodes
|
.nonCodeNodes[0]
|
||||||
expect(sketchNonCodeMeta[3]).toEqual({
|
expect(sketchNonCodeMeta[3]).toEqual({
|
||||||
type: 'NonCodeNode',
|
type: 'NonCodeNode',
|
||||||
start: 125,
|
start: 125,
|
||||||
|
|||||||
@ -7,7 +7,8 @@ describe('testing artifacts', () => {
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
test('sketch artifacts', async () => {
|
test('sketch artifacts', async () => {
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
@ -27,7 +28,7 @@ show(mySketch001)`
|
|||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [21, 42],
|
sourceRange: [46, 71],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
@ -37,7 +38,7 @@ show(mySketch001)`
|
|||||||
to: [-1.59, -1.54],
|
to: [-1.59, -1.54],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [48, 73],
|
sourceRange: [77, 102],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -47,7 +48,7 @@ show(mySketch001)`
|
|||||||
from: [-1.59, -1.54],
|
from: [-1.59, -1.54],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [79, 103],
|
sourceRange: [108, 132],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -55,14 +56,16 @@ show(mySketch001)`
|
|||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: [{ sourceRange: [21, 42] }],
|
planeId: expect.any(String),
|
||||||
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
test('extrude artifacts', async () => {
|
test('extrude artifacts', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
@ -82,7 +85,7 @@ show(mySketch001)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [21, 42] }],
|
__meta: [{ sourceRange: [46, 71] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
@ -90,7 +93,8 @@ show(mySketch001)`
|
|||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
|
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
|
||||||
const code = `
|
const code = `
|
||||||
const sk1 = startSketchAt([0, 0])
|
const sk1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.5, 0], %)
|
|> lineTo([-2.5, 0], %)
|
||||||
|> lineTo({ to: [0, 10], tag: "p" }, %)
|
|> lineTo({ to: [0, 10], tag: "p" }, %)
|
||||||
|> lineTo([2.5, 0], %)
|
|> lineTo([2.5, 0], %)
|
||||||
@ -99,7 +103,8 @@ const sk1 = startSketchAt([0, 0])
|
|||||||
// |> ry(5, %)
|
// |> ry(5, %)
|
||||||
const theExtrude = extrude(2, sk1)
|
const theExtrude = extrude(2, sk1)
|
||||||
// const theTransf = getExtrudeWallTransform('p', theExtrude)
|
// const theTransf = getExtrudeWallTransform('p', theExtrude)
|
||||||
const sk2 = startSketchAt([0, 0])
|
const sk2 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([-2.5, 0], %)
|
|> lineTo([-2.5, 0], %)
|
||||||
|> lineTo({ to: [0, 3], tag: "p" }, %)
|
|> lineTo({ to: [0, 3], tag: "p" }, %)
|
||||||
|> lineTo([2.5, 0], %)
|
|> lineTo([2.5, 0], %)
|
||||||
@ -122,7 +127,7 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [13, 34] }],
|
__meta: [{ sourceRange: [38, 63] }],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'ExtrudeGroup',
|
type: 'ExtrudeGroup',
|
||||||
@ -131,7 +136,7 @@ show(theExtrude, sk2)`
|
|||||||
height: 2,
|
height: 2,
|
||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
__meta: [{ sourceRange: [302, 323] }],
|
__meta: [{ sourceRange: [356, 381] }],
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|||||||
@ -72,7 +72,7 @@ export class KCLUndefinedValueError extends KCLError {
|
|||||||
* Currently the diagnostics are all errors, but in the future they could include lints.
|
* Currently the diagnostics are all errors, but in the future they could include lints.
|
||||||
* */
|
* */
|
||||||
export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] {
|
export function kclErrToDiagnostic(errors: KCLError[]): Diagnostic[] {
|
||||||
return errors.flatMap((err) => {
|
return errors?.flatMap((err) => {
|
||||||
return err.sourceRanges.map(([from, to]) => {
|
return err.sourceRanges.map(([from, to]) => {
|
||||||
return { from, to, message: err.msg, severity: 'error' }
|
return { from, to, message: err.msg, severity: 'error' }
|
||||||
})
|
})
|
||||||
|
|||||||
@ -41,7 +41,8 @@ const newVar = myVar + 1`
|
|||||||
expect(root.magicNum.value).toBe(69)
|
expect(root.magicNum.value).toBe(69)
|
||||||
})
|
})
|
||||||
it('sketch declaration', async () => {
|
it('sketch declaration', async () => {
|
||||||
let code = `const mySketch = startSketchAt([0,0])
|
let code = `const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({to: [0,2], tag: "myPath"}, %)
|
|> lineTo({to: [0,2], tag: "myPath"}, %)
|
||||||
|> lineTo([2,3], %)
|
|> lineTo([2,3], %)
|
||||||
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|
||||||
@ -57,7 +58,7 @@ show(mySketch)
|
|||||||
to: [0, 2],
|
to: [0, 2],
|
||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [43, 80],
|
sourceRange: [72, 109],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
@ -68,7 +69,7 @@ show(mySketch)
|
|||||||
from: [0, 2],
|
from: [0, 2],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [86, 102],
|
sourceRange: [115, 131],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -77,7 +78,7 @@ show(mySketch)
|
|||||||
to: [5, -1],
|
to: [5, -1],
|
||||||
from: [2, 3],
|
from: [2, 3],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [108, 151],
|
sourceRange: [137, 180],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'rightPath',
|
name: 'rightPath',
|
||||||
@ -87,8 +88,8 @@ show(mySketch)
|
|||||||
expect(_return).toEqual([
|
expect(_return).toEqual([
|
||||||
{
|
{
|
||||||
type: 'Identifier',
|
type: 'Identifier',
|
||||||
start: 174,
|
start: 203,
|
||||||
end: 182,
|
end: 211,
|
||||||
name: 'mySketch',
|
name: 'mySketch',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
@ -132,7 +133,8 @@ show(mySketch)
|
|||||||
it('execute pipe sketch into call expression', async () => {
|
it('execute pipe sketch into call expression', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = [
|
const code = [
|
||||||
'const mySk1 = startSketchAt([0,0])',
|
"const mySk1 = startSketchOn('XY')",
|
||||||
|
' |> startProfileAt([0,0], %)',
|
||||||
' |> lineTo([1,1], %)',
|
' |> lineTo([1,1], %)',
|
||||||
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
|
||||||
' |> lineTo([1,1], %)',
|
' |> lineTo([1,1], %)',
|
||||||
@ -147,7 +149,7 @@ show(mySketch)
|
|||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
sourceRange: [14, 34],
|
sourceRange: [39, 63],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
value: [
|
value: [
|
||||||
@ -157,7 +159,7 @@ show(mySketch)
|
|||||||
from: [0, 0],
|
from: [0, 0],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [40, 56],
|
sourceRange: [69, 85],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -166,7 +168,7 @@ show(mySketch)
|
|||||||
to: [0, 1],
|
to: [0, 1],
|
||||||
from: [1, 1],
|
from: [1, 1],
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [62, 100],
|
sourceRange: [91, 129],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
name: 'myPath',
|
name: 'myPath',
|
||||||
@ -177,7 +179,7 @@ show(mySketch)
|
|||||||
from: [0, 1],
|
from: [0, 1],
|
||||||
name: '',
|
name: '',
|
||||||
__geoMeta: {
|
__geoMeta: {
|
||||||
sourceRange: [106, 122],
|
sourceRange: [135, 151],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -185,7 +187,8 @@ show(mySketch)
|
|||||||
position: [0, 0, 0],
|
position: [0, 0, 0],
|
||||||
rotation: [0, 0, 0, 1],
|
rotation: [0, 0, 0, 1],
|
||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: [{ sourceRange: [14, 34] }],
|
planeId: expect.any(String),
|
||||||
|
__meta: [{ sourceRange: [39, 63] }],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('execute array expression', async () => {
|
it('execute array expression', async () => {
|
||||||
@ -329,7 +332,8 @@ describe('testing math operators', () => {
|
|||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
'const part001 = startSketchAt([0, 0])',
|
"const part001 = startSketchOn('XY')",
|
||||||
|
' |> startProfileAt([0, 0], %)',
|
||||||
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
|
||||||
].join('\n')
|
].join('\n')
|
||||||
const { root } = await exe(code)
|
const { root } = await exe(code)
|
||||||
@ -341,7 +345,8 @@ describe('testing math operators', () => {
|
|||||||
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
`const myVar = 3`,
|
`const myVar = 3`,
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
|
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
|
||||||
` |> line([`,
|
` |> line([`,
|
||||||
` min(segLen('seg01', %), myVar),`,
|
` min(segLen('seg01', %), myVar),`,
|
||||||
@ -377,7 +382,8 @@ describe('testing math operators', () => {
|
|||||||
describe('Testing Errors', () => {
|
describe('Testing Errors', () => {
|
||||||
it('should throw an error when a variable is not defined', async () => {
|
it('should throw an error when a variable is not defined', async () => {
|
||||||
const code = `const myVar = 5
|
const code = `const myVar = 5
|
||||||
const theExtrude = startSketchAt([0, 0])
|
const theExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.4, 5], %)
|
|> line([-2.4, 5], %)
|
||||||
|> line([-0.76], myVarZ, %)
|
|> line([-0.76], myVarZ, %)
|
||||||
|> line([5,5], %)
|
|> line([5,5], %)
|
||||||
@ -388,7 +394,7 @@ show(theExtrude)`
|
|||||||
new KCLError(
|
new KCLError(
|
||||||
'undefined_value',
|
'undefined_value',
|
||||||
'memory item key `myVarZ` is not defined',
|
'memory item key `myVarZ` is not defined',
|
||||||
[[100, 106]]
|
[[129, 135]]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@ -109,8 +109,8 @@ describe('Testing addSketchTo', () => {
|
|||||||
'yz'
|
'yz'
|
||||||
)
|
)
|
||||||
const str = recast(result.modifiedAst)
|
const str = recast(result.modifiedAst)
|
||||||
expect(str).toBe(`const part001 = startSketchAt('default')
|
expect(str).toBe(`const part001 = startSketchOn('YZ')
|
||||||
|> ry(90, %)
|
|> startProfileAt('default', %)
|
||||||
|> line('default', %)
|
|> line('default', %)
|
||||||
show(part001)
|
show(part001)
|
||||||
`)
|
`)
|
||||||
@ -133,7 +133,8 @@ function giveSketchFnCallTagTestHelper(
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe('Testing giveSketchFnCallTag', () => {
|
describe('Testing giveSketchFnCallTag', () => {
|
||||||
const code = `const part001 = startSketchAt([0, 0])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.57, -0.13], %)
|
|> line([-2.57, -0.13], %)
|
||||||
|> line([0, 0.83], %)
|
|> line([0, 0.83], %)
|
||||||
|> line([0.82, 0.34], %)
|
|> line([0.82, 0.34], %)
|
||||||
@ -185,7 +186,8 @@ fn ghi = (x) => {
|
|||||||
const abc = 3
|
const abc = 3
|
||||||
const identifierGuy = 5
|
const identifierGuy = 5
|
||||||
const yo = 5 + 6
|
const yo = 5 + 6
|
||||||
const part001 = startSketchAt([-1.2, 4.83])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|> line([2.8, 0], %)
|
|> line([2.8, 0], %)
|
||||||
|> angledLine([100 + 100, 3.09], %)
|
|> angledLine([100 + 100, 3.09], %)
|
||||||
|> angledLine([abc, 3.09], %)
|
|> angledLine([abc, 3.09], %)
|
||||||
|
|||||||
@ -32,21 +32,26 @@ import { isLiteralArrayOrStatic } from './std/sketchcombos'
|
|||||||
|
|
||||||
export function addStartSketch(
|
export function addStartSketch(
|
||||||
node: Program,
|
node: Program,
|
||||||
|
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
|
||||||
start: [number, number],
|
start: [number, number],
|
||||||
end: [number, number]
|
end: [number, number]
|
||||||
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const _name = findUniqueName(node, 'part')
|
const _name = findUniqueName(node, 'part')
|
||||||
|
|
||||||
const startSketchAt = createCallExpression('startSketchAt', [
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
||||||
|
createLiteral(axis.toUpperCase()),
|
||||||
|
])
|
||||||
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||||
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
createArrayExpression([createLiteral(start[0]), createLiteral(start[1])]),
|
||||||
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
const initialLineTo = createCallExpression('line', [
|
const initialLineTo = createCallExpression('line', [
|
||||||
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
const pipeBody = [startSketchAt, initialLineTo]
|
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
||||||
|
|
||||||
const variableDeclaration = createVariableDeclaration(
|
const variableDeclaration = createVariableDeclaration(
|
||||||
_name,
|
_name,
|
||||||
@ -79,11 +84,11 @@ export function addSketchTo(
|
|||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const _name = name || findUniqueName(node, 'part')
|
const _name = name || findUniqueName(node, 'part')
|
||||||
|
|
||||||
const startSketchAt = createCallExpressionStdLib('startSketchAt', [
|
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
|
||||||
createLiteral('default'),
|
createLiteral(axis.toUpperCase()),
|
||||||
])
|
])
|
||||||
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
|
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
|
||||||
createLiteral(90),
|
createLiteral('default'),
|
||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
const initialLineTo = createCallExpressionStdLib('line', [
|
const initialLineTo = createCallExpressionStdLib('line', [
|
||||||
@ -91,10 +96,7 @@ export function addSketchTo(
|
|||||||
createPipeSubstitution(),
|
createPipeSubstitution(),
|
||||||
])
|
])
|
||||||
|
|
||||||
const pipeBody =
|
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
|
||||||
axis !== 'xy'
|
|
||||||
? [startSketchAt, rotate, initialLineTo]
|
|
||||||
: [startSketchAt, initialLineTo]
|
|
||||||
|
|
||||||
const variableDeclaration = createVariableDeclaration(
|
const variableDeclaration = createVariableDeclaration(
|
||||||
_name,
|
_name,
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import {
|
|||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
isTypeInValue,
|
isTypeInValue,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
|
doesPipeHaveCallExp,
|
||||||
|
hasExtrudeSketchGroup,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import {
|
import {
|
||||||
@ -26,7 +28,8 @@ const halfArmAngle = armAngle / 2
|
|||||||
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
||||||
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
||||||
|
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> yLineTo(1, %)
|
|> yLineTo(1, %)
|
||||||
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
||||||
|
|
||||||
@ -57,7 +60,8 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing argIsNotIdentifier', () => {
|
describe('testing argIsNotIdentifier', () => {
|
||||||
const code = `const part001 = startSketchAt([-1.2, 4.83])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|> line([2.8, 0], %)
|
|> line([2.8, 0], %)
|
||||||
|> angledLine([100 + 100, 3.09], %)
|
|> angledLine([100 + 100, 3.09], %)
|
||||||
|> angledLine([abc, 3.09], %)
|
|> angledLine([abc, 3.09], %)
|
||||||
@ -194,7 +198,8 @@ show(part001)`
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing getNodePathFromSourceRange', () => {
|
describe('testing getNodePathFromSourceRange', () => {
|
||||||
const code = `const part001 = startSketchAt([0.39, -0.05])
|
const code = `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.39, -0.05], %)
|
||||||
|> line([0.94, 2.61], %)
|
|> line([0.94, 2.61], %)
|
||||||
|> line([-0.21, -1.4], %)
|
|> line([-0.21, -1.4], %)
|
||||||
show(part001)`
|
show(part001)`
|
||||||
@ -210,7 +215,7 @@ show(part001)`
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
['init', ''],
|
['init', ''],
|
||||||
['body', 'PipeExpression'],
|
['body', 'PipeExpression'],
|
||||||
[1, 'index'],
|
[2, 'index'],
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
it('finds the last line when cursor is put at the end', () => {
|
it('finds the last line when cursor is put at the end', () => {
|
||||||
@ -225,7 +230,7 @@ show(part001)`
|
|||||||
[0, 'index'],
|
[0, 'index'],
|
||||||
['init', ''],
|
['init', ''],
|
||||||
['body', 'PipeExpression'],
|
['body', 'PipeExpression'],
|
||||||
[2, 'index'],
|
[3, 'index'],
|
||||||
]
|
]
|
||||||
expect(result).toEqual(expected)
|
expect(result).toEqual(expected)
|
||||||
// expect similar result for start of line
|
// expect similar result for start of line
|
||||||
@ -243,3 +248,114 @@ show(part001)`
|
|||||||
expect(selectWholeThing).toEqual(expected)
|
expect(selectWholeThing).toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('testing doesPipeHave', () => {
|
||||||
|
it('finds close', () => {
|
||||||
|
const exampleCode = `const length001 = 2
|
||||||
|
const part001 = startSketchAt([-1.41, 3.46])
|
||||||
|
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|
||||||
|
|> angledLine([-35, length001], %)
|
||||||
|
|> line([-3.22, -7.36], %)
|
||||||
|
|> angledLine([-175, segLen('seg01', %)], %)
|
||||||
|
|> close(%)
|
||||||
|
`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const result = doesPipeHaveCallExp({
|
||||||
|
calleeName: 'close',
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [100, 101] },
|
||||||
|
})
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
})
|
||||||
|
it('finds extrude', () => {
|
||||||
|
const exampleCode = `const length001 = 2
|
||||||
|
const part001 = startSketchAt([-1.41, 3.46])
|
||||||
|
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|
||||||
|
|> angledLine([-35, length001], %)
|
||||||
|
|> line([-3.22, -7.36], %)
|
||||||
|
|> angledLine([-175, segLen('seg01', %)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(1, %)
|
||||||
|
`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const result = doesPipeHaveCallExp({
|
||||||
|
calleeName: 'extrude',
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [100, 101] },
|
||||||
|
})
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
})
|
||||||
|
it('does NOT find close', () => {
|
||||||
|
const exampleCode = `const length001 = 2
|
||||||
|
const part001 = startSketchAt([-1.41, 3.46])
|
||||||
|
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|
||||||
|
|> angledLine([-35, length001], %)
|
||||||
|
|> line([-3.22, -7.36], %)
|
||||||
|
|> angledLine([-175, segLen('seg01', %)], %)
|
||||||
|
`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const result = doesPipeHaveCallExp({
|
||||||
|
calleeName: 'close',
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [100, 101] },
|
||||||
|
})
|
||||||
|
expect(result).toEqual(false)
|
||||||
|
})
|
||||||
|
it('returns false if not a pipe', () => {
|
||||||
|
const exampleCode = `const length001 = 2`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const result = doesPipeHaveCallExp({
|
||||||
|
calleeName: 'close',
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [9, 10] },
|
||||||
|
})
|
||||||
|
expect(result).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('testing hasExtrudeSketchGroup', () => {
|
||||||
|
it('find sketch group', async () => {
|
||||||
|
const exampleCode = `const length001 = 2
|
||||||
|
const part001 = startSketchAt([-1.41, 3.46])
|
||||||
|
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|
||||||
|
|> angledLine([-35, length001], %)
|
||||||
|
|> line([-3.22, -7.36], %)
|
||||||
|
|> angledLine([-175, segLen('seg01', %)], %)`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const result = hasExtrudeSketchGroup({
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [100, 101] },
|
||||||
|
programMemory,
|
||||||
|
})
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
})
|
||||||
|
it('find extrude group', async () => {
|
||||||
|
const exampleCode = `const length001 = 2
|
||||||
|
const part001 = startSketchAt([-1.41, 3.46])
|
||||||
|
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|
||||||
|
|> angledLine([-35, length001], %)
|
||||||
|
|> line([-3.22, -7.36], %)
|
||||||
|
|> angledLine([-175, segLen('seg01', %)], %)
|
||||||
|
|> extrude(1, %)`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const result = hasExtrudeSketchGroup({
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [100, 101] },
|
||||||
|
programMemory,
|
||||||
|
})
|
||||||
|
expect(result).toEqual(true)
|
||||||
|
})
|
||||||
|
it('finds nothing', async () => {
|
||||||
|
const exampleCode = `const length001 = 2`
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
const programMemory = await enginelessExecutor(ast)
|
||||||
|
const result = hasExtrudeSketchGroup({
|
||||||
|
ast,
|
||||||
|
selection: { type: 'default', range: [10, 11] },
|
||||||
|
programMemory,
|
||||||
|
})
|
||||||
|
expect(result).toEqual(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import {
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
SketchGroup,
|
SketchGroup,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
|
PipeExpression,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
|
||||||
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
import { getSketchSegmentFromSourceRange } from './std/sketchConstraints'
|
||||||
@ -511,3 +512,47 @@ export function isLinesParallelAndConstrained(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function doesPipeHaveCallExp({
|
||||||
|
ast,
|
||||||
|
selection,
|
||||||
|
calleeName,
|
||||||
|
}: {
|
||||||
|
calleeName: string
|
||||||
|
ast: Program
|
||||||
|
selection: Selection
|
||||||
|
}): boolean {
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||||
|
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
).node
|
||||||
|
if (pipeExpression.type !== 'PipeExpression') return false
|
||||||
|
return pipeExpression.body.some(
|
||||||
|
(expression) =>
|
||||||
|
expression.type === 'CallExpression' &&
|
||||||
|
expression.callee.name === calleeName
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasExtrudeSketchGroup({
|
||||||
|
ast,
|
||||||
|
selection,
|
||||||
|
programMemory,
|
||||||
|
}: {
|
||||||
|
ast: Program
|
||||||
|
selection: Selection
|
||||||
|
programMemory: ProgramMemory
|
||||||
|
}): boolean {
|
||||||
|
const pathToNode = getNodePathFromSourceRange(ast, selection.range)
|
||||||
|
const varDec = getNodeFromPath<VariableDeclaration>(
|
||||||
|
ast,
|
||||||
|
pathToNode,
|
||||||
|
'VariableDeclaration'
|
||||||
|
).node
|
||||||
|
if (varDec.type !== 'VariableDeclaration') return false
|
||||||
|
const varName = varDec.declarations[0].id.name
|
||||||
|
const varValue = programMemory?.root[varName]
|
||||||
|
return varValue?.type === 'ExtrudeGroup' || varValue?.type === 'SketchGroup'
|
||||||
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { exportSave } from 'lib/exportSave'
|
|||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
import * as Sentry from '@sentry/react'
|
import * as Sentry from '@sentry/react'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
|
|
||||||
let lastMessage = ''
|
let lastMessage = ''
|
||||||
|
|
||||||
@ -286,14 +287,18 @@ export class EngineConnection {
|
|||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
const message: Models['WebSocketResponse_type'] = JSON.parse(event.data)
|
||||||
|
|
||||||
if (!message.success) {
|
if (!message.success) {
|
||||||
|
const errorsString = message?.errors
|
||||||
|
?.map((error) => {
|
||||||
|
return ` - ${error.error_code}: ${error.message}`
|
||||||
|
})
|
||||||
|
.join('\n')
|
||||||
if (message.request_id) {
|
if (message.request_id) {
|
||||||
console.error(`Error in response to request ${message.request_id}:`)
|
console.error(
|
||||||
|
`Error in response to request ${message.request_id}:\n${errorsString}`
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
console.error(`Error from server:`)
|
console.error(`Error from server:\n${errorsString}`)
|
||||||
}
|
}
|
||||||
message?.errors?.forEach((error) => {
|
|
||||||
console.error(` - ${error.error_code}: ${error.message}`)
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -593,11 +598,14 @@ export class EngineCommandManager {
|
|||||||
outSequence = 1
|
outSequence = 1
|
||||||
inSequence = 1
|
inSequence = 1
|
||||||
engineConnection?: EngineConnection
|
engineConnection?: EngineConnection
|
||||||
|
defaultPlanes: DefaultPlanes = { xy: '', yz: '', xz: '' }
|
||||||
// Folks should realize that wait for ready does not get called _everytime_
|
// Folks should realize that wait for ready does not get called _everytime_
|
||||||
// the connection resets and restarts, it only gets called the first time.
|
// the connection resets and restarts, it only gets called the first time.
|
||||||
// Be careful what you put here.
|
// Be careful what you put here.
|
||||||
waitForReady: Promise<void> = new Promise(() => {})
|
|
||||||
private resolveReady = () => {}
|
private resolveReady = () => {}
|
||||||
|
waitForReady: Promise<void> = new Promise((resolve) => {
|
||||||
|
this.resolveReady = resolve
|
||||||
|
})
|
||||||
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
[event: string]: {
|
[event: string]: {
|
||||||
@ -639,9 +647,6 @@ export class EngineCommandManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.waitForReady = new Promise((resolve) => {
|
|
||||||
this.resolveReady = resolve
|
|
||||||
})
|
|
||||||
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
|
const url = `${VITE_KC_API_WS_MODELING_URL}?video_res_width=${width}&video_res_height=${height}`
|
||||||
this.engineConnection = new EngineConnection({
|
this.engineConnection = new EngineConnection({
|
||||||
url,
|
url,
|
||||||
@ -669,12 +674,15 @@ export class EngineCommandManager {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// We execute the code here to make sure if the stream was to
|
// Inisialize the planes.
|
||||||
// restart in a session, we want to make sure to execute the code.
|
this.initPlanes().then(() => {
|
||||||
// We force it to re-execute the code because we want to make sure
|
// We execute the code here to make sure if the stream was to
|
||||||
// the code is executed everytime the stream is restarted.
|
// restart in a session, we want to make sure to execute the code.
|
||||||
// We pass undefined for the code so it reads from the current state.
|
// We force it to re-execute the code because we want to make sure
|
||||||
executeCode(undefined, true)
|
// the code is executed everytime the stream is restarted.
|
||||||
|
// We pass undefined for the code so it reads from the current state.
|
||||||
|
executeCode(undefined, true)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
onClose: () => {
|
onClose: () => {
|
||||||
setIsStreamReady(false)
|
setIsStreamReady(false)
|
||||||
@ -725,7 +733,11 @@ export class EngineCommandManager {
|
|||||||
message.request_id
|
message.request_id
|
||||||
) {
|
) {
|
||||||
this.handleModelingCommand(message.resp, message.request_id)
|
this.handleModelingCommand(message.resp, message.request_id)
|
||||||
} else if (!message.success && message.request_id) {
|
} else if (
|
||||||
|
!message.success &&
|
||||||
|
message.request_id &&
|
||||||
|
this.artifactMap[message.request_id]
|
||||||
|
) {
|
||||||
this.handleFailedModelingCommand(message)
|
this.handleFailedModelingCommand(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1173,6 +1185,95 @@ export class EngineCommandManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
private async initPlanes() {
|
||||||
|
const [xy, yz, xz] = [
|
||||||
|
await this.createPlane({
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
color: { r: 0.7, g: 0.28, b: 0.28, a: 0.4 },
|
||||||
|
}),
|
||||||
|
await this.createPlane({
|
||||||
|
x_axis: { x: 0, y: 1, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.7, b: 0.28, a: 0.4 },
|
||||||
|
}),
|
||||||
|
await this.createPlane({
|
||||||
|
x_axis: { x: 1, y: 0, z: 0 },
|
||||||
|
y_axis: { x: 0, y: 0, z: 1 },
|
||||||
|
color: { r: 0.28, g: 0.28, b: 0.7, a: 0.4 },
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
this.defaultPlanes = { xy, yz, xz }
|
||||||
|
|
||||||
|
this.subscribeTo({
|
||||||
|
event: 'select_with_point',
|
||||||
|
callback: ({ data }) => {
|
||||||
|
if (!data?.entity_id) return
|
||||||
|
if (
|
||||||
|
![
|
||||||
|
this.defaultPlanes.xy,
|
||||||
|
this.defaultPlanes.yz,
|
||||||
|
this.defaultPlanes.xz,
|
||||||
|
].includes(data.entity_id)
|
||||||
|
)
|
||||||
|
return
|
||||||
|
this.onPlaneSelectCallback(data.entity_id)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onPlaneSelectCallback = (id: string) => {}
|
||||||
|
onPlaneSelected(callback: (id: string) => void) {
|
||||||
|
this.onPlaneSelectCallback = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
async setPlaneHidden(id: string, hidden: boolean): Promise<string> {
|
||||||
|
return await this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'object_visible',
|
||||||
|
object_id: id,
|
||||||
|
hidden: hidden,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createPlane({
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
color,
|
||||||
|
}: {
|
||||||
|
x_axis: Models['Point3d_type']
|
||||||
|
y_axis: Models['Point3d_type']
|
||||||
|
color: Models['Color_type']
|
||||||
|
}): Promise<string> {
|
||||||
|
const planeId = uuidv4()
|
||||||
|
await this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'make_plane',
|
||||||
|
size: 60,
|
||||||
|
origin: { x: 0, y: 0, z: 0 },
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
clobber: false,
|
||||||
|
hide: true,
|
||||||
|
},
|
||||||
|
cmd_id: planeId,
|
||||||
|
})
|
||||||
|
await this.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd: {
|
||||||
|
type: 'plane_set_color',
|
||||||
|
plane_id: planeId,
|
||||||
|
color,
|
||||||
|
},
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
})
|
||||||
|
await this.setPlaneHidden(planeId, true)
|
||||||
|
return planeId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const engineCommandManager = new EngineCommandManager()
|
export const engineCommandManager = new EngineCommandManager()
|
||||||
|
|||||||
@ -96,7 +96,8 @@ describe('testing changeSketchArguments', () => {
|
|||||||
const lineAfterChange = 'lineTo([2, 3], %)'
|
const lineAfterChange = 'lineTo([2, 3], %)'
|
||||||
test('changeSketchArguments', async () => {
|
test('changeSketchArguments', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
|
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
@ -112,20 +113,6 @@ show(mySketch001)
|
|||||||
programMemory,
|
programMemory,
|
||||||
[sourceStart, sourceStart + lineToChange.length],
|
[sourceStart, sourceStart + lineToChange.length],
|
||||||
[2, 3],
|
[2, 3],
|
||||||
{
|
|
||||||
mode: 'sketch',
|
|
||||||
sketchMode: 'sketchEdit',
|
|
||||||
pathId: '',
|
|
||||||
rotation: [0, 0, 0, 1],
|
|
||||||
position: [0, 0, 0],
|
|
||||||
pathToNode: [
|
|
||||||
['body', ''],
|
|
||||||
[0, 'index'],
|
|
||||||
['declarations', 'VariableDeclaration'],
|
|
||||||
[0, 'index'],
|
|
||||||
['init', 'VariableDeclarator'],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
[0, 0]
|
[0, 0]
|
||||||
)
|
)
|
||||||
expect(recast(modifiedAst)).toBe(expectedCode)
|
expect(recast(modifiedAst)).toBe(expectedCode)
|
||||||
@ -137,7 +124,8 @@ describe('testing addNewSketchLn', () => {
|
|||||||
test('addNewSketchLn', async () => {
|
test('addNewSketchLn', async () => {
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const code = `
|
const code = `
|
||||||
const mySketch001 = startSketchAt([0, 0])
|
const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
@ -145,7 +133,7 @@ show(mySketch001)`
|
|||||||
const ast = parse(code)
|
const ast = parse(code)
|
||||||
const programMemory = await enginelessExecutor(ast)
|
const programMemory = await enginelessExecutor(ast)
|
||||||
const sourceStart = code.indexOf(lineToChange)
|
const sourceStart = code.indexOf(lineToChange)
|
||||||
expect(sourceStart).toBe(66)
|
expect(sourceStart).toBe(95)
|
||||||
let { modifiedAst } = addNewSketchLn({
|
let { modifiedAst } = addNewSketchLn({
|
||||||
node: ast,
|
node: ast,
|
||||||
programMemory,
|
programMemory,
|
||||||
@ -160,7 +148,8 @@ show(mySketch001)`
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
let expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
let expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
@ -181,7 +170,8 @@ show(mySketch001)
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
expectedCode = `const mySketch001 = startSketchAt([0, 0])
|
expectedCode = `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
@ -196,7 +186,8 @@ describe('testing addTagForSketchOnFace', () => {
|
|||||||
it('needs to be in it', async () => {
|
it('needs to be in it', async () => {
|
||||||
const originalLine = 'lineTo([-1.59, -1.54], %)'
|
const originalLine = 'lineTo([-1.59, -1.54], %)'
|
||||||
// Enable rotations #152
|
// Enable rotations #152
|
||||||
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
|
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> ${line}
|
|> ${line}
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import {
|
|||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
} from '../queryAst'
|
} from '../queryAst'
|
||||||
import { isLiteralArrayOrStatic } from './sketchcombos'
|
import { isLiteralArrayOrStatic } from './sketchcombos'
|
||||||
import { GuiModes, toolTips, ToolTip } from '../../useStore'
|
import { toolTips, ToolTip } from '../../useStore'
|
||||||
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
import { createPipeExpression, splitPathAtPipeExpression } from '../modifyAst'
|
||||||
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
import { generateUuidFromHashSeed } from '../../lib/uuid'
|
||||||
|
|
||||||
@ -908,7 +908,6 @@ export function changeSketchArguments(
|
|||||||
programMemory: ProgramMemory,
|
programMemory: ProgramMemory,
|
||||||
sourceRange: SourceRange,
|
sourceRange: SourceRange,
|
||||||
args: [number, number],
|
args: [number, number],
|
||||||
guiMode: GuiModes,
|
|
||||||
from: [number, number]
|
from: [number, number]
|
||||||
): { modifiedAst: Program } {
|
): { modifiedAst: Program } {
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
@ -917,7 +916,6 @@ export function changeSketchArguments(
|
|||||||
_node,
|
_node,
|
||||||
thePath
|
thePath
|
||||||
)
|
)
|
||||||
if (guiMode.mode !== 'sketch') throw new Error('not in sketch mode')
|
|
||||||
|
|
||||||
if (callExpression?.callee?.name in sketchLineHelperMap) {
|
if (callExpression?.callee?.name in sketchLineHelperMap) {
|
||||||
const { updateArgs } = sketchLineHelperMap[callExpression.callee.name]
|
const { updateArgs } = sketchLineHelperMap[callExpression.callee.name]
|
||||||
|
|||||||
@ -52,7 +52,8 @@ async function testingSwapSketchFnCall({
|
|||||||
|
|
||||||
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
|
||||||
const bigExampleArr = [
|
const bigExampleArr = [
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
|
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
|
||||||
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
|
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
|
||||||
` |> angledLine({`,
|
` |> angledLine({`,
|
||||||
@ -277,7 +278,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
`const angledLineOfYLengthY = 0.89`,
|
`const angledLineOfYLengthY = 0.89`,
|
||||||
`const angledLineToXx = -1.86`,
|
`const angledLineToXx = -1.86`,
|
||||||
`const angledLineToYy = -0.76`,
|
`const angledLineToYy = -0.76`,
|
||||||
`const part001 = startSketchAt([0, 0])`,
|
`const part001 = startSketchOn('XY')`,
|
||||||
|
` |> startProfileAt([0, 0], %)`,
|
||||||
// ` |> rx(90, %)`,
|
// ` |> rx(90, %)`,
|
||||||
` |> lineTo([1, 1], %)`,
|
` |> lineTo([1, 1], %)`,
|
||||||
` |> line([lineX, 2.13], %)`,
|
` |> line([lineX, 2.13], %)`,
|
||||||
@ -371,7 +373,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
|
|||||||
|
|
||||||
describe('testing getSketchSegmentIndexFromSourceRange', () => {
|
describe('testing getSketchSegmentIndexFromSourceRange', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const part001 = startSketchAt([0, 0.04]) // segment-in-start
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0.04], %) // segment-in-start
|
||||||
|> line([0, 0.4], %)
|
|> line([0, 0.4], %)
|
||||||
|> xLine(3.48, %)
|
|> xLine(3.48, %)
|
||||||
|> line([2.14, 1.35], %) // normal-segment
|
|> line([2.14, 1.35], %) // normal-segment
|
||||||
|
|||||||
@ -92,7 +92,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([1, 3.82], %) // ln-should-get-tag
|
|> line([1, 3.82], %) // ln-should-get-tag
|
||||||
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|
||||||
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
|
||||||
@ -128,7 +129,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
||||||
|> angledLineToX([
|
|> angledLineToX([
|
||||||
-angleToMatchLengthX('seg01', myVar, %),
|
-angleToMatchLengthX('seg01', myVar, %),
|
||||||
@ -231,7 +233,8 @@ describe('testing transformAstForSketchLines for vertical and horizontal constra
|
|||||||
const inputScript = `const myVar = 2
|
const inputScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
@ -259,7 +262,8 @@ show(part001)
|
|||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> xLine(-6.28, %) // select for horizontal constraint 1
|
|> xLine(-6.28, %) // select for horizontal constraint 1
|
||||||
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
|> line([-1.07, myVar], %) // select for vertical constraint 1
|
||||||
@ -317,7 +321,8 @@ show(part001)
|
|||||||
const expectModifiedScript = `const myVar = 2
|
const expectModifiedScript = `const myVar = 2
|
||||||
const myVar2 = 12
|
const myVar2 = 12
|
||||||
const myVar3 = -10
|
const myVar3 = -10
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|
||||||
|> yLine(myVar, %) // select for vertical constraint 1
|
|> yLine(myVar, %) // select for vertical constraint 1
|
||||||
@ -376,7 +381,8 @@ show(part001)
|
|||||||
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
|
describe('testing transformAstForSketchLines for vertical and horizontal distance constraints', () => {
|
||||||
describe('testing setHorzDistance for line', () => {
|
describe('testing setHorzDistance for line', () => {
|
||||||
const inputScript = `const myVar = 1
|
const inputScript = `const myVar = 1
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0.31, 1.67], %) // base selection
|
|> line([0.31, 1.67], %) // base selection
|
||||||
|> line([0.45, 1.46], %)
|
|> line([0.45, 1.46], %)
|
||||||
|> line([0.45, 1.46], %) // free
|
|> line([0.45, 1.46], %) // free
|
||||||
@ -477,7 +483,8 @@ const baseThickHalf = baseThick / 2
|
|||||||
const halfHeight = totalHeight / 2
|
const halfHeight = totalHeight / 2
|
||||||
const halfArmAngle = armAngle / 2
|
const halfArmAngle = armAngle / 2
|
||||||
|
|
||||||
const part001 = startSketchAt([-0.01, -0.05])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-0.01, -0.05], %)
|
||||||
|> line([0.01, 0.94 + 0], %) // partial
|
|> line([0.01, 0.94 + 0], %) // partial
|
||||||
|> xLine(3.03, %) // partial
|
|> xLine(3.03, %) // partial
|
||||||
|> angledLine({
|
|> angledLine({
|
||||||
|
|||||||
@ -1306,7 +1306,7 @@ export function getRemoveConstraintsTransforms(
|
|||||||
return theTransforms
|
return theTransforms
|
||||||
}
|
}
|
||||||
|
|
||||||
type PathToNodeMap = { [key: number]: PathToNode }
|
export type PathToNodeMap = { [key: number]: PathToNode }
|
||||||
|
|
||||||
export function transformSecondarySketchLinesTagFirst({
|
export function transformSecondarySketchLinesTagFirst({
|
||||||
ast,
|
ast,
|
||||||
|
|||||||
@ -5,7 +5,8 @@ beforeAll(() => initPromise)
|
|||||||
|
|
||||||
describe('testing angledLineThatIntersects', () => {
|
describe('testing angledLineThatIntersects', () => {
|
||||||
it('angledLineThatIntersects should intersect with another line', async () => {
|
it('angledLineThatIntersects should intersect with another line', async () => {
|
||||||
const code = (offset: string) => `const part001 = startSketchAt([0, 0])
|
const code = (offset: string) => `const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo({to:[2, 2], tag: "yo"}, %)
|
|> lineTo({to:[2, 2], tag: "yo"}, %)
|
||||||
|> lineTo([3, 1], %)
|
|> lineTo([3, 1], %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
|
|||||||
@ -1,29 +1,29 @@
|
|||||||
import { Selections, StoreState } from '../useStore'
|
import { Selections, StoreState } from '../useStore'
|
||||||
import { Program, PathToNode } from './wasm'
|
import { Program, PathToNode } from './wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
|
import { ArtifactMap } from './std/engineConnection'
|
||||||
|
import { isOverlap } from 'lib/utils'
|
||||||
|
|
||||||
export function updateCursors(
|
export function pathMapToSelections(
|
||||||
setCursor: StoreState['setCursor'],
|
ast: Program,
|
||||||
selectionRanges: Selections,
|
prevSelections: Selections,
|
||||||
pathToNodeMap: { [key: number]: PathToNode }
|
pathToNodeMap: { [key: number]: PathToNode }
|
||||||
): (newAst: Program) => void {
|
): Selections {
|
||||||
return (newAst: Program) => {
|
const newSelections: Selections = {
|
||||||
const newSelections: Selections = {
|
...prevSelections,
|
||||||
...selectionRanges,
|
codeBasedSelections: [],
|
||||||
codeBasedSelections: [],
|
|
||||||
}
|
|
||||||
Object.entries(pathToNodeMap).forEach(([index, path]) => {
|
|
||||||
const node = getNodeFromPath(newAst, path).node as any
|
|
||||||
const type = selectionRanges.codeBasedSelections[Number(index)].type
|
|
||||||
if (node) {
|
|
||||||
newSelections.codeBasedSelections.push({
|
|
||||||
range: [node.start, node.end],
|
|
||||||
type: type || 'default',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setCursor(newSelections)
|
|
||||||
}
|
}
|
||||||
|
Object.entries(pathToNodeMap).forEach(([index, path]) => {
|
||||||
|
const node = getNodeFromPath(ast, path).node as any
|
||||||
|
const type = prevSelections.codeBasedSelections[Number(index)].type
|
||||||
|
if (node) {
|
||||||
|
newSelections.codeBasedSelections.push({
|
||||||
|
range: [node.start, node.end],
|
||||||
|
type: type || 'default',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return newSelections
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isReducedMotion(): boolean {
|
export function isReducedMotion(): boolean {
|
||||||
@ -34,3 +34,27 @@ export function isReducedMotion(): boolean {
|
|||||||
window.matchMedia('(prefers-reduced-motion)').matches
|
window.matchMedia('(prefers-reduced-motion)').matches
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCursorInSketchCommandRange(
|
||||||
|
artifactMap: ArtifactMap,
|
||||||
|
selectionRanges: Selections
|
||||||
|
): string | false {
|
||||||
|
const overlapingEntries: [string, ArtifactMap[string]][] = Object.entries(
|
||||||
|
artifactMap
|
||||||
|
).filter(([id, artifact]: [string, ArtifactMap[string]]) =>
|
||||||
|
selectionRanges.codeBasedSelections.some(
|
||||||
|
(selection) =>
|
||||||
|
Array.isArray(selection?.range) &&
|
||||||
|
Array.isArray(artifact?.range) &&
|
||||||
|
isOverlap(selection.range, artifact.range) &&
|
||||||
|
(artifact.commandType === 'start_path' ||
|
||||||
|
artifact.commandType === 'extend_path' ||
|
||||||
|
artifact.commandType === 'close_path')
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return overlapingEntries.length && overlapingEntries[0][1].parentId
|
||||||
|
? overlapingEntries[0][1].parentId
|
||||||
|
: overlapingEntries.find(
|
||||||
|
([, artifact]) => artifact.commandType === 'start_path'
|
||||||
|
)?.[0] || false
|
||||||
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
|
|||||||
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
|
||||||
import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
import type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
import type { Token } from '../wasm-lib/kcl/bindings/Token'
|
||||||
|
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
|
|
||||||
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
export type { Program } from '../wasm-lib/kcl/bindings/Program'
|
||||||
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
export type { Value } from '../wasm-lib/kcl/bindings/Value'
|
||||||
@ -118,6 +119,7 @@ export const executor = async (
|
|||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
|
planes: DefaultPlanes,
|
||||||
// work around while the gemotry is still be stored on the frontend
|
// work around while the gemotry is still be stored on the frontend
|
||||||
// will be removed when the stream UI is added.
|
// will be removed when the stream UI is added.
|
||||||
tempMapCallback: (a: {
|
tempMapCallback: (a: {
|
||||||
@ -129,7 +131,8 @@ export const executor = async (
|
|||||||
const _programMemory = await _executor(
|
const _programMemory = await _executor(
|
||||||
node,
|
node,
|
||||||
programMemory,
|
programMemory,
|
||||||
engineCommandManager
|
engineCommandManager,
|
||||||
|
planes
|
||||||
)
|
)
|
||||||
const { artifactMap, sourceRangeMap } =
|
const { artifactMap, sourceRangeMap } =
|
||||||
await engineCommandManager.waitForAllCommands(node, _programMemory)
|
await engineCommandManager.waitForAllCommands(node, _programMemory)
|
||||||
@ -142,13 +145,15 @@ export const executor = async (
|
|||||||
export const _executor = async (
|
export const _executor = async (
|
||||||
node: Program,
|
node: Program,
|
||||||
programMemory: ProgramMemory = { root: {}, return: null },
|
programMemory: ProgramMemory = { root: {}, return: null },
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager,
|
||||||
|
planes: DefaultPlanes
|
||||||
): Promise<ProgramMemory> => {
|
): Promise<ProgramMemory> => {
|
||||||
try {
|
try {
|
||||||
const memory: ProgramMemory = await execute_wasm(
|
const memory: ProgramMemory = await execute_wasm(
|
||||||
JSON.stringify(node),
|
JSON.stringify(node),
|
||||||
JSON.stringify(programMemory),
|
JSON.stringify(programMemory),
|
||||||
engineCommandManager
|
engineCommandManager,
|
||||||
|
JSON.stringify(planes)
|
||||||
)
|
)
|
||||||
return memory
|
return memory
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -190,6 +195,7 @@ export const modifyAstForSketch = async (
|
|||||||
engineCommandManager: EngineCommandManager,
|
engineCommandManager: EngineCommandManager,
|
||||||
ast: Program,
|
ast: Program,
|
||||||
variableName: string,
|
variableName: string,
|
||||||
|
currentPlane: string,
|
||||||
engineId: string
|
engineId: string
|
||||||
): Promise<Program> => {
|
): Promise<Program> => {
|
||||||
try {
|
try {
|
||||||
@ -197,6 +203,7 @@ export const modifyAstForSketch = async (
|
|||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
JSON.stringify(ast),
|
JSON.stringify(ast),
|
||||||
variableName,
|
variableName,
|
||||||
|
JSON.stringify(currentPlane),
|
||||||
engineId
|
engineId
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,8 @@ const filletR = thickness * 2
|
|||||||
const shelfMountL = 9
|
const shelfMountL = 9
|
||||||
const wallMountL = 8
|
const wallMountL = 8
|
||||||
|
|
||||||
const bracket = startSketchAt([0, 0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, wallMountL], %)
|
|> line([0, wallMountL], %)
|
||||||
|> tangentalArc({
|
|> tangentalArc({
|
||||||
radius: filletR,
|
radius: filletR,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import {
|
|||||||
EngineCommand,
|
EngineCommand,
|
||||||
} from '../lang/std/engineConnection'
|
} from '../lang/std/engineConnection'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
type WebSocketResponse = Models['OkWebSocketResponseData_type']
|
||||||
|
|
||||||
@ -64,7 +65,11 @@ export async function enginelessExecutor(
|
|||||||
}) as any as EngineCommandManager
|
}) as any as EngineCommandManager
|
||||||
await mockEngineCommandManager.waitForReady
|
await mockEngineCommandManager.waitForReady
|
||||||
mockEngineCommandManager.startNewSession()
|
mockEngineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, mockEngineCommandManager)
|
const programMemory = await _executor(ast, pm, mockEngineCommandManager, {
|
||||||
|
xy: uuidv4(),
|
||||||
|
yz: uuidv4(),
|
||||||
|
xz: uuidv4(),
|
||||||
|
})
|
||||||
await mockEngineCommandManager.waitForAllCommands()
|
await mockEngineCommandManager.waitForAllCommands()
|
||||||
return programMemory
|
return programMemory
|
||||||
}
|
}
|
||||||
@ -83,7 +88,11 @@ export async function executor(
|
|||||||
})
|
})
|
||||||
await engineCommandManager.waitForReady
|
await engineCommandManager.waitForReady
|
||||||
engineCommandManager.startNewSession()
|
engineCommandManager.startNewSession()
|
||||||
const programMemory = await _executor(ast, pm, engineCommandManager)
|
const programMemory = await _executor(ast, pm, engineCommandManager, {
|
||||||
|
xy: uuidv4(),
|
||||||
|
yz: uuidv4(),
|
||||||
|
xz: uuidv4(),
|
||||||
|
})
|
||||||
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||||
return programMemory
|
return programMemory
|
||||||
}
|
}
|
||||||
|
|||||||
853
src/machines/modelingMachine.ts
Normal file
853
src/machines/modelingMachine.ts
Normal file
@ -0,0 +1,853 @@
|
|||||||
|
import { PathToNode } from 'lang/wasm'
|
||||||
|
import { engineCommandManager } from 'lang/std/engineConnection'
|
||||||
|
import { isReducedMotion } from 'lang/util'
|
||||||
|
import { Axis, Selection, SelectionRangeTypeMap, Selections } from 'useStore'
|
||||||
|
import { assign, createMachine } from 'xstate'
|
||||||
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
import { isCursorInSketchCommandRange } from 'lang/util'
|
||||||
|
import {
|
||||||
|
doesPipeHaveCallExp,
|
||||||
|
getNodePathFromSourceRange,
|
||||||
|
hasExtrudeSketchGroup,
|
||||||
|
} from 'lang/queryAst'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
import {
|
||||||
|
horzVertInfo,
|
||||||
|
applyConstraintHorzVert,
|
||||||
|
} from 'components/Toolbar/HorzVert'
|
||||||
|
import {
|
||||||
|
applyConstraintHorzVertAlign,
|
||||||
|
horzVertDistanceInfo,
|
||||||
|
} from 'components/Toolbar/SetHorzVertDistance'
|
||||||
|
import { angleBetweenInfo } from 'components/Toolbar/SetAngleBetween'
|
||||||
|
import { setAngleLengthInfo } from 'components/Toolbar/setAngleLength'
|
||||||
|
import {
|
||||||
|
applyConstraintEqualLength,
|
||||||
|
setEqualLengthInfo,
|
||||||
|
} from 'components/Toolbar/EqualLength'
|
||||||
|
import { extrudeSketch } from 'lang/modifyAst'
|
||||||
|
import { getNodeFromPath } from '../lang/queryAst'
|
||||||
|
import { CallExpression, PipeExpression } from '../lang/wasm'
|
||||||
|
|
||||||
|
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||||
|
|
||||||
|
export type SetSelections =
|
||||||
|
| {
|
||||||
|
selectionType: 'singleCodeCursor'
|
||||||
|
selection?: Selection
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
selectionType: 'otherSelection'
|
||||||
|
selection: Axis
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
selectionType: 'completeSelection'
|
||||||
|
selection: Selections
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
selectionType: 'mirrorCodeMirrorSelections'
|
||||||
|
selection: Selections
|
||||||
|
}
|
||||||
|
|
||||||
|
export const modelingMachine = createMachine(
|
||||||
|
{
|
||||||
|
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogDMAVgDsAOmEiAjLMGCAHIIAsAJlHKANCACeiaQDZBEsctGrVATmmiaNQQF8H2tBhz5x2CJjAEAymDsAASwWGDknNy0DEggLGyRPLECCMrCCuL20qrCRpYagvnaegjZBtLiBqLploIGCtZVyk4u6Fh4UJ7evgAicGERQSx47NG88RxcSaApdcKSNKIKi8ppwsrLwsX60oriqgaWBgbKImpWqi0gru0eXj4EfaE+g5AwY7ETibyzx5lLu1sygM61ERV0+ho0mU4gURlOwihlis2SuN3cnXuvX6L2CJD42FgH2YrEm3B+QnM4mqdhoyPUILqBm2CAaFTS5houQUCMczmubQxXQeAVxQ1QI2JcVJ32SiGUXPEhjBtWklgaokMzIhqVUNHEG0WgjVqkEUN2aMFHWFvlF4WCbzAUq+UwpqRoGQ2pwacPW4JKJxhgYU0kWu3Wymklrc1qx-gGeIJRPo4xlrrlqUEqkqdMKOSRNEOLPWGTVhkswlU0O5fNaMbu3XjYoAZiRKM60+SMwqMgY7Go6mbalCWaIjLCjtJ0n36tUFNHbpjGwBRXDsMAAJxCAGtAuQABYdhLpmY7SPiSzAyOXwGFUQs01BtXVEEV9VSZr89Gxldrzc7vdD2kGISWPLtTwQepBGpEN8lDGhVBDLYdTUfVZzvYFQ3MS4vytBsHieBMglbdsU0+Ttpn4Sk0KzBR1EKdING1EozQqYxajyNQ6MOBchTjO1BhITBMCPMlKJSSNoIsEEllQrlhGQko9RhZEDFUL1vWEUMcLrRcbUeHF7SCISRLI0CxLdRR2ROJQwQQ2RmMQENJD1OxjR5aE+1rAV6yXB4wD4dgNwAVwwIIRjANdRNlCDlGRJU6kfapK0vdYWSnBQJEsfJMtODYrM-XS+MbAKgtCsBwr-KLgNTMDxPlawJ0DKtXNkLl0o2NjNULPMPQ2HSfL0vxd3YA9iDIShMGGwDopPKjSg0fUaDURFFoVbJ7x1S9oOSmQcl2CtRF461ptG-dxFOg8AElGwE4JhiiszpTqt1pB9C8NA8plynVFlyn1EMzVfdTrAU46PEu87IZukUiNCKAAFtItGJ6XXA+a3vVD6vV2Y41IQlkzAMakLCnD0K0jK9wc6SGLpG67G0IsUHpRkDnosjM3oUi91SsRYjmUywWRUDJRFEY0PXzdQrGpunALls6YexZ4jPhpHHrZtH6tKHkJHkLJ1AUGpsofQxxBEWpxayCXFFl2noZXABHYLsCYIJ2FQVBTM1ijLK5Njp20wtPMEUc+yVRC4vgpZlqjXDfIVg9E-3JWCGXZ3XaCBHUAANwqj2vdm9HZg9NjpPUZbIykFkKYNTD1nsGycjt+modb1OAmCFWIimIvtbVMwczF4wtXqRyEDHfVsiNwtahyTU46Kk7W+T1PkBIXcQjARHkaCPON04cghL716kKVdYjErEWFEy9LoXZKsQT7I3jWEFv5Ydh5183tXd-3VANzYAAF7cHYMfVGvtOZggyOkewqkjBqQrOlOBlQqjyCrEoacR145DRXp-XwRBuCwCCiQPAQR-6AJAWuISQQICEjARQJ0ECXpQOqJULm+QVArFvjqcsMIDg2UwRsdIhVBpCntu3RshDcDEI3KQ3Ae9NyHxoXQ4hE0mE+xYRBGwlh9QpQOp5awdR0piAqOLLS5g0HmlEd+CGeDJEPGkbI+Rxl8A+BPpzOEKk+wOT6nYHk6VNgGkOLkUJOQ1jvzOqvKRRCSFkJ8Pgdgh5mEc20UbDIN4GjHAKopfQjQLzLAVCIrkdE344PEfYwCqcnFxIURQ4BoCTI6GMjgKAuAPHaKWMTJQCFzBZIOEbdK6kYSRg0EsRCdhLyWEiUnfBxBYlyLIfvZRwlmlCWwG0jpGNljQQVMCKohZzBiHSiIHasg1DIhEFpOEMy25VJiTI2pQQwDOxoQkqASStkpDejYA0voqg5HKFOLQvC0gwK0sOPs1g3K3PEAAGTwBVAAKp7TAacM5u2znnd2qKvlCEUDA8ZCk1TpE8qOJQSouQ9SnMIfIGhYUItwMi1F4gAAKEo1xBAAIIQAwBAAgPKIDiklCkmK80zTAnNg0dIUg1JqFWMWKESoThQkyn6I2UgGWIqCCir2F1t7q2CIKyAAreXCo1rVVJ4rEIVEOJlLyZpCz1EJqaA0Bx1JPx+dJLVTKdUsoCDvTlxr+WEIRkwHw64gjuA0ZasVsxgQSBnsPMFyJcmpBvkqXKozlpWDij65leqA2Gu5byk16cXZuyZQAdxxYXUVc1ZgV32G9MeCEjhHGFm9JU2Uq5yGlTYvCNMV6MoLZgcQV1cAcAIHihA6k1T-CuZqXIVc-r2EyPUMQmokJ2AMPmv1eqJ1TqoDVciWj5qmmypkS5cClAghBSUSsxNCglJSosRYu7ynL3liO-dY6AByqAgjspGLAU1QqWYzoZJYC8G0wSvuRMWdSSoim6N0a-TKsKuVVtIcEepVCwGYFofQ9RlVmyoAIBAbgYBPC4BzqgXc4gYDsAALR4caZgZjeAyOQaWPMRCyJNTGmBccdKS7YRmmMG5fs0zP12PllhnD5CAENOoYR1RDDKCkfI5uDcADxDhpIOwMjG4EaMcCKx5T+GhKcdwNx+txdECG3mIdaWjQjaXnSlUDIqroTmHDOsTD2GOCKIPtgI+aniOMK0xRqjNG6MMaY8x5ZYXrNcdQDx3RkhzDytonCYwJjGqIUyvIWwiJnyBcU8l8LRG1FRbSwQHTemDNGYAaZxLVXUu2fS-Z7W6hMtiHUupXLRhNolGBTtG+4tjBwjpLUCrwWyBQB8NFyjTK4v0eo4lxbPgbN2c0ValIfXSynGylBKcVZAmVgvB6ZayxR4enm3iNxFV6uNY3PpzAhnjNtfM9tsAu3uv7bjY5lUkhgTqnbR6qcgTdhXtkDYeHxojiPajZFD5+4VuxbwPFzb5n3lJIBxl4m6hKwKQ0Ju1Q6V1QVGWh6E4Fh8wbBR-jjHr2Ny6fe8177ZmWMs8Jz1t0fXibbSrPkBUsc01qj2Cqxu8gn5KFuUEXAgGSK2iIhgVswVMD3U+0ymd2Q4qSArG9a8PJgShxQlOGDUI6hG2nuYbytjOgkCC5wfAW9cRTHjF3BMvcBec2DtSXRY4eTHEjND3hy7qR9g2Oqe1l55yyc6AecI24Oge-tF7-Xoz9jLo5AcXpJi1J-NWOaOosdZYp-IGn933dEjTpPeZYHpRKaVGNBXDQ6pwcdSkOwlQtLr3LBwvyZXGB4CxCd7GhtQg6SSBkHIRQKh1D3sQMx5zyxzRSCkLog4i8xE-h8FPhzqRi-b8OYcTU8ILdjcHBHXRdI3ohlOI7wdycj-90ONmHNq09lQnUCyZydzRQTqLSY4KcWFfBd-V6S5fYJkG3eQQoKoGuPUXPKQBAkMB3AdBOWmH9XVTAKAjMaSEwS5WlaoLMCwFfWdM0SoHtK+RCDBGWJPZOeFbVPA8QHoKjAgiCYwag3IcWG1VVBkRDaCZ9f6WlKwBXJgnA1gllYDINUtCALg89NDc2ThXKBAk4UbRASsIMFDG+EEdYCwPdNgotXeYNJQxtcOODCmfZIcRDXQrkOPAw84YwllQ9dgCwxzV+I3U0UgqQBnSg65fYC9FQFQU4OkD9JeOTKJXAllADIDDldgcfJvafWdE4YmU4H6U0aeOEewyoFDOkLIRiWFNAbFPAzwhAErLKQFMwBNOoaQGuagy8coVaeeKoGTKIodeTV3JTShdjGrDTF7LrCoi9JaGwARDBAfTzfrXIIrUJKEEEFHDrCLWrTTNLEYhUHaQcKEHNbIZaExDYWA0AjiCwI4RPTo5ghTBbZ7LTDY35PUIwIcZaEQJQQJRYTIEWBEewOoCsZnNHJJW409A7RzcPD6d8dBCFXIKnEQTINtZKaoPUAaJ3ZOJXFXNsMACos46kEQVVawCsQsSnFCd4woHkCItqKsWWF3HDdPOvOaLWN0XKC8A5dzG1PgkxTKHmJYAofKJ-SvfcVPGk33dGekzmBnSQE4anQ2OKQodk7MWQCxREFVOiJwJwIAA */
|
||||||
|
id: 'Modeling',
|
||||||
|
|
||||||
|
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
|
||||||
|
predictableActionArguments: true,
|
||||||
|
preserveActionOrder: true,
|
||||||
|
|
||||||
|
context: {
|
||||||
|
guiMode: 'default',
|
||||||
|
selection: [] as string[],
|
||||||
|
selectionRanges: {
|
||||||
|
otherSelections: [],
|
||||||
|
codeBasedSelections: [],
|
||||||
|
} as Selections,
|
||||||
|
selectionRangeTypeMap: {} as SelectionRangeTypeMap,
|
||||||
|
sketchPathToNode: null as PathToNode | null, // maybe too specific, and we should have a generic pathToNode, but being specific seems less risky when I'm not sure
|
||||||
|
sketchEnginePathId: '' as string,
|
||||||
|
sketchPlaneId: '' as string,
|
||||||
|
},
|
||||||
|
|
||||||
|
schema: {
|
||||||
|
events: {} as
|
||||||
|
| { type: 'Deselect all' }
|
||||||
|
| { type: 'Deselect edge'; data: Selection & { type: 'edge' } }
|
||||||
|
| { type: 'Deselect axis'; data: Axis }
|
||||||
|
| {
|
||||||
|
type: 'Deselect segment'
|
||||||
|
data: Selection & { type: 'line' | 'arc' }
|
||||||
|
}
|
||||||
|
| { type: 'Deselect face'; data: Selection & { type: 'face' } }
|
||||||
|
| {
|
||||||
|
type: 'Deselect point'
|
||||||
|
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
|
||||||
|
}
|
||||||
|
| { type: 'Enter sketch' }
|
||||||
|
| { type: 'Select all'; data: Selection & { type: 'all ' } }
|
||||||
|
| { type: 'Select edge'; data: Selection & { type: 'edge' } }
|
||||||
|
| { type: 'Select axis'; data: Axis }
|
||||||
|
| { type: 'Select segment'; data: Selection & { type: 'line' | 'arc' } }
|
||||||
|
| { type: 'Select face'; data: Selection & { type: 'face' } }
|
||||||
|
| { type: 'Select default plane'; data: { planeId: string } }
|
||||||
|
| { type: 'Set selection'; data: SetSelections }
|
||||||
|
| {
|
||||||
|
type: 'Select point'
|
||||||
|
data: Selection & { type: 'point' | 'line-end' | 'line-mid' }
|
||||||
|
}
|
||||||
|
| { type: 'Sketch no face' }
|
||||||
|
| { type: 'Toggle gui mode' }
|
||||||
|
| { type: 'Cancel' }
|
||||||
|
| { type: 'CancelSketch' }
|
||||||
|
| {
|
||||||
|
type: 'Add point'
|
||||||
|
data: {
|
||||||
|
coords: { x: number; y: number }[]
|
||||||
|
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz' | null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
| { type: 'Equip tool' }
|
||||||
|
| { type: 'Equip move tool' }
|
||||||
|
| { type: 'Set radius' }
|
||||||
|
| { type: 'Complete line' }
|
||||||
|
| { type: 'Set distance' }
|
||||||
|
| { type: 'Equip new tool' }
|
||||||
|
| { type: 'update_code'; data: string }
|
||||||
|
| { type: 'Make segment horizontal' }
|
||||||
|
| { type: 'Make segment vertical' }
|
||||||
|
| { type: 'Constrain horizontal distance' }
|
||||||
|
| { type: 'Constrain vertical distance' }
|
||||||
|
| { type: 'Constrain angle' }
|
||||||
|
| { type: 'Constrain horizontally align' }
|
||||||
|
| { type: 'Constrain vertically align' }
|
||||||
|
| { type: 'Constrain length' }
|
||||||
|
| { type: 'Constrain equal length' }
|
||||||
|
| { type: 'extrude intent' },
|
||||||
|
// ,
|
||||||
|
},
|
||||||
|
|
||||||
|
states: {
|
||||||
|
idle: {
|
||||||
|
on: {
|
||||||
|
'Set selection': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect point': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Remove from code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: remove highlight',
|
||||||
|
],
|
||||||
|
cond: 'Selection contains point',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect edge': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Remove from code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: remove highlight',
|
||||||
|
],
|
||||||
|
cond: 'Selection contains edge',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect axis': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Remove from other selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: remove highlight',
|
||||||
|
],
|
||||||
|
cond: 'Selection contains axis',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select point': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Add to code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: add highlight',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select edge': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Add to code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: add highlight',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select axis': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Add to other selection',
|
||||||
|
// 'Engine: add highlight',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select face': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Add to code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: add highlight',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Enter sketch': [
|
||||||
|
{
|
||||||
|
target: 'Sketch',
|
||||||
|
cond: 'Selection is one face',
|
||||||
|
actions: [
|
||||||
|
'set sketch metadata',
|
||||||
|
'sketch mode enabled',
|
||||||
|
'edit mode enter',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'Sketch no face',
|
||||||
|
],
|
||||||
|
|
||||||
|
'Deselect face': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Remove from code-based selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: remove highlight',
|
||||||
|
],
|
||||||
|
cond: 'Selection contains face',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select all': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: 'Add to code-based selection',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect all': {
|
||||||
|
target: 'idle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Clear selection',
|
||||||
|
'Update code selection cursors',
|
||||||
|
// 'Engine: remove highlight',
|
||||||
|
],
|
||||||
|
cond: 'Selection is not empty',
|
||||||
|
},
|
||||||
|
|
||||||
|
'extrude intent': [
|
||||||
|
{
|
||||||
|
target: 'awaiting selection',
|
||||||
|
cond: 'has no selection',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'idle',
|
||||||
|
cond: 'has valid extrude selection',
|
||||||
|
internal: true,
|
||||||
|
actions: 'AST extrude',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Sketch: {
|
||||||
|
states: {
|
||||||
|
SketchIdle: {
|
||||||
|
on: {
|
||||||
|
'Select point': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Update code selection cursors',
|
||||||
|
'Add to code-based selection',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Select segment': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: [
|
||||||
|
'Update code selection cursors',
|
||||||
|
'Add to code-based selection',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect point': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
cond: 'Selection contains point',
|
||||||
|
actions: [
|
||||||
|
'Update code selection cursors',
|
||||||
|
'Add to code-based selection',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Deselect segment': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
cond: 'Selection contains line',
|
||||||
|
actions: [
|
||||||
|
'Update code selection cursors',
|
||||||
|
'Add to code-based selection',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Equip tool': {
|
||||||
|
target: 'Line Tool',
|
||||||
|
actions: 'set tool line',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Equip move tool': 'Move Tool',
|
||||||
|
|
||||||
|
'Set selection': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Make segment vertical': {
|
||||||
|
cond: 'Can make selection vertical',
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: ['Make selection vertical'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Make segment horizontal': {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
cond: 'Can make selection horizontal',
|
||||||
|
actions: ['Make selection horizontal'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain horizontal distance': {
|
||||||
|
target: 'Await horizontal distance info',
|
||||||
|
cond: 'Can constrain horizontal distance',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain vertical distance': {
|
||||||
|
target: 'Await vertical distance info',
|
||||||
|
cond: 'Can constrain vertical distance',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain angle': {
|
||||||
|
target: 'Await angle info',
|
||||||
|
cond: 'Can constrain angle',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain length': {
|
||||||
|
target: 'Await length info',
|
||||||
|
cond: 'Can constrain length',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain horizontally align': {
|
||||||
|
cond: 'Can constrain horizontally align',
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: ['Constrain horizontally align'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain vertically align': {
|
||||||
|
cond: 'Can constrain vertically align',
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: ['Constrain vertically align'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Constrain equal length': {
|
||||||
|
cond: 'Can constrain equal length',
|
||||||
|
target: 'SketchIdle',
|
||||||
|
internal: true,
|
||||||
|
actions: ['Constrain equal length'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
entry: 'equip select',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Line Tool': {
|
||||||
|
states: {
|
||||||
|
Done: {
|
||||||
|
type: 'final',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Point Added': {
|
||||||
|
on: {
|
||||||
|
'Add point': {
|
||||||
|
target: 'Segment Added',
|
||||||
|
actions: ['AST start new sketch'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Segment Added': {
|
||||||
|
on: {
|
||||||
|
'Add point': {
|
||||||
|
target: 'Segment Added',
|
||||||
|
internal: true,
|
||||||
|
actions: ['AST add line segment'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Complete line': {
|
||||||
|
target: 'Done',
|
||||||
|
actions: ['Modify AST', 'Update code selection cursors'],
|
||||||
|
},
|
||||||
|
|
||||||
|
'Equip new tool': {
|
||||||
|
target: 'Segment Added',
|
||||||
|
internal: true,
|
||||||
|
actions: 'set tool',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
Init: {
|
||||||
|
always: [
|
||||||
|
{
|
||||||
|
target: 'Segment Added',
|
||||||
|
cond: 'is editing existing sketch',
|
||||||
|
},
|
||||||
|
'No Points',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
'No Points': {
|
||||||
|
on: {
|
||||||
|
'Add point': 'Point Added',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// invoke: [
|
||||||
|
// {
|
||||||
|
// src: 'createLine',
|
||||||
|
// id: 'Create line',
|
||||||
|
// onDone: 'SketchIdle',
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
initial: 'Init',
|
||||||
|
|
||||||
|
on: {
|
||||||
|
'Equip move tool': 'Move Tool',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Move Tool': {
|
||||||
|
entry: 'set tool move',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Await horizontal distance info': {
|
||||||
|
invoke: {
|
||||||
|
src: 'Get horizontal info',
|
||||||
|
id: 'get-horizontal-info',
|
||||||
|
onDone: {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
onError: 'SketchIdle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Await vertical distance info': {
|
||||||
|
invoke: {
|
||||||
|
src: 'Get vertical info',
|
||||||
|
id: 'get-vertical-info',
|
||||||
|
onDone: {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
onError: 'SketchIdle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Await angle info': {
|
||||||
|
invoke: {
|
||||||
|
src: 'Get angle info',
|
||||||
|
id: 'get-angle-info',
|
||||||
|
onDone: {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
onError: 'SketchIdle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Await length info': {
|
||||||
|
invoke: {
|
||||||
|
src: 'Get length info',
|
||||||
|
id: 'get-length-info',
|
||||||
|
onDone: {
|
||||||
|
target: 'SketchIdle',
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
onError: 'SketchIdle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
initial: 'SketchIdle',
|
||||||
|
|
||||||
|
on: {
|
||||||
|
CancelSketch: '.SketchIdle',
|
||||||
|
},
|
||||||
|
|
||||||
|
exit: 'sketch exit execute',
|
||||||
|
},
|
||||||
|
|
||||||
|
'Sketch no face': {
|
||||||
|
entry: 'show default planes',
|
||||||
|
|
||||||
|
exit: 'hide default planes',
|
||||||
|
on: {
|
||||||
|
'Select default plane': {
|
||||||
|
target: 'Sketch',
|
||||||
|
actions: [
|
||||||
|
'reset sketch metadata',
|
||||||
|
'set default plane id',
|
||||||
|
'sketch mode enabled',
|
||||||
|
'create path',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'awaiting selection': {
|
||||||
|
on: {
|
||||||
|
'Set selection': {
|
||||||
|
target: 'checking selection',
|
||||||
|
actions: 'Set selection',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'checking selection': {
|
||||||
|
always: [
|
||||||
|
{
|
||||||
|
target: 'idle',
|
||||||
|
cond: 'has valid extrude selection',
|
||||||
|
actions: 'AST extrude',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
target: 'idle',
|
||||||
|
actions: 'toast extrude failed',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
initial: 'idle',
|
||||||
|
|
||||||
|
on: {
|
||||||
|
Cancel: {
|
||||||
|
target: 'idle',
|
||||||
|
// TODO what if we're existing extrude equiped, should these actions still be fired?
|
||||||
|
// mabye cancel needs to have a guard for if else logic?
|
||||||
|
actions: [
|
||||||
|
'edit_mode_exit',
|
||||||
|
'default_camera_disable_sketch_mode',
|
||||||
|
'reset sketch metadata',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
guards: {
|
||||||
|
'is editing existing sketch': ({ sketchPathToNode }) =>
|
||||||
|
!!sketchPathToNode,
|
||||||
|
'Can make selection horizontal': ({ selectionRanges }) =>
|
||||||
|
horzVertInfo(selectionRanges, 'horizontal').enabled,
|
||||||
|
'Can make selection vertical': ({ selectionRanges }) =>
|
||||||
|
horzVertInfo(selectionRanges, 'vertical').enabled,
|
||||||
|
'Can constrain horizontal distance': ({ selectionRanges }) =>
|
||||||
|
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
|
||||||
|
.enabled,
|
||||||
|
'Can constrain vertical distance': ({ selectionRanges }) =>
|
||||||
|
horzVertDistanceInfo({ selectionRanges, constraint: 'setVertDistance' })
|
||||||
|
.enabled,
|
||||||
|
'Can constrain angle': ({ selectionRanges }) =>
|
||||||
|
angleBetweenInfo({ selectionRanges }).enabled,
|
||||||
|
'Can constrain length': ({ selectionRanges }) =>
|
||||||
|
setAngleLengthInfo({ selectionRanges }).enabled,
|
||||||
|
'Can constrain horizontally align': ({ selectionRanges }) =>
|
||||||
|
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
|
||||||
|
.enabled,
|
||||||
|
'Can constrain vertically align': ({ selectionRanges }) =>
|
||||||
|
horzVertDistanceInfo({ selectionRanges, constraint: 'setHorzDistance' })
|
||||||
|
.enabled,
|
||||||
|
'Can constrain equal length': ({ selectionRanges }) =>
|
||||||
|
setEqualLengthInfo({ selectionRanges }).enabled,
|
||||||
|
'has no selection': ({ selectionRanges }) => {
|
||||||
|
if (selectionRanges?.codeBasedSelections?.length < 1) return true
|
||||||
|
const selection = selectionRanges?.codeBasedSelections?.[0] || {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectionRanges.codeBasedSelections.length === 1 &&
|
||||||
|
!hasExtrudeSketchGroup({
|
||||||
|
ast: kclManager.ast,
|
||||||
|
programMemory: kclManager.programMemory,
|
||||||
|
selection,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
},
|
||||||
|
'has valid extrude selection': ({ selectionRanges }) => {
|
||||||
|
if (selectionRanges.codeBasedSelections.length !== 1) return false
|
||||||
|
const isSketchPipe = isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
)
|
||||||
|
const common = {
|
||||||
|
selection: selectionRanges.codeBasedSelections[0],
|
||||||
|
ast: kclManager.ast,
|
||||||
|
}
|
||||||
|
const hasClose = doesPipeHaveCallExp({ calleeName: 'close', ...common })
|
||||||
|
const hasExtrude = doesPipeHaveCallExp({
|
||||||
|
calleeName: 'extrude',
|
||||||
|
...common,
|
||||||
|
})
|
||||||
|
return !!isSketchPipe && hasClose && !hasExtrude
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
'Add to code-based selection': assign({
|
||||||
|
selectionRanges: ({ selectionRanges }, event) => ({
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections: [
|
||||||
|
...selectionRanges.codeBasedSelections,
|
||||||
|
event.data,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'Add to other selection': assign({
|
||||||
|
selectionRanges: ({ selectionRanges }, event) => ({
|
||||||
|
...selectionRanges,
|
||||||
|
otherSelections: [...selectionRanges.otherSelections, event.data],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'Remove from code-based selection': assign({
|
||||||
|
selectionRanges: ({ selectionRanges }, event) => ({
|
||||||
|
...selectionRanges,
|
||||||
|
codeBasedSelections: [
|
||||||
|
...selectionRanges.codeBasedSelections,
|
||||||
|
event.data,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'Remove from other selection': assign({
|
||||||
|
selectionRanges: ({ selectionRanges }, event) => ({
|
||||||
|
...selectionRanges,
|
||||||
|
otherSelections: [...selectionRanges.otherSelections, event.data],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'Clear selection': assign({
|
||||||
|
selectionRanges: () => ({
|
||||||
|
otherSelections: [],
|
||||||
|
codeBasedSelections: [],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
'sketch mode enabled': ({ sketchPlaneId }) => {
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'sketch_mode_enable',
|
||||||
|
plane_id: sketchPlaneId,
|
||||||
|
ortho: true,
|
||||||
|
animated: !isReducedMotion(),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'edit mode enter': ({ selectionRanges }) => {
|
||||||
|
const pathId = isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
)
|
||||||
|
pathId &&
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'edit_mode_enter',
|
||||||
|
target: pathId,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'hide default planes': ({}) => {
|
||||||
|
kclManager.hidePlanes()
|
||||||
|
},
|
||||||
|
edit_mode_exit: () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'edit_mode_exit' },
|
||||||
|
}),
|
||||||
|
default_camera_disable_sketch_mode: () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: { type: 'default_camera_disable_sketch_mode' },
|
||||||
|
}),
|
||||||
|
'reset sketch metadata': assign({
|
||||||
|
sketchPathToNode: null,
|
||||||
|
sketchEnginePathId: '',
|
||||||
|
sketchPlaneId: '',
|
||||||
|
}),
|
||||||
|
'set sketch metadata': assign(({ selectionRanges }) => {
|
||||||
|
const sourceRange = selectionRanges.codeBasedSelections[0].range
|
||||||
|
const sketchPathToNode = getNodePathFromSourceRange(
|
||||||
|
kclManager.ast,
|
||||||
|
sourceRange
|
||||||
|
)
|
||||||
|
const pipeExpression = getNodeFromPath<PipeExpression>(
|
||||||
|
kclManager.ast,
|
||||||
|
sketchPathToNode,
|
||||||
|
'PipeExpression'
|
||||||
|
).node
|
||||||
|
if (pipeExpression.type !== 'PipeExpression') return {}
|
||||||
|
const sketchCallExpression = pipeExpression.body.find(
|
||||||
|
(e) =>
|
||||||
|
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
|
||||||
|
) as CallExpression
|
||||||
|
if (!sketchCallExpression) return {}
|
||||||
|
const firstArg = sketchCallExpression.arguments[0]
|
||||||
|
let planeId = ''
|
||||||
|
if (firstArg.type === 'Literal' && firstArg.value) {
|
||||||
|
const planeStrCleaned = firstArg.value
|
||||||
|
.toString()
|
||||||
|
.toLowerCase()
|
||||||
|
.replace('-', '')
|
||||||
|
if (
|
||||||
|
planeStrCleaned === 'xy' ||
|
||||||
|
planeStrCleaned === 'xz' ||
|
||||||
|
planeStrCleaned === 'yz'
|
||||||
|
) {
|
||||||
|
planeId = kclManager.getPlaneId(planeStrCleaned)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('planeId', planeId)
|
||||||
|
|
||||||
|
const sketchEnginePathId =
|
||||||
|
isCursorInSketchCommandRange(
|
||||||
|
engineCommandManager.artifactMap,
|
||||||
|
selectionRanges
|
||||||
|
) || ''
|
||||||
|
return {
|
||||||
|
sketchPathToNode,
|
||||||
|
sketchEnginePathId,
|
||||||
|
sketchPlaneId: planeId,
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'set tool line': () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool: 'sketch_line',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'equip select': () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool: 'select',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'set tool move': () =>
|
||||||
|
engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
type: 'set_tool',
|
||||||
|
tool: 'move',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
'Make selection horizontal': ({ selectionRanges }) => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
|
||||||
|
selectionRanges,
|
||||||
|
'horizontal',
|
||||||
|
kclManager.ast,
|
||||||
|
kclManager.programMemory
|
||||||
|
)
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO re implement cursor shit
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'Make selection vertical': ({ selectionRanges }) => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVert(
|
||||||
|
selectionRanges,
|
||||||
|
'vertical',
|
||||||
|
kclManager.ast,
|
||||||
|
kclManager.programMemory
|
||||||
|
)
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO re implement cursor shit
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'Constrain horizontally align': ({ selectionRanges }) => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
|
||||||
|
selectionRanges,
|
||||||
|
constraint: 'setVertDistance',
|
||||||
|
})
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO re implement cursor shit
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'Constrain vertically align': ({ selectionRanges }) => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = applyConstraintHorzVertAlign({
|
||||||
|
selectionRanges,
|
||||||
|
constraint: 'setHorzDistance',
|
||||||
|
})
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO re implement cursor shit
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'Constrain equal length': ({ selectionRanges }) => {
|
||||||
|
const { modifiedAst, pathToNodeMap } = applyConstraintEqualLength({
|
||||||
|
selectionRanges,
|
||||||
|
})
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
// TODO re implement cursor shit
|
||||||
|
// callBack: updateCursors(setCursor, selectionRanges, pathToNodeMap),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'AST extrude': ({ selectionRanges }) => {
|
||||||
|
const pathToNode = getNodePathFromSourceRange(
|
||||||
|
kclManager.ast,
|
||||||
|
selectionRanges.codeBasedSelections[0].range
|
||||||
|
)
|
||||||
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketch(
|
||||||
|
kclManager.ast,
|
||||||
|
pathToNode
|
||||||
|
)
|
||||||
|
// TODO not handling focusPath correctly I think
|
||||||
|
kclManager.updateAst(modifiedAst, true, {
|
||||||
|
focusPath: pathToExtrudeArg,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'set default plane id': assign({
|
||||||
|
sketchPlaneId: (_, { data }) => data.planeId,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
98
src/machines/modelingMachine.typegen.ts
Normal file
98
src/machines/modelingMachine.typegen.ts
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
// This file was automatically generated. Edits will be overwritten
|
||||||
|
|
||||||
|
export interface Typegen0 {
|
||||||
|
'@@xstate/typegen': true;
|
||||||
|
internalEvents: {
|
||||||
|
"": { type: "" };
|
||||||
|
"done.invoke.get-angle-info": { type: "done.invoke.get-angle-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||||
|
"done.invoke.get-horizontal-info": { type: "done.invoke.get-horizontal-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||||
|
"done.invoke.get-length-info": { type: "done.invoke.get-length-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||||
|
"done.invoke.get-vertical-info": { type: "done.invoke.get-vertical-info"; data: unknown; __tip: "See the XState TS docs to learn how to strongly type this." };
|
||||||
|
"error.platform.get-angle-info": { type: "error.platform.get-angle-info"; data: unknown };
|
||||||
|
"error.platform.get-horizontal-info": { type: "error.platform.get-horizontal-info"; data: unknown };
|
||||||
|
"error.platform.get-length-info": { type: "error.platform.get-length-info"; data: unknown };
|
||||||
|
"error.platform.get-vertical-info": { type: "error.platform.get-vertical-info"; data: unknown };
|
||||||
|
"xstate.init": { type: "xstate.init" };
|
||||||
|
"xstate.stop": { type: "xstate.stop" };
|
||||||
|
};
|
||||||
|
invokeSrcNameMap: {
|
||||||
|
"Get angle info": "done.invoke.get-angle-info";
|
||||||
|
"Get horizontal info": "done.invoke.get-horizontal-info";
|
||||||
|
"Get length info": "done.invoke.get-length-info";
|
||||||
|
"Get vertical info": "done.invoke.get-vertical-info";
|
||||||
|
};
|
||||||
|
missingImplementations: {
|
||||||
|
actions: "AST add line segment" | "AST start new sketch" | "Modify AST" | "Set selection" | "Update code selection cursors" | "create path" | "set tool" | "show default planes" | "sketch exit execute" | "toast extrude failed";
|
||||||
|
delays: never;
|
||||||
|
guards: "Selection contains axis" | "Selection contains edge" | "Selection contains face" | "Selection contains line" | "Selection contains point" | "Selection is not empty" | "Selection is one face";
|
||||||
|
services: "Get angle info" | "Get horizontal info" | "Get length info" | "Get vertical info";
|
||||||
|
};
|
||||||
|
eventsCausingActions: {
|
||||||
|
"AST add line segment": "Add point";
|
||||||
|
"AST extrude": "" | "extrude intent";
|
||||||
|
"AST start new sketch": "Add point";
|
||||||
|
"Add to code-based selection": "Deselect point" | "Deselect segment" | "Select all" | "Select edge" | "Select face" | "Select point" | "Select segment";
|
||||||
|
"Add to other selection": "Select axis";
|
||||||
|
"Clear selection": "Deselect all";
|
||||||
|
"Constrain equal length": "Constrain equal length";
|
||||||
|
"Constrain horizontally align": "Constrain horizontally align";
|
||||||
|
"Constrain vertically align": "Constrain vertically align";
|
||||||
|
"Make selection horizontal": "Make segment horizontal";
|
||||||
|
"Make selection vertical": "Make segment vertical";
|
||||||
|
"Modify AST": "Complete line";
|
||||||
|
"Remove from code-based selection": "Deselect edge" | "Deselect face" | "Deselect point";
|
||||||
|
"Remove from other selection": "Deselect axis";
|
||||||
|
"Set selection": "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info";
|
||||||
|
"Update code selection cursors": "Complete line" | "Deselect all" | "Deselect axis" | "Deselect edge" | "Deselect face" | "Deselect point" | "Deselect segment" | "Select edge" | "Select face" | "Select point" | "Select segment";
|
||||||
|
"create path": "Select default plane";
|
||||||
|
"default_camera_disable_sketch_mode": "Cancel";
|
||||||
|
"edit mode enter": "Enter sketch";
|
||||||
|
"edit_mode_exit": "Cancel";
|
||||||
|
"equip select": "CancelSketch" | "Constrain equal length" | "Constrain horizontally align" | "Constrain vertically align" | "Deselect point" | "Deselect segment" | "Enter sketch" | "Make segment horizontal" | "Make segment vertical" | "Select default plane" | "Select point" | "Select segment" | "Set selection" | "done.invoke.get-angle-info" | "done.invoke.get-horizontal-info" | "done.invoke.get-length-info" | "done.invoke.get-vertical-info" | "error.platform.get-angle-info" | "error.platform.get-horizontal-info" | "error.platform.get-length-info" | "error.platform.get-vertical-info";
|
||||||
|
"hide default planes": "Cancel" | "Select default plane" | "xstate.stop";
|
||||||
|
"reset sketch metadata": "Cancel" | "Select default plane";
|
||||||
|
"set default plane id": "Select default plane";
|
||||||
|
"set sketch metadata": "Enter sketch";
|
||||||
|
"set tool": "Equip new tool";
|
||||||
|
"set tool line": "Equip tool";
|
||||||
|
"set tool move": "Equip move tool";
|
||||||
|
"show default planes": "Enter sketch";
|
||||||
|
"sketch exit execute": "Cancel" | "Complete line" | "xstate.stop";
|
||||||
|
"sketch mode enabled": "Enter sketch" | "Select default plane";
|
||||||
|
"toast extrude failed": "";
|
||||||
|
};
|
||||||
|
eventsCausingDelays: {
|
||||||
|
|
||||||
|
};
|
||||||
|
eventsCausingGuards: {
|
||||||
|
"Can constrain angle": "Constrain angle";
|
||||||
|
"Can constrain equal length": "Constrain equal length";
|
||||||
|
"Can constrain horizontal distance": "Constrain horizontal distance";
|
||||||
|
"Can constrain horizontally align": "Constrain horizontally align";
|
||||||
|
"Can constrain length": "Constrain length";
|
||||||
|
"Can constrain vertical distance": "Constrain vertical distance";
|
||||||
|
"Can constrain vertically align": "Constrain vertically align";
|
||||||
|
"Can make selection horizontal": "Make segment horizontal";
|
||||||
|
"Can make selection vertical": "Make segment vertical";
|
||||||
|
"Selection contains axis": "Deselect axis";
|
||||||
|
"Selection contains edge": "Deselect edge";
|
||||||
|
"Selection contains face": "Deselect face";
|
||||||
|
"Selection contains line": "Deselect segment";
|
||||||
|
"Selection contains point": "Deselect point";
|
||||||
|
"Selection is not empty": "Deselect all";
|
||||||
|
"Selection is one face": "Enter sketch";
|
||||||
|
"has no selection": "extrude intent";
|
||||||
|
"has valid extrude selection": "" | "extrude intent";
|
||||||
|
"is editing existing sketch": "";
|
||||||
|
};
|
||||||
|
eventsCausingServices: {
|
||||||
|
"Get angle info": "Constrain angle";
|
||||||
|
"Get horizontal info": "Constrain horizontal distance";
|
||||||
|
"Get length info": "Constrain length";
|
||||||
|
"Get vertical info": "Constrain vertical distance";
|
||||||
|
};
|
||||||
|
matchesStates: "Sketch" | "Sketch no face" | "Sketch.Await angle info" | "Sketch.Await horizontal distance info" | "Sketch.Await length info" | "Sketch.Await vertical distance info" | "Sketch.Line Tool" | "Sketch.Line Tool.Done" | "Sketch.Line Tool.Init" | "Sketch.Line Tool.No Points" | "Sketch.Line Tool.Point Added" | "Sketch.Line Tool.Segment Added" | "Sketch.Move Tool" | "Sketch.SketchIdle" | "awaiting selection" | "checking selection" | "idle" | { "Sketch"?: "Await angle info" | "Await horizontal distance info" | "Await length info" | "Await vertical distance info" | "Line Tool" | "Move Tool" | "SketchIdle" | { "Line Tool"?: "Done" | "Init" | "No Points" | "Point Added" | "Segment Added"; }; };
|
||||||
|
tags: never;
|
||||||
|
}
|
||||||
|
|
||||||
@ -2,18 +2,15 @@ import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
|||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { useDismiss } from '.'
|
import { useDismiss } from '.'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export default function FutureWork() {
|
export default function FutureWork() {
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const { deferredSetCode } = useStore((s) => ({
|
|
||||||
deferredSetCode: s.deferredSetCode,
|
|
||||||
}))
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
deferredSetCode(bracket)
|
kclManager.setCode(bracket)
|
||||||
}, [deferredSetCode])
|
}, [kclManager.setCode])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionButton } from '../../components/ActionButton'
|
import { ActionButton } from '../../components/ActionButton'
|
||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import {
|
||||||
|
ONBOARDING_PROJECT_NAME,
|
||||||
|
onboardingPaths,
|
||||||
|
useDismiss,
|
||||||
|
useNextClick,
|
||||||
|
} from '.'
|
||||||
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
||||||
import { Themes, getSystemTheme } from 'lib/theme'
|
import { Themes, getSystemTheme } from 'lib/theme'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { bracket } from 'lib/exampleKcl'
|
||||||
import { useStore } from 'useStore'
|
|
||||||
import {
|
import {
|
||||||
createNewProject,
|
createNewProject,
|
||||||
getNextProjectIndex,
|
getNextProjectIndex,
|
||||||
@ -15,24 +19,28 @@ import { isTauri } from 'lib/isTauri'
|
|||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { paths } from 'Router'
|
import { paths } from 'Router'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
function OnboardingWithNewFile() {
|
function OnboardingWithNewFile() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.INDEX)
|
const next = useNextClick(onboardingPaths.INDEX)
|
||||||
const { deferredSetCode } = useStore((s) => ({
|
|
||||||
deferredSetCode: s.deferredSetCode,
|
|
||||||
}))
|
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
context: { defaultDirectory, defaultProjectName },
|
context: { defaultDirectory },
|
||||||
},
|
},
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
|
|
||||||
async function createAndOpenNewProject() {
|
async function createAndOpenNewProject() {
|
||||||
const projects = await getProjectsInDir(defaultDirectory)
|
const projects = await getProjectsInDir(defaultDirectory)
|
||||||
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
|
const nextIndex = await getNextProjectIndex(
|
||||||
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
|
ONBOARDING_PROJECT_NAME,
|
||||||
|
projects
|
||||||
|
)
|
||||||
|
const name = interpolateProjectNameWithIndex(
|
||||||
|
ONBOARDING_PROJECT_NAME,
|
||||||
|
nextIndex
|
||||||
|
)
|
||||||
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||||
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||||
}
|
}
|
||||||
@ -65,7 +73,7 @@ function OnboardingWithNewFile() {
|
|||||||
<ActionButton
|
<ActionButton
|
||||||
Element="button"
|
Element="button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
deferredSetCode(bracket)
|
kclManager.setCode(bracket)
|
||||||
next()
|
next()
|
||||||
}}
|
}}
|
||||||
icon={{ icon: faArrowRight }}
|
icon={{ icon: faArrowRight }}
|
||||||
@ -116,10 +124,6 @@ function OnboardingWithNewFile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Introduction() {
|
export default function Introduction() {
|
||||||
const { deferredSetCode, code } = useStore((s) => ({
|
|
||||||
code: s.code,
|
|
||||||
deferredSetCode: s.deferredSetCode,
|
|
||||||
}))
|
|
||||||
const {
|
const {
|
||||||
settings: {
|
settings: {
|
||||||
state: {
|
state: {
|
||||||
@ -136,10 +140,10 @@ export default function Introduction() {
|
|||||||
const next = useNextClick(onboardingPaths.CAMERA)
|
const next = useNextClick(onboardingPaths.CAMERA)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (code === '') deferredSetCode(bracket)
|
if (kclManager.code === '') kclManager.setCode(bracket)
|
||||||
}, [code, deferredSetCode])
|
}, [kclManager.code, kclManager.setCode])
|
||||||
|
|
||||||
return !(code !== '' && code !== bracket) ? (
|
return !(kclManager.code !== '' && kclManager.code !== bracket) ? (
|
||||||
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
<div className="fixed grid place-content-center inset-0 bg-chalkboard-110/50 z-50">
|
||||||
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
<div className="max-w-3xl bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
|
||||||
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
<h1 className="text-2xl font-bold flex gap-4 flex-wrap items-center">
|
||||||
|
|||||||
@ -3,18 +3,16 @@ import { ActionButton } from '../../components/ActionButton'
|
|||||||
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
import { onboardingPaths, useDismiss, useNextClick } from '.'
|
||||||
import { useStore } from 'useStore'
|
import { useStore } from 'useStore'
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
|
|
||||||
export default function Sketching() {
|
export default function Sketching() {
|
||||||
const { deferredSetCode, buttonDownInStream } = useStore((s) => ({
|
const buttonDownInStream = useStore((s) => s.buttonDownInStream)
|
||||||
deferredSetCode: s.deferredSetCode,
|
|
||||||
buttonDownInStream: s.buttonDownInStream,
|
|
||||||
}))
|
|
||||||
const dismiss = useDismiss()
|
const dismiss = useDismiss()
|
||||||
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
const next = useNextClick(onboardingPaths.FUTURE_WORK)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
deferredSetCode('')
|
kclManager.setCode('')
|
||||||
}, [deferredSetCode])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Outlet, useRouteLoaderData, useNavigate } from 'react-router-dom'
|
import { Outlet, useNavigate } from 'react-router-dom'
|
||||||
import Introduction from './Introduction'
|
import Introduction from './Introduction'
|
||||||
import Camera from './Camera'
|
import Camera from './Camera'
|
||||||
import Sketching from './Sketching'
|
import Sketching from './Sketching'
|
||||||
@ -15,7 +15,10 @@ import UserMenu from './UserMenu'
|
|||||||
import ProjectMenu from './ProjectMenu'
|
import ProjectMenu from './ProjectMenu'
|
||||||
import Export from './Export'
|
import Export from './Export'
|
||||||
import FutureWork from './FutureWork'
|
import FutureWork from './FutureWork'
|
||||||
import { IndexLoaderData, paths } from 'Router'
|
import { paths } from 'Router'
|
||||||
|
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
||||||
|
|
||||||
|
export const ONBOARDING_PROJECT_NAME = 'Tutorial Project $nn'
|
||||||
|
|
||||||
export const onboardingPaths = {
|
export const onboardingPaths = {
|
||||||
INDEX: '/',
|
INDEX: '/',
|
||||||
@ -86,29 +89,23 @@ export const onboardingRoutes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
export function useNextClick(newStatus: string) {
|
export function useNextClick(newStatus: string) {
|
||||||
|
const filePath = useAbsoluteFilePath()
|
||||||
const {
|
const {
|
||||||
settings: { send },
|
settings: { send },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
|
||||||
|
|
||||||
return useCallback(() => {
|
return useCallback(() => {
|
||||||
send({
|
send({
|
||||||
type: 'Set Onboarding Status',
|
type: 'Set Onboarding Status',
|
||||||
data: { onboardingStatus: newStatus },
|
data: { onboardingStatus: newStatus },
|
||||||
})
|
})
|
||||||
navigate(
|
navigate(filePath + paths.ONBOARDING.INDEX.slice(0, -1) + newStatus)
|
||||||
paths.FILE +
|
}, [filePath, newStatus, send, navigate])
|
||||||
'/' +
|
|
||||||
encodeURIComponent(project?.path || 'new') +
|
|
||||||
paths.ONBOARDING.INDEX.slice(0, -1) +
|
|
||||||
newStatus
|
|
||||||
)
|
|
||||||
}, [project, newStatus, send, navigate])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDismiss() {
|
export function useDismiss() {
|
||||||
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
const filePath = useAbsoluteFilePath()
|
||||||
const {
|
const {
|
||||||
settings: { send },
|
settings: { send },
|
||||||
} = useGlobalStateContext()
|
} = useGlobalStateContext()
|
||||||
@ -119,10 +116,8 @@ export function useDismiss() {
|
|||||||
type: 'Set Onboarding Status',
|
type: 'Set Onboarding Status',
|
||||||
data: { onboardingStatus: 'dismissed' },
|
data: { onboardingStatus: 'dismissed' },
|
||||||
})
|
})
|
||||||
navigate(
|
navigate(filePath)
|
||||||
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
|
}, [send, navigate, filePath])
|
||||||
)
|
|
||||||
}, [send, navigate, routeData])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Onboarding = () => {
|
const Onboarding = () => {
|
||||||
|
|||||||
@ -24,11 +24,19 @@ import {
|
|||||||
} from 'lib/cameraControls'
|
} from 'lib/cameraControls'
|
||||||
import { UnitSystem } from 'machines/settingsMachine'
|
import { UnitSystem } from 'machines/settingsMachine'
|
||||||
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
import { useDotDotSlash } from 'hooks/useDotDotSlash'
|
||||||
|
import {
|
||||||
|
createNewProject,
|
||||||
|
getNextProjectIndex,
|
||||||
|
getProjectsInDir,
|
||||||
|
interpolateProjectNameWithIndex,
|
||||||
|
} from 'lib/tauriFS'
|
||||||
|
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const location = useLocation()
|
const location = useLocation()
|
||||||
|
const isFileSettings = location.pathname.includes(paths.FILE)
|
||||||
const dotDotSlash = useDotDotSlash()
|
const dotDotSlash = useDotDotSlash()
|
||||||
useHotkeys('esc', () => navigate(dotDotSlash()))
|
useHotkeys('esc', () => navigate(dotDotSlash()))
|
||||||
const {
|
const {
|
||||||
@ -63,6 +71,33 @@ export const Settings = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restartOnboarding() {
|
||||||
|
send({
|
||||||
|
type: 'Set Onboarding Status',
|
||||||
|
data: { onboardingStatus: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (isFileSettings) {
|
||||||
|
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
||||||
|
} else {
|
||||||
|
createAndOpenNewProject()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createAndOpenNewProject() {
|
||||||
|
const projects = await getProjectsInDir(defaultDirectory)
|
||||||
|
const nextIndex = await getNextProjectIndex(
|
||||||
|
ONBOARDING_PROJECT_NAME,
|
||||||
|
projects
|
||||||
|
)
|
||||||
|
const name = interpolateProjectNameWithIndex(
|
||||||
|
ONBOARDING_PROJECT_NAME,
|
||||||
|
nextIndex
|
||||||
|
)
|
||||||
|
const newFile = await createNewProject(defaultDirectory + '/' + name)
|
||||||
|
navigate(`${paths.FILE}/${encodeURIComponent(newFile.path)}`)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-40 overflow-auto body-bg">
|
<div className="fixed inset-0 z-40 overflow-auto body-bg">
|
||||||
<AppHeader showToolbar={false} project={loaderData?.project}>
|
<AppHeader showToolbar={false} project={loaderData?.project}>
|
||||||
@ -257,26 +292,18 @@ export const Settings = () => {
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
{location.pathname.includes(paths.FILE) && (
|
<SettingsSection
|
||||||
<SettingsSection
|
title="Onboarding"
|
||||||
title="Onboarding"
|
description="Replay the onboarding process"
|
||||||
description="Replay the onboarding process"
|
>
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
onClick={restartOnboarding}
|
||||||
|
icon={{ icon: faArrowRotateBack }}
|
||||||
>
|
>
|
||||||
<ActionButton
|
Replay Onboarding
|
||||||
Element="button"
|
</ActionButton>
|
||||||
onClick={() => {
|
</SettingsSection>
|
||||||
send({
|
|
||||||
type: 'Set Onboarding Status',
|
|
||||||
data: { onboardingStatus: '' },
|
|
||||||
})
|
|
||||||
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
|
|
||||||
}}
|
|
||||||
icon={{ icon: faArrowRotateBack }}
|
|
||||||
>
|
|
||||||
Replay Onboarding
|
|
||||||
</ActionButton>
|
|
||||||
</SettingsSection>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
426
src/useStore.ts
426
src/useStore.ts
@ -5,28 +5,36 @@ import {
|
|||||||
parse,
|
parse,
|
||||||
Program,
|
Program,
|
||||||
_executor,
|
_executor,
|
||||||
recast,
|
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
Position,
|
Position,
|
||||||
PathToNode,
|
PathToNode,
|
||||||
Rotation,
|
Rotation,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
} from './lang/wasm'
|
} from './lang/wasm'
|
||||||
import { getNodeFromPath } from './lang/queryAst'
|
|
||||||
import { enginelessExecutor } from './lib/testHelpers'
|
import { enginelessExecutor } from './lib/testHelpers'
|
||||||
import { EditorSelection } from '@codemirror/state'
|
import { EditorSelection } from '@codemirror/state'
|
||||||
import { EngineCommandManager } from './lang/std/engineConnection'
|
import { EngineCommandManager } from './lang/std/engineConnection'
|
||||||
import { KCLError } from './lang/errors'
|
import { KCLError } from './lang/errors'
|
||||||
import { deferExecution } from 'lib/utils'
|
import { kclManager } from 'lang/KclSinglton'
|
||||||
import { bracket } from 'lib/exampleKcl'
|
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import { engineCommandManager } from './lang/std/engineConnection'
|
|
||||||
|
export type Axis = 'y-axis' | 'x-axis' | 'z-axis'
|
||||||
|
|
||||||
export type Selection = {
|
export type Selection = {
|
||||||
type: 'default' | 'line-end' | 'line-mid'
|
type:
|
||||||
|
| 'default'
|
||||||
|
| 'line-end'
|
||||||
|
| 'line-mid'
|
||||||
|
| 'face'
|
||||||
|
| 'point'
|
||||||
|
| 'edge'
|
||||||
|
| 'line'
|
||||||
|
| 'arc'
|
||||||
|
| 'all'
|
||||||
range: SourceRange
|
range: SourceRange
|
||||||
}
|
}
|
||||||
export type Selections = {
|
export type Selections = {
|
||||||
otherSelections: ('y-axis' | 'x-axis' | 'z-axis')[]
|
otherSelections: Axis[]
|
||||||
codeBasedSelections: Selection[]
|
codeBasedSelections: Selection[]
|
||||||
}
|
}
|
||||||
export type ToolTip =
|
export type ToolTip =
|
||||||
@ -61,54 +69,6 @@ export const toolTips = [
|
|||||||
'angledLineThatIntersects',
|
'angledLineThatIntersects',
|
||||||
] as any as ToolTip[]
|
] as any as ToolTip[]
|
||||||
|
|
||||||
export type GuiModes =
|
|
||||||
| {
|
|
||||||
mode: 'default'
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'sketch'
|
|
||||||
sketchMode: ToolTip
|
|
||||||
isTooltip: true
|
|
||||||
waitingFirstClick: boolean
|
|
||||||
rotation: Rotation
|
|
||||||
position: Position
|
|
||||||
pathId: string
|
|
||||||
pathToNode: PathToNode
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'sketch'
|
|
||||||
sketchMode: 'sketchEdit'
|
|
||||||
rotation: Rotation
|
|
||||||
position: Position
|
|
||||||
pathToNode: PathToNode
|
|
||||||
pathId: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'sketch'
|
|
||||||
sketchMode: 'enterSketchEdit'
|
|
||||||
rotation: Rotation
|
|
||||||
position: Position
|
|
||||||
pathToNode: PathToNode
|
|
||||||
pathId: string
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'sketch'
|
|
||||||
sketchMode: 'selectFace'
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'canEditSketch'
|
|
||||||
pathId: string
|
|
||||||
pathToNode: PathToNode
|
|
||||||
rotation: Rotation
|
|
||||||
position: Position
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
mode: 'canEditExtrude'
|
|
||||||
pathToNode: PathToNode
|
|
||||||
rotation: Rotation
|
|
||||||
position: Position
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PaneType =
|
export type PaneType =
|
||||||
| 'code'
|
| 'code'
|
||||||
| 'variables'
|
| 'variables'
|
||||||
@ -117,50 +77,15 @@ export type PaneType =
|
|||||||
| 'logs'
|
| 'logs'
|
||||||
| 'lspMessages'
|
| 'lspMessages'
|
||||||
|
|
||||||
|
export interface SelectionRangeTypeMap {
|
||||||
|
[key: number]: Selection['type']
|
||||||
|
}
|
||||||
|
|
||||||
export interface StoreState {
|
export interface StoreState {
|
||||||
editorView: EditorView | null
|
editorView: EditorView | null
|
||||||
setEditorView: (editorView: EditorView) => void
|
setEditorView: (editorView: EditorView) => void
|
||||||
highlightRange: [number, number]
|
highlightRange: [number, number]
|
||||||
setHighlightRange: (range: Selection['range']) => void
|
setHighlightRange: (range: Selection['range']) => void
|
||||||
setCursor: (selections: Selections) => void
|
|
||||||
setCursor2: (a?: Selection) => void
|
|
||||||
selectionRanges: Selections
|
|
||||||
selectionRangeTypeMap: { [key: number]: Selection['type'] }
|
|
||||||
setSelectionRanges: (range: Selections) => void
|
|
||||||
guiMode: GuiModes
|
|
||||||
lastGuiMode: GuiModes
|
|
||||||
setGuiMode: (guiMode: GuiModes) => void
|
|
||||||
logs: string[]
|
|
||||||
addLog: (log: string) => void
|
|
||||||
setLogs: (logs: string[]) => void
|
|
||||||
kclErrors: KCLError[]
|
|
||||||
addKCLError: (err: KCLError) => void
|
|
||||||
setErrors: (errors: KCLError[]) => void
|
|
||||||
resetKCLErrors: () => void
|
|
||||||
ast: Program
|
|
||||||
setAst: (ast: Program) => void
|
|
||||||
executeAst: (ast?: Program) => void
|
|
||||||
executeAstMock: (ast?: Program) => void
|
|
||||||
updateAst: (
|
|
||||||
ast: Program,
|
|
||||||
execute: boolean,
|
|
||||||
optionalParams?: {
|
|
||||||
focusPath?: PathToNode
|
|
||||||
callBack?: (ast: Program) => void
|
|
||||||
}
|
|
||||||
) => void
|
|
||||||
updateAstAsync: (
|
|
||||||
ast: Program,
|
|
||||||
reexecute: boolean,
|
|
||||||
focusPath?: PathToNode
|
|
||||||
) => void
|
|
||||||
code: string
|
|
||||||
setCode: (code: string) => void
|
|
||||||
deferredSetCode: (code: string) => void
|
|
||||||
executeCode: (code?: string, force?: boolean) => void
|
|
||||||
formatCode: () => void
|
|
||||||
programMemory: ProgramMemory
|
|
||||||
setProgramMemory: (programMemory: ProgramMemory) => void
|
|
||||||
isShiftDown: boolean
|
isShiftDown: boolean
|
||||||
setIsShiftDown: (isShiftDown: boolean) => void
|
setIsShiftDown: (isShiftDown: boolean) => void
|
||||||
mediaStream?: MediaStream
|
mediaStream?: MediaStream
|
||||||
@ -180,8 +105,6 @@ export interface StoreState {
|
|||||||
streamWidth: number
|
streamWidth: number
|
||||||
streamHeight: number
|
streamHeight: number
|
||||||
}) => void
|
}) => void
|
||||||
isExecuting: boolean
|
|
||||||
setIsExecuting: (isExecuting: boolean) => void
|
|
||||||
|
|
||||||
showHomeMenu: boolean
|
showHomeMenu: boolean
|
||||||
setHomeShowMenu: (showMenu: boolean) => void
|
setHomeShowMenu: (showMenu: boolean) => void
|
||||||
@ -196,18 +119,9 @@ export interface StoreState {
|
|||||||
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
setHomeMenuItems: (items: { name: string; path: string }[]) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
let pendingAstUpdates: number[] = []
|
|
||||||
|
|
||||||
export const useStore = create<StoreState>()(
|
export const useStore = create<StoreState>()(
|
||||||
persist(
|
persist(
|
||||||
(set, get) => {
|
(set, get) => {
|
||||||
// We defer this so that likely our ast has caught up to the code.
|
|
||||||
// If we are making changes that are not reflected in the ast, we
|
|
||||||
// should not be updating the ast.
|
|
||||||
const setDeferredCode = deferExecution((code: string) => {
|
|
||||||
set({ code })
|
|
||||||
get().executeCode(code)
|
|
||||||
}, 600)
|
|
||||||
return {
|
return {
|
||||||
editorView: null,
|
editorView: null,
|
||||||
setEditorView: (editorView) => {
|
setEditorView: (editorView) => {
|
||||||
@ -221,214 +135,6 @@ export const useStore = create<StoreState>()(
|
|||||||
editorView.dispatch({ effects: addLineHighlight.of(selection) })
|
editorView.dispatch({ effects: addLineHighlight.of(selection) })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
executeCode: async (code, force) => {
|
|
||||||
const result = await executeCode({
|
|
||||||
code: code || get().code,
|
|
||||||
lastAst: get().ast,
|
|
||||||
engineCommandManager: engineCommandManager,
|
|
||||||
force,
|
|
||||||
})
|
|
||||||
if (!result.isChange) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
set({
|
|
||||||
ast: result.ast,
|
|
||||||
logs: result.logs,
|
|
||||||
kclErrors: result.errors,
|
|
||||||
programMemory: result.programMemory,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setCursor: (selections) => {
|
|
||||||
const { editorView } = get()
|
|
||||||
if (!editorView) return
|
|
||||||
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
|
||||||
const selectionRangeTypeMap: { [key: number]: Selection['type'] } = {}
|
|
||||||
set({ selectionRangeTypeMap })
|
|
||||||
selections.codeBasedSelections.forEach(({ range, type }) => {
|
|
||||||
if (range?.[1]) {
|
|
||||||
ranges.push(EditorSelection.cursor(range[1]))
|
|
||||||
selectionRangeTypeMap[range[1]] = type
|
|
||||||
}
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
ranges.length &&
|
|
||||||
editorView.dispatch({
|
|
||||||
selection: EditorSelection.create(
|
|
||||||
ranges,
|
|
||||||
selections.codeBasedSelections.length - 1
|
|
||||||
),
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setCursor2: (codeSelections) => {
|
|
||||||
const currestSelections = get().selectionRanges
|
|
||||||
const code = get().code
|
|
||||||
if (!codeSelections) {
|
|
||||||
get().setCursor({
|
|
||||||
otherSelections: currestSelections.otherSelections,
|
|
||||||
codeBasedSelections: [
|
|
||||||
{
|
|
||||||
range: [0, code.length ? code.length - 1 : 0],
|
|
||||||
type: 'default',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const selections: Selections = {
|
|
||||||
...currestSelections,
|
|
||||||
codeBasedSelections: get().isShiftDown
|
|
||||||
? [...currestSelections.codeBasedSelections, codeSelections]
|
|
||||||
: [codeSelections],
|
|
||||||
}
|
|
||||||
get().setCursor(selections)
|
|
||||||
},
|
|
||||||
selectionRangeTypeMap: {},
|
|
||||||
selectionRanges: {
|
|
||||||
otherSelections: [],
|
|
||||||
codeBasedSelections: [],
|
|
||||||
},
|
|
||||||
setSelectionRanges: (selectionRanges) =>
|
|
||||||
set({ selectionRanges, selectionRangeTypeMap: {} }),
|
|
||||||
guiMode: { mode: 'default' },
|
|
||||||
lastGuiMode: { mode: 'default' },
|
|
||||||
setGuiMode: (guiMode) => {
|
|
||||||
set({ guiMode })
|
|
||||||
},
|
|
||||||
logs: [],
|
|
||||||
addLog: (log) => {
|
|
||||||
if (Array.isArray(log)) {
|
|
||||||
const cleanLog: any = log.map(({ __geoMeta, ...rest }) => rest)
|
|
||||||
set((state) => ({ logs: [...state.logs, cleanLog] }))
|
|
||||||
} else {
|
|
||||||
set((state) => ({ logs: [...state.logs, log] }))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setLogs: (logs) => {
|
|
||||||
set({ logs })
|
|
||||||
},
|
|
||||||
kclErrors: [],
|
|
||||||
addKCLError: (e) => {
|
|
||||||
set((state) => ({ kclErrors: [...state.kclErrors, e] }))
|
|
||||||
},
|
|
||||||
resetKCLErrors: () => {
|
|
||||||
set({ kclErrors: [] })
|
|
||||||
},
|
|
||||||
setErrors: (errors) => {
|
|
||||||
set({ kclErrors: errors })
|
|
||||||
},
|
|
||||||
ast: {
|
|
||||||
start: 0,
|
|
||||||
end: 0,
|
|
||||||
body: [],
|
|
||||||
nonCodeMeta: {
|
|
||||||
nonCodeNodes: {},
|
|
||||||
start: null,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setAst: (ast) => {
|
|
||||||
set({ ast })
|
|
||||||
},
|
|
||||||
executeAst: async (ast) => {
|
|
||||||
const _ast = ast || get().ast
|
|
||||||
if (!get().isStreamReady) return
|
|
||||||
|
|
||||||
set({ isExecuting: true })
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
|
||||||
ast: _ast,
|
|
||||||
engineCommandManager,
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
programMemory,
|
|
||||||
logs,
|
|
||||||
kclErrors: errors,
|
|
||||||
isExecuting: false,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
executeAstMock: async (ast) => {
|
|
||||||
const _ast = ast || get().ast
|
|
||||||
if (!get().isStreamReady) return
|
|
||||||
|
|
||||||
const { logs, errors, programMemory } = await executeAst({
|
|
||||||
ast: _ast,
|
|
||||||
engineCommandManager,
|
|
||||||
useFakeExecutor: true,
|
|
||||||
})
|
|
||||||
set({
|
|
||||||
programMemory,
|
|
||||||
logs,
|
|
||||||
kclErrors: errors,
|
|
||||||
isExecuting: false,
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updateAst: async (
|
|
||||||
ast,
|
|
||||||
reexecute,
|
|
||||||
{ focusPath, callBack = () => {} } = {}
|
|
||||||
) => {
|
|
||||||
const newCode = recast(ast)
|
|
||||||
const astWithUpdatedSource = parse(newCode)
|
|
||||||
callBack(astWithUpdatedSource)
|
|
||||||
|
|
||||||
set({
|
|
||||||
ast: astWithUpdatedSource,
|
|
||||||
code: newCode,
|
|
||||||
})
|
|
||||||
if (focusPath) {
|
|
||||||
const { node } = getNodeFromPath<any>(
|
|
||||||
astWithUpdatedSource,
|
|
||||||
focusPath
|
|
||||||
)
|
|
||||||
const { start, end } = node
|
|
||||||
if (!start || !end) return
|
|
||||||
setTimeout(() => {
|
|
||||||
get().setCursor({
|
|
||||||
codeBasedSelections: [
|
|
||||||
{
|
|
||||||
type: 'default',
|
|
||||||
range: [start, end],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
otherSelections: [],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reexecute) {
|
|
||||||
// Call execute on the set ast.
|
|
||||||
get().executeAst(astWithUpdatedSource)
|
|
||||||
} else {
|
|
||||||
// When we don't re-execute, we still want to update the program
|
|
||||||
// memory with the new ast. So we will hit the mock executor
|
|
||||||
// instead.
|
|
||||||
get().executeAstMock(astWithUpdatedSource)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
updateAstAsync: async (ast, reexecute, focusPath) => {
|
|
||||||
// clear any pending updates
|
|
||||||
pendingAstUpdates.forEach((id) => clearTimeout(id))
|
|
||||||
pendingAstUpdates = []
|
|
||||||
// setup a new update
|
|
||||||
pendingAstUpdates.push(
|
|
||||||
setTimeout(() => {
|
|
||||||
get().updateAst(ast, reexecute, { focusPath })
|
|
||||||
}, 100) as unknown as number
|
|
||||||
)
|
|
||||||
},
|
|
||||||
code: bracket,
|
|
||||||
setCode: (code) => set({ code }),
|
|
||||||
deferredSetCode: (code) => {
|
|
||||||
set({ code })
|
|
||||||
setDeferredCode(code)
|
|
||||||
},
|
|
||||||
formatCode: async () => {
|
|
||||||
const code = get().code
|
|
||||||
const ast = parse(code)
|
|
||||||
const newCode = recast(ast)
|
|
||||||
set({ code: newCode, ast })
|
|
||||||
},
|
|
||||||
programMemory: { root: {}, return: null },
|
|
||||||
setProgramMemory: (programMemory) => set({ programMemory }),
|
|
||||||
isShiftDown: false,
|
isShiftDown: false,
|
||||||
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
setIsShiftDown: (isShiftDown) => set({ isShiftDown }),
|
||||||
setMediaStream: (mediaStream) => set({ mediaStream }),
|
setMediaStream: (mediaStream) => set({ mediaStream }),
|
||||||
@ -451,8 +157,6 @@ export const useStore = create<StoreState>()(
|
|||||||
setStreamDimensions: (streamDimensions) => {
|
setStreamDimensions: (streamDimensions) => {
|
||||||
set({ streamDimensions })
|
set({ streamDimensions })
|
||||||
},
|
},
|
||||||
isExecuting: false,
|
|
||||||
setIsExecuting: (isExecuting) => set({ isExecuting }),
|
|
||||||
|
|
||||||
// tauri specific app settings
|
// tauri specific app settings
|
||||||
defaultDir: {
|
defaultDir: {
|
||||||
@ -472,9 +176,7 @@ export const useStore = create<StoreState>()(
|
|||||||
name: 'store',
|
name: 'store',
|
||||||
partialize: (state) =>
|
partialize: (state) =>
|
||||||
Object.fromEntries(
|
Object.fromEntries(
|
||||||
Object.entries(state).filter(([key]) =>
|
Object.entries(state).filter(([key]) => ['openPanes'].includes(key))
|
||||||
['code', 'openPanes'].includes(key)
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -508,15 +210,17 @@ const defaultProgramMemory: ProgramMemory['root'] = {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeCode({
|
export async function executeCode({
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
code,
|
code,
|
||||||
lastAst,
|
lastAst,
|
||||||
|
defaultPlanes,
|
||||||
force,
|
force,
|
||||||
}: {
|
}: {
|
||||||
code: string
|
code: string
|
||||||
lastAst: Program
|
lastAst: Program
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
defaultPlanes: DefaultPlanes
|
||||||
force?: boolean
|
force?: boolean
|
||||||
}): Promise<
|
}): Promise<
|
||||||
| {
|
| {
|
||||||
@ -566,6 +270,7 @@ async function executeCode({
|
|||||||
const { logs, errors, programMemory } = await executeAst({
|
const { logs, errors, programMemory } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
|
defaultPlanes,
|
||||||
})
|
})
|
||||||
return {
|
return {
|
||||||
ast,
|
ast,
|
||||||
@ -576,13 +281,15 @@ async function executeCode({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function executeAst({
|
export async function executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
|
defaultPlanes,
|
||||||
useFakeExecutor = false,
|
useFakeExecutor = false,
|
||||||
}: {
|
}: {
|
||||||
ast: Program
|
ast: Program
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
defaultPlanes: DefaultPlanes
|
||||||
useFakeExecutor?: boolean
|
useFakeExecutor?: boolean
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
logs: string[]
|
logs: string[]
|
||||||
@ -605,7 +312,8 @@ async function executeAst({
|
|||||||
root: defaultProgramMemory,
|
root: defaultProgramMemory,
|
||||||
return: null,
|
return: null,
|
||||||
},
|
},
|
||||||
engineCommandManager
|
engineCommandManager,
|
||||||
|
defaultPlanes
|
||||||
))
|
))
|
||||||
|
|
||||||
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
await engineCommandManager.waitForAllCommands(ast, programMemory)
|
||||||
@ -637,3 +345,79 @@ async function executeAst({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dispatchCodeMirrorCursor({
|
||||||
|
selections,
|
||||||
|
editorView,
|
||||||
|
}: {
|
||||||
|
selections: Selections
|
||||||
|
editorView: EditorView
|
||||||
|
}): {
|
||||||
|
selectionRangeTypeMap: SelectionRangeTypeMap
|
||||||
|
} {
|
||||||
|
const ranges: ReturnType<typeof EditorSelection.cursor>[] = []
|
||||||
|
const selectionRangeTypeMap: SelectionRangeTypeMap = {}
|
||||||
|
selections.codeBasedSelections.forEach(({ range, type }) => {
|
||||||
|
if (range?.[1]) {
|
||||||
|
ranges.push(EditorSelection.cursor(range[1]))
|
||||||
|
selectionRangeTypeMap[range[1]] = type
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
ranges.length &&
|
||||||
|
editorView.dispatch({
|
||||||
|
selection: EditorSelection.create(
|
||||||
|
ranges,
|
||||||
|
selections.codeBasedSelections.length - 1
|
||||||
|
),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
selectionRangeTypeMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setCodeMirrorCursor({
|
||||||
|
codeSelection,
|
||||||
|
currestSelections,
|
||||||
|
editorView,
|
||||||
|
isShiftDown,
|
||||||
|
}: {
|
||||||
|
codeSelection?: Selection
|
||||||
|
currestSelections: Selections
|
||||||
|
editorView: EditorView
|
||||||
|
isShiftDown: boolean
|
||||||
|
}): SelectionRangeTypeMap {
|
||||||
|
// This DOES NOT set the `selectionRanges` in xstate context
|
||||||
|
// instead it updates/dispatches to the editor, which in turn updates the xstate context
|
||||||
|
// I've found this the best way to deal with the editor without causing an infinite loop
|
||||||
|
// and really we want the editor to be in charge of cursor positions and for `selectionRanges` mirror it
|
||||||
|
// because we want to respect the user manually placing the cursor too.
|
||||||
|
const code = kclManager.code
|
||||||
|
if (!codeSelection) {
|
||||||
|
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
|
||||||
|
editorView,
|
||||||
|
selections: {
|
||||||
|
otherSelections: currestSelections.otherSelections,
|
||||||
|
codeBasedSelections: [
|
||||||
|
{
|
||||||
|
range: [0, code.length ? code.length - 1 : 0],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return selectionRangeTypeMap
|
||||||
|
}
|
||||||
|
const selections: Selections = {
|
||||||
|
...currestSelections,
|
||||||
|
codeBasedSelections: isShiftDown
|
||||||
|
? [...currestSelections.codeBasedSelections, codeSelection]
|
||||||
|
: [codeSelection],
|
||||||
|
}
|
||||||
|
const selectionRangeTypeMap = dispatchCodeMirrorCursor({
|
||||||
|
editorView,
|
||||||
|
selections,
|
||||||
|
})
|
||||||
|
return selectionRangeTypeMap
|
||||||
|
}
|
||||||
|
|||||||
62
src/wasm-lib/Cargo.lock
generated
62
src/wasm-lib/Cargo.lock
generated
@ -156,7 +156,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -167,7 +167,7 @@ checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -472,7 +472,7 @@ dependencies = [
|
|||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -692,7 +692,7 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -706,7 +706,7 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_tokenstream",
|
"serde_tokenstream",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -976,7 +976,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1390,7 +1390,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
@ -1426,9 +1426,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kittycad"
|
name = "kittycad"
|
||||||
version = "0.2.28"
|
version = "0.2.31"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "35b2f9302648dbb06fd7121687f9505fc3179eba84111a06d76b246e3158f5dc"
|
checksum = "539b323537b877fc8dd130362b8f1af9af8051c19208bb8bfd816ab7c330f2bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@ -1733,7 +1733,7 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "openapitor"
|
name = "openapitor"
|
||||||
version = "0.0.9"
|
version = "0.0.9"
|
||||||
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#fa0345c514fcc9ae6cd74ae35c8e5c2800fec34f"
|
source = "git+https://github.com/KittyCAD/kittycad.rs?branch=main#c122a9b1d6afe51c25e545b5e0bbeb91d367e6d2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1879,7 +1879,7 @@ dependencies = [
|
|||||||
"regex",
|
"regex",
|
||||||
"regex-syntax 0.7.5",
|
"regex-syntax 0.7.5",
|
||||||
"structmeta",
|
"structmeta",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1914,7 +1914,7 @@ dependencies = [
|
|||||||
"bincode",
|
"bincode",
|
||||||
"either",
|
"either",
|
||||||
"fnv",
|
"fnv",
|
||||||
"itertools 0.11.0",
|
"itertools 0.10.5",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"nom",
|
"nom",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
@ -1943,7 +1943,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2217,9 +2217,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.21"
|
version = "0.11.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "78fdbab6a7e1d7b13cc8ff10197f47986b41c639300cc3c8158cac7847c9bbef"
|
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.4",
|
"base64 0.21.4",
|
||||||
"bytes",
|
"bytes",
|
||||||
@ -2573,7 +2573,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2607,7 +2607,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2619,7 +2619,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"serde",
|
"serde",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2836,7 +2836,7 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"structmeta-derive",
|
"structmeta-derive",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2847,7 +2847,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2885,9 +2885,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.37"
|
version = "2.0.38"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -2996,7 +2996,7 @@ checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3102,7 +3102,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3218,7 +3218,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3247,7 +3247,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3333,7 +3333,7 @@ dependencies = [
|
|||||||
"Inflector",
|
"Inflector",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
"termcolor",
|
"termcolor",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3535,7 +3535,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3570,7 +3570,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.38",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
@ -3830,9 +3830,9 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.15"
|
version = "0.5.16"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
|
checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -11,7 +11,7 @@ crate-type = ["cdylib"]
|
|||||||
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||||
gloo-utils = "0.2.0"
|
gloo-utils = "0.2.0"
|
||||||
kcl-lib = { path = "kcl" }
|
kcl-lib = { path = "kcl" }
|
||||||
kittycad = { version = "0.2.27", default-features = false, features = ["js"] }
|
kittycad = { version = "0.2.31", default-features = false, features = ["js"] }
|
||||||
serde_json = "1.0.107"
|
serde_json = "1.0.107"
|
||||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
wasm-bindgen = "0.2.87"
|
wasm-bindgen = "0.2.87"
|
||||||
@ -20,9 +20,9 @@ wasm-bindgen-futures = "0.4.37"
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
image = "0.24.7"
|
image = "0.24.7"
|
||||||
kittycad = "0.2.27"
|
kittycad = "0.2.31"
|
||||||
pretty_assertions = "1.4.0"
|
pretty_assertions = "1.4.0"
|
||||||
reqwest = { version = "0.11.21", default-features = false }
|
reqwest = { version = "0.11.22", default-features = false }
|
||||||
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||||
twenty-twenty = "0.6.1"
|
twenty-twenty = "0.6.1"
|
||||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
|
|||||||
@ -16,7 +16,7 @@ proc-macro2 = "1"
|
|||||||
quote = "1"
|
quote = "1"
|
||||||
serde = { version = "1.0.188", features = ["derive"] }
|
serde = { version = "1.0.188", features = ["derive"] }
|
||||||
serde_tokenstream = "0.2"
|
serde_tokenstream = "0.2"
|
||||||
syn = { version = "2.0.37", features = ["full"] }
|
syn = { version = "2.0.38", features = ["full"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
expectorate = "1.1.0"
|
expectorate = "1.1.0"
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language"
|
description = "KittyCAD Language"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ clap = { version = "4.4.6", features = ["cargo", "derive", "env", "unicode"], op
|
|||||||
dashmap = "5.5.3"
|
dashmap = "5.5.3"
|
||||||
derive-docs = { version = "0.1.4" }
|
derive-docs = { version = "0.1.4" }
|
||||||
#derive-docs = { path = "../derive-docs" }
|
#derive-docs = { path = "../derive-docs" }
|
||||||
kittycad = { version = "0.2.27", default-features = false, features = ["js"] }
|
kittycad = { version = "0.2.31", default-features = false, features = ["js"] }
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
parse-display = "0.8.2"
|
parse-display = "0.8.2"
|
||||||
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
|
||||||
@ -24,7 +24,7 @@ serde_json = "1.0.107"
|
|||||||
thiserror = "1.0.49"
|
thiserror = "1.0.49"
|
||||||
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
|
ts-rs = { version = "7", package = "ts-rs-json-value", features = ["serde-json-impl", "schemars-impl", "uuid-impl"] }
|
||||||
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
|
||||||
winnow = "0.5.15"
|
winnow = "0.5.16"
|
||||||
|
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
js-sys = { version = "0.3.64" }
|
js-sys = { version = "0.3.64" }
|
||||||
@ -36,7 +36,7 @@ web-sys = { version = "0.3.64", features = ["console"] }
|
|||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
|
||||||
futures = { version = "0.3.28" }
|
futures = { version = "0.3.28" }
|
||||||
reqwest = { version = "0.11.21", default-features = false }
|
reqwest = { version = "0.11.22", default-features = false }
|
||||||
tokio = { version = "1.32.0", features = ["full"] }
|
tokio = { version = "1.32.0", features = ["full"] }
|
||||||
tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] }
|
tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] }
|
||||||
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
tower-lsp = { version = "0.20.0", features = ["proposed"] }
|
||||||
|
|||||||
@ -6,36 +6,30 @@ pub fn bench_lex(c: &mut Criterion) {
|
|||||||
c.bench_function("lex_pipes_on_pipes", |b| b.iter(|| lex(PIPES_PROGRAM)));
|
c.bench_function("lex_pipes_on_pipes", |b| b.iter(|| lex(PIPES_PROGRAM)));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bench_lex_parse(c: &mut Criterion) {
|
pub fn bench_parse(c: &mut Criterion) {
|
||||||
c.bench_function("parse_lex_cube", |b| b.iter(|| lex_and_parse(CUBE_PROGRAM)));
|
for (name, file) in [
|
||||||
c.bench_function("parse_lex_big_kitt", |b| b.iter(|| lex_and_parse(KITT_PROGRAM)));
|
("pipes_on_pipes", PIPES_PROGRAM),
|
||||||
c.bench_function("parse_lex_pipes_on_pipes", |b| b.iter(|| lex_and_parse(PIPES_PROGRAM)));
|
("big_kitt", KITT_PROGRAM),
|
||||||
|
("cube", CUBE_PROGRAM),
|
||||||
|
] {
|
||||||
|
let tokens = kcl_lib::token::lexer(file);
|
||||||
|
c.bench_function(&format!("parse_{name}"), move |b| {
|
||||||
|
let tok = tokens.clone();
|
||||||
|
b.iter(move || {
|
||||||
|
let parser = kcl_lib::parser::Parser::new(tok.clone());
|
||||||
|
black_box(parser.ast().unwrap());
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex(program: &str) {
|
fn lex(program: &str) {
|
||||||
black_box(kcl_lib::token::lexer(program));
|
black_box(kcl_lib::token::lexer(program));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lex_and_parse(program: &str) {
|
criterion_group!(benches, bench_lex, bench_parse);
|
||||||
let tokens = kcl_lib::token::lexer(program);
|
|
||||||
let parser = kcl_lib::parser::Parser::new(tokens);
|
|
||||||
black_box(parser.ast().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_lex, bench_lex_parse);
|
|
||||||
criterion_main!(benches);
|
criterion_main!(benches);
|
||||||
|
|
||||||
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
|
||||||
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||||
const CUBE_PROGRAM: &str = r#"fn cube = (pos, scale) => {
|
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
|
||||||
const sg = startSketchAt(pos)
|
|
||||||
|> line([0, scale], %)
|
|
||||||
|> line([scale, 0], %)
|
|
||||||
|> line([0, -scale], %)
|
|
||||||
|
|
||||||
return sg
|
|
||||||
}
|
|
||||||
|
|
||||||
const b1 = cube([0,0], 10)
|
|
||||||
const pt1 = b1[0]
|
|
||||||
show(b1)"#;
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
enum-variant-size-threshold = 24
|
enum-variant-size-threshold = 48
|
||||||
|
|||||||
@ -31,6 +31,8 @@ pub async fn modify_ast_for_sketch(
|
|||||||
program: &mut Program,
|
program: &mut Program,
|
||||||
// The name of the sketch.
|
// The name of the sketch.
|
||||||
sketch_name: &str,
|
sketch_name: &str,
|
||||||
|
// The type of plane the sketch is on. `XY` or `XZ`, etc
|
||||||
|
plane: crate::executor::PlaneType,
|
||||||
// The ID of the parent sketch.
|
// The ID of the parent sketch.
|
||||||
sketch_id: uuid::Uuid,
|
sketch_id: uuid::Uuid,
|
||||||
) -> Result<String, KclError> {
|
) -> Result<String, KclError> {
|
||||||
@ -153,10 +155,11 @@ pub async fn modify_ast_for_sketch(
|
|||||||
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
y: (first_control_points.points[1].y - first_control_points.points[0].y),
|
||||||
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
z: (first_control_points.points[1].z - first_control_points.points[0].z),
|
||||||
};
|
};
|
||||||
let sketch = create_start_sketch_at(
|
let sketch = create_start_sketch_on(
|
||||||
sketch_name,
|
sketch_name,
|
||||||
[first_control_points.points[0].x, first_control_points.points[0].y],
|
[first_control_points.points[0].x, first_control_points.points[0].y],
|
||||||
[start_sketch_at_end.x, start_sketch_at_end.y],
|
[start_sketch_at_end.x, start_sketch_at_end.y],
|
||||||
|
plane,
|
||||||
additional_lines,
|
additional_lines,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
@ -174,19 +177,24 @@ pub async fn modify_ast_for_sketch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
/// Create a pipe expression that starts a sketch at the given point and draws a line to the given point.
|
||||||
fn create_start_sketch_at(
|
fn create_start_sketch_on(
|
||||||
name: &str,
|
name: &str,
|
||||||
start: [f64; 2],
|
start: [f64; 2],
|
||||||
end: [f64; 2],
|
end: [f64; 2],
|
||||||
|
plane: crate::executor::PlaneType,
|
||||||
additional_lines: Vec<[f64; 2]>,
|
additional_lines: Vec<[f64; 2]>,
|
||||||
) -> Result<VariableDeclarator, KclError> {
|
) -> Result<VariableDeclarator, KclError> {
|
||||||
let start_sketch_at = CallExpression::new(
|
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;
|
||||||
"startSketchAt",
|
let start_profile_at = CallExpression::new(
|
||||||
vec![ArrayExpression::new(vec![
|
"startProfileAt",
|
||||||
Literal::new(round_before_recast(start[0]).into()).into(),
|
vec![
|
||||||
Literal::new(round_before_recast(start[1]).into()).into(),
|
ArrayExpression::new(vec![
|
||||||
])
|
Literal::new(round_before_recast(start[0]).into()).into(),
|
||||||
.into()],
|
Literal::new(round_before_recast(start[1]).into()).into(),
|
||||||
|
])
|
||||||
|
.into(),
|
||||||
|
PipeSubstitution::new().into(),
|
||||||
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Keep track of where we are so we can close the sketch if we need to.
|
// Keep track of where we are so we can close the sketch if we need to.
|
||||||
@ -209,7 +217,7 @@ fn create_start_sketch_at(
|
|||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut pipe_body = vec![start_sketch_at.into(), initial_line.into()];
|
let mut pipe_body = vec![start_sketch_on.into(), start_profile_at.into(), initial_line.into()];
|
||||||
|
|
||||||
for (index, line) in additional_lines.iter().enumerate() {
|
for (index, line) in additional_lines.iter().enumerate() {
|
||||||
current_position.x += line[0];
|
current_position.x += line[0];
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Data types for the AST.
|
//! Data types for the AST.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, fmt::Write};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use parse_display::{Display, FromStr};
|
use parse_display::{Display, FromStr};
|
||||||
@ -10,9 +10,8 @@ use serde_json::Map;
|
|||||||
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::EngineConnection,
|
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
executor::{ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
|
||||||
parser::PIPE_OPERATOR,
|
parser::PIPE_OPERATOR,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -29,73 +28,77 @@ pub struct Program {
|
|||||||
impl Program {
|
impl Program {
|
||||||
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||||
let indentation = options.get_indentation(indentation_level);
|
let indentation = options.get_indentation(indentation_level);
|
||||||
let result = self
|
let result =
|
||||||
.body
|
self.body
|
||||||
.iter()
|
.iter()
|
||||||
.map(|statement| match statement.clone() {
|
.map(|statement| match statement.clone() {
|
||||||
BodyItem::ExpressionStatement(expression_statement) => {
|
BodyItem::ExpressionStatement(expression_statement) => {
|
||||||
expression_statement
|
expression_statement
|
||||||
.expression
|
.expression
|
||||||
.recast(options, indentation_level, false)
|
.recast(options, indentation_level, false)
|
||||||
}
|
|
||||||
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
|
|
||||||
.declarations
|
|
||||||
.iter()
|
|
||||||
.map(|declaration| {
|
|
||||||
format!(
|
|
||||||
"{}{} {} = {}",
|
|
||||||
indentation,
|
|
||||||
variable_declaration.kind,
|
|
||||||
declaration.id.name,
|
|
||||||
declaration.init.recast(options, 0, false)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<String>(),
|
|
||||||
BodyItem::ReturnStatement(return_statement) => {
|
|
||||||
format!(
|
|
||||||
"{}return {}",
|
|
||||||
indentation,
|
|
||||||
return_statement.argument.recast(options, 0, false)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.enumerate()
|
|
||||||
.map(|(index, recast_str)| {
|
|
||||||
let start_string = if index == 0 {
|
|
||||||
// We need to indent.
|
|
||||||
if let Some(start) = self.non_code_meta.start.clone() {
|
|
||||||
start.format(&indentation)
|
|
||||||
} else {
|
|
||||||
indentation.to_string()
|
|
||||||
}
|
}
|
||||||
} else {
|
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
|
||||||
// Do nothing, we already applied the indentation elsewhere.
|
.declarations
|
||||||
String::new()
|
.iter()
|
||||||
};
|
.fold(String::new(), |mut output, declaration| {
|
||||||
|
let _ = write!(
|
||||||
|
output,
|
||||||
|
"{}{} {} = {}",
|
||||||
|
indentation,
|
||||||
|
variable_declaration.kind,
|
||||||
|
declaration.id.name,
|
||||||
|
declaration.init.recast(options, 0, false)
|
||||||
|
);
|
||||||
|
output
|
||||||
|
}),
|
||||||
|
BodyItem::ReturnStatement(return_statement) => {
|
||||||
|
format!(
|
||||||
|
"{}return {}",
|
||||||
|
indentation,
|
||||||
|
return_statement.argument.recast(options, 0, false)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.enumerate()
|
||||||
|
.fold(String::new(), |mut output, (index, recast_str)| {
|
||||||
|
let start_string = if index == 0 {
|
||||||
|
// We need to indent.
|
||||||
|
if let Some(start) = self.non_code_meta.start.clone() {
|
||||||
|
start.format(&indentation)
|
||||||
|
} else {
|
||||||
|
indentation.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Do nothing, we already applied the indentation elsewhere.
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
// determine the value of the end string
|
// determine the value of the end string
|
||||||
// basically if we are inside a nested function we want to end with a new line
|
// basically if we are inside a nested function we want to end with a new line
|
||||||
let maybe_line_break: String = if index == self.body.len() - 1 && indentation_level == 0 {
|
let maybe_line_break: String = if index == self.body.len() - 1 && indentation_level == 0 {
|
||||||
String::new()
|
String::new()
|
||||||
} else {
|
} else {
|
||||||
"\n".to_string()
|
"\n".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
|
let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
|
||||||
Some(custom_white_space_or_comment) => custom_white_space_or_comment.format(&indentation),
|
Some(noncodes) => noncodes
|
||||||
None => String::new(),
|
.iter()
|
||||||
};
|
.map(|custom_white_space_or_comment| custom_white_space_or_comment.format(&indentation))
|
||||||
let end_string = if custom_white_space_or_comment.is_empty() {
|
.collect::<String>(),
|
||||||
maybe_line_break
|
None => String::new(),
|
||||||
} else {
|
};
|
||||||
custom_white_space_or_comment
|
let end_string = if custom_white_space_or_comment.is_empty() {
|
||||||
};
|
maybe_line_break
|
||||||
|
} else {
|
||||||
|
custom_white_space_or_comment
|
||||||
|
};
|
||||||
|
|
||||||
format!("{}{}{}", start_string, recast_str, end_string)
|
let _ = write!(output, "{}{}{}", start_string, recast_str, end_string);
|
||||||
})
|
output
|
||||||
.collect::<String>()
|
})
|
||||||
.trim()
|
.trim()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
// Insert a final new line if the user wants it.
|
// Insert a final new line if the user wants it.
|
||||||
if options.insert_final_newline {
|
if options.insert_final_newline {
|
||||||
@ -631,7 +634,7 @@ impl BinaryPart {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
// stop the execution of the pipe.
|
// stop the execution of the pipe.
|
||||||
@ -646,13 +649,13 @@ impl BinaryPart {
|
|||||||
Ok(value.clone())
|
Ok(value.clone())
|
||||||
}
|
}
|
||||||
BinaryPart::BinaryExpression(binary_expression) => {
|
BinaryPart::BinaryExpression(binary_expression) => {
|
||||||
binary_expression.get_result(memory, &mut new_pipe_info, engine).await
|
binary_expression.get_result(memory, &mut new_pipe_info, ctx).await
|
||||||
}
|
}
|
||||||
BinaryPart::CallExpression(call_expression) => {
|
BinaryPart::CallExpression(call_expression) => {
|
||||||
call_expression.execute(memory, &mut new_pipe_info, engine).await
|
call_expression.execute(memory, &mut new_pipe_info, ctx).await
|
||||||
}
|
}
|
||||||
BinaryPart::UnaryExpression(unary_expression) => {
|
BinaryPart::UnaryExpression(unary_expression) => {
|
||||||
unary_expression.get_result(memory, &mut new_pipe_info, engine).await
|
unary_expression.get_result(memory, &mut new_pipe_info, ctx).await
|
||||||
}
|
}
|
||||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
|
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(memory),
|
||||||
}
|
}
|
||||||
@ -707,30 +710,35 @@ pub struct NonCodeNode {
|
|||||||
impl NonCodeNode {
|
impl NonCodeNode {
|
||||||
pub fn value(&self) -> String {
|
pub fn value(&self) -> String {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
NonCodeValue::InlineComment { value } => value.clone(),
|
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
|
||||||
NonCodeValue::BlockComment { value } => value.clone(),
|
NonCodeValue::BlockComment { value, style: _ } => value.clone(),
|
||||||
NonCodeValue::NewLineBlockComment { value } => value.clone(),
|
NonCodeValue::NewLineBlockComment { value, style: _ } => value.clone(),
|
||||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(&self, indentation: &str) -> String {
|
pub fn format(&self, indentation: &str) -> String {
|
||||||
match &self.value {
|
match &self.value {
|
||||||
NonCodeValue::InlineComment { value } => format!(" // {}\n", value),
|
NonCodeValue::InlineComment {
|
||||||
NonCodeValue::BlockComment { value } => {
|
value,
|
||||||
|
style: CommentStyle::Line,
|
||||||
|
} => format!(" // {}\n", value),
|
||||||
|
NonCodeValue::InlineComment {
|
||||||
|
value,
|
||||||
|
style: CommentStyle::Block,
|
||||||
|
} => format!(" /* {} */", value),
|
||||||
|
NonCodeValue::BlockComment { value, style } => {
|
||||||
let add_start_new_line = if self.start == 0 { "" } else { "\n" };
|
let add_start_new_line = if self.start == 0 { "" } else { "\n" };
|
||||||
if value.contains('\n') {
|
match style {
|
||||||
format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
|
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
||||||
} else {
|
CommentStyle::Line => format!("{}{}// {}\n", add_start_new_line, indentation, value),
|
||||||
format!("{}{}// {}\n", add_start_new_line, indentation, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NonCodeValue::NewLineBlockComment { value } => {
|
NonCodeValue::NewLineBlockComment { value, style } => {
|
||||||
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
|
||||||
if value.contains('\n') {
|
match style {
|
||||||
format!("{}{}/* {} */\n", add_start_new_line, indentation, value)
|
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
|
||||||
} else {
|
CommentStyle::Line => format!("{}{}// {}\n", add_start_new_line, indentation, value),
|
||||||
format!("{}{}// {}\n", add_start_new_line, indentation, value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NonCodeValue::NewLine => "\n\n".to_string(),
|
NonCodeValue::NewLine => "\n\n".to_string(),
|
||||||
@ -738,14 +746,27 @@ impl NonCodeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
|
pub enum CommentStyle {
|
||||||
|
/// Like // foo
|
||||||
|
Line,
|
||||||
|
/// Like /* foo */
|
||||||
|
Block,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
pub enum NonCodeValue {
|
pub enum NonCodeValue {
|
||||||
/// An inline comment.
|
/// An inline comment.
|
||||||
/// An example of this is the following: `1 + 1 // This is an inline comment`.
|
/// Here are examples:
|
||||||
|
/// `1 + 1 // This is an inline comment`.
|
||||||
|
/// `1 + 1 /* Here's another */`.
|
||||||
InlineComment {
|
InlineComment {
|
||||||
value: String,
|
value: String,
|
||||||
|
style: CommentStyle,
|
||||||
},
|
},
|
||||||
/// A block comment.
|
/// A block comment.
|
||||||
/// An example of this is the following:
|
/// An example of this is the following:
|
||||||
@ -759,11 +780,13 @@ pub enum NonCodeValue {
|
|||||||
/// If it did it would be a `NewLineBlockComment`.
|
/// If it did it would be a `NewLineBlockComment`.
|
||||||
BlockComment {
|
BlockComment {
|
||||||
value: String,
|
value: String,
|
||||||
|
style: CommentStyle,
|
||||||
},
|
},
|
||||||
/// A block comment that has a new line above it.
|
/// A block comment that has a new line above it.
|
||||||
/// The user explicitly added a new line above the block comment.
|
/// The user explicitly added a new line above the block comment.
|
||||||
NewLineBlockComment {
|
NewLineBlockComment {
|
||||||
value: String,
|
value: String,
|
||||||
|
style: CommentStyle,
|
||||||
},
|
},
|
||||||
// A new line like `\n\n` NOT a new line like `\n`.
|
// A new line like `\n\n` NOT a new line like `\n`.
|
||||||
// This is also not a comment.
|
// This is also not a comment.
|
||||||
@ -774,7 +797,7 @@ pub enum NonCodeValue {
|
|||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NonCodeMeta {
|
pub struct NonCodeMeta {
|
||||||
pub non_code_nodes: HashMap<usize, NonCodeNode>,
|
pub non_code_nodes: HashMap<usize, Vec<NonCodeNode>>,
|
||||||
pub start: Option<NonCodeNode>,
|
pub start: Option<NonCodeNode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -795,7 +818,10 @@ impl<'de> Deserialize<'de> for NonCodeMeta {
|
|||||||
let helper = NonCodeMetaHelper::deserialize(deserializer)?;
|
let helper = NonCodeMetaHelper::deserialize(deserializer)?;
|
||||||
let mut non_code_nodes = HashMap::new();
|
let mut non_code_nodes = HashMap::new();
|
||||||
for (key, value) in helper.non_code_nodes {
|
for (key, value) in helper.non_code_nodes {
|
||||||
non_code_nodes.insert(key.parse().map_err(serde::de::Error::custom)?, value);
|
non_code_nodes
|
||||||
|
.entry(key.parse().map_err(serde::de::Error::custom)?)
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(value);
|
||||||
}
|
}
|
||||||
Ok(NonCodeMeta {
|
Ok(NonCodeMeta {
|
||||||
non_code_nodes,
|
non_code_nodes,
|
||||||
@ -804,6 +830,12 @@ impl<'de> Deserialize<'de> for NonCodeMeta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl NonCodeMeta {
|
||||||
|
pub fn insert(&mut self, i: usize, new: NonCodeNode) {
|
||||||
|
self.non_code_nodes.entry(i).or_default().push(new);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@ -879,7 +911,7 @@ impl CallExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
let fn_name = self.callee.name.clone();
|
let fn_name = self.callee.name.clone();
|
||||||
|
|
||||||
@ -893,7 +925,7 @@ impl CallExpression {
|
|||||||
value.clone()
|
value.clone()
|
||||||
}
|
}
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
binary_expression.get_result(memory, pipe_info, engine).await?
|
binary_expression.get_result(memory, pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
@ -901,15 +933,11 @@ impl CallExpression {
|
|||||||
// THIS IS IMPORTANT.
|
// THIS IS IMPORTANT.
|
||||||
let mut new_pipe_info = pipe_info.clone();
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
new_pipe_info.is_in_pipe = false;
|
new_pipe_info.is_in_pipe = false;
|
||||||
call_expression.execute(memory, &mut new_pipe_info, engine).await?
|
call_expression.execute(memory, &mut new_pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => {
|
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).await?,
|
||||||
unary_expression.get_result(memory, pipe_info, engine).await?
|
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
}
|
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
Value::ObjectExpression(object_expression) => {
|
|
||||||
object_expression.execute(memory, pipe_info, engine).await?
|
|
||||||
}
|
|
||||||
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, engine).await?,
|
|
||||||
Value::PipeExpression(pipe_expression) => {
|
Value::PipeExpression(pipe_expression) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("PipeExpression not implemented here: {:?}", pipe_expression),
|
message: format!("PipeExpression not implemented here: {:?}", pipe_expression),
|
||||||
@ -941,12 +969,12 @@ impl CallExpression {
|
|||||||
match &self.function {
|
match &self.function {
|
||||||
Function::StdLib { func } => {
|
Function::StdLib { func } => {
|
||||||
// Attempt to call the function.
|
// Attempt to call the function.
|
||||||
let args = crate::std::Args::new(fn_args, self.into(), engine.clone());
|
let args = crate::std::Args::new(fn_args, self.into(), ctx.clone());
|
||||||
let result = func.std_lib_fn()(args).await?;
|
let result = func.std_lib_fn()(args).await?;
|
||||||
if pipe_info.is_in_pipe {
|
if pipe_info.is_in_pipe {
|
||||||
pipe_info.index += 1;
|
pipe_info.index += 1;
|
||||||
pipe_info.previous_results.push(result);
|
pipe_info.previous_results.push(result);
|
||||||
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), engine).await
|
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), ctx).await
|
||||||
} else {
|
} else {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@ -954,7 +982,7 @@ impl CallExpression {
|
|||||||
Function::InMemory => {
|
Function::InMemory => {
|
||||||
let func = memory.get(&fn_name, self.into())?;
|
let func = memory.get(&fn_name, self.into())?;
|
||||||
let result = func
|
let result = func
|
||||||
.call_fn(fn_args, memory.clone(), engine.clone())
|
.call_fn(fn_args, memory.clone(), ctx.clone())
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| {
|
.ok_or_else(|| {
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
@ -969,7 +997,7 @@ impl CallExpression {
|
|||||||
pipe_info.index += 1;
|
pipe_info.index += 1;
|
||||||
pipe_info.previous_results.push(result);
|
pipe_info.previous_results.push(result);
|
||||||
|
|
||||||
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), engine).await
|
execute_pipe_body(memory, &pipe_info.body.clone(), pipe_info, self.into(), ctx).await
|
||||||
} else {
|
} else {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@ -1507,7 +1535,7 @@ impl ArrayExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
let mut results = Vec::with_capacity(self.elements.len());
|
let mut results = Vec::with_capacity(self.elements.len());
|
||||||
|
|
||||||
@ -1519,7 +1547,7 @@ impl ArrayExpression {
|
|||||||
value.clone()
|
value.clone()
|
||||||
}
|
}
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
binary_expression.get_result(memory, pipe_info, engine).await?
|
binary_expression.get_result(memory, pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
@ -1527,16 +1555,12 @@ impl ArrayExpression {
|
|||||||
// THIS IS IMPORTANT.
|
// THIS IS IMPORTANT.
|
||||||
let mut new_pipe_info = pipe_info.clone();
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
new_pipe_info.is_in_pipe = false;
|
new_pipe_info.is_in_pipe = false;
|
||||||
call_expression.execute(memory, &mut new_pipe_info, engine).await?
|
call_expression.execute(memory, &mut new_pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => {
|
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).await?,
|
||||||
unary_expression.get_result(memory, pipe_info, engine).await?
|
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
}
|
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
Value::ObjectExpression(object_expression) => {
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, ctx).await?,
|
||||||
object_expression.execute(memory, pipe_info, engine).await?
|
|
||||||
}
|
|
||||||
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, engine).await?,
|
|
||||||
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, engine).await?,
|
|
||||||
Value::PipeSubstitution(pipe_substitution) => {
|
Value::PipeSubstitution(pipe_substitution) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
||||||
@ -1663,7 +1687,7 @@ impl ObjectExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
let mut object = Map::new();
|
let mut object = Map::new();
|
||||||
for property in &self.properties {
|
for property in &self.properties {
|
||||||
@ -1674,7 +1698,7 @@ impl ObjectExpression {
|
|||||||
value.clone()
|
value.clone()
|
||||||
}
|
}
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
binary_expression.get_result(memory, pipe_info, engine).await?
|
binary_expression.get_result(memory, pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
@ -1682,16 +1706,12 @@ impl ObjectExpression {
|
|||||||
// THIS IS IMPORTANT.
|
// THIS IS IMPORTANT.
|
||||||
let mut new_pipe_info = pipe_info.clone();
|
let mut new_pipe_info = pipe_info.clone();
|
||||||
new_pipe_info.is_in_pipe = false;
|
new_pipe_info.is_in_pipe = false;
|
||||||
call_expression.execute(memory, &mut new_pipe_info, engine).await?
|
call_expression.execute(memory, &mut new_pipe_info, ctx).await?
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => {
|
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).await?,
|
||||||
unary_expression.get_result(memory, pipe_info, engine).await?
|
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
}
|
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, ctx).await?,
|
||||||
Value::ObjectExpression(object_expression) => {
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, ctx).await?,
|
||||||
object_expression.execute(memory, pipe_info, engine).await?
|
|
||||||
}
|
|
||||||
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, engine).await?,
|
|
||||||
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, engine).await?,
|
|
||||||
Value::PipeSubstitution(pipe_substitution) => {
|
Value::PipeSubstitution(pipe_substitution) => {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
|
||||||
@ -2109,7 +2129,7 @@ impl BinaryExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
// stop the execution of the pipe.
|
// stop the execution of the pipe.
|
||||||
@ -2119,12 +2139,12 @@ impl BinaryExpression {
|
|||||||
|
|
||||||
let left_json_value = self
|
let left_json_value = self
|
||||||
.left
|
.left
|
||||||
.get_result(memory, &mut new_pipe_info, engine)
|
.get_result(memory, &mut new_pipe_info, ctx)
|
||||||
.await?
|
.await?
|
||||||
.get_json_value()?;
|
.get_json_value()?;
|
||||||
let right_json_value = self
|
let right_json_value = self
|
||||||
.right
|
.right
|
||||||
.get_result(memory, &mut new_pipe_info, engine)
|
.get_result(memory, &mut new_pipe_info, ctx)
|
||||||
.await?
|
.await?
|
||||||
.get_json_value()?;
|
.get_json_value()?;
|
||||||
|
|
||||||
@ -2283,7 +2303,7 @@ impl UnaryExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
|
||||||
// stop the execution of the pipe.
|
// stop the execution of the pipe.
|
||||||
@ -2294,7 +2314,7 @@ impl UnaryExpression {
|
|||||||
let num = parse_json_number_as_f64(
|
let num = parse_json_number_as_f64(
|
||||||
&self
|
&self
|
||||||
.argument
|
.argument
|
||||||
.get_result(memory, &mut new_pipe_info, engine)
|
.get_result(memory, &mut new_pipe_info, ctx)
|
||||||
.await?
|
.await?
|
||||||
.get_json_value()?,
|
.get_json_value()?,
|
||||||
self.into(),
|
self.into(),
|
||||||
@ -2397,7 +2417,9 @@ impl PipeExpression {
|
|||||||
let mut s = statement.recast(options, indentation_level + 1, true);
|
let mut s = statement.recast(options, indentation_level + 1, true);
|
||||||
let non_code_meta = self.non_code_meta.clone();
|
let non_code_meta = self.non_code_meta.clone();
|
||||||
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
|
||||||
s += non_code_meta_value.format(&indentation).trim_end_matches('\n')
|
for val in non_code_meta_value {
|
||||||
|
s += val.format(&indentation).trim_end_matches('\n')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if index != self.body.len() - 1 {
|
if index != self.body.len() - 1 {
|
||||||
@ -2427,12 +2449,12 @@ impl PipeExpression {
|
|||||||
&self,
|
&self,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
// Reset the previous results.
|
// Reset the previous results.
|
||||||
pipe_info.previous_results = vec![];
|
pipe_info.previous_results = vec![];
|
||||||
pipe_info.index = 0;
|
pipe_info.index = 0;
|
||||||
execute_pipe_body(memory, &self.body, pipe_info, self.into(), engine).await
|
execute_pipe_body(memory, &self.body, pipe_info, self.into(), ctx).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rename all identifiers that have the old name to the new given name.
|
/// Rename all identifiers that have the old name to the new given name.
|
||||||
@ -2449,7 +2471,7 @@ async fn execute_pipe_body(
|
|||||||
body: &[Value],
|
body: &[Value],
|
||||||
pipe_info: &mut PipeInfo,
|
pipe_info: &mut PipeInfo,
|
||||||
source_range: SourceRange,
|
source_range: SourceRange,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<MemoryItem, KclError> {
|
) -> Result<MemoryItem, KclError> {
|
||||||
if pipe_info.index == body.len() {
|
if pipe_info.index == body.len() {
|
||||||
pipe_info.is_in_pipe = false;
|
pipe_info.is_in_pipe = false;
|
||||||
@ -2474,15 +2496,15 @@ async fn execute_pipe_body(
|
|||||||
|
|
||||||
match expression {
|
match expression {
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
let result = binary_expression.get_result(memory, pipe_info, engine).await?;
|
let result = binary_expression.get_result(memory, pipe_info, ctx).await?;
|
||||||
pipe_info.previous_results.push(result);
|
pipe_info.previous_results.push(result);
|
||||||
pipe_info.index += 1;
|
pipe_info.index += 1;
|
||||||
execute_pipe_body(memory, body, pipe_info, source_range, engine).await
|
execute_pipe_body(memory, body, pipe_info, source_range, ctx).await
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
pipe_info.is_in_pipe = true;
|
pipe_info.is_in_pipe = true;
|
||||||
pipe_info.body = body.to_vec();
|
pipe_info.body = body.to_vec();
|
||||||
call_expression.execute(memory, pipe_info, engine).await
|
call_expression.execute(memory, pipe_info, ctx).await
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Return an error this should not happen.
|
// Return an error this should not happen.
|
||||||
@ -2775,7 +2797,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_get_lsp_symbols() {
|
fn test_get_lsp_symbols() {
|
||||||
let code = r#"const part001 = startSketchAt([0.0000000000, 5.0000000000])
|
let code = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|
|
||||||
const part002 = "part002"
|
const part002 = "part002"
|
||||||
@ -2796,25 +2819,10 @@ show(part001)"#;
|
|||||||
assert_eq!(symbols.len(), 7);
|
assert_eq!(symbols.len(), 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_recast_with_std_and_non_stdlib() {
|
|
||||||
let some_program_string = r#"{"body":[{"type":"VariableDeclaration","start":0,"end":0,"declarations":[{"type":"VariableDeclarator","start":0,"end":0,"id":{"type":"Identifier","start":0,"end":0,"name":"part001"},"init":{"type":"PipeExpression","start":0,"end":0,"body":[{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"startSketchAt"},"function":{"type":"StdLib","func":{"name":"startSketchAt","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"ry"},"function":{"type":"InMemory"},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":90,"raw":"90"},{"type":"PipeSubstitution","start":0,"end":0}]},{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"line"},"function":{"type":"StdLib","func":{"name":"line","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Literal","start":0,"end":0,"value":"default","raw":"default"},{"type":"PipeSubstitution","start":0,"end":0}]}],"nonCodeMeta":{"nonCodeNodes":{},"start":null}}}],"kind":"const"},{"type":"ExpressionStatement","start":0,"end":0,"expression":{"type":"CallExpression","start":0,"end":0,"callee":{"type":"Identifier","start":0,"end":0,"name":"show"},"function":{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}},"optional":false,"arguments":[{"type":"Identifier","start":0,"end":0,"name":"part001"}]}}],"start":0,"end":0,"nonCodeMeta":{"nonCodeNodes":{},"start":null}}"#;
|
|
||||||
let some_program: crate::ast::types::Program = serde_json::from_str(some_program_string).unwrap();
|
|
||||||
|
|
||||||
let recasted = some_program.recast(&Default::default(), 0);
|
|
||||||
assert_eq!(
|
|
||||||
recasted,
|
|
||||||
r#"const part001 = startSketchAt('default')
|
|
||||||
|> ry(90, %)
|
|
||||||
|> line('default', %)
|
|
||||||
show(part001)
|
|
||||||
"#
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_with_bad_indentation() {
|
fn test_recast_with_bad_indentation() {
|
||||||
let some_program_string = r#"const part001 = startSketchAt([0.0, 5.0])
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#;
|
|> line([0.6804562304, 0.9087880491], %)"#;
|
||||||
let tokens = crate::token::lexer(some_program_string);
|
let tokens = crate::token::lexer(some_program_string);
|
||||||
@ -2824,7 +2832,8 @@ show(part001)
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|> line([0.6804562304, 0.9087880491], %)
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
"#
|
"#
|
||||||
@ -2833,7 +2842,8 @@ show(part001)
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_with_bad_indentation_and_inline_comment() {
|
fn test_recast_with_bad_indentation_and_inline_comment() {
|
||||||
let some_program_string = r#"const part001 = startSketchAt([0.0, 5.0])
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %) // hello world
|
|> line([0.4900857016, -0.0240763666], %) // hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#;
|
|> line([0.6804562304, 0.9087880491], %)"#;
|
||||||
let tokens = crate::token::lexer(some_program_string);
|
let tokens = crate::token::lexer(some_program_string);
|
||||||
@ -2843,7 +2853,8 @@ show(part001)
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %) // hello world
|
|> line([0.4900857016, -0.0240763666], %) // hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
"#
|
"#
|
||||||
@ -2851,7 +2862,8 @@ show(part001)
|
|||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_with_bad_indentation_and_line_comment() {
|
fn test_recast_with_bad_indentation_and_line_comment() {
|
||||||
let some_program_string = r#"const part001 = startSketchAt([0.0, 5.0])
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
// hello world
|
// hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)"#;
|
|> line([0.6804562304, 0.9087880491], %)"#;
|
||||||
@ -2862,7 +2874,8 @@ show(part001)
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const part001 = startSketchAt([0.0, 5.0])
|
r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
// hello world
|
// hello world
|
||||||
|> line([0.6804562304, 0.9087880491], %)
|
|> line([0.6804562304, 0.9087880491], %)
|
||||||
@ -2890,9 +2903,9 @@ show(part001)
|
|||||||
recasted,
|
recasted,
|
||||||
r#"fn myFn = () => {
|
r#"fn myFn = () => {
|
||||||
// this is a comment
|
// this is a comment
|
||||||
const yo = { a: { b: { c: '123' } } }
|
const yo = { a: { b: { c: '123' } } } /* block
|
||||||
/* block
|
|
||||||
comment */
|
comment */
|
||||||
|
|
||||||
const key = 'c'
|
const key = 'c'
|
||||||
// this is also a comment
|
// this is also a comment
|
||||||
return things
|
return things
|
||||||
@ -2904,7 +2917,8 @@ show(part001)
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_recast_lots_of_comments() {
|
fn test_recast_lots_of_comments() {
|
||||||
let some_program_string = r#"// comment at start
|
let some_program_string = r#"// comment at start
|
||||||
const mySk1 = startSketchAt([0, 0])
|
const mySk1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
// comment here
|
// comment here
|
||||||
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|
||||||
@ -2926,27 +2940,28 @@ const mySk1 = startSketchAt([0, 0])
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"// comment at start
|
r#"// comment at start
|
||||||
const mySk1 = startSketchAt([0, 0])
|
const mySk1 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
// comment here
|
// comment here
|
||||||
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|
||||||
|> lineTo([1, 1], %)
|
|> lineTo([1, 1], %)
|
||||||
/* and
|
/* and
|
||||||
here
|
here */
|
||||||
|
// a comment between pipe expression statements
|
||||||
a comment between pipe expression statements */
|
|
||||||
|> rx(90, %)
|
|> rx(90, %)
|
||||||
// and another with just white space between others below
|
// and another with just white space between others below
|
||||||
|> ry(45, %)
|
|> ry(45, %)
|
||||||
|> rx(45, %)
|
|> rx(45, %)
|
||||||
// one more for good measure
|
// one more for good measure
|
||||||
"#
|
"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_multiline_object() {
|
fn test_recast_multiline_object() {
|
||||||
let some_program_string = r#"const part001 = startSketchAt([-0.01, -0.08])
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-0.01, -0.08], %)
|
||||||
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|
||||||
|> line([2.77, -1.24], %)
|
|> line([2.77, -1.24], %)
|
||||||
|> angledLineThatIntersects({
|
|> angledLineThatIntersects({
|
||||||
@ -3006,16 +3021,19 @@ const things = "things"
|
|||||||
let program = parser.ast().unwrap();
|
let program = parser.ast().unwrap();
|
||||||
|
|
||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(recasted.trim(), some_program_string.trim());
|
let expected = some_program_string.trim();
|
||||||
|
// Currently new parser removes an empty line
|
||||||
|
let actual = recasted.trim();
|
||||||
|
assert_eq!(actual, expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_comment_tokens_inside_strings() {
|
fn test_recast_comment_tokens_inside_strings() {
|
||||||
let some_program_string = r#"let b = {
|
let some_program_string = r#"let b = {
|
||||||
"end": 141,
|
end: 141,
|
||||||
"start": 125,
|
start: 125,
|
||||||
"type": "NonCodeNode",
|
type: "NonCodeNode",
|
||||||
"value": "
|
value: "
|
||||||
// a comment
|
// a comment
|
||||||
"
|
"
|
||||||
}"#;
|
}"#;
|
||||||
@ -3034,7 +3052,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
||||||
|> angledLineToX([
|
|> angledLineToX([
|
||||||
-angleToMatchLengthX('seg01', myVar, %),
|
-angleToMatchLengthX('seg01', myVar, %),
|
||||||
@ -3059,7 +3078,8 @@ const myVar2 = 5
|
|||||||
const myVar3 = 6
|
const myVar3 = 6
|
||||||
const myAng = 40
|
const myAng = 40
|
||||||
const myAng2 = 134
|
const myAng2 = 134
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|
||||||
|> angledLineToX([
|
|> angledLineToX([
|
||||||
-angleToMatchLengthX('seg01', myVar, %),
|
-angleToMatchLengthX('seg01', myVar, %),
|
||||||
@ -3087,7 +3107,8 @@ const part001 = startSketchAt([0, 0])
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_recast_after_rename_std() {
|
fn test_recast_after_rename_std() {
|
||||||
let some_program_string = r#"const part001 = startSketchAt([0.0000000000, 5.0000000000])
|
let some_program_string = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0000000000, 5.0000000000], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|
|
||||||
const part002 = "part002"
|
const part002 = "part002"
|
||||||
@ -3109,7 +3130,8 @@ show(part001)"#;
|
|||||||
let recasted = program.recast(&Default::default(), 0);
|
let recasted = program.recast(&Default::default(), 0);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
recasted,
|
recasted,
|
||||||
r#"const mySuperCoolPart = startSketchAt([0.0, 5.0])
|
r#"const mySuperCoolPart = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0.0, 5.0], %)
|
||||||
|> line([0.4900857016, -0.0240763666], %)
|
|> line([0.4900857016, -0.0240763666], %)
|
||||||
|
|
||||||
const part002 = "part002"
|
const part002 = "part002"
|
||||||
@ -3153,7 +3175,8 @@ show(mySuperCoolPart)
|
|||||||
const l = 8
|
const l = 8
|
||||||
const h = 10
|
const h = 10
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
@ -3172,7 +3195,8 @@ show(firstExtrude)"#;
|
|||||||
const l = 8
|
const l = 8
|
||||||
const h = 10
|
const h = 10
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0, 0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
|
|||||||
|
|
||||||
use crate::executor::SourceRange;
|
use crate::executor::SourceRange;
|
||||||
|
|
||||||
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS)]
|
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
pub enum KclError {
|
pub enum KclError {
|
||||||
@ -28,7 +28,7 @@ pub enum KclError {
|
|||||||
Engine(KclErrorDetails),
|
Engine(KclErrorDetails),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, ts_rs::TS)]
|
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
pub struct KclErrorDetails {
|
pub struct KclErrorDetails {
|
||||||
#[serde(rename = "sourceRanges")]
|
#[serde(rename = "sourceRanges")]
|
||||||
@ -78,6 +78,22 @@ impl KclError {
|
|||||||
KclError::Engine(e) => e.source_ranges.clone(),
|
KclError::Engine(e) => e.source_ranges.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the inner error message.
|
||||||
|
pub fn message(&self) -> &str {
|
||||||
|
match &self {
|
||||||
|
KclError::Syntax(e) => &e.message,
|
||||||
|
KclError::Semantic(e) => &e.message,
|
||||||
|
KclError::Type(e) => &e.message,
|
||||||
|
KclError::Unimplemented(e) => &e.message,
|
||||||
|
KclError::Unexpected(e) => &e.message,
|
||||||
|
KclError::ValueAlreadyDefined(e) => &e.message,
|
||||||
|
KclError::UndefinedValue(e) => &e.message,
|
||||||
|
KclError::InvalidExpression(e) => &e.message,
|
||||||
|
KclError::Engine(e) => &e.message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
|
pub fn to_lsp_diagnostic(&self, code: &str) -> Diagnostic {
|
||||||
let (message, _, _) = self.get_message_line_column(code);
|
let (message, _, _) = self.get_message_line_column(code);
|
||||||
let source_ranges = self.source_ranges();
|
let source_ranges = self.source_ranges();
|
||||||
|
|||||||
@ -3,13 +3,15 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use kittycad::types::{Color, ModelingCmd, Point3D};
|
||||||
|
use parse_display::{Display, FromStr};
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{BodyItem, Function, FunctionExpression, Value},
|
ast::types::{BodyItem, Function, FunctionExpression, Value},
|
||||||
engine::EngineConnection,
|
engine::{EngineConnection, EngineManager},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -101,6 +103,7 @@ impl ProgramReturn {
|
|||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum MemoryItem {
|
pub enum MemoryItem {
|
||||||
UserVal(UserVal),
|
UserVal(UserVal),
|
||||||
|
Plane(Box<Plane>),
|
||||||
SketchGroup(Box<SketchGroup>),
|
SketchGroup(Box<SketchGroup>),
|
||||||
ExtrudeGroup(Box<ExtrudeGroup>),
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
@ -115,6 +118,166 @@ pub enum MemoryItem {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A plane.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct Plane {
|
||||||
|
/// The id of the plane.
|
||||||
|
pub id: uuid::Uuid,
|
||||||
|
// The code for the plane either a string or custom.
|
||||||
|
pub value: PlaneType,
|
||||||
|
/// Origin of the plane.
|
||||||
|
pub origin: Point3d,
|
||||||
|
/// What should the plane’s X axis be?
|
||||||
|
pub x_axis: Point3d,
|
||||||
|
/// What should the plane’s Y axis be?
|
||||||
|
pub y_axis: Point3d,
|
||||||
|
/// The z-axis (normal).
|
||||||
|
pub z_axis: Point3d,
|
||||||
|
#[serde(rename = "__meta")]
|
||||||
|
pub meta: Vec<Metadata>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Type for a plane.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
#[display(style = "camelCase")]
|
||||||
|
pub enum PlaneType {
|
||||||
|
#[serde(rename = "XY", alias = "xy")]
|
||||||
|
#[display("XY")]
|
||||||
|
XY,
|
||||||
|
#[serde(rename = "XZ", alias = "xz")]
|
||||||
|
#[display("XZ")]
|
||||||
|
XZ,
|
||||||
|
#[serde(rename = "YZ", alias = "yz")]
|
||||||
|
#[display("YZ")]
|
||||||
|
YZ,
|
||||||
|
/// A custom plane.
|
||||||
|
#[serde(rename = "Custom")]
|
||||||
|
#[display("Custom")]
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct DefaultPlanes {
|
||||||
|
pub xy: uuid::Uuid,
|
||||||
|
pub xz: uuid::Uuid,
|
||||||
|
pub yz: uuid::Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultPlanes {
|
||||||
|
pub async fn new(engine: &EngineConnection) -> Result<Self, KclError> {
|
||||||
|
// Create new default planes.
|
||||||
|
let default_size = 60.0;
|
||||||
|
let default_origin = Point3D { x: 0.0, y: 0.0, z: 0.0 };
|
||||||
|
|
||||||
|
// Create xy plane.
|
||||||
|
let xy = uuid::Uuid::new_v4();
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
xy,
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::MakePlane {
|
||||||
|
clobber: false,
|
||||||
|
origin: default_origin.clone(),
|
||||||
|
size: default_size,
|
||||||
|
x_axis: Point3D { x: 1.0, y: 0.0, z: 0.0 },
|
||||||
|
y_axis: Point3D { x: 0.0, y: 1.0, z: 0.0 },
|
||||||
|
hide: Some(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// Set the color.
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::PlaneSetColor {
|
||||||
|
color: Color {
|
||||||
|
r: 0.7,
|
||||||
|
g: 0.28,
|
||||||
|
b: 0.28,
|
||||||
|
a: 0.4,
|
||||||
|
},
|
||||||
|
plane_id: xy,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create yz plane.
|
||||||
|
let yz = uuid::Uuid::new_v4();
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
yz,
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::MakePlane {
|
||||||
|
clobber: false,
|
||||||
|
origin: default_origin.clone(),
|
||||||
|
size: default_size,
|
||||||
|
x_axis: Point3D { x: 0.0, y: 1.0, z: 0.0 },
|
||||||
|
y_axis: Point3D { x: 0.0, y: 0.0, z: 1.0 },
|
||||||
|
hide: Some(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// Set the color.
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::PlaneSetColor {
|
||||||
|
color: Color {
|
||||||
|
r: 0.28,
|
||||||
|
g: 0.7,
|
||||||
|
b: 0.28,
|
||||||
|
a: 0.4,
|
||||||
|
},
|
||||||
|
plane_id: yz,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// Create xz plane.
|
||||||
|
let xz = uuid::Uuid::new_v4();
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
xz,
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::MakePlane {
|
||||||
|
clobber: false,
|
||||||
|
origin: default_origin,
|
||||||
|
size: default_size,
|
||||||
|
x_axis: Point3D { x: 1.0, y: 0.0, z: 0.0 },
|
||||||
|
y_axis: Point3D { x: 0.0, y: 0.0, z: 1.0 },
|
||||||
|
hide: Some(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
// Set the color.
|
||||||
|
engine
|
||||||
|
.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
SourceRange::default(),
|
||||||
|
ModelingCmd::PlaneSetColor {
|
||||||
|
color: Color {
|
||||||
|
r: 0.28,
|
||||||
|
g: 0.28,
|
||||||
|
b: 0.7,
|
||||||
|
a: 0.4,
|
||||||
|
},
|
||||||
|
plane_id: xz,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Self { xy, xz, yz })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(tag = "type", rename_all = "camelCase")]
|
#[serde(tag = "type", rename_all = "camelCase")]
|
||||||
@ -140,7 +303,7 @@ pub type MemoryFunction =
|
|||||||
memory: ProgramMemory,
|
memory: ProgramMemory,
|
||||||
expression: Box<FunctionExpression>,
|
expression: Box<FunctionExpression>,
|
||||||
metadata: Vec<Metadata>,
|
metadata: Vec<Metadata>,
|
||||||
engine: EngineConnection,
|
ctx: ExecutorContext,
|
||||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>;
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>;
|
||||||
|
|
||||||
fn force_memory_function<
|
fn force_memory_function<
|
||||||
@ -149,7 +312,7 @@ fn force_memory_function<
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
Box<FunctionExpression>,
|
Box<FunctionExpression>,
|
||||||
Vec<Metadata>,
|
Vec<Metadata>,
|
||||||
EngineConnection,
|
ExecutorContext,
|
||||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>,
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>,
|
||||||
>(
|
>(
|
||||||
f: F,
|
f: F,
|
||||||
@ -165,6 +328,7 @@ impl From<MemoryItem> for Vec<SourceRange> {
|
|||||||
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::ExtrudeTransform(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
||||||
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
||||||
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -187,11 +351,11 @@ impl MemoryItem {
|
|||||||
&self,
|
&self,
|
||||||
args: Vec<MemoryItem>,
|
args: Vec<MemoryItem>,
|
||||||
memory: ProgramMemory,
|
memory: ProgramMemory,
|
||||||
engine: EngineConnection,
|
ctx: ExecutorContext,
|
||||||
) -> Result<Option<ProgramReturn>, KclError> {
|
) -> Result<Option<ProgramReturn>, KclError> {
|
||||||
if let MemoryItem::Function { func, expression, meta } = &self {
|
if let MemoryItem::Function { func, expression, meta } = &self {
|
||||||
if let Some(func) = func {
|
if let Some(func) = func {
|
||||||
func(args, memory, expression.clone(), meta.clone(), engine).await
|
func(args, memory, expression.clone(), meta.clone(), ctx).await
|
||||||
} else {
|
} else {
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: format!("Not a function: {:?}", expression),
|
message: format!("Not a function: {:?}", expression),
|
||||||
@ -222,6 +386,8 @@ pub struct SketchGroup {
|
|||||||
pub position: Position,
|
pub position: Position,
|
||||||
/// The rotation of the sketch group.
|
/// The rotation of the sketch group.
|
||||||
pub rotation: Rotation,
|
pub rotation: Rotation,
|
||||||
|
/// The plane id of the sketch group.
|
||||||
|
pub plane_id: Option<uuid::Uuid>,
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
#[serde(rename = "__meta")]
|
#[serde(rename = "__meta")]
|
||||||
pub meta: Vec<Metadata>,
|
pub meta: Vec<Metadata>,
|
||||||
@ -414,6 +580,18 @@ pub struct Point3d {
|
|||||||
pub z: f64,
|
pub z: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Point3d {
|
||||||
|
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
||||||
|
Self { x, y, z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Point3d> for kittycad::types::Point3D {
|
||||||
|
fn from(p: Point3d) -> Self {
|
||||||
|
Self { x: p.x, y: p.y, z: p.z }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Metadata.
|
/// Metadata.
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -593,12 +771,19 @@ impl Default for PipeInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The executor context.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ExecutorContext {
|
||||||
|
pub engine: EngineConnection,
|
||||||
|
pub planes: DefaultPlanes,
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute a AST's program.
|
/// Execute a AST's program.
|
||||||
pub async fn execute(
|
pub async fn execute(
|
||||||
program: crate::ast::types::Program,
|
program: crate::ast::types::Program,
|
||||||
memory: &mut ProgramMemory,
|
memory: &mut ProgramMemory,
|
||||||
options: BodyType,
|
options: BodyType,
|
||||||
engine: &EngineConnection,
|
ctx: &ExecutorContext,
|
||||||
) -> Result<ProgramMemory, KclError> {
|
) -> Result<ProgramMemory, KclError> {
|
||||||
let mut pipe_info = PipeInfo::default();
|
let mut pipe_info = PipeInfo::default();
|
||||||
|
|
||||||
@ -617,23 +802,23 @@ pub async fn execute(
|
|||||||
args.push(memory_item.clone());
|
args.push(memory_item.clone());
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expr) => {
|
Value::CallExpression(call_expr) => {
|
||||||
let result = call_expr.execute(memory, &mut pipe_info, engine).await?;
|
let result = call_expr.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
args.push(result);
|
args.push(result);
|
||||||
}
|
}
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
let result = binary_expression.get_result(memory, &mut pipe_info, engine).await?;
|
let result = binary_expression.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
args.push(result);
|
args.push(result);
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => {
|
Value::UnaryExpression(unary_expression) => {
|
||||||
let result = unary_expression.get_result(memory, &mut pipe_info, engine).await?;
|
let result = unary_expression.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
args.push(result);
|
args.push(result);
|
||||||
}
|
}
|
||||||
Value::ObjectExpression(object_expression) => {
|
Value::ObjectExpression(object_expression) => {
|
||||||
let result = object_expression.execute(memory, &mut pipe_info, engine).await?;
|
let result = object_expression.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
args.push(result);
|
args.push(result);
|
||||||
}
|
}
|
||||||
Value::ArrayExpression(array_expression) => {
|
Value::ArrayExpression(array_expression) => {
|
||||||
let result = array_expression.execute(memory, &mut pipe_info, engine).await?;
|
let result = array_expression.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
args.push(result);
|
args.push(result);
|
||||||
}
|
}
|
||||||
// We do nothing for the rest.
|
// We do nothing for the rest.
|
||||||
@ -651,7 +836,7 @@ pub async fn execute(
|
|||||||
|
|
||||||
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
|
||||||
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
} else if let Some(func) = memory.clone().root.get(&fn_name) {
|
||||||
let result = func.call_fn(args.clone(), memory.clone(), engine.clone()).await?;
|
let result = func.call_fn(args.clone(), memory.clone(), ctx.clone()).await?;
|
||||||
|
|
||||||
memory.return_ = result;
|
memory.return_ = result;
|
||||||
} else {
|
} else {
|
||||||
@ -677,7 +862,7 @@ pub async fn execute(
|
|||||||
memory.add(&var_name, value.clone(), source_range)?;
|
memory.add(&var_name, value.clone(), source_range)?;
|
||||||
}
|
}
|
||||||
Value::BinaryExpression(binary_expression) => {
|
Value::BinaryExpression(binary_expression) => {
|
||||||
let result = binary_expression.get_result(memory, &mut pipe_info, engine).await?;
|
let result = binary_expression.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::FunctionExpression(function_expression) => {
|
Value::FunctionExpression(function_expression) => {
|
||||||
@ -686,7 +871,7 @@ pub async fn execute(
|
|||||||
memory: ProgramMemory,
|
memory: ProgramMemory,
|
||||||
function_expression: Box<FunctionExpression>,
|
function_expression: Box<FunctionExpression>,
|
||||||
_metadata: Vec<Metadata>,
|
_metadata: Vec<Metadata>,
|
||||||
engine: EngineConnection| {
|
ctx: ExecutorContext| {
|
||||||
Box::pin(async move {
|
Box::pin(async move {
|
||||||
let mut fn_memory = memory.clone();
|
let mut fn_memory = memory.clone();
|
||||||
|
|
||||||
@ -714,7 +899,7 @@ pub async fn execute(
|
|||||||
function_expression.body.clone(),
|
function_expression.body.clone(),
|
||||||
&mut fn_memory,
|
&mut fn_memory,
|
||||||
BodyType::Block,
|
BodyType::Block,
|
||||||
&engine,
|
&ctx,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -733,11 +918,11 @@ pub async fn execute(
|
|||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expression) => {
|
Value::CallExpression(call_expression) => {
|
||||||
let result = call_expression.execute(memory, &mut pipe_info, engine).await?;
|
let result = call_expression.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::PipeExpression(pipe_expression) => {
|
Value::PipeExpression(pipe_expression) => {
|
||||||
let result = pipe_expression.get_result(memory, &mut pipe_info, engine).await?;
|
let result = pipe_expression.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::PipeSubstitution(pipe_substitution) => {
|
Value::PipeSubstitution(pipe_substitution) => {
|
||||||
@ -750,11 +935,11 @@ pub async fn execute(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
Value::ArrayExpression(array_expression) => {
|
Value::ArrayExpression(array_expression) => {
|
||||||
let result = array_expression.execute(memory, &mut pipe_info, engine).await?;
|
let result = array_expression.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::ObjectExpression(object_expression) => {
|
Value::ObjectExpression(object_expression) => {
|
||||||
let result = object_expression.execute(memory, &mut pipe_info, engine).await?;
|
let result = object_expression.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::MemberExpression(member_expression) => {
|
Value::MemberExpression(member_expression) => {
|
||||||
@ -762,7 +947,7 @@ pub async fn execute(
|
|||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expression) => {
|
Value::UnaryExpression(unary_expression) => {
|
||||||
let result = unary_expression.get_result(memory, &mut pipe_info, engine).await?;
|
let result = unary_expression.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.add(&var_name, result, source_range)?;
|
memory.add(&var_name, result, source_range)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -770,11 +955,11 @@ pub async fn execute(
|
|||||||
}
|
}
|
||||||
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
|
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
|
||||||
Value::BinaryExpression(bin_expr) => {
|
Value::BinaryExpression(bin_expr) => {
|
||||||
let result = bin_expr.get_result(memory, &mut pipe_info, engine).await?;
|
let result = bin_expr.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::UnaryExpression(unary_expr) => {
|
Value::UnaryExpression(unary_expr) => {
|
||||||
let result = unary_expr.get_result(memory, &mut pipe_info, engine).await?;
|
let result = unary_expr.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::Identifier(identifier) => {
|
Value::Identifier(identifier) => {
|
||||||
@ -785,15 +970,15 @@ pub async fn execute(
|
|||||||
memory.return_ = Some(ProgramReturn::Value(literal.into()));
|
memory.return_ = Some(ProgramReturn::Value(literal.into()));
|
||||||
}
|
}
|
||||||
Value::ArrayExpression(array_expr) => {
|
Value::ArrayExpression(array_expr) => {
|
||||||
let result = array_expr.execute(memory, &mut pipe_info, engine).await?;
|
let result = array_expr.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::ObjectExpression(obj_expr) => {
|
Value::ObjectExpression(obj_expr) => {
|
||||||
let result = obj_expr.execute(memory, &mut pipe_info, engine).await?;
|
let result = obj_expr.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::CallExpression(call_expr) => {
|
Value::CallExpression(call_expr) => {
|
||||||
let result = call_expr.execute(memory, &mut pipe_info, engine).await?;
|
let result = call_expr.execute(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::MemberExpression(member_expr) => {
|
Value::MemberExpression(member_expr) => {
|
||||||
@ -801,7 +986,7 @@ pub async fn execute(
|
|||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::PipeExpression(pipe_expr) => {
|
Value::PipeExpression(pipe_expr) => {
|
||||||
let result = pipe_expr.get_result(memory, &mut pipe_info, engine).await?;
|
let result = pipe_expr.get_result(memory, &mut pipe_info, ctx).await?;
|
||||||
memory.return_ = Some(ProgramReturn::Value(result));
|
memory.return_ = Some(ProgramReturn::Value(result));
|
||||||
}
|
}
|
||||||
Value::PipeSubstitution(_) => {}
|
Value::PipeSubstitution(_) => {}
|
||||||
@ -825,7 +1010,9 @@ mod tests {
|
|||||||
let program = parser.ast()?;
|
let program = parser.ast()?;
|
||||||
let mut mem: ProgramMemory = Default::default();
|
let mut mem: ProgramMemory = Default::default();
|
||||||
let engine = EngineConnection::new().await?;
|
let engine = EngineConnection::new().await?;
|
||||||
let memory = execute(program, &mut mem, BodyType::Root, &engine).await?;
|
let planes = DefaultPlanes::new(&engine).await?;
|
||||||
|
let ctx = ExecutorContext { engine, planes };
|
||||||
|
let memory = execute(program, &mut mem, BodyType::Root, &ctx).await?;
|
||||||
|
|
||||||
Ok(memory)
|
Ok(memory)
|
||||||
}
|
}
|
||||||
@ -849,7 +1036,8 @@ const newVar = myVar + 1"#;
|
|||||||
async fn test_execute_angled_line_that_intersects() {
|
async fn test_execute_angled_line_that_intersects() {
|
||||||
let ast_fn = |offset: &str| -> String {
|
let ast_fn = |offset: &str| -> String {
|
||||||
format!(
|
format!(
|
||||||
r#"const part001 = startSketchAt([0, 0])
|
r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> lineTo({{to:[2, 2], tag: "yo"}}, %)
|
|> lineTo({{to:[2, 2], tag: "yo"}}, %)
|
||||||
|> lineTo([3, 1], %)
|
|> lineTo([3, 1], %)
|
||||||
|> angledLineThatIntersects({{
|
|> angledLineThatIntersects({{
|
||||||
@ -896,7 +1084,8 @@ const yo = 5 + 6
|
|||||||
|
|
||||||
const abc = 3
|
const abc = 3
|
||||||
const identifierGuy = 5
|
const identifierGuy = 5
|
||||||
const part001 = startSketchAt([-1.2, 4.83])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-1.2, 4.83], %)
|
||||||
|> line([2.8, 0], %)
|
|> line([2.8, 0], %)
|
||||||
|> angledLine([100 + 100, 3.01], %)
|
|> angledLine([100 + 100, 3.01], %)
|
||||||
|> angledLine([abc, 3.02], %)
|
|> angledLine([abc, 3.02], %)
|
||||||
@ -913,7 +1102,8 @@ show(part001)"#;
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_pipe_substitutions_unary() {
|
async fn test_execute_with_pipe_substitutions_unary() {
|
||||||
let ast = r#"const myVar = 3
|
let ast = r#"const myVar = 3
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
||||||
|> line([
|
|> line([
|
||||||
min(segLen('seg01', %), myVar),
|
min(segLen('seg01', %), myVar),
|
||||||
@ -928,7 +1118,8 @@ show(part001)"#;
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_pipe_substitutions() {
|
async fn test_execute_with_pipe_substitutions() {
|
||||||
let ast = r#"const myVar = 3
|
let ast = r#"const myVar = 3
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
|> line({ to: [3, 4], tag: 'seg01' }, %)
|
||||||
|> line([
|
|> line([
|
||||||
min(segLen('seg01', %), myVar),
|
min(segLen('seg01', %), myVar),
|
||||||
@ -951,7 +1142,8 @@ const halfArmAngle = armAngle / 2
|
|||||||
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
||||||
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
||||||
|
|
||||||
const part001 = startSketchAt([0, 0])
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> yLineTo(1, %)
|
|> yLineTo(1, %)
|
||||||
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
||||||
|
|
||||||
@ -972,7 +1164,8 @@ fn thing = () => {
|
|||||||
return -8
|
return -8
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, thing()], %)
|
|> line([0, thing()], %)
|
||||||
@ -994,7 +1187,8 @@ fn thing = (x) => {
|
|||||||
return -x
|
return -x
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, thing(8)], %)
|
|> line([0, thing(8)], %)
|
||||||
@ -1016,7 +1210,8 @@ fn thing = (x) => {
|
|||||||
return [0, -x]
|
return [0, -x]
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line(thing(8), %)
|
|> line(thing(8), %)
|
||||||
@ -1042,7 +1237,8 @@ fn thing = (x) => {
|
|||||||
return other_thing(x)
|
return other_thing(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, thing(8)], %)
|
|> line([0, thing(8)], %)
|
||||||
@ -1057,7 +1253,8 @@ show(firstExtrude)"#;
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_execute_with_function_sketch() {
|
async fn test_execute_with_function_sketch() {
|
||||||
let ast = r#"fn box = (h, l, w) => {
|
let ast = r#"fn box = (h, l, w) => {
|
||||||
const myBox = startSketchAt([0,0])
|
const myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
@ -1077,7 +1274,8 @@ show(fnBox)"#;
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_get_member_of_object_with_function_period() {
|
async fn test_get_member_of_object_with_function_period() {
|
||||||
let ast = r#"fn box = (obj) => {
|
let ast = r#"fn box = (obj) => {
|
||||||
let myBox = startSketchAt(obj.start)
|
let myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(obj.start, %)
|
||||||
|> line([0, obj.l], %)
|
|> line([0, obj.l], %)
|
||||||
|> line([obj.w, 0], %)
|
|> line([obj.w, 0], %)
|
||||||
|> line([0, -obj.l], %)
|
|> line([0, -obj.l], %)
|
||||||
@ -1097,7 +1295,8 @@ show(thisBox)
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_get_member_of_object_with_function_brace() {
|
async fn test_get_member_of_object_with_function_brace() {
|
||||||
let ast = r#"fn box = (obj) => {
|
let ast = r#"fn box = (obj) => {
|
||||||
let myBox = startSketchAt(obj["start"])
|
let myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(obj["start"], %)
|
||||||
|> line([0, obj["l"]], %)
|
|> line([0, obj["l"]], %)
|
||||||
|> line([obj["w"], 0], %)
|
|> line([obj["w"], 0], %)
|
||||||
|> line([0, -obj["l"]], %)
|
|> line([0, -obj["l"]], %)
|
||||||
@ -1117,7 +1316,8 @@ show(thisBox)
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_get_member_of_object_with_function_mix_period_brace() {
|
async fn test_get_member_of_object_with_function_mix_period_brace() {
|
||||||
let ast = r#"fn box = (obj) => {
|
let ast = r#"fn box = (obj) => {
|
||||||
let myBox = startSketchAt(obj["start"])
|
let myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(obj["start"], %)
|
||||||
|> line([0, obj["l"]], %)
|
|> line([0, obj["l"]], %)
|
||||||
|> line([obj["w"], 0], %)
|
|> line([obj["w"], 0], %)
|
||||||
|> line([10 - obj["w"], -obj.l], %)
|
|> line([10 - obj["w"], -obj.l], %)
|
||||||
@ -1138,7 +1338,8 @@ show(thisBox)
|
|||||||
#[ignore] // ignore til we get loops
|
#[ignore] // ignore til we get loops
|
||||||
async fn test_execute_with_function_sketch_loop_objects() {
|
async fn test_execute_with_function_sketch_loop_objects() {
|
||||||
let ast = r#"fn box = (obj) => {
|
let ast = r#"fn box = (obj) => {
|
||||||
let myBox = startSketchAt(obj.start)
|
let myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(obj.start, %)
|
||||||
|> line([0, obj.l], %)
|
|> line([0, obj.l], %)
|
||||||
|> line([obj.w, 0], %)
|
|> line([obj.w, 0], %)
|
||||||
|> line([0, -obj.l], %)
|
|> line([0, -obj.l], %)
|
||||||
@ -1160,7 +1361,8 @@ for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h:
|
|||||||
#[ignore] // ignore til we get loops
|
#[ignore] // ignore til we get loops
|
||||||
async fn test_execute_with_function_sketch_loop_array() {
|
async fn test_execute_with_function_sketch_loop_array() {
|
||||||
let ast = r#"fn box = (h, l, w, start) => {
|
let ast = r#"fn box = (h, l, w, start) => {
|
||||||
const myBox = startSketchAt([0,0])
|
const myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
@ -1182,7 +1384,8 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_get_member_of_array_with_function() {
|
async fn test_get_member_of_array_with_function() {
|
||||||
let ast = r#"fn box = (array) => {
|
let ast = r#"fn box = (array) => {
|
||||||
let myBox = startSketchAt(array[0])
|
let myBox =startSketchOn('XY')
|
||||||
|
|> startProfileAt(array[0], %)
|
||||||
|> line([0, array[1]], %)
|
|> line([0, array[1]], %)
|
||||||
|> line([array[2], 0], %)
|
|> line([array[2], 0], %)
|
||||||
|> line([0, -array[1]], %)
|
|> line([0, -array[1]], %)
|
||||||
@ -1256,7 +1459,8 @@ const leg1 = 5 // inches
|
|||||||
const leg2 = 8 // inches
|
const leg2 = 8 // inches
|
||||||
fn thickness = () => { return 0.56 }
|
fn thickness = () => { return 0.56 }
|
||||||
|
|
||||||
const bracket = startSketchAt([0,0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, leg1], %)
|
|> line([0, leg1], %)
|
||||||
|> line([leg2, 0], %)
|
|> line([leg2, 0], %)
|
||||||
|> line([0, -thickness()], %)
|
|> line([0, -thickness()], %)
|
||||||
@ -1280,7 +1484,8 @@ const leg2 = 8 // inches
|
|||||||
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
|
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
|
||||||
const thickness = 0.56 // inches. App does not support square root function yet
|
const thickness = 0.56 // inches. App does not support square root function yet
|
||||||
|
|
||||||
const bracket = startSketchAt([0,0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, leg1], %)
|
|> line([0, leg1], %)
|
||||||
|> line([leg2, 0], %)
|
|> line([leg2, 0], %)
|
||||||
|> line([0, -thickness], %)
|
|> line([0, -thickness], %)
|
||||||
@ -1300,7 +1505,8 @@ const leg1 = 5 // inches
|
|||||||
const leg2 = 8 // inches
|
const leg2 = 8 // inches
|
||||||
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
|
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
|
||||||
const thickness = 0.32 // inches. App does not support square root function yet
|
const thickness = 0.32 // inches. App does not support square root function yet
|
||||||
const bracket = startSketchAt([0,0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, leg1], %)
|
|> line([0, leg1], %)
|
||||||
|> line([leg2, 0], %)
|
|> line([leg2, 0], %)
|
||||||
|> line([0, -thickness], %)
|
|> line([0, -thickness], %)
|
||||||
@ -1324,7 +1530,8 @@ const leg1 = 5 // inches
|
|||||||
const leg2 = 8 // inches
|
const leg2 = 8 // inches
|
||||||
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
|
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
|
||||||
const thickness = 0.32 // inches. App does not support square root function yet
|
const thickness = 0.32 // inches. App does not support square root function yet
|
||||||
const bracket = startSketchAt([0,0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, leg1], %)
|
|> line([0, leg1], %)
|
||||||
|> line([leg2, 0], %)
|
|> line([leg2, 0], %)
|
||||||
|> line([0, -thickness], %)
|
|> line([0, -thickness], %)
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use std::{collections::HashMap, str::FromStr};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::{
|
ast::types::{
|
||||||
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, ExpressionStatement,
|
ArrayExpression, BinaryExpression, BinaryPart, BodyItem, CallExpression, CommentStyle, ExpressionStatement,
|
||||||
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta,
|
FunctionExpression, Identifier, Literal, LiteralIdentifier, MemberExpression, MemberObject, NonCodeMeta,
|
||||||
NonCodeNode, NonCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
NonCodeNode, NonCodeValue, ObjectExpression, ObjectKeyInfo, ObjectProperty, PipeExpression, PipeSubstitution,
|
||||||
Program, ReturnStatement, UnaryExpression, UnaryOperator, Value, VariableDeclaration, VariableDeclarator,
|
Program, ReturnStatement, UnaryExpression, UnaryOperator, Value, VariableDeclaration, VariableDeclarator,
|
||||||
@ -13,6 +13,8 @@ use crate::{
|
|||||||
token::{Token, TokenType},
|
token::{Token, TokenType},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod parser_impl;
|
||||||
|
|
||||||
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
|
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
|
||||||
pub const PIPE_OPERATOR: &str = "|>";
|
pub const PIPE_OPERATOR: &str = "|>";
|
||||||
|
|
||||||
@ -180,24 +182,7 @@ impl Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast(&self) -> Result<Program, KclError> {
|
pub fn ast(&self) -> Result<Program, KclError> {
|
||||||
let body = self.make_body(
|
parser_impl::run_parser(&mut self.tokens.as_slice())
|
||||||
0,
|
|
||||||
vec![],
|
|
||||||
NonCodeMeta {
|
|
||||||
non_code_nodes: HashMap::new(),
|
|
||||||
start: None,
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
let end = match self.get_token(body.last_index) {
|
|
||||||
Ok(token) => token.end,
|
|
||||||
Err(_) => self.tokens[self.tokens.len() - 1].end,
|
|
||||||
};
|
|
||||||
Ok(Program {
|
|
||||||
start: 0,
|
|
||||||
end,
|
|
||||||
body: body.body,
|
|
||||||
non_code_meta: body.non_code_meta,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_identifier(&self, index: usize) -> Result<Identifier, KclError> {
|
fn make_identifier(&self, index: usize) -> Result<Identifier, KclError> {
|
||||||
@ -209,7 +194,7 @@ impl Parser {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_literal(&self, index: usize) -> Result<Literal, KclError> {
|
fn make_literal(&self, index: usize) -> Result<Literal, KclError> {
|
||||||
let token = self.get_token(index)?;
|
let token = self.get_token(index)?;
|
||||||
let value = if token.token_type == TokenType::Number {
|
let value = if token.token_type == TokenType::Number {
|
||||||
if let Ok(value) = token.value.parse::<i64>() {
|
if let Ok(value) = token.value.parse::<i64>() {
|
||||||
@ -295,6 +280,11 @@ impl Parser {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let is_block_style = non_code_tokens
|
||||||
|
.first()
|
||||||
|
.map(|tok| matches!(tok.token_type, TokenType::BlockComment))
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let full_string = non_code_tokens
|
let full_string = non_code_tokens
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| {
|
.map(|t| {
|
||||||
@ -336,11 +326,32 @@ impl Parser {
|
|||||||
value: if start_end_string.starts_with("\n\n") && is_new_line_comment {
|
value: if start_end_string.starts_with("\n\n") && is_new_line_comment {
|
||||||
// Preserve if they want a whitespace line before the comment.
|
// Preserve if they want a whitespace line before the comment.
|
||||||
// But let's just allow one.
|
// But let's just allow one.
|
||||||
NonCodeValue::NewLineBlockComment { value: full_string }
|
NonCodeValue::NewLineBlockComment {
|
||||||
|
value: full_string,
|
||||||
|
style: if is_block_style {
|
||||||
|
CommentStyle::Block
|
||||||
|
} else {
|
||||||
|
CommentStyle::Line
|
||||||
|
},
|
||||||
|
}
|
||||||
} else if is_new_line_comment {
|
} else if is_new_line_comment {
|
||||||
NonCodeValue::BlockComment { value: full_string }
|
NonCodeValue::BlockComment {
|
||||||
|
value: full_string,
|
||||||
|
style: if is_block_style {
|
||||||
|
CommentStyle::Block
|
||||||
|
} else {
|
||||||
|
CommentStyle::Line
|
||||||
|
},
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
NonCodeValue::InlineComment { value: full_string }
|
NonCodeValue::InlineComment {
|
||||||
|
value: full_string,
|
||||||
|
style: if is_block_style {
|
||||||
|
CommentStyle::Block
|
||||||
|
} else {
|
||||||
|
CommentStyle::Line
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
Ok((Some(node), end_index - 1))
|
Ok((Some(node), end_index - 1))
|
||||||
@ -552,7 +563,7 @@ impl Parser {
|
|||||||
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
_previous_keys: Option<Vec<ObjectKeyInfo>>,
|
||||||
has_opening_brace: bool,
|
has_opening_brace: bool,
|
||||||
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
) -> Result<Vec<ObjectKeyInfo>, KclError> {
|
||||||
let previous_keys = _previous_keys.unwrap_or(vec![]);
|
let previous_keys = _previous_keys.unwrap_or_default();
|
||||||
let next_token = self.next_meaningful_token(index, None)?;
|
let next_token = self.next_meaningful_token(index, None)?;
|
||||||
if next_token.index == self.tokens.len() - 1 {
|
if next_token.index == self.tokens.len() - 1 {
|
||||||
return Ok(previous_keys);
|
return Ok(previous_keys);
|
||||||
@ -1064,7 +1075,7 @@ impl Parser {
|
|||||||
let mut _non_code_meta: NonCodeMeta;
|
let mut _non_code_meta: NonCodeMeta;
|
||||||
if let Some(node) = next_pipe.non_code_node {
|
if let Some(node) = next_pipe.non_code_node {
|
||||||
_non_code_meta = non_code_meta;
|
_non_code_meta = non_code_meta;
|
||||||
_non_code_meta.non_code_nodes.insert(previous_values.len(), node);
|
_non_code_meta.insert(previous_values.len(), node);
|
||||||
} else {
|
} else {
|
||||||
_non_code_meta = non_code_meta;
|
_non_code_meta = non_code_meta;
|
||||||
}
|
}
|
||||||
@ -1435,7 +1446,7 @@ impl Parser {
|
|||||||
self.make_params(next_brace_or_comma_token.index, _previous_params)
|
self.make_params(next_brace_or_comma_token.index, _previous_params)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
fn make_unary_expression(&self, index: usize) -> Result<UnaryExpressionResult, KclError> {
|
||||||
let current_token = self.get_token(index)?;
|
let current_token = self.get_token(index)?;
|
||||||
let next_token = self.next_meaningful_token(index, None)?;
|
let next_token = self.next_meaningful_token(index, None)?;
|
||||||
if next_token.token.is_none() {
|
if next_token.token.is_none() {
|
||||||
@ -1633,7 +1644,7 @@ impl Parser {
|
|||||||
if previous_body.is_empty() {
|
if previous_body.is_empty() {
|
||||||
non_code_meta.start = next_token.non_code_node;
|
non_code_meta.start = next_token.non_code_node;
|
||||||
} else {
|
} else {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.make_body(next_token.index, previous_body, non_code_meta);
|
return self.make_body(next_token.index, previous_body, non_code_meta);
|
||||||
@ -1641,14 +1652,14 @@ impl Parser {
|
|||||||
|
|
||||||
let next = self.next_meaningful_token(token_index, None)?;
|
let next = self.next_meaningful_token(token_index, None)?;
|
||||||
if let Some(node) = &next.non_code_node {
|
if let Some(node) = &next.non_code_node {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if token.token_type == TokenType::Keyword && VariableKind::from_str(&token.value).is_ok() {
|
if token.token_type == TokenType::Keyword && VariableKind::from_str(&token.value).is_ok() {
|
||||||
let declaration = self.make_variable_declaration(token_index)?;
|
let declaration = self.make_variable_declaration(token_index)?;
|
||||||
let next_thing = self.next_meaningful_token(declaration.last_index, None)?;
|
let next_thing = self.next_meaningful_token(declaration.last_index, None)?;
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
let mut _previous_body = previous_body;
|
let mut _previous_body = previous_body;
|
||||||
_previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration {
|
_previous_body.push(BodyItem::VariableDeclaration(VariableDeclaration {
|
||||||
@ -1669,7 +1680,7 @@ impl Parser {
|
|||||||
let statement = self.make_return_statement(token_index)?;
|
let statement = self.make_return_statement(token_index)?;
|
||||||
let next_thing = self.next_meaningful_token(statement.last_index, None)?;
|
let next_thing = self.next_meaningful_token(statement.last_index, None)?;
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
let mut _previous_body = previous_body;
|
let mut _previous_body = previous_body;
|
||||||
_previous_body.push(BodyItem::ReturnStatement(ReturnStatement {
|
_previous_body.push(BodyItem::ReturnStatement(ReturnStatement {
|
||||||
@ -1693,7 +1704,7 @@ impl Parser {
|
|||||||
let expression = self.make_expression_statement(token_index)?;
|
let expression = self.make_expression_statement(token_index)?;
|
||||||
let next_thing = self.next_meaningful_token(expression.last_index, None)?;
|
let next_thing = self.next_meaningful_token(expression.last_index, None)?;
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
let mut _previous_body = previous_body;
|
let mut _previous_body = previous_body;
|
||||||
_previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement {
|
_previous_body.push(BodyItem::ExpressionStatement(ExpressionStatement {
|
||||||
@ -1716,7 +1727,7 @@ impl Parser {
|
|||||||
&& next_thing_token.token_type == TokenType::Operator
|
&& next_thing_token.token_type == TokenType::Operator
|
||||||
{
|
{
|
||||||
if let Some(node) = &next_thing.non_code_node {
|
if let Some(node) = &next_thing.non_code_node {
|
||||||
non_code_meta.non_code_nodes.insert(previous_body.len(), node.clone());
|
non_code_meta.insert(previous_body.len(), node.clone());
|
||||||
}
|
}
|
||||||
let expression = self.make_expression_statement(token_index)?;
|
let expression = self.make_expression_statement(token_index)?;
|
||||||
let mut _previous_body = previous_body;
|
let mut _previous_body = previous_body;
|
||||||
@ -1913,33 +1924,12 @@ const key = 'c'"#,
|
|||||||
end: 60,
|
end: 60,
|
||||||
value: NonCodeValue::BlockComment {
|
value: NonCodeValue::BlockComment {
|
||||||
value: "this is a comment".to_string(),
|
value: "this is a comment".to_string(),
|
||||||
|
style: CommentStyle::Line,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
31,
|
31,
|
||||||
);
|
);
|
||||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
||||||
let tokens = crate::token::lexer(
|
|
||||||
r#"const mySketch = startSketchAt([0,0])
|
|
||||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
|
||||||
|> lineTo([1, 1], %) /* this is
|
|
||||||
a comment
|
|
||||||
spanning a few lines */
|
|
||||||
|> lineTo({ to: [1,0], tag: "rightPath" }, %)
|
|
||||||
|> close(%)"#,
|
|
||||||
);
|
|
||||||
let parser = Parser::new(tokens);
|
|
||||||
let index = 57;
|
|
||||||
let expected_output = (
|
|
||||||
Some(NonCodeNode {
|
|
||||||
start: 106,
|
|
||||||
end: 166,
|
|
||||||
value: NonCodeValue::BlockComment {
|
|
||||||
value: "this is\n a comment\n spanning a few lines".to_string(),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
59,
|
|
||||||
);
|
|
||||||
assert_eq!(parser.make_non_code_node(index).unwrap(), expected_output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -1988,6 +1978,35 @@ const key = 'c'"#,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_math_parse() {
|
||||||
|
let tokens = crate::token::lexer(r#"5 + "a""#);
|
||||||
|
let actual = Parser::new(tokens).ast().unwrap().body;
|
||||||
|
let expr = BinaryExpression {
|
||||||
|
start: 0,
|
||||||
|
end: 7,
|
||||||
|
operator: BinaryOperator::Add,
|
||||||
|
left: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
start: 0,
|
||||||
|
end: 1,
|
||||||
|
value: serde_json::Value::Number(serde_json::Number::from(5)),
|
||||||
|
raw: "5".to_owned(),
|
||||||
|
})),
|
||||||
|
right: BinaryPart::Literal(Box::new(Literal {
|
||||||
|
start: 4,
|
||||||
|
end: 7,
|
||||||
|
value: serde_json::Value::String("a".to_owned()),
|
||||||
|
raw: r#""a""#.to_owned(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
let expected = vec![BodyItem::ExpressionStatement(ExpressionStatement {
|
||||||
|
start: 0,
|
||||||
|
end: 7,
|
||||||
|
expression: Value::BinaryExpression(Box::new(expr)),
|
||||||
|
})];
|
||||||
|
assert_eq!(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_is_code_token() {
|
fn test_is_code_token() {
|
||||||
let tokens = [
|
let tokens = [
|
||||||
@ -2070,7 +2089,8 @@ const key = 'c'"#,
|
|||||||
fn test_next_meaningful_token() {
|
fn test_next_meaningful_token() {
|
||||||
let _offset = 1;
|
let _offset = 1;
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
r#"const mySketch = startSketchAt([0,0])
|
r#"const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||||
|> lineTo([1, 1], %) /* this is
|
|> lineTo([1, 1], %) /* this is
|
||||||
a comment
|
a comment
|
||||||
@ -2082,10 +2102,10 @@ const key = 'c'"#,
|
|||||||
let index = 17;
|
let index = 17;
|
||||||
let expected_output = TokenReturnWithNonCode {
|
let expected_output = TokenReturnWithNonCode {
|
||||||
token: Some(Token {
|
token: Some(Token {
|
||||||
token_type: TokenType::Brace,
|
token_type: TokenType::Number,
|
||||||
start: 49,
|
start: 60,
|
||||||
end: 50,
|
end: 61,
|
||||||
value: "(".to_string(),
|
value: "0".to_string(),
|
||||||
}),
|
}),
|
||||||
index: 18,
|
index: 18,
|
||||||
non_code_node: None,
|
non_code_node: None,
|
||||||
@ -2095,9 +2115,9 @@ const key = 'c'"#,
|
|||||||
let expected_output = TokenReturnWithNonCode {
|
let expected_output = TokenReturnWithNonCode {
|
||||||
token: Some(Token {
|
token: Some(Token {
|
||||||
token_type: TokenType::Brace,
|
token_type: TokenType::Brace,
|
||||||
start: 50,
|
start: 61,
|
||||||
end: 51,
|
end: 62,
|
||||||
value: "{".to_string(),
|
value: "]".to_string(),
|
||||||
}),
|
}),
|
||||||
index: 19,
|
index: 19,
|
||||||
non_code_node: None,
|
non_code_node: None,
|
||||||
@ -2106,10 +2126,10 @@ const key = 'c'"#,
|
|||||||
let index = 21;
|
let index = 21;
|
||||||
let expected_output = TokenReturnWithNonCode {
|
let expected_output = TokenReturnWithNonCode {
|
||||||
token: Some(Token {
|
token: Some(Token {
|
||||||
token_type: TokenType::Colon,
|
token_type: TokenType::Operator,
|
||||||
start: 54,
|
start: 64,
|
||||||
end: 55,
|
end: 65,
|
||||||
value: ":".to_string(),
|
value: "%".to_string(),
|
||||||
}),
|
}),
|
||||||
index: 22,
|
index: 22,
|
||||||
non_code_node: None,
|
non_code_node: None,
|
||||||
@ -2118,10 +2138,10 @@ const key = 'c'"#,
|
|||||||
let index = 24;
|
let index = 24;
|
||||||
let expected_output = TokenReturnWithNonCode {
|
let expected_output = TokenReturnWithNonCode {
|
||||||
token: Some(Token {
|
token: Some(Token {
|
||||||
token_type: TokenType::Number,
|
token_type: TokenType::Operator,
|
||||||
start: 57,
|
start: 69,
|
||||||
end: 58,
|
end: 71,
|
||||||
value: "0".to_string(),
|
value: "|>".to_string(),
|
||||||
}),
|
}),
|
||||||
index: 25,
|
index: 25,
|
||||||
non_code_node: None,
|
non_code_node: None,
|
||||||
@ -2130,324 +2150,12 @@ const key = 'c'"#,
|
|||||||
let index = 25;
|
let index = 25;
|
||||||
let expected_output = TokenReturnWithNonCode {
|
let expected_output = TokenReturnWithNonCode {
|
||||||
token: Some(Token {
|
token: Some(Token {
|
||||||
token_type: TokenType::Comma,
|
token_type: TokenType::Word,
|
||||||
start: 58,
|
start: 72,
|
||||||
end: 59,
|
end: 78,
|
||||||
value: ",".to_string(),
|
value: "lineTo".to_string(),
|
||||||
}),
|
}),
|
||||||
index: 26,
|
index: 27,
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 28;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 61,
|
|
||||||
end: 62,
|
|
||||||
value: "]".to_string(),
|
|
||||||
}),
|
|
||||||
index: 29,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 29;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 62,
|
|
||||||
end: 63,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 30,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 32;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Colon,
|
|
||||||
start: 67,
|
|
||||||
end: 68,
|
|
||||||
value: ":".to_string(),
|
|
||||||
}),
|
|
||||||
index: 33,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 37;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 79,
|
|
||||||
end: 80,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 38,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 40;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 82,
|
|
||||||
end: 83,
|
|
||||||
value: ")".to_string(),
|
|
||||||
}),
|
|
||||||
index: 41,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 45;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 95,
|
|
||||||
end: 96,
|
|
||||||
value: "(".to_string(),
|
|
||||||
}),
|
|
||||||
index: 46,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 46;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 96,
|
|
||||||
end: 97,
|
|
||||||
value: "[".to_string(),
|
|
||||||
}),
|
|
||||||
index: 47,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 47;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Number,
|
|
||||||
start: 97,
|
|
||||||
end: 98,
|
|
||||||
value: "1".to_string(),
|
|
||||||
}),
|
|
||||||
index: 48,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 48;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 98,
|
|
||||||
end: 99,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 49,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 51;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 101,
|
|
||||||
end: 102,
|
|
||||||
value: "]".to_string(),
|
|
||||||
}),
|
|
||||||
index: 52,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 52;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 102,
|
|
||||||
end: 103,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 53,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 55;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 105,
|
|
||||||
end: 106,
|
|
||||||
value: ")".to_string(),
|
|
||||||
}),
|
|
||||||
index: 56,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 62;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 175,
|
|
||||||
end: 176,
|
|
||||||
value: "(".to_string(),
|
|
||||||
}),
|
|
||||||
index: 63,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 63;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 176,
|
|
||||||
end: 177,
|
|
||||||
value: "{".to_string(),
|
|
||||||
}),
|
|
||||||
index: 64,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 66;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Colon,
|
|
||||||
start: 180,
|
|
||||||
end: 181,
|
|
||||||
value: ":".to_string(),
|
|
||||||
}),
|
|
||||||
index: 67,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 69;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Number,
|
|
||||||
start: 183,
|
|
||||||
end: 184,
|
|
||||||
value: "1".to_string(),
|
|
||||||
}),
|
|
||||||
index: 70,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 70;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 184,
|
|
||||||
end: 185,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 71,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 71;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Number,
|
|
||||||
start: 185,
|
|
||||||
end: 186,
|
|
||||||
value: "0".to_string(),
|
|
||||||
}),
|
|
||||||
index: 72,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 72;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 186,
|
|
||||||
end: 187,
|
|
||||||
value: "]".to_string(),
|
|
||||||
}),
|
|
||||||
index: 73,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 73;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 187,
|
|
||||||
end: 188,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 74,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 76;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Colon,
|
|
||||||
start: 192,
|
|
||||||
end: 193,
|
|
||||||
value: ":".to_string(),
|
|
||||||
}),
|
|
||||||
index: 77,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 81;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Comma,
|
|
||||||
start: 207,
|
|
||||||
end: 208,
|
|
||||||
value: ",".to_string(),
|
|
||||||
}),
|
|
||||||
index: 82,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 84;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 210,
|
|
||||||
end: 211,
|
|
||||||
value: ")".to_string(),
|
|
||||||
}),
|
|
||||||
index: 85,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 89;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 222,
|
|
||||||
end: 223,
|
|
||||||
value: "(".to_string(),
|
|
||||||
}),
|
|
||||||
index: 90,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 90;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Operator,
|
|
||||||
start: 223,
|
|
||||||
end: 224,
|
|
||||||
value: "%".to_string(),
|
|
||||||
}),
|
|
||||||
index: 91,
|
|
||||||
non_code_node: None,
|
|
||||||
};
|
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
|
||||||
let index = 91;
|
|
||||||
let expected_output = TokenReturnWithNonCode {
|
|
||||||
token: Some(Token {
|
|
||||||
token_type: TokenType::Brace,
|
|
||||||
start: 224,
|
|
||||||
end: 225,
|
|
||||||
value: ")".to_string(),
|
|
||||||
}),
|
|
||||||
index: 92,
|
|
||||||
non_code_node: None,
|
non_code_node: None,
|
||||||
};
|
};
|
||||||
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
|
||||||
@ -2456,7 +2164,8 @@ const key = 'c'"#,
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_find_closing_brace() {
|
fn test_find_closing_brace() {
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
r#"const mySketch = startSketchAt([0,0])
|
r#"const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||||
|> lineTo([1, 1], %) /* this is
|
|> lineTo([1, 1], %) /* this is
|
||||||
a comment
|
a comment
|
||||||
@ -2465,11 +2174,10 @@ const key = 'c'"#,
|
|||||||
|> close(%)"#,
|
|> close(%)"#,
|
||||||
);
|
);
|
||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
assert_eq!(parser.find_closing_brace(7, 0, "").unwrap(), 13);
|
assert_eq!(parser.find_closing_brace(7, 0, "").unwrap(), 9);
|
||||||
assert_eq!(parser.find_closing_brace(18, 0, "").unwrap(), 41);
|
assert_eq!(parser.find_closing_brace(14, 0, "").unwrap(), 23);
|
||||||
assert_eq!(parser.find_closing_brace(46, 0, "").unwrap(), 56);
|
assert_eq!(parser.find_closing_brace(29, 0, "").unwrap(), 47);
|
||||||
assert_eq!(parser.find_closing_brace(63, 0, "").unwrap(), 85);
|
assert_eq!(parser.find_closing_brace(57, 0, "").unwrap(), 62);
|
||||||
assert_eq!(parser.find_closing_brace(90, 0, "").unwrap(), 92);
|
|
||||||
|
|
||||||
let basic = "( hey )";
|
let basic = "( hey )";
|
||||||
let parser = Parser::new(crate::token::lexer(basic));
|
let parser = Parser::new(crate::token::lexer(basic));
|
||||||
@ -2490,7 +2198,8 @@ const key = 'c'"#,
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_is_call_expression() {
|
fn test_is_call_expression() {
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
r#"const mySketch = startSketchAt([0,0])
|
r#"const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||||
|> lineTo([1, 1], %) /* this is
|
|> lineTo([1, 1], %) /* this is
|
||||||
a comment
|
a comment
|
||||||
@ -2501,8 +2210,8 @@ const key = 'c'"#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
|
|
||||||
assert_eq!(parser.is_call_expression(4).unwrap(), None);
|
assert_eq!(parser.is_call_expression(4).unwrap(), None);
|
||||||
assert_eq!(parser.is_call_expression(6).unwrap(), Some(13));
|
assert_eq!(parser.is_call_expression(6).unwrap(), Some(9));
|
||||||
assert_eq!(parser.is_call_expression(15).unwrap(), None);
|
assert_eq!(parser.is_call_expression(9).unwrap(), None);
|
||||||
assert_eq!(parser.is_call_expression(43).unwrap(), None);
|
assert_eq!(parser.is_call_expression(43).unwrap(), None);
|
||||||
assert_eq!(parser.is_call_expression(60).unwrap(), None);
|
assert_eq!(parser.is_call_expression(60).unwrap(), None);
|
||||||
assert_eq!(parser.is_call_expression(87).unwrap(), None);
|
assert_eq!(parser.is_call_expression(87).unwrap(), None);
|
||||||
@ -2511,7 +2220,8 @@ const key = 'c'"#,
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_find_next_declaration_keyword() {
|
fn test_find_next_declaration_keyword() {
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
r#"const mySketch = startSketchAt([0,0])
|
r#"const mySketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|
||||||
|> lineTo([1, 1], %) /* this is
|
|> lineTo([1, 1], %) /* this is
|
||||||
a comment
|
a comment
|
||||||
@ -2522,7 +2232,10 @@ const key = 'c'"#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parser.find_next_declaration_keyword(4).unwrap(),
|
parser.find_next_declaration_keyword(4).unwrap(),
|
||||||
TokenReturn { token: None, index: 92 }
|
TokenReturn {
|
||||||
|
token: None,
|
||||||
|
index: 102
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
@ -2986,7 +2699,8 @@ show(mySk1)"#;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_parse_half_pipe_small() {
|
fn test_parse_half_pipe_small() {
|
||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
"const secondExtrude = startSketchAt([0,0])
|
"const secondExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|",
|
|",
|
||||||
);
|
);
|
||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
@ -3067,7 +2781,8 @@ const height = [obj["a"] -1, 0]"#,
|
|||||||
let tokens = crate::token::lexer(
|
let tokens = crate::token::lexer(
|
||||||
"const height = 10
|
"const height = 10
|
||||||
|
|
||||||
const firstExtrude = startSketchAt([0,0])
|
const firstExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, 8], %)
|
|> line([0, 8], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, -8], %)
|
|> line([0, -8], %)
|
||||||
@ -3076,7 +2791,8 @@ const firstExtrude = startSketchAt([0,0])
|
|||||||
|
|
||||||
show(firstExtrude)
|
show(firstExtrude)
|
||||||
|
|
||||||
const secondExtrude = startSketchAt([0,0])
|
const secondExtrude = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|",
|
|",
|
||||||
);
|
);
|
||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
@ -3137,10 +2853,6 @@ z(-[["#,
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
|
||||||
result.err().unwrap().to_string(),
|
|
||||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([1, 2])], message: "missing a closing brace for the function call" }"#
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -3156,7 +2868,7 @@ z(-[["#,
|
|||||||
// https://github.com/KittyCAD/modeling-app/issues/696
|
// https://github.com/KittyCAD/modeling-app/issues/696
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"semantic: KclErrorDetails { source_ranges: [], message: "file is empty" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [], message: "file is empty" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3170,7 +2882,7 @@ z(-[["#,
|
|||||||
// https://github.com/KittyCAD/modeling-app/issues/696
|
// https://github.com/KittyCAD/modeling-app/issues/696
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"semantic: KclErrorDetails { source_ranges: [], message: "file is empty" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [], message: "file is empty" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3188,7 +2900,7 @@ e
|
|||||||
.err()
|
.err()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_string()
|
.to_string()
|
||||||
.contains("expected to be started on a identifier or literal"));
|
.contains("expected whitespace, found ')' which is brace"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -3197,7 +2909,11 @@ e
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result.err().unwrap().to_string().contains("expected another token"));
|
assert!(result
|
||||||
|
.err()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
.contains("expected whitespace, found ')' which is brace"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -3209,11 +2925,7 @@ e
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert!(result
|
assert!(result.err().unwrap().to_string().contains("Unexpected token"));
|
||||||
.err()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
.contains("unexpected end of expression"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -3347,7 +3059,9 @@ e
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_stdlib_in_fn_name() {
|
fn test_error_stdlib_in_fn_name() {
|
||||||
let some_program_string = r#"fn cos = () {}"#;
|
let some_program_string = r#"fn cos = () => {
|
||||||
|
return 1
|
||||||
|
}"#;
|
||||||
let tokens = crate::token::lexer(some_program_string);
|
let tokens = crate::token::lexer(some_program_string);
|
||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
@ -3448,16 +3162,20 @@ thing(false)
|
|||||||
let parser = Parser::new(tokens);
|
let parser = Parser::new(tokens);
|
||||||
let result = parser.ast();
|
let result = parser.ast();
|
||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
|
// TODO: https://github.com/KittyCAD/modeling-app/issues/784
|
||||||
|
// Improve this error message.
|
||||||
|
// It should say that the compiler is expecting a function expression on the RHS.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 2])], message: "Expected a `let` variable kind, found: `fn`" }"#
|
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([11, 18])], message: "Unexpected token" }"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_member_expression_sketch_group() {
|
fn test_member_expression_sketch_group() {
|
||||||
let some_program_string = r#"fn cube = (pos, scale) => {
|
let some_program_string = r#"fn cube = (pos, scale) => {
|
||||||
const sg = startSketchAt(pos)
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt(pos, %)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
|> line([scale, 0], %)
|
|> line([scale, 0], %)
|
||||||
|> line([0, -scale], %)
|
|> line([0, -scale], %)
|
||||||
@ -3487,19 +3205,11 @@ let other_thing = 2 * cos(3)"#;
|
|||||||
parser.ast().unwrap();
|
parser.ast().unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_parse_pipes_on_pipes() {
|
|
||||||
let code = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
|
|
||||||
|
|
||||||
let tokens = crate::token::lexer(code);
|
|
||||||
let parser = Parser::new(tokens);
|
|
||||||
parser.ast().unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_negative_arguments() {
|
fn test_negative_arguments() {
|
||||||
let some_program_string = r#"fn box = (p, h, l, w) => {
|
let some_program_string = r#"fn box = (p, h, l, w) => {
|
||||||
const myBox = startSketchAt(p)
|
const myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(p, %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
|
|||||||
1967
src/wasm-lib/kcl/src/parser/parser_impl.rs
Normal file
1967
src/wasm-lib/kcl/src/parser/parser_impl.rs
Normal file
File diff suppressed because it is too large
Load Diff
107
src/wasm-lib/kcl/src/parser/parser_impl/error.rs
Normal file
107
src/wasm-lib/kcl/src/parser/parser_impl/error.rs
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
use winnow::error::{ErrorKind, ParseError, StrContext};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
token::Token,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Accumulate context while backtracking errors
|
||||||
|
/// Very similar to [`winnow::error::ContextError`] type,
|
||||||
|
/// but the 'cause' field is always a [`KclError`],
|
||||||
|
/// instead of a dynamic [`std::error::Error`] trait object.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ContextError<C = StrContext> {
|
||||||
|
pub context: Vec<C>,
|
||||||
|
pub cause: Option<KclError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseError<&[Token], ContextError>> for KclError {
|
||||||
|
fn from(err: ParseError<&[Token], ContextError>) -> Self {
|
||||||
|
let Some(last_token) = err.input().last() else {
|
||||||
|
return KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: Default::default(),
|
||||||
|
message: "file is empty".to_owned(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
let (input, offset, err) = (err.input().to_vec(), err.offset(), err.into_inner());
|
||||||
|
|
||||||
|
if let Some(e) = err.cause {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
// See docs on `offset`.
|
||||||
|
if offset >= input.len() {
|
||||||
|
let context = err.context.first();
|
||||||
|
return KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: last_token.as_source_ranges(),
|
||||||
|
message: match context {
|
||||||
|
Some(what) => format!("Unexpected end of file. The compiler {what}"),
|
||||||
|
None => "Unexpected end of file while still parsing".to_owned(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let bad_token = &input[offset];
|
||||||
|
// TODO: Add the Winnow parser context to the error.
|
||||||
|
// See https://github.com/KittyCAD/modeling-app/issues/784
|
||||||
|
KclError::Syntax(KclErrorDetails {
|
||||||
|
source_ranges: bad_token.as_source_ranges(),
|
||||||
|
message: "Unexpected token".to_owned(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> From<KclError> for ContextError<C> {
|
||||||
|
fn from(e: KclError) -> Self {
|
||||||
|
Self {
|
||||||
|
context: Default::default(),
|
||||||
|
cause: Some(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C> std::default::Default for ContextError<C> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
context: Default::default(),
|
||||||
|
cause: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, C> winnow::error::ParserError<I> for ContextError<C> {
|
||||||
|
#[inline]
|
||||||
|
fn from_error_kind(_input: &I, _kind: ErrorKind) -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn append(self, _input: &I, _kind: ErrorKind) -> Self {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn or(self, other: Self) -> Self {
|
||||||
|
other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, I> winnow::error::AddContext<I, C> for ContextError<C> {
|
||||||
|
#[inline]
|
||||||
|
fn add_context(mut self, _input: &I, ctx: C) -> Self {
|
||||||
|
self.context.push(ctx);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<C, I> winnow::error::FromExternalError<I, KclError> for ContextError<C> {
|
||||||
|
#[inline]
|
||||||
|
fn from_external_error(_input: &I, _kind: ErrorKind, e: KclError) -> Self {
|
||||||
|
let mut err = Self::default();
|
||||||
|
{
|
||||||
|
err.cause = Some(e);
|
||||||
|
}
|
||||||
|
err
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -17,9 +17,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ast::types::parse_json_number_as_f64,
|
ast::types::parse_json_number_as_f64,
|
||||||
engine::{EngineConnection, EngineManager},
|
engine::EngineManager,
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{ExtrudeGroup, MemoryItem, Metadata, SketchGroup, SourceRange},
|
executor::{ExecutorContext, ExtrudeGroup, MemoryItem, Metadata, Plane, SketchGroup, SourceRange},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
pub type StdFn = fn(Args) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<MemoryItem, KclError>>>>;
|
||||||
@ -59,6 +59,8 @@ impl StdLib {
|
|||||||
Box::new(crate::std::sketch::AngledLineOfYLength),
|
Box::new(crate::std::sketch::AngledLineOfYLength),
|
||||||
Box::new(crate::std::sketch::AngledLineThatIntersects),
|
Box::new(crate::std::sketch::AngledLineThatIntersects),
|
||||||
Box::new(crate::std::sketch::StartSketchAt),
|
Box::new(crate::std::sketch::StartSketchAt),
|
||||||
|
Box::new(crate::std::sketch::StartSketchOn),
|
||||||
|
Box::new(crate::std::sketch::StartProfileAt),
|
||||||
Box::new(crate::std::sketch::Close),
|
Box::new(crate::std::sketch::Close),
|
||||||
Box::new(crate::std::sketch::Arc),
|
Box::new(crate::std::sketch::Arc),
|
||||||
Box::new(crate::std::sketch::TangentalArc),
|
Box::new(crate::std::sketch::TangentalArc),
|
||||||
@ -109,15 +111,15 @@ impl Default for StdLib {
|
|||||||
pub struct Args {
|
pub struct Args {
|
||||||
pub args: Vec<MemoryItem>,
|
pub args: Vec<MemoryItem>,
|
||||||
pub source_range: SourceRange,
|
pub source_range: SourceRange,
|
||||||
engine: EngineConnection,
|
pub ctx: ExecutorContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub fn new(args: Vec<MemoryItem>, source_range: SourceRange, engine: EngineConnection) -> Self {
|
pub fn new(args: Vec<MemoryItem>, source_range: SourceRange, ctx: ExecutorContext) -> Self {
|
||||||
Self {
|
Self {
|
||||||
args,
|
args,
|
||||||
source_range,
|
source_range,
|
||||||
engine,
|
ctx,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +128,7 @@ impl Args {
|
|||||||
id: uuid::Uuid,
|
id: uuid::Uuid,
|
||||||
cmd: kittycad::types::ModelingCmd,
|
cmd: kittycad::types::ModelingCmd,
|
||||||
) -> Result<OkWebSocketResponseData, KclError> {
|
) -> Result<OkWebSocketResponseData, KclError> {
|
||||||
self.engine.send_modeling_cmd(id, self.source_range, cmd).await
|
self.ctx.engine.send_modeling_cmd(id, self.source_range, cmd).await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
fn make_user_val_from_json(&self, j: serde_json::Value) -> Result<MemoryItem, KclError> {
|
||||||
@ -308,6 +310,44 @@ impl Args {
|
|||||||
Ok((data, sketch_group))
|
Ok((data, sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_data_and_plane<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<Plane>), KclError> {
|
||||||
|
let first_value = self
|
||||||
|
.args
|
||||||
|
.first()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?
|
||||||
|
.get_json_value()?;
|
||||||
|
|
||||||
|
let data: T = serde_json::from_value(first_value).map_err(|e| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let second_value = self.args.get(1).ok_or_else(|| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a Plane as the second argument, found `{:?}`", self.args),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let plane = if let MemoryItem::Plane(p) = second_value {
|
||||||
|
p.clone()
|
||||||
|
} else {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: format!("Expected a Plane as the second argument, found `{:?}`", self.args),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((data, plane))
|
||||||
|
}
|
||||||
|
|
||||||
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
|
fn get_segment_name_to_number_sketch_group(&self) -> Result<(String, f64, Box<SketchGroup>), KclError> {
|
||||||
// Iterate over our args, the first argument should be a UserVal with a string value.
|
// Iterate over our args, the first argument should be a UserVal with a string value.
|
||||||
// The second argument should be a number.
|
// The second argument should be a number.
|
||||||
|
|||||||
@ -9,7 +9,9 @@ use serde::{Deserialize, Serialize};
|
|||||||
use super::utils::Angle;
|
use super::utils::Angle;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
executor::{BasePath, GeoMeta, MemoryItem, Path, Point2d, Position, Rotation, SketchGroup},
|
executor::{
|
||||||
|
BasePath, GeoMeta, MemoryItem, Path, Plane, PlaneType, Point2d, Point3d, Position, Rotation, SketchGroup,
|
||||||
|
},
|
||||||
std::{
|
std::{
|
||||||
utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
|
utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
|
||||||
Args,
|
Args,
|
||||||
@ -649,11 +651,202 @@ pub async fn start_sketch_at(args: Args) -> Result<MemoryItem, KclError> {
|
|||||||
Ok(MemoryItem::SketchGroup(sketch_group))
|
Ok(MemoryItem::SketchGroup(sketch_group))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start a sketch at a given point.
|
/// Start a sketch at a given point on the 'XY' plane.
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "startSketchAt",
|
name = "startSketchAt",
|
||||||
}]
|
}]
|
||||||
async fn inner_start_sketch_at(data: LineData, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
async fn inner_start_sketch_at(data: LineData, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
||||||
|
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
|
||||||
|
let xy_plane = PlaneData::XY;
|
||||||
|
let plane = inner_start_sketch_on(xy_plane, args.clone()).await?;
|
||||||
|
let sketch_group = inner_start_profile_at(data, plane, args).await?;
|
||||||
|
Ok(sketch_group)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Data for a plane.
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub enum PlaneData {
|
||||||
|
/// The XY plane.
|
||||||
|
#[serde(rename = "XY", alias = "xy")]
|
||||||
|
XY,
|
||||||
|
/// The opposite side of the XY plane.
|
||||||
|
#[serde(rename = "-XY", alias = "-xy")]
|
||||||
|
NegXY,
|
||||||
|
/// The XZ plane.
|
||||||
|
#[serde(rename = "XZ", alias = "xz")]
|
||||||
|
XZ,
|
||||||
|
/// The opposite side of the XZ plane.
|
||||||
|
#[serde(rename = "-XZ", alias = "-xz")]
|
||||||
|
NegXZ,
|
||||||
|
/// The YZ plane.
|
||||||
|
#[serde(rename = "YZ", alias = "yz")]
|
||||||
|
YZ,
|
||||||
|
/// The opposite side of the YZ plane.
|
||||||
|
#[serde(rename = "-YZ", alias = "-yz")]
|
||||||
|
NegYZ,
|
||||||
|
/// A defined plane.
|
||||||
|
Plane {
|
||||||
|
/// Origin of the plane.
|
||||||
|
origin: Box<Point3d>,
|
||||||
|
/// What should the plane’s X axis be?
|
||||||
|
x_axis: Box<Point3d>,
|
||||||
|
/// What should the plane’s Y axis be?
|
||||||
|
y_axis: Box<Point3d>,
|
||||||
|
/// The z-axis (normal).
|
||||||
|
z_axis: Box<Point3d>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PlaneData> for Plane {
|
||||||
|
fn from(value: PlaneData) -> Self {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
match value {
|
||||||
|
PlaneData::XY => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(1.0, 0.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 1.0, 0.0),
|
||||||
|
z_axis: Point3d::new(0.0, 0.0, 1.0),
|
||||||
|
value: PlaneType::XY,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::NegXY => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(1.0, 0.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 1.0, 0.0),
|
||||||
|
z_axis: Point3d::new(0.0, 0.0, -1.0),
|
||||||
|
value: PlaneType::XY,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::XZ => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(1.0, 0.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 0.0, 1.0),
|
||||||
|
z_axis: Point3d::new(0.0, 1.0, 0.0),
|
||||||
|
value: PlaneType::XZ,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::NegXZ => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(1.0, 0.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 0.0, 1.0),
|
||||||
|
z_axis: Point3d::new(0.0, -1.0, 0.0),
|
||||||
|
value: PlaneType::XZ,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::YZ => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(0.0, 1.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 0.0, 1.0),
|
||||||
|
z_axis: Point3d::new(1.0, 0.0, 0.0),
|
||||||
|
value: PlaneType::YZ,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::NegYZ => Plane {
|
||||||
|
id,
|
||||||
|
origin: Point3d::new(0.0, 0.0, 0.0),
|
||||||
|
x_axis: Point3d::new(0.0, 1.0, 0.0),
|
||||||
|
y_axis: Point3d::new(0.0, 0.0, 1.0),
|
||||||
|
z_axis: Point3d::new(-1.0, 0.0, 0.0),
|
||||||
|
value: PlaneType::YZ,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
PlaneData::Plane {
|
||||||
|
origin,
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
z_axis,
|
||||||
|
} => Plane {
|
||||||
|
id,
|
||||||
|
origin: *origin,
|
||||||
|
x_axis: *x_axis,
|
||||||
|
y_axis: *y_axis,
|
||||||
|
z_axis: *z_axis,
|
||||||
|
value: PlaneType::Custom,
|
||||||
|
meta: vec![],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a sketch on a specific plane.
|
||||||
|
pub async fn start_sketch_on(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let data: PlaneData = args.get_data()?;
|
||||||
|
|
||||||
|
let plane = inner_start_sketch_on(data, args).await?;
|
||||||
|
Ok(MemoryItem::Plane(plane))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a sketch at a given point.
|
||||||
|
#[stdlib {
|
||||||
|
name = "startSketchOn",
|
||||||
|
}]
|
||||||
|
async fn inner_start_sketch_on(data: PlaneData, args: Args) -> Result<Box<Plane>, KclError> {
|
||||||
|
let mut plane: Plane = data.clone().into();
|
||||||
|
|
||||||
|
plane.id = match data {
|
||||||
|
PlaneData::XY | PlaneData::NegXY => args.ctx.planes.xy,
|
||||||
|
PlaneData::XZ | PlaneData::NegXZ => args.ctx.planes.xz,
|
||||||
|
PlaneData::YZ | PlaneData::NegYZ => args.ctx.planes.yz,
|
||||||
|
PlaneData::Plane {
|
||||||
|
origin,
|
||||||
|
x_axis,
|
||||||
|
y_axis,
|
||||||
|
z_axis: _,
|
||||||
|
} => {
|
||||||
|
let id = uuid::Uuid::new_v4();
|
||||||
|
// Create the plane.
|
||||||
|
args.send_modeling_cmd(
|
||||||
|
id,
|
||||||
|
ModelingCmd::MakePlane {
|
||||||
|
clobber: false,
|
||||||
|
origin: (*origin).into(),
|
||||||
|
size: 60.0,
|
||||||
|
x_axis: (*x_axis).into(),
|
||||||
|
y_axis: (*y_axis).into(),
|
||||||
|
hide: Some(true),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
id
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enter sketch mode on the plane.
|
||||||
|
args.send_modeling_cmd(
|
||||||
|
uuid::Uuid::new_v4(),
|
||||||
|
ModelingCmd::SketchModeEnable {
|
||||||
|
animated: false,
|
||||||
|
ortho: false,
|
||||||
|
plane_id: plane.id,
|
||||||
|
// We pass in the normal for the plane here.
|
||||||
|
disable_camera_with_plane: Some(plane.z_axis.clone().into()),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(Box::new(plane))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a profile at a given point.
|
||||||
|
pub async fn start_profile_at(args: Args) -> Result<MemoryItem, KclError> {
|
||||||
|
let (data, plane): (LineData, Box<Plane>) = args.get_data_and_plane()?;
|
||||||
|
|
||||||
|
let sketch_group = inner_start_profile_at(data, plane, args).await?;
|
||||||
|
Ok(MemoryItem::SketchGroup(sketch_group))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a profile at a given point.
|
||||||
|
#[stdlib {
|
||||||
|
name = "startProfileAt",
|
||||||
|
}]
|
||||||
|
async fn inner_start_profile_at(data: LineData, plane: Box<Plane>, args: Args) -> Result<Box<SketchGroup>, KclError> {
|
||||||
let to = match &data {
|
let to = match &data {
|
||||||
LineData::PointWithTag { to, .. } => *to,
|
LineData::PointWithTag { to, .. } => *to,
|
||||||
LineData::Point(to) => *to,
|
LineData::Point(to) => *to,
|
||||||
@ -694,6 +887,7 @@ async fn inner_start_sketch_at(data: LineData, args: Args) -> Result<Box<SketchG
|
|||||||
id: path_id,
|
id: path_id,
|
||||||
position: Position([0.0, 0.0, 0.0]),
|
position: Position([0.0, 0.0, 0.0]),
|
||||||
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
|
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
|
||||||
|
plane_id: Some(plane.id),
|
||||||
value: vec![],
|
value: vec![],
|
||||||
start: current_path,
|
start: current_path,
|
||||||
meta: vec![args.source_range.into()],
|
meta: vec![args.source_range.into()],
|
||||||
@ -728,6 +922,13 @@ async fn inner_close(sketch_group: Box<SketchGroup>, args: Args) -> Result<Box<S
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
// Exit sketch mode, since if we were in a plane we'd want to disable the sketch mode after.
|
||||||
|
if sketch_group.plane_id.is_some() {
|
||||||
|
// We were on a plane, disable the sketch mode.
|
||||||
|
args.send_modeling_cmd(uuid::Uuid::new_v4(), ModelingCmd::SketchModeDisable {})
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
let mut new_sketch_group = sketch_group.clone();
|
let mut new_sketch_group = sketch_group.clone();
|
||||||
new_sketch_group.value.push(Path::ToPoint {
|
new_sketch_group.value.push(Path::ToPoint {
|
||||||
base: BasePath {
|
base: BasePath {
|
||||||
@ -1199,7 +1400,7 @@ mod tests {
|
|||||||
|
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use crate::std::sketch::LineData;
|
use crate::std::sketch::{LineData, PlaneData};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_deserialize_line_data() {
|
fn test_deserialize_line_data() {
|
||||||
@ -1221,4 +1422,23 @@ mod tests {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deserialize_plane_data() {
|
||||||
|
let data = PlaneData::XY;
|
||||||
|
let mut str_json = serde_json::to_string(&data).unwrap();
|
||||||
|
assert_eq!(str_json, "\"XY\"");
|
||||||
|
|
||||||
|
str_json = "\"YZ\"".to_string();
|
||||||
|
let data: PlaneData = serde_json::from_str(&str_json).unwrap();
|
||||||
|
assert_eq!(data, PlaneData::YZ);
|
||||||
|
|
||||||
|
str_json = "\"-YZ\"".to_string();
|
||||||
|
let data: PlaneData = serde_json::from_str(&str_json).unwrap();
|
||||||
|
assert_eq!(data, PlaneData::NegYZ);
|
||||||
|
|
||||||
|
str_json = "\"-xz\"".to_string();
|
||||||
|
let data: PlaneData = serde_json::from_str(&str_json).unwrap();
|
||||||
|
assert_eq!(data, PlaneData::NegXZ);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,8 @@ use schemars::JsonSchema;
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tower_lsp::lsp_types::SemanticTokenType;
|
use tower_lsp::lsp_types::SemanticTokenType;
|
||||||
|
|
||||||
|
use crate::{ast::types::VariableKind, executor::SourceRange};
|
||||||
|
|
||||||
mod tokeniser;
|
mod tokeniser;
|
||||||
|
|
||||||
/// The types of tokens.
|
/// The types of tokens.
|
||||||
@ -142,15 +144,39 @@ impl Token {
|
|||||||
TokenType::Whitespace | TokenType::LineComment | TokenType::BlockComment
|
TokenType::Whitespace | TokenType::LineComment | TokenType::BlockComment
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_source_range(&self) -> SourceRange {
|
||||||
|
SourceRange([self.start, self.end])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_source_ranges(&self) -> Vec<SourceRange> {
|
||||||
|
vec![self.as_source_range()]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Is this token the beginning of a variable/function declaration?
|
||||||
|
/// If so, what kind?
|
||||||
|
/// If not, returns None.
|
||||||
|
pub fn declaration_keyword(&self) -> Option<VariableKind> {
|
||||||
|
if !matches!(self.token_type, TokenType::Keyword) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(match self.value.as_str() {
|
||||||
|
"var" => VariableKind::Var,
|
||||||
|
"let" => VariableKind::Let,
|
||||||
|
"fn" => VariableKind::Fn,
|
||||||
|
"const" => VariableKind::Const,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Token> for crate::executor::SourceRange {
|
impl From<Token> for SourceRange {
|
||||||
fn from(token: Token) -> Self {
|
fn from(token: Token) -> Self {
|
||||||
Self([token.start, token.end])
|
Self([token.start, token.end])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Token> for crate::executor::SourceRange {
|
impl From<&Token> for SourceRange {
|
||||||
fn from(token: &Token) -> Self {
|
fn from(token: &Token) -> Self {
|
||||||
Self([token.start, token.end])
|
Self([token.start, token.end])
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1434,13 +1434,13 @@ const things = "things"
|
|||||||
fn test_kitt() {
|
fn test_kitt() {
|
||||||
let program = include_str!("../../../tests/executor/inputs/kittycad_svg.kcl");
|
let program = include_str!("../../../tests/executor/inputs/kittycad_svg.kcl");
|
||||||
let actual = lexer(program).unwrap();
|
let actual = lexer(program).unwrap();
|
||||||
assert_eq!(actual.len(), 5088);
|
assert_eq!(actual.len(), 5098);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pipes_on_pipes() {
|
fn test_pipes_on_pipes() {
|
||||||
let program = include_str!("../../../tests/executor/inputs/pipes_on_pipes.kcl");
|
let program = include_str!("../../../tests/executor/inputs/pipes_on_pipes.kcl");
|
||||||
let actual = lexer(program).unwrap();
|
let actual = lexer(program).unwrap();
|
||||||
assert_eq!(actual.len(), 17836);
|
assert_eq!(actual.len(), 17846);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn test_lexer_negative_word() {
|
fn test_lexer_negative_word() {
|
||||||
|
|||||||
@ -16,16 +16,21 @@ pub async fn execute_wasm(
|
|||||||
program_str: &str,
|
program_str: &str,
|
||||||
memory_str: &str,
|
memory_str: &str,
|
||||||
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||||
|
planes_str: &str,
|
||||||
) -> Result<JsValue, String> {
|
) -> Result<JsValue, String> {
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
|
|
||||||
|
use kcl_lib::executor::ExecutorContext;
|
||||||
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
let program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||||
|
let planes: kcl_lib::executor::DefaultPlanes = serde_json::from_str(planes_str).map_err(|e| e.to_string())?;
|
||||||
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
let mut mem: kcl_lib::executor::ProgramMemory = serde_json::from_str(memory_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let engine = kcl_lib::engine::EngineConnection::new(manager)
|
let engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
|
let ctx = ExecutorContext { engine, planes };
|
||||||
|
|
||||||
let memory = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &engine)
|
let memory = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx)
|
||||||
.await
|
.await
|
||||||
.map_err(String::from)?;
|
.map_err(String::from)?;
|
||||||
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
// The serde-wasm-bindgen does not work here because of weird HashMap issues so we use the
|
||||||
@ -40,11 +45,14 @@ pub async fn modify_ast_for_sketch_wasm(
|
|||||||
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
|
||||||
program_str: &str,
|
program_str: &str,
|
||||||
sketch_name: &str,
|
sketch_name: &str,
|
||||||
|
plane_type: &str,
|
||||||
sketch_id: &str,
|
sketch_id: &str,
|
||||||
) -> Result<JsValue, String> {
|
) -> Result<JsValue, String> {
|
||||||
// deserialize the ast from a stringified json
|
// deserialize the ast from a stringified json
|
||||||
let mut program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
let mut program: kcl_lib::ast::types::Program = serde_json::from_str(program_str).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
let plane: kcl_lib::executor::PlaneType = serde_json::from_str(plane_type).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
let mut engine = kcl_lib::engine::EngineConnection::new(manager)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
.map_err(|e| format!("{:?}", e))?;
|
||||||
@ -53,6 +61,7 @@ pub async fn modify_ast_for_sketch_wasm(
|
|||||||
&mut engine,
|
&mut engine,
|
||||||
&mut program,
|
&mut program,
|
||||||
sketch_name,
|
sketch_name,
|
||||||
|
plane,
|
||||||
uuid::Uuid::parse_str(sketch_id).map_err(|e| e.to_string())?,
|
uuid::Uuid::parse_str(sketch_id).map_err(|e| e.to_string())?,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
12
src/wasm-lib/tests/executor/inputs/cube.kcl
Normal file
12
src/wasm-lib/tests/executor/inputs/cube.kcl
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
fn cube = (pos, scale) => {
|
||||||
|
const sg = startSketchAt(pos)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|
||||||
|
return sg
|
||||||
|
}
|
||||||
|
|
||||||
|
const b1 = cube([0,0], 10)
|
||||||
|
const pt1 = b1[0]
|
||||||
|
show(b1)
|
||||||
@ -1,4 +1,5 @@
|
|||||||
const svg = startSketchAt([0, 0])
|
const svg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|
||||||
|> lineTo([2.52, -26.04], %) // MoveAbsolute
|
|> lineTo([2.52, -26.04], %) // MoveAbsolute
|
||||||
|> lineTo([2.52, -25.2], %) // VerticalLineAbsolute
|
|> lineTo([2.52, -25.2], %) // VerticalLineAbsolute
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
const svg = startSketchAt([0, 0])
|
const svg = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|
||||||
|> lineTo([22.687663, -2.7664351], %) // MoveRelative
|
|> lineTo([22.687663, -2.7664351], %) // MoveRelative
|
||||||
|> lineTo([15.687664000000002, -5.7664351], %) // MoveRelative
|
|> lineTo([15.687664000000002, -5.7664351], %) // MoveRelative
|
||||||
|
|||||||
@ -37,10 +37,13 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
let program = parser.ast()?;
|
let program = parser.ast()?;
|
||||||
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
|
||||||
let engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
let engine = kcl_lib::engine::EngineConnection::new(ws).await?;
|
||||||
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &engine).await?;
|
let planes = kcl_lib::executor::DefaultPlanes::new(&engine).await?;
|
||||||
|
let ctx = kcl_lib::executor::ExecutorContext { engine, planes };
|
||||||
|
let _ = kcl_lib::executor::execute(program, &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?;
|
||||||
|
|
||||||
// Send a snapshot request to the engine.
|
// Send a snapshot request to the engine.
|
||||||
let resp = engine
|
let resp = ctx
|
||||||
|
.engine
|
||||||
.send_modeling_cmd(
|
.send_modeling_cmd(
|
||||||
uuid::Uuid::new_v4(),
|
uuid::Uuid::new_v4(),
|
||||||
kcl_lib::executor::SourceRange::default(),
|
kcl_lib::executor::SourceRange::default(),
|
||||||
@ -68,7 +71,8 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_execute_with_function_sketch() {
|
async fn serial_test_execute_with_function_sketch() {
|
||||||
let code = r#"fn box = (h, l, w) => {
|
let code = r#"fn box = (h, l, w) => {
|
||||||
const myBox = startSketchAt([0,0])
|
const myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0,0], %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
@ -83,13 +87,14 @@ const fnBox = box(3, 6, 10)
|
|||||||
show(fnBox)"#;
|
show(fnBox)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/function_sketch.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_execute_with_function_sketch_with_position() {
|
async fn serial_test_execute_with_function_sketch_with_position() {
|
||||||
let code = r#"fn box = (p, h, l, w) => {
|
let code = r#"fn box = (p, h, l, w) => {
|
||||||
const myBox = startSketchAt(p)
|
const myBox = startSketchOn('XY')
|
||||||
|
|> startProfileAt(p, %)
|
||||||
|> line([0, l], %)
|
|> line([0, l], %)
|
||||||
|> line([w, 0], %)
|
|> line([w, 0], %)
|
||||||
|> line([0, -l], %)
|
|> line([0, -l], %)
|
||||||
@ -102,12 +107,17 @@ async fn serial_test_execute_with_function_sketch_with_position() {
|
|||||||
show(box([0,0], 3, 6, 10))"#;
|
show(box([0,0], 3, 6, 10))"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/function_sketch_with_position.png", &result, 1.0);
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/function_sketch_with_position.png",
|
||||||
|
&result,
|
||||||
|
0.999,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_execute_with_angled_line() {
|
async fn serial_test_execute_with_angled_line() {
|
||||||
let code = r#"const part001 = startSketchAt([4.83, 12.56])
|
let code = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([4.83, 12.56], %)
|
||||||
|> line([15.1, 2.48], %)
|
|> line([15.1, 2.48], %)
|
||||||
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|
||||||
|> line([-15.17, -4.1], %)
|
|> line([-15.17, -4.1], %)
|
||||||
@ -119,7 +129,7 @@ async fn serial_test_execute_with_angled_line() {
|
|||||||
show(part001)"#;
|
show(part001)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/angled_line.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -133,7 +143,8 @@ const FOS = 2
|
|||||||
const leg1 = 5 // inches
|
const leg1 = 5 // inches
|
||||||
const leg2 = 8 // inches
|
const leg2 = 8 // inches
|
||||||
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
|
||||||
const bracket = startSketchAt([0, 0])
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([0, leg1], %)
|
|> line([0, leg1], %)
|
||||||
|> line([leg2, 0], %)
|
|> line([leg2, 0], %)
|
||||||
|> line([0, -thickness], %)
|
|> line([0, -thickness], %)
|
||||||
@ -145,7 +156,7 @@ const bracket = startSketchAt([0, 0])
|
|||||||
show(bracket)"#;
|
show(bracket)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -180,12 +191,13 @@ const bracket = startSketchAt([0, 0])
|
|||||||
show(bracket)"#;
|
show(bracket)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn serial_test_execute_engine_error_return() {
|
async fn serial_test_execute_engine_error_return() {
|
||||||
let code = r#"const part001 = startSketchAt([5.5229, 5.25217])
|
let code = r#"const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([5.5229, 5.25217], %)
|
||||||
|> line([10.50433, -1.19122], %)
|
|> line([10.50433, -1.19122], %)
|
||||||
|> line([8.01362, -5.48731], %)
|
|> line([8.01362, -5.48731], %)
|
||||||
|> line([-1.02877, -6.76825], %)
|
|> line([-1.02877, -6.76825], %)
|
||||||
@ -197,7 +209,7 @@ async fn serial_test_execute_engine_error_return() {
|
|||||||
assert!(result.is_err());
|
assert!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.err().unwrap().to_string(),
|
result.err().unwrap().to_string(),
|
||||||
r#"engine: KclErrorDetails { source_ranges: [SourceRange([193, 206])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"The path is not closed. Solid2D construction requires a closed path!\" }])" }"#,
|
r#"engine: KclErrorDetails { source_ranges: [SourceRange([222, 235])], message: "Modeling command failed: Some([ApiError { error_code: BadRequest, message: \"The path is not closed. Solid2D construction requires a closed path!\" }])" }"#,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +219,7 @@ async fn serial_test_execute_pipes_on_pipes() {
|
|||||||
let code = include_str!("inputs/pipes_on_pipes.kcl");
|
let code = include_str!("inputs/pipes_on_pipes.kcl");
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/pipes_on_pipes.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -215,13 +227,14 @@ async fn serial_test_execute_kittycad_svg() {
|
|||||||
let code = include_str!("inputs/kittycad_svg.kcl");
|
let code = include_str!("inputs/kittycad_svg.kcl");
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/kittycad_svg.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_member_expression_sketch_group() {
|
async fn test_member_expression_sketch_group() {
|
||||||
let code = r#"fn cube = (pos, scale) => {
|
let code = r#"fn cube = (pos, scale) => {
|
||||||
const sg = startSketchAt(pos)
|
const sg = startSketchOn('XY')
|
||||||
|
|> startProfileAt(pos, %)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
|> line([scale, 0], %)
|
|> line([scale, 0], %)
|
||||||
|> line([0, -scale], %)
|
|> line([0, -scale], %)
|
||||||
@ -252,7 +265,8 @@ async fn test_close_arc() {
|
|||||||
const radius = 40
|
const radius = 40
|
||||||
const height = 3
|
const height = 3
|
||||||
|
|
||||||
const body = startSketchAt([center[0]+radius, center[1]])
|
const body = startSketchOn('XY')
|
||||||
|
|> startProfileAt([center[0]+radius, center[1]], %)
|
||||||
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(height, %)
|
|> extrude(height, %)
|
||||||
@ -260,7 +274,7 @@ const body = startSketchAt([center[0]+radius, center[1]])
|
|||||||
show(body)"#;
|
show(body)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/close_arc.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -270,7 +284,8 @@ const height = 10
|
|||||||
const length = 12
|
const length = 12
|
||||||
|
|
||||||
fn box = (sk1, sk2, scale) => {
|
fn box = (sk1, sk2, scale) => {
|
||||||
const boxSketch = startSketchAt([sk1, sk2])
|
const boxSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([sk1, sk2], %)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
|> line([scale, 0], %)
|
|> line([scale, 0], %)
|
||||||
|> line([0, -scale], %)
|
|> line([0, -scale], %)
|
||||||
@ -285,7 +300,7 @@ let thing = box(-12, -15, 10)
|
|||||||
box(-20, -5, 10)"#;
|
box(-20, -5, 10)"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/negative_args.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -298,7 +313,7 @@ async fn test_basic_tangental_arc() {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -311,7 +326,7 @@ async fn test_basic_tangental_arc_with_point() {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc_with_point.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc_with_point.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
@ -324,5 +339,95 @@ async fn test_basic_tangental_arc_to() {
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = execute_and_snapshot(code).await.unwrap();
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc_to.png", &result, 1.0);
|
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc_to.png", &result, 0.999);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_different_planes_same_drawing() {
|
||||||
|
let code = r#"const width = 5
|
||||||
|
const height = 10
|
||||||
|
const length = 12
|
||||||
|
|
||||||
|
fn box = (sk1, sk2, scale, plane) => {
|
||||||
|
const boxsketch = startSketchOn(plane)
|
||||||
|
|> startProfileAt([sk1, sk2], %)
|
||||||
|
|> line([0, scale], %)
|
||||||
|
|> line([scale, 0], %)
|
||||||
|
|> line([0, -scale], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(scale, %)
|
||||||
|
return boxsketch
|
||||||
|
}
|
||||||
|
|
||||||
|
box(0, 0, 5, 'xy')
|
||||||
|
box(10, 23, 8, 'xz')
|
||||||
|
box(30, 43, 18, '-xy')
|
||||||
|
let thing = box(-12, -15, 10, 'yz')
|
||||||
|
box(-20, -5, 10, 'xy')"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image(
|
||||||
|
"tests/executor/outputs/different_planes_same_drawing.png",
|
||||||
|
&result,
|
||||||
|
0.999,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_lots_of_planes() {
|
||||||
|
let code = r#"const sigmaAllow = 15000 // psi
|
||||||
|
const width = 11 // inch
|
||||||
|
const p = 150 // Force on shelf - lbs
|
||||||
|
const distance = 12 // inches
|
||||||
|
const FOS = 2
|
||||||
|
const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))
|
||||||
|
const filletR = thickness * 2
|
||||||
|
const shelfMountL = 9
|
||||||
|
const wallMountL = 8
|
||||||
|
|
||||||
|
const bracket = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([0, wallMountL], %)
|
||||||
|
|> tangentalArc({ radius: filletR, offset: 90 }, %)
|
||||||
|
|> line([-shelfMountL, 0], %)
|
||||||
|
|> line([0, -thickness], %)
|
||||||
|
|> line([shelfMountL, 0], %)
|
||||||
|
|> tangentalArc({
|
||||||
|
radius: filletR - thickness,
|
||||||
|
offset: -90
|
||||||
|
}, %)
|
||||||
|
|> line([0, -wallMountL], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(width, %)
|
||||||
|
|
||||||
|
show(bracket)
|
||||||
|
const part001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-15.53, -10.28], %)
|
||||||
|
|> line([10.49, -2.08], %)
|
||||||
|
|> line([10.42, 8.47], %)
|
||||||
|
|> line([-19.16, 5.1], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(4, %)
|
||||||
|
|
||||||
|
const part002 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([-9.35, 19.18], %)
|
||||||
|
|> line([32.14, -2.47], %)
|
||||||
|
|> line([8.39, -3.73], %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
const part003 = startSketchOn('-XZ')
|
||||||
|
|> startProfileAt([13.82, 16.51], %)
|
||||||
|
|> line([-6.24, -30.82], %)
|
||||||
|
|> line([8.39, -3.73], %)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
const part004 = startSketchOn('YZ')
|
||||||
|
|> startProfileAt([19.04, 20.22], %)
|
||||||
|
|> line([9.44, -30.16], %)
|
||||||
|
|> line([8.39, -3.73], %)
|
||||||
|
|> close(%)
|
||||||
|
"#;
|
||||||
|
|
||||||
|
let result = execute_and_snapshot(code).await.unwrap();
|
||||||
|
twenty_twenty::assert_image("tests/executor/outputs/lots_of_planes.png", &result, 0.999);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user