Compare commits

...

44 Commits

Author SHA1 Message Date
a7b09f996a A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-03-03 09:24:32 +00:00
fa0a21b841 trigger ci 2024-03-03 20:14:42 +11:00
6cd66aca78 A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-03-03 09:01:56 +00:00
1a370cecda fix camera set from debug panel 2024-03-03 19:54:24 +11:00
223b5952aa Refactor mouse event args (#1613)
* refactor mouse event interfaces

Importantly returning multiple intersections from raycastRing, but other clean up

* refactor enter exit args interface

* type tweak
2024-03-03 16:23:16 +11:00
fedffbb384 Grackle: stdlib LineTo function (#1601)
* Bump execution plan

* Grackle: lineTo stdlib function (#1605)

* Remove test JSON output
2024-03-02 18:39:31 -06:00
ed4e3df3b2 unused vars cleanup (#1608) 2024-03-02 20:30:24 +11:00
18d200e790 add test: Can edit segments by dragging their handles (#1607) 2024-03-02 20:08:13 +11:00
0c50a5996d show selected color for start selected at (#1606) 2024-03-02 19:00:24 +11:00
73bca2dcfc fully remove show (#1592)
* fully remove show

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>

* fmt

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

* updates

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

* rm

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

* updates

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

* fix tests

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-03-01 17:16:18 -08:00
c6a50a3cdf try parallel plawright (#1579)
* parallel plawright

* test robustness tweak

* change to only double speed
2024-03-02 11:25:50 +11:00
b81c9d04cc make kcl std lib first class (#1603)
* make kcl std lib first class

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

* fix tests

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

* u[dates

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>
2024-03-01 14:23:30 -08:00
9d8a7064da make startProfileAt UI editable (#1599)
make startProfileAt editable
2024-03-02 08:48:30 +11:00
b0e6140e9f Implement dual camera sync direction (#1597)
* implement dual camer sync direction

The existance of the client side scene requires two cameras to stay in sync, really these need to be a master-slave relationship, intitial this was implemented with the client side scene taking the lead and sending updates to the server using the  endpoint (as it didn't require an new endpoints), but even though we added a sequence property to this endpoint and sent it over udp, it was still an abuse of this endpoint as the engine didn't have this endpoint setup with a fload of messages and low-latency in mind.

Now we have migrated back to sending mouse events to the engine instead, but with the engine replying with camera details on drag_end etc so that we can keep the client camera in sync.

The client side camera still does take the master role in sketch mode as it makes sense to keep the low latency benfits of the local camera for the locallay rendered assets in sketch mode, moving the camera in this mode already did hide the engine camera while the camera is moving so as to avoid ghoasting so this works well.

The camera controls now work by syncing in either direction depending on what's appropiate

* fmt

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* update default plane extrude numbers

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* trigger-ci

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-02 08:20:50 +11:00
f9df7ff885 import docs (#1602)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-01 12:19:24 -08:00
aec9637d7a EngineConnection should fail fast if socket closes (#1600)
* EngineConnection should fail fast if socket closes

* Fix clippy lint
2024-03-01 14:43:11 -05:00
e4c5fad8c7 failing auto complete test (#1578) 2024-03-01 08:22:04 -08:00
cc0d601294 enable concurrency for playwright action (#1598) 2024-03-01 07:08:02 -08:00
69cefafc19 Bump image from 0.24.8 to 0.24.9 in /src/wasm-lib (#1584)
Bumps [image](https://github.com/image-rs/image) from 0.24.8 to 0.24.9.
- [Changelog](https://github.com/image-rs/image/blob/master/CHANGES.md)
- [Commits](https://github.com/image-rs/image/compare/v0.24.8...v0.24.9)

---
updated-dependencies:
- dependency-name: image
  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>
2024-02-29 19:57:37 -08:00
b187ca3422 Bump kittycad from 0.2.54 to 0.2.58 in /src/wasm-lib (#1583)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.54 to 0.2.58.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.54...v0.2.58)

---
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>
2024-02-29 19:56:45 -08:00
1edadcaa0f Bump kittycad from 0.2.53 to 0.2.58 in /src-tauri (#1581)
Bumps [kittycad](https://github.com/KittyCAD/kittycad.rs) from 0.2.53 to 0.2.58.
- [Release notes](https://github.com/KittyCAD/kittycad.rs/releases)
- [Commits](https://github.com/KittyCAD/kittycad.rs/compare/v0.2.53...v0.2.58)

---
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>
2024-02-29 19:56:09 -08:00
95c0ded8cf Refactor: move point-parsing into its own function (#1590)
This will be reused in future stdlib functions.

Also, add a field for argument number to the "invalid argument type" error message.
2024-02-29 17:55:34 -06:00
0ebb4e2cad one more sentry (#1591)
Update KclSingleton.tsx
2024-02-29 14:56:57 -08:00
f3e0939057 Cut release v0.15.4 (#1587) 2024-03-01 09:50:28 +11:00
f5e233d8a0 Finish removing Sentry (#1588)
Finish removing Sentry

Following Frank's PR in 8f5034f997, I'm
sending up a PR to finish pulling Sentry out of the codebase, rather
than just disabling it via configuration.

F

Signed-off-by: Paul R. Tagliamonte <paul@kittycad.io>
2024-02-29 16:41:20 -05:00
1cab3e628f client side sketch scene not respecting base-unit-scale (#1576)
* client side sketch scene not respecting base-unit-scale

* test tweak

* remove dead code

* fix test

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* test fix up

* trigger ci

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)

* trigger ci

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-01 06:55:49 +11:00
2ca6ba52b6 Bump serde_json from 1.0.113 to 1.0.114 in /src-tauri (#1463)
Bumps [serde_json](https://github.com/serde-rs/json) from 1.0.113 to 1.0.114.
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.113...v1.0.114)

---
updated-dependencies:
- dependency-name: serde_json
  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>
2024-02-29 11:35:35 -08:00
f741ea2e09 Bump serde from 1.0.196 to 1.0.197 in /src-tauri (#1462)
Bumps [serde](https://github.com/serde-rs/serde) from 1.0.196 to 1.0.197.
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](https://github.com/serde-rs/serde/compare/v1.0.196...v1.0.197)

---
updated-dependencies:
- dependency-name: serde
  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>
2024-02-29 11:34:43 -08:00
9f2a7781fc Bump anyhow from 1.0.79 to 1.0.80 in /src-tauri (#1465)
Bumps [anyhow](https://github.com/dtolnay/anyhow) from 1.0.79 to 1.0.80.
- [Release notes](https://github.com/dtolnay/anyhow/releases)
- [Commits](https://github.com/dtolnay/anyhow/compare/1.0.79...1.0.80)

---
updated-dependencies:
- dependency-name: anyhow
  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>
2024-02-29 11:34:30 -08:00
990f2b4154 Vector for tracking cargo tests (#1580)
* try and log to vector

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

* iupdates

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

* try and log to vector

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

* try and log to vector

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

* ud[ates

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

* ud[ates

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

* ud[ates

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

* ud[ates

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

* ud[ates

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

* ud[ates

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

* ud[ates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-29 11:14:01 -08:00
0af0f15281 Bump clap from 4.5.0 to 4.5.1 in /src/wasm-lib (#1448)
Bumps [clap](https://github.com/clap-rs/clap) from 4.5.0 to 4.5.1.
- [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/clap_complete-v4.5.0...clap_complete-v4.5.1)

---
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>
2024-02-29 00:53:57 -08:00
b558548b94 Bump google-github-actions/auth from 2.1.1 to 2.1.2 (#1521)
Bumps [google-github-actions/auth](https://github.com/google-github-actions/auth) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google-github-actions/auth/compare/v2.1.1...v2.1.2)

---
updated-dependencies:
- dependency-name: google-github-actions/auth
  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>
2024-02-29 00:52:34 -08:00
29e0f9a270 Bump kittycad-execution-plan from 9cb86ba to 29086e1 in /src/wasm-lib (#1570)
Bump kittycad-execution-plan in /src/wasm-lib

Bumps [kittycad-execution-plan](https://github.com/KittyCAD/modeling-api) from `9cb86ba` to `29086e1`.
- [Commits](9cb86ba54e...29086e1079)

---
updated-dependencies:
- dependency-name: kittycad-execution-plan
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-29 00:52:18 -08:00
9385c32cfb Bump kittycad-modeling-session from 9cb86ba to 29086e1 in /src/wasm-lib (#1568)
Bump kittycad-modeling-session in /src/wasm-lib

Bumps [kittycad-modeling-session](https://github.com/KittyCAD/modeling-api) from `9cb86ba` to `29086e1`.
- [Commits](9cb86ba54e...29086e1079)

---
updated-dependencies:
- dependency-name: kittycad-modeling-session
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-29 00:51:56 -08:00
ce3fb5c353 Bump tauri-plugin-fs-extra from 01211ff to ed682dd in /src-tauri (#1567)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `01211ff` to `ed682dd`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](01211ff075...ed682dd96e)

---
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>
2024-02-29 00:51:24 -08:00
f920490518 Bump syn from 2.0.49 to 2.0.52 in /src/wasm-lib (#1563)
Bumps [syn](https://github.com/dtolnay/syn) from 2.0.49 to 2.0.52.
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.49...2.0.52)

---
updated-dependencies:
- dependency-name: syn
  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>
2024-02-29 00:51:03 -08:00
d681e667ee Hide cam when moving (#1577)
hide cam when moving
2024-02-29 19:25:48 +11:00
5c6515a60e Fix autocomplete in comment (#1575) 2024-02-28 23:24:11 -08:00
eb8a33312d fix trailing comma (#1574)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-28 22:24:11 -08:00
d351b3bbe4 fix recast (#1571)
* fix recast

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>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-28 21:19:10 -08:00
47d40eb801 Update test artifacts for patterns with holes (#1566)
* update test artifacts

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

* update known issues

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

* screenshots

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-02-28 19:18:23 -08:00
adc4b6148d Cut release v0.15.3 (#1546) 2024-02-29 12:39:20 +11:00
27d0d4a28b bump kittcad/lib version (#1565) 2024-02-29 11:57:47 +11:00
fb609c19ef Grackle: implement StartSketchAt stdlib function (#1535)
* Grackle: implement StartSketchAt stdlib function

* Move startsketchAt into a new 'sketch' module

* Further divide module

* Write SketchGroup to EP memory
2024-02-28 16:24:03 -06:00
104 changed files with 4038 additions and 2351 deletions

View File

@ -3,4 +3,3 @@ VITE_KC_API_BASE_URL=https://api.dev.zoo.dev
VITE_KC_SITE_BASE_URL=https://dev.zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=5000
VITE_KC_SENTRY_DSN=

View File

@ -3,4 +3,3 @@ VITE_KC_API_BASE_URL=https://api.zoo.dev
VITE_KC_SITE_BASE_URL=https://zoo.dev
VITE_KC_SKIP_AUTH=false
VITE_KC_CONNECTION_TIMEOUT_MS=15000
VITE_KC_SENTRY_DSN=

View File

@ -40,6 +40,20 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install vector
run: |
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
chmod +x /tmp/vector.sh
/tmp/vector.sh -y -no-modify-path
mkdir -p /tmp/vector
cp .github/workflows/vector.toml /tmp/vector.toml
sed -i "s#GITHUB_WORKFLOW#${GITHUB_WORKFLOW}#g" /tmp/vector.toml
sed -i "s#GITHUB_REPOSITORY#${GITHUB_REPOSITORY}#g" /tmp/vector.toml
sed -i "s#GITHUB_SHA#${GITHUB_SHA}#g" /tmp/vector.toml
sed -i "s#GITHUB_REF_NAME#${GITHUB_REF_NAME}#g" /tmp/vector.toml
sed -i "s#GH_ACTIONS_AXIOM_TOKEN#${{secrets.GH_ACTIONS_AXIOM_TOKEN}}#g" /tmp/vector.toml
cat /tmp/vector.toml
${HOME}/.vector/bin/vector --config /tmp/vector.toml &
- uses: taiki-e/install-action@cargo-llvm-cov
- uses: taiki-e/install-action@nextest
- name: Rust Cache
@ -48,7 +62,7 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo nextest run --workspace --no-fail-fast -P ci
cargo nextest run --workspace --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -336,7 +336,7 @@ jobs:
cat last_download.json
- name: Authenticate to Google Cloud
uses: 'google-github-actions/auth@v2.1.1'
uses: 'google-github-actions/auth@v2.1.2'
with:
credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}'

View File

@ -4,6 +4,11 @@ on:
branches: [ main ]
pull_request:
branches: [ main ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
playwright-ubuntu:
timeout-minutes: 60

21
.github/workflows/vector.toml vendored Normal file
View File

@ -0,0 +1,21 @@
[sources.github-actions-file]
type = "file"
data_dir = "/tmp/vector"
include = ["/tmp/github-actions.log"]
# Modify the logs to include the action name.
[transforms.add-action-name]
type = "remap"
inputs = [ "github-actions-file" ]
source = '''
.action = "GITHUB_WORKFLOW"
.repo = "GITHUB_REPOSITORY"
.sha = "GITHUB_SHA"
.ref = "GITHUB_REF_NAME"
'''
[sinks.axiom]
type = "axiom"
inputs = ["add-action-name"]
token = "GH_ACTIONS_AXIOM_TOKEN"
dataset = "github-actions"

3
.gitignore vendored
View File

@ -33,6 +33,7 @@ src/wasm-lib/bindings
src/wasm-lib/kcl/bindings
public/wasm_lib_bg.wasm
src/wasm-lib/lcov.info
src/wasm-lib/grackle/test_json_output
e2e/playwright/playwright-secrets.env
e2e/playwright/temp1.png
@ -54,3 +55,5 @@ e2e/playwright/export-snapshots/*embedded.gltf
## generated files
src/**/*.typegen.ts
src/wasm-lib/grackle/stdlib_cube_partial.json

View File

@ -8,10 +8,6 @@ once fixed in engine will just start working here with no language changes.
model for that sketch and its underlying 3D object.
If you see a red line around your model, it means this is happening.
- **Patterns**: If you try and pass a pattern to `hole` currently only the first
item in the pattern is being subtracted. This is an engine bug that is being
worked on.
- **Import**: Right now you can import a file, even if that file has brep data
you cannot edit it, after v1, the engine will account for this. You also cannot
currently move or transform the imported objects at all, once we have assemblies

File diff suppressed because it is too large Load Diff

View File

@ -20,6 +20,7 @@
* [`atan`](#atan)
* [`bezierCurve`](#bezierCurve)
* [`ceil`](#ceil)
* [`circle`](#circle)
* [`close`](#close)
* [`cos`](#cos)
* [`e`](#e)
@ -49,7 +50,6 @@
* [`segEndX`](#segEndX)
* [`segEndY`](#segEndY)
* [`segLen`](#segLen)
* [`show`](#show)
* [`sin`](#sin)
* [`sqrt`](#sqrt)
* [`startProfileAt`](#startProfileAt)
@ -3438,6 +3438,290 @@ ceil(num: number) -> number
### circle
Sketch a circle on the given plane
```
circle(plane: SketchData, center: [number, number], radius: number) -> SketchGroup
```
#### Arguments
* `plane`: `SketchData` - Data for start sketch on. You can start a sketch on a plane or an extrude group.
```
"XY" |
"-XY" |
"XZ" |
"-XZ" |
"YZ" |
"-YZ" |
{
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,
},
},
} |
{
// The id of the extrusion end cap
endCapId: uuid,
// The height of the extrude group.
height: number,
// The id of the extrude group.
id: uuid,
// The position of the extrude group.
position: [number, number, number],
// The rotation of the extrude group.
rotation: [number, number, number, number],
// The id of the extrusion start cap
startCapId: uuid,
// The extrude surfaces.
value: [{
// The face id for the extrude plane.
faceId: uuid,
// The id of the geometry.
id: uuid,
// The name.
name: string,
// The position.
position: [number, number, number],
// The rotation.
rotation: [number, number, number, number],
// The source range.
sourceRange: [number, number],
type: "extrudePlane",
} |
{
// The face id for the extrude plane.
faceId: uuid,
// The id of the geometry.
id: uuid,
// The name.
name: string,
// The position.
position: [number, number, number],
// The rotation.
rotation: [number, number, number, number],
// The source range.
sourceRange: [number, number],
type: "extrudeArc",
}],
// The x-axis of the extrude group base plane in the 3D space
xAxis: {
x: number,
y: number,
z: number,
},
// The y-axis of the extrude group base plane in the 3D space
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis of the extrude group base plane in the 3D space
zAxis: {
x: number,
y: number,
z: number,
},
}
```
* `center`: `[number, number]`
* `radius`: `number`
#### Returns
* `SketchGroup` - A sketch group is a collection of paths.
```
{
// The plane id or face id of the sketch group.
entityId: uuid,
// The id of the sketch group.
id: uuid,
// What the sketch is on (can be a plane or a face).
on: {
// The id of the plane.
id: uuid,
// Origin of the plane.
origin: {
x: number,
y: number,
z: number,
},
type: "plane",
// Type for a plane.
value: "XY" | "XZ" | "YZ" | "Custom",
// 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,
},
} |
{
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
sketchGroupId: uuid,
type: "face",
// The tag of the face.
value: string,
// What should the faces X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the faces Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
},
// The position of the sketch group.
position: [number, number, number],
// The rotation of the sketch group base plane.
rotation: [number, number, number, number],
// The starting path.
start: {
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
},
// The paths in the sketch group.
value: [{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "ToPoint",
} |
{
// arc's direction
ccw: string,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArcTo",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArc",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Horizontal",
// The x coordinate.
x: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "AngledLineTo",
// The x coordinate.
x: number,
// The y coordinate.
y: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Base",
}],
// The x-axis of the sketch group base plane in the 3D space
xAxis: {
x: number,
y: number,
z: number,
},
// The y-axis of the sketch group base plane in the 3D space
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis of the sketch group base plane in the 3D space
zAxis: {
x: number,
y: number,
z: number,
},
}
```
### close
Close the current sketch.
@ -4703,6 +4987,7 @@ hole(hole_sketch_group: SketchGroupSet, sketch_group: SketchGroup) -> SketchGrou
Import a CAD file.
For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters. Otherwise you can specify the unit by passing in the options parameter. If you import a gltf file, we will try to find the bin file and import it as well.
Import paths are relative to the current project directory. This only works in the desktop app not in browser.
```
import(file_path: String, options: ImportFormat) -> ImportedGeometry
@ -6086,8 +6371,8 @@ patternCircular(data: CircularPatternData, geometry: Geometry) -> Geometries
{
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
arcDegrees: number,
// The axis around which to make the pattern. This is a 3D vector.
axis: [number, number, number],
// The axis around which to make the pattern. This is a 2D vector.
axis: [number, number],
// The center about which to make th pattern. This is a 3D vector.
center: [number, number, number],
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
@ -6355,8 +6640,8 @@ patternLinear(data: LinearPatternData, geometry: Geometry) -> Geometries
* `data`: `LinearPatternData` - Data for a linear pattern.
```
{
// The axis of the pattern. This is a 3D vector.
axis: [number, number, number],
// The axis of the pattern. This is a 2D vector.
axis: [number, number],
// The distance between each repetition. This can also be referred to as spacing.
distance: number,
// The number of repetitions. Must be greater than 0. This excludes the original entity. For example, if `repetitions` is 1, the original entity will be copied once.
@ -7383,185 +7668,6 @@ segLen(segment_name: string, sketch_group: SketchGroup) -> number
### show
Render a model.
```
show(sketch: SketchGroup)
```
#### Arguments
* `sketch`: `SketchGroup` - A sketch group is a collection of paths.
```
{
// The plane id or face id of the sketch group.
entityId: uuid,
// The id of the sketch group.
id: uuid,
// What the sketch is on (can be a plane or a face).
on: {
// The id of the plane.
id: uuid,
// Origin of the plane.
origin: {
x: number,
y: number,
z: number,
},
type: "plane",
// Type for a plane.
value: "XY" | "XZ" | "YZ" | "Custom",
// 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,
},
} |
{
// The id of the face.
id: uuid,
// The original sketch group id of the object we are sketching on.
sketchGroupId: uuid,
type: "face",
// The tag of the face.
value: string,
// What should the faces X axis be?
xAxis: {
x: number,
y: number,
z: number,
},
// What should the faces Y axis be?
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis (normal).
zAxis: {
x: number,
y: number,
z: number,
},
},
// The position of the sketch group.
position: [number, number, number],
// The rotation of the sketch group base plane.
rotation: [number, number, number, number],
// The starting path.
start: {
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
},
// The paths in the sketch group.
value: [{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "ToPoint",
} |
{
// arc's direction
ccw: string,
// the arc's center
center: [number, number],
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArcTo",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "TangentialArc",
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Horizontal",
// The x coordinate.
x: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "AngledLineTo",
// The x coordinate.
x: number,
// The y coordinate.
y: number,
} |
{
// The from point.
from: [number, number],
// The name of the path.
name: string,
// The to point.
to: [number, number],
type: "Base",
}],
// The x-axis of the sketch group base plane in the 3D space
xAxis: {
x: number,
y: number,
z: number,
},
// The y-axis of the sketch group base plane in the 3D space
yAxis: {
x: number,
y: number,
z: number,
},
// The z-axis of the sketch group base plane in the 3D space
zAxis: {
x: number,
y: number,
z: number,
},
}
```
### sin
Computes the sine of a number (in radians).

View File

@ -3,6 +3,7 @@ import { secrets } from './secrets'
import { getUtils } from './test-utils'
import waitOn from 'wait-on'
import { Themes } from '../../src/lib/theme'
import { roundOff } from 'lib/utils'
/*
debug helper: unfortunately we do rely on exact coord mouse clicks in a few places
@ -15,9 +16,9 @@ document.addEventListener('mousemove', (e) =>
*/
const commonPoints = {
startAt: '[26.38, -35.59]',
num1: 26.63,
num2: 53.01,
startAt: '[9.06, -12.22]',
num1: 9.14,
num2: 18.2,
}
test.beforeEach(async ({ context, page }) => {
@ -65,10 +66,8 @@ test('Basic sketch', async ({ page }) => {
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// select a plane
await page.mouse.click(700, 200)
@ -90,7 +89,6 @@ test('Basic sketch', async ({ page }) => {
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num = 26.63
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
@ -284,10 +282,9 @@ test('Can create sketches on all planes and their back sides', async ({
}) => {
await u.openDebugPanel()
await u.updateCamPosition(viewCmd)
await u.clearCommandLogs()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await u.updateCamPosition(viewCmd)
await u.closeDebugPanel()
await page.mouse.click(clickCoords.x, clickCoords.y)
@ -315,7 +312,7 @@ test('Can create sketches on all planes and their back sides', async ({
const codeTemplate = (
plane = 'XY'
) => `const part001 = startSketchOn('${plane}')
|> startProfileAt([32.13, -43.34], %)`
|> startProfileAt([1.14, -1.54], %)`
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('XY'),
@ -325,7 +322,7 @@ test('Can create sketches on all planes and their back sides', async ({
await TestSinglePlane({
viewCmd: camPos,
expectedCode: codeTemplate('YZ'),
clickCoords: { x: 700, y: 300 }, // green plane
clickCoords: { x: 700, y: 250 }, // green plane
})
await TestSinglePlane({
viewCmd: camPos,
@ -386,12 +383,16 @@ test('Auto complete works', async ({ page }) => {
await page.keyboard.press('ArrowDown')
await page.keyboard.press('ArrowDown')
await page.keyboard.press('Enter')
await page.keyboard.type('(5, %)')
// finish line with comment
await page.keyboard.type('(5, %) // lin')
await page.waitForTimeout(100)
// there shouldn't be any auto complete options for 'lin' in the comment
await expect(page.locator('.cm-completionLabel')).not.toBeVisible()
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> xLine(5, %)`)
|> xLine(5, %) // lin`)
})
// Onboarding tests
@ -699,6 +700,8 @@ test('Can extrude from the command bar', async ({ page, context }) => {
).toBeDisabled()
await page.keyboard.press('Enter')
await expect(page.getByText('Confirm Extrude')).toBeVisible()
// Check that the code was updated
await page.keyboard.press('Enter')
// Unfortunately this indentation seems to matter for the test
@ -793,7 +796,7 @@ test('Can add multiple sketches', async ({ page }) => {
await u.clearAndCloseDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
const startAt2 = '[26.23, -35.39]'
const startAt2 = '[0.93,-1.25]'
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
@ -807,7 +810,7 @@ const part002 = startSketchOn('XY')
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
const num2 = 26.48
const num2 = 0.94
await expect(
(await page.locator('.cm-content').innerText()).replace(/\s/g, '')
).toBe(
@ -825,7 +828,7 @@ const part002 = startSketchOn('XY')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${num2}], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(num2 - 0.01)}], %)`.replace(/\s/g, '')
)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(
@ -835,8 +838,8 @@ const part002 = startSketchOn('XY')
const part002 = startSketchOn('XY')
|> startProfileAt(${startAt2}, %)
|> line([${num2}, 0], %)
|> line([0, ${num2}], %)
|> line([-52.71, 0], %)`.replace(/\s/g, '')
|> line([0, ${roundOff(num2 - 0.01)}], %)
|> line([-1.87, 0], %)`.replace(/\s/g, '')
)
})
@ -930,7 +933,7 @@ fn yohey = (pos) => {
|> line([-15.79, 17.08], %)
return ''
}
yohey([15.79, -34.6])
`
)
@ -1050,3 +1053,77 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await expect(page.locator('.cm-content')).not.toHaveText(previousCodeContent)
previousCodeContent = await page.locator('.cm-content').innerText()
})
test('Can edit segments by dragging their handles', async ({
page,
context,
}) => {
const u = getUtils(page)
await context.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -5.38], %)`
)
})
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
const startPX = [652, 418]
const lineEndPX = [794, 416]
const arcEndPX = [893, 318]
const dragPX = 30
await page.getByText('startProfileAt([4.61, -14.01], %)').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(100)
let prevContent = await page.locator('.cm-content').innerText()
const step5 = { steps: 5 }
// drag startProfieAt handle
await page.mouse.move(startPX[0], startPX[1])
await page.mouse.down()
await page.mouse.move(startPX[0] + dragPX, startPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag line handle
await page.mouse.move(lineEndPX[0] + dragPX, lineEndPX[1] - dragPX)
await page.mouse.down()
await page.mouse.move(
lineEndPX[0] + dragPX * 2,
lineEndPX[1] - dragPX * 2,
step5
)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
prevContent = await page.locator('.cm-content').innerText()
// drag tangentialArcTo handle
await page.mouse.move(arcEndPX[0], arcEndPX[1])
await page.mouse.down()
await page.mouse.move(arcEndPX[0] + dragPX, arcEndPX[1] - dragPX, step5)
await page.mouse.up()
await page.waitForTimeout(100)
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([7.01, -11.79], %)
|> line([14.69, 2.73], %)
|> tangentialArcTo([27.6, -3.25], %)`)
})

View File

@ -384,13 +384,13 @@ test('extrude on each default plane should be stable', async ({
}) => {
const u = getUtils(page)
const makeCode = (plane = 'XY') => `const part001 = startSketchOn('${plane}')
|> startProfileAt([0.70, 0.44], %)
|> line([0.66, -0.02], %)
|> line([0.28, 0.50], %)
|> line([-0.56, 0.44], %)
|> line([-0.54, -0.38], %)
|> startProfileAt([7.00, 4.40], %)
|> line([6.60, -0.20], %)
|> line([2.80, 5.00], %)
|> line([-5.60, 4.40], %)
|> line([-5.40, -3.80], %)
|> close(%)
|> extrude(1.00, %)
|> extrude(10.00, %)
`
await context.addInitScript(async (code) => {
localStorage.setItem('persistCode', code)
@ -435,7 +435,23 @@ test('extrude on each default plane should be stable', async ({
await runSnapshotsForOtherPlanes('-YZ')
})
test('Draft segments should look right', async ({ page }) => {
test('Draft segments should look right', async ({ page, context }) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'in',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'imperial',
})
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
@ -468,7 +484,7 @@ test('Draft segments should look right', async ({ page }) => {
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
@ -482,8 +498,8 @@ test('Draft segments should look right', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
@ -493,3 +509,202 @@ test('Draft segments should look right', async ({ page }) => {
maxDiffPixels: 100,
})
})
test('Client side scene scale should match engine scale inch', async ({
page,
context,
}) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'in',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'imperial',
})
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`)
// click tangential arc tool again to unequip it
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})
test('Client side scene scale should match engine scale mm', async ({
page,
context,
}) => {
await context.addInitScript(async () => {
localStorage.setItem(
'SETTINGS_PERSIST_KEY',
JSON.stringify({
baseUnit: 'mm',
cameraControls: 'KittyCAD',
defaultDirectory: '',
defaultProjectName: 'project-$nnn',
onboardingStatus: 'dismissed',
showDebugPanel: true,
textWrapping: 'On',
theme: 'system',
unitSystem: 'metric',
})
)
})
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
const PUR = 400 / 37.5 //pixeltoUnitRatio
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await expect(
page.getByRole('button', { name: 'Start Sketch' })
).not.toBeDisabled()
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeVisible()
// click on "Start Sketch" button
await u.clearCommandLogs()
await u.doAndWaitForImageDiff(
() => page.getByRole('button', { name: 'Start Sketch' }).click(),
200
)
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)`)
await page.waitForTimeout(100)
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> line([232.2, 0], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt([230.03, -310.33], %)
|> line([232.2, 0], %)
|> tangentialArcTo([694.43, -78.12], %)`)
await page.getByRole('button', { name: 'Tangential Arc' }).click()
await page.waitForTimeout(100)
// screen shot should show the sketch
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
// exit sketch
await u.openAndClearDebugPanel()
await page.getByRole('button', { name: 'Exit Sketch' }).click()
// wait for execution done
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.clearAndCloseDebugPanel()
await page.waitForTimeout(200)
// second screen shot should look almost identical, i.e. scale should be the same.
await expect(page).toHaveScreenshot({
maxDiffPixels: 100,
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.15.2",
"version": "0.15.4",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.10.2",
@ -10,12 +10,11 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.17",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "^0.0.53",
"@kittycad/lib": "^0.0.54",
"@lezer/javascript": "^1.4.9",
"@open-rpc/client-js": "^1.8.1",
"@react-hook/resize-observer": "^1.2.6",
"@replit/codemirror-interact": "^6.3.0",
"@sentry/react": "^7.77.0",
"@tauri-apps/api": "^1.5.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^14.0.0",

View File

@ -18,7 +18,7 @@ export default defineConfig({
/* Retry on CI only */
retries: process.env.CI ? 3 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : 1,
workers: process.env.CI ? 2 : 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */

22
src-tauri/Cargo.lock generated
View File

@ -67,9 +67,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.79"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
[[package]]
name = "app"
@ -1664,9 +1664,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.53"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a086e1a1bbddb3b38959c0f0ce6de6b3a3b7566e38e0b7d5fb101e91911beed4"
checksum = "049c3881ffbe77bf1c3a968372a246ce906eceb79f61cd0bc5fa229bec3504cb"
dependencies = [
"anyhow",
"async-trait",
@ -3235,9 +3235,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2"
dependencies = [
"serde_derive",
]
@ -3253,9 +3253,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.196"
version = "1.0.197"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
@ -3275,9 +3275,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.113"
version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0"
dependencies = [
"itoa 1.0.6",
"ryu",
@ -3872,7 +3872,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#01211ff0759d578e0e9ac8c98c31fdf09077eb34"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#ed682dd96eb765e7cd3cdbc3cc64f794a0d6f9df"
dependencies = [
"log",
"serde",

View File

@ -16,7 +16,7 @@ tauri-build = { version = "1.5.1", features = [] }
[dependencies]
anyhow = "1"
kittycad = "0.2.53"
kittycad = "0.2.58"
oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

View File

@ -7,7 +7,7 @@
},
"package": {
"productName": "zoo-modeling-app",
"version": "0.15.2"
"version": "0.15.4"
},
"tauri": {
"allowlist": {

View File

@ -3,15 +3,8 @@ import {
createBrowserRouter,
Outlet,
redirect,
useLocation,
RouterProvider,
} from 'react-router-dom'
import {
matchRoutes,
createRoutesFromChildren,
useNavigationType,
} from 'react-router'
import { useEffect } from 'react'
import { ErrorPage } from './components/ErrorPage'
import { Settings } from './routes/Settings'
import Onboarding, { onboardingRoutes } from './routes/Onboarding'
@ -38,8 +31,6 @@ import { ContextFrom } from 'xstate'
import CommandBarProvider, {
CommandBar,
} from 'components/CommandBar/CommandBar'
import { TEST, VITE_KC_SENTRY_DSN } from './env'
import * as Sentry from '@sentry/react'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import { KclContextProvider, kclManager } from 'lang/KclSingleton'
import FileMachineProvider from 'components/FileMachineProvider'
@ -48,38 +39,6 @@ import { paths } from 'lib/paths'
import { IndexLoaderData, HomeLoaderData } from 'lib/types'
import { fileSystemManager } from 'lang/std/fileSystemManager'
if (VITE_KC_SENTRY_DSN && !TEST) {
Sentry.init({
dsn: VITE_KC_SENTRY_DSN,
// TODO(paultag): pass in the right env here.
// environment: "production",
integrations: [
new Sentry.BrowserTracing({
routingInstrumentation: Sentry.reactRouterV6Instrumentation(
useEffect,
useLocation,
useNavigationType,
createRoutesFromChildren,
matchRoutes
),
}),
new Sentry.Replay(),
],
// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
tracesSampleRate: 1.0,
// TODO: Add in kittycad.io endpoints
tracePropagationTargets: ['localhost'],
// Capture Replay for 10% of all sessions,
// plus for 100% of sessions with an error
replaysSessionSampleRate: 0.1,
replaysOnErrorSampleRate: 1.0,
})
}
export const BROWSER_FILE_NAME = 'new'
type CreateBrowserRouterArg = Parameters<typeof createBrowserRouter>[0]

View File

@ -16,7 +16,11 @@ import {
SKETCH_LAYER,
ZOOM_MAGIC_NUMBER,
} from './sceneInfra'
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
import {
EngineCommand,
Subscription,
engineCommandManager,
} from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { deg2Rad } from 'lib/utils2d'
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
@ -28,6 +32,12 @@ const FRAMES_TO_ANIMATE_IN = 30
const tempQuaternion = new Quaternion() // just used for maths
type interactionType = 'pan' | 'rotate' | 'zoom'
const throttledEngCmd = throttle((cmd: EngineCommand) => {
engineCommandManager.sendSceneCommand(cmd)
}, 1000 / 15)
interface ThreeCamValues {
position: Vector3
quaternion: Quaternion
@ -110,10 +120,11 @@ const throttledUpdateEngineFov = throttle(
lastCmdDelay
) as any as number
},
1000 / 15
1000 / 30
)
export class CameraControls {
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
camera: PerspectiveCamera | OrthographicCamera
target: Vector3
domElement: HTMLCanvasElement
@ -151,6 +162,17 @@ export class CameraControls {
get isPerspective() {
return this.camera instanceof PerspectiveCamera
}
private debounceTimer = 0
handleStart = () => {
if (this.debounceTimer) clearTimeout(this.debounceTimer)
this._isCamMovingCallback(true, false)
}
handleEnd = () => {
this.debounceTimer = setTimeout(() => {
this._isCamMovingCallback(false, false)
}, 400) as any as number
}
// reacts hooks into some of this singleton's properties
reactCameraProperties: ReactCameraProperties = {
@ -187,6 +209,7 @@ export class CameraControls {
this.camera.zoom = camProps.zoom || 1
}
this.camera.updateProjectionMatrix()
console.log('doing this thing', camProps)
this.update(true)
}
@ -209,6 +232,46 @@ export class CameraControls {
this.onWindowResize()
this.update()
this._usePerspectiveCamera()
const cb: Subscription<
'default_camera_zoom' | 'camera_drag_end' | 'default_camera_get_settings'
>['callback'] = ({ data, type }) => {
const camSettings = data.settings
this.camera.position.set(
camSettings.pos.x,
camSettings.pos.y,
camSettings.pos.z
)
this.target.set(
camSettings.center.x,
camSettings.center.y,
camSettings.center.z
)
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
this.camera.fov = camSettings.fov_y
} else if (
this.camera instanceof OrthographicCamera &&
camSettings.ortho_scale
) {
this.camera.zoom = camSettings.ortho_scale
}
this.onCameraChange()
}
setTimeout(() => {
engineCommandManager.subscribeTo({
event: 'camera_drag_end',
callback: cb,
})
engineCommandManager.subscribeTo({
event: 'default_camera_zoom',
callback: cb,
})
engineCommandManager.subscribeTo({
event: 'default_camera_get_settings',
callback: cb,
})
})
}
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
@ -242,6 +305,21 @@ export class CameraControls {
onMouseDown = (event: MouseEvent) => {
this.isDragging = true
this.mouseDownPosition.set(event.clientX, event.clientY)
let interaction = this.getInteractionType(event)
if (interaction === 'none') return
this.handleStart()
if (this.syncDirection === 'engineToClient') {
void engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_start',
interaction,
window: { x: event.clientX, y: event.clientY },
},
cmd_id: uuidv4(),
})
}
}
onMouseMove = (event: MouseEvent) => {
@ -252,36 +330,34 @@ export class CameraControls {
.sub(this.mouseDownPosition)
this.mouseDownPosition.copy(this.mouseNewPosition)
let state: 'pan' | 'rotate' | 'zoom' = 'pan'
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
if (this.interactionGuards.pan.callback(event as any)) {
if (this.enablePan === false) return
// handleMouseDownPan(event)
state = 'pan'
} else if (this.interactionGuards.rotate.callback(event as any)) {
if (this.enableRotate === false) return
// handleMouseDownRotate(event)
state = 'rotate'
} else if (this.interactionGuards.zoom.dragCallback(event as any)) {
if (this.enableZoom === false) return
// handleMouseDownDolly(event)
state = 'zoom'
} else {
if (this.syncDirection === 'engineToClient') {
throttledEngCmd({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction,
window: { x: event.clientX, y: event.clientY },
},
cmd_id: uuidv4(),
})
return
}
// Implement camera movement logic here based on deltaMove
// For example, for rotating the camera around the target:
if (state === 'rotate') {
if (interaction === 'rotate') {
this.pendingRotation = this.pendingRotation
? this.pendingRotation
: new Vector2()
this.pendingRotation.x += deltaMove.x
this.pendingRotation.y += deltaMove.y
} else if (state === 'zoom') {
} else if (interaction === 'zoom') {
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
this.pendingZoom *= 1 + deltaMove.y * 0.01
} else if (state === 'pan') {
} else if (interaction === 'pan') {
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
let distance = this.camera.position.distanceTo(this.target)
if (this.camera instanceof OrthographicCamera) {
@ -297,15 +373,52 @@ export class CameraControls {
onMouseUp = (event: MouseEvent) => {
this.isDragging = false
this.handleEnd()
if (this.syncDirection === 'engineToClient') {
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
void engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_end',
interaction,
window: { x: event.clientX, y: event.clientY },
},
cmd_id: uuidv4(),
})
}
}
onMouseWheel = (event: WheelEvent) => {
// Assume trackpad if the deltas are small and integers
this.handleStart()
if (this.syncDirection === 'engineToClient') {
const interactions = this.interactionGuards.zoom.scrollCallback(
event as any
)
if (!interactions) {
this.handleEnd()
return
}
throttledEngCmd({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude: -event.deltaY * 0.4,
},
cmd_id: uuidv4(),
})
this.handleEnd()
return
}
const isTrackpad = Math.abs(event.deltaY) <= 1 || event.deltaY % 1 === 0
const zoomSpeed = isTrackpad ? 0.02 : 0.1 // Reduced zoom speed for trackpad
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
this.pendingZoom *= 1 + (event.deltaY > 0 ? zoomSpeed : -zoomSpeed)
this.handleEnd()
}
useOrthographicCamera = () => {
@ -358,7 +471,7 @@ export class CameraControls {
return this.camera
}
usePerspectiveCamera = () => {
_usePerspectiveCamera = () => {
const { x: px, y: py, z: pz } = this.camera.position
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
const zoom = this.camera.zoom
@ -374,14 +487,17 @@ export class CameraControls {
)
direction.normalize()
this.camera.position.copy(this.target).addScaledVector(direction, distance)
}
usePerspectiveCamera = () => {
this._usePerspectiveCamera()
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_perspective',
parameters: {
fov_y: this.camera.fov,
fov_y:
this.camera instanceof PerspectiveCamera ? this.camera.fov : 45,
...calculateNearFarFromFOV(this.lastPerspectiveFov),
},
},
@ -454,7 +570,7 @@ export class CameraControls {
update = (forceUpdate = false) => {
// If there are any changes that need to be applied to the camera, apply them here.
let didChange = forceUpdate
let didChange = false
if (this.pendingRotation) {
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
this.pendingRotation = null // Clear the pending rotation after applying it
@ -506,8 +622,8 @@ export class CameraControls {
// Update the camera's matrices
this.camera.updateMatrixWorld()
if (didChange) {
this.onCameraChange()
if (didChange || forceUpdate) {
this.onCameraChange(forceUpdate)
}
// damping would be implemented here in update if we choose to add it.
@ -618,6 +734,10 @@ export class CameraControls {
duration = 500,
toOrthographic = true
): Promise<void> {
if (this.syncDirection === 'engineToClient')
console.warn(
'tweenCameraToQuaternion not design to work with engineToClient syncDirection.'
)
const isVertical = isQuaternionVertical(targetQuaternion)
let remainingDuration = duration
if (isVertical) {
@ -700,6 +820,10 @@ export class CameraControls {
animateToOrthographic = () =>
new Promise((resolve) => {
if (this.syncDirection === 'engineToClient')
console.warn(
'animate To Orthographic not design to work with engineToClient syncDirection.'
)
this.isFovAnimationInProgress = true
let currentFov = this.lastPerspectiveFov
this.fovBeforeOrtho = currentFov
@ -733,6 +857,10 @@ export class CameraControls {
})
animateToPerspective = () =>
new Promise((resolve) => {
if (this.syncDirection === 'engineToClient')
console.warn(
'animate To Perspective not design to work with engineToClient syncDirection.'
)
this.isFovAnimationInProgress = true
// Immediately set the camera to perspective with a very low FOV
const targetFov = this.fovBeforeOrtho // Target FOV for perspective
@ -771,7 +899,7 @@ export class CameraControls {
this.reactCameraPropertiesCallback(a)
}, 200)
onCameraChange = () => {
onCameraChange = (forceUpdate = false) => {
const distance = this.target.distanceTo(this.camera.position)
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
this.camera.far = distance * 2
@ -779,13 +907,14 @@ export class CameraControls {
this.camera.updateProjectionMatrix()
}
throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
zoom: this.camera.zoom,
isPerspective: this.isPerspective,
target: this.target,
})
if (this.syncDirection === 'clientToEngine' || forceUpdate)
throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
zoom: this.camera.zoom,
isPerspective: this.isPerspective,
target: this.target,
})
this.deferReactUpdate({
type: this.isPerspective ? 'perspective' : 'orthographic',
[this.isPerspective ? 'fov' : 'zoom']:
@ -806,9 +935,18 @@ export class CameraControls {
})
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
}
getInteractionType = (event: any) =>
_getInteractionType(
this.interactionGuards,
event,
this.enablePan,
this.enableRotate,
this.enableZoom
)
}
// currently duplicated, delete one
// Pure function helpers
function calculateNearFarFromFOV(fov: number) {
const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
@ -816,7 +954,6 @@ function calculateNearFarFromFOV(fov: number) {
return { z_near: 0.1, z_far }
}
// currently duplicated, delete one
function convertThreeCamValuesToEngineCam({
target,
position,
@ -857,8 +994,6 @@ function convertThreeCamValuesToEngineCam({
}
}
// Pure function helpers
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
// Direction from position to target, normalized.
let direction = new Vector3().subVectors(target, position).normalize()
@ -877,3 +1012,17 @@ function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
return quaternion
}
function _getInteractionType(
interactionGuards: MouseGuard,
event: any,
enablePan: boolean,
enableRotate: boolean,
enableZoom: boolean
): interactionType | 'none' {
let state: interactionType | 'none' = 'none'
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
if (enableRotate && interactionGuards.rotate.callback(event)) return 'rotate'
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
return state
}

View File

@ -56,6 +56,7 @@ import { engineCommandManager } from 'lang/std/engineConnection'
import {
createArcGeometry,
dashedStraight,
profileStart,
straightSegment,
tangentialArcToSegment,
} from './segments'
@ -64,6 +65,7 @@ import {
addNewSketchLn,
changeSketchArguments,
compareVec2Epsilon2,
updateStartProfileAtArgs,
} from 'lang/std/sketch'
import { isReducedMotion, throttle } from 'lib/utils'
import {
@ -85,6 +87,7 @@ export const TANGENTIAL_ARC_TO_SEGMENT = 'tangential-arc-to-segment'
export const TANGENTIAL_ARC_TO_SEGMENT_BODY = 'tangential-arc-to-segment-body'
export const TANGENTIAL_ARC_TO__SEGMENT_DASH =
'tangential-arc-to-segment-body-dashed'
export const PROFILE_START = 'profile-start'
// This singleton Class is responsible for all of the things the user sees and interacts with.
// That mostly mean sketch elements.
@ -106,9 +109,10 @@ class SceneEntities {
Object.values(this.activeSegments).forEach((segment) => {
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, segment)
: perspScale(sceneInfra.camControls.camera, segment)) /
sceneInfra._baseUnitMultiplier
if (
segment.userData.from &&
segment.userData.to &&
@ -136,6 +140,9 @@ class SceneEntities {
scale: factor,
})
}
if (segment.name === PROFILE_START) {
segment.scale.set(factor, factor, factor)
}
})
if (this.axisGroup) {
const factor =
@ -143,9 +150,9 @@ class SceneEntities {
? orthoFactor
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
const x = this.axisGroup.getObjectByName(X_AXIS)
x?.scale.set(1, factor, 1)
x?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
const y = this.axisGroup.getObjectByName(Y_AXIS)
y?.scale.set(factor, 1, 1)
y?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
}
}
@ -169,6 +176,7 @@ class SceneEntities {
this.scene.add(this.intersectionPlane)
}
createSketchAxis(sketchPathToNode: PathToNode) {
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const baseXColor = 0x000055
const baseYColor = 0x550000
const xAxisGeometry = new BoxGeometry(100000, 0.3, 0.01)
@ -208,6 +216,14 @@ class SceneEntities {
sceneInfra.camControls.target
)
gridHelper.scale.set(sceneScale, sceneScale, sceneScale)
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, this.axisGroup)
xAxisMesh?.scale.set(1, factor / sceneInfra._baseUnitMultiplier, 1)
yAxisMesh?.scale.set(factor / sceneInfra._baseUnitMultiplier, 1, 1)
this.axisGroup.add(xAxisMesh, yAxisMesh, gridHelper)
this.currentSketchQuaternion &&
this.axisGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
@ -279,9 +295,28 @@ class SceneEntities {
)
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, dummy)
: perspScale(sceneInfra.camControls.camera, dummy)) /
sceneInfra._baseUnitMultiplier
let segPathToNode = getNodePathFromSourceRange(
draftSegment ? truncatedAst : kclManager.ast,
sketchGroup.start.__geoMeta.sourceRange
)
const _profileStart = profileStart({
from: sketchGroup.start.from,
id: sketchGroup.start.__geoMeta.id,
pathToNode: segPathToNode,
scale: factor,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
child.layers.set(SKETCH_LAYER)
})
group.add(_profileStart)
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
sketchGroup.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
draftSegment ? truncatedAst : kclManager.ast,
@ -329,17 +364,18 @@ class SceneEntities {
this.scene.add(group)
if (!draftSegment) {
sceneInfra.setCallbacks({
onDrag: (args) => {
if (args.event.which !== 1) return
onDrag: ({ selected, intersectionPoint, mouseEvent }) => {
if (mouseEvent.which !== 1) return
this.onDragSegment({
...args,
object: selected,
intersection2d: intersectionPoint.twoD,
sketchPathToNode,
})
},
onMove: () => {},
onClick: (args) => {
if (args?.event.which !== 1) return
if (!args || !args.object) {
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
type: 'Set selection',
data: {
@ -348,22 +384,26 @@ class SceneEntities {
})
return
}
const { object } = args
const event = getEventForSegmentSelection(object)
const { selected } = args
const event = getEventForSegmentSelection(selected)
if (!event) return
sceneInfra.modelingSend(event)
},
onMouseEnter: ({ object }) => {
onMouseEnter: ({ selected }) => {
// TODO change the color of the segment to yellow?
// Give a few pixels grace around each of the segments
// for hover.
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
const obj = object as Mesh
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
mat.color.offsetHSL(0, 0, 0.5)
}
const parent = getParentGroup(object)
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
const node = getNodeFromPath<CallExpression>(
@ -373,18 +413,22 @@ class SceneEntities {
).node
sceneInfra.highlightCallback([node.start, node.end])
const yellow = 0xffff00
colorSegment(object, yellow)
colorSegment(selected, yellow)
return
}
sceneInfra.highlightCallback([0, 0])
},
onMouseLeave: ({ object }) => {
onMouseLeave: ({ selected }) => {
sceneInfra.highlightCallback([0, 0])
const parent = getParentGroup(object)
const parent = getParentGroup(selected, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const isSelected = parent?.userData?.isSelected
colorSegment(object, isSelected ? 0x0000ff : 0xffffff)
if ([X_AXIS, Y_AXIS].includes(object?.userData?.type)) {
const obj = object as Mesh
colorSegment(selected, isSelected ? 0x0000ff : 0xffffff)
if ([X_AXIS, Y_AXIS].includes(selected?.userData?.type)) {
const obj = selected as Mesh
const mat = obj.material as MeshBasicMaterial
mat.color.set(obj.userData.baseColor)
if (obj.userData.isSelected) mat.color.offsetHSL(0, 0, 0.2)
@ -396,15 +440,15 @@ class SceneEntities {
onDrag: () => {},
onClick: async (args) => {
if (!args) return
if (args.event.which !== 1) return
const { intersection2d } = args
if (!intersection2d) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
if (!intersectionPoint?.twoD) return
const firstSeg = sketchGroup.value[0]
const isClosingSketch = compareVec2Epsilon2(
firstSeg.from,
[intersection2d.x, intersection2d.y],
1
[intersectionPoint.twoD.x, intersectionPoint.twoD.y],
0.5
)
let modifiedAst
if (isClosingSketch) {
@ -419,7 +463,7 @@ class SceneEntities {
modifiedAst = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
to: [intersection2d.x, intersection2d.y],
to: [intersectionPoint.twoD.x, intersectionPoint.twoD.y],
from: [lastSegment.to[0], lastSegment.to[1]],
fnName:
lastSegment.type === 'TangentialArcTo'
@ -435,7 +479,7 @@ class SceneEntities {
},
onMove: (args) => {
this.onDragSegment({
...args,
intersection2d: args.intersectionPoint.twoD,
object: Object.values(this.activeSegments).slice(-1)[0],
sketchPathToNode,
draftInfo: {
@ -482,15 +526,11 @@ class SceneEntities {
)
onDragSegment({
object,
event,
intersectPoint,
intersection2d,
sketchPathToNode,
draftInfo,
}: {
object: any
event: any
intersectPoint: Vector3
intersection2d: Vector2
sketchPathToNode: PathToNode
draftInfo?: {
@ -500,7 +540,11 @@ class SceneEntities {
variableDeclarationName: string
}
}) {
const group = getParentGroup(object)
const group = getParentGroup(object, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
if (!group) return
const pathToNode: PathToNode = JSON.parse(
JSON.stringify(group.userData.pathToNode)
@ -524,13 +568,28 @@ class SceneEntities {
).node
if (node.type !== 'CallExpression') return
const modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
from
)
let modded: {
modifiedAst: Program
pathToNode: PathToNode
}
if (group.name === PROFILE_START) {
modded = updateStartProfileAtArgs({
node: modifiedAst,
pathToNode,
to,
from,
previousProgramMemory: kclManager.programMemory,
})
} else {
modded = changeSketchArguments(
modifiedAst,
kclManager.programMemory,
[node.start, node.end],
to,
from
)
}
modifiedAst = modded.modifiedAst
const { truncatedAst, programMemoryOverride, variableDeclarationName } =
draftInfo
@ -549,10 +608,16 @@ class SceneEntities {
programMemoryOverride,
})
this.sceneProgramMemory = programMemory
const sketchGroup = programMemory.root[variableDeclarationName]
.value as Path[]
const sketchGroup = programMemory.root[
variableDeclarationName
] as SketchGroup
const sgPaths = sketchGroup.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
sketchGroup.forEach((segment, index) => {
const updateSegment = (
segment: Path | SketchGroup['start'],
index: number
) => {
const segPathToNode = getNodePathFromSourceRange(
modifiedAst,
segment.__geoMeta.sourceRange
@ -567,12 +632,13 @@ class SceneEntities {
// const prevSegment = sketchGroup.slice(index - 1)[0]
const type = group?.userData?.type
const factor =
sceneInfra.camControls.camera instanceof OrthographicCamera
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, group)
: perspScale(sceneInfra.camControls.camera, group)) /
sceneInfra._baseUnitMultiplier
if (type === TANGENTIAL_ARC_TO_SEGMENT) {
this.updateTangentialArcToSegment({
prevSegment: sketchGroup[index - 1],
prevSegment: sgPaths[index - 1],
from: segment.from,
to: segment.to,
group: group,
@ -585,8 +651,13 @@ class SceneEntities {
group: group,
scale: factor,
})
} else if (type === PROFILE_START) {
group.position.set(segment.from[0], segment.from[1], 0)
group.scale.set(factor, factor, factor)
}
})
}
updateSegment(sketchGroup.start, 0)
sgPaths.forEach(updateSegment)
})()
}
@ -606,9 +677,7 @@ class SceneEntities {
group.userData.from = from
group.userData.to = to
group.userData.prevSegment = prevSegment
const arrowGroup = group.children.find(
(child) => child.userData.type === ARROWHEAD
) as Group
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
arrowGroup.position.set(to[0], to[1], 0)
@ -683,9 +752,7 @@ class SceneEntities {
const shape = new Shape()
shape.moveTo(0, -0.08 * scale)
shape.lineTo(0, 0.08 * scale) // The width of the line
const arrowGroup = group.children.find(
(child) => child.userData.type === ARROWHEAD
) as Group
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
arrowGroup.position.set(to[0], to[1], 0)
@ -781,22 +848,24 @@ class SceneEntities {
}
setupDefaultPlaneHover() {
sceneInfra.setCallbacks({
onMouseEnter: ({ object }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = object.userData.type
object.material.color = defaultPlaneColor(type, 0.5, 1)
onMouseEnter: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type, 0.5, 1)
},
onMouseLeave: ({ object }) => {
if (object.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = object.userData.type
object.material.color = defaultPlaneColor(type)
onMouseLeave: ({ selected }) => {
if (!(selected instanceof Mesh && selected.parent)) return
if (selected.parent.userData.type !== DEFAULT_PLANES) return
const type: DefaultPlane = selected.userData.type
selected.material.color = defaultPlaneColor(type)
},
onClick: (args) => {
if (!args || !args.object) return
if (args.event.which !== 1) return
const { intersection } = args
const type = intersection.object.name || ''
const posNorm = Number(intersection.normal?.z) > 0
if (!args || !args.intersects?.[0]) return
if (args.mouseEvent.which !== 1) return
const { intersects } = args
const type = intersects?.[0].object.name || ''
const posNorm = Number(intersects?.[0]?.normal?.z) > 0
let planeString: DefaultPlaneStr = posNorm ? 'XY' : '-XY'
let normal: [number, number, number] = posNorm ? [0, 0, 1] : [0, 0, -1]
if (type === YZ_PLANE) {
@ -968,9 +1037,9 @@ export function quaternionFromSketchGroup(
}
function colorSegment(object: any, color: number) {
const arrowHead = getParentGroup(object, [ARROWHEAD])
if (arrowHead) {
arrowHead.traverse((child) => {
const segmentHead = getParentGroup(object, [ARROWHEAD, PROFILE_START])
if (segmentHead) {
segmentHead.traverse((child) => {
if (child instanceof Mesh) {
child.material.color.set(color)
}

View File

@ -18,7 +18,6 @@ import {
Intersection,
Object3D,
Object3DEventMap,
BoxGeometry,
} from 'three'
import { compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext'
@ -49,31 +48,36 @@ export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
interface BaseCallbackArgs2 {
object: any
event: any
}
interface BaseCallbackArgs {
event: any
}
interface OnDragCallbackArgs extends BaseCallbackArgs {
object: any
intersection2d: Vector2
intersectPoint: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
}
interface OnClickCallbackArgs extends BaseCallbackArgs {
intersection2d?: Vector2
intersectPoint: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
object?: any
interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap>
mouseEvent: MouseEvent
}
interface onMoveCallbackArgs {
event: any
intersection2d: Vector2
intersectPoint: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
}
interface OnClickCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint?: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
interface OnMoveCallbackArgs {
mouseEvent: MouseEvent
intersectionPoint: {
twoD: Vector2
threeD: Vector3
}
intersects: Intersection<Object3D<Object3DEventMap>>[]
selected?: Object3D<Object3DEventMap>
}
// This singleton class is responsible for all of the under the hood setup for the client side scene.
@ -88,17 +92,19 @@ class SceneInfra {
fov = 45
fovBeforeAnimate = 45
isFovAnimationInProgress = false
_baseUnit: BaseUnit = 'mm'
_baseUnitMultiplier = 1
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {}
onMoveCallback: (arg: OnMoveCallbackArgs) => void = () => {}
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
onMouseEnter: (arg: BaseCallbackArgs2) => void = () => {}
onMouseLeave: (arg: BaseCallbackArgs2) => void = () => {}
onMouseEnter: (arg: OnMouseEnterLeaveArgs) => void = () => {}
onMouseLeave: (arg: OnMouseEnterLeaveArgs) => void = () => {}
setCallbacks = (callbacks: {
onDrag?: (arg: OnDragCallbackArgs) => void
onMove?: (arg: onMoveCallbackArgs) => void
onMove?: (arg: OnMoveCallbackArgs) => void
onClick?: (arg?: OnClickCallbackArgs) => void
onMouseEnter?: (arg: BaseCallbackArgs2) => void
onMouseLeave?: (arg: BaseCallbackArgs2) => void
onMouseEnter?: (arg: OnMouseEnterLeaveArgs) => void
onMouseLeave?: (arg: OnMouseEnterLeaveArgs) => void
}) => {
this.onDragCallback = callbacks.onDrag || this.onDragCallback
this.onMoveCallback = callbacks.onMove || this.onMoveCallback
@ -107,6 +113,15 @@ class SceneInfra {
this.onMouseLeave = callbacks.onMouseLeave || this.onMouseLeave
this.selected = null // following selections between callbacks being set is too tricky
}
set baseUnit(unit: BaseUnit) {
this._baseUnit = unit
this._baseUnitMultiplier = baseUnitTomm(unit)
this.scene.scale.set(
this._baseUnitMultiplier,
this._baseUnitMultiplier,
this._baseUnitMultiplier
)
}
resetMouseListeners = () => {
sceneInfra.setCallbacks({
onDrag: () => {},
@ -132,10 +147,9 @@ class SceneInfra {
currentMouseVector = new Vector2()
selected: {
mouseDownVector: Vector2
object: any
object: Object3D<Object3DEventMap>
hasBeenDragged: boolean
} | null = null
selectedObject: null | any = null
mouseDownVector: null | Vector2 = null
constructor() {
@ -202,7 +216,12 @@ class SceneInfra {
const axisGroup = this.scene
.getObjectByName(AXIS_GROUP)
?.getObjectByName('gridHelper')
planesGroup && planesGroup.scale.set(scale, scale, scale)
planesGroup &&
planesGroup.scale.set(
scale / sceneInfra._baseUnitMultiplier,
scale / sceneInfra._baseUnitMultiplier,
scale / sceneInfra._baseUnitMultiplier
)
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
}
@ -227,8 +246,8 @@ class SceneInfra {
// Dispose of any other resources like geometries, materials, textures
}
getPlaneIntersectPoint = (): {
intersection2d?: Vector2
intersectPoint: Vector3
twoD?: Vector2
threeD?: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
} | null => {
this.planeRaycaster.setFromCamera(
@ -239,23 +258,11 @@ class SceneInfra {
this.scene.children,
true
)
if (
planeIntersects.length > 0 &&
planeIntersects[0].object.userData.type !== RAYCASTABLE_PLANE
) {
const intersect = planeIntersects[0]
return {
intersectPoint: intersect.point,
intersection: intersect,
}
}
if (
!(
planeIntersects.length > 0 &&
planeIntersects[0].object.userData.type === RAYCASTABLE_PLANE
)
const recastablePlaneIntersect = planeIntersects.find(
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
)
return null
if (!planeIntersects.length) return null
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
const planePosition = planeIntersects[0].object.position
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
.clone()
@ -270,16 +277,21 @@ class SceneInfra {
}
return {
intersection2d: new Vector2(transformedPoint.x, transformedPoint.y), // z should be 0
intersectPoint,
twoD: new Vector2(
transformedPoint.x / this._baseUnitMultiplier,
transformedPoint.y / this._baseUnitMultiplier
), // z should be 0
threeD: intersectPoint.divideScalar(this._baseUnitMultiplier),
intersection: planeIntersects[0],
}
}
onMouseMove = (event: MouseEvent) => {
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
onMouseMove = (mouseEvent: MouseEvent) => {
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y =
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersects = this.raycastRing()
if (this.selected) {
const hasBeenDragged = !compareVec2Epsilon2(
@ -295,47 +307,56 @@ class SceneInfra {
if (
hasBeenDragged &&
planeIntersectPoint &&
planeIntersectPoint.intersection2d
planeIntersectPoint.twoD &&
planeIntersectPoint.threeD
) {
// // console.log('onDrag', this.selected)
this.onDragCallback({
object: this.selected.object,
event,
intersection2d: planeIntersectPoint.intersection2d,
...planeIntersectPoint,
mouseEvent,
intersectionPoint: {
twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
selected: this.selected.object,
})
}
} else if (planeIntersectPoint && planeIntersectPoint.intersection2d) {
} else if (
planeIntersectPoint &&
planeIntersectPoint.twoD &&
planeIntersectPoint.threeD
) {
this.onMoveCallback({
event,
intersection2d: planeIntersectPoint.intersection2d,
...planeIntersectPoint,
mouseEvent,
intersectionPoint: {
twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
})
}
const intersect = this.raycastRing()
if (intersect) {
const firstIntersectObject = intersect.object
if (intersects[0]) {
const firstIntersectObject = intersects[0].object
if (this.hoveredObject !== firstIntersectObject) {
if (this.hoveredObject) {
this.onMouseLeave({
object: this.hoveredObject,
event,
selected: this.hoveredObject,
mouseEvent: mouseEvent,
})
}
this.hoveredObject = firstIntersectObject
this.onMouseEnter({
object: this.hoveredObject,
event,
selected: this.hoveredObject,
mouseEvent: mouseEvent,
})
}
} else {
if (this.hoveredObject) {
this.onMouseLeave({
object: this.hoveredObject,
event,
selected: this.hoveredObject,
mouseEvent: mouseEvent,
})
this.hoveredObject = null
}
@ -345,41 +366,38 @@ class SceneInfra {
raycastRing = (
pixelRadius = 8,
rayRingCount = 32
): Intersection<Object3D<Object3DEventMap>> | undefined => {
): Intersection<Object3D<Object3DEventMap>>[] => {
const mouseDownVector = this.currentMouseVector.clone()
let closestIntersection:
| Intersection<Object3D<Object3DEventMap>>
| undefined = undefined
let closestDistance = Infinity
const intersectionsMap = new Map<
Object3D,
Intersection<Object3D<Object3DEventMap>>
>()
const updateClosestIntersection = (
const updateIntersectionsMap = (
intersections: Intersection<Object3D<Object3DEventMap>>[]
) => {
let intersection = null
for (let i = 0; i < intersections.length; i++) {
if (intersections[i].object.type !== 'GridHelper') {
intersection = intersections[i]
break
intersections.forEach((intersection) => {
if (intersection.object.type !== 'GridHelper') {
const existingIntersection = intersectionsMap.get(intersection.object)
if (
!existingIntersection ||
existingIntersection.distance > intersection.distance
) {
intersectionsMap.set(intersection.object, intersection)
}
}
}
if (!intersection) return
if (intersection.distance < closestDistance) {
closestDistance = intersection.distance
closestIntersection = intersection
}
})
}
// Check the center point
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
updateClosestIntersection(
updateIntersectionsMap(
this.raycaster.intersectObjects(this.scene.children, true)
)
// Check the ring points
for (let i = 0; i < rayRingCount; i++) {
const angle = (i / rayRingCount) * Math.PI * 2
const offsetX = ((pixelRadius * Math.cos(angle)) / window.innerWidth) * 2
const offsetY = ((pixelRadius * Math.sin(angle)) / window.innerHeight) * 2
const ringVector = new Vector2(
@ -387,11 +405,15 @@ class SceneInfra {
mouseDownVector.y - offsetY
)
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
updateClosestIntersection(
updateIntersectionsMap(
this.raycaster.intersectObjects(this.scene.children, true)
)
}
return closestIntersection
// Convert the map values to an array and sort by distance
return Array.from(intersectionsMap.values()).sort(
(a, b) => a.distance - b.distance
)
}
onMouseDown = (event: MouseEvent) => {
@ -399,45 +421,60 @@ class SceneInfra {
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
const mouseDownVector = this.currentMouseVector.clone()
const intersect = this.raycastRing()
const intersect = this.raycastRing()[0]
if (intersect) {
const intersectParent = intersect?.object?.parent as Group
this.selected = intersectParent.isGroup
? {
mouseDownVector,
object: intersect?.object,
object: intersect.object,
hasBeenDragged: false,
}
: null
}
}
onMouseUp = (event: MouseEvent) => {
this.currentMouseVector.x = (event.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y = -(event.clientY / window.innerHeight) * 2 + 1
onMouseUp = (mouseEvent: MouseEvent) => {
this.currentMouseVector.x = (mouseEvent.clientX / window.innerWidth) * 2 - 1
this.currentMouseVector.y =
-(mouseEvent.clientY / window.innerHeight) * 2 + 1
const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersects = this.raycastRing()
if (this.selected) {
if (this.selected.hasBeenDragged) {
// this is where we could fire a onDragEnd event
// console.log('onDragEnd', this.selected)
} else if (planeIntersectPoint) {
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
// fire onClick event as there was no drags
this.onClickCallback({
object: this.selected?.object,
event,
...planeIntersectPoint,
mouseEvent,
intersectionPoint: {
twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
selected: this.selected.object,
})
} else if (planeIntersectPoint) {
this.onClickCallback({
mouseEvent,
intersects,
})
} else {
this.onClickCallback()
}
// Clear the selected state whether it was dragged or not
this.selected = null
} else if (planeIntersectPoint) {
} else if (planeIntersectPoint?.twoD && planeIntersectPoint?.threeD) {
this.onClickCallback({
event,
...planeIntersectPoint,
mouseEvent,
intersectionPoint: {
twoD: planeIntersectPoint.twoD,
threeD: planeIntersectPoint.threeD,
},
intersects,
})
} else {
this.onClickCallback()
@ -483,7 +520,11 @@ class SceneInfra {
this.camControls.camera,
this.camControls.target
)
planesGroup.scale.set(sceneScale, sceneScale, sceneScale)
planesGroup.scale.set(
sceneScale / sceneInfra._baseUnitMultiplier,
sceneScale / sceneInfra._baseUnitMultiplier,
sceneScale / sceneInfra._baseUnitMultiplier
)
this.scene.add(planesGroup)
}
removeDefaultPlanes() {

View File

@ -1,5 +1,6 @@
import { Coords2d } from 'lang/std/sketch'
import {
BoxGeometry,
BufferGeometry,
CatmullRomCurve3,
ConeGeometry,
@ -19,6 +20,7 @@ import {
import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
import { PathToNode, SketchGroup, getTangentialArcToInfo } from 'lang/wasm'
import {
PROFILE_START,
STRAIGHT_SEGMENT,
STRAIGHT_SEGMENT_BODY,
STRAIGHT_SEGMENT_DASH,
@ -29,6 +31,38 @@ import {
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import { ARROWHEAD } from './sceneInfra'
export function profileStart({
from,
id,
pathToNode,
scale = 1,
}: {
from: Coords2d
id: string
pathToNode: PathToNode
scale?: number
}) {
const group = new Group()
const geometry = new BoxGeometry(0.8, 0.8, 0.8)
const body = new MeshBasicMaterial({ color: 0xffffff })
const mesh = new Mesh(geometry, body)
group.add(mesh)
group.userData = {
type: PROFILE_START,
id,
from,
pathToNode,
isSelected: false,
}
group.name = PROFILE_START
group.position.set(from[0], from[1], 0)
group.scale.set(scale, scale, scale)
return group
}
export function straightSegment({
from,
to,
@ -81,6 +115,7 @@ export function straightSegment({
pathToNode,
isSelected: false,
}
group.name = STRAIGHT_SEGMENT
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)
@ -169,6 +204,7 @@ export function tangentialArcToSegment({
pathToNode,
isSelected: false,
}
group.name = TANGENTIAL_ARC_TO_SEGMENT
const arrowGroup = createArrowhead(scale)
arrowGroup.position.set(to[0], to[1], 0)

View File

@ -18,6 +18,7 @@ import {
import { isTauri } from 'lib/isTauri'
import { settingsCommandBarConfig } from 'lib/commandBarConfigs/settingsCommandConfig'
import { authCommandBarConfig } from 'lib/commandBarConfigs/authCommandConfig'
import { sceneInfra } from 'clientSideScene/sceneInfra'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -97,6 +98,7 @@ export const GlobalStateProvider = ({
if (settingsState.context.theme !== 'system') return
setThemeClass(e.matches ? Themes.Dark : Themes.Light)
}
sceneInfra.baseUnit = settingsState?.context?.baseUnit || 'mm'
matcher.addEventListener('change', listener)
return () => matcher.removeEventListener('change', listener)

View File

@ -25,8 +25,7 @@ describe('processMemory', () => {
|> lineTo([-3.35, 0.17], %)
|> lineTo([0.98, 5.16], %)
|> lineTo([2.15, 4.32], %)
// |> rx(90, %)
show(theExtrude, theSketch)`
// |> rx(90, %)`
const ast = parse(code)
const programMemory = await enginelessExecutor(ast, {
root: {},

View File

@ -1,15 +1,8 @@
import {
MouseEventHandler,
WheelEventHandler,
useEffect,
useRef,
useState,
} from 'react'
import { MouseEventHandler, useEffect, useRef, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import { useStore } from '../useStore'
import { getNormalisedCoordinates, throttle } from '../lib/utils'
import { getNormalisedCoordinates } from '../lib/utils'
import Loading from './Loading'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { Models } from '@kittycad/lib'
import { engineCommandManager } from '../lang/std/engineConnection'
@ -36,7 +29,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
streamDimensions: s.streamDimensions,
}))
const { settings } = useGlobalStateContext()
const cameraControls = settings?.context?.cameraControls
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus()
@ -68,19 +60,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
setClickCoords({ x, y })
}
const fps = 60
const handleScroll: WheelEventHandler<HTMLVideoElement> = throttle((e) => {
if (!cameraMouseDragGuards[cameraControls].zoom.scrollCallback(e)) return
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude: e.deltaY * 0.4,
},
cmd_id: uuidv4(),
})
}, Math.round(1000 / fps))
const handleMouseUp: MouseEventHandler<HTMLDivElement> = ({
clientX,
clientY,
@ -159,7 +138,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
muted
autoPlay
controls={false}
onWheel={handleScroll}
onPlay={() => setIsLoading(false)}
onMouseMoveCapture={handleMouseMove}
className={`w-full cursor-pointer h-full ${isExecuting && 'blur-md'}`}

View File

@ -27,6 +27,8 @@ describe('UserSidebarMenu tests', () => {
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
}
render(
@ -57,6 +59,8 @@ describe('UserSidebarMenu tests', () => {
first_name: '',
last_name: '',
name: '',
can_train_on_data: false,
is_service_account: false,
}
render(
@ -84,6 +88,8 @@ describe('UserSidebarMenu tests', () => {
first_name: 'Test',
last_name: 'User',
image: '',
can_train_on_data: false,
is_service_account: false,
}
render(

View File

@ -4,6 +4,7 @@ import { ViewPlugin, hoverTooltip, tooltips } from '@codemirror/view'
import { CompletionTriggerKind } from 'vscode-languageserver-protocol'
import { offsetToPos } from 'editor/plugins/lsp/util'
import { LanguageServerOptions } from 'editor/plugins/lsp'
import { syntaxTree } from '@codemirror/language'
import {
LanguageServerPlugin,
documentUri,
@ -40,6 +41,14 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
if (plugin == null) return null
const { state, pos, explicit } = context
let nodeBefore = syntaxTree(state).resolveInner(pos, -1)
if (
nodeBefore.name === 'BlockComment' ||
nodeBefore.name === 'LineComment'
)
return null
const line = state.doc.lineAt(pos)
let trigKind: CompletionTriggerKind = CompletionTriggerKind.Invoked
let trigChar: string | undefined
@ -60,6 +69,7 @@ export function kclPlugin(options: LanguageServerOptions): Extension {
) {
return null
}
return await plugin.requestCompletion(
context,
offsetToPos(state.doc, pos),

View File

@ -7,6 +7,5 @@ export const VITE_KC_API_BASE_URL = import.meta.env.VITE_KC_API_BASE_URL
export const VITE_KC_SITE_BASE_URL = import.meta.env.VITE_KC_SITE_BASE_URL
export const VITE_KC_CONNECTION_TIMEOUT_MS = import.meta.env
.VITE_KC_CONNECTION_TIMEOUT_MS
export const VITE_KC_SENTRY_DSN = import.meta.env.VITE_KC_SENTRY_DSN
export const TEST = import.meta.env.TEST
export const DEV = import.meta.env.DEV

View File

@ -93,7 +93,7 @@ class KclManager {
// Note that PROJECT_ENTRYPOINT is hardcoded until we support multiple files
this._params.id &&
writeTextFile(this._params.id, code).catch((err) => {
// TODO: add Sentry per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
// TODO: add tracing per GH issue #254 (https://github.com/KittyCAD/modeling-app/issues/254)
console.error('error saving file', err)
toast.error('Error saving file, please check file permissions')
})

View File

@ -11,59 +11,53 @@ const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
show(mySketch001)`
// |> rx(45, %)`
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
(a) => programMemory?.root?.[a.name]
)
expect(shown).toEqual([
{
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
type: 'SketchGroup',
on: expect.any(Object),
start: {
to: [0, 0],
from: [0, 0],
name: '',
__geoMeta: {
id: expect.any(String),
sourceRange: [46, 71],
},
},
value: [
{
type: 'ToPoint',
name: '',
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [77, 102],
id: expect.any(String),
sourceRange: [46, 71],
},
},
value: [
{
type: 'ToPoint',
name: '',
to: [-1.59, -1.54],
from: [0, 0],
__geoMeta: {
sourceRange: [77, 102],
id: expect.any(String),
},
{
type: 'ToPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
name: '',
__geoMeta: {
sourceRange: [108, 132],
id: expect.any(String),
},
{
type: 'ToPoint',
to: [0.46, -5.82],
from: [-1.59, -1.54],
name: '',
__geoMeta: {
sourceRange: [108, 132],
id: expect.any(String),
},
},
],
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
id: expect.any(String),
entityId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
},
])
},
],
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
id: expect.any(String),
entityId: expect.any(String),
__meta: [{ sourceRange: [46, 71] }],
})
})
test('extrude artifacts', async () => {
// Enable rotations #152
@ -73,30 +67,24 @@ const mySketch001 = startSketchOn('XY')
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
|> extrude(2, %)
show(mySketch001)`
|> extrude(2, %)`
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const shown = programMemory?.return?.map(
// @ts-ignore
(a) => programMemory?.root?.[a.name]
)
expect(shown).toEqual([
{
type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
endCapId: null,
startCapId: null,
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [46, 71] }],
},
])
const sketch001 = programMemory?.root?.mySketch001
expect(sketch001).toEqual({
type: 'ExtrudeGroup',
id: expect.any(String),
value: [],
height: 2,
position: [0, 0, 0],
rotation: [0, 0, 0, 1],
endCapId: null,
startCapId: null,
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [46, 71] }],
})
})
test('sketch extrude and sketch on one of the faces', async () => {
// Enable rotations #152
@ -120,14 +108,10 @@ const sk2 = startSketchOn('XY')
// |> transform(theTransf, %)
|> extrude(2, %)
show(theExtrude, sk2)`
`
const programMemory = await enginelessExecutor(parse(code))
// @ts-ignore
const geos = programMemory?.return?.map(
// @ts-ignore
({ name }) => programMemory?.root?.[name]
)
const geos = [programMemory?.root?.theExtrude, programMemory?.root?.sk2]
expect(geos).toEqual([
{
type: 'ExtrudeGroup',

View File

@ -47,9 +47,8 @@ const newVar = myVar + 1`
|> lineTo([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
// |> close(%)
show(mySketch)
`
const { root, return: _return } = await exe(code)
const { root } = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const minusGeo = root.mySketch.value
expect(minusGeo).toEqual([
@ -84,15 +83,6 @@ show(mySketch)
name: 'rightPath',
},
])
// expect(root.mySketch.sketch[0]).toEqual(root.mySketch.sketch[4].firstPath)
expect(_return).toEqual([
{
type: 'Identifier',
start: 203,
end: 211,
name: 'mySketch',
},
])
})
it('pipe binary expression into call expression', async () => {
@ -357,7 +347,6 @@ describe('testing math operators', () => {
` -legLen(segLen('seg01', %), myVar)`,
`], %)`,
``,
`show(part001)`,
].join('\n')
const { root } = await exe(code)
const sketch = root.part001
@ -392,8 +381,7 @@ const theExtrude = startSketchOn('XY')
|> line([-0.76], myVarZ, %)
|> line([5,5], %)
|> close(%)
|> extrude(4, %)
show(theExtrude)`
|> extrude(4, %)`
await expect(exe(code)).rejects.toEqual(
new KCLError(
'undefined_value',

View File

@ -122,7 +122,6 @@ describe('Testing addSketchTo', () => {
expect(str).toBe(`const part001 = startSketchOn('YZ')
|> startProfileAt('default', %)
|> line('default', %)
show(part001)
`)
})
})
@ -147,8 +146,7 @@ describe('Testing giveSketchFnCallTag', () => {
|> startProfileAt([0, 0], %)
|> line([-2.57, -0.13], %)
|> line([0, 0.83], %)
|> line([0.82, 0.34], %)
show(part001)`
|> line([0.82, 0.34], %)`
it('Should add tag to a sketch function call', () => {
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
code,
@ -204,8 +202,7 @@ const part001 = startSketchOn('XY')
|> angledLine([def(yo), 3.09], %)
|> angledLine([ghi(%), 3.09], %)
|> angledLine([jkl(yo) + 2, 3.09], %)
const yo2 = hmm([identifierGuy + 5])
show(part001)`
const yo2 = hmm([identifierGuy + 5])`
it('should move a binary expression into a new variable', async () => {
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)

View File

@ -6,7 +6,6 @@ import {
PipeExpression,
VariableDeclaration,
VariableDeclarator,
ExpressionStatement,
Value,
Literal,
PipeSubstitution,
@ -128,16 +127,8 @@ export function addSketchTo(
createPipeExpression(pipeBody)
)
const showCallIndex = getShowIndex(_node)
let sketchIndex = showCallIndex
if (showCallIndex === -1) {
_node.body = [...node.body, variableDeclaration]
sketchIndex = _node.body.length - 1
} else {
const newBody = [...node.body]
newBody.splice(showCallIndex, 0, variableDeclaration)
_node.body = newBody
}
_node.body = [...node.body, variableDeclaration]
let sketchIndex = _node.body.length - 1
let pathToNode: PathToNode = [
['body', ''],
[sketchIndex, 'index'],
@ -150,7 +141,7 @@ export function addSketchTo(
}
return {
modifiedAst: addToShow(_node, _name),
modifiedAst: _node,
id: _name,
pathToNode,
}
@ -191,44 +182,6 @@ export function findUniqueName(
return findUniqueName(searchStr, name, pad, index + 1)
}
function addToShow(node: Program, name: string): Program {
const _node = { ...node }
const dumbyStartend = { start: 0, end: 0 }
const showCallIndex = getShowIndex(_node)
if (showCallIndex === -1) {
const showCall = createCallExpressionStdLib('show', [
createIdentifier(name),
])
const showExpressionStatement: ExpressionStatement = {
type: 'ExpressionStatement',
...dumbyStartend,
expression: showCall,
}
_node.body = [..._node.body, showExpressionStatement]
return _node
}
const showCall = { ..._node.body[showCallIndex] } as ExpressionStatement
const showCallArgs = (showCall.expression as CallExpression).arguments
const newShowCallArgs: Value[] = [...showCallArgs, createIdentifier(name)]
const newShowExpression = createCallExpressionStdLib('show', newShowCallArgs)
_node.body[showCallIndex] = {
...showCall,
expression: newShowExpression,
}
return _node
}
function getShowIndex(node: Program): number {
return node.body.findIndex(
(statement) =>
statement.type === 'ExpressionStatement' &&
statement.expression.type === 'CallExpression' &&
statement.expression.callee.type === 'Identifier' &&
statement.expression.callee.name === 'show'
)
}
export function mutateArrExp(
node: Value,
updateWith: ArrayExpression
@ -348,15 +301,10 @@ export function extrudeSketch(
}
const name = findUniqueName(node, 'part')
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
let showCallIndex = getShowIndex(_node)
if (showCallIndex === -1) {
// We didn't find a show, so let's just append everything
showCallIndex = _node.body.length
}
_node.body.splice(showCallIndex, 0, VariableDeclaration)
_node.body.splice(_node.body.length, 0, VariableDeclaration)
const pathToExtrudeArg: PathToNode = [
['body', ''],
[showCallIndex, 'index'],
[_node.body.length, 'index'],
['declarations', 'VariableDeclaration'],
[0, 'index'],
['init', 'VariableDeclarator'],
@ -365,7 +313,7 @@ export function extrudeSketch(
]
return {
modifiedAst: node,
pathToNode: [...pathToNode.slice(0, -1), [showCallIndex, 'index']],
pathToNode: [...pathToNode.slice(0, -1), [-1, 'index']],
pathToExtrudeArg,
}
}
@ -425,7 +373,7 @@ export function sketchOnExtrudedFace(
_node.body.splice(expressionIndex + 1, 0, newSketch)
return {
modifiedAst: addToShow(_node, newSketchName),
modifiedAst: _node,
pathToNode: [...pathToNode.slice(0, -1), [expressionIndex, 'index']],
}
}

View File

@ -34,8 +34,7 @@ const part001 = startSketchOn('XY')
|> xLine(3.84, %) // selection-range-7ish-before-this
const variableBelowShouldNotBeIncluded = 3
show(part001)`
`
const rangeStart = code.indexOf('// selection-range-7ish-before-this') - 7
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
@ -69,8 +68,7 @@ describe('testing argIsNotIdentifier', () => {
|> angledLine([ghi(%), 3.09], %)
|> angledLine([jkl('yo') + 2, 3.09], %)
const yo = 5 + 6
const yo2 = hmm([identifierGuy + 5])
show(part001)`
const yo2 = hmm([identifierGuy + 5])`
it('find a safe binaryExpression', () => {
const ast = parse(code)
const rangeStart = code.indexOf('100 + 100') + 2
@ -201,8 +199,7 @@ describe('testing getNodePathFromSourceRange', () => {
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([0.39, -0.05], %)
|> line([0.94, 2.61], %)
|> line([-0.21, -1.4], %)
show(part001)`
|> line([-0.21, -1.4], %)`
it('finds the second line when cursor is put at the end', () => {
const searchLn = `line([0.94, 2.61], %)`
const sourceIndex = code.indexOf(searchLn) + searchLn.length

View File

@ -68,8 +68,6 @@ log(5, myVar)
|> lineTo([1, 1], %)
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|> close(%)
show(mySketch)
`
const { ast } = code2ast(code)
const recasted = recast(ast)
@ -331,7 +329,6 @@ describe('it recasts wrapped object expressions in pipe bodies with correct inde
intersectTag: 'seg01'
}, %)
|> line([-0.42, -1.72], %)
show(part001)
`
const { ast } = code2ast(code)
const recasted = recast(ast)

View File

@ -3,7 +3,6 @@ import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
import { Models } from '@kittycad/lib'
import { exportSave } from 'lib/exportSave'
import { v4 as uuidv4 } from 'uuid'
import * as Sentry from '@sentry/react'
import { getNodePathFromSourceRange } from 'lang/queryAst'
import { sceneInfra } from 'clientSideScene/sceneInfra'
@ -290,12 +289,6 @@ class EngineConnection {
}
}
// shouldTrace will return true when Sentry should be used to instrument
// the Engine.
shouldTrace() {
return Sentry.getCurrentHub()?.getClient()?.getOptions()?.sendClientReports
}
// connect will attempt to connect to the Engine over a WebSocket, and
// establish the WebRTC connections.
//
@ -308,41 +301,6 @@ class EngineConnection {
// Information on the connect transaction
class SpanPromise {
span: Sentry.Span
promise: Promise<void>
resolve?: (v: void) => void
constructor(span: Sentry.Span) {
this.span = span
this.promise = new Promise((resolve) => {
this.resolve = (v: void) => {
// here we're going to invoke finish before resolving the
// promise so that a `.then()` will order strictly after
// all spans have -- for sure -- been resolved, rather than
// doing a `then` on this promise.
this.span.finish()
resolve(v)
}
})
}
}
let webrtcMediaTransaction: Sentry.Transaction
let websocketSpan: SpanPromise
let mediaTrackSpan: SpanPromise
let dataChannelSpan: SpanPromise
let handshakeSpan: SpanPromise
let iceSpan: SpanPromise
const spanStart = (op: string) =>
new SpanPromise(webrtcMediaTransaction.startChild({ op }))
if (this.shouldTrace()) {
webrtcMediaTransaction = Sentry.startTransaction({ name: 'webrtc-media' })
websocketSpan = spanStart('websocket')
}
const createPeerConnection = () => {
this.pc = new RTCPeerConnection()
@ -393,10 +351,6 @@ class EngineConnection {
// From what I understand, only after have we done the ICE song and
// dance is it safest to connect the video tracks / stream
case 'connected':
if (this.shouldTrace()) {
iceSpan.resolve?.()
}
// Let the browser attach to the video stream now
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! })
break
@ -429,17 +383,6 @@ class EngineConnection {
},
}
if (this.shouldTrace()) {
let mediaStreamTrack = mediaStream.getVideoTracks()[0]
mediaStreamTrack.addEventListener('unmute', () => {
// let settings = mediaStreamTrack.getSettings()
// mediaTrackSpan.span.setTag("fps", settings.frameRate)
// mediaTrackSpan.span.setTag("width", settings.width)
// mediaTrackSpan.span.setTag("height", settings.height)
mediaTrackSpan.resolve?.()
})
}
this.webrtcStatsCollector = (): Promise<ClientMetrics> => {
return new Promise((resolve, reject) => {
if (mediaStream.getVideoTracks().length !== 1) {
@ -522,10 +465,6 @@ class EngineConnection {
},
}
if (this.shouldTrace()) {
dataChannelSpan.resolve?.()
}
// Everything is now connected.
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
@ -577,27 +516,6 @@ class EngineConnection {
if (this.token) {
this.send({ headers: { Authorization: `Bearer ${this.token}` } })
}
if (this.shouldTrace()) {
websocketSpan.resolve?.()
handshakeSpan = spanStart('handshake')
iceSpan = spanStart('ice')
dataChannelSpan = spanStart('data-channel')
mediaTrackSpan = spanStart('media-track')
}
if (this.shouldTrace()) {
void Promise.all([
handshakeSpan.promise,
iceSpan.promise,
dataChannelSpan.promise,
mediaTrackSpan.promise,
]).then(() => {
console.log('All spans finished, reporting')
webrtcMediaTransaction?.finish()
})
}
})
this.websocket.addEventListener('close', (event) => {
@ -786,13 +704,6 @@ failed cmd type was ${artifactThatFailed?.commandType}`
type: ConnectingType.WebRTCConnecting,
},
}
if (this.shouldTrace()) {
// When both ends have a local and remote SDP, we've been able to
// set up successfully. We'll still need to find the right ICE
// servers, but this is hand-shook.
handshakeSpan.resolve?.()
}
break
case 'trickle_ice':
@ -885,7 +796,7 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
}
interface Subscription<T extends ModelTypes> {
export interface Subscription<T extends ModelTypes> {
event: T
callback: (
data: Extract<Models['OkModelingCmdResponse_type'], { type: T }>
@ -1015,6 +926,15 @@ export class EngineCommandManager {
},
})
sceneInfra.camControls.onCameraChange()
this.sendSceneCommand({
// CameraControls subscribes to default_camera_get_settings response events
// firing this at connection ensure the camera's are synced initially
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
})
this.initPlanes().then(() => {
this.resolveReady()

View File

@ -101,7 +101,6 @@ describe('testing changeSketchArguments', () => {
|> ${line}
|> lineTo([0.46, -5.82], %)
// |> rx(45, %)
show(mySketch001)
`
const code = genCode(lineToChange)
const expectedCode = genCode(lineAfterChange)
@ -128,8 +127,7 @@ const mySketch001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
// |> rx(45, %)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
show(mySketch001)`
|> lineTo([0.46, -5.82], %)`
const ast = parse(code)
const programMemory = await enginelessExecutor(ast)
const sourceStart = code.indexOf(lineToChange)
@ -155,7 +153,6 @@ show(mySketch001)`
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
|> lineTo([2, 3], %)
show(mySketch001)
`
expect(recast(modifiedAst)).toBe(expectedCode)
@ -177,7 +174,6 @@ show(mySketch001)
|> lineTo([-1.59, -1.54], %)
|> lineTo([0.46, -5.82], %)
|> close(%)
show(mySketch001)
`
expect(recast(modifiedAst)).toBe(expectedCode)
})
@ -192,7 +188,6 @@ describe('testing addTagForSketchOnFace', () => {
// |> rx(45, %)
|> ${line}
|> lineTo([0.46, -5.82], %)
show(mySketch001)
`
const code = genCode(originalLine)
const ast = parse(code)

View File

@ -91,12 +91,6 @@ export function createFirstArg(
throw new Error('all sketch line types should have been covered')
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
type LineData = {
from: [number, number, number]
to: [number, number, number]
}
export const lineTo: SketchLineHelper = {
add: ({
node,
@ -966,6 +960,30 @@ export const angledLineThatIntersects: SketchLineHelper = {
addTag: addTagWithTo('angleTo'), // TODO might be wrong
}
export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
node,
pathToNode,
to,
}) => {
const _node = { ...node }
const { node: callExpression } = getNodeFromPath<CallExpression>(
_node,
pathToNode
)
const toArrExp = createArrayExpression([
createLiteral(roundOff(to[0])),
createLiteral(roundOff(to[1])),
])
mutateArrExp(callExpression.arguments?.[0], toArrExp) ||
mutateObjExpProp(callExpression.arguments?.[0], toArrExp, 'to')
return {
modifiedAst: _node,
pathToNode,
}
}
export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
line,
lineTo,

View File

@ -88,7 +88,6 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
` |> yLine(-1.07, %)`,
` |> xLineTo(3.27, %)`,
` |> yLineTo(2.14, %)`,
`show(part001)`,
]
const bigExample = bigExampleArr.join('\n')
it('line with tag converts to xLine', async () => {
@ -290,7 +289,6 @@ describe('testing swapping out sketch calls with xLine/xLineTo while keeping var
` |> angledLineToX([330, angledLineToXx], %)`,
` |> angledLineToY([217, angledLineToYy], %)`,
` |> line([0.89, -0.1], %)`,
`show(part001)`,
]
const varExample = variablesExampleArr.join('\n')
it('line keeps variable when converted to xLine', async () => {
@ -378,8 +376,7 @@ const part001 = startSketchOn('XY')
|> line([0, 0.4], %)
|> xLine(3.48, %)
|> line([2.14, 1.35], %) // normal-segment
|> xLine(3.54, %)
show(part001)`
|> xLine(3.54, %)`
it('normal case works', async () => {
const programMemory = await enginelessExecutor(parse(code))
const index = code.indexOf('// normal-segment') - 7

View File

@ -123,7 +123,6 @@ const part001 = startSketchOn('XY')
|> yLine(1.04, %) // ln-yLine-free should sub in segLen
|> xLineTo(30, %) // ln-xLineTo-free should convert to xLine
|> yLineTo(20, %) // ln-yLineTo-free should convert to yLine
show(part001)
`
const expectModifiedScript = `const myVar = 3
const myVar2 = 5
@ -196,7 +195,6 @@ const part001 = startSketchOn('XY')
|> yLine(segLen('seg01', %), %) // ln-yLine-free should sub in segLen
|> xLine(segLen('seg01', %), %) // ln-xLineTo-free should convert to xLine
|> yLine(segLen('seg01', %), %) // ln-yLineTo-free should convert to yLine
show(part001)
`
it('should transform the ast', async () => {
const ast = parse(inputScript)
@ -257,7 +255,6 @@ const part001 = startSketchOn('XY')
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)
`
it('should transform horizontal lines the ast', async () => {
const expectModifiedScript = `const myVar = 2
@ -286,7 +283,6 @@ const part001 = startSketchOn('XY')
|> angledLineToY([223, 7.68], %) // select for vertical constraint 9
|> xLineTo(myVar3, %) // select for horizontal constraint 10
|> angledLineToY([301, myVar], %) // select for vertical constraint 10
show(part001)
`
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
@ -345,7 +341,6 @@ const part001 = startSketchOn('XY')
|> yLineTo(7.68, %) // select for vertical constraint 9
|> angledLineToX([333, myVar3], %) // select for horizontal constraint 10
|> yLineTo(myVar, %) // select for vertical constraint 10
show(part001)
`
const ast = parse(inputScript)
const selectionRanges: Selections['codeBasedSelections'] = inputScript
@ -389,7 +384,6 @@ const part001 = startSketchOn('XY')
|> line([0.45, 1.46], %) // free
|> line([myVar, 0.01], %) // xRelative
|> line([0.7, myVar], %) // yRelative
show(part001)
`
it('testing for free to horizontal and vertical distance', async () => {
const expectedHorizontalCode = await helperThing(
@ -501,8 +495,7 @@ const part001 = startSketchOn('XY')
|> xLine(3.36, %) // partial
|> line([-1.49, 1.06], %) // free
|> xLine(-3.43 + 0, %) // full
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full
show(part001)`
|> angledLineOfXLength([243 + 0, 1.2 + 0], %) // full`
const ast = parse(code)
const constraintLevels: ReturnType<
typeof getConstraintLevelFromSourceRange

View File

@ -15,8 +15,7 @@ describe('testing angledLineThatIntersects', () => {
offset: ${offset},
tag: "yo2"
}, %)
const intersect = segEndX('yo2', part001)
show(part001)`
const intersect = segEndX('yo2', part001)`
const { root } = await enginelessExecutor(parse(code('-1')))
expect(root.intersect.value).toBe(1 + Math.sqrt(2))
const { root: noOffset } = await enginelessExecutor(parse(code('0')))

View File

@ -40,9 +40,9 @@ export interface MouseGuard {
}
const butName = (e: React.MouseEvent) => ({
middle: !!(e.buttons & 4),
right: !!(e.buttons & 2),
left: !!(e.buttons & 1),
middle: !!(e.buttons & 4) || e.button === 1,
right: !!(e.buttons & 2) || e.button === 2,
left: !!(e.buttons & 1) || e.button === 0,
})
export const cameraMouseDragGuards: Record<CameraSystem, MouseGuard> = {

View File

@ -20,6 +20,7 @@ import {
TANGENTIAL_ARC_TO_SEGMENT,
sceneEntitiesManager,
getParentGroup,
PROFILE_START,
} from 'clientSideScene/sceneEntities'
import { Mesh } from 'three'
import { AXIS_GROUP, X_AXIS } from 'clientSideScene/sceneInfra'
@ -188,7 +189,11 @@ export async function getEventForSelectWithPoint(
export function getEventForSegmentSelection(
obj: any
): ModelingMachineEvent | null {
const group = getParentGroup(obj)
const group = getParentGroup(obj, [
STRAIGHT_SEGMENT,
TANGENTIAL_ARC_TO_SEGMENT,
PROFILE_START,
])
const axisGroup = getParentGroup(obj, [AXIS_GROUP])
if (!group && !axisGroup) return null
if (axisGroup?.userData.type === AXIS_GROUP) {
@ -407,8 +412,8 @@ function updateSceneObjectColors(codeBasedSelections: Selection[]) {
}
Object.values(sceneEntitiesManager.activeSegments).forEach((segmentGroup) => {
if (
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT].includes(
segmentGroup?.userData?.type
![STRAIGHT_SEGMENT, TANGENTIAL_ARC_TO_SEGMENT, PROFILE_START].includes(
segmentGroup?.name
)
)
return

View File

@ -20,6 +20,8 @@ const LOCAL_USER: Models['User_type'] = {
phone: '555-555-5555',
first_name: 'Test',
last_name: 'User',
can_train_on_data: false,
is_service_account: false,
}
export interface UserContext {

View File

@ -119,7 +119,7 @@ export type MoveDesc = { line: number; snippet: string }
export const modelingMachine = createMachine(
{
/** @xstate-layout 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-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
/** @xstate-layout N4IgpgJg5mDOIC5QFkD2EwBsCWA7KAxAMICGuAxlgNoAMAuoqAA6qzYAu2qujIAHogC0AdgCsAZgB04gEyjhADnEA2GgoUAWJQBoQAT0QBGGuICckmoZkbTM42YWKAvk91oMOfAQDKYdgAJYLDByTm5aBiQQFjYwniiBBFtpGhlxDRphGg1ZURlhXQMEcRLDSVF5UwV84XEK8Rc3dCw8KElsCEwwHz9A4NCuXAjeGI5B3kTBLWFJDWV5hVSZZTrlBUKjS1FZiUrDeWVDasaQdxb8ds7ugFFcdjAAJ0CAaz9yAAthqNG4iaF07bCNLiRYlGiZUyZDbFYQzfIaLSQ5Sw0TGZQnM6eNodLoEW73J6wV7sD5UQyRZisMbcP4IQRrGiSdRmfIqUxrCrQlbKcoQ4RHDTWUx5DHNLGXXHXPjsB4AVwwX0psXGCSEohWO3Uh2UOSsmmhhn2jPyNGUolMhjWwvZoo8rUk3mJH2IZEomEdb0+9BGVN+qoQhoUPOMwgRi1BplkBqOZUDCkN4ksClEClt5zaHpJ7wdTveAEkrj0AkEugNwt7vr6VaBEoZQ2UKsIOcmrPMDRCLIKaOa6oKU6I0+LMx8c56C7jkCRXn0oABbMB3fwAN0enHIJEwiuiVZp-qsFskRxkmibmlSBX0Rg0hikifBcgk+SUGkH9uH2ff4+6k+nQTnC4Cd5UAebAAC9uHYDctx+at+CMeFJFUINu0NfJkRkA1xENcoNHkSwVmMCoZFfC531HLMv2IbhYBlEg8H8ICQPAu4N38CBsBo10wGgnd4hreDRA0Ts0hoKp0gtdRoTSdkLAqURwVwi0bxIjNc3Ij5KKIajaPolcHjXVj2M4ihuIrJVqT4uCA2WBRJGFY9UR1YxNAwy8EBkVlymsAVYSBM0X1cU4xTfNTP0LLTcBoh46NwfwAEEACFvH8AANHjlV3fjrOTWZxDEQwbCwmRbHEKThSE4VkUWVJzVqBpAsxELPXU-Nwu06L6MS5KAE10os2k9W2aw7GUOR9jNUboUTdRJCBOTLTUUMAqaO1SNC3NNPamL-DIKAuj6v0spvHJpCUapk0UOtTCk+S4X7Xz1WEUxTGEFSWpazbIp02KunwdgvQpbcMss2sShmFR1VEm80n2KTDlsptTGvJ6wxUV6GuCtbmrC3EIqi7amEeQncHY8hZUwEgniMyCTIO2Daw82ybEjKwMiOVRlCk+MhP5KoXqBBENDEN6yJx7o8e+hjgLAiCN0wPQdpwKAhjMoH+r3ZYZCZFMtAkXCMhWA1HMkYqddhfJ1RKEX1rHNqvo62K9IMzB5cV7BlbpzKrKsJ7cuPJ7HLMVEDVG7YUxeqo1mKxx6pW9N3rFqj7e22BcBIJh-HYVBUs9kGjCwqRlhBNQXuqMwpOyLWPPVeZVBTIFiIx1bVOxja7fx+jU-TzPs961WYK90GbwsJssNhPLjA0KSCpmQTHDkC1Qx1WOgubhO29xrb6LAABHWVWN+qB-tzgbiqkVEhaerI8gkC8ijrRNcpMcFuwm0xrdb23N+T+imEpuXqD914gNWQMwlgER7MjKeblDCRjAaUQ4z1MhqAHE3eOosN7iy3rFB4YBZyoBXP4cg2D2CwBPhrLQiFWbqmPNeFMpUYFKDKPhWw2QwRBkbnHIcNsKKFgAEpgEEGAPgIRZT3HIUdOoUgbywMTGkIWFRDDQnUFrdIqRkS1yUIcD+WYPqFmuHvbAGcAAyeAwA91QJuIBwMBoKBkhIFQzM9aSTckoMOmRhpyDmKiQwOiRyJwMbKIxmddoAWwKxSm5Ae4SKsiNWyIJLaWnkG4hh99jBlFPHXRQ6Q5jLVXugtScUADudFALS2YpBTAbEOI00oP4PAAAzVABAIDcDAO0XAS5UCvEkDAdgghGIyxYpgQQjTUAxMSCJISCIdSiVqM9ceUlrzmGTMeGGyhbCPT8dmYppSpZMVllU6mXF6m4CaQQR4DxgKSCYBTdgTSHizl6X4AZ5TDmjLOeM6x6ssoiW2IaBECZcLHhelJEEWtsimnBHUNCvi0HcOarsjgy5VzYHXEcmpJyxktLaR0rpPS+mCCdmijcHymkTMQDDLWN5LomGRLA-IUljxSEcDfOwpQgTbMkEigIxL0XVOMnU7Flzrm3JIPc4CTzCV8tJWMil7kipMm7GIReOsmWCkPBNVIIJzpKC5Ty+KSVUqnPOa03A7S8D4vaYSkgAAjWAgg+Bkq+YDAeecFWWlmqNe8cg6jrDcsVUah4MjXmXprWo+qSnIq6sa4VDwrkPBuXch5UqXl2odU6uV3zDqxLHrlHVdRIRzzKp6v1ZosLZFDO-eFTVdEGpjd1E1zSzUWs6d061ab7WCD0M6+V6zpCNgqLYOBuEpqmhmDYOx8l6HdjyY1LGdao0BAbU2i58bRXJslc8-p6bu29uzfTSlebzwrD8isUNU1LRCVNOyUaY08qcPyQixdezdpdFXS2vF7bt2CDfYIrNrrgH+hhuDSwN4b7PWDgGioPJIzdjAxUIMsJI2vvwO+uNCak3ipTT+v9+7AM2OA1hBG2Q9RWBLnWG6dZDzgayHIbsyx0ZcNrSOA1h9-oftxZa79hL2PvHwz6QjvysLbByNJKBMhzxwxyqaDlKg6xWBQ8ivjq6RWJrFRKx5P6+MCcrEJ3NnrjAmBTJaE8rkijLA1fMawdiFEonRDWhdrGl3+EJg8YmpNyaUwFbU8x2LP3cYJS8tzHm0VeYeIIY5JldPmRzZMzWdl6HWDvVHUQXM5DSH5HMM0kHfVKYCCFhcnmKZU0xSZVT671Obq04SwrJMwslci2VygMW1ZxcpQl4UShkseVS1JXymWCrzHNPZCQXLTHmosZgIsfRSxxD7ZJ8w15JNHEyEcPIqTKUvUPF10SNmkKPvnS3XRE3zFZ0sXiQxGcaYwDuOEqpkTokHsHpS2EQl9hmHBOoPmnM3L83zYoWEokHPMac9mU7U3JB5lwBwAgC2VBMgSatpGC8uTxhNiCSMkI8pPTSONsxkPoew7JARn5sT8ha3kseK95GNnQlhEtlYEhNkbPBKg0Hx2RwQ-O5gSQuBJUbhmyWEI83nvuusLUaQx54wcIyDXLkQtZrXgUdza8WF8eTZ55IAActnAACqgPApCCBxQgBAQIkF9KucN3cPt8wKqv3NIKRYOouQbNyhHLIigzoa7O6gSxOv9c2+N6QEyVjSftYDJocwJg0jVFErrFxRQshlBsIg0ox4VhcqJ+wOHYuBr4VmBdOlDGE8GmBLMDRywoWWbhRz96Oe4fkkE2T2sKEmSQkhHMWQ4ICrl4PIoPWhxLORhB0+lj2YAAqoS7sRIeFErOQv+ii4j4e9yo1zAbKUMKRMthirl+vEyK9knLC4RTFy6f+Awlz4X80wJwTuf+-Dy3yPlnpmJkH8KU05nEBIx5ObNIPCU0OYJjcfMHSQWUEmbOTifSd8AAeVwBxXNS-R6Tim8En0EEgNaUEBgPYHgJVlXxewDFkCkDmBMFwlEkyEWDvkQHNFshbEQxpScW2X8H538AaRIEoB6DmzYjAA4PJgKwpnNXlUEAKnMASWM0NFDAkFgS5AyyRnZCG0sAfFAKO0kDIGwFnHFVaB7lcyEO6ACzbR6Q0K0PuEECzkEA4MoHlUUUPGoMWAyCWChDclGniSsDBBBVDHUDehMO0PwF0LFXNSXzm0GD7SBBozkAXiQlDGhDnkPFWDsHBBWBqB8Jh1MJ0Kzn8GERqR0KJE9CQNbStXULSPFUEQsLyKzBsLNCoVZGeg8jSFhGhDoQHWPGALsDmFoRcECn5wwHgCiCOxfzX0EEWDKFyGySQk0B0DcmGMZAcMk3kmfnrDHzUJxDAEGKIPpEoUehehBFwhPGUXRwkDUA+3cMsGrXr3fHWPdXZCZmLxUFLyRi5EQi62sETHSGqC2Uc05w-EwSuJAWehNh1HjDyg+LqGjGSBvDylSGqAtFhPy32SGUqR8yxU+T+L3CDCZgyFLn5BqFSDBWSABDvHmFsH5HhJlQxUFT81RL01byMBUU1V7ykQ3yUQDXowsG8m5mG1RHhJjRSibTRKOjsSkDUCKlH0SNEjKiwh2CqlUAWhTEO0xm+O5RcxXTGQFO9jsUp1oWI0rjHWqOrkHSqBhPhL-X5JpMjyOGFEQjvByAcDyEjBujUCZDjxg0yFGjqHhJUzVPNLX0tMZAyFWBM1HzbADW5FmlhMcGRCRktnhLq2K28yiyFWpNi19OTH-3ZHkjPDTxKH63R15mTAtEbBtC+Pekf0sXVMmXsBHhKGTEnQxNiNUBNmVxhJsHozrzAKVLLN5xzwrMpX2GYTqlrKNLWC5AhPmByRWC7w2V90h350eQ3F7PchsAnXHmKjUFjx-wQBT1mEUMjCKiDFkBnK11138ANyNz6JTKIOsAkBNiYS0HsDSENjcibCrnjATCOCbHvOzxh3YEXMIk331msBMG3z7xgRjBo3UATCTHPxLLIkv1u04Bvx7kXLkE0Fmj8hUAciemuhgQY0BKDA5GvisHZw7PeiwOgMtzwNzAQL-KtHKF7DTL9VkBwqKEEh5DmAZTPyqnVBYLYKsLWJ9I2L7HouWVUDqCekmhcIy2vHsGnS0FkFTBLN8M4H8MyMCIEsvPdUtCkBelZxsHkFpzkJ5GAsjLnmmlUMVOKM0L8KgF0OyJolyNzD-McBNlhGQjynZHZSmibF5HsGoKFmKgKi6KcCAA */
id: 'Modeling',
tsTypes: {} as import('./modelingMachine.typegen').Typegen0,
@ -481,6 +481,7 @@ export const modelingMachine = createMachine(
'animate after sketch',
'tear down client sketch',
'remove sketch grid',
'engineToClient cam sync direction',
],
entry: ['add axis n grid', 'conditionally equip line tool'],
@ -514,6 +515,8 @@ export const modelingMachine = createMachine(
internal: true,
},
},
entry: 'clientToEngine cam sync direction',
},
'animating to existing sketch': {
@ -524,6 +527,8 @@ export const modelingMachine = createMachine(
onDone: 'Sketch',
},
],
entry: 'clientToEngine cam sync direction',
},
},
@ -824,13 +829,13 @@ export const modelingMachine = createMachine(
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
if (args.event.which !== 1) return
const { intersection2d } = args
if (!intersection2d || !sketchPathToNode) return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchPathToNode) return
const { modifiedAst } = addStartProfileAt(
kclManager.ast,
sketchPathToNode,
[intersection2d.x, intersection2d.y]
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
)
await kclManager.updateAst(modifiedAst, false)
sceneEntitiesManager.removeIntersectionPlane()
@ -845,6 +850,12 @@ export const modelingMachine = createMachine(
// (note the orbit controls are always active though)
sceneInfra.resetMouseListeners()
},
'clientToEngine cam sync direction': () => {
sceneInfra.camControls.syncDirection = 'clientToEngine'
},
'engineToClient cam sync direction': () => {
sceneInfra.camControls.syncDirection = 'engineToClient'
},
},
// end actions
}

147
src/wasm-lib/Cargo.lock generated
View File

@ -246,7 +246,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -257,7 +257,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -574,9 +574,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.5.0"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f"
checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da"
dependencies = [
"clap_builder",
"clap_derive",
@ -584,9 +584,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.5.0"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99"
checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb"
dependencies = [
"anstream",
"anstyle",
@ -606,7 +606,7 @@ dependencies = [
"heck",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -856,7 +856,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -897,7 +897,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
"synstructure 0.13.0",
]
@ -949,7 +949,7 @@ dependencies = [
"regex",
"serde",
"serde_tokenstream",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -965,7 +965,7 @@ dependencies = [
"regex",
"serde",
"serde_tokenstream",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -977,7 +977,7 @@ dependencies = [
"diesel_table_macro_syntax",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -986,7 +986,7 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc5557efc453706fed5e4fa85006fe9817c224c3f480a34c7e5959fd700921c5"
dependencies = [
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -1036,7 +1036,7 @@ checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -1118,7 +1118,7 @@ checksum = "eecf8589574ce9b895052fa12d69af7a233f99e6107f5cb8dd1044f2a17bfdcb"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -1335,7 +1335,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -1404,9 +1404,9 @@ dependencies = [
[[package]]
name = "gif"
version = "0.12.0"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045"
checksum = "3fb2d69b19215e18bb912fa30f7ce15846e301408695e44e0ef719f1da9e19f2"
dependencies = [
"color_quant",
"weezl",
@ -1440,7 +1440,7 @@ dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -1460,13 +1460,17 @@ name = "grackle"
version = "0.1.0"
dependencies = [
"kcl-lib",
"kittycad",
"kittycad-execution-plan",
"kittycad-execution-plan-macros",
"kittycad-execution-plan-traits",
"kittycad-modeling-cmds",
"kittycad-modeling-session",
"pretty_assertions",
"serde_json",
"thiserror",
"tokio",
"uuid",
]
[[package]]
@ -1711,9 +1715,9 @@ dependencies = [
[[package]]
name = "image"
version = "0.24.8"
version = "0.24.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "034bbe799d1909622a74d1193aa50147769440040ff36cb2baa947609b0a4e23"
checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d"
dependencies = [
"bytemuck",
"byteorder",
@ -1912,7 +1916,7 @@ dependencies = [
"itertools 0.12.1",
"js-sys",
"kittycad",
"kittycad-execution-plan-macros 0.1.4 (git+https://github.com/KittyCAD/modeling-api?branch=main)",
"kittycad-execution-plan-macros",
"kittycad-execution-plan-traits",
"lazy_static",
"parse-display 0.9.0",
@ -1943,14 +1947,14 @@ dependencies = [
"pretty_assertions",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
name = "kittycad"
version = "0.2.54"
version = "0.2.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13958174d876353f429ea8230dc92fe86f164819cea2e51bbf22e01a4c2a496e"
checksum = "049c3881ffbe77bf1c3a968372a246ce906eceb79f61cd0bc5fa229bec3504cb"
dependencies = [
"anyhow",
"async-trait",
@ -1986,7 +1990,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan"
version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"bytes",
"insta",
@ -2004,30 +2008,18 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan-macros"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d31b689c944d00aadda2ef83d8422a6efff97e1be5654a61f9d95496f0c19e"
version = "0.1.6"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
]
[[package]]
name = "kittycad-execution-plan-macros"
version = "0.1.4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#632b75a0242400fa34373d7973b9149b0e08aa3f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
name = "kittycad-execution-plan-traits"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3ec8efd57b59697eb140b63c0ffe7db44fdfe5a55f14e45513411eba2280ba5"
version = "0.1.11"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"serde",
"thiserror",
@ -2036,8 +2028,8 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.1.18"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
version = "0.1.27"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"anyhow",
"chrono",
@ -2047,8 +2039,9 @@ dependencies = [
"enum-iterator-derive",
"euler",
"http 0.2.9",
"kittycad-execution-plan-macros 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
"kittycad-execution-plan-macros",
"kittycad-execution-plan-traits",
"kittycad-modeling-cmds-macros",
"kittycad-unit-conversion-derive",
"measurements",
"parse-display 0.8.2",
@ -2061,10 +2054,20 @@ dependencies = [
"webrtc",
]
[[package]]
name = "kittycad-modeling-cmds-macros"
version = "0.1.2"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.52",
]
[[package]]
name = "kittycad-modeling-session"
version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#08f05d91062380fe3a69f4baa1f1301532d31977"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#9c96767289139f03036c2ba40f889f974ca3e976"
dependencies = [
"futures",
"kittycad",
@ -2496,7 +2499,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -2653,7 +2656,7 @@ dependencies = [
"regex",
"regex-syntax 0.7.5",
"structmeta 0.2.0",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -2667,7 +2670,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.2",
"structmeta 0.3.0",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -2733,7 +2736,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3601,7 +3604,7 @@ checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3635,7 +3638,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3656,7 +3659,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3913,7 +3916,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive 0.2.0",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3925,7 +3928,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive 0.3.0",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3936,7 +3939,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -3947,7 +3950,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -4019,9 +4022,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.49"
version = "2.0.52"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915aea9e586f80826ee59f8453c1101f9d1c4b3964cd2460185ee8e299ada496"
checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
dependencies = [
"proc-macro2",
"quote",
@ -4054,7 +4057,7 @@ checksum = "285ba80e733fac80aa4270fbcdf83772a79b80aa35c97075320abfee4a915b06"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
"unicode-xid",
]
@ -4184,7 +4187,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -4291,7 +4294,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -4454,7 +4457,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -4482,7 +4485,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -4566,7 +4569,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
"termcolor",
]
@ -4813,7 +4816,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
"wasm-bindgen-shared",
]
@ -4848,7 +4851,7 @@ checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -5134,9 +5137,9 @@ dependencies = [
[[package]]
name = "weezl"
version = "0.1.7"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb"
checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "winapi"
@ -5466,7 +5469,7 @@ checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]
@ -5486,7 +5489,7 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.49",
"syn 2.0.52",
]
[[package]]

View File

@ -21,7 +21,7 @@ wasm-bindgen-futures = "0.4.41"
[dev-dependencies]
anyhow = "1"
image = "0.24.8"
image = "0.24.9"
kittycad = { workspace = true, default-features = true }
pretty_assertions = "1.4.0"
reqwest = { version = "0.11.24", default-features = false }
@ -58,11 +58,12 @@ members = [
]
[workspace.dependencies]
kittycad = { version = "0.2.54", default-features = false, features = ["js", "requests"] }
kittycad = { version = "0.2.58", default-features = false, features = ["js", "requests"] }
kittycad-execution-plan = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-traits = "0.1.10"
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-macros = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-execution-plan-traits = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-modeling-cmds = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
kittycad-modeling-session = { git = "https://github.com/KittyCAD/modeling-api", branch = "main" }
[[test]]
name = "executor"
@ -73,6 +74,9 @@ name = "modify"
path = "tests/modify/main.rs"
# Example: how to point modeling-api at a different repo (e.g. a branch or a local clone)
# [patch."https://github.com/KittyCAD/modeling-api"]
# kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
# kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }
#[patch."https://github.com/KittyCAD/modeling-api"]
#kittycad-execution-plan = { path = "../../../modeling-api/execution-plan" }
#kittycad-execution-plan-macros = { path = "../../../modeling-api/execution-plan-macros" }
#kittycad-execution-plan-traits = { path = "../../../modeling-api/execution-plan-traits" }
#kittycad-modeling-cmds = { path = "../../../modeling-api/modeling-cmds" }
#kittycad-modeling-session = { path = "../../../modeling-api/modeling-session" }

View File

@ -19,7 +19,7 @@ quote = "1"
regex = "1.10"
serde = { version = "1.0.193", features = ["derive"] }
serde_tokenstream = "0.2"
syn = { version = "2.0.49", features = ["full"] }
syn = { version = "2.0.52", features = ["full"] }
[dev-dependencies]
expectorate = "1.1.0"

View File

@ -7,11 +7,15 @@ description = "A new executor for KCL which compiles to Execution Plans"
[dependencies]
kcl-lib = { path = "../kcl" }
kittycad = { workspace = true }
kittycad-execution-plan = { workspace = true }
kittycad-execution-plan-traits = { workspace = true }
kittycad-execution-plan-macros = { workspace = true }
kittycad-modeling-cmds = { workspace = true }
kittycad-modeling-session = { workspace = true }
thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["macros", "rt"] }
uuid = "1.7"
[dev-dependencies]
pretty_assertions = "1"

View File

@ -103,7 +103,11 @@ impl BindingScope {
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
(
"startSketchAt".into(),
EpBinding::from(KclFunction::StartSketchAt(native_functions::StartSketchAt)),
EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)),
),
(
"lineTo".into(),
EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)),
),
]),
parent: None,

View File

@ -45,6 +45,13 @@ pub enum CompileError {
NoReturnStmt,
#[error("You used the %, which means \"substitute this argument for the value to the left in this |> pipeline\". But there is no such value, because you're not calling a pipeline.")]
NotInPipeline,
#[error("The function '{fn_name}' expects a parameter of type {expected} as argument number {arg_number} but you supplied {actual}")]
ArgWrongType {
fn_name: &'static str,
expected: &'static str,
actual: String,
arg_number: usize,
},
}
#[derive(Debug, thiserror::Error)]

View File

@ -252,6 +252,7 @@ impl Planner {
} = match callee {
KclFunction::Id(f) => f.call(&mut self.next_addr, args)?,
KclFunction::StartSketchAt(f) => f.call(&mut self.next_addr, args)?,
KclFunction::LineTo(f) => f.call(&mut self.next_addr, args)?,
KclFunction::Add(f) => f.call(&mut self.next_addr, args)?,
KclFunction::UserDefined(f) => {
let UserDefinedFunction {
@ -618,7 +619,8 @@ impl Eq for UserDefinedFunction {}
#[cfg_attr(test, derive(Eq, PartialEq))]
enum KclFunction {
Id(native_functions::Id),
StartSketchAt(native_functions::StartSketchAt),
StartSketchAt(native_functions::sketch::StartSketchAt),
LineTo(native_functions::sketch::LineTo),
Add(native_functions::Add),
UserDefined(UserDefinedFunction),
}

View File

@ -2,12 +2,13 @@
//! This includes some of the stdlib, e.g. `startSketchAt`.
//! But some other stdlib functions will be written in KCL.
use kcl_lib::std::sketch::PlaneData;
use kittycad_execution_plan::{BinaryArithmetic, Destination, Instruction};
use kittycad_execution_plan_traits::{Address, Value};
use kittycad_execution_plan_traits::Address;
use crate::{CompileError, EpBinding, EvalPlan};
pub mod sketch;
/// The identity function. Always returns its first input.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
@ -41,34 +42,6 @@ impl Callable for Id {
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct StartSketchAt;
impl Callable for StartSketchAt {
fn call(&self, next_addr: &mut Address, _args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
// Store the plane.
let plane = PlaneData::XY.into_parts();
instructions.push(Instruction::SetValue {
address: next_addr.offset_by(plane.len()),
value_parts: plane,
});
// TODO: Get the plane ID from global context.
// TODO: Send this command:
// 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()),
// },
// TODO: Send ModelingCmd::StartPath at the given point.
// TODO (maybe): Store the SketchGroup in KCEP memory.
todo!()
}
}
/// A test function that adds two numbers.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]

View File

@ -0,0 +1,7 @@
//! Native functions for sketching on the plane.
pub mod helpers;
pub mod stdlib_functions;
pub mod types;
pub use stdlib_functions::{LineTo, StartSketchAt};

View File

@ -0,0 +1,137 @@
use kittycad_execution_plan::{api_request::ApiRequest, Destination, Instruction};
use kittycad_execution_plan_traits::{Address, InMemory};
use kittycad_modeling_cmds::{id::ModelingCmdId, ModelingCmdEndpoint};
use crate::{binding_scope::EpBinding, error::CompileError};
/// Emit instructions for an API call with no parameters.
pub fn no_arg_api_call(instrs: &mut Vec<Instruction>, endpoint: ModelingCmdEndpoint, cmd_id: ModelingCmdId) {
instrs.push(Instruction::ApiRequest(ApiRequest {
endpoint,
store_response: None,
arguments: vec![],
cmd_id,
}))
}
/// Emit instructions for an API call with the given parameters.
/// The API parameters are stored in the EP memory stack.
/// So, they have to be pushed onto the stack in the right order,
/// i.e. the reverse order in which the API call's Rust struct defines the fields.
pub fn stack_api_call<const N: usize>(
instrs: &mut Vec<Instruction>,
endpoint: ModelingCmdEndpoint,
store_response: Option<Address>,
cmd_id: ModelingCmdId,
data: [Vec<kittycad_execution_plan_traits::Primitive>; N],
) {
let arguments = vec![InMemory::StackPop; data.len()];
instrs.extend(data.map(|data| Instruction::StackPush { data }));
instrs.push(Instruction::ApiRequest(ApiRequest {
endpoint,
store_response,
arguments,
cmd_id,
}))
}
pub fn single_binding(
b: EpBinding,
fn_name: &'static str,
expected: &'static str,
arg_number: usize,
) -> Result<Address, CompileError> {
match b {
EpBinding::Single(a) => Ok(a),
EpBinding::Sequence { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "array".to_owned(),
arg_number,
}),
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "object".to_owned(),
arg_number,
}),
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "function".to_owned(),
arg_number,
}),
}
}
pub fn sequence_binding(
b: EpBinding,
fn_name: &'static str,
expected: &'static str,
arg_number: usize,
) -> Result<Vec<EpBinding>, CompileError> {
match b {
EpBinding::Sequence { elements, .. } => Ok(elements),
EpBinding::Single(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "single".to_owned(),
arg_number,
}),
EpBinding::Map { .. } => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "object".to_owned(),
arg_number,
}),
EpBinding::Function(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "function".to_owned(),
arg_number,
}),
}
}
/// Extract a 2D point from an argument to a Cabble.
pub fn arg_point2d(
arg: EpBinding,
fn_name: &'static str,
instructions: &mut Vec<Instruction>,
next_addr: &mut Address,
arg_number: usize,
) -> Result<Address, CompileError> {
let expected = "2D point (array with length 2)";
let elements = sequence_binding(arg, "startSketchAt", "an array of length 2", arg_number)?;
if elements.len() != 2 {
return Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: format!("array of length {}", elements.len()),
arg_number: 0,
});
}
// KCL stores points as an array.
// KC API stores them as Rust objects laid flat out in memory.
let start = next_addr.offset_by(2);
let start_x = start;
let start_y = start + 1;
let start_z = start + 2;
instructions.extend([
Instruction::Copy {
source: single_binding(elements[0].clone(), "startSketchAt", "number", arg_number)?,
destination: Destination::Address(start_x),
length: 1,
},
Instruction::Copy {
source: single_binding(elements[1].clone(), "startSketchAt", "number", arg_number)?,
destination: Destination::Address(start_y),
length: 1,
},
Instruction::SetPrimitive {
address: start_z,
value: 0.0.into(),
},
]);
Ok(start)
}

View File

@ -0,0 +1,210 @@
use kittycad_execution_plan::{api_request::ApiRequest, Destination, Instruction};
use kittycad_execution_plan_traits::{Address, InMemory, Value};
use kittycad_modeling_cmds::{
shared::{Point3d, Point4d},
ModelingCmdEndpoint,
};
use uuid::Uuid;
use super::{
helpers::{arg_point2d, no_arg_api_call, single_binding, stack_api_call},
types::{Axes, BasePath, Plane, SketchGroup},
};
use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan};
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct LineTo;
impl Callable for LineTo {
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let fn_name = "lineTo";
// Get both required params.
let mut args_iter = args.into_iter();
let Some(to) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 0,
});
};
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 1,
});
};
// Check the type of both required params.
let to = arg_point2d(to, fn_name, &mut instructions, next_addr, 0)?;
let sg = single_binding(sketch_group, fn_name, "sketch group", 1)?;
let id = Uuid::new_v4();
let start_of_line = next_addr.offset(1);
let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
instructions.extend([
// Push the `to` 2D point onto the stack.
Instruction::Copy {
source: to,
length: 2,
destination: Destination::StackPush,
},
// Make it a 3D point.
Instruction::StackExtend { data: vec![0.0.into()] },
// Append the new path segment to memory.
// First comes its tag.
Instruction::SetPrimitive {
address: start_of_line,
value: "Line".to_owned().into(),
},
// Then its end
Instruction::StackPop {
destination: Some(start_of_line + 1),
},
// Then its `relative` field.
Instruction::SetPrimitive {
address: start_of_line + 1 + length_of_3d_point,
value: false.into(),
},
// Send the ExtendPath request
Instruction::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::ExtendPath,
store_response: None,
arguments: vec![
// Path ID
InMemory::Address(sg + SketchGroup::path_id_offset()),
// Segment
InMemory::Address(start_of_line),
],
cmd_id: id.into(),
}),
]);
// TODO: Create a new SketchGroup from the old one + add the new path, then store it.
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(Address::ZERO + 9999),
})
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct StartSketchAt;
impl Callable for StartSketchAt {
fn call(&self, next_addr: &mut Address, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
// First, before we send any API calls, let's validate the arguments to this function.
let mut args_iter = args.into_iter();
let Some(start) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: "startSketchAt".into(),
required: 1,
actual: 0,
});
};
let start_point = arg_point2d(start, "startSketchAt", &mut instructions, next_addr, 0)?;
let tag = match args_iter.next() {
None => None,
Some(b) => Some(single_binding(b, "startSketchAt", "a single string", 1)?),
};
// Define some constants:
let axes = Axes {
x: Point3d { x: 1.0, y: 0.0, z: 0.0 },
y: Point3d { x: 0.0, y: 1.0, z: 0.0 },
z: Point3d { x: 0.0, y: 0.0, z: 1.0 },
};
let origin = Point3d::default();
// Now the function can start.
// First API call: make the plane.
let plane_id = Uuid::new_v4();
stack_api_call(
&mut instructions,
ModelingCmdEndpoint::MakePlane,
None,
plane_id.into(),
[
Some(true).into_parts(), // hide
vec![false.into()], // clobber
vec![60.0.into()], // size
axes.y.into_parts(),
axes.x.into_parts(),
origin.into_parts(),
],
);
// Next, enter sketch mode.
stack_api_call(
&mut instructions,
ModelingCmdEndpoint::SketchModeEnable,
None,
Uuid::new_v4().into(),
[
Some(axes.z).into_parts(),
vec![false.into()], // animated
vec![false.into()], // ortho mode
vec![plane_id.into()],
],
);
// Then start a path
let path_id = Uuid::new_v4();
no_arg_api_call(&mut instructions, ModelingCmdEndpoint::StartPath, path_id.into());
// Move the path pen to the given point.
instructions.push(Instruction::StackPush {
data: vec![path_id.into()],
});
instructions.push(Instruction::ApiRequest(ApiRequest {
endpoint: ModelingCmdEndpoint::MovePathPen,
store_response: None,
arguments: vec![InMemory::StackPop, InMemory::Address(start_point)],
cmd_id: Uuid::new_v4().into(),
}));
// Starting a sketch creates a sketch group.
// Updating the sketch will update this sketch group later.
let sketch_group = SketchGroup {
id: path_id,
position: origin,
rotation: Point4d {
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0,
},
// TODO: Must copy the existing data (from the arguments to this KCL function)
// over these values after writing to memory.
path_first: BasePath {
from: Default::default(),
to: Default::default(),
name: Default::default(),
},
path_rest: Vec::new(),
on: super::types::SketchSurface::Plane(Plane {
id: plane_id,
value: super::types::PlaneType::XY,
origin,
axes,
}),
axes,
entity_id: Some(plane_id),
};
let sketch_group_primitives = sketch_group.clone().into_parts();
let sketch_group_addr = next_addr.offset_by(sketch_group_primitives.len());
instructions.push(Instruction::SetValue {
address: sketch_group_addr,
value_parts: sketch_group_primitives,
});
instructions.extend(sketch_group.set_base_path(sketch_group_addr, start_point, tag));
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(sketch_group_addr),
})
}
}

View File

@ -0,0 +1,142 @@
use kittycad_execution_plan::{Destination, Instruction};
use kittycad_execution_plan_macros::ExecutionPlanValue;
use kittycad_execution_plan_traits::{Address, Value};
use kittycad_modeling_cmds::shared::{Point2d, Point3d, Point4d};
use uuid::Uuid;
/// A sketch group is a collection of paths.
#[derive(Clone, ExecutionPlanValue)]
pub struct SketchGroup {
// NOTE to developers
// Do NOT reorder these fields without updating the _offset() methods below.
/// The id of the sketch group.
pub id: Uuid,
/// What the sketch is on (can be a plane or a face).
pub on: SketchSurface,
/// The position of the sketch group.
pub position: Point3d,
/// The rotation of the sketch group base plane.
pub rotation: Point4d,
/// The X, Y and Z axes of this sketch's base plane, in 3D space.
pub axes: Axes,
/// The plane id or face id of the sketch group.
pub entity_id: Option<Uuid>,
/// The base path.
pub path_first: BasePath,
/// Paths after the first path, if any.
pub path_rest: Vec<Path>,
}
impl SketchGroup {
/// Get the offset for the `id` field.
pub fn path_id_offset() -> usize {
0
}
pub fn set_base_path(&self, sketch_group: Address, start_point: Address, tag: Option<Address>) -> Vec<Instruction> {
let base_path_addr = sketch_group
+ self.id.into_parts().len()
+ self.on.into_parts().len()
+ self.position.into_parts().len()
+ self.rotation.into_parts().len()
+ self.axes.into_parts().len()
+ self.entity_id.into_parts().len()
+ self.entity_id.into_parts().len();
let mut out = vec![
// Copy over the `from` field.
Instruction::Copy {
source: start_point,
destination: Destination::Address(base_path_addr),
length: 1,
},
// Copy over the `to` field.
Instruction::Copy {
source: start_point,
destination: Destination::Address(base_path_addr + self.path_first.from.into_parts().len()),
length: 1,
},
];
if let Some(tag) = tag {
// Copy over the `name` field.
out.push(Instruction::Copy {
source: tag,
destination: Destination::Address(
base_path_addr + self.path_first.from.into_parts().len() + self.path_first.to.into_parts().len(),
),
length: 1,
});
}
out
}
}
/// The X, Y and Z axes.
#[derive(Clone, Copy, ExecutionPlanValue)]
pub struct Axes {
pub x: Point3d,
pub y: Point3d,
pub z: Point3d,
}
#[derive(Clone, ExecutionPlanValue)]
pub struct BasePath {
pub from: Point2d<f64>,
pub to: Point2d<f64>,
pub name: String,
}
/// A path.
#[derive(Clone, ExecutionPlanValue)]
pub enum Path {
/// A path that goes to a point.
ToPoint { base: BasePath },
/// A arc that is tangential to the last path segment that goes to a point
TangentialArcTo {
base: BasePath,
/// the arc's center
center: Point2d,
/// arc's direction
ccw: bool,
},
/// A path that is horizontal.
Horizontal {
base: BasePath,
/// The x coordinate.
x: f64,
},
/// An angled line to.
AngledLineTo {
base: BasePath,
/// The x coordinate.
x: Option<f64>,
/// The y coordinate.
y: Option<f64>,
},
/// A base path.
Base { base: BasePath },
}
#[derive(Clone, Copy, ExecutionPlanValue)]
pub enum SketchSurface {
Plane(Plane),
}
/// A plane.
#[derive(Clone, Copy, ExecutionPlanValue)]
pub struct Plane {
/// The id of the plane.
pub id: Uuid,
// The code for the plane either a string or custom.
pub value: PlaneType,
/// Origin of the plane.
pub origin: Point3d,
pub axes: Axes,
}
/// Type for a plane.
#[derive(Clone, Copy, ExecutionPlanValue)]
pub enum PlaneType {
XY,
XZ,
YZ,
Custom,
}

View File

@ -1,7 +1,8 @@
use std::collections::HashMap;
use std::{collections::HashMap, env};
use ep::{Destination, UnaryArithmetic};
use ept::{ListHeader, ObjectHeader};
use kittycad_modeling_session::SessionBuilder;
use pretty_assertions::assert_eq;
use super::*;
@ -1044,6 +1045,86 @@ fn store_object_with_array_property() {
)
}
#[tokio::test]
async fn stdlib_cube_partial() {
let program = r#"
let cube = startSketchAt([0.0, 0.0])
|> lineTo([4.0, 0.0], %)
"#;
let (_plan, _scope) = must_plan(program);
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let client = test_client().await;
let _mem = crate::execute(ast, Some(client)).await.unwrap();
// use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat, ModelingCmd};
// let out = client
// .run_command(
// uuid::Uuid::new_v4().into(),
// each_cmd::TakeSnapshot {
// format: ImageFormat::Png,
// },
// )
// .await
// .unwrap();
// let out = match out {
// OkModelingCmdResponse::TakeSnapshot(b) => b,
// other => panic!("wrong output: {other:?}"),
// };
// let out: Vec<u8> = out.contents.into();
}
async fn test_client() -> Session {
let kittycad_api_token = env::var("KITTYCAD_API_TOKEN").expect("You must set $KITTYCAD_API_TOKEN");
let kittycad_api_client = kittycad::Client::new(kittycad_api_token);
let session_builder = SessionBuilder {
client: kittycad_api_client,
fps: Some(10),
unlocked_framerate: Some(false),
video_res_height: Some(720),
video_res_width: Some(1280),
buffer_reqs: None,
await_response_timeout: None,
};
match Session::start(session_builder).await {
Err(e) => match e {
kittycad::types::error::Error::InvalidRequest(s) => panic!("Request did not meet requirements {s}"),
kittycad::types::error::Error::CommunicationError(e) => {
panic!(" A server error either due to the data, or with the connection: {e}")
}
kittycad::types::error::Error::RequestError(e) => panic!("Could not build request: {e}"),
kittycad::types::error::Error::SerdeError { error, status } => {
panic!("Serde error (HTTP {status}): {error}")
}
kittycad::types::error::Error::InvalidResponsePayload { error, response } => {
panic!("Invalid response payload. Error {error}, response {response:?}")
}
kittycad::types::error::Error::Server { body, status } => panic!("Server error (HTTP {status}): {body}"),
kittycad::types::error::Error::UnexpectedResponse(resp) => {
let status = resp.status();
let url = resp.url().to_owned();
match resp.text().await {
Ok(body) => panic!(
"Unexpected response from KittyCAD API.
URL:{url}
HTTP {status}
---Body----
{body}"
),
Err(e) => panic!(
"Unexpected response from KittyCAD API.
URL:{url}
HTTP {status}
---Body could not be read, the error is----
{e}"
),
}
}
},
Ok(x) => x,
}
}
#[ignore = "haven't done API calls or stdlib yet"]
#[test]
fn stdlib_api_calls() {

View File

@ -15,7 +15,7 @@ databake = "0.1.7"
kcl-lib = { path = "../kcl" }
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.49", features = ["full"] }
syn = { version = "2.0.52", features = ["full"] }
[dev-dependencies]
pretty_assertions = "1.4.0"

View File

@ -14,7 +14,7 @@ keywords = ["kcl", "KittyCAD", "CAD"]
anyhow = { version = "1.0.79", features = ["backtrace"] }
async-recursion = "1.0.5"
async-trait = "0.1.77"
clap = { version = "4.5.0", features = ["cargo", "derive", "env", "unicode"], optional = true }
clap = { version = "4.5.1", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.8" }

View File

@ -96,7 +96,19 @@ impl Program {
let custom_white_space_or_comment = match self.non_code_meta.non_code_nodes.get(&index) {
Some(noncodes) => noncodes
.iter()
.map(|custom_white_space_or_comment| custom_white_space_or_comment.format(&indentation))
.enumerate()
.map(|(i, custom_white_space_or_comment)| {
let formatted = custom_white_space_or_comment.format(&indentation);
if i == 0 && !formatted.trim().is_empty() {
if let NonCodeValue::BlockComment { .. } = custom_white_space_or_comment.value {
format!("\n{}", formatted)
} else {
formatted
}
} else {
formatted
}
})
.collect::<String>(),
None => String::new(),
};
@ -159,6 +171,35 @@ impl Program {
}
}
/// Returns a non code meta that includes the given character position.
pub fn get_non_code_meta_for_position(&self, pos: usize) -> Option<&NonCodeMeta> {
// Check if its in the body.
if self.non_code_meta.contains(pos) {
return Some(&self.non_code_meta);
}
let Some(item) = self.get_body_item_for_position(pos) else {
return None;
};
// Recurse over the item.
let value = match item {
BodyItem::ExpressionStatement(expression_statement) => Some(&expression_statement.expression),
BodyItem::VariableDeclaration(variable_declaration) => variable_declaration.get_value_for_position(pos),
BodyItem::ReturnStatement(return_statement) => Some(&return_statement.argument),
};
// Check if the value's non code meta contains the position.
if let Some(value) = value {
if let Some(non_code_meta) = value.get_non_code_meta() {
if non_code_meta.contains(pos) {
return Some(non_code_meta);
}
}
}
None
}
/// Returns all the lsp symbols in the program.
pub fn get_lsp_symbols(&self, code: &str) -> Vec<DocumentSymbol> {
let mut symbols = vec![];
@ -431,6 +472,24 @@ impl Value {
}
}
// Get the non code meta for the value.
pub fn get_non_code_meta(&self) -> Option<&NonCodeMeta> {
match self {
Value::BinaryExpression(_bin_exp) => None,
Value::ArrayExpression(_array_exp) => None,
Value::ObjectExpression(_obj_exp) => None,
Value::MemberExpression(_mem_exp) => None,
Value::Literal(_literal) => None,
Value::FunctionExpression(_func_exp) => None,
Value::CallExpression(_call_exp) => None,
Value::Identifier(_ident) => None,
Value::PipeExpression(pipe_exp) => Some(&pipe_exp.non_code_meta),
Value::UnaryExpression(_unary_exp) => None,
Value::PipeSubstitution(_pipe_substitution) => None,
Value::None(_none) => None,
}
}
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Value) {
if source_range == self.clone().into() {
*self = new_value;
@ -736,6 +795,10 @@ pub struct NonCodeNode {
}
impl NonCodeNode {
pub fn contains(&self, pos: usize) -> bool {
self.start <= pos && pos <= self.end
}
pub fn value(&self) -> String {
match &self.value {
NonCodeValue::InlineComment { value, style: _ } => value.clone(),
@ -755,18 +818,27 @@ impl NonCodeNode {
value,
style: CommentStyle::Block,
} => format!(" /* {} */", value),
NonCodeValue::BlockComment { value, style } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n" };
match style {
CommentStyle::Block => format!("{}{}/* {} */", add_start_new_line, indentation, value),
CommentStyle::Line => format!("{}{}// {}\n", add_start_new_line, indentation, value),
NonCodeValue::BlockComment { value, style } => match style {
CommentStyle::Block => format!("{}/* {} */", indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}//\n", indentation)
} else {
format!("{}// {}\n", indentation, value.trim())
}
}
}
},
NonCodeValue::NewLineBlockComment { value, style } => {
let add_start_new_line = if self.start == 0 { "" } else { "\n\n" };
match style {
CommentStyle::Block => format!("{}{}/* {} */\n", add_start_new_line, indentation, value),
CommentStyle::Line => format!("{}{}// {}\n", add_start_new_line, indentation, value),
CommentStyle::Line => {
if value.trim().is_empty() {
format!("{}{}//\n", add_start_new_line, indentation)
} else {
format!("{}{}// {}\n", add_start_new_line, indentation, value.trim())
}
}
}
}
NonCodeValue::NewLine => "\n\n".to_string(),
@ -863,6 +935,16 @@ impl NonCodeMeta {
pub fn insert(&mut self, i: usize, new: NonCodeNode) {
self.non_code_nodes.entry(i).or_default().push(new);
}
pub fn contains(&self, pos: usize) -> bool {
if self.start.iter().any(|node| node.contains(pos)) {
return true;
}
self.non_code_nodes
.iter()
.any(|(_, nodes)| nodes.iter().any(|node| node.contains(pos)))
}
}
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
@ -2533,7 +2615,13 @@ impl PipeExpression {
let non_code_meta = self.non_code_meta.clone();
if let Some(non_code_meta_value) = non_code_meta.non_code_nodes.get(&index) {
for val in non_code_meta_value {
s += val.format(&indentation).trim_end_matches('\n')
let formatted = val.format(&indentation).trim_end_matches('\n').to_string();
if let NonCodeValue::BlockComment { .. } = val.value {
s += "\n";
s += &formatted;
} else {
s += &formatted;
}
}
}
@ -3003,8 +3091,7 @@ let baz = {a: 1, b: "thing"}
fn ghi = (x) => {
return x
}
show(part001)"#;
"#;
let tokens = crate::token::lexer(code);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
@ -3106,6 +3193,109 @@ show(part001)"#;
"#
);
}
#[test]
fn test_recast_comment_under_variable() {
let some_program_string = r#"const key = 'c'
// this is also a comment
const thing = 'foo'
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"const key = 'c'
// this is also a comment
const thing = 'foo'
"#
);
}
#[test]
fn test_recast_multiline_comment_start_file() {
let some_program_string = r#"// hello world
// I am a comment
const key = 'c'
// this is also a comment
// hello
const thing = 'foo'
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"// hello world
// I am a comment
const key = 'c'
// this is also a comment
// hello
const thing = 'foo'
"#
);
}
#[test]
fn test_recast_empty_comment() {
let some_program_string = r#"// hello world
//
// I am a comment
const key = 'c'
//
// I am a comment
const thing = 'c'
const foo = 'bar' //
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"// hello world
//
// I am a comment
const key = 'c'
//
// I am a comment
const thing = 'c'
const foo = 'bar' //
"#
);
}
#[test]
fn test_recast_multiline_comment_under_variable() {
let some_program_string = r#"const key = 'c'
// this is also a comment
// hello
const thing = 'foo'
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"const key = 'c'
// this is also a comment
// hello
const thing = 'foo'
"#
);
}
#[test]
fn test_recast_comment_at_start() {
let test_program = r#"
@ -3181,9 +3371,7 @@ const mySk1 = startSketchOn('XY')
offset: -1.35,
intersectTag: 'seg01'
}, %)
|> line([-0.42, -1.72], %)
show(part001)"#;
|> line([-0.42, -1.72], %)"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
@ -3332,8 +3520,7 @@ let baz = {a: 1, part001: "thing"}
fn ghi = (part001) => {
return part001
}
show(part001)"#;
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let mut program = parser.ast().unwrap();
@ -3355,8 +3542,6 @@ let baz = { a: 1, part001: "thing" }
fn ghi = (part001) => {
return part001
}
show(mySuperCoolPart)
"#
);
}
@ -3381,6 +3566,97 @@ show(mySuperCoolPart)
);
}
#[test]
fn test_recast_trailing_comma() {
let some_program_string = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> arc({
radius: 1,
angle_start: 0,
angle_end: 180,
}, %)"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> arc({
radius: 1,
angle_start: 0,
angle_end: 180
}, %)
"#
);
}
#[test]
fn test_ast_get_non_code_node() {
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
const h = 30
// st
const cylinder = startSketchOn('-XZ')
|> startProfileAt([50, 0], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: r
}, %)
|> extrude(h, %)
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let value = program.get_non_code_meta_for_position(50);
assert!(value.is_some());
}
#[test]
fn test_ast_get_non_code_node_pipe() {
let some_program_string = r#"const r = 20 / pow(pi(), 1 / 3)
const h = 30
// st
const cylinder = startSketchOn('-XZ')
|> startProfileAt([50, 0], %)
// comment
|> arc({
angle_end: 360,
angle_start: 0,
radius: r
}, %)
|> extrude(h, %)
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let value = program.get_non_code_meta_for_position(124);
assert!(value.is_some());
}
#[test]
fn test_ast_get_non_code_node_inline_comment() {
let some_program_string = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> xLine(5, %) // lin
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let value = program.get_non_code_meta_for_position(86);
assert!(value.is_some());
}
#[test]
fn test_recast_negative_var() {
let some_program_string = r#"const w = 20
@ -3394,8 +3670,7 @@ const firstExtrude = startSketchOn('XY')
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
@ -3414,8 +3689,48 @@ const firstExtrude = startSketchOn('XY')
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
"#
);
}
show(firstExtrude)
#[test]
fn test_recast_multiline_comment() {
let some_program_string = r#"const w = 20
const l = 8
const h = 10
// This is my comment
// It has multiple lines
// And it's really long
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
let program = parser.ast().unwrap();
let recasted = program.recast(&Default::default(), 0);
assert_eq!(
recasted,
r#"const w = 20
const l = 8
const h = 10
// This is my comment
// It has multiple lines
// And it's really long
const firstExtrude = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, l], %)
|> line([w, 0], %)
|> line([0, -l], %)
|> close(%)
|> extrude(h, %)
"#
);
}

View File

@ -566,17 +566,4 @@ mod tests {
}
);
}
#[test]
fn test_deserialize_function_show() {
let some_function_string = r#"{"type":"StdLib","func":{"name":"show","summary":"","description":"","tags":[],"returnValue":{"type":"","required":false,"name":"","schema":{}},"args":[],"unpublished":false,"deprecated":false}}"#;
let some_function: crate::ast::types::Function = serde_json::from_str(some_function_string).unwrap();
assert_eq!(
some_function,
crate::ast::types::Function::StdLib {
func: Box::new(crate::std::Show),
}
);
}
}

View File

@ -1,7 +1,7 @@
//! Functions for setting up our WebSocket and WebRTC connections for communications with the
//! engine.
use std::sync::Arc;
use std::sync::{Arc, Mutex};
use anyhow::{anyhow, Result};
use dashmap::DashMap;
@ -15,6 +15,12 @@ use crate::{
errors::{KclError, KclErrorDetails},
};
#[derive(Debug, PartialEq)]
enum SocketHealth {
Active,
Inactive,
}
type WebSocketTcpWrite = futures::stream::SplitSink<tokio_tungstenite::WebSocketStream<reqwest::Upgraded>, WsMsg>;
#[derive(Debug, Clone)]
#[allow(dead_code)] // for the TcpReadHandle
@ -22,6 +28,7 @@ pub struct EngineConnection {
engine_req_tx: mpsc::Sender<ToEngineReq>,
responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>>,
tcp_read_handle: Arc<TcpReadHandle>,
socket_health: Arc<Mutex<SocketHealth>>,
}
pub struct TcpRead {
@ -119,7 +126,9 @@ impl EngineConnection {
let responses: Arc<DashMap<uuid::Uuid, WebSocketResponse>> = Arc::new(DashMap::new());
let responses_clone = responses.clone();
let socket_health = Arc::new(Mutex::new(SocketHealth::Active));
let socket_health_tcp_read = socket_health.clone();
let tcp_read_handle = tokio::spawn(async move {
// Get Websocket messages from API server
loop {
@ -131,6 +140,7 @@ impl EngineConnection {
}
Err(e) => {
println!("got ws error: {:?}", e);
*socket_health_tcp_read.lock().unwrap() = SocketHealth::Inactive;
return Err(e);
}
}
@ -143,6 +153,7 @@ impl EngineConnection {
handle: Arc::new(tcp_read_handle),
}),
responses,
socket_health,
})
}
}
@ -192,6 +203,14 @@ impl EngineManager for EngineConnection {
// Wait for the response.
let current_time = std::time::Instant::now();
while current_time.elapsed().as_secs() < 60 {
if let Ok(guard) = self.socket_health.lock() {
if *guard == SocketHealth::Inactive {
return Err(KclError::Engine(KclErrorDetails {
message: "Modeling command failed: websocket closed early".to_string(),
source_ranges: vec![source_range],
}));
}
}
// We pop off the responses to cleanup our mappings.
if let Some((_, resp)) = self.responses.remove(&id) {
return if let Some(data) = &resp.resp {

View File

@ -1017,7 +1017,7 @@ impl ExecutorContext {
pub async fn execute(
program: crate::ast::types::Program,
memory: &mut ProgramMemory,
options: BodyType,
_options: BodyType,
ctx: &ExecutorContext,
) -> Result<ProgramMemory, KclError> {
// Before we even start executing the program, set the units.
@ -1073,24 +1073,11 @@ pub async fn execute(
_ => (),
}
}
let _show_fn = Box::new(crate::std::Show);
match ctx.stdlib.get_either(&call_expr.callee.name) {
FunctionKind::Core(func) => {
use crate::docs::StdLibFn;
if func.name() == _show_fn.name() {
if options != BodyType::Root {
return Err(KclError::Semantic(KclErrorDetails {
message: "Cannot call show outside of a root".to_string(),
source_ranges: vec![call_expr.into()],
}));
}
memory.return_ = Some(ProgramReturn::Arguments(call_expr.arguments.clone()));
} else {
let args = crate::std::Args::new(args, call_expr.into(), ctx.clone());
let result = func.std_lib_fn()(args).await?;
memory.return_ = Some(ProgramReturn::Value(result));
}
let args = crate::std::Args::new(args, call_expr.into(), ctx.clone());
let result = func.std_lib_fn()(args).await?;
memory.return_ = Some(ProgramReturn::Value(result));
}
FunctionKind::Std(func) => {
let mut newmem = memory.clone();
@ -1352,8 +1339,7 @@ const newVar = myVar + 1"#;
offset: {},
tag: "yo2"
}}, %)
const intersect = segEndX('yo2', part001)
show(part001)"#,
const intersect = segEndX('yo2', part001)"#,
offset
)
};
@ -1399,8 +1385,7 @@ const part001 = startSketchOn('XY')
|> angledLine([ghi(2), 3.04], %)
|> angledLine([jkl(yo) + 2, 3.05], %)
|> close(%)
const yo2 = hmm([identifierGuy + 5])
show(part001)"#;
const yo2 = hmm([identifierGuy + 5])"#;
parse_execute(ast).await.unwrap();
}
@ -1415,8 +1400,7 @@ const part001 = startSketchOn('XY')
min(segLen('seg01', %), myVar),
-legLen(segLen('seg01', %), myVar)
], %)
show(part001)"#;
"#;
parse_execute(ast).await.unwrap();
}
@ -1431,8 +1415,7 @@ const part001 = startSketchOn('XY')
min(segLen('seg01', %), myVar),
legLen(segLen('seg01', %), myVar)
], %)
show(part001)"#;
"#;
parse_execute(ast).await.unwrap();
}
@ -1454,8 +1437,7 @@ const part001 = startSketchOn('XY')
|> xLine(3.84, %) // selection-range-7ish-before-this
const variableBelowShouldNotBeIncluded = 3
show(part001)"#;
"#;
parse_execute(ast).await.unwrap();
}
@ -1476,9 +1458,7 @@ const firstExtrude = startSketchOn('XY')
|> line([w, 0], %)
|> line([0, thing()], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
|> extrude(h, %)"#;
parse_execute(ast).await.unwrap();
}
@ -1499,9 +1479,7 @@ const firstExtrude = startSketchOn('XY')
|> line([w, 0], %)
|> line([0, thing(8)], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
|> extrude(h, %)"#;
parse_execute(ast).await.unwrap();
}
@ -1522,9 +1500,7 @@ const firstExtrude = startSketchOn('XY')
|> line([w, 0], %)
|> line(thing(8), %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
|> extrude(h, %)"#;
parse_execute(ast).await.unwrap();
}
@ -1549,9 +1525,7 @@ const firstExtrude = startSketchOn('XY')
|> line([w, 0], %)
|> line([0, thing(8)], %)
|> close(%)
|> extrude(h, %)
show(firstExtrude)"#;
|> extrude(h, %)"#;
parse_execute(ast).await.unwrap();
}
@ -1570,9 +1544,7 @@ show(firstExtrude)"#;
return myBox
}
const fnBox = box(3, 6, 10)
show(fnBox)"#;
const fnBox = box(3, 6, 10)"#;
parse_execute(ast).await.unwrap();
}
@ -1592,8 +1564,6 @@ show(fnBox)"#;
}
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
show(thisBox)
"#;
parse_execute(ast).await.unwrap();
}
@ -1613,8 +1583,6 @@ show(thisBox)
}
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
show(thisBox)
"#;
parse_execute(ast).await.unwrap();
}
@ -1634,8 +1602,6 @@ show(thisBox)
}
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
show(thisBox)
"#;
parse_execute(ast).await.unwrap();
}
@ -1657,7 +1623,6 @@ let myBox = startSketchOn('XY')
for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
const thisBox = box(var)
show(thisBox)
}"#;
parse_execute(ast).await.unwrap();
@ -1681,7 +1646,6 @@ for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h:
for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
const thisBox = box(var[0], var[1], var[2], var[3])
show(thisBox)
}"#;
parse_execute(ast).await.unwrap();
@ -1703,7 +1667,6 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
const thisBox = box([[0,0], 6, 10, 3])
show(thisBox)
"#;
parse_execute(ast).await.unwrap();
}
@ -1820,7 +1783,6 @@ const bracket = startSketchOn('XY')
|> line([0, -1 * leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
"#;
parse_execute(ast).await.unwrap();
}
@ -1845,7 +1807,6 @@ const bracket = startSketchOn('XY')
|> line([0, -1 * leg1 + thickness], %)
|> close(%)
|> extrude(width, %)
show(bracket)
"#;
parse_execute(ast).await.unwrap();
}

View File

@ -17,14 +17,13 @@ use tower_lsp::{
DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FullDocumentDiagnosticReport,
Hover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
InitializedParams, InlayHint, InlayHintParams, InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf,
ParameterInformation, ParameterLabel, Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams,
RenameParams, SemanticToken, SemanticTokenType, SemanticTokens, SemanticTokensFullOptions,
SemanticTokensLegend, SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions,
SemanticTokensResult, SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp,
SignatureHelpOptions, SignatureHelpParams, SignatureInformation, StaticRegistrationOptions, TextDocumentItem,
TextDocumentRegistrationOptions, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
TextEdit, WorkDoneProgressOptions, WorkspaceEdit, WorkspaceFoldersServerCapabilities,
WorkspaceServerCapabilities,
Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken,
SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
},
Client, LanguageServer,
};
@ -636,8 +635,9 @@ impl LanguageServer for Backend {
/// Get completions from our stdlib.
pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMap<String, CompletionItem>> {
let mut completions = HashMap::new();
let combined = stdlib.combined();
for internal_fn in stdlib.fns.values() {
for internal_fn in combined.values() {
completions.insert(internal_fn.name(), internal_fn.to_completion_item());
}
@ -652,32 +652,12 @@ pub fn get_completions_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMa
/// Get signatures from our stdlib.
pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib) -> Result<HashMap<String, SignatureHelp>> {
let mut signatures = HashMap::new();
let combined = stdlib.combined();
for internal_fn in stdlib.fns.values() {
for internal_fn in combined.values() {
signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
}
let show = SignatureHelp {
signatures: vec![SignatureInformation {
label: "show".to_string(),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::PlainText,
value: "Show a model.".to_string(),
})),
parameters: Some(vec![ParameterInformation {
label: ParameterLabel::Simple("sg: SketchGroup".to_string()),
documentation: Some(Documentation::MarkupContent(MarkupContent {
kind: MarkupKind::PlainText,
value: "A sketch group.".to_string(),
})),
}]),
active_parameter: None,
}],
active_signature: Some(0),
active_parameter: None,
};
signatures.insert("show".to_string(), show);
Ok(signatures)
}

View File

@ -440,6 +440,7 @@ fn object(i: TokenSlice) -> PResult<ObjectExpression> {
"a comma-separated list of key-value pairs, e.g. 'height: 4, width: 3'",
))
.parse_next(i)?;
ignore_trailing_comma(i);
ignore_whitespace(i);
let end = close_brace(i)?.end;
Ok(ObjectExpression { start, end, properties })
@ -977,6 +978,11 @@ fn ignore_whitespace(i: TokenSlice) {
let _: PResult<()> = repeat(0.., whitespace).parse_next(i);
}
// A helper function to ignore a trailing comma.
fn ignore_trailing_comma(i: TokenSlice) {
let _ = opt(comma).parse_next(i);
}
/// Matches at least 1 whitespace.
fn require_whitespace(i: TokenSlice) -> PResult<()> {
repeat(1.., whitespace).parse_next(i)
@ -1903,7 +1909,6 @@ const mySk1 = startSketchAt([0, 0])"#;
let test_program = r#"startSketchAt([0, 0])
|> lineTo([0, -0], %) // MoveRelative
show(svg)
"#;
let tokens = crate::token::lexer(test_program);
let mut slice = &tokens[..];
@ -2233,8 +2238,6 @@ const firstExtrude = startSketchOn('XY')
|> close(%)
|> extrude(2, %)
show(firstExtrude)
const secondExtrude = startSketchOn('XY')
|> startProfileAt([0,0], %)
|",
@ -2718,9 +2721,7 @@ const b2 = cube([3,3], 4)
const pt1 = b1[0]
const pt2 = b2[0]
show(b1)
show(b2)"#;
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
parser.ast().unwrap();
@ -2749,7 +2750,7 @@ let other_thing = 2 * cos(3)"#;
return myBox
}
let myBox = box([0,0], -3, -16, -10)
show(myBox)"#;
"#;
let tokens = crate::token::lexer(some_program_string);
let parser = crate::parser::Parser::new(tokens);
parser.ast().unwrap();

View File

@ -4,18 +4,18 @@ expression: actual
---
{
"start": 0,
"end": 90,
"end": 59,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 0,
"end": 74,
"end": 58,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 74,
"end": 58,
"id": {
"type": "Identifier",
"start": 6,
@ -26,47 +26,47 @@ expression: actual
"type": "PipeExpression",
"type": "PipeExpression",
"start": 17,
"end": 74,
"end": 58,
"body": [
{
"type": "CallExpression",
"type": "CallExpression",
"start": 17,
"end": 56,
"end": 40,
"callee": {
"type": "Identifier",
"start": 17,
"end": 39,
"name": "unstable_stdlib_circle"
"end": 23,
"name": "circle"
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 40,
"end": 44,
"start": 24,
"end": 28,
"value": "XY",
"raw": "'XY'"
},
{
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 46,
"end": 51,
"start": 30,
"end": 35,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 47,
"end": 48,
"start": 31,
"end": 32,
"value": 0,
"raw": "0"
},
{
"type": "Literal",
"type": "Literal",
"start": 49,
"end": 50,
"start": 33,
"end": 34,
"value": 0,
"raw": "0"
}
@ -75,8 +75,8 @@ expression: actual
{
"type": "Literal",
"type": "Literal",
"start": 53,
"end": 55,
"start": 37,
"end": 39,
"value": 22,
"raw": "22"
}
@ -86,28 +86,28 @@ expression: actual
{
"type": "CallExpression",
"type": "CallExpression",
"start": 60,
"end": 74,
"start": 44,
"end": 58,
"callee": {
"type": "Identifier",
"start": 60,
"end": 67,
"start": 44,
"end": 51,
"name": "extrude"
},
"arguments": [
{
"type": "Literal",
"type": "Literal",
"start": 68,
"end": 70,
"start": 52,
"end": 54,
"value": 14,
"raw": "14"
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 72,
"end": 73
"start": 56,
"end": 57
}
],
"optional": false
@ -121,34 +121,6 @@ expression: actual
}
],
"kind": "const"
},
{
"type": "ExpressionStatement",
"type": "ExpressionStatement",
"start": 75,
"end": 89,
"expression": {
"type": "CallExpression",
"type": "CallExpression",
"start": 75,
"end": 89,
"callee": {
"type": "Identifier",
"start": 75,
"end": 79,
"name": "show"
},
"arguments": [
{
"type": "Identifier",
"type": "Identifier",
"start": 80,
"end": 88,
"name": "cylinder"
}
],
"optional": false
}
}
],
"nonCodeMeta": {

View File

@ -110,6 +110,9 @@ impl From<ImportFormat> for kittycad::types::InputFormat {
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
/// Otherwise you can specify the unit by passing in the options parameter.
/// If you import a gltf file, we will try to find the bin file and import it as well.
///
/// Import paths are relative to the current project directory. This only works in the desktop app
/// not in browser.
pub async fn import(args: Args) -> Result<MemoryItem, KclError> {
let (file_path, options): (String, Option<ImportFormat>) = args.get_import_data()?;
@ -121,6 +124,9 @@ pub async fn import(args: Args) -> Result<MemoryItem, KclError> {
/// For formats lacking unit data (STL, OBJ, PLY), the default import unit is millimeters.
/// Otherwise you can specify the unit by passing in the options parameter.
/// If you import a gltf file, we will try to find the bin file and import it as well.
///
/// Import paths are relative to the current project directory. This only works in the desktop app
/// not in browser.
#[stdlib {
name = "import",
}]

View File

@ -11,6 +11,9 @@ pub trait KclStdLibFn: StdLibFn {
fn kcl_clone_box(&self) -> Box<dyn KclStdLibFn>;
fn function(&self) -> &FunctionExpression;
fn program(&self) -> &Program;
fn std_lib(&self) -> Box<dyn StdLibFn> {
self.clone_box()
}
}
impl ts_rs::TS for dyn KclStdLibFn {

View File

@ -37,7 +37,6 @@ pub type FnMap = HashMap<String, StdFn>;
lazy_static! {
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
Box::new(Show),
Box::new(LegLen),
Box::new(LegAngX),
Box::new(LegAngY),
@ -133,6 +132,15 @@ impl StdLib {
Self { fns, kcl_fns }
}
// Get the combined hashmaps.
pub fn combined(&self) -> HashMap<String, Box<dyn StdLibFn>> {
let mut combined = self.fns.clone();
for (k, v) in self.kcl_fns.clone() {
combined.insert(k, v.std_lib());
}
combined
}
pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
self.fns.get(name).cloned()
}
@ -705,21 +713,6 @@ impl Args {
}
}
/// Render a model.
// This never actually gets called so this is fine.
pub async fn show<'a>(args: Args) -> Result<MemoryItem, KclError> {
let sketch_group = args.get_sketch_group()?;
inner_show(sketch_group);
args.make_user_val_from_f64(0.0)
}
/// Render a model.
#[stdlib {
name = "show",
}]
fn inner_show(_sketch: Box<SketchGroup>) {}
/// Returns the length of the given leg.
pub async fn leg_length(args: Args) -> Result<MemoryItem, KclError> {
let (hypotenuse, leg) = args.get_hypotenuse_leg()?;
@ -789,6 +782,7 @@ mod tests {
#[test]
fn test_generate_stdlib_markdown_docs() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let mut buf = String::new();
buf.push_str("<!--- DO NOT EDIT THIS FILE. IT IS AUTOMATICALLY GENERATED. -->\n\n");
@ -800,8 +794,8 @@ mod tests {
buf.push_str("* [Functions](#functions)\n");
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
if internal_fn.unpublished() || internal_fn.deprecated() {
continue;
}
@ -813,8 +807,8 @@ mod tests {
buf.push_str("## Functions\n\n");
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
if internal_fn.unpublished() {
continue;
}
@ -874,11 +868,12 @@ mod tests {
#[test]
fn test_generate_stdlib_json_schema() {
let stdlib = StdLib::new();
let combined = stdlib.combined();
let mut json_data = vec![];
for key in stdlib.fns.keys().sorted() {
let internal_fn = stdlib.fns.get(key).unwrap();
for key in combined.keys().sorted() {
let internal_fn = combined.get(key).unwrap();
json_data.push(internal_fn.to_json().unwrap());
}
expectorate::assert_contents(

View File

@ -23,8 +23,8 @@ pub struct LinearPatternData {
pub repetitions: usize,
/// The distance between each repetition. This can also be referred to as spacing.
pub distance: f64,
/// The axis of the pattern. This is a 3D vector.
pub axis: [f64; 3],
/// The axis of the pattern. This is a 2D vector.
pub axis: [f64; 2],
}
/// Data for a circular pattern.
@ -36,8 +36,8 @@ pub struct CircularPatternData {
/// This excludes the original entity. For example, if `repetitions` is 1,
/// the original entity will be copied once.
pub repetitions: usize,
/// The axis around which to make the pattern. This is a 3D vector.
pub axis: [f64; 3],
/// The axis around which to make the pattern. This is a 2D vector.
pub axis: [f64; 2],
/// The center about which to make th pattern. This is a 3D vector.
pub center: [f64; 3],
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
@ -50,7 +50,7 @@ pub struct CircularPatternData {
pub async fn pattern_linear(args: Args) -> Result<MemoryItem, KclError> {
let (data, geometry): (LinearPatternData, Geometry) = args.get_data_and_geometry()?;
if data.axis == [0.0, 0.0, 0.0] {
if data.axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -70,7 +70,7 @@ pub async fn pattern_linear(args: Args) -> Result<MemoryItem, KclError> {
pub async fn pattern_circular(args: Args) -> Result<MemoryItem, KclError> {
let (data, geometry): (CircularPatternData, Geometry) = args.get_data_and_geometry()?;
if data.axis == [0.0, 0.0, 0.0] {
if data.axis == [0.0, 0.0] {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the circular pattern cannot be the zero vector. Otherwise they will just duplicate in place."
@ -97,7 +97,11 @@ async fn inner_pattern_linear(data: LinearPatternData, geometry: Geometry, args:
.send_modeling_cmd(
id,
ModelingCmd::EntityLinearPattern {
axis: data.axis.into(),
axis: kittycad::types::Point3D {
x: data.axis[0],
y: data.axis[1],
z: 0.0,
},
entity_id: geometry.id(),
num_repetitions: data.repetitions as u32,
spacing: data.distance,
@ -154,7 +158,11 @@ async fn inner_pattern_circular(
.send_modeling_cmd(
id,
ModelingCmd::EntityCircularPattern {
axis: data.axis.into(),
axis: kittycad::types::Point3D {
x: data.axis[0],
y: data.axis[1],
z: 0.0,
},
entity_id: geometry.id(),
center: data.center.into(),
num_repetitions: data.repetitions as u32,

View File

@ -48,7 +48,7 @@ impl std::fmt::Debug for Circle {
/// TODO: Parse the KCL in a macro and generate these
impl StdLibFn for Circle {
fn name(&self) -> String {
"unstable_stdlib_circle".to_owned()
"circle".to_owned()
}
fn summary(&self) -> String {
@ -64,15 +64,56 @@ impl StdLibFn for Circle {
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
Vec::new() // TODO
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
let mut args = Vec::new();
for parameter in &self.function.params {
match parameter.identifier.name.as_str() {
"plane" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "SketchData".to_string(),
schema: <crate::std::sketch::SketchData>::json_schema(&mut generator),
required: true,
});
}
"center" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "[number, number]".to_string(),
schema: <[f64; 2]>::json_schema(&mut generator),
required: true,
});
}
"radius" => {
args.push(crate::docs::StdLibFnArg {
name: parameter.identifier.name.to_owned(),
type_: "number".to_string(),
schema: <f64>::json_schema(&mut generator),
required: true,
});
}
_ => panic!("Unknown parameter: {:?}", parameter.identifier.name),
}
}
args
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
None
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "SketchGroup".to_owned(),
type_: "SketchGroup".to_string(),
schema: <crate::executor::SketchGroup>::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
true
false
}
fn deprecated(&self) -> bool {

View File

@ -53,7 +53,7 @@ fn block_comment(i: &mut Located<&str>) -> PResult<Token> {
}
fn line_comment(i: &mut Located<&str>) -> PResult<Token> {
let inner = (r#"//"#, take_till(1.., ['\n', '\r'])).recognize();
let inner = (r#"//"#, take_till(0.., ['\n', '\r'])).recognize();
let (value, range) = inner.with_span().parse_next(i)?;
Ok(Token::from_range(range, TokenType::LineComment, value.to_string()))
}
@ -1463,13 +1463,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(), 5098);
assert_eq!(actual.len(), 5093);
}
#[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(), 17846);
assert_eq!(actual.len(), 17841);
}
#[test]
fn test_lexer_negative_word() {

View File

@ -6,7 +6,7 @@ fn cube = (length, center) => {
let p1 = [-l + x, l + y]
let p2 = [ l + x, l + y]
let p3 = [ l + x, -l + y]
return startSketchAt(p0)
|> lineTo(p1, %)
|> lineTo(p2, %)
@ -17,4 +17,3 @@ fn cube = (length, center) => {
}
const myCube = cube(40, [0,0])
show(myCube)

View File

@ -1,2 +1 @@
const cylinder = unstable_stdlib_circle('XY', [0,0], 22) |> extrude(14, %)
show(cylinder)
const cylinder = circle('XY', [0,0], 22) |> extrude(14, %)

View File

@ -308,4 +308,3 @@ const svg = startSketchOn('XY')
|> lineTo([13.44, -13.44], %) // VerticalLineHorizonal
|> lineTo([14.28, -13.44], %) // HorizontalLineRelative
|> close(%)
show(svg)

View File

@ -468,4 +468,3 @@ const svg = startSketchOn('XY')
|> bezierCurve({ control1: [0, -2], control2: [-2.68, -2.67], to: [-1.36, -2.34] }, %) // CubicBezierAbsolute
|> bezierCurve({ control1: [0, -0], control2: [0, -1.34], to: [0, -0.68] }, %) // CubicBezierAbsolute
|> close(%)
show(svg)

View File

@ -602,23 +602,14 @@ const part004 = startSketchOn('YZ')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|> close(%)
return sg
}
const square = startSketchOn('XY')
let code = r#"const square = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, 10], %)
|> line([10, 0], %)
|> line([0, -10], %)
|> close(%)
|> hole(circle([2, 2], .5), %)
|> hole(circle([2, 8], .5), %)
|> hole(circle('XY', [2, 2], .5), %)
|> hole(circle('XY', [2, 8], .5), %)
|> extrude(2, %)
"#;
@ -631,7 +622,7 @@ const square = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn optional_params() {
let code = r#"
fn circle = (pos, radius, tag?) => {
fn other_circle = (pos, radius, tag?) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
@ -640,7 +631,7 @@ async fn optional_params() {
return sg
}
const thing = circle([2, 2], 20)
const thing = other_circle([2, 2], 20)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
@ -650,19 +641,7 @@ const thing = circle([2, 2], 20)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_rounded_with_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
fn tarc = (to, sketchGroup, tag?) => {
let code = r#"fn tarc = (to, sketchGroup, tag?) => {
return tangentialArcTo(to, sketchGroup, tag)
}
@ -685,10 +664,10 @@ const holeRadius = 1
const holeIndex = 6
const part = roundedRectangle([0, 0], 20, 20, 4)
|> hole(circle([-holeIndex, holeIndex], holeRadius), %)
|> hole(circle([holeIndex, holeIndex], holeRadius), %)
|> hole(circle([-holeIndex, -holeIndex], holeRadius), %)
|> hole(circle([holeIndex, -holeIndex], holeRadius), %)
|> hole(circle('XY', [-holeIndex, holeIndex], holeRadius), %)
|> hole(circle('XY', [holeIndex, holeIndex], holeRadius), %)
|> hole(circle('XY', [-holeIndex, -holeIndex], holeRadius), %)
|> hole(circle('XY', [holeIndex, -holeIndex], holeRadius), %)
|> extrude(2, %)
"#;
@ -700,19 +679,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_top_level_expression() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
circle([0,0], 22) |> extrude(14, %)"#;
let code = r#"circle('XY', [0,0], 22) |> extrude(14, %)"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
.await
@ -722,20 +689,8 @@ circle([0,0], 22) |> extrude(14, %)"#;
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
|> patternLinear({axis: [0,0,1], repetitions: 12, distance: 2}, %)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,1], repetitions: 12, distance: 2}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -746,26 +701,14 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_3d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)
|> line([0, -1], %)
|> close(%)
|> extrude(1, %)
|> patternLinear({axis: [1, 0,1], repetitions: 3, distance: 6}, %)
|> patternLinear({axis: [1, 0], repetitions: 3, distance: 6}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -776,20 +719,8 @@ const part = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_negative_distance() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
|> patternLinear({axis: [0,0,1], repetitions: 12, distance: -2}, %)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,1], repetitions: 12, distance: -2}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -804,20 +735,8 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_negative_axis() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
|> patternLinear({axis: [0,0,-1], repetitions: 12, distance: 2}, %)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternLinear({axis: [0,-1], repetitions: 12, distance: 2}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -832,20 +751,8 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_linear_basic_holes() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const circles = circle([5, 5], 1)
|> patternLinear({axis: [1,1,0], repetitions: 12, distance: 3}, %)
let code = r#"const circles = circle('XY', [5, 5], 1)
|> patternLinear({axis: [1,1], repetitions: 12, distance: 3}, %)
const rectangle = startSketchOn('XY')
|> startProfileAt([0, 0], %)
@ -865,20 +772,8 @@ const rectangle = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_basic_2d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = circle([0,0], 2)
|> patternCircular({axis: [0,0,1], center: [20, 20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %)
let code = r#"const part = circle('XY', [0,0], 2)
|> patternCircular({axis: [0,1], center: [20, 20, 20], repetitions: 12, arcDegrees: 210, rotateDuplicates: true}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -889,26 +784,14 @@ const part = circle([0,0], 2)
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_basic_3d() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)
|> line([0, -1], %)
|> close(%)
|> extrude(1, %)
|> patternCircular({axis: [0,1,0], center: [-20, -20, -20], repetitions: 40, arcDegrees: 360, rotateDuplicates: false}, %)
|> patternCircular({axis: [0,1], center: [-20, -20, -20], repetitions: 40, arcDegrees: 360, rotateDuplicates: false}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)
@ -919,26 +802,14 @@ const part = startSketchOn('XY')
#[tokio::test(flavor = "multi_thread")]
async fn serial_test_patterns_circular_3d_tilted_axis() {
let code = r#"fn circle = (pos, radius) => {
const sg = startSketchOn('XY')
|> startProfileAt([pos[0] + radius, pos[1]], %)
|> arc({
angle_end: 360,
angle_start: 0,
radius: radius
}, %)
|> close(%)
return sg
}
const part = startSketchOn('XY')
let code = r#"const part = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0,1], %)
|> line([1, 0], %)
|> line([0, -1], %)
|> close(%)
|> extrude(1, %)
|> patternCircular({axis: [1,1,-1], center: [10, 0, 10], repetitions: 10, arcDegrees: 360, rotateDuplicates: true}, %)
|> patternCircular({axis: [1,1], center: [10, 0, 10], repetitions: 10, arcDegrees: 360, rotateDuplicates: true}, %)
"#;
let result = execute_and_snapshot(code, kittycad::types::UnitLength::Mm)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Some files were not shown because too many files have changed in this diff Show More