Compare commits

...

20 Commits

Author SHA1 Message Date
ddce447c0b A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu) 2024-03-20 18:37:48 +00:00
e8769eb543 Add ability to write prelude in KCL 2024-03-20 14:31:42 -04:00
46358b41a2 Bind all unary, binary and constants to KCL (#1781) 2024-03-20 13:12:43 -04:00
59274b76bf Add onboarding check workflow (#1764)
* add workflow

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

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-03-20 08:28:55 -07:00
d11d363f19 Cut release v0.16.0 (#1763) 2024-03-20 08:44:09 -04:00
f22ad7c4e7 Fix file route resolution to restor file switching (#1768)
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-03-20 11:39:57 +11:00
1913519f68 Revert "Add ping pong health, remove a timeout interval, fix up netwo… (#1771)
Revert "Add ping pong health, remove a timeout interval, fix up network events (#1555)"

This reverts commit 61d7950ca3.

Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
2024-03-20 11:39:49 +11:00
4b9d4fd45b Bump mio in fuzz tests (#1773) 2024-03-19 19:30:47 -05:00
78e6816b06 Always run cargo clippy in CI (#1772)
It's now a required job before merge is allowed. Unfortunately GitHub now blocks any non-Rust PR, because they require cargo clippy but don't trigger it to run.

Solution is simple, just always run cargo clippy, so it can pass, so that merge is allowed.
2024-03-20 11:02:49 +11:00
6607ea1663 Bump tauri-plugin-fs-extra from d95a1b3 to 6db4320 in /src-tauri (#1756)
Bumps [tauri-plugin-fs-extra](https://github.com/tauri-apps/plugins-workspace) from `d95a1b3` to `6db4320`.
- [Release notes](https://github.com/tauri-apps/plugins-workspace/releases)
- [Commits](d95a1b382f...6db4320e98)

---
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-03-19 15:53:07 -07:00
644a8ef3ca Remove cargo build CI job (#1767)
We shouldn't actually need `cargo build` CI checks. Because we're not building any binaries. Just `cargo check` should be enough. WASM builds are tested elsewhere.
2024-03-19 21:38:21 +00:00
e3e132c0d5 Fix cargo warnings (#1766)
CI is currently broken [logs here](https://github.com/KittyCAD/modeling-app/actions/runs/8349085118/job/22852456873?pr=1765#step:9:1046). Trying to fix.

OK I've fixed it. Process to fix was:

1. Revert be3fed8427 ("Add support for line, xLine, yLine, xLineTo, yLineTo (#1754)")
2. Restore that commit without any of its changes to Cargo.lock (it had, IMO, a lot of unnecessary changes)
3. `cargo update -p kittycad-execution-plan` (redoing only the necessary changes)
2024-03-19 21:19:57 +00:00
be3fed8427 Add support for line, xLine, yLine, xLineTo, yLineTo (#1754)
* Add support for line, xLine, yLine, xLineTo, yLineTo

* Fix minor memory misalignment

* Address PR comments
2024-03-19 12:11:45 -04:00
cefa6f85fe tag changes followup (#1747)
* tag changes followup

* fmt
2024-03-17 18:24:03 +11:00
47ff4623bd updating example kcl back to bracket with updated changes (#1743)
* updating example kcl

* parantheses error

* fix recast bug (#1746)

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

* minor fix

* adding comment to kcl

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-03-15 23:10:34 -07:00
69ff651201 fix recast bug (#1746)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-15 20:18:34 -07:00
788ae5dbab update starting example (#1742)
update starting example
2024-03-15 16:05:26 -07:00
816870253e Make tag last optional param everywhere (#1739)
* Make tag last optional param

* Update all test assertions with correct tag format

* Format ts

* Some progress on tests and code mods

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

* More sketch fixes

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

* Only 1 test left

* Clean up console.log

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

* Fix last ts test

* Clean up fmt

* Fix clippy too

* Update docs and fix small oversight on angled lines

* Fix more rust tests

* Make typescript happy

* Fmt

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
2024-03-15 17:03:42 -04:00
4987965731 better copilot test (#1741)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-03-15 13:07:02 -07:00
042ceb42fd Update cargo-test.yml (#1740) 2024-03-15 19:44:41 +00:00
92 changed files with 2257 additions and 2161 deletions

View File

@ -1,3 +1,3 @@
[codespell]
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo
ignore-words-list: crate,everytime,inout,co-ordinate,ot,nwo,absolutey
skip: **/target,node_modules,build,**/Cargo.lock

View File

@ -1,50 +0,0 @@
on:
push:
branches:
- main
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
pull_request:
paths:
- '**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
name: cargo build
jobs:
cargobuild:
name: cargo build
runs-on: ubuntu-latest
strategy:
matrix:
dir: ['src/wasm-lib']
steps:
- uses: actions/checkout@v4
- name: Install latest rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: install dependencies
if: matrix.dir == 'src-tauri'
run: |
sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
- name: Rust Cache
uses: Swatinem/rust-cache@v2.6.1
- name: Run cargo build
run: |
cd "${{ matrix.dir }}"
cargo build --all
shell: bash

View File

@ -9,12 +9,6 @@ on:
- '**.rs'
- .github/workflows/cargo-clippy.yml
pull_request:
paths:
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
- '**.rs'
- .github/workflows/cargo-build.yml
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

View File

@ -3,7 +3,7 @@ on:
branches:
- main
paths:
- src/wasm-lib/**.rs'
- 'src/wasm-lib/**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'
@ -11,7 +11,7 @@ on:
pull_request:
paths:
- src/wasm-lib/**.rs'
- 'src/wasm-lib/**.rs'
- '**/Cargo.toml'
- '**/Cargo.lock'
- '**/rust-toolchain.toml'

36
.github/workflows/check-exampleKcl.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: Check Onboarding KCL
on:
pull_request:
types: [opened, synchronize]
paths:
- 'src/lib/exampleKcl.ts'
permissions:
contents: read
issues: write
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Comment on PR
uses: actions/github-script@v6
with:
script: |
const message = '`src/lib/exampleKcl.ts` has been updated in this PR, please review and update the `src/routes/onboarding`, if needed.';
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
// Post a comment on the PR
await github.rest.issues.createComment({
owner,
repo,
issue_number,
body: message,
});

View File

@ -17,7 +17,7 @@ angleToMatchLengthX(segment_name: string, to: number, sketch_group: SketchGroup)
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %)
|> line([1, 3.82], %, 'seg01')
|> angledLineToX([
-angleToMatchLengthX('seg01', 10, %),
5

View File

@ -17,7 +17,7 @@ angleToMatchLengthY(segment_name: string, to: number, sketch_group: SketchGroup)
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %)
|> line([1, 3.82], %, 'seg01')
|> angledLineToX([
-angleToMatchLengthY('seg01', 10, %),
5

View File

@ -9,7 +9,7 @@ Draw an angled line.
```js
angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
angledLine(data: AngledLineData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> angledLine({ angle: 45, length: 10, tag: "edge1" }, %)
|> angledLine({ angle: 45, length: 10 }, %, "edge1")
|> line([10, 10], %)
|> line([0, 10], %)
|> close(%, "edge2")
@ -33,8 +33,6 @@ startSketchOn('XY')
angle: number,
// The length of the line.
length: number,
// The tag.
tag: string,
} |
[number, number]
```
@ -202,6 +200,7 @@ startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line of a given x length.
```js
angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ angledLineOfXLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
```js
startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLineOfXLength({ angle: 45, length: 10, tag: "edge1" }, %)
|> angledLineOfXLength({ angle: 45, length: 10 }, %, "edge1")
|> line([10, 10], %)
|> line([0, 10], %)
|> close(%, "edge2")
@ -33,8 +33,6 @@ startSketchOn('XZ')
angle: number,
// The length of the line.
length: number,
// The tag.
tag: string,
} |
[number, number]
```
@ -202,6 +200,7 @@ startSketchOn('XZ')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line of a given y length.
```js
angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGroup
angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ angledLineOfYLength(data: AngledLineData, sketch_group: SketchGroup) -> SketchGr
```js
startSketchOn('YZ')
|> startProfileAt([0, 0], %)
|> angledLineOfYLength({ angle: 45, length: 10, tag: "edge1" }, %)
|> angledLineOfYLength({ angle: 45, length: 10 }, %, "edge1")
|> line([10, 10], %)
|> line([0, 10], %)
|> close(%, "edge2")
@ -34,8 +34,6 @@ startSketchOn('YZ')
angle: number,
// The length of the line.
length: number,
// The tag.
tag: string,
} |
[number, number]
```
@ -203,6 +201,7 @@ startSketchOn('YZ')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line that intersects with a given line.
```js
angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch_group: SketchGroup) -> SketchGroup
angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,14 +17,13 @@ angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch_group: Sketc
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo({ to: [2, 2], tag: "yo" }, %)
|> lineTo([2, 2], %, "yo")
|> lineTo([3, 1], %)
|> angledLineThatIntersects({
angle: 180,
intersectTag: 'yo',
offset: 12,
tag: "yo2"
}, %)
offset: 12
}, %, "yo2")
|> line([4, 0], %)
|> close(%, "yo3")
|> extrude(10, %)
@ -41,8 +40,6 @@ const part001 = startSketchOn('XY')
intersectTag: string,
// The offset from the intersecting line.
offset: number,
// The tag.
tag: string,
}
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
@ -209,6 +206,7 @@ const part001 = startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line to a given x coordinate.
```js
angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
angledLineToX(data: AngledLineToData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> angledLineToX({ angle: 45, to: 10, tag: "edge1" }, %)
|> angledLineToX({ angle: 45, to: 10 }, %, "edge1")
|> line([10, 10], %)
|> line([0, 10], %)
|> close(%, "edge2")
@ -32,12 +32,9 @@ startSketchOn('XY')
{
// The angle of the line.
angle: number,
// The tag.
tag: string,
// The point to draw to.
to: number,
} |
[number, number]
}
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
@ -203,6 +200,7 @@ startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line to a given y coordinate.
```js
angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
angledLineToY(data: AngledLineToData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> angledLineToY({ angle: 45, to: 10, tag: "edge1" }, %)
|> angledLineToY({ angle: 45, to: 10 }, %, "edge1")
|> line([10, 10], %)
|> line([0, 10], %)
|> close(%, "edge2")
@ -31,12 +31,9 @@ startSketchOn('XY')
{
// The angle of the line.
angle: number,
// The tag.
tag: string,
// The point to draw to.
to: number,
} |
[number, number]
}
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
@ -202,6 +199,7 @@ startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw an arc.
```js
arc(data: ArcData, sketch_group: SketchGroup) -> SketchGroup
arc(data: ArcData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -20,9 +20,8 @@ startSketchOn('-YZ')
|> arc({
angle_start: 0,
angle_end: 360,
radius: 10,
tag: "edge1"
}, %)
radius: 10
}, %, "edge1")
|> extrude(10, %)
```
@ -37,16 +36,12 @@ startSketchOn('-YZ')
angle_start: number,
// The radius.
radius: number,
// The tag.
tag: string,
} |
{
// The center.
center: [number, number],
// The radius.
radius: number,
// The tag.
tag: string,
// The to point.
to: [number, number],
}
@ -215,6 +210,7 @@ startSketchOn('-YZ')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw a bezier curve.
```js
bezierCurve(data: BezierData, sketch_group: SketchGroup) -> SketchGroup
bezierCurve(data: BezierData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -20,9 +20,8 @@ startSketchOn('XY')
|> bezierCurve({
to: [10, 10],
control1: [5, 0],
control2: [5, 10],
tag: "edge1"
}, %)
control2: [5, 10]
}, %, "edge1")
|> close(%)
|> extrude(10, %)
```
@ -36,8 +35,6 @@ startSketchOn('XY')
control1: [number, number],
// The second control point.
control2: [number, number],
// The tag.
tag: string,
// The to point.
to: [number, number],
}
@ -206,6 +203,7 @@ startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -17,9 +17,9 @@ fillet(data: FilletData, extrude_group: ExtrudeGroup) -> ExtrudeGroup
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [0, 10], tag: "thing" }, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({ to: [0, -10], tag: "thing2" }, %)
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({ radius: 2, tags: ["thing", "thing2"] }, %)

View File

@ -19,7 +19,7 @@ const box = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, 10], %)
|> line([10, 0], %)
|> line({ to: [0, -10], tag: "surface" }, %)
|> line([0, -10], %, "surface")
|> close(%)
|> extrude(5, %)

View File

@ -17,9 +17,9 @@ getNextAdjacentEdge(tag: String, extrude_group: ExtrudeGroup) -> Uuid
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [0, 10], tag: "thing" }, %)
|> line({ to: [10, 0], tag: "thing1" }, %)
|> line({ to: [0, -10], tag: "thing2" }, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %, "thing1")
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({

View File

@ -17,9 +17,9 @@ getOppositeEdge(tag: String, extrude_group: ExtrudeGroup) -> Uuid
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [0, 10], tag: "thing" }, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({ to: [0, -10], tag: "thing2" }, %)
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({

View File

@ -17,9 +17,9 @@ getPreviousAdjacentEdge(tag: String, extrude_group: ExtrudeGroup) -> Uuid
```js
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [0, 10], tag: "thing" }, %)
|> line({ to: [10, 0], tag: "thing1" }, %)
|> line({ to: [0, -10], tag: "thing2" }, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %, "thing1")
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({

View File

@ -17,7 +17,7 @@ lastSegX(sketch_group: SketchGroup) -> number
```js
startSketchOn("YZ")
|> startProfileAt([0, 0], %)
|> line({ to: [5, 0], tag: "thing" }, %)
|> line([5, 0], %, "thing")
|> line([5, 5], %)
|> line([0, lastSegX(%)], %)
|> close(%)

View File

@ -17,7 +17,7 @@ lastSegY(sketch_group: SketchGroup) -> number
```js
startSketchOn("YZ")
|> startProfileAt([0, 0], %)
|> line({ to: [5, 0], tag: "thing" }, %)
|> line([5, 0], %, "thing")
|> line([5, 5], %)
|> line([0, lastSegY(%)], %)
|> close(%)

View File

@ -9,7 +9,7 @@ Draw a line.
```js
line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
line(delta: [number], sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -18,23 +18,14 @@ line(data: LineData, sketch_group: SketchGroup) -> SketchGroup
startSketchOn('-XY')
|> startProfileAt([0, 0], %)
|> line([10, 10], %)
|> line({ to: [20, 10], tag: "edge1" }, %)
|> line([20, 10], %, "edge1")
|> close(%, "edge2")
|> extrude(10, %)
```
### Arguments
* `data`: `LineData` - Data to draw a line. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: [number, number],
} |
[number, number]
```
* `delta`: `[number]` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -199,6 +190,7 @@ startSketchOn('-XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw a line to a point.
```js
lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
lineTo(to: [number], sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -18,18 +18,9 @@ lineTo(data: LineToData, sketch_group: SketchGroup) -> SketchGroup
fn rectShape = (pos, w, l) => {
const rr = startSketchOn('YZ')
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo({
to: [pos[0] + w / 2, pos[1] - (l / 2)],
tag: "edge1"
}, %)
|> lineTo({
to: [pos[0] + w / 2, pos[1] + l / 2],
tag: "edge2"
}, %)
|> lineTo({
to: [pos[0] - (w / 2), pos[1] + l / 2],
tag: "edge3"
}, %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
return rr
}
@ -40,16 +31,7 @@ const part = rectShape([0, 0], 20, 20)
### Arguments
* `data`: `LineToData` - Data to draw a line to a point. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: [number, number],
} |
[number, number]
```
* `to`: `[number]` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -214,6 +196,7 @@ const part = rectShape([0, 0], 20, 20)
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -18,7 +18,7 @@ segAng(segment_name: string, sketch_group: SketchGroup) -> number
const part001 = startSketchOn('XY')
|> startProfileAt([4.83, 12.56], %)
|> line([15.1, 2.48], %)
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|> line([3.15, -9.85], %, 'seg01')
|> line([-15.17, -4.1], %)
|> angledLine([segAng('seg01', %), 12.35], %)
|> line([-13.02, 10.03], %)

View File

@ -17,7 +17,7 @@ segEndX(segment_name: string, sketch_group: SketchGroup) -> number
```js
startSketchOn("YZ")
|> startProfileAt([0, 0], %)
|> line({ to: [5, 0], tag: "thing" }, %)
|> line([5, 0], %, "thing")
|> line([5, 5], %)
|> line([segEndX("thing", %), 5], %)
|> close(%)

View File

@ -17,7 +17,7 @@ segEndY(segment_name: string, sketch_group: SketchGroup) -> number
```js
startSketchOn("YZ")
|> startProfileAt([0, 0], %)
|> line({ to: [5, 0], tag: "thing" }, %)
|> line([5, 0], %, "thing")
|> line([5, 5], %)
|> line([segEndY("thing", %), 5], %)
|> close(%)

View File

@ -17,7 +17,7 @@ segLen(segment_name: string, sketch_group: SketchGroup) -> number
```js
startSketchOn("YZ")
|> startProfileAt([0, 0], %)
|> line({ to: [5, 0], tag: "thing" }, %)
|> line([5, 0], %, "thing")
|> line([5, 5], %)
|> line([0, segLen("thing", %)], %)
|> close(%)

View File

@ -9,7 +9,7 @@ Start a profile at a given point.
```js
startProfileAt(data: LineData, sketch_surface: SketchSurface) -> SketchGroup
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: String) -> SketchGroup
```
### Examples
@ -22,16 +22,7 @@ startSketchOn('XY')
### Arguments
* `data`: `LineData` - Data to draw a line. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: [number, number],
} |
[number, number]
```
* `to`: `[number]` (REQUIRED)
* `sketch_surface`: `SketchSurface` - A sketch group type. (REQUIRED)
```js
{
@ -93,6 +84,7 @@ startSketchOn('XY')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Start a sketch at a given point on the 'XY' plane.
```js
startSketchAt(data: LineData) -> SketchGroup
startSketchAt(data: [number]) -> SketchGroup
```
### Examples
@ -21,16 +21,7 @@ startSketchAt([0, 0])
### Arguments
* `data`: `LineData` - Data to draw a line. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: [number, number],
} |
[number, number]
```
* `data`: `[number]` (REQUIRED)
### Returns

View File

@ -18,7 +18,7 @@ startSketchOn(data: SketchData, tag?: SketchOnFaceTag) -> SketchSurface
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([10, 10], %)
|> line({ to: [20, 10], tag: "edge1" }, %)
|> line([20, 10], %, "edge1")
|> close(%, "edge2")
```
@ -40,7 +40,7 @@ const box = cube([0, 0], 20)
const part001 = startSketchOn(box, "start")
|> startProfileAt([0, 0], %)
|> line([10, 10], %)
|> line({ to: [20, 10], tag: "edge1" }, %)
|> line([20, 10], %, "edge1")
|> close(%)
|> extrude(20, %)
```

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ Draw an arc.
```js
tangentialArc(data: TangentialArcData, sketch_group: SketchGroup) -> SketchGroup
tangentialArc(data: TangentialArcData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,8 +17,8 @@ tangentialArc(data: TangentialArcData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('-YZ')
|> startProfileAt([0, 0], %)
|> line({ to: [10, 10], tag: "edge0" }, %)
|> tangentialArc({ radius: 10, offset: 90, tag: "edge1" }, %)
|> line([10, 10], %, "edge1")
|> tangentialArc({ radius: 10, offset: 90 }, %, "edge1")
|> close(%)
|> extrude(10, %)
```
@ -33,12 +33,6 @@ startSketchOn('-YZ')
// Radius of the arc. Not to be confused with Raiders of the Lost Ark.
radius: number,
} |
{
// The tag.
tag: string,
// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
to: [number, number],
} |
[number, number]
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
@ -205,6 +199,7 @@ startSketchOn('-YZ')
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -17,7 +17,7 @@ tangentialArcTo(to: [number], sketch_group: SketchGroup, tag?: String) -> Sketch
```js
startSketchOn('-YZ')
|> startProfileAt([0, 0], %)
|> line({ to: [10, 10], tag: "edge0" }, %)
|> line([10, 10], %, "edge0")
|> tangentialArcTo([10, 0], %)
|> close(%)
```

View File

@ -9,7 +9,7 @@ Draw a line on the x-axis.
```js
xLine(data: AxisLineData, sketch_group: SketchGroup) -> SketchGroup
xLine(length: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -25,16 +25,7 @@ startSketchOn('YZ')
### Arguments
* `data`: `AxisLineData` - Data to draw a line on an axis. (REQUIRED)
```js
{
// The length of the line.
length: number,
// The tag.
tag: string,
} |
number
```
* `length`: `number` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -199,6 +190,7 @@ number
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw a line to a point on the x-axis.
```js
xLineTo(data: AxisLineToData, sketch_group: SketchGroup) -> SketchGroup
xLineTo(to: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ xLineTo(data: AxisLineToData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> xLineTo({ to: 10, tag: "edge1" }, %)
|> xLineTo(10, %, "edge1")
|> line([10, 10], %)
|> close(%, "edge2")
|> extrude(10, %)
@ -25,16 +25,7 @@ startSketchOn('XY')
### Arguments
* `data`: `AxisLineToData` - Data to draw a line to a point on an axis. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: number,
} |
number
```
* `to`: `number` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -199,6 +190,7 @@ number
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw a line on the y-axis.
```js
yLine(data: AxisLineData, sketch_group: SketchGroup) -> SketchGroup
yLine(length: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -25,16 +25,7 @@ startSketchOn('XY')
### Arguments
* `data`: `AxisLineData` - Data to draw a line on an axis. (REQUIRED)
```js
{
// The length of the line.
length: number,
// The tag.
tag: string,
} |
number
```
* `length`: `number` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -199,6 +190,7 @@ number
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -9,7 +9,7 @@ Draw a line to a point on the y-axis.
```js
yLineTo(data: AxisLineToData, sketch_group: SketchGroup) -> SketchGroup
yLineTo(to: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
```
### Examples
@ -17,7 +17,7 @@ yLineTo(data: AxisLineToData, sketch_group: SketchGroup) -> SketchGroup
```js
startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> yLineTo({ to: 10, tag: "edge1" }, %)
|> yLineTo(10, %, "edge1")
|> line([10, 10], %)
|> close(%, "edge2")
|> extrude(10, %)
@ -26,16 +26,7 @@ startSketchOn('XZ')
### Arguments
* `data`: `AxisLineToData` - Data to draw a line to a point on an axis. (REQUIRED)
```js
{
// The tag.
tag: string,
// The to point.
to: number,
} |
number
```
* `to`: `number` (REQUIRED)
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. (REQUIRED)
```js
{
@ -200,6 +191,7 @@ number
},
}
```
* `tag`: `String` (OPTIONAL)
### Returns

View File

@ -130,7 +130,7 @@ test('Basic sketch', async ({ page }) => {
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line({ to: [${commonPoints.num1}, 0], tag: 'seg01' }, %)
|> line([${commonPoints.num1}, 0], %, 'seg01')
|> line([0, ${commonPoints.num1}], %)
|> angledLine([180, segLen('seg01', %)], %)`)
})

View File

@ -55,10 +55,9 @@ const part001 = startSketchOn('-XZ')
|> angledLineToY({
angle: topAng,
to: totalHeightHalf,
tag: 'seg04'
}, %)
|> xLineTo({ to: totalLen, tag: 'seg03' }, %)
|> yLine({ length: -armThick, tag: 'seg01' }, %)
}, %, 'seg04')
|> xLineTo(totalLen, %, 'seg03')
|> yLine(-armThick, %, 'seg01')
|> angledLineThatIntersects({
angle: HALF_TURN,
offset: -armThick,
@ -68,8 +67,7 @@ const part001 = startSketchOn('-XZ')
|> angledLineToY({
angle: -bottomAng,
to: -totalHeightHalf - armThick,
tag: 'seg02'
}, %)
}, %, 'seg02')
|> xLineTo(segEndX('seg03', %) + 0, %)
|> yLine(-segLen('seg01', %), %)
|> angledLineThatIntersects({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.15.6",
"version": "0.16.0",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.10.2",

2
src-tauri/Cargo.lock generated
View File

@ -3876,7 +3876,7 @@ dependencies = [
[[package]]
name = "tauri-plugin-fs-extra"
version = "0.0.0"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#d95a1b382f512a0be748e7a89e0f8fc4278010e2"
source = "git+https://github.com/tauri-apps/plugins-workspace?branch=v1#6db4320e98a4278d605dd05d4d87e1433939a7cd"
dependencies = [
"log",
"serde",

View File

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

View File

@ -59,6 +59,7 @@ const router = createBrowserRouter([
{
loader: fileLoader,
id: paths.FILE,
path: paths.FILE + '/:id',
element: (
<Auth>
<FileMachineProvider>
@ -74,8 +75,11 @@ const router = createBrowserRouter([
),
children: [
{
path: paths.FILE + '/:id',
loader: onboardingRedirectLoader,
index: true,
element: <></>,
},
{
children: [
{
path: makeUrlPathRelative(paths.SETTINGS),

View File

@ -30,7 +30,7 @@ describe('NetworkHealthIndicator tests', () => {
fireEvent.click(screen.getByTestId('network-toggle'))
expect(screen.getByTestId('network')).toHaveTextContent(
NETWORK_HEALTH_TEXT[NetworkHealthState.Issue]
NETWORK_HEALTH_TEXT[NetworkHealthState.Ok]
)
})

View File

@ -6,8 +6,7 @@ import {
ConnectingTypeGroup,
DisconnectingType,
engineCommandManager,
EngineCommandManagerEvents,
EngineConnectionEvents,
EngineConnectionState,
EngineConnectionStateType,
ErrorType,
initialConnectingTypeGroupState,
@ -82,35 +81,37 @@ const overallConnectionStateIcon: Record<
}
export function useNetworkStatus() {
const [steps, setSteps] = useState(
structuredClone(initialConnectingTypeGroupState)
)
const [steps, setSteps] = useState(initialConnectingTypeGroupState)
const [internetConnected, setInternetConnected] = useState<boolean>(true)
const [overallState, setOverallState] = useState<NetworkHealthState>(
NetworkHealthState.Disconnected
NetworkHealthState.Ok
)
const [pingPongHealth, setPingPongHealth] = useState<'OK' | 'BAD'>('BAD')
const [hasCopied, setHasCopied] = useState<boolean>(false)
const [error, setError] = useState<ErrorType | undefined>(undefined)
const hasIssue = (i: [ConnectingType, boolean | undefined]) =>
i[1] === undefined ? i[1] : !i[1]
const issues: Record<ConnectingTypeGroup, boolean> = {
[ConnectingTypeGroup.WebSocket]: steps[ConnectingTypeGroup.WebSocket].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].some(
(a: [ConnectingType, boolean | undefined]) => a[1] === false
),
}
const [issues, setIssues] = useState<
Record<ConnectingTypeGroup, boolean | undefined>
>({
[ConnectingTypeGroup.WebSocket]: undefined,
[ConnectingTypeGroup.ICE]: undefined,
[ConnectingTypeGroup.WebRTC]: undefined,
})
const hasIssues: boolean =
issues[ConnectingTypeGroup.WebSocket] ||
issues[ConnectingTypeGroup.ICE] ||
issues[ConnectingTypeGroup.WebRTC]
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
useEffect(() => {
setOverallState(
!internetConnected
? NetworkHealthState.Disconnected
: hasIssues || hasIssues === undefined
: hasIssues
? NetworkHealthState.Issue
: NetworkHealthState.Ok
)
@ -133,59 +134,19 @@ export function useNetworkStatus() {
}, [])
useEffect(() => {
console.log(pingPongHealth)
}, [pingPongHealth])
useEffect(() => {
const issues = {
[ConnectingTypeGroup.WebSocket]: steps[
ConnectingTypeGroup.WebSocket
].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].reduce(
(acc: boolean | undefined, a) =>
acc === true || acc === undefined ? acc : hasIssue(a),
false
),
}
setIssues(issues)
}, [steps])
useEffect(() => {
setHasIssues(
issues[ConnectingTypeGroup.WebSocket] ||
issues[ConnectingTypeGroup.ICE] ||
issues[ConnectingTypeGroup.WebRTC]
)
}, [issues])
useEffect(() => {
const onPingPongChange = ({ detail: state }: CustomEvent) => {
setPingPongHealth(state)
}
const onConnectionStateChange = ({
detail: engineConnectionState,
}: CustomEvent) => {
setSteps((steps) => {
let nextSteps = structuredClone(steps)
engineCommandManager.onConnectionStateChange(
(engineConnectionState: EngineConnectionState) => {
let hasSetAStep = false
if (
engineConnectionState.type === EngineConnectionStateType.Connecting
) {
const groups = Object.values(nextSteps)
const groups = Object.values(steps)
for (let group of groups) {
for (let step of group) {
if (step[0] !== engineConnectionState.value.type) continue
step[1] = true
hasSetAStep = true
}
}
}
@ -193,7 +154,7 @@ export function useNetworkStatus() {
if (
engineConnectionState.type === EngineConnectionStateType.Disconnecting
) {
const groups = Object.values(nextSteps)
const groups = Object.values(steps)
for (let group of groups) {
for (let step of group) {
if (
@ -204,6 +165,7 @@ export function useNetworkStatus() {
?.type === step[0]
) {
step[1] = false
hasSetAStep = true
}
}
}
@ -214,50 +176,11 @@ export function useNetworkStatus() {
}
}
// Reset the state of all steps if we have disconnected.
if (
engineConnectionState.type === EngineConnectionStateType.Disconnected
) {
return structuredClone(initialConnectingTypeGroupState)
if (hasSetAStep) {
setSteps(steps)
}
return nextSteps
})
}
const onEngineAvailable = ({ detail: engineConnection }: CustomEvent) => {
engineConnection.addEventListener(
EngineConnectionEvents.PingPongChanged,
onPingPongChange as EventListener
)
engineConnection.addEventListener(
EngineConnectionEvents.ConnectionStateChanged,
onConnectionStateChange as EventListener
)
}
engineCommandManager.addEventListener(
EngineCommandManagerEvents.EngineAvailable,
onEngineAvailable as EventListener
}
)
return () => {
engineCommandManager.removeEventListener(
EngineCommandManagerEvents.EngineAvailable,
onEngineAvailable as EventListener
)
// When the component is unmounted these should be assigned, but it's possible
// the component mounts and unmounts before engine is available.
engineCommandManager.engineConnection?.addEventListener(
EngineConnectionEvents.PingPongChanged,
onPingPongChange as EventListener
)
engineCommandManager.engineConnection?.addEventListener(
EngineConnectionEvents.ConnectionStateChanged,
onConnectionStateChange as EventListener
)
}
}, [])
return {
@ -269,7 +192,6 @@ export function useNetworkStatus() {
error,
setHasCopied,
hasCopied,
pingPongHealth,
}
}
@ -334,18 +256,18 @@ export const NetworkHealthIndicator = () => {
size="lg"
icon={
hasIssueToIcon[
String(issues[name as ConnectingTypeGroup])
issues[name as ConnectingTypeGroup].toString()
]
}
iconClassName={
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
issues[name as ConnectingTypeGroup].toString()
].icon
}
bgClassName={
'rounded-sm ' +
hasIssueToIconColors[
String(issues[name as ConnectingTypeGroup])
issues[name as ConnectingTypeGroup].toString()
].bg
}
/>

View File

@ -32,7 +32,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
const { state } = useModelingContext()
const { isExecuting } = useKclContext()
const { overallState } = useNetworkStatus()
const isNetworkOkay = overallState === NetworkHealthState.Ok
useEffect(() => {

View File

@ -360,7 +360,7 @@ describe('testing pipe operator special', () => {
test('pipe operator with sketch', () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo([2, 3], %)
|> lineTo({ to: [0, 1], tag: "myPath" }, %)
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> rx(45, %)
`
@ -370,18 +370,18 @@ describe('testing pipe operator special', () => {
{
type: 'VariableDeclaration',
start: 0,
end: 145,
end: 132,
kind: 'const',
declarations: [
{
type: 'VariableDeclarator',
start: 6,
end: 145,
end: 132,
id: { type: 'Identifier', start: 6, end: 14, name: 'mySketch' },
init: {
type: 'PipeExpression',
start: 17,
end: 145,
end: 132,
body: [
{
type: 'CallExpression',
@ -457,7 +457,7 @@ describe('testing pipe operator special', () => {
{
type: 'CallExpression',
start: 67,
end: 107,
end: 94,
callee: {
type: 'Identifier',
start: 67,
@ -466,121 +466,92 @@ describe('testing pipe operator special', () => {
},
arguments: [
{
type: 'ObjectExpression',
type: 'ArrayExpression',
start: 74,
end: 103,
properties: [
end: 80,
elements: [
{
type: 'ObjectProperty',
start: 76,
end: 86,
key: {
type: 'Identifier',
start: 76,
end: 78,
name: 'to',
},
value: {
type: 'ArrayExpression',
start: 80,
end: 86,
elements: [
{
type: 'Literal',
start: 81,
end: 82,
value: 0,
raw: '0',
},
{
type: 'Literal',
start: 84,
end: 85,
value: 1,
raw: '1',
},
],
},
type: 'Literal',
start: 75,
end: 76,
value: 0,
raw: '0',
},
{
type: 'ObjectProperty',
start: 88,
end: 101,
key: {
type: 'Identifier',
start: 88,
end: 91,
name: 'tag',
},
value: {
type: 'Literal',
start: 93,
end: 101,
value: 'myPath',
raw: '"myPath"',
},
type: 'Literal',
start: 78,
end: 79,
value: 1,
raw: '1',
},
],
},
{ type: 'PipeSubstitution', start: 105, end: 106 },
{ type: 'PipeSubstitution', start: 82, end: 83 },
{
type: 'Literal',
start: 85,
end: 93,
value: 'myPath',
raw: '"myPath"',
},
],
optional: false,
},
{
type: 'CallExpression',
start: 113,
end: 130,
start: 100,
end: 117,
callee: {
type: 'Identifier',
start: 113,
end: 119,
start: 100,
end: 106,
name: 'lineTo',
},
arguments: [
{
type: 'ArrayExpression',
start: 120,
end: 126,
start: 107,
end: 113,
elements: [
{
type: 'Literal',
start: 121,
end: 122,
start: 108,
end: 109,
value: 1,
raw: '1',
},
{
type: 'Literal',
start: 124,
end: 125,
start: 111,
end: 112,
value: 1,
raw: '1',
},
],
},
{ type: 'PipeSubstitution', start: 128, end: 129 },
{ type: 'PipeSubstitution', start: 115, end: 116 },
],
optional: false,
},
{
type: 'CallExpression',
start: 136,
end: 145,
start: 123,
end: 132,
callee: {
type: 'Identifier',
start: 136,
end: 138,
start: 123,
end: 125,
name: 'rx',
},
arguments: [
{
type: 'Literal',
start: 139,
end: 141,
start: 126,
end: 128,
value: 45,
raw: '45',
},
{ type: 'PipeSubstitution', start: 143, end: 144 },
{ type: 'PipeSubstitution', start: 130, end: 131 },
],
optional: false,
},
@ -1502,11 +1473,11 @@ const key = 'c'`
})
it('comments nested within a block statement', () => {
const code = `const mySketch = startSketchAt([0,0])
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|> lineTo([0, 1], %, 'myPath')
|> lineTo([1, 1], %) /* this is
a comment
spanning a few lines */
|> lineTo({ to: [1,0], tag: "rightPath" }, %)
|> lineTo([1,0], %, "rightPath")
|> close(%)
`
@ -1516,8 +1487,8 @@ const key = 'c'`
.nonCodeNodes
expect(sketchNonCodeMeta[indexOfSecondLineToExpression][0]).toEqual({
type: 'NonCodeNode',
start: 106,
end: 163,
start: 93,
end: 150,
value: {
type: 'inlineComment',
style: 'block',
@ -1529,7 +1500,7 @@ const key = 'c'`
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([0, 1], %, "myPath")',
' |> lineTo([1, 1], %)',
'// a comment',
' |> rx(90, %)',
@ -1540,8 +1511,8 @@ const key = 'c'`
.nonCodeNodes[3][0]
expect(sketchNonCodeMeta).toEqual({
type: 'NonCodeNode',
start: 125,
end: 138,
start: 114,
end: 127,
value: {
type: 'blockComment',
value: 'a comment',

View File

@ -94,7 +94,7 @@ const mySketch001 = startSketchOn('XY')
const sk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 10], tag: "p" }, %)
|> lineTo([0, 10], %, "p")
|> lineTo([2.5, 0], %)
// |> rx(45, %)
// |> translate([1,0,1], %)
@ -104,7 +104,7 @@ const theExtrude = extrude(2, sk1)
const sk2 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([-2.5, 0], %)
|> lineTo({ to: [0, 3], tag: "p" }, %)
|> lineTo([0, 3], %, "p")
|> lineTo([2.5, 0], %)
// |> transform(theTransf, %)
|> extrude(2, %)
@ -143,7 +143,7 @@ const sk2 = startSketchOn('XY')
xAxis: { x: 1, y: 0, z: 0 },
yAxis: { x: 0, y: 1, z: 0 },
zAxis: { x: 0, y: 0, z: 1 },
__meta: [{ sourceRange: [356, 381] }],
__meta: [{ sourceRange: [343, 368] }],
},
])
})

View File

@ -43,9 +43,9 @@ const newVar = myVar + 1`
it('sketch declaration', async () => {
let code = `const mySketch = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> lineTo({to: [0,2], tag: "myPath"}, %)
|> lineTo([0,2], %, "myPath")
|> lineTo([2,3], %)
|> lineTo({ to: [5,-1], tag: "rightPath" }, %)
|> lineTo([5,-1], %, "rightPath")
// |> close(%)
`
const { root } = await exe(code)
@ -57,7 +57,7 @@ const newVar = myVar + 1`
to: [0, 2],
from: [0, 0],
__geoMeta: {
sourceRange: [72, 109],
sourceRange: [72, 98],
id: expect.any(String),
},
name: 'myPath',
@ -68,7 +68,7 @@ const newVar = myVar + 1`
from: [0, 2],
name: '',
__geoMeta: {
sourceRange: [115, 131],
sourceRange: [104, 120],
id: expect.any(String),
},
},
@ -77,7 +77,7 @@ const newVar = myVar + 1`
to: [5, -1],
from: [2, 3],
__geoMeta: {
sourceRange: [137, 180],
sourceRange: [126, 156],
id: expect.any(String),
},
name: 'rightPath',
@ -99,7 +99,7 @@ const newVar = myVar + 1`
// const code = [
// 'const mySk1 = startSketchAt([0,0])',
// ' |> lineTo([1,1], %)',
// ' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
// ' |> lineTo([0, 1], %, "myPath")',
// ' |> lineTo([1, 1], %)',
// 'const rotated = rx(90, mySk1)',
// ].join('\n')
@ -126,7 +126,7 @@ const newVar = myVar + 1`
"const mySk1 = startSketchOn('XY')",
' |> startProfileAt([0,0], %)',
' |> lineTo([1,1], %)',
' |> lineTo({to: [0, 1], tag: "myPath"}, %)',
' |> lineTo([0, 1], %, "myPath")',
' |> lineTo([1,1], %)',
// ' |> rx(90, %)',
].join('\n')
@ -159,7 +159,7 @@ const newVar = myVar + 1`
to: [0, 1],
from: [1, 1],
__geoMeta: {
sourceRange: [91, 129],
sourceRange: [91, 118],
id: expect.any(String),
},
name: 'myPath',
@ -170,7 +170,7 @@ const newVar = myVar + 1`
from: [0, 1],
name: '',
__geoMeta: {
sourceRange: [135, 151],
sourceRange: [124, 140],
id: expect.any(String),
},
},
@ -341,7 +341,7 @@ describe('testing math operators', () => {
`const myVar = 3`,
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> line({ to: [3, 4], tag: 'seg01' }, %)`,
` |> line([3, 4], %, 'seg01')`,
` |> line([`,
` min(segLen('seg01', %), myVar),`,
` -legLen(segLen('seg01', %), myVar)`,

View File

@ -9,10 +9,10 @@ describe('testing getNodePathFromSourceRange', () => {
const myVar = 5
const sk3 = startSketchAt([0, 0])
|> lineTo([1, 2], %)
|> lineTo({ to: [3, 4], tag: 'yo' }, %)
|> lineTo([3, 4], %, 'yo')
|> close(%)
`
const subStr = "lineTo({ to: [3, 4], tag: 'yo' }, %)"
const subStr = "lineTo([3, 4], %, 'yo')"
const lineToSubstringIndex = code.indexOf(subStr)
const sourceRange: [number, number] = [
lineToSubstringIndex,

View File

@ -152,25 +152,25 @@ describe('Testing giveSketchFnCallTag', () => {
code,
'line([0, 0.83], %)'
)
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg01' }, %)")
expect(newCode).toContain("line([0, 0.83], %, 'seg01')")
expect(tag).toBe('seg01')
expect(isTagExisting).toBe(false)
})
it('Should create a unique tag if seg01 already exists', () => {
let _code = code.replace(
'line([-2.57, -0.13], %)',
"line({ to: [-2.57, -0.13], tag: 'seg01' }, %)"
"line([-2.57, -0.13], %, 'seg01')"
)
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code,
'line([0, 0.83], %)'
)
expect(newCode).toContain("line({ to: [0, 0.83], tag: 'seg02' }, %)")
expect(newCode).toContain("line([0, 0.83], %, 'seg02')")
expect(tag).toBe('seg02')
expect(isTagExisting).toBe(false)
})
it('Should return existing tag if it already exists', () => {
const lineButWithTag = "line({ to: [-2.57, -0.13], tag: 'butts' }, %)"
const lineButWithTag = "line([-2.57, -0.13], %, 'butts')"
let _code = code.replace('line([-2.57, -0.13], %)', lineButWithTag)
const { newCode, tag, isTagExisting } = giveSketchFnCallTagTestHelper(
_code,

View File

@ -23,11 +23,7 @@ import {
getNodePathFromSourceRange,
isNodeSafeToReplace,
} from './queryAst'
import {
addTagForSketchOnFace,
getFirstArg,
createFirstArg,
} from './std/sketch'
import { addTagForSketchOnFace } from './std/sketch'
import { isLiteralArrayOrStatic } from './std/sketchcombos'
import { DefaultPlaneStr } from 'clientSideScene/sceneEntities'
import { roundOff } from 'lib/utils'
@ -606,22 +602,25 @@ export function giveSketchFnCallTag(
path,
'CallExpression'
)
const firstArg = getFirstArg(primaryCallExp)
const isTagExisting = !!firstArg.tag
const tagValue = (firstArg.tag ||
createLiteral(tag || findUniqueName(ast, 'seg', 2))) as Literal
const tagStr = String(tagValue.value)
const newFirstArg = createFirstArg(
primaryCallExp.callee.name as ToolTip,
firstArg.val,
tagValue
)
primaryCallExp.arguments[0] = newFirstArg
return {
modifiedAst: ast,
tag: tagStr,
isTagExisting,
pathToNode: path,
// Tag is always 3rd expression now, using arg index feels brittle
// but we can come up with a better way to identify tag later.
const thirdArg = primaryCallExp.arguments?.[2]
const tagLiteral =
thirdArg || (createLiteral(tag || findUniqueName(ast, 'seg', 2)) as Literal)
const isTagExisting = !!thirdArg
if (!isTagExisting) {
primaryCallExp.arguments[2] = tagLiteral
}
if ('value' in tagLiteral) {
// Now TypeScript knows tagLiteral has a value property
return {
modifiedAst: ast,
tag: String(tagLiteral.value),
isTagExisting,
pathToNode: path,
}
} else {
throw new Error('Unable to assign tag without value')
}
}

View File

@ -250,7 +250,7 @@ describe('testing doesPipeHave', () => {
it('finds close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> line([19.49, 1.16], %, 'seg01')
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
@ -267,7 +267,7 @@ const part001 = startSketchAt([-1.41, 3.46])
it('finds extrude', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> line([19.49, 1.16], %, 'seg01')
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
@ -285,7 +285,7 @@ const part001 = startSketchAt([-1.41, 3.46])
it('does NOT find close', () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> line([19.49, 1.16], %, 'seg01')
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)
@ -314,7 +314,7 @@ describe('testing hasExtrudeSketchGroup', () => {
it('find sketch group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> line([19.49, 1.16], %, 'seg01')
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)`
@ -330,7 +330,7 @@ const part001 = startSketchAt([-1.41, 3.46])
it('find extrude group', async () => {
const exampleCode = `const length001 = 2
const part001 = startSketchAt([-1.41, 3.46])
|> line({ to: [19.49, 1.16], tag: 'seg01' }, %)
|> line([19.49, 1.16], %, 'seg01')
|> angledLine([-35, length001], %)
|> line([-3.22, -7.36], %)
|> angledLine([-175, segLen('seg01', %)], %)

View File

@ -64,9 +64,9 @@ log(5, myVar)
})
it('recast sketch declaration', () => {
let code = `const mySketch = startSketchAt([0, 0])
|> lineTo({ to: [0, 1], tag: "myPath" }, %)
|> lineTo([0, 1], %, "myPath")
|> lineTo([1, 1], %)
|> lineTo({ to: [1, 0], tag: "rightPath" }, %)
|> lineTo([1, 0], %, "rightPath")
|> close(%)
`
const { ast } = code2ast(code)
@ -77,7 +77,7 @@ log(5, myVar)
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
' |> lineTo({ to: [0, 1], tag: "myTag" }, %)',
' |> lineTo([0, 1], %, "myTag")',
' |> lineTo([1, 1], %)',
' |> rx(90, %)',
].join('\n')
@ -237,7 +237,7 @@ const key = 'c'
const code = [
'const mySk1 = startSketchAt([0, 0])',
' |> lineTo([1, 1], %)',
' |> lineTo({ to: [0, 1], tag: "myTag" }, %)',
' |> lineTo([0, 1], %, "myTag")',
' |> lineTo([1, 1], %)',
' // a comment',
' |> rx(90, %)',
@ -253,7 +253,7 @@ const key = 'c'
const mySk1 = startSketchAt([0, 0])
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([0, 1], %, 'myTag')
|> lineTo([1, 1], %) /* and
here
*/
@ -275,7 +275,7 @@ const mySk1 = startSketchAt([0, 0])
const mySk1 = startSketchAt([0, 0])
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([0, 1], %, 'myTag')
|> lineTo([1, 1], %) /* and
here */
// a comment between pipe expression statements
@ -321,7 +321,7 @@ describe('testing call Expressions in BinaryExpressions and UnaryExpressions', (
describe('it recasts wrapped object expressions in pipe bodies with correct indentation', () => {
it('with a single line', () => {
const code = `const part001 = startSketchAt([-0.01, -0.08])
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|> line([0.62, 4.15], %, 'seg01')
|> line([2.77, -1.24], %)
|> angledLineThatIntersects({
angle: 201,

View File

@ -1,5 +1,5 @@
import { PathToNode, Program, SourceRange } from 'lang/wasm'
import { VITE_KC_API_WS_MODELING_URL } from 'env'
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'
@ -8,9 +8,6 @@ import { sceneInfra } from 'clientSideScene/sceneInfra'
let lastMessage = ''
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
interface CommandInfo {
commandType: CommandTypes
range: SourceRange
@ -40,6 +37,11 @@ export interface ArtifactMap {
[key: string]: ResultCommand | PendingCommand | FailedCommand
}
interface NewTrackArgs {
conn: EngineConnection
mediaStream: MediaStream
}
// This looks funny, I know. This is needed because node and the browser
// disagree as to the type. In a browser it's a number, but in node it's a
// "Timeout".
@ -156,28 +158,10 @@ export type EngineConnectionState =
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
| State<EngineConnectionStateType.Disconnected, void>
export type PingPongState = 'OK' | 'BAD'
export enum EngineConnectionEvents {
// Fires for each ping-pong success or failure.
PingPongChanged = 'ping-pong-changed', // (state: PingPongState) => void
// For now, this is only used by the NetworkHealthIndicator.
// We can eventually use it for more, but one step at a time.
ConnectionStateChanged = 'connection-state-changed', // (state: EngineConnectionState) => void
// These are used for the EngineCommandManager and were created
// before onConnectionStateChange existed.
ConnectionStarted = 'connection-started', // (engineConnection: EngineConnection) => void
Opened = 'opened', // (engineConnection: EngineConnection) => void
Closed = 'closed', // (engineConnection: EngineConnection) => void
NewTrack = 'new-track', // (track: NewTrackArgs) => void
}
// EngineConnection encapsulates the connection(s) to the Engine
// for the EngineCommandManager; namely, the underlying WebSocket
// and WebRTC connections.
class EngineConnection extends EventTarget {
class EngineConnection {
websocket?: WebSocket
pc?: RTCPeerConnection
unreliableDataChannel?: RTCDataChannel
@ -211,12 +195,7 @@ class EngineConnection extends EventTarget {
}
}
this._state = next
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.ConnectionStateChanged, {
detail: this._state,
})
)
this.onConnectionStateChange(this._state)
}
private failedConnTimeout: Timeout | null
@ -224,39 +203,74 @@ class EngineConnection extends EventTarget {
readonly url: string
private readonly token?: string
// For now, this is only used by the NetworkHealthIndicator.
// We can eventually use it for more, but one step at a time.
private onConnectionStateChange: (state: EngineConnectionState) => void
// These are used for the EngineCommandManager and were created
// before onConnectionStateChange existed.
private onEngineConnectionOpen: (engineConnection: EngineConnection) => void
private onConnectionStarted: (engineConnection: EngineConnection) => void
private onClose: (engineConnection: EngineConnection) => void
private onNewTrack: (track: NewTrackArgs) => void
// TODO: actual type is ClientMetrics
private webrtcStatsCollector?: () => Promise<ClientMetrics>
private pingPongSpan: { ping?: Date; pong?: Date }
constructor({ url, token }: { url: string; token?: string }) {
super()
constructor({
url,
token,
onConnectionStateChange = () => {},
onNewTrack = () => {},
onEngineConnectionOpen = () => {},
onConnectionStarted = () => {},
onClose = () => {},
}: {
url: string
token?: string
onConnectionStateChange?: (state: EngineConnectionState) => void
onEngineConnectionOpen?: (engineConnection: EngineConnection) => void
onConnectionStarted?: (engineConnection: EngineConnection) => void
onClose?: (engineConnection: EngineConnection) => void
onNewTrack?: (track: NewTrackArgs) => void
}) {
this.url = url
this.token = token
this.failedConnTimeout = null
this.onConnectionStateChange = onConnectionStateChange
this.onEngineConnectionOpen = onEngineConnectionOpen
this.onConnectionStarted = onConnectionStarted
this.pingPongSpan = { ping: undefined, pong: undefined }
this.onClose = onClose
this.onNewTrack = onNewTrack
// TODO(paultag): This ought to be tweakable.
const pingIntervalMs = 10000
// Without an interval ping, our connection will timeout.
setInterval(() => {
let pingInterval = setInterval(() => {
switch (this.state.type as EngineConnectionStateType) {
case EngineConnectionStateType.ConnectionEstablished:
this.send({ type: 'ping' })
this.pingPongSpan.ping = new Date()
break
case EngineConnectionStateType.Disconnecting:
case EngineConnectionStateType.Disconnected:
// Reconnect if we have disconnected.
if (!this.isConnecting()) this.connect()
clearInterval(pingInterval)
break
default:
if (this.isConnecting()) break
// Means we never could do an initial connection. Reconnect everything.
if (!this.pingPongSpan.ping) this.connect()
break
}
}, pingIntervalMs)
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
let connectRetryInterval = setInterval(() => {
if (this.state.type !== EngineConnectionStateType.Disconnected) return
// Only try reconnecting when completely disconnected.
clearInterval(connectRetryInterval)
console.log('Trying to reconnect')
this.connect()
}, connectionTimeoutMs)
}
isConnecting() {
@ -338,11 +352,7 @@ class EngineConnection extends EventTarget {
// dance is it safest to connect the video tracks / stream
case 'connected':
// Let the browser attach to the video stream now
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.NewTrack, {
detail: { conn: this, mediaStream: this.mediaStream! },
})
)
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! })
break
case 'failed':
this.disconnectAll()
@ -458,9 +468,7 @@ class EngineConnection extends EventTarget {
// Everything is now connected.
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
)
this.onEngineConnectionOpen(this)
})
this.unreliableDataChannel.addEventListener('close', (event) => {
@ -502,10 +510,6 @@ class EngineConnection extends EventTarget {
},
}
// Send an initial ping
this.send({ type: 'ping' })
this.pingPongSpan.ping = new Date()
// This is required for when KCMA is running stand-alone / within Tauri.
// Otherwise when run in a browser, the token is sent implicitly via
// the Cookie header.
@ -571,34 +575,12 @@ failed cmd type was ${artifactThatFailed?.commandType}`
let resp = message.resp
// If there's no body to the response, we can bail here.
// !resp.type is usually "pong" response for our "ping"
if (!resp || !resp.type) {
return
}
switch (resp.type) {
case 'pong':
this.pingPongSpan.pong = new Date()
if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
if (
Math.abs(
this.pingPongSpan.pong.valueOf() -
this.pingPongSpan.ping.valueOf()
) >= pingIntervalMs
) {
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
detail: 'BAD',
})
)
} else {
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
detail: 'OK',
})
)
}
}
break
case 'ice_server_info':
let ice_servers = resp.data?.ice_servers
@ -745,11 +727,27 @@ failed cmd type was ${artifactThatFailed?.commandType}`
}
})
this.dispatchEvent(
new CustomEvent(EngineConnectionEvents.ConnectionStarted, {
detail: this,
})
)
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
if (this.failedConnTimeout) {
clearTimeout(this.failedConnTimeout)
this.failedConnTimeout = null
}
this.failedConnTimeout = setTimeout(() => {
if (this.isReady()) {
return
}
this.failedConnTimeout = null
this.state = {
type: EngineConnectionStateType.Disconnecting,
value: {
type: DisconnectingType.Timeout,
},
}
this.disconnectAll()
this.finalizeIfAllConnectionsClosed()
}, connectionTimeoutMs)
this.onConnectionStarted(this)
}
unreliableSend(message: object | string) {
// TODO(paultag): Add in logic to determine the connection state and
@ -798,8 +796,6 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
}
// TODO: Should eventually be replaced with native EventTarget event system,
// as it manages events in a more familiar way to other developers.
export interface Subscription<T extends ModelTypes> {
event: T
callback: (
@ -827,11 +823,7 @@ export type CommandLog =
data: null
}
export enum EngineCommandManagerEvents {
EngineAvailable = 'engine-available',
}
export class EngineCommandManager extends EventTarget {
export class EngineCommandManager {
artifactMap: ArtifactMap = {}
lastArtifactMap: ArtifactMap = {}
sceneCommandArtifacts: ArtifactMap = {}
@ -865,9 +857,10 @@ export class EngineCommandManager extends EventTarget {
}
} = {} as any
constructor() {
super()
callbacksEngineStateConnection: ((state: EngineConnectionState) => void)[] =
[]
constructor() {
this.engineConnection = undefined
;(async () => {
// circular dependency needs one to be lazy loaded
@ -908,17 +901,12 @@ export class EngineCommandManager extends EventTarget {
this.engineConnection = new EngineConnection({
url,
token,
})
this.dispatchEvent(
new CustomEvent(EngineCommandManagerEvents.EngineAvailable, {
detail: this.engineConnection,
})
)
this.engineConnection.addEventListener(
EngineConnectionEvents.Opened,
() => {
onConnectionStateChange: (state: EngineConnectionState) => {
for (let cb of this.callbacksEngineStateConnection) {
cb(state)
}
},
onEngineConnectionOpen: () => {
// Make the axis gizmo.
// We do this after the connection opened to avoid a race condition.
// Connected opened is the last thing that happens when the stream
@ -953,98 +941,78 @@ export class EngineCommandManager extends EventTarget {
setIsStreamReady(true)
executeCode(undefined, true)
})
}
)
this.engineConnection.addEventListener(
EngineConnectionEvents.Closed,
() => {
},
onClose: () => {
setIsStreamReady(false)
}
)
},
onConnectionStarted: (engineConnection) => {
engineConnection?.pc?.addEventListener('datachannel', (event) => {
let unreliableDataChannel = event.channel
this.engineConnection.addEventListener(
EngineConnectionEvents.ConnectionStarted,
(({ detail: engineConnection }: CustomEvent) => {
engineConnection?.pc?.addEventListener(
'datachannel',
(event: RTCDataChannelEvent) => {
let unreliableDataChannel = event.channel
unreliableDataChannel.addEventListener(
'message',
(event: MessageEvent) => {
const result: UnreliableResponses = JSON.parse(event.data)
Object.values(
this.unreliableSubscriptions[result.type] || {}
).forEach(
// TODO: There is only one response that uses the unreliable channel atm,
// highlight_set_entity, if there are more it's likely they will all have the same
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
// per unreliable subscription.
(callback) => {
if (
result?.data?.sequence &&
result?.data.sequence > this.inSequence &&
result.type === 'highlight_set_entity'
) {
this.inSequence = result.data.sequence
callback(result)
}
}
)
unreliableDataChannel.addEventListener('message', (event) => {
const result: UnreliableResponses = JSON.parse(event.data)
Object.values(
this.unreliableSubscriptions[result.type] || {}
).forEach(
// TODO: There is only one response that uses the unreliable channel atm,
// highlight_set_entity, if there are more it's likely they will all have the same
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
// per unreliable subscription.
(callback) => {
if (
result?.data?.sequence &&
result?.data.sequence > this.inSequence &&
result.type === 'highlight_set_entity'
) {
this.inSequence = result.data.sequence
callback(result)
}
}
)
}
)
})
})
// When the EngineConnection starts a connection, we want to register
// callbacks into the WebSocket/PeerConnection.
engineConnection.websocket?.addEventListener(
'message',
(event: MessageEvent) => {
if (event.data instanceof ArrayBuffer) {
// If the data is an ArrayBuffer, it's the result of an export command,
// because in all other cases we send JSON strings. But in the case of
// export we send a binary blob.
// Pass this to our export function.
void exportSave(event.data)
} else {
const message: Models['WebSocketResponse_type'] = JSON.parse(
event.data
)
if (
message.success &&
message.resp.type === 'modeling' &&
message.request_id
) {
this.handleModelingCommand(message.resp, message.request_id)
} else if (
!message.success &&
message.request_id &&
this.artifactMap[message.request_id]
) {
this.handleFailedModelingCommand(message)
}
engineConnection.websocket?.addEventListener('message', (event) => {
if (event.data instanceof ArrayBuffer) {
// If the data is an ArrayBuffer, it's the result of an export command,
// because in all other cases we send JSON strings. But in the case of
// export we send a binary blob.
// Pass this to our export function.
void exportSave(event.data)
} else {
const message: Models['WebSocketResponse_type'] = JSON.parse(
event.data
)
if (
message.success &&
message.resp.type === 'modeling' &&
message.request_id
) {
this.handleModelingCommand(message.resp, message.request_id)
} else if (
!message.success &&
message.request_id &&
this.artifactMap[message.request_id]
) {
this.handleFailedModelingCommand(message)
}
}
)
}) as EventListener
)
})
},
onNewTrack: ({ mediaStream }) => {
console.log('received track', mediaStream)
this.engineConnection.addEventListener(EngineConnectionEvents.NewTrack, (({
detail: { mediaStream },
}: CustomEvent) => {
console.log('received track', mediaStream)
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
console.log('peer is not sending video to us')
// this.engineConnection?.close()
// this.engineConnection?.connect()
})
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
console.log('peer is not sending video to us')
// this.engineConnection?.close()
// this.engineConnection?.connect()
})
setMediaStream(mediaStream)
}) as EventListener)
setMediaStream(mediaStream)
},
})
this.engineConnection?.connect()
}
@ -1234,6 +1202,9 @@ export class EngineCommandManager extends EventTarget {
) {
delete this.unreliableSubscriptions[event][id]
}
onConnectionStateChange(callback: (state: EngineConnectionState) => void) {
this.callbacksEngineStateConnection.push(callback)
}
endSession() {
// TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)`
// we need to loop over them each individually because if the engine doesn't recognise a single

View File

@ -206,9 +206,7 @@ describe('testing addTagForSketchOnFace', () => {
},
'lineTo'
)
const expectedCode = genCode(
"lineTo({ to: [-1.59, -1.54], tag: 'seg01' }, %)"
)
const expectedCode = genCode("lineTo([-1.59, -1.54], %, 'seg01')")
expect(recast(modifiedAst)).toBe(expectedCode)
})
})

View File

@ -53,40 +53,34 @@ export function getCoordsFromPaths(skGroup: SketchGroup, index = 0): Coords2d {
export function createFirstArg(
sketchFn: ToolTip,
val: Value | [Value, Value] | [Value, Value, Value],
tag?: Value
val: Value | [Value, Value] | [Value, Value, Value]
): Value {
if (!tag) {
if (Array.isArray(val)) {
return createArrayExpression(val)
}
return val
}
if (Array.isArray(val)) {
if (['line', 'lineTo'].includes(sketchFn))
return createObjectExpression({ to: createArrayExpression(val), tag })
if (
['angledLine', 'angledLineOfXLength', 'angledLineOfYLength'].includes(
sketchFn
)
[
'angledLine',
'angledLineOfXLength',
'angledLineOfYLength',
'angledLineToX',
'angledLineToY',
'line',
'lineTo',
].includes(sketchFn)
)
return createObjectExpression({ angle: val[0], length: val[1], tag })
if (['angledLineToX', 'angledLineToY'].includes(sketchFn))
return createObjectExpression({ angle: val[0], to: val[1], tag })
return createArrayExpression(val)
if (['angledLineThatIntersects'].includes(sketchFn) && val[2])
return createObjectExpression({
angle: val[0],
offset: val[1],
intersectTag: val[2],
tag,
})
} else {
if (['xLine', 'yLine'].includes(sketchFn))
return createObjectExpression({ length: val, tag })
if (['xLineTo', 'yLineTo'].includes(sketchFn))
return createObjectExpression({ to: val, tag })
if (['startSketchAt'].includes(sketchFn))
return createObjectExpression({ to: val, tag })
if (
['startSketchAt', 'xLine', 'xLineTo', 'yLine', 'yLineTo'].includes(
sketchFn
)
)
return val
}
throw new Error('all sketch line types should have been covered')
}
@ -155,7 +149,7 @@ export const lineTo: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('default'),
addTag: addTag(),
}
export const line: SketchLineHelper = {
@ -246,7 +240,7 @@ export const line: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('default'),
addTag: addTag(),
}
export const xLineTo: SketchLineHelper = {
@ -294,7 +288,7 @@ export const xLineTo: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('default'),
addTag: addTag(),
}
export const yLineTo: SketchLineHelper = {
@ -342,7 +336,7 @@ export const yLineTo: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('default'),
addTag: addTag(),
}
export const xLine: SketchLineHelper = {
@ -392,7 +386,7 @@ export const xLine: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('length'),
addTag: addTag(),
}
export const yLine: SketchLineHelper = {
@ -436,7 +430,7 @@ export const yLine: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('length'),
addTag: addTag(),
}
export const tangentialArcTo: SketchLineHelper = {
@ -516,7 +510,7 @@ export const tangentialArcTo: SketchLineHelper = {
}
},
// TODO copy-paste from angledLine
addTag: addTagWithTo('angleLength'),
addTag: addTag(),
}
export const angledLine: SketchLineHelper = {
add: ({
@ -582,7 +576,7 @@ export const angledLine: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleLength'),
addTag: addTag(),
}
export const angledLineOfXLength: SketchLineHelper = {
@ -655,7 +649,7 @@ export const angledLineOfXLength: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleLength'),
addTag: addTag(),
}
export const angledLineOfYLength: SketchLineHelper = {
@ -729,7 +723,7 @@ export const angledLineOfYLength: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleLength'),
addTag: addTag(),
}
export const angledLineToX: SketchLineHelper = {
@ -798,7 +792,7 @@ export const angledLineToX: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleTo'),
addTag: addTag(),
}
export const angledLineToY: SketchLineHelper = {
@ -868,7 +862,7 @@ export const angledLineToY: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleTo'),
addTag: addTag(),
}
export const angledLineThatIntersects: SketchLineHelper = {
@ -957,7 +951,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
pathToNode,
}
},
addTag: addTagWithTo('angleTo'), // TODO might be wrong
addTag: addTag(), // TODO might be wrong
}
export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
@ -1180,77 +1174,32 @@ function isAngleLiteral(lineArugement: Value): boolean {
type addTagFn = (a: ModifyAstBase) => { modifiedAst: Program; tag: string }
function addTagWithTo(
argType: 'angleLength' | 'angleTo' | 'default' | 'length'
): addTagFn {
function addTag(): addTagFn {
return ({ node, pathToNode }) => {
let tagName = findUniqueName(node, 'seg', 2)
const _node = { ...node }
const { node: callExpression } = getNodeFromPath<CallExpression>(
const { node: primaryCallExp } = getNodeFromPath<CallExpression>(
_node,
pathToNode
pathToNode,
'CallExpression'
)
const firstArg = callExpression.arguments?.[0]
if (firstArg.type === 'ObjectExpression') {
const existingTagName = firstArg.properties?.find(
(prop) => prop.key.name === 'tag'
)
if (!existingTagName) {
mutateObjExpProp(
callExpression.arguments?.[0],
createLiteral(tagName),
'tag'
)
} else {
tagName = `${(existingTagName.value as Literal).value}`
}
// Tag is always 3rd expression now, using arg index feels brittle
// but we can come up with a better way to identify tag later.
const thirdArg = primaryCallExp.arguments?.[2]
const tagLiteral =
thirdArg || (createLiteral(findUniqueName(_node, 'seg', 2)) as Literal)
const isTagExisting = !!thirdArg
if (!isTagExisting) {
primaryCallExp.arguments[2] = tagLiteral
}
if ('value' in tagLiteral) {
// Now TypeScript knows tagLiteral has a value property
return {
modifiedAst: _node,
tag: tagName,
tag: String(tagLiteral.value),
}
} else {
throw new Error('Unable to assign tag without value')
}
if (firstArg.type === 'ArrayExpression') {
const objExp =
argType === 'default'
? createObjectExpression({
to: firstArg,
tag: createLiteral(tagName),
})
: argType === 'angleLength'
? createObjectExpression({
angle: firstArg.elements[0],
length: firstArg.elements[1],
tag: createLiteral(tagName),
})
: createObjectExpression({
angle: firstArg.elements[0],
to: firstArg.elements[1],
tag: createLiteral(tagName),
})
callExpression.arguments[0] = objExp
return {
modifiedAst: _node,
tag: tagName,
}
}
if (firstArg.type === 'Literal') {
const objExp =
argType === 'length'
? createObjectExpression({
length: firstArg,
tag: createLiteral(tagName),
})
: createObjectExpression({
to: firstArg,
tag: createLiteral(tagName),
})
callExpression.arguments[0] = objExp
return {
modifiedAst: _node,
tag: tagName,
}
}
throw new Error('lineTo must be called with an object or array')
}
}

View File

@ -54,29 +54,17 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
const bigExampleArr = [
`const part001 = startSketchOn('XY')`,
` |> startProfileAt([0, 0], %)`,
` |> lineTo({ to: [1, 1], tag: 'abc1' }, %)`,
` |> line({ to: [-2.04, -0.7], tag: 'abc2' }, %)`,
` |> angledLine({`,
` angle: 157,`,
` length: 1.69,`,
` tag: 'abc3'`,
` }, %)`,
` |> angledLineOfXLength({`,
` angle: 217,`,
` length: 0.86,`,
` tag: 'abc4'`,
` }, %)`,
` |> angledLineOfYLength({`,
` angle: 104,`,
` length: 1.58,`,
` tag: 'abc5'`,
` }, %)`,
` |> angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)`,
` |> angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)`,
` |> xLine({ length: 1.47, tag: 'abc8' }, %)`,
` |> yLine({ length: 1.57, tag: 'abc9' }, %)`,
` |> xLineTo({ to: 1.49, tag: 'abc10' }, %)`,
` |> yLineTo({ to: 2.64, tag: 'abc11' }, %)`,
` |> lineTo([1, 1], %, 'abc1')`,
` |> line([-2.04, -0.7], %, 'abc2')`,
` |> angledLine({ angle: 157, length: 1.69 }, %, 'abc3')`,
` |> angledLineOfXLength({ angle: 217, length: 0.86 }, %, 'abc4')`,
` |> angledLineOfYLength({ angle: 104, length: 1.58 }, %, 'abc5')`,
` |> angledLineToX({ angle: 55, to: -2.89 }, %, 'abc6')`,
` |> angledLineToY({ angle: 330, to: 2.53 }, %, 'abc7')`,
` |> xLine(1.47, %, 'abc8')`,
` |> yLine(1.57, %, 'abc9')`,
` |> xLineTo(1.49, %, 'abc10')`,
` |> yLineTo(2.64, %, 'abc11')`,
` |> lineTo([2.55, 3.58], %) // lineTo`,
` |> line([0.73, -0.75], %)`,
` |> angledLine([63, 1.38], %) // angledLine`,
@ -91,8 +79,8 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
]
const bigExample = bigExampleArr.join('\n')
it('line with tag converts to xLine', async () => {
const callToSwap = "line({ to: [-2.04, -0.7], tag: 'abc2' }, %)"
const expectedLine = "xLine({ length: -2.04, tag: 'abc2' }, %)"
const callToSwap = "line([-2.04, -0.7], %, 'abc2')"
const expectedLine = "xLine(-2.04, %, 'abc2')"
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap,
@ -117,10 +105,10 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('lineTo with tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "lineTo({ to: [1, 1], tag: 'abc1' }, %)",
callToSwap: "lineTo([1, 1], %, 'abc1')",
constraintType: 'horizontal',
})
const expectedLine = "xLineTo({ to: 1, tag: 'abc1' }, %)"
const expectedLine = "xLineTo(1, %, 'abc1')"
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
@ -139,16 +127,11 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('angledLine with tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLine({`,
` angle: 157,`,
` length: 1.69,`,
` tag: 'abc3'`,
` }, %)`,
].join('\n'),
callToSwap: "angledLine({ angle: 157, length: 1.69 }, %, 'abc3')",
constraintType: 'horizontal',
})
const expectedLine = "xLine({ length: -1.56, tag: 'abc3' }, %)"
const expectedLine = "xLine(-1.56, %, 'abc3')"
console.log(newCode)
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
@ -167,16 +150,11 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('angledLineOfXLength with tag converts to xLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLineOfXLength({`,
` angle: 217,`,
` length: 0.86,`,
` tag: 'abc4'`,
` }, %)`,
].join('\n'),
callToSwap:
"angledLineOfXLength({ angle: 217, length: 0.86 }, %, 'abc4')",
constraintType: 'horizontal',
})
const expectedLine = "xLine({ length: -0.86, tag: 'abc4' }, %)"
const expectedLine = "xLine(-0.86, %, 'abc4')"
// hmm "-0.86" is correct since the angle is 104, but need to make sure this is compatible `-myVar`
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
@ -196,16 +174,11 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('angledLineOfYLength with tag converts to yLine', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: [
`angledLineOfYLength({`,
` angle: 104,`,
` length: 1.58,`,
` tag: 'abc5'`,
` }, %)`,
].join('\n'),
callToSwap:
"angledLineOfYLength({ angle: 104, length: 1.58 }, %, 'abc5')",
constraintType: 'vertical',
})
const expectedLine = "yLine({ length: 1.58, tag: 'abc5' }, %)"
const expectedLine = "yLine(1.58, %, 'abc5')"
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
@ -224,10 +197,10 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('angledLineToX with tag converts to xLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "angledLineToX({ angle: 55, to: -2.89, tag: 'abc6' }, %)",
callToSwap: "angledLineToX({ angle: 55, to: -2.89 }, %, 'abc6')",
constraintType: 'horizontal',
})
const expectedLine = "xLineTo({ to: -2.89, tag: 'abc6' }, %)"
const expectedLine = "xLineTo(-2.89, %, 'abc6')"
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))
@ -246,10 +219,10 @@ describe('testing swapping out sketch calls with xLine/xLineTo', () => {
it('angledLineToY with tag converts to yLineTo', async () => {
const { newCode, originalRange } = await testingSwapSketchFnCall({
inputCode: bigExample,
callToSwap: "angledLineToY({ angle: 330, to: 2.53, tag: 'abc7' }, %)",
callToSwap: "angledLineToY({ angle: 330, to: 2.53 }, %, 'abc7')",
constraintType: 'vertical',
})
const expectedLine = "yLineTo({ to: 2.53, tag: 'abc7' }, %)"
const expectedLine = "yLineTo(2.53, %, 'abc7')"
expect(newCode).toContain(expectedLine)
// new line should start at the same place as the old line
expect(originalRange[0]).toBe(newCode.indexOf(expectedLine))

View File

@ -131,7 +131,7 @@ const myAng = 40
const myAng2 = 134
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> line([1, 3.82], %, 'seg01') // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
myVar
@ -485,8 +485,7 @@ const part001 = startSketchOn('XY')
|> angledLine({
angle: halfArmAngle,
length: 2.45,
tag: 'seg01bing'
}, %) // partial
}, %, 'seg01bing') // partial
|> xLine(4.4, %) // partial
|> yLine(-1, %) // partial
|> xLine(-4.2 + 0, %) // full

View File

@ -60,11 +60,12 @@ function createCallWrapper(
tag?: Value,
valueUsedInTransform?: number
): ReturnType<TransformCallback> {
const args = [createFirstArg(a, val), createPipeSubstitution()]
if (tag) {
args.push(tag)
}
return {
callExp: createCallExpression(a, [
createFirstArg(a, val, tag),
createPipeSubstitution(),
]),
callExp: createCallExpression(a, args),
valueUsedInTransform,
}
}
@ -89,14 +90,15 @@ function intersectCallWrapper({
offset: offsetVal,
intersectTag,
}
const args: Value[] = [
createObjectExpression(firstArg),
createPipeSubstitution(),
]
if (tag) {
firstArg['tag'] = tag
args.push(tag)
}
return {
callExp: createCallExpression(fnName, [
createObjectExpression(firstArg),
createPipeSubstitution(),
]),
callExp: createCallExpression(fnName, args),
valueUsedInTransform,
}
}
@ -1419,7 +1421,8 @@ export function transformAstSketchLines({
const callExp = getNode<CallExpression>('CallExpression')?.node
const varDec = getNode<VariableDeclarator>('VariableDeclarator').node
const { val, tag: callBackTag } = getFirstArg(callExp)
const { val } = getFirstArg(callExp)
const callBackTag = callExp.arguments[2]
const _referencedSegmentNameVal =
callExp.arguments[0]?.type === 'ObjectExpression' &&
callExp.arguments[0].properties?.find(

View File

@ -7,14 +7,13 @@ describe('testing angledLineThatIntersects', () => {
it('angledLineThatIntersects should intersect with another line', async () => {
const code = (offset: string) => `const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo({to:[2, 2], tag: "yo"}, %)
|> lineTo([2, 2], %, "yo")
|> lineTo([3, 1], %)
|> angledLineThatIntersects({
angle: 180,
intersectTag: 'yo',
offset: ${offset},
tag: "yo2"
}, %)
}, %, "yo2")
const intersect = segEndX('yo2', part001)`
const { root } = await enginelessExecutor(parse(code('-1')))
expect(root.intersect.value).toBe(1 + Math.sqrt(2))

View File

@ -1,29 +1,38 @@
export const bracket = `const sigmaAllow = 15000 // psi
const width = 11 // inch
const p = 150 // Force on shelf - lbs
const distance = 12 // inches
const FOS = 2
const thickness = sqrt(distance * p * FOS * 6 / ( sigmaAllow * width ))
const filletR = thickness * 2
const shelfMountL = 9
const wallMountL = 8
export const bracket = `// Shelf Bracket
// This is a shelf bracket made out of 6061-T6 aluminum sheet metal. The required thickness is calculated based on a point load of 300 lbs applied to the end of the shelf. There are two brackets holding up the shelf, so the moment experienced is divided by 2. The shelf is 1 foot long from the wall.
const sigmaAllow = 35000 // psi
const width = 6 // inch
const p = 300 // Force on shelf - lbs
const distance = 12 // inches
const M = 12 * 300 / 2 // Moment experienced at fixed end of bracket
const FOS = 2 // Factor of safety of 2
const shelfMountL = 8 // The length of the bracket holding up the shelf is 6 inches
const wallMountL = 8 // the length of the bracket
// Calculate the thickness off the allowable bending stress and factor of safety
const thickness = sqrt(6 * M * FOS / (width * sigmaAllow))
// 0.25 inch fillet radius
const filletR = 0.25
// Sketch the bracket and extrude with fillets
const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, wallMountL], %)
|> tangentialArc({
radius: filletR,
offset: 90
}, %)
|> line([0, wallMountL], %, 'outerEdge')
|> line([-shelfMountL, 0], %)
|> line([0, -thickness], %)
|> line([shelfMountL, 0], %)
|> tangentialArc({
radius: filletR - thickness,
offset: -90
}, %)
|> line([0, -wallMountL], %)
|> line([shelfMountL - thickness, 0], %, 'innerEdge')
|> line([0, -wallMountL + thickness], %)
|> close(%)
|> extrude(width, %)
|> fillet({
radius: filletR,
tags: [getNextAdjacentEdge('innerEdge', %)]
}, %)
|> fillet({
radius: filletR + thickness,
tags: [getNextAdjacentEdge('outerEdge', %)]
}, %)
`

View File

@ -61,7 +61,7 @@ export default function ParametricModeling() {
<p className="my-4">
We are able to easily calculate the thickness of the material based
on the width of the bracket to meet a set safety factor on{' '}
<em className="text-energy-60 dark:text-energy-20">line 6</em>.
<em className="text-energy-60 dark:text-energy-20">line 14</em>.
</p>
</section>
<OnboardingButtons

View File

@ -1471,7 +1471,9 @@ dependencies = [
name = "grackle"
version = "0.1.0"
dependencies = [
"image",
"kcl-lib",
"kcl-macros",
"kittycad",
"kittycad-execution-plan",
"kittycad-execution-plan-macros",
@ -1482,6 +1484,7 @@ dependencies = [
"serde_json",
"thiserror",
"tokio",
"twenty-twenty",
"uuid",
]
@ -2007,7 +2010,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan"
version = "0.1.0"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"bytes",
"insta",
@ -2027,7 +2030,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan-macros"
version = "0.1.9"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"proc-macro2",
"quote",
@ -2037,7 +2040,7 @@ dependencies = [
[[package]]
name = "kittycad-execution-plan-traits"
version = "0.1.13"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"serde",
"thiserror",
@ -2046,8 +2049,8 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds"
version = "0.1.30"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
version = "0.1.32"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"anyhow",
"chrono",
@ -2075,7 +2078,7 @@ dependencies = [
[[package]]
name = "kittycad-modeling-cmds-macros"
version = "0.1.2"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"proc-macro2",
"quote",
@ -2085,11 +2088,12 @@ dependencies = [
[[package]]
name = "kittycad-modeling-session"
version = "0.1.1"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#4dfeb5c9ce2cc3fb853dd14cf948a922f3724ef4"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"futures",
"kittycad",
"kittycad-modeling-cmds",
"lsystem",
"reqwest",
"serde_json",
"thiserror",
@ -2187,6 +2191,12 @@ dependencies = [
"url",
]
[[package]]
name = "lsystem"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23c47210f2a9f5ae2073e7b847026e3233bcb012aa6845201c69c26481762a81"
[[package]]
name = "matchit"
version = "0.7.3"

View File

@ -6,7 +6,9 @@ description = "A new executor for KCL which compiles to Execution Plans"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
image = { version = "0.24.7", default-features = false, features = ["png"] }
kcl-lib = { path = "../kcl" }
kcl-macros = { path = "../kcl-macros/" }
kittycad = { workspace = true }
kittycad-execution-plan = { workspace = true }
kittycad-execution-plan-traits = { workspace = true }
@ -15,6 +17,7 @@ kittycad-modeling-cmds = { workspace = true }
kittycad-modeling-session = { workspace = true }
thiserror = "1.0.57"
tokio = { version = "1.36.0", features = ["macros", "rt"] }
twenty-twenty = "0.7.0"
uuid = "1.7"
[dev-dependencies]

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@ -2,13 +2,16 @@ use std::collections::HashMap;
use kcl_lib::ast::types::{LiteralIdentifier, LiteralValue};
use kittycad_execution_plan::constants;
use kittycad_execution_plan_traits::Primitive;
use super::{native_functions, Address};
use crate::{CompileError, KclFunction};
/// KCL values which can be written to KCEP memory.
/// This is recursive. For example, the bound value might be an array, which itself contains bound values.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
#[cfg_attr(test, derive(PartialEq))]
pub enum EpBinding {
/// A KCL value which gets stored in a particular address in KCEP memory.
Single(Address),
@ -23,6 +26,8 @@ pub enum EpBinding {
properties: HashMap<String, EpBinding>,
},
/// Not associated with a KCEP address.
Constant(Primitive),
/// Not associated with a KCEP address.
Function(KclFunction),
/// SketchGroups have their own storage.
SketchGroup { index: usize },
@ -52,11 +57,13 @@ impl EpBinding {
EpBinding::SketchGroup { .. } => Err(CompileError::CannotIndex),
EpBinding::Single(_) => Err(CompileError::CannotIndex),
EpBinding::Function(_) => Err(CompileError::CannotIndex),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
},
// Objects can be indexed by string properties.
LiteralValue::String(property) => match self {
EpBinding::Single(_) => Err(CompileError::NoProperties),
EpBinding::Function(_) => Err(CompileError::NoProperties),
EpBinding::Constant(_) => Err(CompileError::CannotIndex),
EpBinding::SketchGroup { .. } => Err(CompileError::NoProperties),
EpBinding::Sequence { .. } => Err(CompileError::ArrayDoesNotHaveProperties),
EpBinding::Map {
@ -103,8 +110,58 @@ impl BindingScope {
// TODO: Actually put the stdlib prelude in here,
// things like `startSketchAt` and `line`.
ep_bindings: HashMap::from([
("E".into(), EpBinding::Constant(constants::E)),
("PI".into(), EpBinding::Constant(constants::PI)),
("id".into(), EpBinding::from(KclFunction::Id(native_functions::Id))),
("abs".into(), EpBinding::from(KclFunction::Abs(native_functions::Abs))),
(
"acos".into(),
EpBinding::from(KclFunction::Acos(native_functions::Acos)),
),
(
"asin".into(),
EpBinding::from(KclFunction::Asin(native_functions::Asin)),
),
(
"atan".into(),
EpBinding::from(KclFunction::Atan(native_functions::Atan)),
),
(
"ceil".into(),
EpBinding::from(KclFunction::Ceil(native_functions::Ceil)),
),
("cos".into(), EpBinding::from(KclFunction::Cos(native_functions::Cos))),
(
"floor".into(),
EpBinding::from(KclFunction::Floor(native_functions::Floor)),
),
("ln".into(), EpBinding::from(KclFunction::Ln(native_functions::Ln))),
(
"log10".into(),
EpBinding::from(KclFunction::Log10(native_functions::Log10)),
),
(
"log2".into(),
EpBinding::from(KclFunction::Log2(native_functions::Log2)),
),
("sin".into(), EpBinding::from(KclFunction::Sin(native_functions::Sin))),
(
"sqrt".into(),
EpBinding::from(KclFunction::Sqrt(native_functions::Sqrt)),
),
("tan".into(), EpBinding::from(KclFunction::Tan(native_functions::Tan))),
(
"toDegrees".into(),
EpBinding::from(KclFunction::ToDegrees(native_functions::ToDegrees)),
),
(
"toRadians".into(),
EpBinding::from(KclFunction::ToRadians(native_functions::ToRadians)),
),
("add".into(), EpBinding::from(KclFunction::Add(native_functions::Add))),
("log".into(), EpBinding::from(KclFunction::Log(native_functions::Log))),
("max".into(), EpBinding::from(KclFunction::Max(native_functions::Max))),
("min".into(), EpBinding::from(KclFunction::Min(native_functions::Min))),
(
"startSketchAt".into(),
EpBinding::from(KclFunction::StartSketchAt(native_functions::sketch::StartSketchAt)),
@ -113,6 +170,26 @@ impl BindingScope {
"lineTo".into(),
EpBinding::from(KclFunction::LineTo(native_functions::sketch::LineTo)),
),
(
"line".into(),
EpBinding::from(KclFunction::Line(native_functions::sketch::Line)),
),
(
"xLineTo".into(),
EpBinding::from(KclFunction::XLineTo(native_functions::sketch::XLineTo)),
),
(
"xLine".into(),
EpBinding::from(KclFunction::XLine(native_functions::sketch::XLine)),
),
(
"yLineTo".into(),
EpBinding::from(KclFunction::YLineTo(native_functions::sketch::YLineTo)),
),
(
"yLine".into(),
EpBinding::from(KclFunction::YLine(native_functions::sketch::YLine)),
),
(
"extrude".into(),
EpBinding::from(KclFunction::Extrude(native_functions::sketch::Extrude)),

View File

@ -76,7 +76,7 @@ impl From<ExecutionFailed> for Error {
) -> Self {
Self::Execution {
error,
instruction,
instruction: instruction.expect("no instruction"),
instruction_index,
}
}

View File

@ -11,6 +11,8 @@ use kcl_lib::{
ast,
ast::types::{BodyItem, FunctionExpressionParts, KclNone, LiteralValue, Program},
};
extern crate alloc;
use kcl_macros::parse_file;
use kcl_value_group::into_single_value;
use kittycad_execution_plan::{self as ep, Destination, Instruction};
use kittycad_execution_plan_traits as ept;
@ -24,9 +26,11 @@ use self::{
};
/// Execute a KCL program by compiling into an execution plan, then running that.
pub async fn execute(ast: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
/// Include a `prelude.kcl` inlined as a `Program` struct thanks to a proc_macro `parse!`.
/// This makes additional functions available to the user.
pub async fn execute(ast_user: Program, session: &mut Option<Session>) -> Result<ep::Memory, Error> {
let mut planner = Planner::new();
let (plan, _retval) = planner.build_plan(ast)?;
let (plan, _retval) = planner.build_plan(ast_user)?;
let mut mem = ep::Memory::default();
ep::execute(&mut mem, plan, session).await?;
Ok(mem)
@ -54,7 +58,9 @@ impl Planner {
/// If successful, return the KCEP instructions for executing the given program.
/// If the program is a function with a return, then it also returns the KCL function's return value.
fn build_plan(&mut self, program: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
fn build_plan(&mut self, ast_user: Program) -> Result<(Vec<Instruction>, Option<EpBinding>), CompileError> {
let ast_prelude = parse_file!("./prelude.kcl");
let program = ast_prelude.merge(ast_user);
program
.body
.into_iter()
@ -259,9 +265,32 @@ impl Planner {
binding,
} = match callee {
KclFunction::Id(f) => f.call(&mut ctx, args)?,
KclFunction::Abs(f) => f.call(&mut ctx, args)?,
KclFunction::Acos(f) => f.call(&mut ctx, args)?,
KclFunction::Asin(f) => f.call(&mut ctx, args)?,
KclFunction::Atan(f) => f.call(&mut ctx, args)?,
KclFunction::Ceil(f) => f.call(&mut ctx, args)?,
KclFunction::Cos(f) => f.call(&mut ctx, args)?,
KclFunction::Floor(f) => f.call(&mut ctx, args)?,
KclFunction::Ln(f) => f.call(&mut ctx, args)?,
KclFunction::Log10(f) => f.call(&mut ctx, args)?,
KclFunction::Log2(f) => f.call(&mut ctx, args)?,
KclFunction::Sin(f) => f.call(&mut ctx, args)?,
KclFunction::Sqrt(f) => f.call(&mut ctx, args)?,
KclFunction::Tan(f) => f.call(&mut ctx, args)?,
KclFunction::ToDegrees(f) => f.call(&mut ctx, args)?,
KclFunction::ToRadians(f) => f.call(&mut ctx, args)?,
KclFunction::Log(f) => f.call(&mut ctx, args)?,
KclFunction::Max(f) => f.call(&mut ctx, args)?,
KclFunction::Min(f) => f.call(&mut ctx, args)?,
KclFunction::StartSketchAt(f) => f.call(&mut ctx, args)?,
KclFunction::Extrude(f) => f.call(&mut ctx, args)?,
KclFunction::LineTo(f) => f.call(&mut ctx, args)?,
KclFunction::Line(f) => f.call(&mut ctx, args)?,
KclFunction::XLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::XLine(f) => f.call(&mut ctx, args)?,
KclFunction::YLineTo(f) => f.call(&mut ctx, args)?,
KclFunction::YLine(f) => f.call(&mut ctx, args)?,
KclFunction::Add(f) => f.call(&mut ctx, args)?,
KclFunction::Close(f) => f.call(&mut ctx, args)?,
KclFunction::UserDefined(f) => {
@ -629,9 +658,32 @@ impl Eq for UserDefinedFunction {}
#[cfg_attr(test, derive(Eq, PartialEq))]
enum KclFunction {
Id(native_functions::Id),
Abs(native_functions::Abs),
Acos(native_functions::Acos),
Asin(native_functions::Asin),
Atan(native_functions::Atan),
Ceil(native_functions::Ceil),
Cos(native_functions::Cos),
Floor(native_functions::Floor),
Ln(native_functions::Ln),
Log10(native_functions::Log10),
Log2(native_functions::Log2),
Sin(native_functions::Sin),
Sqrt(native_functions::Sqrt),
Tan(native_functions::Tan),
ToDegrees(native_functions::ToDegrees),
ToRadians(native_functions::ToRadians),
StartSketchAt(native_functions::sketch::StartSketchAt),
LineTo(native_functions::sketch::LineTo),
Line(native_functions::sketch::Line),
XLineTo(native_functions::sketch::XLineTo),
XLine(native_functions::sketch::XLine),
YLineTo(native_functions::sketch::YLineTo),
YLine(native_functions::sketch::YLine),
Add(native_functions::Add),
Log(native_functions::Log),
Max(native_functions::Max),
Min(native_functions::Min),
UserDefined(UserDefinedFunction),
Extrude(native_functions::sketch::Extrude),
Close(native_functions::sketch::Close),

View File

@ -2,18 +2,15 @@
//! This includes some of the stdlib, e.g. `startSketchAt`.
//! But some other stdlib functions will be written in KCL.
use kittycad_execution_plan::{BinaryArithmetic, Destination, Instruction};
use kittycad_execution_plan::{
BinaryArithmetic, BinaryOperation, Destination, Instruction, Operand, UnaryArithmetic, UnaryOperation,
};
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))]
pub struct Id;
pub trait Callable {
fn call(&self, ctx: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError>;
}
@ -32,6 +29,65 @@ impl<'a> Context<'a> {
}
}
/// Unary operator macro to quickly create new bindings.
macro_rules! define_unary {
() => {};
($fn_name:ident$( $rest:ident)*) => {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $fn_name;
impl Callable for $fn_name {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
return Err(CompileError::TooManyArgs {
fn_name: "$fn_name".into(),
maximum: 1,
actual: args.len(),
});
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$fn_name".into(),
required: 1,
actual: args.len(),
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand("A single value binding is expected"));
};
let destination = ctx.next_address.offset_by(1);
let instructions = vec![
Instruction::UnaryArithmetic {
arithmetic: UnaryArithmetic {
operation: UnaryOperation::$fn_name,
operand: Operand::Reference(arg0)
},
destination: Destination::Address(destination)
}
];
Ok(EvalPlan {
instructions,
binding: EpBinding::Single(destination),
})
}
}
define_unary!($($rest)*);
};
}
define_unary!(Abs Acos Asin Atan Ceil Cos Floor Ln Log10 Log2 Sin Sqrt Tan ToDegrees ToRadians);
/// The identity function. Always returns its first input.
/// Implemented purely on the KCL side so it doesn't need to be in the
/// define_unary! macro above.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Id;
impl Callable for Id {
fn call(&self, _: &mut Context<'_>, args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
if args.len() > 1 {
@ -56,44 +112,53 @@ impl Callable for Id {
}
}
/// A test function that adds two numbers.
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Add;
/// Binary operator macro to quickly create new bindings.
macro_rules! define_binary {
() => {};
($fn_name:ident$( $rest:ident)*) => {
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct $fn_name;
impl Callable for Add {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let len = args.len();
if len > 2 {
return Err(CompileError::TooManyArgs {
fn_name: "add".into(),
maximum: 2,
actual: len,
});
impl Callable for $fn_name {
fn call(&self, ctx: &mut Context<'_>, mut args: Vec<EpBinding>) -> Result<EvalPlan, CompileError> {
let len = args.len();
if len > 2 {
return Err(CompileError::TooManyArgs {
fn_name: "$fn_name".into(),
maximum: 2,
actual: len,
});
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "$fn_name".into(),
required: 2,
actual: len,
};
const ERR: &str = "cannot use composite values (e.g. array) as arguments to $fn_name";
let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand(ERR));
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else {
return Err(CompileError::InvalidOperand(ERR));
};
let destination = ctx.next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::$fn_name,
operand0: Operand::Reference(arg0),
operand1: Operand::Reference(arg1),
},
destination: Destination::Address(destination),
}],
binding: EpBinding::Single(destination),
})
}
let not_enough_args = CompileError::NotEnoughArgs {
fn_name: "add".into(),
required: 2,
actual: len,
};
const ERR: &str = "cannot use composite values (e.g. array) as arguments to Add";
let EpBinding::Single(arg1) = args.pop().ok_or(not_enough_args.clone())? else {
return Err(CompileError::InvalidOperand(ERR));
};
let EpBinding::Single(arg0) = args.pop().ok_or(not_enough_args)? else {
return Err(CompileError::InvalidOperand(ERR));
};
let destination = ctx.next_address.offset_by(1);
Ok(EvalPlan {
instructions: vec![Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: kittycad_execution_plan::BinaryOperation::Add,
operand0: kittycad_execution_plan::Operand::Reference(arg0),
operand1: kittycad_execution_plan::Operand::Reference(arg1),
},
destination: Destination::Address(destination),
}],
binding: EpBinding::Single(destination),
})
}
define_binary!($($rest)*);
};
}
define_binary!(Add Log Max Min);

View File

@ -3,4 +3,4 @@
pub mod helpers;
pub mod stdlib_functions;
pub use stdlib_functions::{Close, Extrude, LineTo, StartSketchAt};
pub use stdlib_functions::{Close, Extrude, Line, LineTo, StartSketchAt, XLine, XLineTo, YLine, YLineTo};

View File

@ -67,6 +67,12 @@ pub fn sg_binding(
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
pub fn single_binding(
@ -101,6 +107,12 @@ pub fn single_binding(
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
@ -136,10 +148,16 @@ pub fn sequence_binding(
actual: "function".to_owned(),
arg_number,
}),
EpBinding::Constant(_) => Err(CompileError::ArgWrongType {
fn_name,
expected,
actual: "constant".to_owned(),
arg_number,
}),
}
}
/// Extract a 2D point from an argument to a Cabble.
/// Extract a 2D point from an argument to a KCL Function.
pub fn arg_point2d(
arg: EpBinding,
fn_name: &'static str,
@ -148,7 +166,7 @@ pub fn arg_point2d(
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)?;
let elements = sequence_binding(arg, fn_name, "an array of length 2", arg_number)?;
if elements.len() != 2 {
return Err(CompileError::ArgWrongType {
fn_name,
@ -165,12 +183,12 @@ pub fn arg_point2d(
let start_z = start + 2;
instructions.extend([
Instruction::Copy {
source: single_binding(elements[0].clone(), "startSketchAt", "number", arg_number)?,
source: single_binding(elements[0].clone(), fn_name, "number", arg_number)?,
destination: Destination::Address(start_x),
length: 1,
},
Instruction::Copy {
source: single_binding(elements[1].clone(), "startSketchAt", "number", arg_number)?,
source: single_binding(elements[1].clone(), fn_name, "number", arg_number)?,
destination: Destination::Address(start_y),
length: 1,
},

View File

@ -1,7 +1,7 @@
use kittycad_execution_plan::{
api_request::ApiRequest,
sketch_types::{self, Axes, BasePath, Plane, SketchGroup},
Destination, Instruction,
BinaryArithmetic, BinaryOperation, Destination, Instruction, Operand,
};
use kittycad_execution_plan_traits::{Address, InMemory, Primitive, Value};
use kittycad_modeling_cmds::{
@ -13,6 +13,22 @@ use uuid::Uuid;
use super::helpers::{arg_point2d, no_arg_api_call, sg_binding, single_binding, stack_api_call};
use crate::{binding_scope::EpBinding, error::CompileError, native_functions::Callable, EvalPlan};
#[derive(PartialEq)]
pub enum At {
RelativeXY,
AbsoluteXY,
RelativeX,
AbsoluteX,
RelativeY,
AbsoluteY,
}
impl At {
pub fn is_relative(&self) -> bool {
*self == At::RelativeX || *self == At::RelativeY || *self == At::RelativeXY
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Close;
@ -140,25 +156,124 @@ impl Callable for LineTo {
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "lineTo", args, LineBareOptions { at: At::AbsoluteXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct Line;
impl Callable for Line {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "line", args, LineBareOptions { at: At::RelativeXY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLineTo;
impl Callable for XLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLineTo", args, LineBareOptions { at: At::AbsoluteX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct XLine;
impl Callable for XLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "xLine", args, LineBareOptions { at: At::RelativeX })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLineTo;
impl Callable for YLineTo {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLineTo", args, LineBareOptions { at: At::AbsoluteY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
pub struct YLine;
impl Callable for YLine {
fn call(
&self,
ctx: &mut crate::native_functions::Context<'_>,
args: Vec<EpBinding>,
) -> Result<EvalPlan, CompileError> {
LineBare::call(ctx, "yLine", args, LineBareOptions { at: At::RelativeY })
}
}
#[derive(Debug, Clone)]
#[cfg_attr(test, derive(Eq, PartialEq))]
/// Exposes all the possible arguments the `line` modeling command can take.
/// Reduces code for the other line functions needed.
/// We do not expose this to the developer since it does not align with
/// the documentation (there is no "lineBare").
pub struct LineBare;
/// Used to configure the call to handle different line variants.
pub struct LineBareOptions {
/// Where to start coordinates at, ex: At::RelativeXY.
at: At,
}
impl LineBare {
fn call(
ctx: &mut crate::native_functions::Context<'_>,
fn_name: &'static str,
args: Vec<EpBinding>,
opts: LineBareOptions,
) -> Result<EvalPlan, CompileError> {
let mut instructions = Vec::new();
let fn_name = "lineTo";
// Get both required params.
let required = 2;
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,
required,
actual: args_iter.count(),
});
};
let Some(sketch_group) = args_iter.next() else {
return Err(CompileError::NotEnoughArgs {
fn_name: fn_name.into(),
required: 2,
actual: 1,
required,
actual: args_iter.count(),
});
};
let tag = match args_iter.next() {
Some(a) => a,
None => {
@ -171,26 +286,90 @@ impl Callable for LineTo {
EpBinding::Single(empty_string_addr)
}
};
// Check the type of required params.
let to = arg_point2d(to, fn_name, &mut instructions, ctx, 0)?;
// We don't check `to` here because it can take on either a
// EpBinding::Sequence or EpBinding::Single.
let sg = sg_binding(sketch_group, fn_name, "sketch group", 1)?;
let tag = single_binding(tag, fn_name, "string tag", 2)?;
let id = Uuid::new_v4();
// Start of the path segment (which is a straight line).
let length_of_3d_point = Point3d::<f64>::default().into_parts().len();
let start_of_line = ctx.next_address.offset_by(1);
// Reserve space for the line's end, and the `relative: bool` field.
ctx.next_address.offset_by(length_of_3d_point + 1);
let new_sg_index = ctx.assign_sketch_group();
// Copy based on the options.
match opts {
LineBareOptions { at: At::AbsoluteXY, .. } | LineBareOptions { at: At::RelativeXY, .. } => {
// Push the `to` 2D point onto the stack.
let EpBinding::Sequence { elements, length_at: _ } = to.clone() else {
return Err(CompileError::InvalidOperand("Must pass a list of length 2"));
};
let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() else {
return Err(CompileError::InvalidOperand("Must pass a sequence here."));
};
instructions.extend([
Instruction::Copy {
// X
source: el0,
length: 1,
destination: Destination::StackPush,
},
Instruction::Copy {
// Y
source: el1,
length: 1,
destination: Destination::StackExtend,
},
Instruction::StackExtend { data: vec![0.0.into()] }, // Z
]);
}
LineBareOptions { at: At::AbsoluteX, .. } | LineBareOptions { at: At::RelativeX, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
Instruction::Copy {
// X
source: addr,
length: 1,
destination: Destination::StackPush,
},
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Y
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Z
]);
}
LineBareOptions { at: At::AbsoluteY, .. } | LineBareOptions { at: At::RelativeY, .. } => {
let EpBinding::Single(addr) = to else {
return Err(CompileError::InvalidOperand("Must pass a single value here."));
};
instructions.extend([
Instruction::StackPush {
data: vec![Primitive::from(0.0)],
}, // X
Instruction::Copy {
// Y
source: addr,
length: 1,
destination: Destination::StackExtend,
},
Instruction::StackExtend {
data: vec![Primitive::from(0.0)],
}, // Z
]);
}
}
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 {
@ -204,7 +383,7 @@ impl Callable for LineTo {
// Then its `relative` field.
Instruction::SetPrimitive {
address: start_of_line + 1 + length_of_3d_point,
value: false.into(),
value: opts.at.is_relative().into(),
},
// Push the path ID onto the stack.
Instruction::SketchGroupCopyFrom {
@ -231,16 +410,159 @@ impl Callable for LineTo {
data: vec![Primitive::from("ToPoint".to_owned())],
},
// `BasePath::from` point.
// Place them in the secondary stack to prepare ToPoint structure.
Instruction::SketchGroupGetLastPoint {
source: sg,
destination: Destination::StackExtend,
},
// `BasePath::to` point.
Instruction::Copy {
source: start_of_line + 1,
length: 2,
destination: Destination::StackExtend,
]);
// Reserve space for the segment last point
let to_point_from = ctx.next_address.offset_by(2);
instructions.extend([
// Copy to the primary stack as well to be worked with.
Instruction::SketchGroupGetLastPoint {
source: sg,
destination: Destination::Address(to_point_from),
},
]);
// `BasePath::to` point.
// The copy here depends on the incoming `to` data.
// Sometimes it's a list, sometimes it's single datum.
// And the relative/not relative matters. When relative, we need to
// copy coords from `from` into the new `to` coord that don't change.
// At least everything else can be built up from these "primitives".
if let EpBinding::Sequence { elements, length_at: _ } = to.clone() {
if let &[EpBinding::Single(el0), EpBinding::Single(el1)] = elements.as_slice() {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 + y2 } }
LineBareOptions { at: At::RelativeXY, .. } => {
instructions.extend([
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(el0),
},
destination: Destination::StackExtend,
},
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(el1),
},
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y2 } }
LineBareOptions { at: At::AbsoluteXY, .. } => {
// Otherwise just directly copy the new points.
instructions.extend([
Instruction::Copy {
source: el0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: el1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Sequence with At::...X or At::...Y is not valid here. Must be At::...XY.",
));
}
}
}
} else if let EpBinding::Single(addr) = to {
match opts {
// ToPoint { from: { x1, y1 }, to: { x1 + x2, y1 } }
LineBareOptions { at: At::RelativeX } => {
instructions.extend([
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 0),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
},
Instruction::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x2, y1 } }
LineBareOptions { at: At::AbsoluteX } => {
instructions.extend([
Instruction::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: to_point_from + 1,
length: 1,
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y1 + y2 } }
LineBareOptions { at: At::RelativeY } => {
instructions.extend([
Instruction::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::BinaryArithmetic {
arithmetic: BinaryArithmetic {
operation: BinaryOperation::Add,
operand0: Operand::Reference(to_point_from + 1),
operand1: Operand::Reference(addr),
},
destination: Destination::StackExtend,
},
]);
}
// ToPoint { from: { x1, y1 }, to: { x1, y2 } }
LineBareOptions { at: At::AbsoluteY } => {
instructions.extend([
Instruction::Copy {
source: to_point_from + 0,
length: 1,
destination: Destination::StackExtend,
},
Instruction::Copy {
source: addr,
length: 1,
destination: Destination::StackExtend,
},
]);
}
_ => {
return Err(CompileError::InvalidOperand(
"A Single binding with At::...XY is not valid here.",
));
}
}
} else {
return Err(CompileError::InvalidOperand(
"Must be a sequence or single value binding.",
));
}
instructions.extend([
// `BasePath::name` string.
Instruction::Copy {
source: tag,

View File

@ -1,7 +1,7 @@
use std::{collections::HashMap, env};
use ep::{sketch_types, Destination, UnaryArithmetic};
use ept::{ListHeader, ObjectHeader};
use ep::{constants, sketch_types, Destination, UnaryArithmetic};
use ept::{ListHeader, ObjectHeader, Primitive};
use kittycad_modeling_cmds::shared::Point2d;
use kittycad_modeling_session::SessionBuilder;
use pretty_assertions::assert_eq;
@ -1048,14 +1048,10 @@ fn store_object_with_array_property() {
/// Write the program's plan to the KCVM debugger's normal input file.
#[allow(unused)]
fn kcvm_dbg(kcl_program: &str) {
fn kcvm_dbg(kcl_program: &str, path: &str) {
let (plan, _scope, _) = must_plan(kcl_program);
let plan_json = serde_json::to_string_pretty(&plan).unwrap();
std::fs::write(
"/Users/adamchalmers/kc-repos/modeling-api/execution-plan-debugger/test_input.json",
plan_json,
)
.unwrap();
std::fs::write(path, plan_json).unwrap();
}
#[tokio::test]
@ -1069,8 +1065,6 @@ async fn stdlib_cube_partial() {
|> close(%)
|> extrude(100.0, %)
"#;
let (_plan, _scope, last_address) = must_plan(program);
assert_eq!(last_address, Address::ZERO + 66);
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
@ -1113,23 +1107,115 @@ async fn stdlib_cube_partial() {
},
]
);
// use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
// let out = client
// .unwrap()
// .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();
// std::fs::write("image.png", out).unwrap();
use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
let out = client
.unwrap()
.run_command(
uuid::Uuid::new_v4().into(),
kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot {
format: ImageFormat::Png,
}),
)
.await
.unwrap();
let out = match out {
OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b,
other => panic!("wrong output: {other:?}"),
};
use image::io::Reader as ImageReader;
let img = ImageReader::new(std::io::Cursor::new(out))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
twenty_twenty::assert_image("fixtures/cube_lineTo.png", &img, 0.9999);
}
#[tokio::test]
async fn stdlib_cube_xline_yline() {
let program = r#"
let cube = startSketchAt([0.0, 0.0], "adam")
|> xLine(210.0, %, "side0")
|> yLine(210.0, %, "side1")
|> xLine(-210.0, %, "side2")
|> yLine(-210.0, %, "side3")
|> close(%)
|> extrude(100.0, %)
"#;
kcvm_dbg(
program,
"/home/lee/Code/Zoo/modeling-api/execution-plan-debugger/cube_xyline.json",
);
let (_plan, _scope, _last_address) = must_plan(program);
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mut client = Some(test_client().await);
let mem = match crate::execute(ast, &mut client).await {
Ok(mem) => mem,
Err(e) => panic!("{e}"),
};
let sg = &mem.sketch_groups.last().unwrap();
assert_eq!(
sg.path_rest,
vec![
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 0.0, y: 0.0 },
to: Point2d { x: 210.0, y: 0.0 },
name: "side0".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 210.0, y: 0.0 },
to: Point2d { x: 210.0, y: 210.0 },
name: "side1".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 210.0, y: 210.0 },
to: Point2d { x: 0.0, y: 210.0 },
name: "side2".into(),
}
},
sketch_types::PathSegment::ToPoint {
base: sketch_types::BasePath {
from: Point2d { x: 0.0, y: 210.0 },
to: Point2d { x: 0.0, y: 0.0 },
name: "side3".into(),
}
},
]
);
use kittycad_modeling_cmds::{each_cmd, ok_response::OkModelingCmdResponse, ImageFormat};
let out = client
.unwrap()
.run_command(
uuid::Uuid::new_v4().into(),
kittycad_modeling_cmds::ModelingCmd::from(each_cmd::TakeSnapshot {
format: ImageFormat::Png,
}),
)
.await
.unwrap();
let out = match out {
OkModelingCmdResponse::TakeSnapshot(kittycad_modeling_cmds::output::TakeSnapshot { contents: b }) => b,
other => panic!("wrong output: {other:?}"),
};
use image::io::Reader as ImageReader;
let img = ImageReader::new(std::io::Cursor::new(out))
.with_guessed_format()
.unwrap()
.decode()
.unwrap();
twenty_twenty::assert_image("fixtures/cube_xyLine.png", &img, 0.9999);
}
async fn test_client() -> Session {
@ -1328,3 +1414,51 @@ fn mod_and_pow() {
]
);
}
#[tokio::test]
async fn cos_sin_pi() {
let program = "
let x = cos(45.0)*10
let y = sin(45.0)*10
let z = PI
";
let (_plan, scope, _) = must_plan(program);
let Some(EpBinding::Single(x)) = scope.get("x") else {
panic!("Unexpected binding for variable 'x': {:?}", scope.get("x"));
};
let Some(EpBinding::Single(y)) = scope.get("y") else {
panic!("Unexpected binding for variable 'y': {:?}", scope.get("y"));
};
let Some(EpBinding::Constant(z)) = scope.get("z") else {
panic!("Unexpected binding for variable 'z': {:?}", scope.get("z"));
};
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mem = crate::execute(ast, &mut None).await.unwrap();
use ept::ReadMemory;
assert_eq!(*mem.get(x).unwrap(), Primitive::from(5.253219888177298));
assert_eq!(*mem.get(y).unwrap(), Primitive::from(8.509035245341185));
// Constants don't live in memory.
assert_eq!(*z, constants::PI);
}
#[tokio::test]
async fn kcl_prelude() {
let program = "
let the_answer_to_the_universe_is = hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this()
";
let (_plan, scope, _) = must_plan(program);
let Some(EpBinding::Single(the_answer_to_the_universe_is)) = scope.get("the_answer_to_the_universe_is") else {
panic!(
"Unexpected binding for variable 'the_answer_to_the_universe_is': {:?}",
scope.get("the_answer_to_the_universe_is")
);
};
let ast = kcl_lib::parser::Parser::new(kcl_lib::token::lexer(program))
.ast()
.unwrap();
let mem = crate::execute(ast, &mut None).await.unwrap();
use ept::ReadMemory;
assert_eq!(*mem.get(the_answer_to_the_universe_is).unwrap(), Primitive::from(42i64));
}

View File

@ -8,7 +8,6 @@ use syn::{parse_macro_input, LitStr};
/// This macro takes exactly one argument: A string literal containing KCL.
/// # Examples
/// ```
/// extern crate alloc;
/// use kcl_compile_macro::parse_kcl;
/// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4");
/// ```
@ -21,3 +20,14 @@ pub fn parse(input: TokenStream) -> TokenStream {
let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into()
}
/// Same as `parse!` but read in a file.
#[proc_macro]
pub fn parse_file(input: TokenStream) -> TokenStream {
let file_name = parse_macro_input!(input as LitStr);
let kcl_src = std::fs::read_to_string(file_name.value()).unwrap();
let tokens = kcl_lib::token::lexer(&kcl_src);
let ast = kcl_lib::parser::Parser::new(tokens).ast().unwrap();
let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into()
}

View File

@ -7,6 +7,10 @@ name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
dependencies = [
"lazy_static",
"regex",
]
[[package]]
name = "addr2line"
@ -45,10 +49,25 @@ dependencies = [
]
[[package]]
name = "anyhow"
version = "1.0.80"
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247"
dependencies = [
"backtrace",
]
@ -92,7 +111,7 @@ checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -103,7 +122,7 @@ checksum = "c980ee35e870bd1a4d2c8294d4c04d0499e67bca1e4b5cefcc693c2fa00caea9"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -268,12 +287,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.29"
version = "0.4.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d87d9d13be47a5b7c3907137f1290b0459a7f80efb26be8c52afb11963bccb02"
checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"serde",
"wasm-bindgen",
"windows-targets 0.52.0",
]
[[package]]
@ -320,6 +344,21 @@ dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345"
[[package]]
name = "crypto-common"
version = "0.1.6"
@ -368,7 +407,7 @@ checksum = "377af281d8f23663862a7c84623bc5dcf7f8c44b13c7496a590bdc157f941a43"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
"synstructure",
]
@ -380,10 +419,11 @@ checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]]
name = "derive-docs"
version = "0.1.6"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "834580a8bd697658876ed8c9f7727e49f01d34f5b859ca921ac5b99ffc6adf77"
checksum = "b4e18a04fe569a325dd50743821f2605057ff5c4b48e60512270b4406907825b"
dependencies = [
"Inflector",
"convert_case",
"once_cell",
"proc-macro2",
@ -391,7 +431,7 @@ dependencies = [
"regex",
"serde",
"serde_tokenstream",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -521,7 +561,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -592,7 +632,7 @@ dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -739,6 +779,29 @@ dependencies = [
"tokio-rustls 0.24.1",
]
[[package]]
name = "iana-time-zone"
version = "0.1.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -807,22 +870,23 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.68"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "kcl-lib"
version = "0.1.42"
version = "0.1.46"
dependencies = [
"anyhow",
"approx",
"async-recursion",
"async-trait",
"bson",
"chrono",
"dashmap",
"databake",
"derive-docs",
@ -833,12 +897,14 @@ dependencies = [
"kittycad-execution-plan-macros",
"kittycad-execution-plan-traits",
"lazy_static",
"mime_guess",
"parse-display 0.9.0",
"reqwest",
"ropey",
"schemars",
"serde",
"serde_json",
"sha2",
"thiserror",
"tokio",
"tokio-tungstenite",
@ -849,6 +915,7 @@ dependencies = [
"wasm-bindgen-futures",
"web-sys",
"winnow",
"zip",
]
[[package]]
@ -861,9 +928,9 @@ dependencies = [
[[package]]
name = "kittycad"
version = "0.2.54"
version = "0.2.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13958174d876353f429ea8230dc92fe86f164819cea2e51bbf22e01a4c2a496e"
checksum = "f8aa5906d0730bd90f6b3331fe57c04951d00743169a29ee96408767b4060605"
dependencies = [
"anyhow",
"async-trait",
@ -877,6 +944,7 @@ dependencies = [
"http 0.2.9",
"itertools",
"log",
"mime_guess",
"parse-display 0.8.2",
"phonenumber",
"rand",
@ -899,14 +967,13 @@ source = "git+https://github.com/KittyCAD/modeling-api?branch=main#1e6ef9601686c
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[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.13"
source = "git+https://github.com/KittyCAD/modeling-api?branch=main#494225aaac06fab77c4822e7dc48738ecca35392"
dependencies = [
"serde",
"thiserror",
@ -1028,9 +1095,9 @@ dependencies = [
[[package]]
name = "mio"
version = "0.8.10"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3d0b296e374a4e6f3c7b0a1f5a51d748a0d34c85e7dc48fc3fa9a87657fe09"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
dependencies = [
"libc",
"wasi",
@ -1171,7 +1238,7 @@ dependencies = [
"regex",
"regex-syntax 0.7.5",
"structmeta 0.2.0",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1185,7 +1252,7 @@ dependencies = [
"regex",
"regex-syntax 0.8.2",
"structmeta 0.3.0",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1232,7 +1299,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1404,9 +1471,9 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "reqwest"
version = "0.11.24"
version = "0.11.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62"
dependencies = [
"base64 0.21.3",
"bytes",
@ -1692,7 +1759,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1726,7 +1793,7 @@ checksum = "8725e1dfadb3a50f7e5ce0b1a540466f6ed3fe7a0fca2ac2b8b831d31316bd00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1738,7 +1805,7 @@ dependencies = [
"proc-macro2",
"quote",
"serde",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1776,6 +1843,17 @@ dependencies = [
"digest",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
@ -1847,7 +1925,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive 0.2.0",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1859,7 +1937,7 @@ dependencies = [
"proc-macro2",
"quote",
"structmeta-derive 0.3.0",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1870,7 +1948,7 @@ checksum = "a60bcaff7397072dca0017d1db428e30d5002e00b6847703e2e42005c95fbe00"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1881,7 +1959,7 @@ checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -1925,9 +2003,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.50"
version = "2.0.53"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb"
checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032"
dependencies = [
"proc-macro2",
"quote",
@ -1948,7 +2026,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -2004,7 +2082,7 @@ checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -2077,7 +2155,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -2183,7 +2261,7 @@ checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -2212,7 +2290,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
]
[[package]]
@ -2250,7 +2328,7 @@ dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
"termcolor",
]
@ -2382,9 +2460,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
@ -2392,24 +2470,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.41"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
@ -2419,9 +2497,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -2429,22 +2507,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.50",
"syn 2.0.53",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "wasm-streams"
@ -2461,9 +2539,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.68"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -2506,6 +2584,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
@ -2680,3 +2767,14 @@ name = "zeroize"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
[[package]]
name = "zip"
version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
dependencies = [
"byteorder",
"crc32fast",
"crossbeam-utils",
]

View File

@ -351,6 +351,31 @@ impl Program {
None
}
/// Merge two `Program`s together.
pub fn merge(self, ast_other: Program) -> Program {
let mut body_new: Vec<BodyItem> = vec![];
body_new.extend(self.body);
body_new.extend(ast_other.body);
let mut non_code_nodes_new: HashMap<usize, Vec<NonCodeNode>> = HashMap::new();
non_code_nodes_new.extend(self.non_code_meta.non_code_nodes);
non_code_nodes_new.extend(ast_other.non_code_meta.non_code_nodes);
let mut start_new: Vec<NonCodeNode> = vec![];
start_new.extend(self.non_code_meta.start);
start_new.extend(ast_other.non_code_meta.start);
Program {
start: self.start,
end: self.end + (ast_other.end - ast_other.start),
body: body_new,
non_code_meta: NonCodeMeta {
non_code_nodes: non_code_nodes_new,
start: start_new,
},
}
}
}
pub trait ValueMeta {
@ -2519,7 +2544,17 @@ impl UnaryExpression {
}
fn recast(&self, options: &FormatOptions) -> String {
format!("{}{}", &self.operator, self.argument.recast(options, 0))
match self.argument {
BinaryPart::Literal(_)
| BinaryPart::Identifier(_)
| BinaryPart::MemberExpression(_)
| BinaryPart::CallExpression(_) => {
format!("{}{}", &self.operator, self.argument.recast(options, 0))
}
BinaryPart::BinaryExpression(_) | BinaryPart::UnaryExpression(_) => {
format!("{}({})", &self.operator, self.argument.recast(options, 0))
}
}
}
pub async fn get_result(
@ -3351,7 +3386,7 @@ const mySk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([0, 1], %, 'myTag')
|> lineTo([1, 1], %)
/* and
here
@ -3374,7 +3409,7 @@ const mySk1 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([1, 1], %)
// comment here
|> lineTo({ to: [0, 1], tag: 'myTag' }, %)
|> lineTo([0, 1], %, 'myTag')
|> lineTo([1, 1], %)
/* and
here */
@ -3392,7 +3427,7 @@ const mySk1 = startSketchOn('XY')
fn test_recast_multiline_object() {
let some_program_string = r#"const part001 = startSketchOn('XY')
|> startProfileAt([-0.01, -0.08], %)
|> line({ to: [0.62, 4.15], tag: 'seg01' }, %)
|> line([0.62, 4.15], %, 'seg01')
|> line([2.77, -1.24], %)
|> angledLineThatIntersects({
angle: 201,
@ -3482,7 +3517,7 @@ const myAng = 40
const myAng2 = 134
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> line([1, 3.82], %, 'seg01') // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
myVar
@ -3508,7 +3543,7 @@ const myAng = 40
const myAng2 = 134
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [1, 3.82], tag: 'seg01' }, %) // ln-should-get-tag
|> line([1, 3.82], %, 'seg01') // ln-should-get-tag
|> angledLineToX([
-angleToMatchLengthX('seg01', myVar, %),
myVar
@ -3774,6 +3809,25 @@ const firstExtrude = startSketchOn('XY')
assert_eq!(recasted.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_negate_parens() {
let some_program_string = r#"const wallMountL = 3.82
const thickness = 0.5
startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line([0, -(wallMountL - thickness)], %)
|> line([0, -(5 - thickness)], %)
|> line([0, -(5 - 1)], %)
|> line([0, -(-5 - 1)], %)"#;
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.trim(), some_program_string);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_recast_math_nested_parens() {
let some_program_string = r#"const distance = 5

View File

@ -1323,14 +1323,13 @@ const newVar = myVar + 1"#;
format!(
r#"const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo({{to:[2, 2], tag: "yo"}}, %)
|> lineTo([2, 2], %, "yo")
|> lineTo([3, 1], %)
|> angledLineThatIntersects({{
angle: 180,
intersectTag: 'yo',
offset: {},
tag: "yo2"
}}, %)
}}, %, 'yo2')
const intersect = segEndX('yo2', part001)"#,
offset
)
@ -1387,7 +1386,7 @@ const yo2 = hmm([identifierGuy + 5])"#;
let ast = r#"const myVar = 3
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [3, 4], tag: 'seg01' }, %)
|> line([3, 4], %, 'seg01')
|> line([
min(segLen('seg01', %), myVar),
-legLen(segLen('seg01', %), myVar)
@ -1402,7 +1401,7 @@ const part001 = startSketchOn('XY')
let ast = r#"const myVar = 3
const part001 = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> line({ to: [3, 4], tag: 'seg01' }, %)
|> line([3, 4], %, 'seg01')
|> line([
min(segLen('seg01', %), myVar),
legLen(segLen('seg01', %), myVar)

View File

@ -1124,24 +1124,13 @@ async fn test_copilot_lsp_completions_raw() {
let completions = server
.get_completions(
"kcl".to_string(),
r#"// Create a cube.
fn cube = (pos, scale) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const part001 = cube([0,0], 20)
|> close(%)
|> extrude(20, %)
"#
r#"const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
"#
.to_string(),
r#""#.to_string(),
r#" |> close(%)
|> extrude(10, %)"#
.to_string(),
)
.await
.unwrap();
@ -1154,24 +1143,13 @@ const part001 = cube([0,0], 20)
let completions_hit_cache = server
.get_completions(
"kcl".to_string(),
r#"// Create a cube.
fn cube = (pos, scale) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const part001 = cube([0,0], 20)
|> close(%)
|> extrude(20, %)
"#
r#"const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
"#
.to_string(),
r#""#.to_string(),
r#" |> close(%)
|> extrude(10, %)"#
.to_string(),
)
.await
.unwrap();
@ -1202,23 +1180,13 @@ async fn test_copilot_lsp_completions() {
insert_spaces: true,
language_id: "kcl".to_string(),
path: "file:///test.copilot".to_string(),
position: crate::lsp::copilot::types::CopilotPosition { line: 0, character: 1 },
position: crate::lsp::copilot::types::CopilotPosition { line: 3, character: 3 },
relative_path: "test.copilot".to_string(),
source: r#"// Create a cube.
fn cube = (pos, scale) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> line([0, scale], %)
|> line([scale, 0], %)
|> line([0, -scale], %)
return sg
}
const part001 = cube([0,0], 20)
|> close(%)
|> extrude(20, %)
source: r#"const bracket = startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> close(%)
|> extrude(10, %)
"#
.to_string(),
tab_size: 4,

View File

@ -2903,9 +2903,9 @@ mod snapshot_tests {
snapshot_test!(
af,
r#"const mySketch = startSketchAt([0,0])
|> lineTo({ to: [0, 1], tag: 'myPath' }, %)
|> lineTo([0, 1], %, 'myPath')
|> lineTo([1, 1], %)
|> lineTo({ to: [1,0], tag: "rightPath" }, %)
|> lineTo([1, 0], %, 'rightPath')
|> close(%)"#
);
snapshot_test!(

View File

@ -4,18 +4,18 @@ expression: actual
---
{
"start": 0,
"end": 192,
"end": 167,
"body": [
{
"type": "VariableDeclaration",
"type": "VariableDeclaration",
"start": 0,
"end": 192,
"end": 167,
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 192,
"end": 167,
"id": {
"type": "Identifier",
"start": 6,
@ -26,7 +26,7 @@ expression: actual
"type": "PipeExpression",
"type": "PipeExpression",
"start": 17,
"end": 192,
"end": 167,
"body": [
{
"type": "CallExpression",
@ -71,7 +71,7 @@ expression: actual
"type": "CallExpression",
"type": "CallExpression",
"start": 49,
"end": 89,
"end": 76,
"callee": {
"type": "Identifier",
"start": 49,
@ -80,107 +80,24 @@ expression: actual
},
"arguments": [
{
"type": "ObjectExpression",
"type": "ObjectExpression",
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 56,
"end": 85,
"properties": [
{
"type": "ObjectProperty",
"start": 58,
"end": 68,
"key": {
"type": "Identifier",
"start": 58,
"end": 60,
"name": "to"
},
"value": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 62,
"end": 68,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 63,
"end": 64,
"value": 0,
"raw": "0"
},
{
"type": "Literal",
"type": "Literal",
"start": 66,
"end": 67,
"value": 1,
"raw": "1"
}
]
}
},
{
"type": "ObjectProperty",
"start": 70,
"end": 83,
"key": {
"type": "Identifier",
"start": 70,
"end": 73,
"name": "tag"
},
"value": {
"type": "Literal",
"type": "Literal",
"start": 75,
"end": 83,
"value": "myPath",
"raw": "'myPath'"
}
}
]
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 87,
"end": 88
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 101,
"end": 118,
"callee": {
"type": "Identifier",
"start": 101,
"end": 107,
"name": "lineTo"
},
"arguments": [
{
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 108,
"end": 114,
"end": 62,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 109,
"end": 110,
"value": 1,
"raw": "1"
"start": 57,
"end": 58,
"value": 0,
"raw": "0"
},
{
"type": "Literal",
"type": "Literal",
"start": 112,
"end": 113,
"start": 60,
"end": 61,
"value": 1,
"raw": "1"
}
@ -189,8 +106,16 @@ expression: actual
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 116,
"end": 117
"start": 64,
"end": 65
},
{
"type": "Literal",
"type": "Literal",
"start": 67,
"end": 75,
"value": "myPath",
"raw": "'myPath'"
}
],
"optional": false
@ -198,82 +123,44 @@ expression: actual
{
"type": "CallExpression",
"type": "CallExpression",
"start": 130,
"end": 172,
"start": 88,
"end": 105,
"callee": {
"type": "Identifier",
"start": 130,
"end": 136,
"start": 88,
"end": 94,
"name": "lineTo"
},
"arguments": [
{
"type": "ObjectExpression",
"type": "ObjectExpression",
"start": 137,
"end": 168,
"properties": [
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 95,
"end": 101,
"elements": [
{
"type": "ObjectProperty",
"start": 139,
"end": 148,
"key": {
"type": "Identifier",
"start": 139,
"end": 141,
"name": "to"
},
"value": {
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 143,
"end": 148,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 144,
"end": 145,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"type": "Literal",
"start": 146,
"end": 147,
"value": 0,
"raw": "0"
}
]
}
"type": "Literal",
"type": "Literal",
"start": 96,
"end": 97,
"value": 1,
"raw": "1"
},
{
"type": "ObjectProperty",
"start": 150,
"end": 166,
"key": {
"type": "Identifier",
"start": 150,
"end": 153,
"name": "tag"
},
"value": {
"type": "Literal",
"type": "Literal",
"start": 155,
"end": 166,
"value": "rightPath",
"raw": "\"rightPath\""
}
"type": "Literal",
"type": "Literal",
"start": 99,
"end": 100,
"value": 1,
"raw": "1"
}
]
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 170,
"end": 171
"start": 103,
"end": 104
}
],
"optional": false
@ -281,20 +168,73 @@ expression: actual
{
"type": "CallExpression",
"type": "CallExpression",
"start": 184,
"end": 192,
"start": 117,
"end": 147,
"callee": {
"type": "Identifier",
"start": 184,
"end": 189,
"start": 117,
"end": 123,
"name": "lineTo"
},
"arguments": [
{
"type": "ArrayExpression",
"type": "ArrayExpression",
"start": 124,
"end": 130,
"elements": [
{
"type": "Literal",
"type": "Literal",
"start": 125,
"end": 126,
"value": 1,
"raw": "1"
},
{
"type": "Literal",
"type": "Literal",
"start": 128,
"end": 129,
"value": 0,
"raw": "0"
}
]
},
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 132,
"end": 133
},
{
"type": "Literal",
"type": "Literal",
"start": 135,
"end": 146,
"value": "rightPath",
"raw": "'rightPath'"
}
],
"optional": false
},
{
"type": "CallExpression",
"type": "CallExpression",
"start": 159,
"end": 167,
"callee": {
"type": "Identifier",
"start": 159,
"end": 164,
"name": "close"
},
"arguments": [
{
"type": "PipeSubstitution",
"type": "PipeSubstitution",
"start": 190,
"end": 191
"start": 165,
"end": 166
}
],
"optional": false

View File

@ -196,7 +196,7 @@ pub async fn get_extrude_wall_transform(args: Args) -> Result<MemoryItem, KclErr
/// |> startProfileAt([0, 0], %)
/// |> line([0, 10], %)
/// |> line([10, 0], %)
/// |> line({to: [0, -10], tag: "surface"}, %)
/// |> line([0, -10], %, "surface")
/// |> close(%)
/// |> extrude(5, %)
///

View File

@ -48,9 +48,9 @@ pub async fn fillet(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0,0], %)
/// |> line({to: [0, 10], tag: "thing"}, %)
/// |> line([0, 10], %, "thing")
/// |> line([10, 0], %)
/// |> line({to: [0, -10], tag: "thing2"}, %)
/// |> line([0, -10], %, "thing2")
/// |> close(%)
/// |> extrude(10, %)
/// |> fillet({radius: 2, tags: ["thing", "thing2"]}, %)
@ -130,9 +130,9 @@ pub async fn get_opposite_edge(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0,0], %)
/// |> line({to: [0, 10], tag: "thing"}, %)
/// |> line([0, 10], %, "thing")
/// |> line([10, 0], %)
/// |> line({to: [0, -10], tag: "thing2"}, %)
/// |> line([0, -10], %, "thing2")
/// |> close(%)
/// |> extrude(10, %)
/// |> fillet({radius: 2, tags: ["thing", getOppositeEdge("thing", %)]}, %)
@ -199,9 +199,9 @@ pub async fn get_next_adjacent_edge(args: Args) -> Result<MemoryItem, KclError>
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0,0], %)
/// |> line({to: [0, 10], tag: "thing"}, %)
/// |> line({to: [10, 0], tag: "thing1"}, %)
/// |> line({to: [0, -10], tag: "thing2"}, %)
/// |> line([0, 10], %, "thing")
/// |> line([10, 0], %, "thing1")
/// |> line([0, -10], %, "thing2")
/// |> close(%)
/// |> extrude(10, %)
/// |> fillet({radius: 2, tags: [getNextAdjacentEdge("thing", %)]}, %)
@ -277,9 +277,9 @@ pub async fn get_previous_adjacent_edge(args: Args) -> Result<MemoryItem, KclErr
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0,0], %)
/// |> line({to: [0, 10], tag: "thing"}, %)
/// |> line({to: [10, 0], tag: "thing1"}, %)
/// |> line({to: [0, -10], tag: "thing2"}, %)
/// |> line([0, 10], %, "thing")
/// |> line([10, 0], %, "thing1")
/// |> line([0, -10], %, "thing2")
/// |> close(%)
/// |> extrude(10, %)
/// |> fillet({radius: 2, tags: [getPreviousAdjacentEdge("thing2", %)]}, %)

View File

@ -622,7 +622,54 @@ impl Args {
Ok((data, sketch_group))
}
fn get_data_and_sketch_surface<T: serde::de::DeserializeOwned>(&self) -> Result<(T, SketchSurface), KclError> {
fn get_data_and_sketch_group_and_tag<T: serde::de::DeserializeOwned>(
&self,
) -> Result<(T, Box<SketchGroup>, Option<String>), KclError> {
let first_value = self
.args
.first()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a struct as the first argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
})
})?
.get_json_value()?;
let data: T = serde_json::from_value(first_value).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: vec![self.source_range],
})
})?;
let second_value = self.args.get(1).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
})
})?;
let sketch_group = if let MemoryItem::SketchGroup(sg) = second_value {
sg.clone()
} else {
return Err(KclError::Type(KclErrorDetails {
message: format!("Expected a SketchGroup as the second argument, found `{:?}`", self.args),
source_ranges: vec![self.source_range],
}));
};
let tag = if let Some(tag) = self.args.get(2) {
tag.get_json_opt()?
} else {
None
};
Ok((data, sketch_group, tag))
}
fn get_data_and_sketch_surface<T: serde::de::DeserializeOwned>(
&self,
) -> Result<(T, SketchSurface, Option<String>), KclError> {
let first_value = self
.args
.first()
@ -661,8 +708,13 @@ impl Args {
source_ranges: vec![self.source_range],
}));
};
let tag = if let Some(tag) = self.args.get(2) {
tag.get_json_opt()?
} else {
None
};
Ok((data, sketch_surface))
Ok((data, sketch_surface, tag))
}
fn get_data_and_extrude_group<T: serde::de::DeserializeOwned>(&self) -> Result<(T, Box<ExtrudeGroup>), KclError> {

View File

@ -24,7 +24,7 @@ pub async fn segment_end_x(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// startSketchOn("YZ")
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [5, 0], tag: "thing" }, %)
/// |> line([5, 0], %, "thing")
/// |> line([5, 5], %)
/// |> line([segEndX("thing", %), 5], %)
/// |> close(%)
@ -60,7 +60,7 @@ pub async fn segment_end_y(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// startSketchOn("YZ")
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [5, 0], tag: "thing" }, %)
/// |> line([5, 0], %, "thing")
/// |> line([5, 5], %)
/// |> line([segEndY("thing", %), 5], %)
/// |> close(%)
@ -96,7 +96,7 @@ pub async fn last_segment_x(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// startSketchOn("YZ")
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [5, 0], tag: "thing" }, %)
/// |> line([5, 0], %, "thing")
/// |> line([5, 5], %)
/// |> line([0, lastSegX(%)], %)
/// |> close(%)
@ -136,7 +136,7 @@ pub async fn last_segment_y(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// startSketchOn("YZ")
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [5, 0], tag: "thing" }, %)
/// |> line([5, 0], %, "thing")
/// |> line([5, 5], %)
/// |> line([0, lastSegY(%)], %)
/// |> close(%)
@ -175,7 +175,7 @@ pub async fn segment_length(args: Args) -> Result<MemoryItem, KclError> {
/// ```no_run
/// startSketchOn("YZ")
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [5, 0], tag: "thing" }, %)
/// |> line([5, 0], %, "thing")
/// |> line([5, 5], %)
/// |> line([0, segLen("thing", %)], %)
/// |> close(%)
@ -215,7 +215,7 @@ pub async fn segment_angle(args: Args) -> Result<MemoryItem, KclError> {
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([4.83, 12.56], %)
/// |> line([15.1, 2.48], %)
/// |> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
/// |> line([3.15, -9.85], %, 'seg01')
/// |> line([-15.17, -4.1], %)
/// |> angledLine([segAng('seg01', %), 12.35], %)
/// |> line([-13.02, 10.03], %)
@ -254,7 +254,7 @@ pub async fn angle_to_match_length_x(args: Args) -> Result<MemoryItem, KclError>
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [1, 3.82], tag: 'seg01' }, %)
/// |> line([1, 3.82], %, 'seg01')
/// |> angledLineToX([
/// -angleToMatchLengthX('seg01', 10, %),
/// 5
@ -320,7 +320,7 @@ pub async fn angle_to_match_length_y(args: Args) -> Result<MemoryItem, KclError>
/// ```no_run
/// const part001 = startSketchOn('XY')
/// |> startProfileAt([0, 0], %)
/// |> line({ to: [1, 3.82], tag: 'seg01' }, %)
/// |> line([1, 3.82], %, 'seg01')
/// |> angledLineToX([
/// -angleToMatchLengthY('seg01', 10, %),
/// 5

View File

@ -58,12 +58,9 @@ async fn inner_circle(
SketchSurfaceOrGroup::SketchSurface(surface) => surface,
SketchSurfaceOrGroup::SketchGroup(group) => group.on,
};
let mut sketch_group = crate::std::sketch::inner_start_profile_at(
crate::std::sketch::LineData::Point([center[0] + radius, center[1]]),
sketch_surface,
args.clone(),
)
.await?;
let mut sketch_group =
crate::std::sketch::inner_start_profile_at([center[0] + radius, center[1]], sketch_surface, None, args.clone())
.await?;
// Call arc.
sketch_group = crate::std::sketch::inner_arc(
@ -71,9 +68,9 @@ async fn inner_circle(
angle_start: 0.0,
angle_end: 360.0,
radius,
tag,
},
sketch_group,
tag,
args.clone(),
)
.await?;

File diff suppressed because it is too large Load Diff

3
src/wasm-lib/prelude.kcl Normal file
View File

@ -0,0 +1,3 @@
fn hey_from_one_of_the_devs_of_the_past_i_wish_you_a_great_day_and_maybe_gave_you_a_little_smile_as_youve_found_this = () => {
return 42
}

View File

@ -87,7 +87,7 @@ async fn execute_and_snapshot(code: &str, units: kittycad::types::UnitLength) ->
async fn serial_test_sketch_on_face() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([11.19, 28.35], %)
|> line({to: [28.67, -13.25], tag: "here"}, %)
|> line([28.67, -13.25], %, "here")
|> line([-4.12, -22.81], %)
|> line([-33.24, 14.55], %)
|> close(%)
@ -206,9 +206,9 @@ const part002 = startSketchOn(part001, "END")
async fn serial_test_fillet_duplicate_tags() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({radius: 0.5, tags: ["thing", "thing"]}, %)
@ -218,7 +218,7 @@ async fn serial_test_fillet_duplicate_tags() {
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([227, 277])], message: "Duplicate tags are not allowed." }"#,
r#"type: KclErrorDetails { source_ranges: [SourceRange([205, 255])], message: "Duplicate tags are not allowed." }"#,
);
}
@ -226,9 +226,9 @@ async fn serial_test_fillet_duplicate_tags() {
async fn serial_test_basic_fillet_cube_start() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({radius: 2, tags: ["thing", "thing2"]}, %)
@ -244,9 +244,9 @@ async fn serial_test_basic_fillet_cube_start() {
async fn serial_test_basic_fillet_cube_end() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({radius: 2, tags: ["thing", getOppositeEdge("thing", %)]}, %)
@ -263,9 +263,9 @@ async fn serial_test_basic_fillet_cube_end() {
async fn serial_test_basic_fillet_cube_close_opposite() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, -10], %, "thing2")
|> close(%, "thing3")
|> extrude(10, %)
|> fillet({radius: 2, tags: ["thing3", getOppositeEdge("thing3", %)]}, %)
@ -286,9 +286,9 @@ async fn serial_test_basic_fillet_cube_close_opposite() {
async fn serial_test_basic_fillet_cube_next_adjacent() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line({to: [10, 0], tag: "thing1"}, %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %, "thing1")
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({radius: 2, tags: [getNextAdjacentEdge("thing", %)]}, %)
@ -308,9 +308,9 @@ async fn serial_test_basic_fillet_cube_next_adjacent() {
async fn serial_test_basic_fillet_cube_previous_adjacent() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([0,0], %)
|> line({to: [0, 10], tag: "thing"}, %)
|> line({to: [10, 0], tag: "thing1"}, %)
|> line({to: [0, -10], tag: "thing2"}, %)
|> line([0, 10], %, "thing")
|> line([10, 0], %, "thing1")
|> line([0, -10], %, "thing2")
|> close(%)
|> extrude(10, %)
|> fillet({radius: 2, tags: [getPreviousAdjacentEdge("thing2", %)]}, %)
@ -380,7 +380,7 @@ async fn serial_test_execute_with_angled_line() {
let code = r#"const part001 = startSketchOn('XY')
|> startProfileAt([4.83, 12.56], %)
|> line([15.1, 2.48], %)
|> line({ to: [3.15, -9.85], tag: 'seg01' }, %)
|> line([3.15, -9.85], %, 'seg01')
|> line([-15.17, -4.1], %)
|> angledLine([segAng('seg01', %), 12.35], %)
|> line([-13.02, 10.03], %)
@ -1177,7 +1177,7 @@ async fn serial_test_error_sketch_on_arc_face() {
let code = r#"fn cube = (pos, scale) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> tangentialArc({ to: [0, scale], tag: "here" }, %)
|> tangentialArc([0, scale], %, "here")
|> line([scale, 0], %)
|> line([0, -scale], %)
@ -1201,7 +1201,7 @@ const part002 = startSketchOn(part001, "here")
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([294, 324])], message: "Cannot sketch on a non-planar surface: `here`" }"#
r#"type: KclErrorDetails { source_ranges: [SourceRange([281, 311])], message: "Cannot sketch on a non-planar surface: `here`" }"#
);
}
@ -1325,18 +1325,9 @@ async fn serial_test_stdlib_kcl_error_circle() {
fn rectShape = (pos, w, l) => {
const rr = startSketchOn('XY')
|> startProfileAt([pos[0] - (w / 2), pos[1] - (l / 2)], %)
|> lineTo({
to: [pos[0] + w / 2, pos[1] - (l / 2)],
tag: "edge1"
}, %)
|> lineTo({
to: [pos[0] + w / 2, pos[1] + l / 2],
tag: "edge2"
}, %)
|> lineTo({
to: [pos[0] - (w / 2), pos[1] + l / 2],
tag: "edge3"
}, %)
|> lineTo([pos[0] + w / 2, pos[1] - (l / 2)], %, "edge1")
|> lineTo([pos[0] + w / 2, pos[1] + l / 2], %, "edge2")
|> lineTo([pos[0] - (w / 2), pos[1] + l / 2], %, "edge3")
|> close(%, "edge4")
return rr
}
@ -1367,6 +1358,6 @@ const part = rectShape([0, 0], 20, 20)
assert!(result.is_err());
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([987, 1036])], message: "Expected a [number, number] as the first argument, found `[UserVal(UserVal { value: String(\"XY\"), meta: [Metadata { source_range: SourceRange([994, 998]) }] }), UserVal(UserVal { value: Array [Number(-6.0), Number(6)], meta: [Metadata { source_range: SourceRange([1000, 1023]) }] }), UserVal(UserVal { value: Number(1), meta: [Metadata { source_range: SourceRange([856, 857]) }] })]`" }"#
r#"type: KclErrorDetails { source_ranges: [SourceRange([891, 940])], message: "Expected a [number, number] as the first argument, found `[UserVal(UserVal { value: String(\"XY\"), meta: [Metadata { source_range: SourceRange([898, 902]) }] }), UserVal(UserVal { value: Array [Number(-6.0), Number(6)], meta: [Metadata { source_range: SourceRange([904, 927]) }] }), UserVal(UserVal { value: Number(1), meta: [Metadata { source_range: SourceRange([760, 761]) }] })]`" }"#
);
}