Compare commits

...

19 Commits

Author SHA1 Message Date
cfde4e99f9 add more tests 2024-05-23 11:17:04 +10:00
1bcaaec807 Merge remote-tracking branch 'origin' into max-unused-variables 2024-05-23 10:51:11 +10:00
fe621240c3 use tauri command to run commands (#2475)
* use tauri command to run commands

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

* add capabilities

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 17:44:13 -07:00
97faf5ae2b Simplify the pentagon test (#2474)
* plumbus fixes

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

* Simplify the pentagon test

* Fix up triangle png

* Triangle plumbuses now face the camera

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 16:50:54 -07:00
67e90f580d nicer query
added PipeExpression checker.

now query works with :

const xRel001 = -20
      const xRel002 = -50
      const part001 = startSketchOn('-XZ')
        |> startProfileAt([175.73, 109.38], %)
        |> line([xRel001, 178.25], %)
        |> line([-265.39, -87.86], %)
        |> tangentialArcTo([543.32, -355.04], %)
2024-05-22 22:45:22 +02:00
78046eceb6 simple test
const xRel001 = -20
const xRel002 = xRel001-50
2024-05-22 21:18:55 +02:00
e3b9a6e5d8 Port forward to ts-rs 8.1 (currently: git only; waiting for 8.1.1+) (#2443)
Port forward to ts-rs 8.1

We're just waiting on a release that includes a PR that we sent[1] for this
to use the cargo version. For now we'll have to use the git release.

[1]: c5109a00e4
2024-05-22 14:22:07 -04:00
e94b1bc12a Add failing multi-sketch file (#1466)
* Add failing multi-sketch file

* Fix e2e lint

* fmt fml

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 10:19:29 -07:00
c0eff5bc14 Cut release v0.21.7 (#2466) 2024-05-22 13:06:07 -04:00
b0f92c2f6d remove printlns from stdlib (#2467)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 09:43:42 -07:00
718873b3bb updating kcl examples (#2386)
* updating kcl examples

* generate images

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

* add new

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 09:15:38 -07:00
9f815eecc1 bump kcl-lib I think my last merge reverted it somehow (#2465) 2024-05-22 15:57:26 +00:00
0384e5e6c6 Add keyboard shortcuts for sketch and modeling tools (#2419)
* Add keyboard shortcuts for sketch and modeling tools

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

* Add a playwright test

* skip linux

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

* fmt fml

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

* Give more generous test timeout for worst case engine runs

* Fix up test mouse clicks after zoom bug fixes

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2024-05-22 11:07:02 -04:00
48ef0885b7 fix xz-plane (#2376)
* fix xz-plane

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

* images

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

* empty

* update docs

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

* fix

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

* updates

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

* update tests

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

* scene infra fixes

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

* images

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

* revert

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

* fix tests

* more test fix

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

* trigger ci

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-05-21 23:35:33 -07:00
3b2731f924 Bump react and @types/react (#2458) 2024-05-21 23:04:39 -07:00
bf4e04f9f1 Bump @tauri-apps/plugin-os from 2.0.0-beta.2 to 2.0.0-beta.3 (#2456) 2024-05-21 23:03:50 -07:00
24475bbcdf add more rust file tests (#2452)
* addd more rust file tests

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-05-21 20:39:29 -07:00
bcca736a8d Fix rename project directory (#2451)
* make rust function with lots of tests

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

* pull thru function to tauri and app

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

* one more test;

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-05-21 19:23:43 -07:00
502cb08a10 findUnusedVariables
first draft
2024-05-21 22:40:30 +02:00
159 changed files with 1875 additions and 476 deletions

View File

@ -115,7 +115,7 @@ jobs:
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
# TODO when safari works on ubuntu remove the os part of the commit message
# TODO when webkit works on ubuntu remove the os part of the commit message
git commit -am "A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu)" || true
git push
git push origin ${{ github.head_ref }}
@ -181,7 +181,7 @@ jobs:
- name: build web
run: yarn build:local
- name: Run macos/safari flow
# safari doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues)
# webkit doesn't work on Ubuntu because of the same reason tauri doesn't (webRTC issues)
# TODO remove this and the matrix and run all tests on ubuntu when this is fixed
run: yarn playwright test --project="webkit" e2e/playwright/flow-tests.spec.ts
env:

View File

@ -21,7 +21,7 @@ abs(num: number) -> number
```js
const myAngle = -120
const sketch001 = startSketchOn('-XZ')
const sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([8, 0], %)
|> angledLine({ angle: abs(myAngle), length: 5 }, %)

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ angleToMatchLengthX(segment_name: string, to: number, sketch_group: SketchGroup)
### Examples
```js
const sketch001 = startSketchOn('-XZ')
const sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([2, 5], %, 'seg01')
|> angledLineToX([

View File

@ -15,7 +15,7 @@ angleToMatchLengthY(segment_name: string, to: number, sketch_group: SketchGroup)
### Examples
```js
const sketch001 = startSketchOn('-XZ')
const sketch001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([1, 2], %, 'seg01')
|> angledLine({

View File

@ -15,7 +15,7 @@ angledLine(data: AngledLineData, sketch_group: SketchGroup, tag?: String) -> Ske
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> yLineTo(15, %)
|> angledLine({ angle: 30, length: 15 }, %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch_group: Sketc
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> lineTo([5, 10], %)
|> lineTo([-10, 10], %, "lineToIntersect")

View File

@ -15,7 +15,7 @@ angledLineToX(data: AngledLineToData, sketch_group: SketchGroup, tag?: String) -
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLineToX({ angle: 30, to: 10 }, %)
|> line([0, 10], %)

View File

@ -15,7 +15,7 @@ angledLineToY(data: AngledLineToData, sketch_group: SketchGroup, tag?: String) -
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLineToY({ angle: 60, to: 20 }, %)
|> line([-20, 0], %)

View File

@ -15,7 +15,7 @@ arc(data: ArcData, sketch_group: SketchGroup, tag?: String) -> SketchGroup
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([10, 0], %)
|> arc({

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ bezierCurve(data: BezierData, sketch_group: SketchGroup, tag?: String) -> Sketch
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([0, 10], %)
|> bezierCurve({

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ lastSegX(sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn("-XZ")
const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> line([5, 0], %)
|> line([20, 5], %)

View File

@ -15,7 +15,7 @@ lastSegY(sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn("-XZ")
const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> line([5, 0], %)
|> line([20, 5], %)

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ lineTo(to: [number], sketch_group: SketchGroup, tag?: String) -> SketchGroup
### Examples
```js
const exampleSketch = startSketchOn("-XZ")
const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> lineTo([10, 0], %)
|> lineTo([0, 10], %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ patternCircular2d(data: CircularPattern2dData, sketch_group: SketchGroup) -> [Sk
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([.5, 25], %)
|> line([0, 5], %)
|> line([-1, 0], %)

View File

@ -15,7 +15,7 @@ patternCircular3d(data: CircularPattern3dData, extrude_group: ExtrudeGroup) -> [
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> circle([0, 0], 1, %)
const example = extrude(-5, exampleSketch)

View File

@ -15,7 +15,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_group_set: SketchGroupSet) ->
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> circle([0, 0], 1, %)
|> patternLinear2d({
axis: [1, 0],

View File

@ -15,7 +15,7 @@ patternLinear3d(data: LinearPattern3dData, extrude_group: ExtrudeGroup) -> [Extr
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([0, 2], %)
|> line([3, 1], %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ segAng(segment_name: string, sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([10, 0], %)
|> line([5, 10], %, 'seg01')

View File

@ -15,7 +15,7 @@ segEndX(segment_name: string, sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([20, 0], %, "thing")
|> line([0, 5], %)

View File

@ -15,7 +15,7 @@ segEndY(segment_name: string, sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line([20, 0], %)
|> line([0, 3], %, "thing")

View File

@ -15,7 +15,7 @@ segLen(segment_name: string, sketch_group: SketchGroup) -> number
### Examples
```js
const exampleSketch = startSketchOn("-XZ")
const exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %)
|> angledLine({ angle: 60, length: 10 }, %, "thing")
|> tangentialArc({ offset: -120, radius: 5 }, %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -29,7 +29,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myAngle = -120\n\nconst sketch001 = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([8, 0], %)\n |> angledLine({ angle: abs(myAngle), length: 5 }, %)\n |> line([-5, 0], %)\n |> angledLine({ angle: myAngle, length: 5 }, %)\n |> close(%)\n\nconst baseExtrusion = extrude(5, sketch001)"
"const myAngle = -120\n\nconst sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([8, 0], %)\n |> angledLine({ angle: abs(myAngle), length: 5 }, %)\n |> line([-5, 0], %)\n |> angledLine({ angle: myAngle, length: 5 }, %)\n |> close(%)\n\nconst baseExtrusion = extrude(5, sketch001)"
]
},
{
@ -62,7 +62,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = acos(0.5)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: toDegrees(acos(0.5)),\n length: 10\n }, %)\n |> line([5, 0], %)\n |> lineTo([12, 0], %)\n |> close(%)\n\nconst extrude001 = extrude(5, sketch001)"
]
},
{
@ -1068,7 +1068,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const sketch001 = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([2, 5], %, 'seg01')\n |> angledLineToX([\n -angleToMatchLengthX('seg01', 7, %),\n 10\n ], %)\n |> close(%)\n\nconst extrusion = extrude(5, sketch001)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([2, 5], %, 'seg01')\n |> angledLineToX([\n -angleToMatchLengthX('seg01', 7, %),\n 10\n ], %)\n |> close(%)\n\nconst extrusion = extrude(5, sketch001)"
]
},
{
@ -2074,7 +2074,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const sketch001 = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([1, 2], %, 'seg01')\n |> angledLine({\n angle: angleToMatchLengthY('seg01', 15, %),\n length: 5\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrusion = extrude(5, sketch001)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([1, 2], %, 'seg01')\n |> angledLine({\n angle: angleToMatchLengthY('seg01', 15, %),\n length: 5\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrusion = extrude(5, sketch001)"
]
},
{
@ -4070,7 +4070,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> yLineTo(15, %)\n |> angledLine({ angle: 30, length: 15 }, %)\n |> line([8, -10], %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> yLineTo(15, %)\n |> angledLine({ angle: 30, length: 15 }, %)\n |> line([8, -10], %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -10048,7 +10048,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> lineTo([5, 10], %)\n |> lineTo([-10, 10], %, \"lineToIntersect\")\n |> lineTo([0, 20], %)\n |> angledLineThatIntersects({\n angle: 80,\n intersectTag: 'lineToIntersect',\n offset: 10\n }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> lineTo([5, 10], %)\n |> lineTo([-10, 10], %, \"lineToIntersect\")\n |> lineTo([0, 20], %)\n |> angledLineThatIntersects({\n angle: 80,\n intersectTag: 'lineToIntersect',\n offset: 10\n }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -12029,7 +12029,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> angledLineToX({ angle: 30, to: 10 }, %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLineToX({ angle: 30, to: 10 }, %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -14010,7 +14010,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> angledLineToY({ angle: 60, to: 20 }, %)\n |> line([-20, 0], %)\n |> angledLineToY({ angle: 70, to: 10 }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLineToY({ angle: 60, to: 20 }, %)\n |> line([-20, 0], %)\n |> angledLineToY({ angle: 70, to: 10 }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -16038,7 +16038,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> arc({\n angle_start: 0,\n angle_end: 280,\n radius: 16\n }, %)\n |> close(%)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> arc({\n angle_start: 0,\n angle_end: 280,\n radius: 16\n }, %)\n |> close(%)"
]
},
{
@ -16071,7 +16071,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = asin(0.5)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: toDegrees(asin(0.5)),\n length: 20\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrude001 = extrude(5, sketch001)"
]
},
{
@ -16104,7 +16104,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = atan(1.0)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: toDegrees(atan(1.25)),\n length: 20\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrude001 = extrude(5, sketch001)"
]
},
{
@ -18106,7 +18106,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> bezierCurve({\n to: [10, 10],\n control1: [5, 0],\n control2: [5, 10]\n }, %)\n |> lineTo([10, 0], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %)\n |> bezierCurve({\n to: [10, 10],\n control1: [5, 0],\n control2: [5, 10]\n }, %)\n |> lineTo([10, 0], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -18139,7 +18139,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = ceil(4.5)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> lineTo([12, 10], %)\n |> line([ceil(7.02986), 0], %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrude001 = extrude(5, sketch001)"
]
},
{
@ -20424,7 +20424,8 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> circle([0, 0], 10, %)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"-XZ\")\n |> circle([0, 0], 10, %)\n\nconst example = extrude(5, exampleSketch)",
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([-15, 0], %)\n |> line([30, 0], %)\n |> line([0, 30], %)\n |> line([-30, 0], %)\n |> close(%)\n |> hole(circle([0, 15], 5, %), %)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -22414,7 +22415,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const anotherVar = cos(2 * pi())"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 30,\n length: 3 / cos(toRadians(30))\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -22437,7 +22438,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = e()"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 30, length: 2 * e() ^ 2 }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -25953,8 +25954,8 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const example = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> arc({\n angle_end: 0,\n angle_start: 120,\n radius: 5\n }, %)\n |> line([5, 0], %)\n |> line([0, 10], %)\n |> bezierCurve({\n control1: [-10, 0],\n control2: [2, 10],\n to: [-5, 10]\n }, %)\n |> line([-5, -2], %)\n |> close(%)\n |> extrude(10, %)",
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([-10, 0], %)\n |> arc({\n angle_end: -60,\n angle_start: 120,\n radius: 5\n }, %)\n |> line([10, 0], %)\n |> line([5, 0], %)\n |> bezierCurve({\n control1: [-3, 0],\n control2: [2, 10],\n to: [-5, 10]\n }, %)\n |> line([-4, 10], %)\n |> line([-5, -2], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const example = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> arc({\n angle_end: 0,\n angle_start: 120,\n radius: 5\n }, %)\n |> line([5, 0], %)\n |> line([0, 10], %)\n |> bezierCurve({\n control1: [-10, 0],\n control2: [2, 10],\n to: [-5, 10]\n }, %)\n |> line([-5, -2], %)\n |> close(%)\n |> extrude(10, %)",
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([-10, 0], %)\n |> arc({\n angle_end: -60,\n angle_start: 120,\n radius: 5\n }, %)\n |> line([10, 0], %)\n |> line([5, 0], %)\n |> bezierCurve({\n control1: [-3, 0],\n control2: [2, 10],\n to: [-5, 10]\n }, %)\n |> line([-4, 10], %)\n |> line([-5, -2], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -27550,7 +27551,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = floor(4.5)"
"const sketch001 = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> lineTo([12, 10], %)\n |> line([floor(7.02986), 0], %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst extrude001 = extrude(5, sketch001)"
]
},
{
@ -28336,7 +28337,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const box = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %, 'revolveAxis')\n |> line([10, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> extrude(10, %)\n\nconst revolution = startSketchOn('XZ')\n |> startProfileAt([-10, 0], %)\n |> line([0, 10], %)\n |> line([2, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
"const box = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 10], %, 'revolveAxis')\n |> line([10, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> extrude(10, %)\n\nconst revolution = startSketchOn(box, \"revolveAxis\")\n |> startProfileAt([5, 10], %)\n |> line([0, 10], %)\n |> line([2, 0], %)\n |> line([0, -10], %)\n |> close(%)\n |> revolve({\n axis: getEdge('revolveAxis', box),\n angle: 90\n }, %)"
]
},
{
@ -36161,7 +36162,7 @@
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('XY')\n |> startProfileAt([0, 0], %)\n |> line([0, 5], %)\n |> line([5, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> hole(circle([1, 1], .25, %), %)\n |> hole(circle([1, 4], .25, %), %)\n\nconst example = extrude(1, exampleSketch)",
"fn squareHoleSketch = () => {\n const squareSketch = startSketchOn('-XZ')\n |> startProfileAt([-1, -1], %)\n |> line([2, 0], %)\n |> line([0, 2], %)\n |> line([-2, 0], %)\n |> close(%)\n return squareSketch\n}\n\nconst exampleSketch = startSketchOn('-XZ')\n |> circle([0, 0], 3, %)\n |> hole(squareHoleSketch(), %)"
"fn squareHoleSketch = () => {\n const squareSketch = startSketchOn('-XZ')\n |> startProfileAt([-1, -1], %)\n |> line([2, 0], %)\n |> line([0, 2], %)\n |> line([-2, 0], %)\n |> close(%)\n return squareSketch\n}\n\nconst exampleSketch = startSketchOn('-XZ')\n |> circle([0, 0], 3, %)\n |> hole(squareHoleSketch(), %)\nconst example = extrude(1, exampleSketch)"
]
},
{
@ -37807,7 +37808,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> startProfileAt([0, 0], %)\n |> line([5, 0], %)\n |> line([20, 5], %)\n |> line([0, lastSegX(%)], %)\n |> line([-15, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([5, 0], %)\n |> line([20, 5], %)\n |> line([0, lastSegX(%)], %)\n |> line([-15, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -38796,7 +38797,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> startProfileAt([0, 0], %)\n |> line([5, 0], %)\n |> line([20, 5], %)\n |> line([0, lastSegY(%)], %)\n |> line([-15, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([5, 0], %)\n |> line([20, 5], %)\n |> line([0, lastSegY(%)], %)\n |> line([-15, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -40892,7 +40893,8 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([25, 15], %)\n |> line([5, -6], %)\n |> line([-10, -10], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)",
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -42862,7 +42864,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> startProfileAt([0, 0], %)\n |> lineTo([10, 0], %)\n |> lineTo([0, 10], %)\n |> lineTo([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> lineTo([10, 0], %)\n |> lineTo([0, 10], %)\n |> lineTo([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -42895,7 +42897,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = ln(4)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([ln(100), 15], %)\n |> line([5, -6], %)\n |> line([-10, -10], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -42937,7 +42939,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = log(4, 2)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([log(100, 5), 0], %)\n |> line([5, 8], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -42970,7 +42972,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = log10(4)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([log10(100), 0], %)\n |> line([5, 8], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -43003,7 +43005,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = log2(4)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> line([log2(100), 0], %)\n |> line([5, 8], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -43039,7 +43041,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = max(4, 5, 6)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 70,\n length: max(15, 31, 4, 13, 22)\n }, %)\n |> line([20, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -43075,7 +43077,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = min(4, 5, 6)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 70,\n length: min(15, 31, 4, 13, 22)\n }, %)\n |> line([20, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -45067,7 +45069,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([.5, 25], %)\n |> line([0, 5], %)\n |> line([-1, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> patternCircular2d({\n center: [0, 0],\n repetitions: 12,\n arcDegrees: 360,\n rotateDuplicates: true\n }, %)\n\nconst example = extrude(1, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([.5, 25], %)\n |> line([0, 5], %)\n |> line([-1, 0], %)\n |> line([0, -5], %)\n |> close(%)\n |> patternCircular2d({\n center: [0, 0],\n repetitions: 12,\n arcDegrees: 360,\n rotateDuplicates: true\n }, %)\n\nconst example = extrude(1, exampleSketch)"
]
},
{
@ -46648,7 +46650,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> circle([0, 0], 1, %)\n\nconst example = extrude(-5, exampleSketch)\n |> patternCircular3d({\n axis: [1, -1, 0],\n center: [10, -20, 0],\n repetitions: 10,\n arcDegrees: 360,\n rotateDuplicates: true\n }, %)"
"const exampleSketch = startSketchOn('XZ')\n |> circle([0, 0], 1, %)\n\nconst example = extrude(-5, exampleSketch)\n |> patternCircular3d({\n axis: [1, -1, 0],\n center: [10, -20, 0],\n repetitions: 10,\n arcDegrees: 360,\n rotateDuplicates: true\n }, %)"
]
},
{
@ -49626,7 +49628,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> circle([0, 0], 1, %)\n |> patternLinear2d({\n axis: [1, 0],\n repetitions: 6,\n distance: 4\n }, %)\n\nconst example = extrude(1, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> circle([0, 0], 1, %)\n |> patternLinear2d({\n axis: [1, 0],\n repetitions: 6,\n distance: 4\n }, %)\n\nconst example = extrude(1, exampleSketch)"
]
},
{
@ -51191,7 +51193,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 2], %)\n |> line([3, 1], %)\n |> line([0, -4], %)\n |> close(%)\n\nconst example = extrude(1, exampleSketch)\n |> patternLinear3d({\n axis: [1, 0, 1],\n repetitions: 6,\n distance: 6\n }, %)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([0, 2], %)\n |> line([3, 1], %)\n |> line([0, -4], %)\n |> close(%)\n\nconst example = extrude(1, exampleSketch)\n |> patternLinear3d({\n axis: [1, 0, 1],\n repetitions: 6,\n distance: 6\n }, %)"
]
},
{
@ -51214,7 +51216,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = pi() * 3.0"
"const circumference = 70\n\nconst exampleSketch = startSketchOn(\"XZ\")\n |> circle([0, 0], circumference / (2 * pi()), %)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -51256,7 +51258,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = pow(4, 2)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 50, length: pow(5, 2) }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -57090,7 +57092,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([5, 10], %, 'seg01')\n |> line([-10, 0], %)\n |> angledLine([segAng('seg01', %), 10], %)\n |> line([-10, 0], %)\n |> angledLine([segAng('seg01', %), -15], %)\n |> close(%)\n\nconst example = extrude(4, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([5, 10], %, 'seg01')\n |> line([-10, 0], %)\n |> angledLine([segAng('seg01', %), 10], %)\n |> line([-10, 0], %)\n |> angledLine([segAng('seg01', %), -15], %)\n |> close(%)\n\nconst example = extrude(4, exampleSketch)"
]
},
{
@ -58087,7 +58089,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([20, 0], %, \"thing\")\n |> line([0, 5], %)\n |> line([segEndX(\"thing\", %), 0], %)\n |> line([-20, 10], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([20, 0], %, \"thing\")\n |> line([0, 5], %)\n |> line([segEndX(\"thing\", %), 0], %)\n |> line([-20, 10], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -59084,7 +59086,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([20, 0], %)\n |> line([0, 3], %, \"thing\")\n |> line([-10, 0], %)\n |> line([0, segEndY(\"thing\", %)], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([20, 0], %)\n |> line([0, 3], %, \"thing\")\n |> line([-10, 0], %)\n |> line([0, segEndY(\"thing\", %)], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -60081,7 +60083,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn(\"-XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %, \"thing\")\n |> tangentialArc({ offset: -120, radius: 5 }, %)\n |> angledLine({\n angle: -60,\n length: segLen(\"thing\", %)\n }, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %, \"thing\")\n |> tangentialArc({ offset: -120, radius: 5 }, %)\n |> angledLine({\n angle: -60,\n length: segLen(\"thing\", %)\n }, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -60114,7 +60116,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = sin(2 * pi())"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 50,\n length: 15 / sin(toDegrees(135))\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -60147,7 +60149,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = sqrt(4)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 50, length: sqrt(2500) }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -61456,7 +61458,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)",
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)",
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([10, 10], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)",
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([-10, 23], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> line([-10, 0], %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
@ -63762,7 +63764,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = tan(2 * pi())"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 50, length: 50 * tan(1 / 2) }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -65757,7 +65759,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %)\n |> tangentialArc({ radius: 10, offset: -120 }, %)\n |> angledLine({ angle: -60, length: 10 }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %)\n |> tangentialArc({ radius: 10, offset: -120 }, %)\n |> angledLine({ angle: -60, length: 10 }, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -67727,7 +67729,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %)\n |> tangentialArcTo([15, 15], %)\n |> line([10, -15], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 60, length: 10 }, %)\n |> tangentialArcTo([15, 15], %)\n |> line([10, -15], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -67750,7 +67752,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = tau()"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 50, length: 10 * tau() }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -67783,7 +67785,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = toDegrees(2 * pi())"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 50,\n length: 70 * cos(toDegrees(pi() / 4))\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -67816,7 +67818,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const myVar = toRadians(180)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({\n angle: 50,\n length: 70 * cos(toRadians(45))\n }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
},
{
@ -69781,7 +69783,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> xLine(15, %)\n |> angledLine({ angle: 80, length: 15 }, %)\n |> line([8, -10], %)\n |> xLine(10, %)\n |> angledLine({ angle: 120, length: 30 }, %)\n |> xLine(-15, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> xLine(15, %)\n |> angledLine({ angle: 80, length: 15 }, %)\n |> line([8, -10], %)\n |> xLine(10, %)\n |> angledLine({ angle: 120, length: 30 }, %)\n |> xLine(-15, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -71746,7 +71748,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> xLineTo(15, %)\n |> angledLine({ angle: 80, length: 15 }, %)\n |> line([8, -10], %)\n |> xLineTo(40, %)\n |> angledLine({ angle: 135, length: 30 }, %)\n |> xLineTo(10, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> xLineTo(15, %)\n |> angledLine({ angle: 80, length: 15 }, %)\n |> line([8, -10], %)\n |> xLineTo(40, %)\n |> angledLine({ angle: 135, length: 30 }, %)\n |> xLineTo(10, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -73711,7 +73713,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> yLine(15, %)\n |> angledLine({ angle: 30, length: 15 }, %)\n |> line([8, -10], %)\n |> yLine(-5, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
"const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> yLine(15, %)\n |> angledLine({ angle: 30, length: 15 }, %)\n |> line([8, -10], %)\n |> yLine(-5, %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
]
},
{
@ -75676,7 +75678,7 @@
"unpublished": false,
"deprecated": false,
"examples": [
"startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> yLineTo(10, %, \"edge1\")\n |> line([10, 10], %)\n |> close(%, \"edge2\")\n |> extrude(10, %)\n |> fillet({ radius: 2, tags: [\"edge2\"] }, %)"
"const exampleSketch = startSketchOn(\"XZ\")\n |> startProfileAt([0, 0], %)\n |> angledLine({ angle: 50, length: 45 }, %)\n |> yLineTo(0, %)\n |> close(%)\n\nconst example = extrude(5, exampleSketch)"
]
}
]

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ tangentialArc(data: TangentialArcData, sketch_group: SketchGroup, tag?: String)
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLine({ angle: 60, length: 10 }, %)
|> tangentialArc({ radius: 10, offset: -120 }, %)

View File

@ -15,7 +15,7 @@ tangentialArcTo(to: [number], sketch_group: SketchGroup, tag?: String) -> Sketch
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> angledLine({ angle: 60, length: 10 }, %)
|> tangentialArcTo([15, 15], %)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -15,7 +15,7 @@ xLine(length: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLine(15, %)
|> angledLine({ angle: 80, length: 15 }, %)

View File

@ -15,7 +15,7 @@ xLineTo(to: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> xLineTo(15, %)
|> angledLine({ angle: 80, length: 15 }, %)

View File

@ -15,7 +15,7 @@ yLine(length: number, sketch_group: SketchGroup, tag?: String) -> SketchGroup
### Examples
```js
const exampleSketch = startSketchOn('-XZ')
const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> yLine(15, %)
|> angledLine({ angle: 30, length: 15 }, %)

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

@ -80,7 +80,7 @@ test('Basic sketch', async ({ page }) => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await u.closeDebugPanel()
@ -89,7 +89,7 @@ test('Basic sketch', async ({ page }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
@ -97,20 +97,20 @@ test('Basic sketch', async ({ page }) => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
@ -136,7 +136,7 @@ test('Basic sketch', async ({ page }) => {
await page.getByRole('button', { name: 'Equal Length' }).click()
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %, 'seg01')
|> line([0, ${commonPoints.num1}], %)
@ -628,7 +628,7 @@ const sketchOnPlaneAndBackSideTest = async (
const camCmdBackSide: [number, number, number] = [-100, -100, -100]
let camPos: [number, number, number] = [100, 100, 100]
if (plane === '-XY' || plane === '-YZ' || plane === '-XZ') {
if (plane === '-XY' || plane === '-YZ' || plane === 'XZ') {
camPos = camCmdBackSide
}
@ -679,7 +679,7 @@ test.describe('Can create sketches on all planes and their back sides', () => {
})
test('XZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 80 }) // blue plane
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 80 }) // blue plane
})
test('-XY', async ({ page }) => {
@ -691,7 +691,7 @@ test.describe('Can create sketches on all planes and their back sides', () => {
})
test('-XZ', async ({ page }) => {
await sketchOnPlaneAndBackSideTest(page, '-XZ', { x: 700, y: 427 }) // back of blue plane
await sketchOnPlaneAndBackSideTest(page, 'XZ', { x: 700, y: 427 }) // back of blue plane
})
})
@ -1090,28 +1090,28 @@ test('Selections work on fresh and edited sketch', async ({ page }) => {
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
@ -1340,7 +1340,7 @@ test.describe('Command bar tests', () => {
localStorage.setItem(
'persistCode',
`const distance = sqrt(20)
const part001 = startSketchOn('-XZ')
const part001 = startSketchOn('XZ')
|> startProfileAt([-6.95, 10.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
@ -1410,7 +1410,7 @@ test.describe('Command bar tests', () => {
await expect(page.locator('.cm-content')).toHaveText(
`const distance = sqrt(20)
const distance001 = ${KCL_DEFAULT_LENGTH}
const part001 = startSketchOn('-XZ')
const part001 = startSketchOn('XZ')
|> startProfileAt([-6.95, 10.98], %)
|> line([25.1, 0.41], %)
|> line([0.73, -20.93], %)
@ -1446,7 +1446,7 @@ test('Can add multiple sketches', async ({ page }) => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
@ -1455,7 +1455,7 @@ test('Can add multiple sketches', async ({ page }) => {
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await page.waitForTimeout(100)
@ -1463,19 +1463,19 @@ test('Can add multiple sketches', async ({ page }) => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)`)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
const finalCodeFirstSketch = `const part001 = startSketchOn('-XZ')
const finalCodeFirstSketch = `const part001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1}], %)
@ -1609,7 +1609,7 @@ test('Hovering over 3d features highlights code', async ({ page }) => {
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
`const part001 = startSketchOn('XZ')
|> startProfileAt([20, 0], %)
|> line([7.13, 4 + 0], %)
|> angledLine({ angle: 3 + 0, length: 3.14 + 0 }, %)
@ -1628,7 +1628,7 @@ test('Hovering over 3d features highlights code', async ({ page }) => {
}, %)
|> tangentialArcTo([13.14 + 0, 13.14], %)
|> close(%)
|> extrude(5 + 7, %)
|> extrude(5 + 7, %)
`
)
}, KCL_DEFAULT_LENGTH)
@ -1689,7 +1689,7 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
}: any) => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
`const part001 = startSketchOn('XZ')
${extrudeAndEditBlocked}
|> line([25.96, 2.93], %)
|> line([5.25, -5.72], %)
@ -1697,14 +1697,14 @@ test("Various pipe expressions should and shouldn't allow edit and or extrude",
|> line([-27.65, -2.78], %)
|> close(%)
|> extrude(5, %)
const part002 = startSketchOn('-XZ')
const part002 = startSketchOn('XZ')
${extrudeAndEditAllowed}
|> line([10.32, 6.47], %)
|> line([9.71, -6.16], %)
|> line([-3.08, -9.86], %)
|> line([-12.02, -1.54], %)
|> close(%)
const part003 = startSketchOn('-XZ')
const part003 = startSketchOn('XZ')
${editOnly}
|> line([27.55, -1.65], %)
|> line([4.95, -8], %)
@ -1712,7 +1712,7 @@ const part003 = startSketchOn('-XZ')
|> line([-15.79, 17.08], %)
fn yohey = (pos) => {
const part004 = startSketchOn('-XZ')
const part004 = startSketchOn('XZ')
${extrudeAndEditBlockedInFunction}
|> line([27.55, -1.65], %)
|> line([4.95, -10.53], %)
@ -1773,7 +1773,7 @@ fn yohey = (pos) => {
await page.mouse.click(700, 200)
// expect main content to contain `part005` i.e. started a new sketch
await expect(page.locator('.cm-content')).toHaveText(
/part005 = startSketchOn\('-XZ'\)/
/part005 = startSketchOn\('XZ'\)/
)
})
@ -1801,7 +1801,7 @@ test('Deselecting line tool should mean nothing happens on click', async ({
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(600)
@ -1842,12 +1842,94 @@ test('Deselecting line tool should mean nothing happens on click', async ({
previousCodeContent = await page.locator('.cm-content').innerText()
})
test('multi-sketch file shows multiple Edit Sketch buttons', async ({
page,
context,
}) => {
const u = getUtils(page)
const selectionsSnippets = {
startProfileAt1:
'|> startProfileAt([-width / 4 + screwRadius, height / 2], %)',
startProfileAt2: '|> startProfileAt([-width / 2, 0], %)',
startProfileAt3: '|> startProfileAt([0, thickness], %)',
}
await context.addInitScript(
async ({ startProfileAt1, startProfileAt2, startProfileAt3 }: any) => {
localStorage.setItem(
'persistCode',
`
const width = 20
const height = 10
const thickness = 5
const screwRadius = 3
const wireRadius = 2
const wireOffset = 0.5
const screwHole = startSketchOn('XY')
${startProfileAt1}
|> arc({
radius: screwRadius,
angle_start: 0,
angle_end: 360
}, %)
const part001 = startSketchOn('XY')
${startProfileAt2}
|> xLine(width * .5, %)
|> yLine(height, %)
|> xLine(-width * .5, %)
|> close(%)
|> hole(screwHole, %)
|> extrude(thickness, %)
const part002 = startSketchOn('-XZ')
${startProfileAt3}
|> xLine(width / 4, %)
|> tangentialArcTo([width / 2, 0], %)
|> xLine(-width / 4 + wireRadius, %)
|> yLine(wireOffset, %)
|> arc({
radius: wireRadius,
angle_start: 0,
angle_end: 180
}, %)
|> yLine(-wireOffset, %)
|> xLine(-width / 4, %)
|> close(%)
|> extrude(-height, %)
`
)
},
selectionsSnippets
)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
// wait for execution done
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
await page.getByText(selectionsSnippets.startProfileAt1).click()
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByText(selectionsSnippets.startProfileAt2).click()
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByText(selectionsSnippets.startProfileAt3).click()
await expect(page.getByRole('button', { name: 'Extrude' })).toBeDisabled()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
})
test('Can edit segments by dragging their handles', async ({ page }) => {
const u = getUtils(page)
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
`const part001 = startSketchOn('XZ')
|> startProfileAt([4.61, -14.01], %)
|> line([12.73, -0.09], %)
|> tangentialArcTo([24.95, -5.38], %)`
@ -1907,7 +1989,7 @@ test('Can edit segments by dragging their handles', async ({ page }) => {
// expect the code to have changed
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([6.44, -12.07], %)
|> line([14.04, 2.03], %)
|> tangentialArcTo([27.19, -4.2], %)`)
@ -1925,7 +2007,7 @@ const doSnapAtDifferentScales = async (
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
const code = `const part001 = startSketchOn('XZ')
const code = `const part001 = startSketchOn('-XZ')
|> startProfileAt([${roundOff(scale * 87.68)}, ${roundOff(scale * 43.84)}], %)
|> line([${roundOff(scale * 175.36)}, 0], %)
|> line([0, -${roundOff(scale * 175.36) + fudge}], %)
@ -1947,7 +2029,7 @@ const doSnapAtDifferentScales = async (
// select a plane
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('XZ')`
`const part001 = startSketchOn('-XZ')`
)
let prevContent = await page.locator('.cm-content').innerText()
@ -2004,7 +2086,7 @@ test('Sketch on face', async ({ page }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
`const part001 = startSketchOn('-XZ')
`const part001 = startSketchOn('XZ')
|> startProfileAt([3.29, 7.86], %)
|> line([2.48, 2.44], %)
|> line([2.66, 1.17], %)
@ -2213,3 +2295,105 @@ test('Extrude from command bar selects extrude line after', async ({
` |> extrude(${KCL_DEFAULT_LENGTH}, %)`
)
})
test('Basic default modeling and sketch hotkeys work', async ({ page }) => {
// This test can run long if it takes a little too long to load
// the engine.
test.setTimeout(90000)
// This test has a weird bug on ubuntu
test.skip(
process.platform === 'linux',
'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444'
)
// Load the app with the code pane open
await page.addInitScript(async () => {
localStorage.setItem(
'store',
JSON.stringify({
state: {
openPanes: ['code'],
},
version: 0,
})
)
})
// Wait for the app to be ready for use
const u = getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
await page.goto('/')
await u.waitForAuthSkipAppStart()
await u.openDebugPanel()
await u.expectCmdLog('[data-message-type="execution-done"]')
await u.closeDebugPanel()
const codePane = page.getByRole('textbox').locator('div')
const codePaneButton = page.getByRole('tab', { name: 'KCL Code' })
const lineButton = page.getByRole('button', { name: 'Line' })
const arcButton = page.getByRole('button', { name: 'Tangential Arc' })
const extrudeButton = page.getByRole('button', { name: 'Extrude' })
// Test that the hotkeys do nothing when
// focus is on the code pane
await codePane.click()
await page.keyboard.press('s')
await page.keyboard.press('l')
await page.keyboard.press('a')
await page.keyboard.press('e')
await expect(page.locator('.cm-content')).toHaveText('slae')
await page.keyboard.press('Meta+/')
// Test these hotkeys perform actions when
// focus is on the canvas
await page.mouse.move(600, 250)
await page.mouse.click(600, 250)
// Start a sketch
await page.keyboard.press('s')
await page.mouse.move(800, 300)
await page.mouse.click(800, 300)
await page.waitForTimeout(1000)
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
/**
* TODO: There is a bug somewhere that causes this test to fail
* if you toggle the codePane closed before your trigger the
* start of the sketch.
*/
await codePaneButton.click()
// Draw a line
await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200)
await page.mouse.move(800, 250, { steps: 5 })
await page.mouse.click(800, 250)
// Unequip line tool
await page.keyboard.press('l')
await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true')
// Equip arc tool
await page.keyboard.press('a')
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
await page.mouse.move(1000, 100, { steps: 5 })
await page.mouse.click(1000, 100)
await page.keyboard.press('Escape')
await page.keyboard.press('l')
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
// Close profile
await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200)
// Unequip line tool
await page.keyboard.press('Escape')
// Exit sketch
await page.keyboard.press('Escape')
// Extrude
await page.mouse.click(750, 150)
await expect(extrudeButton).not.toBeDisabled()
await page.keyboard.press('e')
await page.mouse.move(850, 180, { steps: 5 })
await page.mouse.click(850, 180)
await page.waitForTimeout(100)
await page.getByRole('button', { name: 'Continue' }).click()
await page.getByRole('button', { name: 'Submit command' }).click()
await codePaneButton.click()
await expect(page.locator('.cm-content')).toContainText('extrude(')
})

View File

@ -447,7 +447,7 @@ test('Draft segments should look right', async ({ page, context }) => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -455,7 +455,7 @@ test('Draft segments should look right', async ({ page, context }) => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
@ -469,7 +469,7 @@ test('Draft segments should look right', async ({ page, context }) => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
@ -506,7 +506,7 @@ test('Draft rectangles should look right', async ({ page, context }) => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
@ -555,7 +555,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -563,7 +563,7 @@ test.describe('Client side scene scale should match engine scale', () => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)`)
await page.waitForTimeout(100)
@ -573,7 +573,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)`)
@ -583,7 +583,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([9.06, -12.22], %)
|> line([9.14, 0], %)
|> tangentialArcTo([27.34, -3.08], %)`)
@ -658,7 +658,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).toHaveText(
`const part001 = startSketchOn('-XZ')`
`const part001 = startSketchOn('XZ')`
)
await page.waitForTimeout(300) // TODO detect animation ending, or disable animation
@ -666,7 +666,7 @@ test.describe('Client side scene scale should match engine scale', () => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %)`)
await page.waitForTimeout(100)
@ -676,7 +676,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %)`)
@ -686,7 +686,7 @@ test.describe('Client side scene scale should match engine scale', () => {
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`const part001 = startSketchOn('-XZ')
.toHaveText(`const part001 = startSketchOn('XZ')
|> startProfileAt([230.03, -310.32], %)
|> line([232.2, 0], %)
|> tangentialArcTo([694.43, -78.12], %)`)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,6 +1,6 @@
{
"name": "untitled-app",
"version": "0.21.6",
"version": "0.21.7",
"private": true,
"dependencies": {
"@codemirror/autocomplete": "^6.16.0",
@ -19,7 +19,7 @@
"@tauri-apps/plugin-dialog": "^2.0.0-beta.2",
"@tauri-apps/plugin-fs": "^2.0.0-beta.3",
"@tauri-apps/plugin-http": "^2.0.0-beta.2",
"@tauri-apps/plugin-os": "^2.0.0-beta.2",
"@tauri-apps/plugin-os": "^2.0.0-beta.3",
"@tauri-apps/plugin-process": "^2.0.0-beta.2",
"@tauri-apps/plugin-shell": "^2.0.0-beta.2",
"@tauri-apps/plugin-updater": "^2.0.0-beta.3",
@ -29,7 +29,7 @@
"@ts-stack/markdown": "^1.5.0",
"@tweenjs/tween.js": "^23.1.1",
"@types/node": "^18.19.31",
"@types/react": "^18.2.77",
"@types/react": "^18.3.2",
"@types/react-dom": "^18.2.25",
"@uiw/react-codemirror": "^4.21.25",
"@xstate/inspect": "^0.8.0",
@ -45,7 +45,7 @@
"jszip": "^3.10.1",
"node-fetch": "^3.3.2",
"re-resizable": "^6.9.11",
"react": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.1",
"react-hotkeys-hook": "^4.5.0",

13
src-tauri/Cargo.lock generated
View File

@ -2567,7 +2567,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.55"
version = "0.1.57"
dependencies = [
"anyhow",
"approx",
@ -6059,9 +6059,8 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ts-rs"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2cae1fc5d05d47aa24b64f9a4f7cba24cdc9187a2084dd97ac57bef5eccae6"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#f898578d80d3e2a54080c1c046c45f9eaa2435c3"
dependencies = [
"chrono",
"thiserror",
@ -6072,11 +6071,9 @@ dependencies = [
[[package]]
name = "ts-rs-macros"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f7f9b821696963053a89a7bd8b292dc34420aea8294d7b225274d488f3ec92"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#f898578d80d3e2a54080c1c046c45f9eaa2435c3"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.65",

View File

@ -56,6 +56,33 @@
]
},
"shell:allow-open",
{
"identifier": "shell:allow-execute",
"allow": [
{
"name": "open",
"cmd": "open",
"args": [
"-R",
{
"validator": "\\S+"
}
],
"sidecar": false
},
{
"name": "explorer",
"cmd": "explorer",
"args": [
"/select",
{
"validator": "\\S+"
}
],
"sidecar": false
}
]
},
"dialog:allow-open",
"dialog:allow-save",
"dialog:allow-message",

View File

@ -18,13 +18,21 @@ use oauth2::TokenResponse;
use tauri::{ipc::InvokeError, Manager};
use tauri_plugin_cli::CliExt;
use tauri_plugin_shell::ShellExt;
use tokio::process::Command;
const DEFAULT_HOST: &str = "https://api.zoo.dev";
const SETTINGS_FILE_NAME: &str = "settings.toml";
const PROJECT_SETTINGS_FILE_NAME: &str = "project.toml";
const PROJECT_FOLDER: &str = "zoo-modeling-app-projects";
#[tauri::command]
async fn rename_project_directory(project_path: &str, new_name: &str) -> Result<PathBuf, InvokeError> {
let project_dir = std::path::Path::new(project_path);
kcl_lib::settings::types::file::rename_project_directory(project_dir, new_name)
.await
.map_err(InvokeError::from_anyhow)
}
#[tauri::command]
fn get_initial_default_dir(app: tauri::AppHandle) -> Result<PathBuf, InvokeError> {
let dir = match app.path().document_dir() {
@ -323,10 +331,20 @@ async fn get_user(token: &str, hostname: &str) -> Result<kittycad::types::User,
/// From this GitHub comment: https://github.com/tauri-apps/tauri/issues/4062#issuecomment-1338048169
/// But with the Linux support removed since we don't need it for now.
#[tauri::command]
fn show_in_folder(path: &str) -> Result<(), InvokeError> {
fn show_in_folder(app: tauri::AppHandle, path: &str) -> Result<(), InvokeError> {
// Check if the file exists.
// If it doesn't, return an error.
if !Path::new(path).exists() {
return Err(InvokeError::from_anyhow(anyhow::anyhow!(
"The file `{}` does not exist",
path
)));
}
#[cfg(not(unix))]
{
Command::new("explorer")
app.shell()
.command("explorer")
.args(["/select,", path]) // The comma after select is not a typo
.spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -334,7 +352,8 @@ fn show_in_folder(path: &str) -> Result<(), InvokeError> {
#[cfg(unix)]
{
Command::new("open")
app.shell()
.command("open")
.args(["-R", path])
.spawn()
.map_err(|e| InvokeError::from_anyhow(e.into()))?;
@ -389,6 +408,7 @@ fn main() -> Result<()> {
write_app_settings_file,
read_project_settings_file,
write_project_settings_file,
rename_project_directory,
])
.plugin(tauri_plugin_cli::init())
.plugin(tauri_plugin_deep_link::init())

View File

@ -74,5 +74,5 @@
}
},
"productName": "Zoo Modeling App",
"version": "0.21.6"
"version": "0.21.7"
}

View File

@ -58,9 +58,8 @@ export function App() {
const {
app: { onboardingStatus },
} = settings.context
const { state, send } = useModelingContext()
const { state } = useModelingContext()
useHotkeys('esc', () => send('Cancel'))
useHotkeys('backspace', (e) => {
e.preventDefault()
})

View File

@ -12,6 +12,8 @@ import {
} from 'components/NetworkHealthIndicator'
import { useStore } from 'useStore'
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
import { useHotkeys } from 'react-hotkeys-hook'
import Tooltip from 'components/Tooltip'
export const Toolbar = () => {
const { commandBarSend } = useCommandsContext()
@ -40,6 +42,56 @@ export const Toolbar = () => {
const disableAllButtons =
overallState !== NetworkHealthState.Ok || isExecuting || !isStreamReady
useHotkeys(
'l',
() =>
state.matches('Sketch.Line tool')
? send('CancelSketch')
: send('Equip Line tool'),
{ enabled: !disableAllButtons, scopes: ['sketch'] }
)
useHotkeys(
'a',
() =>
state.matches('Sketch.Tangential arc to')
? send('CancelSketch')
: send('Equip tangential arc to'),
{ enabled: !disableAllButtons, scopes: ['sketch'] }
)
useHotkeys(
'r',
() =>
state.matches('Sketch.Rectangle tool')
? send('CancelSketch')
: send('Equip rectangle tool'),
{ enabled: !disableAllButtons, scopes: ['sketch'] }
)
useHotkeys(
's',
() =>
state.nextEvents.includes('Enter sketch') && pathId
? send({ type: 'Enter sketch' })
: send({ type: 'Enter sketch', data: { forceNewSketch: true } }),
{ enabled: !disableAllButtons, scopes: ['modeling'] }
)
useHotkeys(
'esc',
() =>
state.matches('Sketch.SketchIdle')
? send('Cancel')
: send('CancelSketch'),
{ enabled: !disableAllButtons, scopes: ['sketch'] }
)
useHotkeys(
'e',
() =>
commandBarSend({
type: 'Find and select command',
data: { name: 'Extrude', ownerMachine: 'modeling' },
}),
{ enabled: !disableAllButtons, scopes: ['modeling'] }
)
function handleToolbarButtonsWheelEvent(ev: WheelEvent<HTMLSpanElement>) {
const span = toolbarButtonsRef.current
if (!span) {
@ -77,6 +129,13 @@ export const Toolbar = () => {
disabled={disableAllButtons}
>
<span data-testid="start-sketch">Start Sketch</span>
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: S
</Tooltip>
</ActionButton>
</li>
)}
@ -94,6 +153,13 @@ export const Toolbar = () => {
disabled={disableAllButtons}
>
Edit Sketch
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: S
</Tooltip>
</ActionButton>
</li>
)}
@ -111,6 +177,13 @@ export const Toolbar = () => {
disabled={disableAllButtons}
>
Exit Sketch
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: Esc
</Tooltip>
</ActionButton>
</li>
)}
@ -134,6 +207,13 @@ export const Toolbar = () => {
disabled={disableAllButtons}
>
Line
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: L
</Tooltip>
</ActionButton>
</li>
<li className="contents" key="tangential-arc-button">
@ -158,6 +238,13 @@ export const Toolbar = () => {
}
>
Tangential Arc
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: A
</Tooltip>
</ActionButton>
</li>
<li className="contents" key="rectangle-button">
@ -187,6 +274,13 @@ export const Toolbar = () => {
}
>
Rectangle
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: R
</Tooltip>
</ActionButton>
</li>
</>
@ -264,6 +358,13 @@ export const Toolbar = () => {
}}
>
Extrude
<Tooltip
delay={1250}
position="bottom"
className="!px-2 !text-xs"
>
Shortcut: E
</Tooltip>
</ActionButton>
</li>
)}

View File

@ -1396,7 +1396,7 @@ export class SceneEntities {
zAxis = posNorm ? [1, 0, 0] : [-1, 0, 0]
yAxis = [0, 0, 1]
} else if (type === XZ_PLANE) {
planeString = posNorm ? 'XZ' : '-XZ'
planeString = posNorm ? '-XZ' : 'XZ'
zAxis = posNorm ? [0, 1, 0] : [0, -1, 0]
yAxis = [0, 0, 1]
}

View File

@ -290,7 +290,7 @@ export const ModelingMachineProvider = ({
kclManager.ast,
sketchDetails?.sketchPathToNode || [],
'VariableDeclaration'
)?.node?.declarations[0]?.init.type !== 'PipeExpression',
)?.node?.declarations?.[0]?.init.type !== 'PipeExpression',
'Selection is on face': ({ selectionRanges }, { data }) => {
if (data?.forceNewSketch) return false
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))

View File

@ -21,6 +21,9 @@ const projectWellFormed = {
created: now.toISOString(),
modified: now.toISOString(),
size: 32,
accessed: null,
type: null,
permission: null,
},
kcl_file_count: 1,
directory_count: 0,

View File

@ -1,6 +1,7 @@
import { parse, recast, initPromise } from './wasm'
import {
findAllPreviousVariables,
findUnusedVariables,
isNodeSafeToReplace,
isTypeInValue,
getNodePathFromSourceRange,
@ -60,6 +61,91 @@ const variableBelowShouldNotBeIncluded = 3
})
})
describe('Test findUnusedVariables', () => {
it('should find unused variable in common kcl code', () => {
// example code
const code = `
const xRel001 = -20
const xRel002 = -50
const part001 = startSketchOn('-XZ')
|> startProfileAt([175.73, 109.38], %)
|> line([xRel001, 178.25], %)
|> line([-265.39, -87.86], %)
|> tangentialArcTo([543.32, -355.04], %)
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables
.map((node) => node.declarations.map((decl) => decl.id.name))
.flat()
).toEqual(['xRel002'])
})
it("should not find used variable, even if it's heavy nested", () => {
// example code
const code = `
const deepWithin = 1
const veryNested = [
{ key: [{ key2: max(5, deepWithin) }] }
]
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables.find((node) =>
node.declarations.find((decl) => decl.id.name === 'deepWithin')
)
).toBeFalsy()
})
it('should not find used variable, even if used in a closure', () => {
// example code
const code = `const usedInClosure = 1
fn myFunction = () => {
return usedInClosure
}
`
// parse into ast
const ast = parse(code)
// find unused variables
const unusedVariables = findUnusedVariables(ast)
// check wether unused variables match the expected result
expect(
unusedVariables.find((node) =>
node.declarations.find((decl) => decl.id.name === 'usedInClosure')
)
).toBeFalsy()
})
// TODO: The commented code in the below does not even parse due to a KCL bug
// When it does parse correctly we'de expect 'a' to be defined but unused
// as it's shadowed by the 'a' in the inner scope
// it('should find unused variable when the same identifier is used in deeper scope', () => {
// const code = `const a = 1
// const b = 2
// fn (a) => {
// return a + 1
// }
// const myVar = b + 5`
// // parse into ast
// const ast = parse(code)
// console.log('ast', ast)
// // find unused variables
// const unusedVariables = findUnusedVariables(ast)
// console.log('unusedVariables', unusedVariables)
// // check wether unused variables match the expected result
// expect(
// unusedVariables
// .map((node) => node.declarations.map((decl) => decl.id.name))
// .flat()
// ).toEqual(['a'])
// })
})
describe('testing argIsNotIdentifier', () => {
const code = `const part001 = startSketchOn('XY')
|> startProfileAt([-1.2, 4.83], %)

View File

@ -392,6 +392,68 @@ export function findAllPreviousVariables(
}
}
export function findUnusedVariables(ast: Program): Array<VariableDeclaration> {
const declaredVariables = new Map<string, VariableDeclarator>() // Map to store declared variables
const usedVariables = new Set<string>() // Set to track used variables
// 1. Traverse and populate
ast.body.forEach((node) => {
traverse(node, {
enter(node) {
if (node.type === 'VariableDeclarator') {
// if node is a VariableDeclarator,
// add it to declaredVariables
declaredVariables.set(node.id.name, node)
} else if (node.type === 'Identifier') {
// if the node is Identifier, (use of a variable)
// check if it is a declared value,
// just in case...
// to be sure it's a part of the declared variables
if (declaredVariables.has(node.name)) {
// if yes - mark it as used
usedVariables.add(node.name)
}
} else if (node.type === 'VariableDeclaration') {
// check if the declaration is model-defining (contains PipeExpression)
const isModelDefining = node.declarations.some(
(decl) => decl.init?.type === 'PipeExpression'
)
if (isModelDefining) {
// If it is, mark all contained variables as used
node.declarations.forEach((decl) => {
usedVariables.add(decl.id.name)
})
}
}
},
})
})
// 2. Remove used variables from declaredVariables
usedVariables.forEach((name) => {
declaredVariables.delete(name)
})
// 3. collect unused VariableDeclarations
const unusedVariableDeclarations: Array<VariableDeclaration> = []
ast.body.forEach((node) => {
if (node.type === 'VariableDeclaration') {
const unusedDeclarators = node.declarations.filter((declarator) =>
declaredVariables.has(declarator.id.name)
)
if (unusedDeclarators.length > 0) {
unusedVariableDeclarations.push({
...node,
declarations: unusedDeclarators,
})
}
}
})
// 4. Return the unused variables
return unusedVariableDeclarations
}
type ReplacerFn = (_ast: Program, varName: string) => { modifiedAst: Program }
export function isNodeSafeToReplace(

View File

@ -75,6 +75,7 @@ export class CoreDumpManager {
const osinfo: OsInfo = {
platform,
arch,
browser: 'tauri',
version: kernelVersion,
}
return JSON.stringify(osinfo)
@ -89,6 +90,7 @@ export class CoreDumpManager {
platform: userAgent,
arch: userAgent,
version: userAgent,
browser: userAgent,
}
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))
}
@ -96,9 +98,9 @@ export class CoreDumpManager {
const parser = new UAParser(userAgent)
const parserResults = parser.getResult()
const osinfo: OsInfo = {
platform: parserResults.os.name,
arch: parserResults.cpu.architecture,
version: parserResults.os.version,
platform: parserResults.os.name || userAgent,
arch: parserResults.cpu.architecture || userAgent,
version: parserResults.os.version || userAgent,
browser: userAgent,
}
return new Promise((resolve) => resolve(JSON.stringify(osinfo)))

View File

@ -39,7 +39,9 @@ export const settingsLoader: LoaderFunction = async ({
)
if (projectPathData) {
const { project_path } = projectPathData
const { settings: s } = await loadAndValidateSettings(project_path)
const { settings: s } = await loadAndValidateSettings(
project_path || undefined
)
settings = s
}
}
@ -118,6 +120,7 @@ export const fileLoader: LoaderFunction = async ({
children: [],
kcl_file_count: 0,
directory_count: 0,
metadata: null,
default_file: project_path,
},
file: {

View File

@ -26,6 +26,13 @@ export async function setState(state: ProjectState | undefined): Promise<void> {
return await invoke('set_state', { state })
}
export async function renameProjectDirectory(
projectPath: string,
newName: string
): Promise<string> {
return invoke<string>('rename_project_directory', { projectPath, newName })
}
// Get the initial default dir for holding all projects.
export async function getInitialDefaultDir(): Promise<string> {
if (!isTauri()) {

View File

@ -45,7 +45,7 @@ export function sortProject(project: FileEntry[]): FileEntry[] {
if ('children' in fileOrDir && fileOrDir.children !== undefined) {
return {
...fileOrDir,
children: sortProject(fileOrDir.children),
children: sortProject(fileOrDir.children || []),
}
} else {
return fileOrDir

View File

@ -1,5 +1,5 @@
import { FormEvent, useEffect } from 'react'
import { remove, rename } from '@tauri-apps/plugin-fs'
import { remove } from '@tauri-apps/plugin-fs'
import {
getNextProjectIndex,
interpolateProjectNameWithIndex,
@ -35,7 +35,11 @@ import { useLspContext } from 'components/LspProvider'
import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { LowerRightControls } from 'components/LowerRightControls'
import { Project } from 'wasm-lib/kcl/bindings/Project'
import { createNewProjectDirectory, listProjects } from 'lib/tauri'
import {
createNewProjectDirectory,
listProjects,
renameProjectDirectory,
} from 'lib/tauri'
// This route only opens in the Tauri desktop context for now,
// as defined in Router.tsx, so we can use the Tauri APIs and types.
@ -122,10 +126,9 @@ const Home = () => {
name = interpolateProjectNameWithIndex(name, nextIndex)
}
await rename(
await renameProjectDirectory(
await join(context.defaultDirectory, oldName),
await join(context.defaultDirectory, name),
{}
name
)
return `Successfully renamed "${oldName}" to "${name}"`
},

View File

@ -1915,7 +1915,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.1.55"
version = "0.1.57"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -4390,9 +4390,8 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ts-rs"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2cae1fc5d05d47aa24b64f9a4f7cba24cdc9187a2084dd97ac57bef5eccae6"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
dependencies = [
"chrono",
"thiserror",
@ -4403,11 +4402,9 @@ dependencies = [
[[package]]
name = "ts-rs-macros"
version = "7.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73f7f9b821696963053a89a7bd8b292dc34420aea8294d7b225274d488f3ec92"
version = "8.1.0"
source = "git+https://github.com/Aleph-Alpha/ts-rs#badbac08e61e65b312880aa64e9ece2976f1bbef"
dependencies = [
"Inflector",
"proc-macro2",
"quote",
"syn 2.0.65",

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.1.55"
version = "0.1.57"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -38,7 +38,8 @@ serde_json = "1.0.116"
sha2 = "0.10.8"
thiserror = "1.0.61"
toml = "0.8.13"
ts-rs = { version = "7.1.1", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings"] }
# TODO: change this to a cargo release once 8.1.1 comes out
ts-rs = { git = "https://github.com/Aleph-Alpha/ts-rs", features = ["uuid-impl", "url-impl", "chrono-impl", "no-serde-warnings"] }
url = { version = "2.5.0", features = ["serde"] }
uuid = { version = "1.8.0", features = ["v4", "js", "serde"] }
validator = { version = "0.18.1", features = ["derive"] }

View File

@ -1,15 +1,15 @@
//! Functions for generating docs for our stdlib functions.
use crate::std::Primitive;
use anyhow::Result;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::path::Path;
use tower_lsp::lsp_types::{
CompletionItem, CompletionItemKind, CompletionItemLabelDetails, Documentation, InsertTextFormat, MarkupContent,
MarkupKind, ParameterInformation, ParameterLabel, SignatureHelp, SignatureInformation,
};
use crate::std::Primitive;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
@ -262,21 +262,30 @@ impl<'de> Deserialize<'de> for Box<dyn StdLibFn> {
}
impl ts_rs::TS for dyn StdLibFn {
const EXPORT_TO: Option<&'static str> = Some("bindings/StdLibFnData");
type WithoutGenerics = Self;
fn name() -> String {
"StdLibFnData".to_string()
}
fn dependencies() -> Vec<ts_rs::Dependency>
where
Self: 'static,
{
StdLibFnData::dependencies()
fn decl() -> String {
StdLibFnData::decl()
}
fn transparent() -> bool {
StdLibFnData::transparent()
fn decl_concrete() -> String {
StdLibFnData::decl_concrete()
}
fn inline() -> String {
StdLibFnData::inline()
}
fn inline_flattened() -> String {
StdLibFnData::inline_flattened()
}
fn output_path() -> Option<&'static Path> {
StdLibFnData::output_path()
}
}

View File

@ -244,11 +244,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
(
PlaneName::Xz,
(
Point3d {
x: -1.0,
y: 0.0,
z: 0.0,
},
Point3d { x: 1.0, y: 0.0, z: 0.0 },
Point3d { x: 0.0, y: 0.0, z: 1.0 },
Some(Color {
r: 0.28,
@ -286,7 +282,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
PlaneName::NegXz,
(
Point3d {
x: 1.0, // TODO this should be -1.0
x: -1.0,
y: 0.0,
z: 0.0,
},

View File

@ -1121,7 +1121,7 @@ impl ExecutorContext {
&self,
program: crate::ast::types::Program,
memory: &mut ProgramMemory,
_body_type: BodyType,
body_type: BodyType,
) -> Result<ProgramMemory, KclError> {
let pipe_info = PipeInfo::default();
@ -1328,8 +1328,10 @@ impl ExecutorContext {
}
}
// Flush the batch queue.
self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
if BodyType::Root == body_type {
// Flush the batch queue.
self.engine.flush_batch(SourceRange([program.end, program.end])).await?;
}
Ok(memory.clone())
}

View File

@ -67,13 +67,13 @@ impl ProjectState {
// Get the extension of the file.
let extension = source_path
.extension()
.ok_or_else(|| anyhow::anyhow!("Error getting the extension of the file: {}", source_path.display()))?;
.ok_or_else(|| anyhow::anyhow!("Error getting the extension of the file: `{}`", source_path.display()))?;
let ext = extension.to_string_lossy().to_string();
// Check if the extension is a relevant file type.
if !crate::settings::utils::RELEVANT_EXTENSIONS.contains(&ext) || ext == "toml" {
return Err(anyhow::anyhow!(
"File type ({}) cannot be opened with this app: {}, try opening one of the following file types: {}",
"File type ({}) cannot be opened with this app: `{}`, try opening one of the following file types: {}",
ext,
source_path.display(),
crate::settings::utils::RELEVANT_EXTENSIONS.join(", ")
@ -89,11 +89,6 @@ impl ProjectState {
)
})?;
// Load the details about the project from the parent directory.
let project = Project::from_path(&parent)
.await
.map_err(|e| anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e))?;
// If we got a import model file, we need to check if we have a file in the project for
// this import model.
if crate::settings::utils::IMPORT_FILE_EXTENSIONS.contains(&ext) {
@ -112,7 +107,7 @@ impl ProjectState {
tokio::fs::write(
&kcl_wrapper_file_path,
format!(
r#"// This file was automatically generated by the application when you
r#"// This file was automatically generated by the application when you
// double-clicked on the model file.
// You can edit this file to add your own content.
// But we recommend you keep the import statement as it is.
@ -126,12 +121,23 @@ const model = import("{}")"#,
.await?;
}
// Load the details about the project from the parent directory.
// We do this after we generate the import file so that the file is included in the project.
let project = Project::from_path(&parent)
.await
.map_err(|e| anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e))?;
return Ok(ProjectState {
project,
current_file: Some(kcl_wrapper_file_path.display().to_string()),
});
}
// Load the details about the project from the parent directory.
let project = Project::from_path(&parent)
.await
.map_err(|e| anyhow::anyhow!("Error loading project from path {}: {:?}", source_path.display(), e))?;
Ok(ProjectState {
project,
current_file: Some(source_path.display().to_string()),
@ -163,8 +169,7 @@ impl ProjectRoute {
{
// Get the project name.
if let Some(project_name) = path
.strip_prefix(&configuration.settings.project.directory)
.unwrap()
.strip_prefix(&configuration.settings.project.directory)?
.iter()
.next()
{
@ -347,6 +352,39 @@ where
Ok(default_file.display().to_string())
}
#[cfg(not(target_arch = "wasm32"))]
/// Rename a directory for a project.
/// This returns the new path of the directory.
pub async fn rename_project_directory<P>(path: P, new_name: &str) -> Result<std::path::PathBuf>
where
P: AsRef<Path> + Send,
{
if new_name.is_empty() {
return Err(anyhow::anyhow!("New name for project cannot be empty"));
}
// Make sure the path is a directory.
if !path.as_ref().is_dir() {
return Err(anyhow::anyhow!("Path `{}` is not a directory", path.as_ref().display()));
}
// Make sure the new name does not exist.
let new_path = path
.as_ref()
.parent()
.ok_or_else(|| anyhow::anyhow!("Parent directory of `{}` not found", path.as_ref().display()))?
.join(new_name);
if new_path.exists() {
return Err(anyhow::anyhow!(
"Path `{}` already exists, cannot rename to an existing path",
new_path.display()
));
}
tokio::fs::rename(path.as_ref(), &new_path).await?;
Ok(new_path)
}
/// Information about a file or directory.
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
#[ts(export)]
@ -722,4 +760,342 @@ mod tests {
);
std::fs::remove_dir_all(dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_empty_dir() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_empty_name() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let result = super::rename_project_directory(&dir, "").await;
assert!(result.is_err());
assert_eq!(result.unwrap_err().to_string(), "New name for project cannot be empty");
std::fs::remove_dir_all(dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_non_empty_dir() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::write(dir.join("main.kcl"), vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_non_empty_dir_recursive() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
std::fs::create_dir_all(dir.join("assembly")).unwrap();
std::fs::write(dir.join("assembly").join("main.kcl"), vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = super::rename_project_directory(&dir, &new_name).await.unwrap();
assert_eq!(new_dir, std::env::temp_dir().join(&new_name));
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_dir_is_file() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::write(&dir, vec![]).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let result = super::rename_project_directory(&dir, &new_name).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!("Path `{}` is not a directory", dir.display())
);
std::fs::remove_file(dir).unwrap();
}
#[tokio::test]
async fn test_rename_project_directory_new_name_exists() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&dir).unwrap();
let new_name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let new_dir = std::env::temp_dir().join(&new_name);
std::fs::create_dir_all(&new_dir).unwrap();
let result = super::rename_project_directory(&dir, &new_name).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"Path `{}` already exists, cannot rename to an existing path",
new_dir.display()
)
);
std::fs::remove_dir_all(new_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_source_path_dot() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
// Set the current directory to the temp project directory.
// This is to simulate the "." path.
std::env::set_current_dir(&tmp_project_dir).unwrap();
let state = super::ProjectState::new_from_path(std::path::PathBuf::from("."))
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(
state
.project
.file
.path
// macOS adds /private to the path i think because we changed curdirs
.trim_start_matches("/private"),
tmp_project_dir.display().to_string()
);
assert_eq!(
state
.current_file
.unwrap()
// macOS adds /private to the path i think because we changed curdirs
.trim_start_matches("/private"),
tmp_project_dir.join("main.kcl").display().to_string()
);
assert_eq!(
state
.project
.default_file
// macOS adds /private to the path i think because we changed curdirs
.trim_start_matches("/private"),
tmp_project_dir.join("main.kcl").display().to_string()
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_main_kcl_not_exists() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
let state = super::ProjectState::new_from_path(tmp_project_dir.clone())
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
assert_eq!(
state.current_file,
Some(tmp_project_dir.join("main.kcl").display().to_string())
);
assert_eq!(
state.project.default_file,
tmp_project_dir.join("main.kcl").display().to_string()
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_main_kcl_exists() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("main.kcl"), vec![]).unwrap();
let state = super::ProjectState::new_from_path(tmp_project_dir.clone())
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
assert_eq!(
state.current_file,
Some(tmp_project_dir.join("main.kcl").display().to_string())
);
assert_eq!(
state.project.default_file,
tmp_project_dir.join("main.kcl").display().to_string()
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_main_kcl() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("main.kcl"), vec![]).unwrap();
let state = super::ProjectState::new_from_path(tmp_project_dir.join("main.kcl"))
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
assert_eq!(
state.current_file,
Some(tmp_project_dir.join("main.kcl").display().to_string())
);
assert_eq!(
state.project.default_file,
tmp_project_dir.join("main.kcl").display().to_string()
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_thing_kcl() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("thing.kcl"), vec![]).unwrap();
let state = super::ProjectState::new_from_path(tmp_project_dir.join("thing.kcl"))
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
assert_eq!(
state.current_file,
Some(tmp_project_dir.join("thing.kcl").display().to_string())
);
assert_eq!(
state.project.default_file,
tmp_project_dir.join("thing.kcl").display().to_string()
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_model_obj() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("model.obj"), vec![]).unwrap();
let state = super::ProjectState::new_from_path(tmp_project_dir.join("model.obj"))
.await
.unwrap();
assert_eq!(state.project.file.name, name);
assert_eq!(state.project.file.path, tmp_project_dir.display().to_string());
assert_eq!(
state.current_file,
Some(tmp_project_dir.join("model.obj.kcl").display().to_string())
);
assert_eq!(
state.project.default_file,
tmp_project_dir.join("model.obj.kcl").display().to_string()
);
// Get the contents of the generated kcl file.
let kcl_file_contents = tokio::fs::read(tmp_project_dir.join("model.obj.kcl")).await.unwrap();
assert_eq!(
String::from_utf8_lossy(&kcl_file_contents),
r#"// This file was automatically generated by the application when you
// double-clicked on the model file.
// You can edit this file to add your own content.
// But we recommend you keep the import statement as it is.
// For more information on the import statement, see the documentation at:
// https://zoo.dev/docs/kcl/import
const model = import("model.obj")"#
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_settings_toml() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("settings.toml"), vec![]).unwrap();
let result = super::ProjectState::new_from_path(tmp_project_dir.join("settings.toml")).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"File type (toml) cannot be opened with this app: `{}`, try opening one of the following file types: stp, glb, fbxb, fbx, gltf, obj, ply, sldprt, step, stl, kcl",
tmp_project_dir.join("settings.toml").display()
)
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_non_relevant_file() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("settings.docx"), vec![]).unwrap();
let result = super::ProjectState::new_from_path(tmp_project_dir.join("settings.docx")).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"File type (docx) cannot be opened with this app: `{}`, try opening one of the following file types: stp, glb, fbxb, fbx, gltf, obj, ply, sldprt, step, stl, kcl",
tmp_project_dir.join("settings.docx").display()
)
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
#[tokio::test]
async fn test_project_state_new_from_path_explicit_open_no_file_extension() {
let name = format!("kittycad-modeling-projects-{}", uuid::Uuid::new_v4());
let tmp_project_dir = std::env::temp_dir().join(&name);
std::fs::create_dir_all(&tmp_project_dir).unwrap();
std::fs::write(tmp_project_dir.join("file"), vec![]).unwrap();
let result = super::ProjectState::new_from_path(tmp_project_dir.join("file")).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
format!(
"Error getting the extension of the file: `{}`",
tmp_project_dir.join("file").display()
)
);
std::fs::remove_dir_all(tmp_project_dir).unwrap();
}
}

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