Compare commits

..

25 Commits

Author SHA1 Message Date
b17e61d963 Cut release v0.10.0 (#803)
Co-authored-by: Frank Noirot <frank@kittycad.io>
2023-10-06 11:09:54 -04:00
d31d07d9c8 Make "Replay Onboarding" button available on home settings page (#804)
* Fix unrelated bug, settings button in the home sidebar
doesn't go to the home settings after my previous fixes to routes

* Turn on "Replay Onboarding" button in home settings

* Use ONBOARDING_PROJECT_NAME in both places

* Fix formatting
2023-10-06 10:00:35 -04:00
7aa2d63c21 Hide planes (#797)
* hide planes in one go

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* update hide;

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-05 19:54:31 -07:00
e1081b0ee6 turning back on all planes (#720)
* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix more tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix stdlib

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

compile

Signed-off-by: Jess Frazelle <github@jessfraz.com>

update sample code

Signed-off-by: Jess Frazelle <github@jessfraz.com>

re-enable the planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fix all tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

boilerplate

Signed-off-by: Jess Frazelle <github@jessfraz.com>

Cut release v0.9.2 (#714)

rust make default planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

use the planes from engine

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

negative args

Signed-off-by: Jess Frazelle <github@jessfraz.com>

diable camera

Signed-off-by: Jess Frazelle <github@jessfraz.com>

hide planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updatress

Signed-off-by: Jess Frazelle <github@jessfraz.com>

fmt

Signed-off-by: Jess Frazelle <github@jessfraz.com>

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

Update src/hooks/useAppMode.ts

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>

cleanups

Signed-off-by: Jess Frazelle <github@jessfraz.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src-tauri (#726)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump tauri-plugin-fs-extra from `b04bde3` to `6c7a4c0` in /src-tauri (#725)

Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `b04bde3` to `6c7a4c0`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](b04bde3461...6c7a4c0984)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump toml from 0.8.0 to 0.8.1 in /src-tauri (#724)

Bumps [toml](https://github.com/toml-rs/toml) from 0.8.0 to 0.8.1.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.0...toml-v0.8.1)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump openapitor from `61a1605` to `d3e98c4` in /src/wasm-lib (#723)

Bumps [openapitor](https://github.com/KittyCAD/kittycad.rs) from `61a1605` to `d3e98c4`.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](61a16059b3...d3e98c4ec0)

---
updated-dependencies:
- dependency-name: openapitor
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump kittycad from 0.2.26 to 0.2.27 in /src/wasm-lib (#722)

Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.26 to 0.2.27.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.26...v0.2.27)

---
updated-dependencies:
- dependency-name: kittycad
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump thiserror from 1.0.48 to 1.0.49 in /src/wasm-lib (#721)

Bumps [thiserror](https://github.com/dtolnay/thiserror) from 1.0.48 to 1.0.49.
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](https://github.com/dtolnay/thiserror/compare/1.0.48...1.0.49)

---
updated-dependencies:
- dependency-name: thiserror
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump expectorate from 1.0.7 to 1.1.0 in /src/wasm-lib (#712)

Bumps [expectorate](https://github.com/oxidecomputer/expectorate) from 1.0.7 to 1.1.0.
- [Release notes](https://github.com/oxidecomputer/expectorate/releases)
- [Commits](https://github.com/oxidecomputer/expectorate/compare/v1.0.7...v1.1.0)

---
updated-dependencies:
- dependency-name: expectorate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

Bump clap from 4.4.4 to 4.4.5 in /src/wasm-lib (#711)

Bumps [clap](https://github.com/clap-rs/clap) from 4.4.4 to 4.4.5.
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/v4.4.4...v4.4.5)

---
updated-dependencies:
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

refactor cleanup

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

type improvements

* use new sketchmode no camera

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* js working better

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* start of negative planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* tests and neg

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* images

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* norma;s

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* better initial load of planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* ts

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fixes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tsc

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix edit sketch

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* add regression test for 2d solid issue

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* show planes

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix clippy

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix tests

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* canecel in progress

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* fix ci as well

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-05 14:27:48 -07:00
59223279b7 Type Error: Type error engineConnection.ts (#786)
* TypeError Fix

* removed not needed console log

* took into account adamchalmers feedback and fixed cl type error

* pretty up

* Irev-dev feedback fixes issue
2023-10-05 16:18:50 +11:00
8a4e717565 Use absolute URLs to settings to avoid relative URL edge cases (#781)
* Create useAbsoluteFilePath hook

* Fix "report bug" link on Error page

* Replace relative URL to settings with absolute URL

* Replace other absolute file URLs to use common hook

* Use named const for default browser file name

* Fix UI tests that now rely on useRouteLoaderData()

Signed-off-by: Frank Noirot <frank@kittycad.io>

---------

Signed-off-by: Frank Noirot <frank@kittycad.io>
2023-10-04 18:00:55 -04:00
80b542ca18 Allow Vertical Scroll Wheel to affect Horizontal Scroll for Toolbar (#780) 2023-10-04 09:35:50 -07:00
e4bfc863ea Bump re-resizable from 6.9.9 to 6.9.11 (#777)
Bumps [re-resizable](https://github.com/bokuweb/react-resizable-box) from 6.9.9 to 6.9.11.
- [Release notes](https://github.com/bokuweb/react-resizable-box/releases)
- [Changelog](https://github.com/bokuweb/re-resizable/blob/master/CHANGELOG.md)
- [Commits](https://github.com/bokuweb/react-resizable-box/commits)

---
updated-dependencies:
- dependency-name: re-resizable
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 10:08:10 -05:00
77ef255de4 Bump tauri from 1.5.0 to 1.5.1 in /src-tauri (#761)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5...tauri-v1.5.1)

---
updated-dependencies:
- dependency-name: tauri
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-04 09:21:58 -05:00
64c3841079 Bump tauri-plugin-fs-extra from 9b96996 to fa32d1a in /src-tauri (#776)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9b96996` to `fa32d1a`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9b96996b5a...fa32d1afa9)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 23:23:43 -05:00
c7bb6bc845 Bump reqwest from 0.11.21 to 0.11.22 in /src/wasm-lib (#775)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.21 to 0.11.22.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.21...v0.11.22)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 23:23:18 -05:00
1af8a8c64f Cut release v0.9.5 (#772) 2023-10-03 17:35:16 -07:00
eb4776826b disable pip for firefox (#770)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:11:44 -07:00
f3dd0469d5 re-execute when we remount the app (#769)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:07:09 -07:00
deea74754d save actual file contents (#768)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 15:06:45 -07:00
3fd798c704 Bump @adobe/css-tools from 4.2.0 to 4.3.1 (#353)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.2.0 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierre Jacquier <pierrejacquier39@gmail.com>
2023-10-03 14:12:58 -05:00
cc9eaf2991 Bump get-func-name from 2.0.0 to 2.0.2 (#730)
Bumps [get-func-name](https://github.com/chaijs/get-func-name) from 2.0.0 to 2.0.2.
- [Release notes](https://github.com/chaijs/get-func-name/releases)
- [Commits](https://github.com/chaijs/get-func-name/commits/v2.0.2)

---
updated-dependencies:
- dependency-name: get-func-name
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 14:12:46 -05:00
6f24031220 add mit (#765)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-03 11:59:57 -07:00
672bcd297f Bump toml from 0.8.1 to 0.8.2 in /src-tauri (#760)
Bumps [toml](https://github.com/toml-rs/toml) from 0.8.1 to 0.8.2.
- [Commits](https://github.com/toml-rs/toml/compare/toml-v0.8.1...toml-v0.8.2)

---
updated-dependencies:
- dependency-name: toml
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 07:20:21 -07:00
3bc182fe16 Bump reqwest from 0.11.20 to 0.11.21 in /src/wasm-lib (#762)
Bumps [reqwest](https://github.com/seanmonstar/reqwest) from 0.11.20 to 0.11.21.
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.11.20...v0.11.21)

---
updated-dependencies:
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 07:20:08 -07:00
589cd39eec Cut release v0.9.4 (#759)
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2023-10-02 20:09:53 -04:00
63feebef5c add report a bug button (#758)
* add report a bug button

* format
2023-10-02 16:33:33 -07:00
65037abd9a new sample code w tangental arc (#756)
* new sample code w tangental arc

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* line 6

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* make sure sample code works in test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2023-10-02 16:14:09 -07:00
97bc339a62 Bump tauri-plugin-fs-extra from 9af4c37 to 9b96996 in /src-tauri (#751)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `9af4c37` to `9b96996`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](9af4c3727c...9b96996b5a)

---
updated-dependencies:
- dependency-name: tauri-plugin-fs-extra
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:24 -07:00
4e9a6375a5 Bump tauri-build from 1.4.1 to 1.5.0 in /src-tauri (#752)
Bumps [tauri-build](https://github.com/tauri-apps/tauri) from 1.4.1 to 1.5.0.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-build-v1.4.1...tauri-build-v1.5)

---
updated-dependencies:
- dependency-name: tauri-build
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 12:27:16 -07:00
73 changed files with 3192 additions and 919 deletions

View File

@ -15,6 +15,9 @@ on:
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo build
jobs:
cargobuild:

View File

@ -15,6 +15,9 @@ on:
- '**/rust-toolchain.toml'
- '**.rs'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo clippy
jobs:
cargoclippy:

View File

@ -17,6 +17,9 @@ on:
- .github/workflows/cargo-criterion.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo criterion
jobs:
cargocriterion:

View File

@ -18,6 +18,9 @@ on:
permissions:
packages: read
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo fmt
jobs:
cargofmt:

View File

@ -17,6 +17,9 @@ on:
- .github/workflows/cargo-test.yml
workflow_dispatch:
permissions: read-all
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo test
jobs:
cargotest:

View File

@ -8,6 +8,9 @@ on:
release:
types: [published]
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
check-format:
runs-on: 'ubuntu-20.04'

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2023 The KittyCAD Authors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

File diff suppressed because it is too large Load Diff

View File

@ -48,7 +48,9 @@
* [`show`](#show)
* [`sin`](#sin)
* [`sqrt`](#sqrt)
* [`startProfileAt`](#startProfileAt)
* [`startSketchAt`](#startSketchAt)
* [`startSketchOn`](#startSketchOn)
* [`tan`](#tan)
* [`tangentalArc`](#tangentalArc)
* [`tangentalArcTo`](#tangentalArcTo)
@ -120,6 +122,8 @@ angleToMatchLengthX(segment_name: string, to: number, sketch_group: SketchGroup)
{
// 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.
@ -204,6 +208,8 @@ angleToMatchLengthY(segment_name: string, to: number, sketch_group: SketchGroup)
{
// 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.
@ -298,6 +304,8 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -364,6 +372,8 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -454,6 +464,8 @@ angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
{
// 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.
@ -520,6 +532,8 @@ angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
{
// 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.
@ -610,6 +624,8 @@ angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
{
// 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.
@ -676,6 +692,8 @@ angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
{
// 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.
@ -767,6 +785,8 @@ angledLineThatIntersects(data: AngeledLineThatIntersectsData, sketch_group: Sket
{
// 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.
@ -833,6 +853,8 @@ angledLineThatIntersects(data: AngeledLineThatIntersectsData, sketch_group: Sket
{
// 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.
@ -923,6 +945,8 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -989,6 +1013,8 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1079,6 +1105,8 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1145,6 +1173,8 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1262,6 +1292,8 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1328,6 +1360,8 @@ arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1467,6 +1501,8 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1533,6 +1569,8 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1631,6 +1669,8 @@ close(sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1697,6 +1737,8 @@ close(sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -1815,6 +1857,8 @@ extrude(length: number, sketch_group: SketchGroup) -> ExtrudeGroup
{
// 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.
@ -1996,6 +2040,8 @@ lastSegX(sketch_group: SketchGroup) -> number
{
// 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.
@ -2078,6 +2124,8 @@ lastSegY(sketch_group: SketchGroup) -> number
{
// 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.
@ -2233,6 +2281,8 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -2299,6 +2349,8 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -2387,6 +2439,8 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -2453,6 +2507,8 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -2693,6 +2749,8 @@ segAng(segment_name: string, sketch_group: SketchGroup) -> number
{
// 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.
@ -2776,6 +2834,8 @@ segEndX(segment_name: string, sketch_group: SketchGroup) -> number
{
// 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.
@ -2859,6 +2919,8 @@ segEndY(segment_name: string, sketch_group: SketchGroup) -> number
{
// 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.
@ -2942,6 +3004,8 @@ segLen(segment_name: string, sketch_group: SketchGroup) -> number
{
// 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.
@ -3024,6 +3088,8 @@ show(sketch: SketchGroup)
{
// 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.
@ -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
@ -3147,6 +3213,40 @@ startSketchAt(data: LineData) -> SketchGroup
} |
[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 planes X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the planes Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
}
```
#### Returns
@ -3155,6 +3255,8 @@ startSketchAt(data: LineData) -> SketchGroup
{
// 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.
@ -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 planes X axis be?
x_axis: {
x: number,
y: number,
z: number,
},
// What should the planes 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 planes X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the planes Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
}
```
### tan
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.
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.
@ -3335,6 +3621,8 @@ tangentalArc(data: TangentalArcData, sketch_group: SketchGroup) -> SketchGroup
{
// 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.
@ -3423,6 +3711,8 @@ tangentalArcTo(data: TangentalArcToData, sketch_group: SketchGroup) -> SketchGro
{
// 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.
@ -3489,6 +3779,8 @@ tangentalArcTo(data: TangentalArcToData, sketch_group: SketchGroup) -> SketchGro
{
// 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.
@ -3596,6 +3888,8 @@ number
{
// 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.
@ -3662,6 +3956,8 @@ number
{
// 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.
@ -3750,6 +4046,8 @@ number
{
// 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.
@ -3816,6 +4114,8 @@ number
{
// 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.
@ -3904,6 +4204,8 @@ number
{
// 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.
@ -3970,6 +4272,8 @@ number
{
// 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.
@ -4058,6 +4362,8 @@ number
{
// 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.
@ -4124,6 +4430,8 @@ number
{
// 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.

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.9.3",
"version": "0.10.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.9.0",
@ -10,7 +10,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.13",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.40",
"@kittycad/lib": "^0.0.43",
"@lezer/javascript": "^1.4.7",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
@ -32,7 +32,7 @@
"fuse.js": "^6.6.2",
"http-server": "^14.1.1",
"json-rpc-2.0": "^1.6.0",
"re-resizable": "^6.9.9",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",

22
src-tauri/Cargo.lock generated
View File

@ -84,7 +84,7 @@ dependencies = [
"tauri-build",
"tauri-plugin-fs-extra",
"tokio",
"toml 0.8.1",
"toml 0.8.2",
]
[[package]]
@ -3712,9 +3712,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "1.5.0"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72aee3277d0a0df01472cc704ab5934a51a1f25348838df17bfb3c5cb727880c"
checksum = "0238c5063bf9613054149a1b6bce4935922e532b7d8211f36989a490a79806be"
dependencies = [
"anyhow",
"base64 0.21.2",
@ -3768,9 +3768,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "1.4.1"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a62b3327886e7ef2978adc668432f1cc53f14e1d46e7ae04f730f4d48584623"
checksum = "defbfc551bd38ab997e5f8e458f87396d2559d05ce32095076ad6c30f7fc5f9c"
dependencies = [
"anyhow",
"cargo_toml",
@ -3828,7 +3828,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#9af4c3727c0d9c7a88b27cb80a6482a5aa461fc5"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#fa32d1afa97f52f74d814c5619b8d95da3268e3e"
dependencies = [
"log",
"serde",
@ -4078,14 +4078,14 @@ dependencies = [
[[package]]
name = "toml"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc1433177506450fe920e46a4f9812d0c211f5dd556da10e731a0a3dfa151f0"
checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.20.1",
"toml_edit 0.20.2",
]
[[package]]
@ -4112,9 +4112,9 @@ dependencies = [
[[package]]
name = "toml_edit"
version = "0.20.1"
version = "0.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca676d9ba1a322c1b64eb8045a5ec5c0cfb0c9d08e15e9ff622589ad5221c8fe"
checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338"
dependencies = [
"indexmap 2.0.0",
"serde",

View File

@ -12,7 +12,7 @@ rust-version = "1.60"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4.1", features = [] }
tauri-build = { version = "1.5.0", features = [] }
[dependencies]
anyhow = "1"
@ -20,10 +20,10 @@ kittycad = "0.2.28"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
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" }
tokio = { version = "1.32.0", features = ["time"] }
toml = "0.8.1"
toml = "0.8.2"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.

View File

@ -8,7 +8,7 @@
},
"package": {
"productName": "kittycad-modeling",
"version": "0.9.3"
"version": "0.10.0"
},
"tauri": {
"allowlist": {

View File

@ -1,9 +1,15 @@
import { render, screen } from '@testing-library/react'
import { App } from './App'
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 CommandBarProvider from 'components/CommandBar'
import { BROWSER_FILE_NAME } from 'Router'
let listener: ((rect: any) => void) | undefined = undefined
;(global as any).ResizeObserver = class ResizeObserver {
@ -24,7 +30,7 @@ describe('App tests', () => {
>
return {
...actual,
useParams: () => ({ id: 'new' }),
useParams: () => ({ id: BROWSER_FILE_NAME }),
useLoaderData: () => ({ code: null }),
}
})
@ -41,12 +47,24 @@ describe('App tests', () => {
})
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -94,6 +94,8 @@ export const paths = {
) as typeof onboardingPaths,
}
export const BROWSER_FILE_NAME = 'new'
export type IndexLoaderData = {
code: string | null
project?: ProjectWithEntryPointMetadata
@ -129,7 +131,9 @@ const router = createBrowserRouter(
{
path: paths.INDEX,
loader: () =>
isTauri() ? redirect(paths.HOME) : redirect(paths.FILE + '/new'),
isTauri()
? redirect(paths.HOME)
: redirect(paths.FILE + '/' + BROWSER_FILE_NAME),
errorElement: <ErrorPage />,
},
{
@ -167,7 +171,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
const code = await readTextFile(params.id + '/' + PROJECT_ENTRYPOINT)
const entrypoint_metadata = await metadata(
@ -212,7 +216,7 @@ const router = createBrowserRouter(
),
loader: async () => {
if (!isTauri()) {
return redirect(paths.FILE + '/new')
return redirect(paths.FILE + '/' + BROWSER_FILE_NAME)
}
const fetchedStorage = localStorage?.getItem(SETTINGS_PERSIST_KEY)
const persistedSettings = JSON.parse(fetchedStorage || '{}') as Partial<

View File

@ -10,7 +10,7 @@ import { SetHorzVertDistance } from './components/Toolbar/SetHorzVertDistance'
import { SetAngleLength } from './components/Toolbar/setAngleLength'
import { SetAbsDistance } from './components/Toolbar/SetAbsDistance'
import { SetAngleBetween } from './components/Toolbar/SetAngleBetween'
import { Fragment, useEffect } from 'react'
import { Fragment, WheelEvent, useRef } from 'react'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { faSearch, faX } from '@fortawesome/free-solid-svg-icons'
import { Popover, Transition } from '@headlessui/react'
@ -62,10 +62,24 @@ export const Toolbar = () => {
executeAst: s.executeAst,
}))
useAppMode()
const toolbarButtonsRef = useRef<HTMLSpanElement>(null)
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
if (!span) {
return
}
span.scrollLeft = span.scrollLeft += ev.deltaY
}
function ToolbarButtons({ className }: React.HTMLAttributes<HTMLElement>) {
return (
<span className={styles.toolbarButtons + ' ' + className}>
<span
ref={toolbarButtonsRef}
onWheel={handleToolbarButtonsWheelEvent}
className={styles.toolbarButtons + ' ' + className}
>
{guiMode.mode === 'default' && (
<button
onClick={() => {

View File

@ -91,11 +91,14 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { ast, programMemory, selectionRange } = useStore((s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
}))
const { ast, programMemory, selectionRange, defaultPlanes } = useStore(
(s) => ({
ast: s.ast,
programMemory: s.programMemory,
selectionRange: s.selectionRanges.codeBasedSelections[0].range,
defaultPlanes: s.defaultPlanes,
})
)
const inputRef = useRef<HTMLInputElement>(null)
const [availableVarInfo, setAvailableVarInfo] = useState<
ReturnType<typeof findAllPreviousVariables>
@ -143,19 +146,22 @@ export function useCalc({
availableVarInfo.variables.forEach(({ key, value }) => {
_programMem.root[key] = { type: 'userVal', value, __meta: [] }
})
executor(ast, _programMem, engineCommandManager).then((programMemory) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})
if (!defaultPlanes) return
executor(ast, _programMem, engineCommandManager, defaultPlanes!).then(
(programMemory) => {
const resultDeclaration = ast.body.find(
(a) =>
a.type === 'VariableDeclaration' &&
a.declarations?.[0]?.id?.name === '__result__'
)
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declarations?.[0]?.init
const result = programMemory?.root?.__result__?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
}
)
} catch (e) {
setCalcResult('NAN')
setValueNode(null)

View File

@ -47,11 +47,9 @@ export const ErrorPage = () => {
Clear storage
</ActionButton>
<ActionButton
Element="link"
Element="externalLink"
icon={{ icon: faBug }}
target="_blank"
rel="noopener noreferrer"
to="https://discord.com/channels/915388055236509727/1138967922614743060"
to="https://github.com/KittyCAD/modeling-app/issues/new"
>
Report Bug
</ActionButton>

View File

@ -14,12 +14,14 @@ describe('processMemory', () => {
}
const otherVar = myFn(5)
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.4, myVar], %)
|> lineTo([-0.76, otherVar], %)
|> extrude(4, %)
const theSketch = startSketchAt([0, 0])
const theSketch = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-3.35, 0.17], %)
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)

View File

@ -46,6 +46,8 @@ export const Stream = ({ className = '' }) => {
updateAst,
setGuiMode,
programMemory,
defaultPlanes,
currentPlane,
} = useStore((s) => ({
mediaStream: s.mediaStream,
setButtonDownInStream: s.setButtonDownInStream,
@ -59,6 +61,8 @@ export const Stream = ({ className = '' }) => {
updateAst: s.updateAst,
setGuiMode: s.setGuiMode,
programMemory: s.programMemory,
defaultPlanes: s.defaultPlanes,
currentPlane: s.currentPlane,
}))
const {
settings: {
@ -252,10 +256,26 @@ export const Stream = ({ className = '' }) => {
let engineId = guiMode.pathId
// Get the current plane string for plane we are on.
let currentPlaneString = ''
if (currentPlane === defaultPlanes?.xy) {
currentPlaneString = 'XY'
} else if (currentPlane === defaultPlanes?.yz) {
currentPlaneString = 'YZ'
} else if (currentPlane === defaultPlanes?.xz) {
currentPlaneString = 'XZ'
}
// Do not supporting editing/moving lines on a non-default plane.
// Eventually we can support this but for now we will just throw an
// error.
if (currentPlaneString === '') return
const updatedAst: Program = await modifyAstForSketch(
engineCommandManager,
ast,
variableName,
currentPlaneString,
engineId
)
@ -282,8 +302,46 @@ export const Stream = ({ className = '' }) => {
})
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 (currentPlane === defaultPlanes?.xy) {
if (z_axis.z === -1) {
currentAxis = '-xy'
} else {
currentAxis = 'xy'
}
} else if (currentPlane === defaultPlanes?.yz) {
if (z_axis.x === -1) {
currentAxis = '-yz'
} else {
currentAxis = 'yz'
}
} else if (currentPlane === defaultPlanes?.xz) {
if (z_axis.y === -1) {
currentAxis = '-xz'
} else {
currentAxis = 'xz'
}
}
// Do not support starting a new sketch on a non-default plane.
if (!currentAxis) return
const _addStartSketch = addStartSketch(
ast,
currentAxis,
[roundOff(coords[0].x), roundOff(coords[0].y)],
[
roundOff(coords[1].x - coords[0].x),
@ -402,6 +460,7 @@ export const Stream = ({ className = '' }) => {
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
disablePictureInPicture
className={`w-full h-full ${isExecuting && 'blur-md'}`}
style={{ transitionDuration: '200ms', transitionProperty: 'filter' }}
/>

View File

@ -1,6 +1,11 @@
import { fireEvent, render, screen } from '@testing-library/react'
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 { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar'
@ -93,11 +98,24 @@ describe('UserSidebarMenu tests', () => {
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return (
<BrowserRouter>
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
</BrowserRouter>
// We have to use a memory router in the testing environment,
// and we have to use the createMemoryRouter function instead of <MemoryRouter /> as of react-router v6.4:
// https://reactrouter.com/en/6.16.0/routers/picking-a-router#using-v64-data-apis
const router = createMemoryRouter(
createRoutesFromElements(
<Route
path="/file/:id"
element={
<CommandBarProvider>
<GlobalStateProvider>{children}</GlobalStateProvider>
</CommandBarProvider>
}
/>
),
{
initialEntries: ['/file/new'],
initialIndex: 0,
}
)
return <RouterProvider router={router} />
}

View File

@ -1,18 +1,24 @@
import { Popover, Transition } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { faBars, faGear, faSignOutAlt } from '@fortawesome/free-solid-svg-icons'
import {
faBars,
faBug,
faGear,
faSignOutAlt,
} from '@fortawesome/free-solid-svg-icons'
import { faGithub } from '@fortawesome/free-brands-svg-icons'
import { useLocation, useNavigate } from 'react-router-dom'
import { Fragment, useState } from 'react'
import { paths } from '../Router'
import makeUrlPathRelative from '../lib/makeUrlPathRelative'
import { Models } from '@kittycad/lib'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
type User = Models['User_type']
const UserSidebarMenu = ({ user }: { user?: User }) => {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const displayedName = getDisplayName(user)
const [imageLoadFailed, setImageLoadFailed] = useState(false)
const navigate = useNavigate()
@ -127,11 +133,10 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
// since /settings is a nested route the sidebar doesn't close
// automatically when navigating to it
close()
navigate(
(location.pathname.endsWith('/')
? location.pathname.slice(0, -1)
: location.pathname) + paths.SETTINGS
)
const targetPath = location.pathname.includes(paths.FILE)
? filePath + paths.SETTINGS
: paths.HOME + paths.SETTINGS
navigate(targetPath)
}}
>
Settings
@ -144,6 +149,14 @@ const UserSidebarMenu = ({ user }: { user?: User }) => {
>
Request a feature
</ActionButton>
<ActionButton
Element="externalLink"
to="https://github.com/KittyCAD/modeling-app/issues/new"
icon={{ icon: faBug }}
className="border-transparent dark:border-transparent dark:hover:border-liquid-60"
>
Report a bug
</ActionButton>
<ActionButton
Element="button"
onClick={() => send('Log out')}

View 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)
)
}

View File

@ -2,30 +2,37 @@
// Once we have xState this should be removed
import { useStore, Selections } from 'useStore'
import { useEffect, useState } from 'react'
import { useEffect } 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
}
import { DefaultPlanes } from '../wasm-lib/kcl/bindings/DefaultPlanes'
import { getNodeFromPath } from '../lang/queryAst'
import { CallExpression, PipeExpression } from '../lang/wasm'
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)
const {
guiMode,
setGuiMode,
selectionRanges,
selectionRangeTypeMap,
defaultPlanes,
setDefaultPlanes,
setCurrentPlane,
ast,
} = useStore((s) => ({
guiMode: s.guiMode,
setGuiMode: s.setGuiMode,
selectionRanges: s.selectionRanges,
selectionRangeTypeMap: s.selectionRangeTypeMap,
defaultPlanes: s.defaultPlanes,
setDefaultPlanes: s.setDefaultPlanes,
setCurrentPlane: s.setCurrentPlane,
ast: s.ast,
}))
useEffect(() => {
if (
guiMode.mode === 'sketch' &&
@ -35,8 +42,10 @@ export function useAppMode() {
const createAndShowPlanes = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
if (!newDefaultPlanes) return
setDefaultPlanes(newDefaultPlanes)
localDefaultPlanes = newDefaultPlanes
} else {
localDefaultPlanes = defaultPlanes
}
@ -52,20 +61,52 @@ export function useAppMode() {
const enableSketchMode = async () => {
let localDefaultPlanes: DefaultPlanes
if (!defaultPlanes) {
localDefaultPlanes = await initDefaultPlanes(engineCommandManager)
setDefaultPlanes(localDefaultPlanes)
const newDefaultPlanes = await initDefaultPlanes(engineCommandManager)
if (!newDefaultPlanes) return
setDefaultPlanes(newDefaultPlanes)
localDefaultPlanes = newDefaultPlanes
} 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
const pipeExpression = getNodeFromPath<PipeExpression>(
ast,
guiMode.pathToNode,
'PipeExpression'
).node
if (pipeExpression.type !== 'PipeExpression') return /// bad bad bad
const sketchCallExpression = pipeExpression.body.find(
(e) =>
e.type === 'CallExpression' && e.callee.name === 'startSketchOn'
) as CallExpression
if (!sketchCallExpression) return // also bad bad bad
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 = localDefaultPlanes[planeStrCleaned]
}
}
if (!planeId) return // they are on some non default plane, which we don't support yet
setCurrentPlane(planeId)
await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'sketch_mode_enable',
plane_id: localDefaultPlanes.xy,
plane_id: planeId,
ortho: true,
animated: !isReducedMotion(),
},
@ -139,6 +180,7 @@ export function useAppMode() {
// user clicked something else in the scene
return
}
setCurrentPlane(data.entity_id)
const sketchModeResponse = await engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
@ -194,10 +236,12 @@ async function createPlane(
x_axis,
y_axis,
color,
hidden,
}: {
x_axis: Models['Point3d_type']
y_axis: Models['Point3d_type']
color: Models['Color_type']
hidden: boolean
}
) {
const planeId = uuidv4()
@ -210,6 +254,7 @@ async function createPlane(
x_axis,
y_axis,
clobber: false,
hide: hidden,
},
cmd_id: planeId,
})
@ -225,61 +270,82 @@ async function createPlane(
return planeId
}
function setDefaultPlanesHidden(
export 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,
},
})
hidePlane(engineCommandManager, planeId, hidden)
})
}
async function initDefaultPlanes(
engineCommandManager: EngineCommandManager
): Promise<DefaultPlanes> {
function hidePlane(
engineCommandManager: EngineCommandManager,
planeId: string,
hidden: boolean
) {
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'object_visible',
object_id: planeId,
hidden: hidden,
},
})
}
export async function initDefaultPlanes(
engineCommandManager: EngineCommandManager,
hidePlanes?: boolean
): Promise<DefaultPlanes | null> {
if (!engineCommandManager.engineConnection?.isReady()) {
return null
}
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 },
hidden: hidePlanes ? true : false,
})
// 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 }
if (hidePlanes) {
hidePlane(engineCommandManager, xy, true)
}
const yz = await 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 },
hidden: hidePlanes ? true : false,
})
if (hidePlanes) {
hidePlane(engineCommandManager, yz, true)
}
const xz = await 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 },
hidden: hidePlanes ? true : false,
})
return { xy, yz, xz }
}
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')
)
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

View File

@ -3,7 +3,6 @@ import { _executor } from '../lang/wasm'
import { useStore } from '../useStore'
import { engineCommandManager } from '../lang/std/engineConnection'
import { deferExecution } from 'lib/utils'
import { v4 as uuidv4 } from 'uuid'
export function useSetupEngineManager(
streamRef: React.RefObject<HTMLDivElement>,
@ -28,6 +27,10 @@ export function useSetupEngineManager(
const hasSetNonZeroDimensions = useRef<boolean>(false)
useEffect(() => {
executeCode()
}, [])
useLayoutEffect(() => {
// Load the engine command manager once with the initial width and height,
// then we do not want to reload it.

View File

@ -7,7 +7,8 @@ describe('testing artifacts', () => {
// Enable rotations #152
test('sketch artifacts', async () => {
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
@ -27,7 +28,7 @@ show(mySketch001)`
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [21, 42],
sourceRange: [46, 71],
},
},
value: [
@ -37,7 +38,7 @@ show(mySketch001)`
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [48, 73],
sourceRange: [77, 102],
id: expect.any(String),
},
},
@ -47,7 +48,7 @@ show(mySketch001)`
from: [-1.59, -1.54],
name: '',
__geoMeta: {
sourceRange: [79, 103],
sourceRange: [108, 132],
id: expect.any(String),
},
},
@ -55,14 +56,16 @@ show(mySketch001)`
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [21, 42] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
])
})
test('extrude artifacts', async () => {
// Enable rotations #152
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
@ -82,7 +85,7 @@ show(mySketch001)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [21, 42] }],
__meta: [{ sourceRange: [46, 71] }],
},
])
})
@ -90,7 +93,8 @@ show(mySketch001)`
// Enable rotations #152
// TODO #153 in order for getExtrudeWallTransform to work we need to query the engine for the location of a face.
const code = `
const sk1 = startSketchAt([0, 0])
const sk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 10], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -99,7 +103,8 @@ const sk1 = startSketchAt([0, 0])
// |> ry(5, %)
const theExtrude = extrude(2, sk1)
// const theTransf = getExtrudeWallTransform('p', theExtrude)
const sk2 = startSketchAt([0, 0])
const sk2 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 3], tag: "p" }, %)
|> lineTo([2.5, 0], %)
@ -122,7 +127,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [13, 34] }],
__meta: [{ sourceRange: [38, 63] }],
},
{
type: 'ExtrudeGroup',
@ -131,7 +136,7 @@ show(theExtrude, sk2)`
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
__meta: [{ sourceRange: [302, 323] }],
__meta: [{ sourceRange: [356, 381] }],
},
])
})

View File

@ -41,7 +41,8 @@ const newVar = myVar + 1`
expect(root.magicNum.value).toBe(69)
})
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([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
@ -57,7 +58,7 @@ show(mySketch)
to: [0, 2],
from: [0, 0],
__geoMeta: {
sourceRange: [43, 80],
sourceRange: [72, 109],
id: expect.any(String),
},
name: 'myPath',
@ -68,7 +69,7 @@ show(mySketch)
from: [0, 2],
name: '',
__geoMeta: {
sourceRange: [86, 102],
sourceRange: [115, 131],
id: expect.any(String),
},
},
@ -77,7 +78,7 @@ show(mySketch)
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [108, 151],
sourceRange: [137, 180],
id: expect.any(String),
},
name: 'rightPath',
@ -87,8 +88,8 @@ show(mySketch)
expect(_return).toEqual([
{
type: 'Identifier',
start: 174,
end: 182,
start: 203,
end: 211,
name: 'mySketch',
},
])
@ -132,7 +133,8 @@ show(mySketch)
it('execute pipe sketch into call expression', async () => {
// Enable rotations #152
const code = [
'const mySk1 = startSketchAt([0,0])',
"const mySk1 = startSketchOn('XY')",
' |> startProfileAt([0,0], %)',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([1,1], %)',
@ -147,7 +149,7 @@ show(mySketch)
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [14, 34],
sourceRange: [39, 63],
},
},
value: [
@ -157,7 +159,7 @@ show(mySketch)
from: [0, 0],
name: '',
__geoMeta: {
sourceRange: [40, 56],
sourceRange: [69, 85],
id: expect.any(String),
},
},
@ -166,7 +168,7 @@ show(mySketch)
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [62, 100],
sourceRange: [91, 129],
id: expect.any(String),
},
name: 'myPath',
@ -177,7 +179,7 @@ show(mySketch)
from: [0, 1],
name: '',
__geoMeta: {
sourceRange: [106, 122],
sourceRange: [135, 151],
id: expect.any(String),
},
},
@ -185,7 +187,8 @@ show(mySketch)
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
id: expect.any(String),
__meta: [{ sourceRange: [14, 34] }],
planeId: expect.any(String),
__meta: [{ sourceRange: [39, 63] }],
})
})
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 () => {
const code = [
'const part001 = startSketchAt([0, 0])',
"const part001 = startSketchOn('XY')",
' |> startProfileAt([0, 0], %)',
'|> line([-2.21, -legLen(5, min(3, 999))], %)',
].join('\n')
const { root } = await exe(code)
@ -341,7 +345,8 @@ describe('testing math operators', () => {
it('test that % substitution feeds down CallExp->ArrExp->UnaryExp->CallExp', async () => {
const code = [
`const myVar = 3`,
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
` |> line([`,
` min(segLen('seg01', %), myVar),`,
@ -377,7 +382,8 @@ describe('testing math operators', () => {
describe('Testing Errors', () => {
it('should throw an error when a variable is not defined', async () => {
const code = `const myVar = 5
const theExtrude = startSketchAt([0, 0])
const theExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([-2.4, 5], %)
|> line([-0.76], myVarZ, %)
|> line([5,5], %)
@ -388,7 +394,7 @@ show(theExtrude)`
new KCLError(
'undefined_value',
'memory item key `myVarZ` is not defined',
[[100, 106]]
[[129, 135]]
)
)
})

View File

@ -109,8 +109,8 @@ describe('Testing addSketchTo', () => {
'yz'
)
const str = recast(result.modifiedAst)
expect(str).toBe(`const part001 = startSketchAt('default')
|> ry(90, %)
expect(str).toBe(`const part001 = startSketchOn('YZ')
|> startProfileAt('default', %)
|> line('default', %)
show(part001)
`)
@ -133,7 +133,8 @@ function giveSketchFnCallTagTestHelper(
}
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([0, 0.83], %)
|> line([0.82, 0.34], %)
@ -185,7 +186,8 @@ fn ghi = (x) => {
const abc = 3
const identifierGuy = 5
const yo = 5 + 6
const part001 = startSketchAt([-1.2, 4.83])
const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)

View File

@ -32,21 +32,26 @@ import { isLiteralArrayOrStatic } from './std/sketchcombos'
export function addStartSketch(
node: Program,
axis: 'xy' | 'xz' | 'yz' | '-xy' | '-xz' | '-yz',
start: [number, number],
end: [number, number]
): { modifiedAst: Program; id: string; pathToNode: PathToNode } {
const _node = { ...node }
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])]),
createPipeSubstitution(),
])
const initialLineTo = createCallExpression('line', [
createArrayExpression([createLiteral(end[0]), createLiteral(end[1])]),
createPipeSubstitution(),
])
const pipeBody = [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,
@ -79,11 +84,11 @@ export function addSketchTo(
const _node = { ...node }
const _name = name || findUniqueName(node, 'part')
const startSketchAt = createCallExpressionStdLib('startSketchAt', [
createLiteral('default'),
const startSketchOn = createCallExpressionStdLib('startSketchOn', [
createLiteral(axis.toUpperCase()),
])
const rotate = createCallExpression(axis === 'xz' ? 'rx' : 'ry', [
createLiteral(90),
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createLiteral('default'),
createPipeSubstitution(),
])
const initialLineTo = createCallExpressionStdLib('line', [
@ -91,10 +96,7 @@ export function addSketchTo(
createPipeSubstitution(),
])
const pipeBody =
axis !== 'xy'
? [startSketchAt, rotate, initialLineTo]
: [startSketchAt, initialLineTo]
const pipeBody = [startSketchOn, startProfileAt, initialLineTo]
const variableDeclaration = createVariableDeclaration(
_name,

View File

@ -26,7 +26,8 @@ const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [1, 2, 3]
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> yLineTo(1, %)
|> xLine(3.84, %) // selection-range-7ish-before-this
@ -57,7 +58,8 @@ show(part001)`
})
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], %)
|> angledLine([100 + 100, 3.09], %)
|> angledLine([abc, 3.09], %)
@ -194,7 +196,8 @@ show(part001)`
})
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.21, -1.4], %)
show(part001)`
@ -210,7 +213,7 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[1, 'index'],
[2, 'index'],
])
})
it('finds the last line when cursor is put at the end', () => {
@ -225,7 +228,7 @@ show(part001)`
[0, 'index'],
['init', ''],
['body', 'PipeExpression'],
[2, 'index'],
[3, 'index'],
]
expect(result).toEqual(expected)
// expect similar result for start of line

View File

@ -725,7 +725,11 @@ export class EngineCommandManager {
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)
}
}

View File

@ -96,7 +96,8 @@ describe('testing changeSketchArguments', () => {
const lineAfterChange = 'lineTo([2, 3], %)'
test('changeSketchArguments', async () => {
// Enable rotations #152
const genCode = (line: string) => `const mySketch001 = startSketchAt([0, 0])
const genCode = (line: string) => `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> ${line}
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
@ -137,7 +138,8 @@ describe('testing addNewSketchLn', () => {
test('addNewSketchLn', async () => {
// Enable rotations #152
const code = `
const mySketch001 = startSketchAt([0, 0])
const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
@ -145,7 +147,7 @@ show(mySketch001)`
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
expect(sourceStart).toBe(66)
expect(sourceStart).toBe(95)
let { modifiedAst } = addNewSketchLn({
node: ast,
programMemory,
@ -160,7 +162,8 @@ show(mySketch001)`
],
})
// Enable rotations #152
let expectedCode = `const mySketch001 = startSketchAt([0, 0])
let expectedCode = `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
@ -181,7 +184,8 @@ show(mySketch001)
],
})
expectedCode = `const mySketch001 = startSketchAt([0, 0])
expectedCode = `const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
@ -196,7 +200,8 @@ describe('testing addTagForSketchOnFace', () => {
it('needs to be in it', async () => {
const originalLine = 'lineTo([-1.59, -1.54], %)'
// 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, %)
|> ${line}
|> lineTo([0.46, -5.82], %)

View File

@ -52,7 +52,8 @@ async function testingSwapSketchFnCall({
describe('testing swaping out sketch calls with xLine/xLineTo', () => {
const bigExampleArr = [
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
` |> angledLine({`,
@ -277,7 +278,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
`const angledLineOfYLengthY = 0.89`,
`const angledLineToXx = -1.86`,
`const angledLineToYy = -0.76`,
`const part001 = startSketchAt([0, 0])`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
// ` |> rx(90, %)`,
` |> lineTo([1, 1], %)`,
` |> line([lineX, 2.13], %)`,
@ -371,7 +373,8 @@ describe('testing swaping out sketch calls with xLine/xLineTo while keeping vari
describe('testing getSketchSegmentIndexFromSourceRange', () => {
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], %)
|> xLine(3.48, %)
|> line([2.14, 1.35], %) // normal-segment

View File

@ -92,7 +92,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
const myAng2 = 134
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([1, 3.82], %) // ln-should-get-tag
|> lineTo([myVar, 1], %) // ln-lineTo-xAbsolute should use angleToMatchLengthX helper
|> lineTo([1, myVar], %) // ln-lineTo-yAbsolute should use angleToMatchLengthY helper
@ -128,7 +129,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
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
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
@ -231,7 +233,8 @@ describe('testing transformAstForSketchLines for vertical and horizontal constra
const inputScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> line([-6.28, 1.4], %) // select for horizontal constraint 1
|> line([-1.07, myVar], %) // select for vertical constraint 1
@ -259,7 +262,8 @@ show(part001)
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> xLine(-6.28, %) // select for horizontal constraint 1
|> line([-1.07, myVar], %) // select for vertical constraint 1
@ -317,7 +321,8 @@ show(part001)
const expectModifiedScript = `const myVar = 2
const myVar2 = 12
const myVar3 = -10
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
|> line([-6.28, 1.4], %) // select for horizontal 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 setHorzDistance for line', () => {
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.45, 1.46], %)
|> line([0.45, 1.46], %) // free
@ -477,7 +483,8 @@ const baseThickHalf = baseThick / 2
const halfHeight = totalHeight / 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
|> xLine(3.03, %) // partial
|> angledLine({

View File

@ -5,7 +5,8 @@ beforeAll(() => initPromise)
describe('testing angledLineThatIntersects', () => {
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([3, 1], %)
|> angledLineThatIntersects({

View File

@ -16,6 +16,7 @@ import { ProgramReturn } from '../wasm-lib/kcl/bindings/ProgramReturn'
import { MemoryItem } from '../wasm-lib/kcl/bindings/MemoryItem'
import type { Program } from '../wasm-lib/kcl/bindings/Program'
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 { Value } from '../wasm-lib/kcl/bindings/Value'
@ -118,6 +119,7 @@ export const executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager,
planes: DefaultPlanes,
// work around while the gemotry is still be stored on the frontend
// will be removed when the stream UI is added.
tempMapCallback: (a: {
@ -129,7 +131,8 @@ export const executor = async (
const _programMemory = await _executor(
node,
programMemory,
engineCommandManager
engineCommandManager,
planes
)
const { artifactMap, sourceRangeMap } =
await engineCommandManager.waitForAllCommands(node, _programMemory)
@ -142,13 +145,15 @@ export const executor = async (
export const _executor = async (
node: Program,
programMemory: ProgramMemory = { root: {}, return: null },
engineCommandManager: EngineCommandManager
engineCommandManager: EngineCommandManager,
planes: DefaultPlanes
): Promise<ProgramMemory> => {
try {
const memory: ProgramMemory = await execute_wasm(
JSON.stringify(node),
JSON.stringify(programMemory),
engineCommandManager
engineCommandManager,
JSON.stringify(planes)
)
return memory
} catch (e: any) {
@ -190,6 +195,7 @@ export const modifyAstForSketch = async (
engineCommandManager: EngineCommandManager,
ast: Program,
variableName: string,
currentPlane: string,
engineId: string
): Promise<Program> => {
try {
@ -197,6 +203,7 @@ export const modifyAstForSketch = async (
engineCommandManager,
JSON.stringify(ast),
variableName,
JSON.stringify(currentPlane),
engineId
)

View File

@ -1,19 +1,28 @@
export const bracket = `// Material: 6061-T6 Aluminum
const sigmaAllow = 35000 // psi
const width = 9 // inch
export const bracket = `const sigmaAllow = 15000 // psi
const width = 11 // inch
const p = 150 // Force on shelf - lbs
const distance = 6 // inches
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 leg1 = 5 // inches
const leg2 = 8 // inches
const thickness = sqrt(distance * p * FOS * 6 / sigmaAllow / width) // inches
const bracket = startSketchAt([0, 0])
|> line([0, leg1], %)
|> line([leg2, 0], %)
const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, wallMountL], %)
|> tangentalArc({
radius: filletR,
offset: 90
}, %)
|> line([-shelfMountL, 0], %)
|> line([0, -thickness], %)
|> line([-leg2 + thickness, 0], %)
|> line([0, -leg1 + thickness], %)
|> line([shelfMountL, 0], %)
|> tangentalArc({
radius: filletR - thickness,
offset: -90
}, %)
|> line([0, -wallMountL], %)
|> close(%)
|> extrude(width, %)

View File

@ -25,7 +25,7 @@ export async function exportSave(data: ArrayBuffer) {
}
// Write the file.
await writeBinaryFile(filePath, uintArray)
await writeBinaryFile(filePath, file.contents)
} else {
// Download the file to the user's computer.
// Now we need to download the files to the user's downloads folder.

View File

@ -4,6 +4,7 @@ import {
EngineCommand,
} from '../lang/std/engineConnection'
import { Models } from '@kittycad/lib'
import { v4 as uuidv4 } from 'uuid'
type WebSocketResponse = Models['OkWebSocketResponseData_type']
@ -64,7 +65,11 @@ export async function enginelessExecutor(
}) as any as EngineCommandManager
await mockEngineCommandManager.waitForReady
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()
return programMemory
}
@ -83,7 +88,11 @@ export async function executor(
})
await engineCommandManager.waitForReady
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)
return programMemory
}

View File

@ -1,6 +1,11 @@
import { faArrowRight, faXmark } from '@fortawesome/free-solid-svg-icons'
import { ActionButton } from '../../components/ActionButton'
import { onboardingPaths, useDismiss, useNextClick } from '.'
import {
ONBOARDING_PROJECT_NAME,
onboardingPaths,
useDismiss,
useNextClick,
} from '.'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { bracket } from 'lib/exampleKcl'
@ -25,14 +30,20 @@ function OnboardingWithNewFile() {
}))
const {
settings: {
context: { defaultDirectory, defaultProjectName },
context: { defaultDirectory },
},
} = useGlobalStateContext()
async function createAndOpenNewProject() {
const projects = await getProjectsInDir(defaultDirectory)
const nextIndex = await getNextProjectIndex(defaultProjectName, projects)
const name = interpolateProjectNameWithIndex(defaultProjectName, nextIndex)
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)}`)
}

View File

@ -1,5 +1,5 @@
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 Camera from './Camera'
import Sketching from './Sketching'
@ -15,7 +15,10 @@ import UserMenu from './UserMenu'
import ProjectMenu from './ProjectMenu'
import Export from './Export'
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 = {
INDEX: '/',
@ -86,29 +89,23 @@ export const onboardingRoutes = [
]
export function useNextClick(newStatus: string) {
const filePath = useAbsoluteFilePath()
const {
settings: { send },
} = useGlobalStateContext()
const navigate = useNavigate()
const { project } = useRouteLoaderData(paths.FILE) as IndexLoaderData
return useCallback(() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: newStatus },
})
navigate(
paths.FILE +
'/' +
encodeURIComponent(project?.path || 'new') +
paths.ONBOARDING.INDEX.slice(0, -1) +
newStatus
)
}, [project, newStatus, send, navigate])
navigate(filePath + paths.ONBOARDING.INDEX.slice(0, -1) + newStatus)
}, [filePath, newStatus, send, navigate])
}
export function useDismiss() {
const routeData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const filePath = useAbsoluteFilePath()
const {
settings: { send },
} = useGlobalStateContext()
@ -119,10 +116,8 @@ export function useDismiss() {
type: 'Set Onboarding Status',
data: { onboardingStatus: 'dismissed' },
})
navigate(
paths.FILE + '/' + encodeURIComponent(routeData?.project?.path || 'new')
)
}, [send, navigate, routeData])
navigate(filePath)
}, [send, navigate, filePath])
}
const Onboarding = () => {

View File

@ -24,11 +24,19 @@ import {
} from 'lib/cameraControls'
import { UnitSystem } from 'machines/settingsMachine'
import { useDotDotSlash } from 'hooks/useDotDotSlash'
import {
createNewProject,
getNextProjectIndex,
getProjectsInDir,
interpolateProjectNameWithIndex,
} from 'lib/tauriFS'
import { ONBOARDING_PROJECT_NAME } from './Onboarding'
export const Settings = () => {
const loaderData = useRouteLoaderData(paths.FILE) as IndexLoaderData
const navigate = useNavigate()
const location = useLocation()
const isFileSettings = location.pathname.includes(paths.FILE)
const dotDotSlash = useDotDotSlash()
useHotkeys('esc', () => navigate(dotDotSlash()))
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 (
<div className="fixed inset-0 z-40 overflow-auto body-bg">
<AppHeader showToolbar={false} project={loaderData?.project}>
@ -257,26 +292,18 @@ export const Settings = () => {
))}
</select>
</SettingsSection>
{location.pathname.includes(paths.FILE) && (
<SettingsSection
title="Onboarding"
description="Replay the onboarding process"
<SettingsSection
title="Onboarding"
description="Replay the onboarding process"
>
<ActionButton
Element="button"
onClick={restartOnboarding}
icon={{ icon: faArrowRotateBack }}
>
<ActionButton
Element="button"
onClick={() => {
send({
type: 'Set Onboarding Status',
data: { onboardingStatus: '' },
})
navigate(dotDotSlash(1) + paths.ONBOARDING.INDEX)
}}
icon={{ icon: faArrowRotateBack }}
>
Replay Onboarding
</ActionButton>
</SettingsSection>
)}
Replay Onboarding
</ActionButton>
</SettingsSection>
</div>
</div>
)

View File

@ -20,6 +20,8 @@ import { KCLError } from './lang/errors'
import { deferExecution } from 'lib/utils'
import { bracket } from 'lib/exampleKcl'
import { engineCommandManager } from './lang/std/engineConnection'
import { DefaultPlanes } from './wasm-lib/kcl/bindings/DefaultPlanes'
import { initDefaultPlanes } from './hooks/useAppMode'
export type Selection = {
type: 'default' | 'line-end' | 'line-mid'
@ -182,6 +184,10 @@ export interface StoreState {
}) => void
isExecuting: boolean
setIsExecuting: (isExecuting: boolean) => void
defaultPlanes: DefaultPlanes | null
setDefaultPlanes: (defaultPlanes: DefaultPlanes) => void
currentPlane: string | null
setCurrentPlane: (currentPlane: string) => void
showHomeMenu: boolean
setHomeShowMenu: (showMenu: boolean) => void
@ -222,10 +228,20 @@ export const useStore = create<StoreState>()(
}
},
executeCode: async (code, force) => {
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
const result = await executeCode({
code: code || get().code,
lastAst: get().ast,
engineCommandManager: engineCommandManager,
defaultPlanes: get().defaultPlanes!,
force,
})
if (!result.isChange) {
@ -332,11 +348,20 @@ export const useStore = create<StoreState>()(
executeAst: async (ast) => {
const _ast = ast || get().ast
if (!get().isStreamReady) return
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
set({ isExecuting: true })
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
defaultPlanes: get().defaultPlanes!,
})
set({
programMemory,
@ -349,10 +374,20 @@ export const useStore = create<StoreState>()(
const _ast = ast || get().ast
if (!get().isStreamReady) return
if (!get().defaultPlanes) {
let defaultPlanes = await initDefaultPlanes(
engineCommandManager,
true
)
if (!defaultPlanes) return
get().setDefaultPlanes(defaultPlanes)
}
const { logs, errors, programMemory } = await executeAst({
ast: _ast,
engineCommandManager,
useFakeExecutor: true,
defaultPlanes: get().defaultPlanes!,
})
set({
programMemory,
@ -453,6 +488,10 @@ export const useStore = create<StoreState>()(
},
isExecuting: false,
setIsExecuting: (isExecuting) => set({ isExecuting }),
defaultPlanes: null,
setDefaultPlanes: (defaultPlanes) => set({ defaultPlanes }),
currentPlane: null,
setCurrentPlane: (currentPlane) => set({ currentPlane }),
// tauri specific app settings
defaultDir: {
@ -512,11 +551,13 @@ async function executeCode({
engineCommandManager,
code,
lastAst,
defaultPlanes,
force,
}: {
code: string
lastAst: Program
engineCommandManager: EngineCommandManager
defaultPlanes: DefaultPlanes
force?: boolean
}): Promise<
| {
@ -566,6 +607,7 @@ async function executeCode({
const { logs, errors, programMemory } = await executeAst({
ast,
engineCommandManager,
defaultPlanes,
})
return {
ast,
@ -579,10 +621,12 @@ async function executeCode({
async function executeAst({
ast,
engineCommandManager,
defaultPlanes,
useFakeExecutor = false,
}: {
ast: Program
engineCommandManager: EngineCommandManager
defaultPlanes: DefaultPlanes
useFakeExecutor?: boolean
}): Promise<{
logs: string[]
@ -605,7 +649,8 @@ async function executeAst({
root: defaultProgramMemory,
return: null,
},
engineCommandManager
engineCommandManager,
defaultPlanes
))
await engineCommandManager.waitForAllCommands(ast, programMemory)

View File

@ -1390,7 +1390,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.32"
version = "0.1.33"
dependencies = [
"anyhow",
"async-recursion",
@ -1426,9 +1426,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.28"
version = "0.2.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35b2f9302648dbb06fd7121687f9505fc3179eba84111a06d76b246e3158f5dc"
checksum = "539b323537b877fc8dd130362b8f1af9af8051c19208bb8bfd816ab7c330f2bb"
dependencies = [
"anyhow",
"async-trait",
@ -2217,9 +2217,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
[[package]]
name = "reqwest"
version = "0.11.20"
version = "0.11.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
checksum = "046cd98826c46c2ac8ddecae268eb5c2e58628688a5fc7a2643704a73faba95b"
dependencies = [
"base64 0.21.4",
"bytes",
@ -2244,6 +2244,7 @@ dependencies = [
"serde",
"serde_json",
"serde_urlencoded",
"system-configuration",
"tokio",
"tokio-rustls",
"tower-service",
@ -2893,6 +2894,27 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "take_mut"
version = "0.2.2"

View File

@ -11,7 +11,7 @@ crate-type = ["cdylib"]
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
gloo-utils = "0.2.0"
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"
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }
wasm-bindgen = "0.2.87"
@ -20,9 +20,9 @@ wasm-bindgen-futures = "0.4.37"
[dev-dependencies]
anyhow = "1"
image = "0.24.7"
kittycad = "0.2.27"
kittycad = "0.2.31"
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.20", default-features = false }
reqwest = { version = "0.11.22", default-features = false }
tokio = { version = "1.32.0", features = ["rt-multi-thread", "macros", "time"] }
twenty-twenty = "0.6.1"
uuid = { version = "1.4.1", features = ["v4", "js", "serde"] }

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language"
version = "0.1.32"
version = "0.1.33"
edition = "2021"
license = "MIT"
@ -15,7 +15,7 @@ clap = { version = "4.4.6", features = ["cargo", "derive", "env", "unicode"], op
dashmap = "5.5.3"
derive-docs = { version = "0.1.4" }
#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"
parse-display = "0.8.2"
schemars = { version = "0.8", features = ["impl_json_schema", "url", "uuid1"] }
@ -36,7 +36,7 @@ web-sys = { version = "0.3.64", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
bson = { version = "2.7.0", features = ["uuid-1", "chrono"] }
futures = { version = "0.3.28" }
reqwest = { version = "0.11.20", default-features = false }
reqwest = { version = "0.11.22", default-features = false }
tokio = { version = "1.32.0", features = ["full"] }
tokio-tungstenite = { version = "0.20.0", features = ["rustls-tls-native-roots"] }
tower-lsp = { version = "0.20.0", features = ["proposed"] }

View File

@ -1 +1 @@
enum-variant-size-threshold = 24
enum-variant-size-threshold = 48

View File

@ -31,6 +31,8 @@ pub async fn modify_ast_for_sketch(
program: &mut Program,
// The name of the sketch.
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.
sketch_id: uuid::Uuid,
) -> 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),
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,
[first_control_points.points[0].x, first_control_points.points[0].y],
[start_sketch_at_end.x, start_sketch_at_end.y],
plane,
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.
fn create_start_sketch_at(
fn create_start_sketch_on(
name: &str,
start: [f64; 2],
end: [f64; 2],
plane: crate::executor::PlaneType,
additional_lines: Vec<[f64; 2]>,
) -> Result<VariableDeclarator, KclError> {
let start_sketch_at = CallExpression::new(
"startSketchAt",
vec![ArrayExpression::new(vec![
Literal::new(round_before_recast(start[0]).into()).into(),
Literal::new(round_before_recast(start[1]).into()).into(),
])
.into()],
let start_sketch_on = CallExpression::new("startSketchOn", vec![Literal::new(plane.to_string().into()).into()])?;
let start_profile_at = CallExpression::new(
"startProfileAt",
vec![
ArrayExpression::new(vec![
Literal::new(round_before_recast(start[0]).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.
@ -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() {
current_position.x += line[0];

View File

@ -1,6 +1,6 @@
//! Data types for the AST.
use std::collections::HashMap;
use std::{collections::HashMap, fmt::Write};
use anyhow::Result;
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 crate::{
engine::EngineConnection,
errors::{KclError, KclErrorDetails},
executor::{MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
executor::{ExecutorContext, MemoryItem, Metadata, PipeInfo, ProgramMemory, SourceRange, UserVal},
parser::PIPE_OPERATOR,
};
@ -29,73 +28,74 @@ pub struct Program {
impl Program {
pub fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
let indentation = options.get_indentation(indentation_level);
let result = self
.body
.iter()
.map(|statement| match statement.clone() {
BodyItem::ExpressionStatement(expression_statement) => {
expression_statement
.expression
.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()
let result =
self.body
.iter()
.map(|statement| match statement.clone() {
BodyItem::ExpressionStatement(expression_statement) => {
expression_statement
.expression
.recast(options, indentation_level, false)
}
} else {
// Do nothing, we already applied the indentation elsewhere.
String::new()
};
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration
.declarations
.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
// 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 {
String::new()
} else {
"\n".to_string()
};
// determine the value of the end string
// 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 {
String::new()
} else {
"\n".to_string()
};
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),
None => String::new(),
};
let end_string = if custom_white_space_or_comment.is_empty() {
maybe_line_break
} else {
custom_white_space_or_comment
};
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),
None => String::new(),
};
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)
})
.collect::<String>()
.trim()
.to_string();
let _ = write!(output, "{}{}{}", start_string, recast_str, end_string);
output
})
.trim()
.to_string();
// Insert a final new line if the user wants it.
if options.insert_final_newline {
@ -631,7 +631,7 @@ impl BinaryPart {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
// 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.
@ -646,13 +646,13 @@ impl BinaryPart {
Ok(value.clone())
}
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) => {
call_expression.execute(memory, &mut new_pipe_info, engine).await
call_expression.execute(memory, &mut new_pipe_info, ctx).await
}
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),
}
@ -879,7 +879,7 @@ impl CallExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
let fn_name = self.callee.name.clone();
@ -893,7 +893,7 @@ impl CallExpression {
value.clone()
}
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) => {
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
@ -901,15 +901,11 @@ impl CallExpression {
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
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) => {
unary_expression.get_result(memory, pipe_info, engine).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::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).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::PipeExpression(pipe_expression) => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("PipeExpression not implemented here: {:?}", pipe_expression),
@ -941,12 +937,12 @@ impl CallExpression {
match &self.function {
Function::StdLib { func } => {
// 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?;
if pipe_info.is_in_pipe {
pipe_info.index += 1;
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 {
Ok(result)
}
@ -954,7 +950,7 @@ impl CallExpression {
Function::InMemory => {
let func = memory.get(&fn_name, self.into())?;
let result = func
.call_fn(fn_args, memory.clone(), engine.clone())
.call_fn(fn_args, memory.clone(), ctx.clone())
.await?
.ok_or_else(|| {
KclError::UndefinedValue(KclErrorDetails {
@ -969,7 +965,7 @@ impl CallExpression {
pipe_info.index += 1;
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 {
Ok(result)
}
@ -1507,7 +1503,7 @@ impl ArrayExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
let mut results = Vec::with_capacity(self.elements.len());
@ -1519,7 +1515,7 @@ impl ArrayExpression {
value.clone()
}
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) => {
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
@ -1527,16 +1523,12 @@ impl ArrayExpression {
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
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) => {
unary_expression.get_result(memory, pipe_info, engine).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) => pipe_expression.get_result(memory, pipe_info, engine).await?,
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).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::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, ctx).await?,
Value::PipeSubstitution(pipe_substitution) => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
@ -1663,7 +1655,7 @@ impl ObjectExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
let mut object = Map::new();
for property in &self.properties {
@ -1674,7 +1666,7 @@ impl ObjectExpression {
value.clone()
}
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) => {
// We DO NOT set this gloablly because if we did and this was called inside a pipe it would
@ -1682,16 +1674,12 @@ impl ObjectExpression {
// THIS IS IMPORTANT.
let mut new_pipe_info = pipe_info.clone();
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) => {
unary_expression.get_result(memory, pipe_info, engine).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) => pipe_expression.get_result(memory, pipe_info, engine).await?,
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, ctx).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::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, ctx).await?,
Value::PipeSubstitution(pipe_substitution) => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("PipeSubstitution not implemented here: {:?}", pipe_substitution),
@ -2109,7 +2097,7 @@ impl BinaryExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
// 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.
@ -2119,12 +2107,12 @@ impl BinaryExpression {
let left_json_value = self
.left
.get_result(memory, &mut new_pipe_info, engine)
.get_result(memory, &mut new_pipe_info, ctx)
.await?
.get_json_value()?;
let right_json_value = self
.right
.get_result(memory, &mut new_pipe_info, engine)
.get_result(memory, &mut new_pipe_info, ctx)
.await?
.get_json_value()?;
@ -2283,7 +2271,7 @@ impl UnaryExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
// 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.
@ -2294,7 +2282,7 @@ impl UnaryExpression {
let num = parse_json_number_as_f64(
&self
.argument
.get_result(memory, &mut new_pipe_info, engine)
.get_result(memory, &mut new_pipe_info, ctx)
.await?
.get_json_value()?,
self.into(),
@ -2427,12 +2415,12 @@ impl PipeExpression {
&self,
memory: &mut ProgramMemory,
pipe_info: &mut PipeInfo,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
// Reset the previous results.
pipe_info.previous_results = vec![];
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.
@ -2449,7 +2437,7 @@ async fn execute_pipe_body(
body: &[Value],
pipe_info: &mut PipeInfo,
source_range: SourceRange,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<MemoryItem, KclError> {
if pipe_info.index == body.len() {
pipe_info.is_in_pipe = false;
@ -2474,15 +2462,15 @@ async fn execute_pipe_body(
match 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.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) => {
pipe_info.is_in_pipe = true;
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.
@ -2775,7 +2763,8 @@ mod tests {
#[test]
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], %)
const part002 = "part002"
@ -2796,25 +2785,10 @@ show(part001)"#;
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]
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.6804562304, 0.9087880491], %)"#;
let tokens = crate::token::lexer(some_program_string);
@ -2824,7 +2798,8 @@ show(part001)
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
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.6804562304, 0.9087880491], %)
"#
@ -2833,7 +2808,8 @@ show(part001)
#[test]
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.6804562304, 0.9087880491], %)"#;
let tokens = crate::token::lexer(some_program_string);
@ -2843,7 +2819,8 @@ show(part001)
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
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.6804562304, 0.9087880491], %)
"#
@ -2851,7 +2828,8 @@ show(part001)
}
#[test]
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], %)
// hello world
|> line([0.6804562304, 0.9087880491], %)"#;
@ -2862,7 +2840,8 @@ show(part001)
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
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.6804562304, 0.9087880491], %)
@ -2904,7 +2883,8 @@ show(part001)
#[test]
fn test_recast_lots_of_comments() {
let some_program_string = r#"// comment at start
const mySk1 = startSketchAt([0, 0])
const mySk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
@ -2926,7 +2906,8 @@ const mySk1 = startSketchAt([0, 0])
assert_eq!(
recasted,
r#"// comment at start
const mySk1 = startSketchAt([0, 0])
const mySk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
@ -2946,7 +2927,8 @@ a comment between pipe expression statements */
#[test]
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([2.77, -1.24], %)
|> angledLineThatIntersects({
@ -3034,7 +3016,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
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
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
@ -3059,7 +3042,8 @@ const myVar2 = 5
const myVar3 = 6
const myAng = 40
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
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
@ -3087,7 +3071,8 @@ const part001 = startSketchAt([0, 0])
#[test]
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], %)
const part002 = "part002"
@ -3109,7 +3094,8 @@ show(part001)"#;
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"const mySuperCoolPart = startSketchAt([0.0, 5.0])
r#"const mySuperCoolPart = startSketchOn('XY')
|> startProfileAt([0.0, 5.0], %)
|> line([0.4900857016, -0.0240763666], %)
const part002 = "part002"
@ -3153,7 +3139,8 @@ show(mySuperCoolPart)
const l = 8
const h = 10
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
@ -3172,7 +3159,8 @@ show(firstExtrude)"#;
const l = 8
const h = 10
const firstExtrude = startSketchAt([0, 0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)

View File

@ -3,13 +3,15 @@
use std::collections::HashMap;
use anyhow::Result;
use kittycad::types::{Color, ModelingCmd, Point3D};
use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
use crate::{
ast::types::{BodyItem, Function, FunctionExpression, Value},
engine::EngineConnection,
engine::{EngineConnection, EngineManager},
errors::{KclError, KclErrorDetails},
};
@ -101,6 +103,7 @@ impl ProgramReturn {
#[serde(tag = "type")]
pub enum MemoryItem {
UserVal(UserVal),
Plane(Box<Plane>),
SketchGroup(Box<SketchGroup>),
ExtrudeGroup(Box<ExtrudeGroup>),
#[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 planes X axis be?
pub x_axis: Point3d,
/// What should the planes 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)]
#[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")]
@ -140,7 +303,7 @@ pub type MemoryFunction =
memory: ProgramMemory,
expression: Box<FunctionExpression>,
metadata: Vec<Metadata>,
engine: EngineConnection,
ctx: ExecutorContext,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>;
fn force_memory_function<
@ -149,7 +312,7 @@ fn force_memory_function<
ProgramMemory,
Box<FunctionExpression>,
Vec<Metadata>,
EngineConnection,
ExecutorContext,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>>>>,
>(
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::ExtrudeTransform(e) => e.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,
args: Vec<MemoryItem>,
memory: ProgramMemory,
engine: EngineConnection,
ctx: ExecutorContext,
) -> Result<Option<ProgramReturn>, KclError> {
if let MemoryItem::Function { func, expression, meta } = &self {
if let Some(func) = func {
func(args, memory, expression.clone(), meta.clone(), engine).await
func(args, memory, expression.clone(), meta.clone(), ctx).await
} else {
Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a function: {:?}", expression),
@ -222,6 +386,8 @@ pub struct SketchGroup {
pub position: Position,
/// The rotation of the sketch group.
pub rotation: Rotation,
/// The plane id of the sketch group.
pub plane_id: Option<uuid::Uuid>,
/// Metadata.
#[serde(rename = "__meta")]
pub meta: Vec<Metadata>,
@ -414,6 +580,18 @@ pub struct Point3d {
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.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[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.
pub async fn execute(
program: crate::ast::types::Program,
memory: &mut ProgramMemory,
options: BodyType,
engine: &EngineConnection,
ctx: &ExecutorContext,
) -> Result<ProgramMemory, KclError> {
let mut pipe_info = PipeInfo::default();
@ -617,23 +802,23 @@ pub async fn execute(
args.push(memory_item.clone());
}
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);
}
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);
}
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);
}
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);
}
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);
}
// We do nothing for the rest.
@ -651,7 +836,7 @@ pub async fn execute(
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
} 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;
} else {
@ -677,7 +862,7 @@ pub async fn execute(
memory.add(&var_name, value.clone(), source_range)?;
}
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)?;
}
Value::FunctionExpression(function_expression) => {
@ -686,7 +871,7 @@ pub async fn execute(
memory: ProgramMemory,
function_expression: Box<FunctionExpression>,
_metadata: Vec<Metadata>,
engine: EngineConnection| {
ctx: ExecutorContext| {
Box::pin(async move {
let mut fn_memory = memory.clone();
@ -714,7 +899,7 @@ pub async fn execute(
function_expression.body.clone(),
&mut fn_memory,
BodyType::Block,
&engine,
&ctx,
)
.await?;
@ -733,11 +918,11 @@ pub async fn execute(
)?;
}
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)?;
}
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)?;
}
Value::PipeSubstitution(pipe_substitution) => {
@ -750,11 +935,11 @@ pub async fn execute(
}));
}
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)?;
}
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)?;
}
Value::MemberExpression(member_expression) => {
@ -762,7 +947,7 @@ pub async fn execute(
memory.add(&var_name, result, source_range)?;
}
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)?;
}
}
@ -770,11 +955,11 @@ pub async fn execute(
}
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
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));
}
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));
}
Value::Identifier(identifier) => {
@ -785,15 +970,15 @@ pub async fn execute(
memory.return_ = Some(ProgramReturn::Value(literal.into()));
}
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));
}
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));
}
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));
}
Value::MemberExpression(member_expr) => {
@ -801,7 +986,7 @@ pub async fn execute(
memory.return_ = Some(ProgramReturn::Value(result));
}
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));
}
Value::PipeSubstitution(_) => {}
@ -825,7 +1010,9 @@ mod tests {
let program = parser.ast()?;
let mut mem: ProgramMemory = Default::default();
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)
}
@ -849,7 +1036,8 @@ const newVar = myVar + 1"#;
async fn test_execute_angled_line_that_intersects() {
let ast_fn = |offset: &str| -> String {
format!(
r#"const part001 = startSketchAt([0, 0])
r#"const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo({{to:[2, 2], tag: "yo"}}, %)
|> lineTo([3, 1], %)
|> angledLineThatIntersects({{
@ -896,7 +1084,8 @@ const yo = 5 + 6
const abc = 3
const identifierGuy = 5
const part001 = startSketchAt([-1.2, 4.83])
const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)
|> line([2.8, 0], %)
|> angledLine([100 + 100, 3.01], %)
|> angledLine([abc, 3.02], %)
@ -913,7 +1102,8 @@ show(part001)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_pipe_substitutions_unary() {
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([
min(segLen('seg01', %), myVar),
@ -928,7 +1118,8 @@ show(part001)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_pipe_substitutions() {
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([
min(segLen('seg01', %), myVar),
@ -951,7 +1142,8 @@ const halfArmAngle = armAngle / 2
const arrExpShouldNotBeIncluded = [1, 2, 3]
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
const part001 = startSketchAt([0, 0])
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> yLineTo(1, %)
|> xLine(3.84, %) // selection-range-7ish-before-this
@ -972,7 +1164,8 @@ fn thing = () => {
return -8
}
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing()], %)
@ -994,7 +1187,8 @@ fn thing = (x) => {
return -x
}
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing(8)], %)
@ -1016,7 +1210,8 @@ fn thing = (x) => {
return [0, -x]
}
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line(thing(8), %)
@ -1042,7 +1237,8 @@ fn thing = (x) => {
return other_thing(x)
}
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, thing(8)], %)
@ -1057,7 +1253,8 @@ show(firstExtrude)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_with_function_sketch() {
let ast = r#"fn box = (h, l, w) => {
const myBox = startSketchAt([0,0])
const myBox = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
@ -1077,7 +1274,8 @@ show(fnBox)"#;
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_period() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchAt(obj.start)
let myBox = startSketchOn('XY')
|> startProfileAt(obj.start, %)
|> line([0, obj.l], %)
|> line([obj.w, 0], %)
|> line([0, -obj.l], %)
@ -1097,7 +1295,8 @@ show(thisBox)
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_brace() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchAt(obj["start"])
let myBox = startSketchOn('XY')
|> startProfileAt(obj["start"], %)
|> line([0, obj["l"]], %)
|> line([obj["w"], 0], %)
|> line([0, -obj["l"]], %)
@ -1117,7 +1316,8 @@ show(thisBox)
#[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_object_with_function_mix_period_brace() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchAt(obj["start"])
let myBox = startSketchOn('XY')
|> startProfileAt(obj["start"], %)
|> line([0, obj["l"]], %)
|> line([obj["w"], 0], %)
|> line([10 - obj["w"], -obj.l], %)
@ -1138,7 +1338,8 @@ show(thisBox)
#[ignore] // ignore til we get loops
async fn test_execute_with_function_sketch_loop_objects() {
let ast = r#"fn box = (obj) => {
let myBox = startSketchAt(obj.start)
let myBox = startSketchOn('XY')
|> startProfileAt(obj.start, %)
|> line([0, obj.l], %)
|> line([obj.w, 0], %)
|> 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
async fn test_execute_with_function_sketch_loop_array() {
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([w, 0], %)
|> 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")]
async fn test_get_member_of_array_with_function() {
let ast = r#"fn box = (array) => {
let myBox = startSketchAt(array[0])
let myBox =startSketchOn('XY')
|> startProfileAt(array[0], %)
|> line([0, array[1]], %)
|> line([array[2], 0], %)
|> line([0, -array[1]], %)
@ -1256,7 +1459,8 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
fn thickness = () => { return 0.56 }
const bracket = startSketchAt([0,0])
const bracket = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, leg1], %)
|> line([leg2, 0], %)
|> line([0, -thickness()], %)
@ -1280,7 +1484,8 @@ const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
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([leg2, 0], %)
|> line([0, -thickness], %)
@ -1300,7 +1505,8 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
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([leg2, 0], %)
|> line([0, -thickness], %)
@ -1324,7 +1530,8 @@ const leg1 = 5 // inches
const leg2 = 8 // inches
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
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([leg2, 0], %)
|> line([0, -thickness], %)

View File

@ -552,7 +552,7 @@ impl Parser {
_previous_keys: Option<Vec<ObjectKeyInfo>>,
has_opening_brace: bool,
) -> 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)?;
if next_token.index == self.tokens.len() - 1 {
return Ok(previous_keys);
@ -1918,28 +1918,6 @@ const key = 'c'"#,
31,
);
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]
@ -2070,7 +2048,8 @@ const key = 'c'"#,
fn test_next_meaningful_token() {
let _offset = 1;
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([1, 1], %) /* this is
a comment
@ -2082,10 +2061,10 @@ const key = 'c'"#,
let index = 17;
let expected_output = TokenReturnWithNonCode {
token: Some(Token {
token_type: TokenType::Brace,
start: 49,
end: 50,
value: "(".to_string(),
token_type: TokenType::Number,
start: 60,
end: 61,
value: "0".to_string(),
}),
index: 18,
non_code_node: None,
@ -2095,9 +2074,9 @@ const key = 'c'"#,
let expected_output = TokenReturnWithNonCode {
token: Some(Token {
token_type: TokenType::Brace,
start: 50,
end: 51,
value: "{".to_string(),
start: 61,
end: 62,
value: "]".to_string(),
}),
index: 19,
non_code_node: None,
@ -2106,10 +2085,10 @@ const key = 'c'"#,
let index = 21;
let expected_output = TokenReturnWithNonCode {
token: Some(Token {
token_type: TokenType::Colon,
start: 54,
end: 55,
value: ":".to_string(),
token_type: TokenType::Operator,
start: 64,
end: 65,
value: "%".to_string(),
}),
index: 22,
non_code_node: None,
@ -2118,10 +2097,10 @@ const key = 'c'"#,
let index = 24;
let expected_output = TokenReturnWithNonCode {
token: Some(Token {
token_type: TokenType::Number,
start: 57,
end: 58,
value: "0".to_string(),
token_type: TokenType::Operator,
start: 69,
end: 71,
value: "|>".to_string(),
}),
index: 25,
non_code_node: None,
@ -2130,324 +2109,12 @@ const key = 'c'"#,
let index = 25;
let expected_output = TokenReturnWithNonCode {
token: Some(Token {
token_type: TokenType::Comma,
start: 58,
end: 59,
value: ",".to_string(),
token_type: TokenType::Word,
start: 72,
end: 78,
value: "lineTo".to_string(),
}),
index: 26,
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,
index: 27,
non_code_node: None,
};
assert_eq!(parser.next_meaningful_token(index, None).unwrap(), expected_output);
@ -2456,7 +2123,8 @@ const key = 'c'"#,
#[test]
fn test_find_closing_brace() {
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([1, 1], %) /* this is
a comment
@ -2465,11 +2133,10 @@ const key = 'c'"#,
|> close(%)"#,
);
let parser = Parser::new(tokens);
assert_eq!(parser.find_closing_brace(7, 0, "").unwrap(), 13);
assert_eq!(parser.find_closing_brace(18, 0, "").unwrap(), 41);
assert_eq!(parser.find_closing_brace(46, 0, "").unwrap(), 56);
assert_eq!(parser.find_closing_brace(63, 0, "").unwrap(), 85);
assert_eq!(parser.find_closing_brace(90, 0, "").unwrap(), 92);
assert_eq!(parser.find_closing_brace(7, 0, "").unwrap(), 9);
assert_eq!(parser.find_closing_brace(14, 0, "").unwrap(), 23);
assert_eq!(parser.find_closing_brace(29, 0, "").unwrap(), 47);
assert_eq!(parser.find_closing_brace(57, 0, "").unwrap(), 62);
let basic = "( hey )";
let parser = Parser::new(crate::token::lexer(basic));
@ -2490,7 +2157,8 @@ const key = 'c'"#,
#[test]
fn test_is_call_expression() {
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([1, 1], %) /* this is
a comment
@ -2501,8 +2169,8 @@ const key = 'c'"#,
let parser = Parser::new(tokens);
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(15).unwrap(), None);
assert_eq!(parser.is_call_expression(6).unwrap(), Some(9));
assert_eq!(parser.is_call_expression(9).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(87).unwrap(), None);
@ -2511,7 +2179,8 @@ const key = 'c'"#,
#[test]
fn test_find_next_declaration_keyword() {
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([1, 1], %) /* this is
a comment
@ -2522,7 +2191,10 @@ const key = 'c'"#,
let parser = Parser::new(tokens);
assert_eq!(
parser.find_next_declaration_keyword(4).unwrap(),
TokenReturn { token: None, index: 92 }
TokenReturn {
token: None,
index: 102
}
);
let tokens = crate::token::lexer(
@ -2986,7 +2658,8 @@ show(mySk1)"#;
#[test]
fn test_parse_half_pipe_small() {
let tokens = crate::token::lexer(
"const secondExtrude = startSketchAt([0,0])
"const secondExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|",
);
let parser = Parser::new(tokens);
@ -3067,7 +2740,8 @@ const height = [obj["a"] -1, 0]"#,
let tokens = crate::token::lexer(
"const height = 10
const firstExtrude = startSketchAt([0,0])
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, 8], %)
|> line([20, 0], %)
|> line([0, -8], %)
@ -3076,7 +2750,8 @@ const firstExtrude = startSketchAt([0,0])
show(firstExtrude)
const secondExtrude = startSketchAt([0,0])
const secondExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|",
);
let parser = Parser::new(tokens);
@ -3457,7 +3132,8 @@ thing(false)
#[test]
fn test_member_expression_sketch_group() {
let some_program_string = r#"fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
@ -3499,7 +3175,8 @@ let other_thing = 2 * cos(3)"#;
#[test]
fn test_negative_arguments() {
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([w, 0], %)
|> line([0, -l], %)

View File

@ -17,9 +17,9 @@ use serde::{Deserialize, Serialize};
use crate::{
ast::types::parse_json_number_as_f64,
engine::{EngineConnection, EngineManager},
engine::EngineManager,
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>>>>;
@ -59,6 +59,8 @@ impl StdLib {
Box::new(crate::std::sketch::AngledLineOfYLength),
Box::new(crate::std::sketch::AngledLineThatIntersects),
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::Arc),
Box::new(crate::std::sketch::TangentalArc),
@ -109,15 +111,15 @@ impl Default for StdLib {
pub struct Args {
pub args: Vec<MemoryItem>,
pub source_range: SourceRange,
engine: EngineConnection,
pub ctx: ExecutorContext,
}
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 {
args,
source_range,
engine,
ctx,
}
}
@ -126,7 +128,7 @@ impl Args {
id: uuid::Uuid,
cmd: kittycad::types::ModelingCmd,
) -> 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> {
@ -308,6 +310,44 @@ impl Args {
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> {
// Iterate over our args, the first argument should be a UserVal with a string value.
// The second argument should be a number.

View File

@ -9,7 +9,9 @@ use serde::{Deserialize, Serialize};
use super::utils::Angle;
use crate::{
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::{
utils::{arc_angles, arc_center_and_end, get_x_component, get_y_component, intersection_with_parallel_line},
Args,
@ -649,11 +651,202 @@ pub async fn start_sketch_at(args: Args) -> Result<MemoryItem, KclError> {
Ok(MemoryItem::SketchGroup(sketch_group))
}
/// Start a sketch at a given point.
/// Start a sketch at a given point on the 'XY' plane.
#[stdlib {
name = "startSketchAt",
}]
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 planes X axis be?
x_axis: Box<Point3d>,
/// What should the planes 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 {
LineData::PointWithTag { 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,
position: Position([0.0, 0.0, 0.0]),
rotation: Rotation([0.0, 0.0, 0.0, 1.0]),
plane_id: Some(plane.id),
value: vec![],
start: current_path,
meta: vec![args.source_range.into()],
@ -728,6 +922,13 @@ async fn inner_close(sketch_group: Box<SketchGroup>, args: Args) -> Result<Box<S
)
.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();
new_sketch_group.value.push(Path::ToPoint {
base: BasePath {
@ -1199,7 +1400,7 @@ mod tests {
use pretty_assertions::assert_eq;
use crate::std::sketch::LineData;
use crate::std::sketch::{LineData, PlaneData};
#[test]
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);
}
}

View File

@ -1434,13 +1434,13 @@ const things = "things"
fn test_kitt() {
let program = include_str!("../../../tests/executor/inputs/kittycad_svg.kcl");
let actual = lexer(program).unwrap();
assert_eq!(actual.len(), 5088);
assert_eq!(actual.len(), 5098);
}
#[test]
fn test_pipes_on_pipes() {
let program = include_str!("../../../tests/executor/inputs/pipes_on_pipes.kcl");
let actual = lexer(program).unwrap();
assert_eq!(actual.len(), 17836);
assert_eq!(actual.len(), 17846);
}
#[test]
fn test_lexer_negative_word() {

View File

@ -16,16 +16,21 @@ pub async fn execute_wasm(
program_str: &str,
memory_str: &str,
manager: kcl_lib::engine::conn_wasm::EngineCommandManager,
planes_str: &str,
) -> Result<JsValue, String> {
// 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 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 engine = kcl_lib::engine::EngineConnection::new(manager)
.await
.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
.map_err(String::from)?;
// 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,
program_str: &str,
sketch_name: &str,
plane_type: &str,
sketch_id: &str,
) -> Result<JsValue, String> {
// 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 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)
.await
.map_err(|e| format!("{:?}", e))?;
@ -53,6 +61,7 @@ pub async fn modify_ast_for_sketch_wasm(
&mut engine,
&mut program,
sketch_name,
plane,
uuid::Uuid::parse_str(sketch_id).map_err(|e| e.to_string())?,
)
.await

View File

@ -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, -25.2], %) // VerticalLineAbsolute

View File

@ -1,4 +1,5 @@
const svg = startSketchAt([0, 0])
const svg = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([22.687663, -2.7664351], %) // MoveRelative
|> lineTo([15.687664000000002, -5.7664351], %) // MoveRelative

View File

@ -37,10 +37,13 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
let program = parser.ast()?;
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
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.
let resp = engine
let resp = ctx
.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
kcl_lib::executor::SourceRange::default(),
@ -68,7 +71,8 @@ async fn execute_and_snapshot(code: &str) -> Result<image::DynamicImage> {
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_execute_with_function_sketch() {
let code = r#"fn box = (h, l, w) => {
const myBox = startSketchAt([0,0])
const myBox = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
@ -89,7 +93,8 @@ show(fnBox)"#;
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_execute_with_function_sketch_with_position() {
let code = r#"fn box = (p, h, l, w) => {
const myBox = startSketchAt(p)
const myBox = startSketchOn('XY')
|> startProfileAt(p, %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
@ -107,7 +112,8 @@ show(box([0,0], 3, 6, 10))"#;
#[tokio::test(flavor = "multi_thread")]
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({ to: [3.15, -9.85], tag: 'seg01' }, %)
|> line([-15.17, -4.1], %)
@ -133,7 +139,8 @@ const FOS = 2
const leg1 = 5 // inches
const leg2 = 8 // 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([leg2, 0], %)
|> line([0, -thickness], %)
@ -148,9 +155,45 @@ show(bracket)"#;
twenty_twenty::assert_image("tests/executor/outputs/parametric.png", &result, 1.0);
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_execute_parametric_with_tan_arc_example() {
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 = startSketchAt([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)"#;
let result = execute_and_snapshot(code).await.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/parametric_with_tan_arc.png", &result, 1.0);
}
#[tokio::test(flavor = "multi_thread")]
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([8.01362, -5.48731], %)
|> line([-1.02877, -6.76825], %)
@ -162,7 +205,7 @@ async fn serial_test_execute_engine_error_return() {
assert!(result.is_err());
assert_eq!(
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!\" }])" }"#,
);
}
@ -186,7 +229,8 @@ async fn serial_test_execute_kittycad_svg() {
#[tokio::test(flavor = "multi_thread")]
async fn test_member_expression_sketch_group() {
let code = r#"fn cube = (pos, scale) => {
const sg = startSketchAt(pos)
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
@ -217,7 +261,8 @@ async fn test_close_arc() {
const radius = 40
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}, %)
|> close(%)
|> extrude(height, %)
@ -235,7 +280,8 @@ const height = 10
const length = 12
fn box = (sk1, sk2, scale) => {
const boxSketch = startSketchAt([sk1, sk2])
const boxSketch = startSketchOn('XY')
|> startProfileAt([sk1, sk2], %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
@ -291,3 +337,89 @@ async fn test_basic_tangental_arc_to() {
let result = execute_and_snapshot(code).await.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/tangental_arc_to.png", &result, 1.0);
}
#[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, 1.0);
}
#[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, 1.0);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -1,14 +1,14 @@
use anyhow::Result;
use kcl_lib::{
ast::{modify::modify_ast_for_sketch, types::Program},
engine::{EngineConnection, EngineManager},
executor::{MemoryItem, SourceRange},
engine::EngineManager,
executor::{ExecutorContext, MemoryItem, PlaneType, SourceRange},
};
use kittycad::types::{ModelingCmd, Point3D};
use pretty_assertions::assert_eq;
/// Setup the engine and parse code for an ast.
async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uuid::Uuid)> {
async fn setup(code: &str, name: &str) -> Result<(ExecutorContext, Program, uuid::Uuid)> {
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
let http_client = reqwest::Client::builder()
.user_agent(user_agent)
@ -38,8 +38,9 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
let program = parser.ast()?;
let mut mem: kcl_lib::executor::ProgramMemory = Default::default();
let engine = kcl_lib::engine::EngineConnection::new(ws).await?;
let memory =
kcl_lib::executor::execute(program.clone(), &mut mem, kcl_lib::executor::BodyType::Root, &engine).await?;
let planes = kcl_lib::executor::DefaultPlanes::new(&engine).await?;
let ctx = ExecutorContext { engine, planes };
let memory = kcl_lib::executor::execute(program.clone(), &mut mem, kcl_lib::executor::BodyType::Root, &ctx).await?;
// We need to get the sketch ID.
// Get the sketch group ID from memory.
@ -49,7 +50,7 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
let sketch_id = sketch_group.id;
let plane_id = uuid::Uuid::new_v4();
engine
ctx.engine
.send_modeling_cmd(
plane_id,
SourceRange::default(),
@ -59,6 +60,7 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
size: 60.0,
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?;
@ -66,7 +68,7 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
// Enter sketch mode.
// We can't get control points without being in sketch mode.
// You can however get path info without sketch mode.
engine
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
SourceRange::default(),
@ -74,13 +76,14 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
animated: false,
ortho: true,
plane_id,
disable_camera_with_plane: Some(Point3D { x: 0.0, y: 0.0, z: 1.0 }),
},
)
.await?;
// Enter edit mode.
// We can't get control points of an existing sketch without being in edit mode.
engine
ctx.engine
.send_modeling_cmd(
uuid::Uuid::new_v4(),
SourceRange::default(),
@ -88,14 +91,15 @@ async fn setup(code: &str, name: &str) -> Result<(EngineConnection, Program, uui
)
.await?;
Ok((engine, program, sketch_id))
Ok((ctx, program, sketch_id))
}
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_modify_sketch_part001() {
let name = "part001";
let code = format!(
r#"const {} = startSketchAt([8.41, 5.78])
r#"const {} = startSketchOn("XY")
|> startProfileAt([8.41, 5.78], %)
|> line([7.37, -11.0], %)
|> line([-8.69, -3.75], %)
|> line([-5.0, 4.25], %)
@ -103,9 +107,9 @@ async fn serial_test_modify_sketch_part001() {
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
let new_code = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id)
.await
.unwrap();
@ -119,7 +123,8 @@ async fn serial_test_modify_sketch_part001() {
async fn serial_test_modify_sketch_part002() {
let name = "part002";
let code = format!(
r#"const {} = startSketchAt([8.41, 5.78])
r#"const {} = startSketchOn("XY")
|> startProfileAt([8.41, 5.78], %)
|> line([7.42, -8.62], %)
|> line([-6.38, -3.51], %)
|> line([-3.77, 3.56], %)
@ -127,9 +132,9 @@ async fn serial_test_modify_sketch_part002() {
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
let new_code = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id)
.await
.unwrap();
@ -144,7 +149,8 @@ async fn serial_test_modify_sketch_part002() {
async fn serial_test_modify_close_sketch() {
let name = "part002";
let code = format!(
r#"const {} = startSketchAt([7.91, 3.89])
r#"const {} = startSketchOn("XY")
|> startProfileAt([7.91, 3.89], %)
|> line([7.42, -8.62], %)
|> line([-6.38, -3.51], %)
|> line([-3.77, 3.56], %)
@ -153,9 +159,9 @@ async fn serial_test_modify_close_sketch() {
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
let new_code = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id)
.await
.unwrap();
@ -169,7 +175,8 @@ async fn serial_test_modify_close_sketch() {
async fn serial_test_modify_line_to_close_sketch() {
let name = "part002";
let code = format!(
r#"const {} = startSketchAt([7.91, 3.89])
r#"const {} = startSketchOn("XY")
|> startProfileAt([7.91, 3.89], %)
|> line([7.42, -8.62], %)
|> line([-6.38, -3.51], %)
|> line([-3.77, 3.56], %)
@ -178,9 +185,9 @@ async fn serial_test_modify_line_to_close_sketch() {
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
let new_code = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id)
.await
.unwrap();
@ -188,7 +195,8 @@ async fn serial_test_modify_line_to_close_sketch() {
assert_eq!(
new_code,
format!(
r#"const {} = startSketchAt([7.91, 3.89])
r#"const {} = startSketchOn("XY")
|> startProfileAt([7.91, 3.89], %)
|> line([7.42, -8.62], %)
|> line([-6.38, -3.51], %)
|> line([-3.77, 3.56], %)
@ -204,7 +212,8 @@ async fn serial_test_modify_with_constraint() {
let name = "part002";
let code = format!(
r#"const thing = 12
const {} = startSketchAt([7.91, 3.89])
const {} = startSketchOn("XY")
|> startProfileAt([7.91, 3.89], %)
|> line([7.42, -8.62], %)
|> line([-6.38, -3.51], %)
|> line([-3.77, 3.56], %)
@ -213,14 +222,14 @@ const {} = startSketchAt([7.91, 3.89])
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let result = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id).await;
let result = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
r#"engine: KclErrorDetails { source_ranges: [SourceRange([159, 164])], message: "Sketch part002 is constrained `partial` and cannot be modified" }"#
r#"engine: KclErrorDetails { source_ranges: [SourceRange([188, 193])], message: "Sketch part002 is constrained `partial` and cannot be modified" }"#
);
}
@ -228,7 +237,8 @@ const {} = startSketchAt([7.91, 3.89])
async fn serial_test_modify_line_should_close_sketch() {
let name = "part003";
let code = format!(
r#"const {} = startSketchAt([13.69, 3.8])
r#"const {} = startSketchOn("XY")
|> startProfileAt([13.69, 3.8], %)
|> line([4.23, -11.79], %)
|> line([-10.7, -1.16], %)
|> line([-3.72, 8.69], %)
@ -237,9 +247,9 @@ async fn serial_test_modify_line_should_close_sketch() {
name
);
let (mut engine, program, sketch_id) = setup(&code, name).await.unwrap();
let (mut ctx, program, sketch_id) = setup(&code, name).await.unwrap();
let mut new_program = program.clone();
let new_code = modify_ast_for_sketch(&mut engine, &mut new_program, name, sketch_id)
let new_code = modify_ast_for_sketch(&mut ctx.engine, &mut new_program, name, PlaneType::XY, sketch_id)
.await
.unwrap();
@ -247,7 +257,8 @@ async fn serial_test_modify_line_should_close_sketch() {
assert_eq!(
new_code,
format!(
r#"const {} = startSketchAt([13.69, 3.8])
r#"const {} = startSketchOn("XY")
|> startProfileAt([13.69, 3.8], %)
|> line([4.23, -11.79], %)
|> line([-10.7, -1.16], %)
|> line([-3.72, 8.69], %)

View File

@ -1,6 +1,9 @@
{
"compilerOptions": {
"baseUrl": "src",
"paths": {
"/*": ["src/*"]
},
"types": ["vite/client", "@types/wicg-file-system-access"],
"target": "esnext",
"lib": [

View File

@ -8,9 +8,9 @@
integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
"@adobe/css-tools@^4.0.1":
version "4.2.0"
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.2.0.tgz#e1a84fca468f4b337816fcb7f0964beb620ba855"
integrity sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==
version "4.3.1"
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.3.1.tgz#abfccb8ca78075a2b6187345c26243c1a0842f28"
integrity sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==
"@alloc/quick-lru@^5.2.0":
version "5.2.0"
@ -1530,10 +1530,10 @@
resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.4.0.tgz#08d6c5e20cf7e4cc02fd181c4b0c225cd31dbb60"
integrity sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==
"@kittycad/lib@^0.0.40":
version "0.0.40"
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-0.0.40.tgz#0ba00c642e76648fb7cb1337e799b9d24724312d"
integrity sha512-R8sQKLWe3lQC7l7cyY49oFgeiMvRh8+bCaaoLiIVYT+YiE9TaS+uwwF1+sR7MiX6YZp/YCBBPFGj4Ci0VHC9Bg==
"@kittycad/lib@^0.0.43":
version "0.0.43"
resolved "https://registry.yarnpkg.com/@kittycad/lib/-/lib-0.0.43.tgz#b93c0961200cf327f5ac6491be1ca01f9edc321c"
integrity sha512-Pe/PQfZ8BWEDOm4dkY4tcPcuCSh2mQPe/W8RvJ6PQNTlB7bmjAj0234pVyG+8zrKKsJC9nq4ye7CZoaXEfUSTg==
dependencies:
node-fetch "3.3.2"
openapi-types "^12.0.0"
@ -3547,12 +3547,7 @@ gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
get-func-name@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==
get-func-name@^2.0.2:
get-func-name@^2.0.0, get-func-name@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.2.tgz#0d7cf20cd13fda808669ffa88f4ffc7a3943fc41"
integrity sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==
@ -4819,10 +4814,10 @@ queue-microtask@^1.2.2:
resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
re-resizable@^6.9.9:
version "6.9.9"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.9.tgz#99e8b31c67a62115dc9c5394b7e55892265be216"
integrity sha512-l+MBlKZffv/SicxDySKEEh42hR6m5bAHfNu3Tvxks2c4Ah+ldnWjfnVRwxo/nxF27SsUsxDS0raAzFuJNKABXA==
re-resizable@^6.9.11:
version "6.9.11"
resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.9.11.tgz#f356e27877f12d926d076ab9ad9ff0b95912b475"
integrity sha512-a3hiLWck/NkmyLvGWUuvkAmN1VhwAz4yOhS6FdMTaxCUVN9joIWkT11wsO68coG/iEYuwn+p/7qAmfQzRhiPLQ==
react-base16-styling@^0.6.0:
version "0.6.0"