Compare commits

...

11 Commits

Author SHA1 Message Date
df8c17ac18 Release derive-docs 0.1.5 (#1236) 2023-12-19 17:06:18 +00:00
9d40f282a8 Remove just one enum (#1096)
# Problem

This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.

Previously, if users want to put a tag on the arc, the function's parameters change type.

```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```

# Solution

My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like

```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```

This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
a61d931826 lint: Remove unnecessary parentheses (#1233)
* lint: Remove unnecessary parentheses

* Fix lints
2023-12-18 23:31:19 -06:00
418350ddbc Bump tauri from 1.5.2 to 1.5.3 in /src-tauri (#1157)
Bumps [tauri](https://github.com/tauri-apps/tauri) from 1.5.2 to 1.5.3.
- [Release notes](https://github.com/tauri-apps/tauri/releases)
- [Commits](https://github.com/tauri-apps/tauri/compare/tauri-v1.5.2...tauri-v1.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 11:08:13 -06:00
d43abe20d9 Bump deps (#1230) 2023-12-18 11:02:58 -06:00
84380f3da9 Zoo rebrand (#1228)
* Add new logomarks

* Replace KittyCAD and KCMA with Zoo and ZMA anywhere it's safe

* fmt

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

* Make README logo a PNG instead of an SVG

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-18 06:15:26 -05:00
eea55ff2b1 fix gltf snapshots (#1224)
* fix gltf snapshots

* delete old assets
2023-12-18 03:22:15 +00:00
10b6c1cfbc Snapshot exports (#1223)
* delete old exports

* update test

* tweaks and assets

* install kittycad cli

* fix weird typo

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

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-12-18 13:17:09 +11:00
d5570e5c62 debug bug (#1211)
* remove camera_drag_move from debug panel

* remove log
2023-12-17 18:10:31 +00:00
0c9589f7ee Revert "Bump actions/upload-artifact from 3 to 4" (#1220)
Revert "Bump actions/upload-artifact from 3 to 4 (#1214)"

This reverts commit 0825cb5a59.
2023-12-15 07:29:58 -05:00
ddf66c1e0f Revert "Bump actions/download-artifact from 3 to 4" (#1219)
Revert "Bump actions/download-artifact from 3 to 4 (#1213)"

This reverts commit 0b5bb5f77d.
2023-12-15 07:29:13 -05:00
75 changed files with 1481 additions and 4154 deletions

View File

@ -107,7 +107,7 @@ jobs:
echo "$(jq --arg url 'https://dl.kittycad.io/releases/modeling-app/nightly/last_update.json' \ echo "$(jq --arg url 'https://dl.kittycad.io/releases/modeling-app/nightly/last_update.json' \
'.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json '.tauri.updater.endpoints[]=$url' src-tauri/tauri.release.conf.json --indent 2)" > src-tauri/tauri.release.conf.json
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: github.event_name == 'schedule' if: github.event_name == 'schedule'
with: with:
path: | path: |
@ -128,7 +128,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
- name: Copy updated .json files - name: Copy updated .json files
if: github.event_name == 'schedule' if: github.event_name == 'schedule'
@ -242,7 +242,7 @@ jobs:
with: with:
args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}" args: "${{ matrix.os == 'macos-latest' && '--target universal-apple-darwin' || '' }} ${{ env.TAURI_CONF_ARGS }}"
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
env: env:
PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }} PREFIX: ${{ matrix.os == 'macos-latest' && 'src-tauri/target/universal-apple-darwin' || 'src-tauri/target' }}
MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }} MODE: ${{ env.BUILD_RELEASE == 'true' && 'release' || 'debug' }}
@ -272,7 +272,7 @@ jobs:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }} NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Nightly build, commit {0}', github.sha) }}
BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }} BUCKET_DIR: ${{ github.event_name == 'release' && 'dl.kittycad.io/releases/modeling-app' || 'dl.kittycad.io/releases/modeling-app/nightly' }}
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
- name: Generate the update static endpoint - name: Generate the update static endpoint
run: | run: |

View File

@ -14,6 +14,7 @@ jobs:
with: with:
node-version-file: '.nvmrc' node-version-file: '.nvmrc'
cache: 'yarn' cache: 'yarn'
- uses: KittyCAD/action-install-cli@v0.2.16
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
- name: Install Playwright Browsers - name: Install Playwright Browsers
@ -33,7 +34,8 @@ jobs:
env: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4 snapshottoken: ${{ secrets.KITTYCAD_API_TOKEN }}
- uses: actions/upload-artifact@v3
if: always() if: always()
with: with:
name: playwright-report name: playwright-report
@ -68,7 +70,7 @@ jobs:
env: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: always() if: always()
with: with:
name: playwright-report name: playwright-report
@ -106,7 +108,7 @@ jobs:
env: env:
CI: true CI: true
token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} token: ${{ secrets.KITTYCAD_API_TOKEN_DEV }}
- uses: actions/upload-artifact@v4 - uses: actions/upload-artifact@v3
if: always() if: always()
with: with:
name: playwright-report name: playwright-report

9
.gitignore vendored
View File

@ -37,6 +37,15 @@ src/wasm-lib/lcov.info
e2e/playwright/playwright-secrets.env e2e/playwright/playwright-secrets.env
e2e/playwright/temp1.png e2e/playwright/temp1.png
e2e/playwright/temp2.png e2e/playwright/temp2.png
# exports from snapshot-tests.spec.ts
e2e/playwright/export-snapshots/*.ply
e2e/playwright/export-snapshots/*.obj
e2e/playwright/export-snapshots/*.step
e2e/playwright/export-snapshots/*.stl
e2e/playwright/export-snapshots/*binary.gltf
e2e/playwright/export-snapshots/*embedded.gltf
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/blob-report/ /blob-report/

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2023 The KittyCAD Authors Copyright (c) 2023 The Zoo Authors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,17 +1,17 @@
![KittyCAD Modeling App](/public/kcma-logomark-outlined.png) ![Zoo Modeling App](/public/zma-logomark-outlined.png)
## KittyCAD Modeling App ## Zoo Modeling App
live at [app.kittycad.io](https://app.kittycad.io/) live at [app.zoo.dev](https://app.zoo.dev/)
A CAD application from the future, brought to you by the [KittyCAD team](https://kittycad.io). A CAD application from the future, brought to you by the [Zoo team](https://zoo.dev).
The KittyCAD modeling app is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence: Modeling App is our take on what a modern modelling experience can be. It is applying several lessons learned in the decades since most major CAD tools came into existence:
- All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text" - All artifacts—including parts and assemblies—should be represented as human-readable code. At the end of the day, your CAD project should be "plain text"
- This makes version control—which is a solved problem in software engineering—trivial for CAD - This makes version control—which is a solved problem in software engineering—trivial for CAD
- All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood - All GUI (or point-and-click) interactions should be actions performed on this code representation under the hood
- This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in KittyCAD Modeling App - This unlocks a hybrid approach to modeling. Whether you point-and-click as you always have or you write your own KCL code, you are performing the same action in Modeling App
- Everything graphics _has_ to be built for the GPU - Everything graphics _has_ to be built for the GPU
- Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it - Most CAD applications have had to retrofit support for GPUs, but our geometry engine is made for GPUs (primarily Nvidia's Vulkan), getting the order of magnitude rendering performance boost with it
- Make the resource-intensive pieces of an application auto-scaling - Make the resource-intensive pieces of an application auto-scaling
@ -19,9 +19,9 @@ The KittyCAD modeling app is our take on what a modern modelling experience can
We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours! We are excited about what a small team of people could build in a short time with our API. We welcome you to try our API, build your own applications, or contribute to ours!
KittyCAD Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more. Modeling App is a _hybrid_ user interface for CAD modeling. You can point-and-click to design parts (and soon assemblies), but everything you make is really just [`kcl` code](https://github.com/KittyCAD/kcl-experiments) under the hood. All of your CAD models can be checked into source control such as GitHub and responsibly versioned, rolled back, and more.
The 3D view in KittyCAD Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine. The 3D view in Modeling App is just a video stream from our hosted geometry engine. The app sends new modeling commands to the engine via WebSockets, which returns back video frames of the view within the engine.
## Tools ## Tools
@ -183,9 +183,9 @@ For more information on fuzzing you can check out
First time running plawright locally, you'll need to add the secrets file First time running plawright locally, you'll need to add the secrets file
```bash ```bash
touch ./e2e/playwright/playwright-secrets.env touch ./e2e/playwright/playwright-secrets.env
echo 'token="your-token"' > ./e2e/playwright/playwright-secrets.env echo 'token="your-token"\nsnapshottoken="your-snapshot-token"' > ./e2e/playwright/playwright-secrets2.env
``` ```
then replace "your-token" with a dev token from dev.kittycad.io/account/api-tokens then replace "your-token" with a dev token from dev.zoo.dev/account/api-tokens
then: then:
run playwright run playwright

View File

@ -19765,46 +19765,16 @@
"tags": [], "tags": [],
"args": [ "args": [
{ {
"name": "data", "name": "to",
"type": "TangentialArcToData", "type": "[number]",
"schema": { "schema": {
"description": "Data to draw a tangential arc to a specific point.", "type": "array",
"anyOf": [ "items": {
{ "type": "number",
"description": "A point with a tag.", "format": "double"
"type": "object", },
"required": [ "maxItems": 2,
"tag", "minItems": 2
"to"
],
"properties": {
"tag": {
"description": "The tag.",
"type": "string"
},
"to": {
"description": "Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
}
}
},
{
"description": "A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.",
"type": "array",
"items": {
"type": "number",
"format": "double"
},
"maxItems": 2,
"minItems": 2
}
]
}, },
"required": true "required": true
}, },
@ -20246,6 +20216,15 @@
} }
}, },
"required": true "required": true
},
{
"name": "tag",
"type": "String",
"schema": {
"type": "string",
"nullable": true
},
"required": true
} }
], ],
"returnValue": { "returnValue": {

View File

@ -3905,21 +3905,12 @@ Draw an arc.
``` ```
tangentialArcTo(data: TangentialArcToData, sketch_group: SketchGroup) -> SketchGroup tangentialArcTo(to: [number], sketch_group: SketchGroup, tag: String) -> SketchGroup
``` ```
#### Arguments #### Arguments
* `data`: `TangentialArcToData` - Data to draw a tangential arc to a specific point. * `to`: `[number]`
```
{
// The tag.
tag: string,
// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
to: [number, number],
} |
[number, number]
```
* `sketch_group`: `SketchGroup` - A sketch group is a collection of paths. * `sketch_group`: `SketchGroup` - A sketch group is a collection of paths.
``` ```
{ {
@ -3985,6 +3976,7 @@ tangentialArcTo(data: TangentialArcToData, sketch_group: SketchGroup) -> SketchG
}], }],
} }
``` ```
* `tag`: `String`
#### Returns #### Returns

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

File diff suppressed because it is too large Load Diff

View File

@ -1,189 +0,0 @@
v 0 -4 0
v 0 0 0
v 0 -4 -1
v 0 0 -1
v 3.0950184 -4 -1
v 3.0950184 0 -1
v 5.9513144 -4 -3
v 5.9513144 0 -3
v 9.5 -4 -3
v 9.5 0 -3
v 9.5 -4 -2.5
v 9.5 0 -2.5
v 6.108964 -4 -2.5
v 6.108964 0 -2.5
v 3.4311862 -4 -0.625
v 4.323779 -4 -1.25
v 4.323779 -0 -1.25
v 3.4311862 -0 -0.625
v 2.5385938 0 0
v 2.5385938 -4 0
v 3.342784 -4 0.375
v 4.146974 -4 0.75
v 3.342784 -0 0.375
v 4.146974 -0 0.75
v 5.755354 0 1.5
v 5.755354 -4 1.5
v 9.5 -4 1.5
v 9.5 0 1.5
v 9.5 -4 2
v 9.5 0 2
v 5.644507 -4 2
v 5.644507 0 2
v 3.5 -4 1
v 3.5 0 1
v 0 -4 1
v 0 0 1
vt 0.0127 -0.0508
vt 0.0127 0.0508
vt -0.0127 -0.0508
vt -0.0127 0.0508
vt -0.039306734 0.0508
vt -0.039306734 -0.0508
vt 0.039306734 0.0508
vt 0.039306734 -0.0508
vt -0.04428355 0.0508
vt -0.04428355 -0.0508
vt 0.04428355 0.0508
vt 0.04428355 -0.0508
vt -0.045068305 0.0508
vt -0.045068305 -0.0508
vt 0.045068305 0.0508
vt 0.045068305 -0.0508
vt -0.00635 0.0508
vt -0.00635 -0.0508
vt 0.00635 0.0508
vt 0.00635 -0.0508
vt 0.04306616 -0.0508
vt 0.04306616 0.0508
vt -0.04306616 -0.0508
vt -0.04306616 0.0508
vt -0.027677217 -0.0508
vt 0.000000000000000048572257 -0.0508
vt 0.000000000000000048572257 0.0508
vt 0.055354435 -0.0508
vt 0.055354435 0.0508
vt -0.027677217 0.0508
vt -0.055354435 0.0508
vt -0.055354435 -0.0508
vt -0.02253807 0.0508
vt -0.04507614 0.0508
vt -0.04507614 -0.0508
vt 0.00000000000000005551115 0.0508
vt -0.02253807 -0.0508
vt 0.00000000000000005551115 -0.0508
vt 0.04507614 -0.0508
vt 0.04507614 0.0508
vt -0.047557 0.0508
vt -0.047557 -0.0508
vt 0.047557 0.0508
vt 0.047557 -0.0508
vt 0.04896476 -0.0508
vt 0.04896476 0.0508
vt -0.04896476 -0.0508
vt -0.04896476 0.0508
vt 0.03005076 -0.0508
vt 0.03005076 0.0508
vt -0.03005076 -0.0508
vt -0.03005076 0.0508
vt 0.04445 -0.0508
vt 0.04445 0.0508
vt -0.04445 -0.0508
vt -0.04445 0.0508
vt 0.08490671 0.009525
vt 0.06448028 0
vt 0.0889 0.0254
vt 0.08715213 -0.015875
vt 0.10982399 -0.03175
vt 0.07861347 -0.0254
vt 0.10533314 0.01905
vt 0.15116338 -0.0762
vt 0 -0.0254
vt 0 0
vt 0.2413 -0.0762
vt 0.15516768 -0.0635
vt 0.2413 -0.0635
vt 0.14337048 0.0508
vt 0.146186 0.0381
vt 0.2413 0.0381
vt 0.2413 0.0508
vt 0 0.0254
vn -1 -0 0
vn 0 -0 -1
vn -0.57357645 -0 -0.81915206
vn 1 -0 0
vn 0 -0 1
vn 0.57357645 -0 0.81915206
vn 0.42261827 -0 -0.9063078
vn -0.42261827 -0 0.9063078
vn -0 1 -0
vn 0 -1 0
o Unnamed-0
f 1/1/1 2/2/1 3/3/1
f 3/3/1 2/2/1 4/4/1
f 3/5/2 4/6/2 5/7/2
f 5/7/2 4/6/2 6/8/2
f 5/9/3 6/10/3 7/11/3
f 7/11/3 6/10/3 8/12/3
f 7/13/2 8/14/2 9/15/2
f 9/15/2 8/14/2 10/16/2
f 9/17/4 10/18/4 11/19/4
f 11/19/4 10/18/4 12/20/4
f 11/21/5 12/22/5 13/23/5
f 13/23/5 12/22/5 14/24/5
f 15/25/6 16/26/6 17/27/6
f 16/26/6 13/28/6 14/29/6
f 18/30/6 19/31/6 20/32/6
f 15/25/6 18/30/6 20/32/6
f 16/26/6 14/29/6 17/27/6
f 18/30/6 15/25/6 17/27/6
f 21/33/7 20/34/7 19/35/7
f 22/36/7 21/33/7 23/37/7
f 23/37/7 24/38/7 22/36/7
f 24/38/7 25/39/7 26/40/7
f 21/33/7 19/35/7 23/37/7
f 26/40/7 22/36/7 24/38/7
f 26/41/2 25/42/2 27/43/2
f 27/43/2 25/42/2 28/44/2
f 27/17/4 28/18/4 29/19/4
f 29/19/4 28/18/4 30/20/4
f 29/45/5 30/46/5 31/47/5
f 31/47/5 30/46/5 32/48/5
f 31/49/8 32/50/8 33/51/8
f 33/51/8 32/50/8 34/52/8
f 33/53/5 34/54/5 35/55/5
f 35/55/5 34/54/5 36/56/5
f 35/1/1 36/2/1 1/3/1
f 1/3/1 36/2/1 2/4/1
f 23/57/9 19/58/9 34/59/9
f 18/60/9 17/61/9 6/62/9
f 23/57/9 34/59/9 24/63/9
f 17/61/9 8/64/9 6/62/9
f 4/65/9 19/58/9 6/62/9
f 4/65/9 2/66/9 19/58/9
f 10/67/9 14/68/9 12/69/9
f 10/67/9 8/64/9 14/68/9
f 8/64/9 17/61/9 14/68/9
f 32/70/9 25/71/9 24/63/9
f 6/62/9 19/58/9 18/60/9
f 24/63/9 34/59/9 32/70/9
f 28/72/9 25/71/9 30/73/9
f 25/71/9 32/70/9 30/73/9
f 19/58/9 2/66/9 36/74/9
f 34/59/9 19/58/9 36/74/9
f 21/57/10 33/59/10 20/58/10
f 22/63/10 33/59/10 21/57/10
f 15/60/10 5/62/10 16/61/10
f 22/63/10 26/71/10 31/70/10
f 35/74/10 20/58/10 33/59/10
f 35/74/10 1/66/10 20/58/10
f 31/70/10 26/71/10 29/73/10
f 29/73/10 26/71/10 27/72/10
f 22/63/10 31/70/10 33/59/10
f 20/58/10 5/62/10 15/60/10
f 16/61/10 5/62/10 7/64/10
f 13/68/10 16/61/10 7/64/10
f 11/69/10 13/68/10 9/67/10
f 13/68/10 7/64/10 9/67/10
f 20/58/10 3/65/10 5/62/10
f 3/65/10 20/58/10 1/66/10

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -1,282 +0,0 @@
ply
format ascii 1.0
comment Generated by kittycad.io
element vertex 204
property float x
property float y
property float z
element face 68
property list uchar uint vertex_indices
end_header
0 0 4
0 0 0
0 -1 4
0 -1 4
0 0 0
0 -1 0
0 -1 4
0 -1 0
3.0950184 -1 4
3.0950184 -1 4
0 -1 0
3.0950184 -1 0
3.0950184 -1 4
3.0950184 -1 0
5.9513144 -3 4
5.9513144 -3 4
3.0950184 -1 0
5.9513144 -3 0
5.9513144 -3 4
5.9513144 -3 0
9.5 -3 4
9.5 -3 4
5.9513144 -3 0
9.5 -3 0
9.5 -3 4
9.5 -3 0
9.5 -2.5 4
9.5 -2.5 4
9.5 -3 0
9.5 -2.5 0
9.5 -2.5 4
9.5 -2.5 0
6.108964 -2.5 4
6.108964 -2.5 4
9.5 -2.5 0
6.108964 -2.5 0
3.4311862 -0.625 4
4.323779 -1.25 4
4.323779 -1.25 0
4.323779 -1.25 4
6.108964 -2.5 4
6.108964 -2.5 0
3.4311862 -0.625 0
2.5385938 0 0
2.5385938 0 4
3.4311862 -0.625 4
3.4311862 -0.625 0
2.5385938 0 4
4.323779 -1.25 4
6.108964 -2.5 0
4.323779 -1.25 0
3.4311862 -0.625 0
3.4311862 -0.625 4
4.323779 -1.25 0
3.342784 0.375 4
2.5385938 0 4
2.5385938 0 0
4.146974 0.75 4
3.342784 0.375 4
3.342784 0.375 0
3.342784 0.375 0
4.146974 0.75 0
4.146974 0.75 4
4.146974 0.75 0
5.755354 1.5 0
5.755354 1.5 4
3.342784 0.375 4
2.5385938 0 0
3.342784 0.375 0
5.755354 1.5 4
4.146974 0.75 4
4.146974 0.75 0
5.755354 1.5 4
5.755354 1.5 0
9.5 1.5 4
9.5 1.5 4
5.755354 1.5 0
9.5 1.5 0
9.5 1.5 4
9.5 1.5 0
9.5 2 4
9.5 2 4
9.5 1.5 0
9.5 2 0
9.5 2 4
9.5 2 0
5.644507 2 4
5.644507 2 4
9.5 2 0
5.644507 2 0
5.644507 2 4
5.644507 2 0
3.5 1 4
3.5 1 4
5.644507 2 0
3.5 1 0
3.5 1 4
3.5 1 0
0 1 4
0 1 4
3.5 1 0
0 1 0
0 1 4
0 1 0
0 0 4
0 0 4
0 1 0
0 0 0
3.342784 0.375 0
2.5385938 0 0
3.5 1 0
3.4311862 -0.625 0
4.323779 -1.25 0
3.0950184 -1 0
3.342784 0.375 0
3.5 1 0
4.146974 0.75 0
4.323779 -1.25 0
5.9513144 -3 0
3.0950184 -1 0
0 -1 0
2.5385938 0 0
3.0950184 -1 0
0 -1 0
0 0 0
2.5385938 0 0
9.5 -3 0
6.108964 -2.5 0
9.5 -2.5 0
9.5 -3 0
5.9513144 -3 0
6.108964 -2.5 0
5.9513144 -3 0
4.323779 -1.25 0
6.108964 -2.5 0
5.644507 2 0
5.755354 1.5 0
4.146974 0.75 0
3.0950184 -1 0
2.5385938 0 0
3.4311862 -0.625 0
4.146974 0.75 0
3.5 1 0
5.644507 2 0
9.5 1.5 0
5.755354 1.5 0
9.5 2 0
5.755354 1.5 0
5.644507 2 0
9.5 2 0
2.5385938 0 0
0 0 0
0 1 0
3.5 1 0
2.5385938 0 0
0 1 0
3.342784 0.375 4
3.5 1 4
2.5385938 0 4
4.146974 0.75 4
3.5 1 4
3.342784 0.375 4
3.4311862 -0.625 4
3.0950184 -1 4
4.323779 -1.25 4
4.146974 0.75 4
5.755354 1.5 4
5.644507 2 4
0 1 4
2.5385938 0 4
3.5 1 4
0 1 4
0 0 4
2.5385938 0 4
5.644507 2 4
5.755354 1.5 4
9.5 2 4
9.5 2 4
5.755354 1.5 4
9.5 1.5 4
4.146974 0.75 4
5.644507 2 4
3.5 1 4
2.5385938 0 4
3.0950184 -1 4
3.4311862 -0.625 4
4.323779 -1.25 4
3.0950184 -1 4
5.9513144 -3 4
6.108964 -2.5 4
4.323779 -1.25 4
5.9513144 -3 4
9.5 -2.5 4
6.108964 -2.5 4
9.5 -3 4
6.108964 -2.5 4
5.9513144 -3 4
9.5 -3 4
2.5385938 0 4
0 -1 4
3.0950184 -1 4
0 -1 4
2.5385938 0 4
0 0 4
3 0 1 2
3 3 4 5
3 6 7 8
3 9 10 11
3 12 13 14
3 15 16 17
3 18 19 20
3 21 22 23
3 24 25 26
3 27 28 29
3 30 31 32
3 33 34 35
3 36 37 38
3 39 40 41
3 42 43 44
3 45 46 47
3 48 49 50
3 51 52 53
3 54 55 56
3 57 58 59
3 60 61 62
3 63 64 65
3 66 67 68
3 69 70 71
3 72 73 74
3 75 76 77
3 78 79 80
3 81 82 83
3 84 85 86
3 87 88 89
3 90 91 92
3 93 94 95
3 96 97 98
3 99 100 101
3 102 103 104
3 105 106 107
3 108 109 110
3 111 112 113
3 114 115 116
3 117 118 119
3 120 121 122
3 123 124 125
3 126 127 128
3 129 130 131
3 132 133 134
3 135 136 137
3 138 139 140
3 141 142 143
3 144 145 146
3 147 148 149
3 150 151 152
3 153 154 155
3 156 157 158
3 159 160 161
3 162 163 164
3 165 166 167
3 168 169 170
3 171 172 173
3 174 175 176
3 177 178 179
3 180 181 182
3 183 184 185
3 186 187 188
3 189 190 191
3 192 193 194
3 195 196 197
3 198 199 200
3 201 202 203

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

View File

@ -74,171 +74,171 @@ DATA;
#58 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016)); #58 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
#59 = VERTEX_POINT('NONE', #58); #59 = VERTEX_POINT('NONE', #58);
#60 = DIRECTION('NONE', (0, -1, 0)); #60 = DIRECTION('NONE', (0, -1, 0));
#61 = VECTOR('NONE', #60, 0.0254); #61 = VECTOR('NONE', #60, 1);
#62 = CARTESIAN_POINT('NONE', (0, 0, -0)); #62 = CARTESIAN_POINT('NONE', (0, 0, -0));
#63 = LINE('NONE', #62, #61); #63 = LINE('NONE', #62, #61);
#64 = DIRECTION('NONE', (0, 0, 1)); #64 = DIRECTION('NONE', (0, 0, 1));
#65 = VECTOR('NONE', #64, 0.1016); #65 = VECTOR('NONE', #64, 1);
#66 = CARTESIAN_POINT('NONE', (0, -0.0254, -0)); #66 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
#67 = LINE('NONE', #66, #65); #67 = LINE('NONE', #66, #65);
#68 = DIRECTION('NONE', (0, -1, 0)); #68 = DIRECTION('NONE', (0, -1, 0));
#69 = VECTOR('NONE', #68, 0.0254); #69 = VECTOR('NONE', #68, 1);
#70 = CARTESIAN_POINT('NONE', (0, 0, 0.1016)); #70 = CARTESIAN_POINT('NONE', (0, 0, 0.1016));
#71 = LINE('NONE', #70, #69); #71 = LINE('NONE', #70, #69);
#72 = DIRECTION('NONE', (0, 0, 1)); #72 = DIRECTION('NONE', (0, 0, 1));
#73 = VECTOR('NONE', #72, 0.1016); #73 = VECTOR('NONE', #72, 1);
#74 = CARTESIAN_POINT('NONE', (0, 0, -0)); #74 = CARTESIAN_POINT('NONE', (0, 0, -0));
#75 = LINE('NONE', #74, #73); #75 = LINE('NONE', #74, #73);
#76 = DIRECTION('NONE', (1, 0, 0)); #76 = DIRECTION('NONE', (1, 0, 0));
#77 = VECTOR('NONE', #76, 0.07861346939195568); #77 = VECTOR('NONE', #76, 1);
#78 = CARTESIAN_POINT('NONE', (0, -0.0254, -0)); #78 = CARTESIAN_POINT('NONE', (0, -0.0254, -0));
#79 = LINE('NONE', #78, #77); #79 = LINE('NONE', #78, #77);
#80 = DIRECTION('NONE', (0, 0, 1)); #80 = DIRECTION('NONE', (0, 0, 1));
#81 = VECTOR('NONE', #80, 0.1016); #81 = VECTOR('NONE', #80, 1);
#82 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0)); #82 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
#83 = LINE('NONE', #82, #81); #83 = LINE('NONE', #82, #81);
#84 = DIRECTION('NONE', (1, 0, 0)); #84 = DIRECTION('NONE', (1, 0, 0));
#85 = VECTOR('NONE', #84, 0.07861346939195568); #85 = VECTOR('NONE', #84, 1);
#86 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016)); #86 = CARTESIAN_POINT('NONE', (0, -0.0254, 0.1016));
#87 = LINE('NONE', #86, #85); #87 = LINE('NONE', #86, #85);
#88 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0)); #88 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
#89 = VECTOR('NONE', #88, 0.08856709721755177); #89 = VECTOR('NONE', #88, 1);
#90 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0)); #90 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, -0));
#91 = LINE('NONE', #90, #89); #91 = LINE('NONE', #90, #89);
#92 = DIRECTION('NONE', (0, 0, 1)); #92 = DIRECTION('NONE', (0, 0, 1));
#93 = VECTOR('NONE', #92, 0.1016); #93 = VECTOR('NONE', #92, 1);
#94 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0)); #94 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
#95 = LINE('NONE', #94, #93); #95 = LINE('NONE', #94, #93);
#96 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0)); #96 = DIRECTION('NONE', (0.8191520442889919, -0.5735764363510459, 0));
#97 = VECTOR('NONE', #96, 0.08856709721755177); #97 = VECTOR('NONE', #96, 1);
#98 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016)); #98 = CARTESIAN_POINT('NONE', (0.07861346939195568, -0.0254, 0.1016));
#99 = LINE('NONE', #98, #97); #99 = LINE('NONE', #98, #97);
#100 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0)); #100 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
#101 = VECTOR('NONE', #100, 0.09013661186554489); #101 = VECTOR('NONE', #100, 1);
#102 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0)); #102 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, -0));
#103 = LINE('NONE', #102, #101); #103 = LINE('NONE', #102, #101);
#104 = DIRECTION('NONE', (0, 0, 1)); #104 = DIRECTION('NONE', (0, 0, 1));
#105 = VECTOR('NONE', #104, 0.1016); #105 = VECTOR('NONE', #104, 1);
#106 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0)); #106 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
#107 = LINE('NONE', #106, #105); #107 = LINE('NONE', #106, #105);
#108 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0)); #108 = DIRECTION('NONE', (1, -0.0000000000000003079278779307945, 0));
#109 = VECTOR('NONE', #108, 0.09013661186554489); #109 = VECTOR('NONE', #108, 1);
#110 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016)); #110 = CARTESIAN_POINT('NONE', (0.1511633881344551, -0.07619999999999998, 0.1016));
#111 = LINE('NONE', #110, #109); #111 = LINE('NONE', #110, #109);
#112 = DIRECTION('NONE', (0, 1, 0)); #112 = DIRECTION('NONE', (0, 1, 0));
#113 = VECTOR('NONE', #112, 0.012700000000000003); #113 = VECTOR('NONE', #112, 1);
#114 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0)); #114 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, -0));
#115 = LINE('NONE', #114, #113); #115 = LINE('NONE', #114, #113);
#116 = DIRECTION('NONE', (0, 0, 1)); #116 = DIRECTION('NONE', (0, 0, 1));
#117 = VECTOR('NONE', #116, 0.1016); #117 = VECTOR('NONE', #116, 1);
#118 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0)); #118 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
#119 = LINE('NONE', #118, #117); #119 = LINE('NONE', #118, #117);
#120 = DIRECTION('NONE', (0, 1, 0)); #120 = DIRECTION('NONE', (0, 1, 0));
#121 = VECTOR('NONE', #120, 0.012700000000000003); #121 = VECTOR('NONE', #120, 1);
#122 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016)); #122 = CARTESIAN_POINT('NONE', (0.2413, -0.0762, 0.1016));
#123 = LINE('NONE', #122, #121); #123 = LINE('NONE', #122, #121);
#124 = DIRECTION('NONE', (-1, 0, 0)); #124 = DIRECTION('NONE', (-1, 0, 0));
#125 = VECTOR('NONE', #124, 0.08613231724678178); #125 = VECTOR('NONE', #124, 1);
#126 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0)); #126 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, -0));
#127 = LINE('NONE', #126, #125); #127 = LINE('NONE', #126, #125);
#128 = DIRECTION('NONE', (0, 0, 1)); #128 = DIRECTION('NONE', (0, 0, 1));
#129 = VECTOR('NONE', #128, 0.1016); #129 = VECTOR('NONE', #128, 1);
#130 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0)); #130 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
#131 = LINE('NONE', #130, #129); #131 = LINE('NONE', #130, #129);
#132 = DIRECTION('NONE', (-1, 0, 0)); #132 = DIRECTION('NONE', (-1, 0, 0));
#133 = VECTOR('NONE', #132, 0.08613231724678178); #133 = VECTOR('NONE', #132, 1);
#134 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016)); #134 = CARTESIAN_POINT('NONE', (0.2413, -0.0635, 0.1016));
#135 = LINE('NONE', #134, #133); #135 = LINE('NONE', #134, #133);
#136 = DIRECTION('NONE', (-0.8191520442889918, 0.573576436351046, 0)); #136 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
#137 = VECTOR('NONE', #136, 0.11070887152193974); #137 = VECTOR('NONE', #136, 1);
#138 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0)); #138 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, -0));
#139 = LINE('NONE', #138, #137); #139 = LINE('NONE', #138, #137);
#140 = DIRECTION('NONE', (0, 0, 1)); #140 = DIRECTION('NONE', (0, 0, 1));
#141 = VECTOR('NONE', #140, 0.1016); #141 = VECTOR('NONE', #140, 1);
#142 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0)); #142 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
#143 = LINE('NONE', #142, #141); #143 = LINE('NONE', #142, #141);
#144 = DIRECTION('NONE', (-0.8191520442889918, 0.573576436351046, 0)); #144 = DIRECTION('NONE', (-0.8191520442889919, 0.573576436351046, 0));
#145 = VECTOR('NONE', #144, 0.11070887152193974); #145 = VECTOR('NONE', #144, 1);
#146 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016)); #146 = CARTESIAN_POINT('NONE', (0.1551676827532182, -0.0635, 0.1016));
#147 = LINE('NONE', #146, #145); #147 = LINE('NONE', #146, #145);
#148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0)); #148 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
#149 = VECTOR('NONE', #148, 0.09015228031811025); #149 = VECTOR('NONE', #148, 1);
#150 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0)); #150 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, -0));
#151 = LINE('NONE', #150, #149); #151 = LINE('NONE', #150, #149);
#152 = DIRECTION('NONE', (0, 0, 1)); #152 = DIRECTION('NONE', (0, 0, 1));
#153 = VECTOR('NONE', #152, 0.1016); #153 = VECTOR('NONE', #152, 1);
#154 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0)); #154 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
#155 = LINE('NONE', #154, #153); #155 = LINE('NONE', #154, #153);
#156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0)); #156 = DIRECTION('NONE', (0.90630778703665, 0.4226182617406993, 0));
#157 = VECTOR('NONE', #156, 0.09015228031811025); #157 = VECTOR('NONE', #156, 1);
#158 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016)); #158 = CARTESIAN_POINT('NONE', (0.06448028432509392, 0, 0.1016));
#159 = LINE('NONE', #158, #157); #159 = LINE('NONE', #158, #157);
#160 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0)); #160 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
#161 = VECTOR('NONE', #160, 0.09511400200349182); #161 = VECTOR('NONE', #160, 1);
#162 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0)); #162 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, -0));
#163 = LINE('NONE', #162, #161); #163 = LINE('NONE', #162, #161);
#164 = DIRECTION('NONE', (0, 0, 1)); #164 = DIRECTION('NONE', (0, 0, 1));
#165 = VECTOR('NONE', #164, 0.1016); #165 = VECTOR('NONE', #164, 1);
#166 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0)); #166 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
#167 = LINE('NONE', #166, #165); #167 = LINE('NONE', #166, #165);
#168 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0)); #168 = DIRECTION('NONE', (1, -0.00000000000000007295344279228718, 0));
#169 = VECTOR('NONE', #168, 0.09511400200349182); #169 = VECTOR('NONE', #168, 1);
#170 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016)); #170 = CARTESIAN_POINT('NONE', (0.14618599799650817, 0.03810000000000001, 0.1016));
#171 = LINE('NONE', #170, #169); #171 = LINE('NONE', #170, #169);
#172 = DIRECTION('NONE', (0, 1, 0)); #172 = DIRECTION('NONE', (0, 1, 0));
#173 = VECTOR('NONE', #172, 0.012699999999999996); #173 = VECTOR('NONE', #172, 1);
#174 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0)); #174 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, -0));
#175 = LINE('NONE', #174, #173); #175 = LINE('NONE', #174, #173);
#176 = DIRECTION('NONE', (0, 0, 1)); #176 = DIRECTION('NONE', (0, 0, 1));
#177 = VECTOR('NONE', #176, 0.1016); #177 = VECTOR('NONE', #176, 1);
#178 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0)); #178 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
#179 = LINE('NONE', #178, #177); #179 = LINE('NONE', #178, #177);
#180 = DIRECTION('NONE', (0, 1, 0)); #180 = DIRECTION('NONE', (0, 1, 0));
#181 = VECTOR('NONE', #180, 0.012699999999999996); #181 = VECTOR('NONE', #180, 1);
#182 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016)); #182 = CARTESIAN_POINT('NONE', (0.2413, 0.0381, 0.1016));
#183 = LINE('NONE', #182, #181); #183 = LINE('NONE', #182, #181);
#184 = DIRECTION('NONE', (-1, 0, 0)); #184 = DIRECTION('NONE', (-1, 0, 0));
#185 = VECTOR('NONE', #184, 0.0979295242190572); #185 = VECTOR('NONE', #184, 1);
#186 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0)); #186 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, -0));
#187 = LINE('NONE', #186, #185); #187 = LINE('NONE', #186, #185);
#188 = DIRECTION('NONE', (0, 0, 1)); #188 = DIRECTION('NONE', (0, 0, 1));
#189 = VECTOR('NONE', #188, 0.1016); #189 = VECTOR('NONE', #188, 1);
#190 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0)); #190 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
#191 = LINE('NONE', #190, #189); #191 = LINE('NONE', #190, #189);
#192 = DIRECTION('NONE', (-1, 0, 0)); #192 = DIRECTION('NONE', (-1, 0, 0));
#193 = VECTOR('NONE', #192, 0.0979295242190572); #193 = VECTOR('NONE', #192, 1);
#194 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016)); #194 = CARTESIAN_POINT('NONE', (0.2413, 0.0508, 0.1016));
#195 = LINE('NONE', #194, #193); #195 = LINE('NONE', #194, #193);
#196 = DIRECTION('NONE', (-0.9063077870366499, -0.42261826174069944, 0)); #196 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
#197 = VECTOR('NONE', #196, 0.06010152021207346); #197 = VECTOR('NONE', #196, 1);
#198 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0)); #198 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, -0));
#199 = LINE('NONE', #198, #197); #199 = LINE('NONE', #198, #197);
#200 = DIRECTION('NONE', (0, 0, 1)); #200 = DIRECTION('NONE', (0, 0, 1));
#201 = VECTOR('NONE', #200, 0.1016); #201 = VECTOR('NONE', #200, 1);
#202 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0)); #202 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
#203 = LINE('NONE', #202, #201); #203 = LINE('NONE', #202, #201);
#204 = DIRECTION('NONE', (-0.9063077870366499, -0.42261826174069944, 0)); #204 = DIRECTION('NONE', (-0.90630778703665, -0.42261826174069944, 0));
#205 = VECTOR('NONE', #204, 0.06010152021207346); #205 = VECTOR('NONE', #204, 1);
#206 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016)); #206 = CARTESIAN_POINT('NONE', (0.14337047578094278, 0.0508, 0.1016));
#207 = LINE('NONE', #206, #205); #207 = LINE('NONE', #206, #205);
#208 = DIRECTION('NONE', (-1, 0, 0)); #208 = DIRECTION('NONE', (-1, 0, 0));
#209 = VECTOR('NONE', #208, 0.08889999999999999); #209 = VECTOR('NONE', #208, 1);
#210 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0)); #210 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, -0));
#211 = LINE('NONE', #210, #209); #211 = LINE('NONE', #210, #209);
#212 = DIRECTION('NONE', (0, 0, 1)); #212 = DIRECTION('NONE', (0, 0, 1));
#213 = VECTOR('NONE', #212, 0.1016); #213 = VECTOR('NONE', #212, 1);
#214 = CARTESIAN_POINT('NONE', (0, 0.0254, -0)); #214 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
#215 = LINE('NONE', #214, #213); #215 = LINE('NONE', #214, #213);
#216 = DIRECTION('NONE', (-1, 0, 0)); #216 = DIRECTION('NONE', (-1, 0, 0));
#217 = VECTOR('NONE', #216, 0.08889999999999999); #217 = VECTOR('NONE', #216, 1);
#218 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016)); #218 = CARTESIAN_POINT('NONE', (0.08889999999999999, 0.0254, 0.1016));
#219 = LINE('NONE', #218, #217); #219 = LINE('NONE', #218, #217);
#220 = DIRECTION('NONE', (0, -1, 0)); #220 = DIRECTION('NONE', (0, -1, 0));
#221 = VECTOR('NONE', #220, 0.0254); #221 = VECTOR('NONE', #220, 1);
#222 = CARTESIAN_POINT('NONE', (0, 0.0254, -0)); #222 = CARTESIAN_POINT('NONE', (0, 0.0254, -0));
#223 = LINE('NONE', #222, #221); #223 = LINE('NONE', #222, #221);
#224 = DIRECTION('NONE', (0, -1, 0)); #224 = DIRECTION('NONE', (0, -1, 0));
#225 = VECTOR('NONE', #224, 0.0254); #225 = VECTOR('NONE', #224, 1);
#226 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016)); #226 = CARTESIAN_POINT('NONE', (0, 0.0254, 0.1016));
#227 = LINE('NONE', #226, #225); #227 = LINE('NONE', #226, #225);
#228 = EDGE_CURVE('NONE', #5, #7, #63, .T.); #228 = EDGE_CURVE('NONE', #5, #7, #63, .T.);

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@ -14,6 +14,7 @@ try {
} catch (err) { } catch (err) {
// probably running in CI // probably running in CI
secrets.token = process.env.token || '' secrets.token = process.env.token || ''
secrets.snapshottoken = process.env.snapshottoken || ''
// add more env vars here to make them available in CI // add more env vars here to make them available in CI
} }

View File

@ -5,6 +5,8 @@ import { v4 as uuidv4 } from 'uuid'
import { getUtils } from './test-utils' import { getUtils } from './test-utils'
import { Models } from '@kittycad/lib' import { Models } from '@kittycad/lib'
import fsp from 'fs/promises' import fsp from 'fs/promises'
import { spawn } from 'child_process'
import { APP_NAME } from 'lib/constants'
test.beforeEach(async ({ context, page }) => { test.beforeEach(async ({ context, page }) => {
await context.addInitScript(async (token) => { await context.addInitScript(async (token) => {
@ -137,6 +139,7 @@ test('change camera, show planes', async ({ page, context }) => {
test('exports of each format should work', async ({ page, context }) => { test('exports of each format should work', async ({ page, context }) => {
// FYI this test doesn't work with only engine running locally // FYI this test doesn't work with only engine running locally
// And you will need to have the KittyCAD CLI installed
const u = getUtils(page) const u = getUtils(page)
await context.addInitScript(async () => { await context.addInitScript(async () => {
;(window as any).playwrightSkipFilePicker = true ;(window as any).playwrightSkipFilePicker = true
@ -194,9 +197,16 @@ const part001 = startSketchOn('-XZ')
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await u.clearAndCloseDebugPanel() await u.clearAndCloseDebugPanel()
await page.getByRole('button', { name: 'KittyCAD Modeling App' }).click() await page.getByRole('button', { name: APP_NAME }).click()
const doExport = async (output: Models['OutputFormat_type']) => { interface Paths {
modelPath: string
imagePath: string
outputType: string
}
const doExport = async (
output: Models['OutputFormat_type']
): Promise<Paths> => {
await page.getByRole('button', { name: 'Export Model' }).click() await page.getByRole('button', { name: 'Export Model' }).click()
const exportSelect = page.getByTestId('export-type') const exportSelect = page.getByTestId('export-type')
@ -210,10 +220,10 @@ const part001 = startSketchOn('-XZ')
const downloadPromise = page.waitForEvent('download') const downloadPromise = page.waitForEvent('download')
await page.getByRole('button', { name: 'Export', exact: true }).click() await page.getByRole('button', { name: 'Export', exact: true }).click()
const download = await downloadPromise const download = await downloadPromise
const downloadLocationer = (extra = '') => const downloadLocationer = (extra = '', isImage = false) =>
`./e2e/playwright/export-snapshots/${output.type}-${ `./e2e/playwright/export-snapshots/${output.type}-${
'storage' in output ? output.storage : '' 'storage' in output ? output.storage : ''
}${extra}.${output.type}` }${extra}.${isImage ? 'png' : output.type}`
const downloadLocation = downloadLocationer() const downloadLocation = downloadLocationer()
const downloadLocation2 = downloadLocationer('-2') const downloadLocation2 = downloadLocationer('-2')
@ -249,6 +259,11 @@ const part001 = startSketchOn('-XZ')
) )
await fsp.writeFile(downloadLocation, newFileContents) await fsp.writeFile(downloadLocation, newFileContents)
} }
return {
modelPath: downloadLocation,
imagePath: downloadLocationer('', true),
outputType: output.type,
}
} }
const axisDirectionPair: Models['AxisDirectionPair_type'] = { const axisDirectionPair: Models['AxisDirectionPair_type'] = {
axis: 'z', axis: 'z',
@ -258,67 +273,116 @@ const part001 = startSketchOn('-XZ')
forward: axisDirectionPair, forward: axisDirectionPair,
up: axisDirectionPair, up: axisDirectionPair,
} }
const exportLocations: Paths[] = []
// NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input // NOTE it was easiest to leverage existing types and have doExport take Models['OutputFormat_type'] as in input
// just note that only `type` and `storage` are used for selecting the drop downs is the app // just note that only `type` and `storage` are used for selecting the drop downs is the app
// the rest are only there to make typescript happy // the rest are only there to make typescript happy
await doExport({ exportLocations.push(
type: 'step', await doExport({
coords: sysType, type: 'step',
}) coords: sysType,
await doExport({ })
type: 'gltf', )
storage: 'embedded', exportLocations.push(
presentation: 'pretty', await doExport({
}) type: 'ply',
await doExport({ coords: sysType,
type: 'gltf', selection: { type: 'default_scene' },
storage: 'binary', storage: 'ascii',
presentation: 'pretty', units: 'in',
}) })
)
exportLocations.push(
await doExport({
type: 'ply',
storage: 'binary_little_endian',
coords: sysType,
selection: { type: 'default_scene' },
units: 'in',
})
)
exportLocations.push(
await doExport({
type: 'ply',
storage: 'binary_big_endian',
coords: sysType,
selection: { type: 'default_scene' },
units: 'in',
})
)
exportLocations.push(
await doExport({
type: 'stl',
storage: 'ascii',
coords: sysType,
units: 'in',
selection: { type: 'default_scene' },
})
)
exportLocations.push(
await doExport({
type: 'stl',
storage: 'binary',
coords: sysType,
units: 'in',
selection: { type: 'default_scene' },
})
)
exportLocations.push(
await doExport({
// obj seems to be a little flaky, times out tests sometimes
type: 'obj',
coords: sysType,
units: 'in',
})
)
exportLocations.push(
await doExport({
type: 'gltf',
storage: 'embedded',
presentation: 'pretty',
})
)
exportLocations.push(
await doExport({
type: 'gltf',
storage: 'binary',
presentation: 'pretty',
})
)
// TODO: gltfs don't seem to work with snap shots. push onto exportLocations once it's figured out
await doExport({ await doExport({
type: 'gltf', type: 'gltf',
storage: 'standard', storage: 'standard',
presentation: 'pretty', presentation: 'pretty',
}) })
await doExport({
type: 'ply', // close page to disconnect websocket since we can only have one open atm
coords: sysType, await page.close()
selection: { type: 'default_scene' },
storage: 'ascii', // snapshot exports, good compromise to capture that exports are healthy without getting bogged down in "did the formatting change" changes
units: 'in', // context: https://github.com/KittyCAD/modeling-app/issues/1222
}) for (const { modelPath, imagePath, outputType } of exportLocations) {
await doExport({ const cliCommand = `export KITTYCAD_TOKEN=${secrets.snapshottoken} && kittycad file snapshot --output-format=png --src-format=${outputType} ${modelPath} ${imagePath}`
type: 'ply', const child = spawn(cliCommand, { shell: true })
storage: 'binary_little_endian', await new Promise((resolve, reject) => {
coords: sysType, child.on('error', (code: any, msg: any) => {
selection: { type: 'default_scene' }, console.log('error', code, msg)
units: 'in', reject()
}) })
await doExport({ child.on('exit', (code, msg) => {
type: 'ply', console.log('exit', code, msg)
storage: 'binary_big_endian', if (code !== 0) {
coords: sysType, reject(`exit code ${code} for model ${modelPath}`)
selection: { type: 'default_scene' }, } else {
units: 'in', resolve(true)
}) }
await doExport({ })
type: 'stl', child.stderr.on('data', (data) => console.log(`stderr: ${data}`))
storage: 'ascii', child.stdout.on('data', (data) => console.log(`stdout: ${data}`))
coords: sysType, })
units: 'in', }
selection: { type: 'default_scene' },
})
await doExport({
type: 'stl',
storage: 'binary',
coords: sysType,
units: 'in',
selection: { type: 'default_scene' },
})
await doExport({
// obj seems to be a little flaky, times out tests sometimes
type: 'obj',
coords: sysType,
units: 'in',
})
}) })

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -7,12 +7,12 @@
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
name="description" name="description"
content="An open-source CAD modeling tool from the future by KittyCAD." content="An open-source CAD modeling tool from the future by Zoo."
/> />
<link rel="apple-touch-icon" href="/logo192.png" /> <link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<script defer data-domain="app.kittycad.io" src="https://plausible.corp.kittycad.io/js/script.js"></script> <script defer data-domain="app.kittycad.io" src="https://plausible.corp.kittycad.io/js/script.js"></script>
<title>KittyCAD Modeling App</title> <title>Modeling App</title>
</head> </head>
<body class="body-bg"> <body class="body-bg">
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>

View File

@ -1,4 +1,4 @@
## KittyCAD Modeling App Roadmap ## Zoo Modeling App Roadmap
This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review. This document ties into our [GH Discussions Feature List](https://github.com/KittyCAD/modeling-app/discussions). Please upvote any features that you want to see next, or add ones that are not listed and we will review.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

13
public/zma-logomark.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 13 KiB

7
public/zoo-logo.svg Normal file
View File

@ -0,0 +1,7 @@
<svg width="438" height="145" viewBox="0 0 438 145" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M88.2136 25.3021V3.12744H0.595269V34.3994H79.827L0.609484 120.312H0.595269V120.326L0.581055 120.34L0.595269 120.355V141.364H20.8936L41.3341 119.189V141.364H128.952V110.092H49.7349L128.952 24.1649V3.12744L108.64 3.15587L88.2136 25.3021Z" fill="white"/>
<path d="M167.36 72.4372C167.36 49.7366 185.824 31.2719 208.525 31.2719C216.514 31.2719 223.976 33.5605 230.288 37.5121L251.78 14.3709C239.698 5.34466 224.73 0 208.525 0C168.582 0 136.088 32.4944 136.088 72.4372C136.088 90.5465 142.769 107.135 153.828 119.857L175.32 96.7156C170.316 89.9069 167.36 81.5061 167.36 72.4372Z" fill="white"/>
<path d="M241.745 48.1442C246.734 54.9671 249.691 63.3679 249.691 72.4368C249.691 95.1232 231.226 113.588 208.525 113.588C200.537 113.588 193.088 111.299 186.777 107.348L165.271 130.503C177.353 139.515 192.321 144.86 208.525 144.86C248.468 144.86 280.963 112.365 280.963 72.4368C280.963 54.3133 274.282 37.7249 263.223 25.0029L241.745 48.1442Z" fill="white"/>
<path d="M419.312 25.0029L397.834 48.1442C402.823 54.9671 405.779 63.3679 405.779 72.4368C405.779 95.1232 387.315 113.588 364.614 113.588C356.626 113.588 349.177 111.299 342.866 107.348L321.359 130.503C333.442 139.515 348.41 144.86 364.614 144.86C404.557 144.86 437.051 112.365 437.051 72.4368C437.051 54.3133 430.371 37.7249 419.312 25.0029Z" fill="white"/>
<path d="M323.449 72.4372C323.449 49.7366 341.913 31.2719 364.614 31.2719C372.603 31.2719 380.065 33.5605 386.376 37.5121L407.869 14.3709C395.786 5.34466 380.819 0 364.614 0C324.671 0 292.177 32.4944 292.177 72.4372C292.177 90.5465 298.858 107.135 309.916 119.857L331.409 96.7156C326.405 89.9069 323.449 81.5061 323.449 72.4372Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

156
src-tauri/Cargo.lock generated
View File

@ -547,12 +547,12 @@ dependencies = [
[[package]] [[package]]
name = "ctor" name = "ctor"
version = "0.1.26" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" checksum = "30d2b3721e861707777e3195b0158f950ae6dc4a27e4d02ff9f67e3eb3de199e"
dependencies = [ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -1530,9 +1530,9 @@ dependencies = [
[[package]] [[package]]
name = "infer" name = "infer"
version = "0.12.0" version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a898e4b7951673fce96614ce5751d13c40fc5674bc2d759288e46c3ab62598b3" checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc"
dependencies = [ dependencies = [
"cfb", "cfb",
] ]
@ -1652,9 +1652,9 @@ dependencies = [
[[package]] [[package]]
name = "json-patch" name = "json-patch"
version = "1.0.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f54898088ccb91df1b492cc80029a6fdf1c48ca0db7c6822a8babad69c94658" checksum = "55ff1e1486799e3f64129f8ccad108b38290df9cd7015cd31bed17239f0789d6"
dependencies = [ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
@ -2194,11 +2194,11 @@ dependencies = [
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.55" version = "0.10.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" checksum = "6b8419dc8cc6d866deb801274bba2e6f8f6108c1bb7fcc10ee5ab864931dbb45"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 2.4.0",
"cfg-if", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
@ -2226,9 +2226,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.90" version = "0.9.97"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" checksum = "c3eaad34cdd97d81de97964fc7f29e2d104f483840d906ef56daa1912338460b"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -2400,9 +2400,17 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [ dependencies = [
"phf_macros 0.10.0",
"phf_shared 0.10.0", "phf_shared 0.10.0",
"proc-macro-hack", ]
[[package]]
name = "phf"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
dependencies = [
"phf_macros 0.11.2",
"phf_shared 0.11.2",
] ]
[[package]] [[package]]
@ -2445,6 +2453,16 @@ dependencies = [
"rand 0.8.5", "rand 0.8.5",
] ]
[[package]]
name = "phf_generator"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0"
dependencies = [
"phf_shared 0.11.2",
"rand 0.8.5",
]
[[package]] [[package]]
name = "phf_macros" name = "phf_macros"
version = "0.8.0" version = "0.8.0"
@ -2461,16 +2479,15 @@ dependencies = [
[[package]] [[package]]
name = "phf_macros" name = "phf_macros"
version = "0.10.0" version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b"
dependencies = [ dependencies = [
"phf_generator 0.10.0", "phf_generator 0.11.2",
"phf_shared 0.10.0", "phf_shared 0.11.2",
"proc-macro-hack",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 1.0.109", "syn 2.0.33",
] ]
[[package]] [[package]]
@ -2491,6 +2508,15 @@ dependencies = [
"siphasher", "siphasher",
] ]
[[package]]
name = "phf_shared"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "phonenumber" name = "phonenumber"
version = "0.3.3+8.13.9" version = "0.3.3+8.13.9"
@ -3734,9 +3760,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri" name = "tauri"
version = "1.5.2" version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9bfe673cf125ef364d6f56b15e8ce7537d9ca7e4dae1cf6fbbdeed2e024db3d9" checksum = "32d563b672acde8d0cc4c1b1f5b855976923f67e8d6fe1eba51df0211e197be2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -3831,9 +3857,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-macros" name = "tauri-macros"
version = "1.4.1" version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613740228de92d9196b795ac455091d3a5fbdac2654abb8bb07d010b62ab43af" checksum = "acea6445eececebd72ed7720cfcca46eee3b5bad8eb408be8f7ef2e3f7411500"
dependencies = [ dependencies = [
"heck 0.4.1", "heck 0.4.1",
"proc-macro2", "proc-macro2",
@ -3878,9 +3904,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-runtime-wry" name = "tauri-runtime-wry"
version = "0.14.1" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8141d72b6b65f2008911e9ef5b98a68d1e3413b7a1464e8f85eb3673bb19a895" checksum = "803a01101bc611ba03e13329951a1bde44287a54234189b9024b78619c1bc206"
dependencies = [ dependencies = [
"cocoa", "cocoa",
"gtk", "gtk",
@ -3898,9 +3924,9 @@ dependencies = [
[[package]] [[package]]
name = "tauri-utils" name = "tauri-utils"
version = "1.5.0" version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34d55e185904a84a419308d523c2c6891d5e2dbcee740c4997eb42e75a7b0f46" checksum = "a52165bb340e6f6a75f1f5eeeab1bb49f861c12abe3a176067d53642b5454986"
dependencies = [ dependencies = [
"brotli", "brotli",
"ctor", "ctor",
@ -3913,7 +3939,7 @@ dependencies = [
"kuchikiki", "kuchikiki",
"log", "log",
"memchr", "memchr",
"phf 0.10.1", "phf 0.11.2",
"proc-macro2", "proc-macro2",
"quote", "quote",
"semver", "semver",
@ -3923,7 +3949,7 @@ dependencies = [
"thiserror", "thiserror",
"url", "url",
"walkdir", "walkdir",
"windows 0.39.0", "windows-version",
] ]
[[package]] [[package]]
@ -4747,12 +4773,36 @@ dependencies = [
"windows_x86_64_msvc 0.48.0", "windows_x86_64_msvc 0.48.0",
] ]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]] [[package]]
name = "windows-tokens" name = "windows-tokens"
version = "0.39.0" version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597"
[[package]]
name = "windows-version"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75aa004c988e080ad34aff5739c39d0312f4684699d6d71fc8a198d057b8b9b4"
dependencies = [
"windows-targets 0.52.0",
]
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.42.2" version = "0.42.2"
@ -4765,6 +4815,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.37.0" version = "0.37.0"
@ -4789,6 +4845,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.37.0" version = "0.37.0"
@ -4813,6 +4875,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.37.0" version = "0.37.0"
@ -4837,6 +4905,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.37.0" version = "0.37.0"
@ -4861,6 +4935,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.42.2" version = "0.42.2"
@ -4873,6 +4953,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.37.0" version = "0.37.0"
@ -4897,6 +4983,12 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.15" version = "0.5.15"
@ -4928,9 +5020,9 @@ dependencies = [
[[package]] [[package]]
name = "wry" name = "wry"
version = "0.24.4" version = "0.24.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ef04bdad49eba2e01f06e53688c8413bd6a87b0bc14b72284465cf96e3578e" checksum = "64a70547e8f9d85da0f5af609143f7bde3ac7457a6e1073104d9b73d6c5ac744"
dependencies = [ dependencies = [
"base64 0.13.1", "base64 0.13.1",
"block", "block",

View File

@ -20,7 +20,7 @@ kittycad = "0.2.42"
oauth2 = "4.4.2" oauth2 = "4.4.2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
tauri = { version = "1.5.2", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] } tauri = { version = "1.5.3", features = [ "os-all", "dialog-all", "fs-all", "http-request", "path-all", "shell-open", "shell-open-api", "devtools"] }
tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-fs-extra = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
tokio = { version = "1.34.0", features = ["time"] } tokio = { version = "1.34.0", features = ["time"] }
toml = "0.8.2" toml = "0.8.2"

View File

@ -76,16 +76,13 @@ async fn login(app: tauri::AppHandle, host: &str) -> Result<String, InvokeError>
// Here we're using an env var to enable the /tmp file (windows not supported for now) // Here we're using an env var to enable the /tmp file (windows not supported for now)
// and bypass the shell::open call as it fails on GitHub Actions. // and bypass the shell::open call as it fails on GitHub Actions.
let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok(); let e2e_tauri_enabled = env::var("E2E_TAURI_ENABLED").is_ok();
if (e2e_tauri_enabled) { if e2e_tauri_enabled {
println!( println!(
"E2E_TAURI_ENABLED is set, won't open {} externally", "E2E_TAURI_ENABLED is set, won't open {} externally",
auth_uri.secret() auth_uri.secret()
); );
fs::write( fs::write("/tmp/kittycad_user_code", details.user_code().secret())
"/tmp/kittycad_user_code", .expect("Unable to write /tmp/kittycad_user_code file");
details.user_code().secret().to_string(),
)
.expect("Unable to write /tmp/kittycad_user_code file");
} else { } else {
tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None) tauri::api::shell::open(&app.shell_scope(), auth_uri.secret(), None)
.map_err(|e| InvokeError::from_anyhow(e.into()))?; .map_err(|e| InvokeError::from_anyhow(e.into()))?;

View File

@ -8,7 +8,7 @@ export const Auth = ({ children }: React.PropsWithChildren) => {
return isLoggingIn ? ( return isLoggingIn ? (
<Loading> <Loading>
<span data-testid="initial-load">Loading KittyCAD Modeling App...</span> <span data-testid="initial-load">Loading Modeling App...</span>
</Loading> </Loading>
) : ( ) : (
<>{children}</> <>{children}</>

View File

@ -33,7 +33,7 @@ export const AppHeader = ({
className={ className={
'w-full grid ' + 'w-full grid ' +
styles.header + styles.header +
' overlaid-panes sticky top-0 z-20 py-1 px-5 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' + ' overlaid-panes sticky top-0 z-20 py-1 px-2 bg-chalkboard-10/70 dark:bg-chalkboard-100/50 border-b dark:border-b-2 border-chalkboard-30 dark:border-chalkboard-90 items-center ' +
className className
} }
> >

View File

@ -19,7 +19,7 @@ const DownloadAppBanner = () => {
<div className="max-w-3xl mx-auto"> <div className="max-w-3xl mx-auto">
<div className="flex gap-2 justify-between items-start"> <div className="flex gap-2 justify-between items-start">
<h2 className="text-xl font-bold mb-4"> <h2 className="text-xl font-bold mb-4">
KittyCAD Modeling App is better as a desktop app! Zoo Modeling App is better as a desktop app!
</h2> </h2>
<ActionButton <ActionButton
Element="button" Element="button"
@ -42,7 +42,7 @@ const DownloadAppBanner = () => {
</code> </code>
, and isn't backed up anywhere! Visit{' '} , and isn't backed up anywhere! Visit{' '}
<a <a
href="https://kittycad.io/modeling-app/download" href="https://zoo.dev/modeling-app/download"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
className="!text-warn-80 dark:!text-warn-80 dark:hover:!text-warn-70 underline" className="!text-warn-80 dark:!text-warn-80 dark:hover:!text-warn-70 underline"

33
src/components/Logo.tsx Normal file
View File

@ -0,0 +1,33 @@
export const Logo = ({
className = 'w-auto h-5 text-chalkboard-120 dark:text-chalkboard-10',
...props
}: React.SVGProps<SVGSVGElement>) => (
<svg
{...props}
className={className}
viewBox="0 0 438 145"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M88.2136 25.3021V3.12744H0.595269V34.3994H79.827L0.609484 120.312H0.595269V120.326L0.581055 120.34L0.595269 120.355V141.364H20.8936L41.3341 119.189V141.364H128.952V110.092H49.7349L128.952 24.1649V3.12744L108.64 3.15587L88.2136 25.3021Z"
fill="currentColor"
/>
<path
d="M167.36 72.4372C167.36 49.7366 185.824 31.2719 208.525 31.2719C216.514 31.2719 223.976 33.5605 230.288 37.5121L251.78 14.3709C239.698 5.34466 224.73 0 208.525 0C168.582 0 136.088 32.4944 136.088 72.4372C136.088 90.5465 142.769 107.135 153.828 119.857L175.32 96.7156C170.316 89.9069 167.36 81.5061 167.36 72.4372Z"
fill="currentColor"
/>
<path
d="M241.745 48.1442C246.734 54.9671 249.691 63.3679 249.691 72.4368C249.691 95.1232 231.226 113.588 208.525 113.588C200.537 113.588 193.088 111.299 186.777 107.348L165.271 130.503C177.353 139.515 192.321 144.86 208.525 144.86C248.468 144.86 280.963 112.365 280.963 72.4368C280.963 54.3133 274.282 37.7249 263.223 25.0029L241.745 48.1442Z"
fill="currentColor"
/>
<path
d="M419.312 25.0029L397.834 48.1442C402.823 54.9671 405.779 63.3679 405.779 72.4368C405.779 95.1232 387.315 113.588 364.614 113.588C356.626 113.588 349.177 111.299 342.866 107.348L321.359 130.503C333.442 139.515 348.41 144.86 364.614 144.86C404.557 144.86 437.051 112.365 437.051 72.4368C437.051 54.3133 430.371 37.7249 419.312 25.0029Z"
fill="currentColor"
/>
<path
d="M323.449 72.4372C323.449 49.7366 341.913 31.2719 364.614 31.2719C372.603 31.2719 380.065 33.5605 386.376 37.5121L407.869 14.3709C395.786 5.34466 380.819 0 364.614 0C324.671 0 292.177 32.4944 292.177 72.4372C292.177 90.5465 298.858 107.135 309.916 119.857L331.409 96.7156C326.405 89.9069 323.449 81.5061 323.449 72.4372Z"
fill="currentColor"
/>
</svg>
)

View File

@ -4,6 +4,7 @@ import ProjectSidebarMenu from './ProjectSidebarMenu'
import { ProjectWithEntryPointMetadata } from '../Router' import { ProjectWithEntryPointMetadata } from '../Router'
import { GlobalStateProvider } from './GlobalStateProvider' import { GlobalStateProvider } from './GlobalStateProvider'
import CommandBarProvider from './CommandBar/CommandBar' import CommandBarProvider from './CommandBar/CommandBar'
import { APP_NAME } from 'lib/constants'
const now = new Date() const now = new Date()
const projectWellFormed = { const projectWellFormed = {
@ -71,9 +72,7 @@ describe('ProjectSidebarMenu tests', () => {
fireEvent.click(screen.getByTestId('project-sidebar-toggle')) fireEvent.click(screen.getByTestId('project-sidebar-toggle'))
expect(screen.getByTestId('projectName')).toHaveTextContent( expect(screen.getByTestId('projectName')).toHaveTextContent(APP_NAME)
'KittyCAD Modeling App'
)
}) })
test('Renders as a link if set to do so', () => { test('Renders as a link if set to do so', () => {

View File

@ -8,6 +8,8 @@ import { ExportButton } from './ExportButton'
import { Fragment } from 'react' import { Fragment } from 'react'
import { FileTree } from './FileTree' import { FileTree } from './FileTree'
import { sep } from '@tauri-apps/api/path' import { sep } from '@tauri-apps/api/path'
import { Logo } from './Logo'
import { APP_NAME } from 'lib/constants'
const ProjectSidebarMenu = ({ const ProjectSidebarMenu = ({
project, project,
@ -21,37 +23,29 @@ const ProjectSidebarMenu = ({
return renderAsLink ? ( return renderAsLink ? (
<Link <Link
to={paths.HOME} to={paths.HOME}
className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90" className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center gap-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90"
data-testid="project-sidebar-link" data-testid="project-sidebar-link"
> >
<img <Logo />
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="w-auto h-9"
/>
<span <span
className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block" className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"
data-testid="project-sidebar-link-name" data-testid="project-sidebar-link-name"
> >
{project?.name ? project.name : 'KittyCAD Modeling App'} {project?.name ? project.name : APP_NAME}
</span> </span>
</Link> </Link>
) : ( ) : (
<Popover className="relative"> <Popover className="relative">
<Popover.Button <Popover.Button
className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 p-0.5 pr-2 flex items-center gap-4 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90" className="rounded-sm h-9 mr-auto max-h-min min-w-max border-0 py-1 px-2 flex items-center gap-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-energy-50 dark:hover:bg-chalkboard-90"
data-testid="project-sidebar-toggle" data-testid="project-sidebar-toggle"
> >
<img <Logo />
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="w-auto h-full"
/>
<div className="flex flex-col items-start py-0.5"> <div className="flex flex-col items-start py-0.5">
<span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block"> <span className="hidden text-sm text-chalkboard-110 dark:text-chalkboard-20 whitespace-nowrap lg:block">
{isTauri() && file?.name {isTauri() && file?.name
? file.name.slice(file.name.lastIndexOf(sep) + 1) ? file.name.slice(file.name.lastIndexOf(sep) + 1)
: 'KittyCAD Modeling App'} : APP_NAME}
</span> </span>
{isTauri() && project?.name && ( {isTauri() && project?.name && (
<span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block"> <span className="hidden text-xs text-chalkboard-70 dark:text-chalkboard-40 whitespace-nowrap lg:block">
@ -88,18 +82,13 @@ const ProjectSidebarMenu = ({
{({ close }) => ( {({ close }) => (
<> <>
<div className="flex items-center gap-4 px-4 py-3"> <div className="flex items-center gap-4 px-4 py-3">
<img <Logo />
src="/kitt-8bit-winking.svg"
alt="KittyCAD App"
className="w-auto h-9"
/>
<div> <div>
<p <p
className="m-0 text-chalkboard-100 dark:text-energy-10 text-mono" className="m-0 text-chalkboard-100 dark:text-energy-10 text-mono"
data-testid="projectName" data-testid="projectName"
> >
{project?.name ? project.name : 'KittyCAD Modeling App'} {project?.name ? project.name : APP_NAME}
</p> </p>
{project?.entrypointMetadata && ( {project?.entrypointMetadata && (
<p <p

View File

@ -981,10 +981,11 @@ export class EngineCommandManager {
!( !(
command.type === 'modeling_cmd_req' && command.type === 'modeling_cmd_req' &&
(command.cmd.type === 'highlight_set_entity' || (command.cmd.type === 'highlight_set_entity' ||
command.cmd.type === 'mouse_move') command.cmd.type === 'mouse_move' ||
command.cmd.type === 'camera_drag_move')
) )
) { ) {
// highlight_set_entity and mouse_move are sent over the unreliable channel and are too noisy // highlight_set_entity, mouse_move and camera_drag_move are sent over the unreliable channel and are too noisy
this.addCommandLog({ this.addCommandLog({
type: 'send-scene', type: 'send-scene',
data: command, data: command,

View File

@ -1537,7 +1537,8 @@ export function isLiteralArrayOrStatic(
if (!val) return false if (!val) return false
if (Array.isArray(val)) { if (Array.isArray(val)) {
const [a, b] = val const a = val[0]
const b = val[1]
return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b) return isLiteralArrayOrStatic(a) && isLiteralArrayOrStatic(b)
} }
return ( return (
@ -1550,7 +1551,8 @@ export function isNotLiteralArrayOrStatic(
val: Value | [Value, Value] | [Value, Value, Value] val: Value | [Value, Value] | [Value, Value, Value]
): boolean { ): boolean {
if (Array.isArray(val)) { if (Array.isArray(val)) {
const [a, b] = val const a = val[0]
const b = val[1]
return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b) return isNotLiteralArrayOrStatic(a) && isNotLiteralArrayOrStatic(b)
} }
return ( return (

1
src/lib/constants.ts Normal file
View File

@ -0,0 +1 @@
export const APP_NAME = 'Modeling App'

View File

@ -27,7 +27,7 @@ export default function Units() {
<div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none"> <div className="fixed inset-0 z-50 grid items-end justify-start px-4 pointer-events-none">
<div <div
className={ className={
'max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -14,7 +14,7 @@ export default function CmdK() {
<div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none"> <div className="fixed inset-0 z-50 grid items-end justify-center pointer-events-none">
<div <div
className={ className={
'max-w-full xl:max-w-4xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-full xl:max-w-4xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >
@ -34,8 +34,8 @@ export default function CmdK() {
</p> </p>
<p className="my-4"> <p className="my-4">
We are working on a command bar that will allow you to quickly see and We are working on a command bar that will allow you to quickly see and
search for any available commands. We are building KittyCAD Modeling search for any available commands. We are building Zoo Modeling App's
App's state management system on top of{' '} state management system on top of{' '}
<a <a
href="https://xstate.js.org/" href="https://xstate.js.org/"
rel="noreferrer noopener" rel="noreferrer noopener"

View File

@ -17,7 +17,7 @@ export default function CodeEditor() {
></div> ></div>
<div <div
className={ className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -1,3 +1,4 @@
import { APP_NAME } from 'lib/constants'
import { OnboardingButtons, onboardingPaths, useDismiss, useNextClick } from '.' import { OnboardingButtons, onboardingPaths, useDismiss, useNextClick } from '.'
import { useStore } from '../../useStore' import { useStore } from '../../useStore'
@ -12,7 +13,7 @@ export default function Export() {
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >
@ -22,9 +23,9 @@ export default function Export() {
Try opening the project menu and clicking "Export Model". Try opening the project menu and clicking "Export Model".
</p> </p>
<p className="my-4"> <p className="my-4">
KittyCAD Modeling App uses{' '} {APP_NAME} uses{' '}
<a <a
href="https://kittycad.io/gltf-format-extension" href="https://zoo.dev/gltf-format-extension"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
@ -32,7 +33,7 @@ export default function Export() {
</a>{' '} </a>{' '}
for the GLTF file format.{' '} for the GLTF file format.{' '}
<a <a
href="https://kittycad.io/docs/api/convert-cad-file" href="https://zoo.dev/docs/api/convert-cad-file"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >

View File

@ -3,6 +3,7 @@ import { useEffect } from 'react'
import { bracket } from 'lib/exampleKcl' import { bracket } from 'lib/exampleKcl'
import { kclManager } from 'lang/KclSinglton' import { kclManager } from 'lang/KclSinglton'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
import { APP_NAME } from 'lib/constants'
export default function FutureWork() { export default function FutureWork() {
const { send } = useModelingContext() const { send } = useModelingContext()
@ -22,7 +23,7 @@ export default function FutureWork() {
return ( return (
<div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50"> <div className="fixed grid justify-center items-center inset-0 bg-chalkboard-100/50 z-50">
<div className="max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded"> <div className="max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded">
<h1 className="text-2xl font-bold">Future Work</h1> <h1 className="text-2xl font-bold">Future Work</h1>
<p className="my-4"> <p className="my-4">
We have curves, cuts, and many more CAD features coming soon. We want We have curves, cuts, and many more CAD features coming soon. We want
@ -32,10 +33,10 @@ export default function FutureWork() {
</p> </p>
<p className="my-4"> <p className="my-4">
If you make anything with the app we'd love to see it! Thank you for If you make anything with the app we'd love to see it! Thank you for
taking time to try out KittyCAD Modeling App, and build the future of taking time to try out {APP_NAME}, and build the future of hardware
hardware design with us 💚. design with us.
</p> </p>
<p className="my-4"> The KittyCAD Team</p> <p className="my-4">💚 The Zoo Team</p>
<OnboardingButtons <OnboardingButtons
className="mt-6" className="mt-6"
dismiss={dismiss} dismiss={dismiss}

View File

@ -17,7 +17,7 @@ export default function InteractiveNumbers() {
></div> ></div>
<div <div
className={ className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -21,6 +21,7 @@ import { paths } from 'Router'
import { useEffect } from 'react' import { useEffect } from 'react'
import { kclManager } from 'lang/KclSinglton' import { kclManager } from 'lang/KclSinglton'
import { sep } from '@tauri-apps/api/path' import { sep } from '@tauri-apps/api/path'
import { APP_NAME } from 'lib/constants'
function OnboardingWithNewFile() { function OnboardingWithNewFile() {
const navigate = useNavigate() const navigate = useNavigate()
@ -129,8 +130,8 @@ export default function Introduction() {
<div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90"> <div className="max-w-3xl p-8 rounded bg-chalkboard-10 dark:bg-chalkboard-90">
<h1 className="flex flex-wrap items-center gap-4 text-2xl font-bold"> <h1 className="flex flex-wrap items-center gap-4 text-2xl font-bold">
<img <img
src={`/kcma-logomark${getLogoTheme()}.svg`} src={`/zma-logomark${getLogoTheme()}.svg`}
alt="KittyCAD Modeling App" alt={APP_NAME}
className="h-20 max-w-full" className="h-20 max-w-full"
/> />
<span className="px-3 py-1 text-base rounded-full bg-energy-10 text-energy-80"> <span className="px-3 py-1 text-base rounded-full bg-energy-10 text-energy-80">
@ -139,11 +140,11 @@ export default function Introduction() {
</h1> </h1>
<section className="my-12"> <section className="my-12">
<p className="my-4"> <p className="my-4">
Welcome to KittyCAD Modeling App! This is a hardware design tool Welcome to {APP_NAME}! This is a hardware design tool that lets you
that lets you edit visually, with code, or both. It's powered by the edit visually, with code, or both. It's powered by the first API
first API created for anyone to build hardware design tools. The 3D created for anyone to build hardware design tools. The 3D view is
view is not running on your computer, but is instead being streamed not running on your computer, but is instead being streamed to you
to you from a remote GPU as video. from a remote GPU as video.
</p> </p>
<p className="my-4"> <p className="my-4">
This is an alpha release, so you will encounter bugs and missing This is an alpha release, so you will encounter bugs and missing

View File

@ -29,7 +29,7 @@ export default function ParametricModeling() {
></div> ></div>
<div <div
className={ className={
'z-10 max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'z-10 max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -13,7 +13,7 @@ export default function ProjectMenu() {
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -22,7 +22,7 @@ export default function Sketching() {
<div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-end inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'max-w-full xl:max-w-2xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-full xl:max-w-2xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >
@ -35,7 +35,7 @@ export default function Sketching() {
</p> </p>
<p className="my-4"> <p className="my-4">
Watch the code pane as you click. Point-and-click interactions are Watch the code pane as you click. Point-and-click interactions are
always just modifying and generating code in KittyCAD Modeling App. always just modifying and generating code in Zoo Modeling App.
</p> </p>
<OnboardingButtons <OnboardingButtons
className="mt-6" className="mt-6"

View File

@ -12,7 +12,7 @@ export default function Streaming() {
<div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-start items-center inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'max-w-xl h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-xl border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg h-3/4 flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >
@ -20,18 +20,18 @@ export default function Streaming() {
<h2 className="text-2xl">Streaming Video</h2> <h2 className="text-2xl">Streaming Video</h2>
<p className="my-4"> <p className="my-4">
The 3D view is not running on your computer. Instead, our The 3D view is not running on your computer. Instead, our
infrastructure spins up the KittyCAD Geometry Engine on a remote infrastructure spins up the Zoo Geometry Engine on a remote GPU, Zoo
GPU, KittyCAD Modeling App sends it a series of commands via Modeling App sends it a series of commands via Websockets and
Websockets and WebRTC, and the Geometry Engine sends back a video WebRTC, and the Geometry Engine sends back a video stream of the 3D
stream of the 3D view. view.
</p> </p>
<p className="my-4"> <p className="my-4">
This means that you could run KittyCAD Modeling App on a Chromebook, This means that you could run Zoo Modeling App on a Chromebook, a
a tablet, or even a phone, as long as you have a good internet tablet, or even a phone, as long as you have a good internet
connection. connection.
</p> </p>
<p className="my-4"> <p className="my-4">
It also means that whatever tools you build on top of the KittyCAD It also means that whatever tools you build on top of the Zoo
Geometry Engine will be able to run on any device with a browser, Geometry Engine will be able to run on any device with a browser,
and you won't have to worry about the performance of the device. and you won't have to worry about the performance of the device.
</p> </p>

View File

@ -12,7 +12,7 @@ export default function UserMenu() {
<div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none"> <div className="fixed grid justify-center items-start inset-0 z-50 pointer-events-none">
<div <div
className={ className={
'max-w-xl flex flex-col justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' + 'max-w-xl flex flex-col border border-chalkboard-50 dark:border-chalkboard-80 shadow-lg justify-center bg-chalkboard-10 dark:bg-chalkboard-90 p-8 rounded' +
(buttonDownInStream ? '' : ' pointer-events-auto') (buttonDownInStream ? '' : ' pointer-events-auto')
} }
> >

View File

@ -5,6 +5,7 @@ import { VITE_KC_SITE_BASE_URL, VITE_KC_API_BASE_URL } from '../env'
import { Themes, getSystemTheme } from '../lib/theme' import { Themes, getSystemTheme } from '../lib/theme'
import { paths } from '../Router' import { paths } from '../Router'
import { useGlobalStateContext } from 'hooks/useGlobalStateContext' import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
import { APP_NAME } from 'lib/constants'
const SignIn = () => { const SignIn = () => {
const { const {
@ -45,20 +46,20 @@ const SignIn = () => {
</span> </span>
</div> </div>
<h1 className="font-bold text-2xl mt-12 mb-6"> <h1 className="font-bold text-2xl mt-12 mb-6">
Sign in to get started with the KittyCAD Modeling App Sign in to get started with the {APP_NAME}
</h1> </h1>
<p className="py-4"> <p className="py-4">
KCMA is an open-source CAD application for creating accurate 3D models ZMA is an open-source CAD application for creating accurate 3D models
for use in manufacturing. It is built on top of the KittyCAD API. for use in manufacturing. It is built on top of KittyCAD, the design
KittyCAD is the first software infrastructure company built API from Zoo. Zoo is the first software infrastructure company built
specifically for the needs of the manufacturing industry. With KCMA we specifically for the needs of the manufacturing industry. With ZMA we
are showing how the KittyCAD API can be used to build entirely new are showing how the KittyCAD API from Zoo can be used to build
kinds of software for manufacturing. entirely new kinds of software for manufacturing.
</p> </p>
<p className="py-4"> <p className="py-4">
KCMA is currently in development. If you would like to be notified ZMA is currently in development. If you would like to be notified when
when KCMA is ready for production, please sign up for our mailing list ZMA is ready for production, please sign up for our mailing list at{' '}
at <a href="https://kittycad.io">kittycad.io</a>. <a href="https://zoo.dev">zoo.dev</a>.
</p> </p>
{isTauri() ? ( {isTauri() ? (
<ActionButton <ActionButton

View File

@ -715,28 +715,16 @@ dependencies = [
[[package]] [[package]]
name = "derive-docs" name = "derive-docs"
version = "0.1.4" version = "0.1.5"
dependencies = [ dependencies = [
"convert_case", "convert_case",
"expectorate", "expectorate",
"once_cell",
"openapitor", "openapitor",
"pretty_assertions", "pretty_assertions",
"proc-macro2", "proc-macro2",
"quote", "quote",
"serde", "regex",
"serde_tokenstream",
"syn 2.0.39",
]
[[package]]
name = "derive-docs"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c357dec14992ba88803535217ed83d6f6cd80efcb8fa8e3f8a30a9b84fadc1c7"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"serde", "serde",
"serde_tokenstream", "serde_tokenstream",
"syn 2.0.39", "syn 2.0.39",
@ -1436,7 +1424,7 @@ dependencies = [
"criterion", "criterion",
"dashmap", "dashmap",
"databake", "databake",
"derive-docs 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "derive-docs",
"expectorate", "expectorate",
"futures", "futures",
"insta", "insta",
@ -3959,18 +3947,18 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.12" version = "0.7.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8db0ac2df3d060f81ec0380ccc5b71c2a7c092cfced671feeee1320e95559c87" checksum = "1c4061bedbb353041c12f413700357bec76df2c7e2ca8e4df8bac24c6bf68e3d"
dependencies = [ dependencies = [
"zerocopy-derive", "zerocopy-derive",
] ]
[[package]] [[package]]
name = "zerocopy-derive" name = "zerocopy-derive"
version = "0.7.12" version = "0.7.31"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b6093bc6d5265ff40b479c834cdd25d8e20784781a2a29a8106327393d0a9ff" checksum = "b3c129550b3e6de3fd0ba67ba5c81818f9805e58b8d7fee80a3a59d2c9fc601a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "derive-docs" name = "derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.4" version = "0.1.5"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"
@ -14,8 +14,10 @@ proc-macro = true
[dependencies] [dependencies]
convert_case = "0.6.0" convert_case = "0.6.0"
once_cell = "1.18.0"
proc-macro2 = "1" proc-macro2 = "1"
quote = "1" quote = "1"
regex = "1.10"
serde = { version = "1.0.193", features = ["derive"] } serde = { version = "1.0.193", features = ["derive"] }
serde_tokenstream = "0.2" serde_tokenstream = "0.2"
syn = { version = "2.0.39", features = ["full"] } syn = { version = "2.0.39", features = ["full"] }

View File

@ -1,15 +1,11 @@
// Copyright 2023 Oxide Computer Company
//! This package defines macro attributes associated with HTTP handlers. These
//! attributes are used both to define an HTTP API and to generate an OpenAPI
//! Spec (OAS) v3 document that describes the API.
// Clippy's style advice is definitely valuable, but not worth the trouble for // Clippy's style advice is definitely valuable, but not worth the trouble for
// automated enforcement. // automated enforcement.
#![allow(clippy::style)] #![allow(clippy::style)]
use convert_case::Casing; use convert_case::Casing;
use once_cell::sync::Lazy;
use quote::{format_ident, quote, quote_spanned, ToTokens}; use quote::{format_ident, quote, quote_spanned, ToTokens};
use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use serde_tokenstream::{from_tokenstream, Error}; use serde_tokenstream::{from_tokenstream, Error};
use syn::{ use syn::{
@ -209,6 +205,18 @@ fn do_stdlib_inner(
quote! { quote! {
Vec<#ty_ident> Vec<#ty_ident>
} }
} else if ty_string.starts_with("Option<") {
let ty_string = ty_string.trim_start_matches("Option<").trim_end_matches('>');
let ty_ident = format_ident!("{}", ty_string);
quote! {
Option<#ty_ident>
}
} else if let Some((inner_array_type, num)) = parse_array_type(&ty_string) {
let ty_string = inner_array_type.to_owned();
let ty_ident = format_ident!("{}", ty_string);
quote! {
[#ty_ident; #num]
}
} else if ty_string.starts_with("Box<") { } else if ty_string.starts_with("Box<") {
let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>'); let ty_string = ty_string.trim_start_matches("Box<").trim_end_matches('>');
let ty_ident = format_ident!("{}", ty_string); let ty_ident = format_ident!("{}", ty_string);
@ -222,10 +230,13 @@ fn do_stdlib_inner(
} }
}; };
let ty_string = clean_type(&ty_string); let ty_string = rust_type_to_openapi_type(&ty_string);
if ty_string != "Args" { if ty_string != "Args" {
let schema = if ty_ident.to_string().starts_with("Vec < ") { let schema = if ty_ident.to_string().starts_with("Vec < ")
|| ty_ident.to_string().starts_with("Option <")
|| ty_ident.to_string().starts_with('[')
{
quote! { quote! {
<#ty_ident>::json_schema(&mut generator) <#ty_ident>::json_schema(&mut generator)
} }
@ -263,7 +274,7 @@ fn do_stdlib_inner(
ret_ty_string.trim().to_string() ret_ty_string.trim().to_string()
}; };
let ret_ty_ident = format_ident!("{}", ret_ty_string); let ret_ty_ident = format_ident!("{}", ret_ty_string);
let ret_ty_string = clean_type(&ret_ty_string); let ret_ty_string = rust_type_to_openapi_type(&ret_ty_string);
quote! { quote! {
Some(#docs_crate::StdLibFnArg { Some(#docs_crate::StdLibFnArg {
name: "".to_string(), name: "".to_string(),
@ -488,15 +499,22 @@ impl Parse for ItemFnForSignature {
} }
} }
fn clean_type(t: &str) -> String { fn rust_type_to_openapi_type(t: &str) -> String {
let mut t = t.to_string(); let mut t = t.to_string();
// Turn vecs into arrays. // Turn vecs into arrays.
// TODO: handle nested types
if t.starts_with("Vec<") { if t.starts_with("Vec<") {
t = t.replace("Vec<", "[").replace('>', "]"); t = t.replace("Vec<", "[").replace('>', "]");
} }
if t.starts_with("Box<") { if t.starts_with("Box<") {
t = t.replace("Box<", "").replace('>', ""); t = t.replace("Box<", "").replace('>', "");
} }
if t.starts_with("Option<") {
t = t.replace("Option<", "").replace('>', "");
}
if let Some((inner_type, _length)) = parse_array_type(&t) {
t = format!("[{inner_type}]")
}
if t == "f64" { if t == "f64" {
return "number".to_string(); return "number".to_string();
@ -507,6 +525,14 @@ fn clean_type(t: &str) -> String {
} }
} }
fn parse_array_type(type_name: &str) -> Option<(&str, usize)> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"\[([a-zA-Z0-9<>]+); ?(\d+)\]").unwrap());
let cap = RE.captures(type_name)?;
let inner_type = cap.get(1)?;
let length = cap.get(2)?.as_str().parse().ok()?;
Some((inner_type.as_str(), length))
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
@ -514,6 +540,19 @@ mod tests {
use super::*; use super::*;
#[test]
fn test_get_inner_array_type() {
for (expected, input) in [
(Some(("f64", 2)), "[f64;2]"),
(Some(("String", 2)), "[String; 2]"),
(Some(("Option<String>", 12)), "[Option<String>;12]"),
(Some(("Option<String>", 12)), "[Option<String>; 12]"),
] {
let actual = parse_array_type(input);
assert_eq!(actual, expected);
}
}
#[test] #[test]
fn test_stdlib_line_to() { fn test_stdlib_line_to() {
let (item, errors) = do_stdlib( let (item, errors) = do_stdlib(
@ -608,4 +647,46 @@ mod tests {
assert!(errors.is_empty()); assert!(errors.is_empty());
expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap()); expectorate::assert_contents("tests/box.gen", &openapitor::types::get_text_fmt(&item).unwrap());
} }
#[test]
fn test_stdlib_option() {
let (item, errors) = do_stdlib(
quote! {
name = "show",
},
quote! {
fn inner_show(
/// The args to do shit to.
args: Option<f64>
) -> Box<f64> {
args
}
},
)
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents("tests/option.gen", &openapitor::types::get_text_fmt(&item).unwrap());
}
#[test]
fn test_stdlib_array() {
let (item, errors) = do_stdlib(
quote! {
name = "show",
},
quote! {
fn inner_show(
/// The args to do shit to.
args: [f64; 2]
) -> Box<f64> {
args
}
},
)
.unwrap();
assert!(errors.is_empty());
expectorate::assert_contents("tests/array.gen", &openapitor::types::get_text_fmt(&item).unwrap());
}
} }

View File

@ -0,0 +1,82 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: show"]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
#[ts(export)]
pub(crate) struct Show {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: show"]
pub(crate) const Show: Show = Show {};
fn boxed_show(
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
>,
>,
> {
Box::pin(show(args))
}
impl crate::docs::StdLibFn for Show {
fn name(&self) -> String {
"show".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "[number]".to_string(),
schema: <[f64; 2usize]>::json_schema(&mut generator),
required: true,
}]
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
boxed_show
}
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
Box::new(self.clone())
}
}
fn inner_show(#[doc = r" The args to do shit to."] args: [f64; 2]) -> Box<f64> {
args
}

View File

@ -0,0 +1,82 @@
#[allow(non_camel_case_types, missing_docs)]
#[doc = "Std lib function: show"]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, schemars :: JsonSchema, ts_rs :: TS)]
#[ts(export)]
pub(crate) struct Show {}
#[allow(non_upper_case_globals, missing_docs)]
#[doc = "Std lib function: show"]
pub(crate) const Show: Show = Show {};
fn boxed_show(
args: crate::std::Args,
) -> std::pin::Pin<
Box<
dyn std::future::Future<
Output = anyhow::Result<crate::executor::MemoryItem, crate::errors::KclError>,
>,
>,
> {
Box::pin(show(args))
}
impl crate::docs::StdLibFn for Show {
fn name(&self) -> String {
"show".to_string()
}
fn summary(&self) -> String {
"".to_string()
}
fn description(&self) -> String {
"".to_string()
}
fn tags(&self) -> Vec<String> {
vec![]
}
fn args(&self) -> Vec<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
vec![crate::docs::StdLibFnArg {
name: "args".to_string(),
type_: "number".to_string(),
schema: <Option<f64>>::json_schema(&mut generator),
required: true,
}]
}
fn return_value(&self) -> Option<crate::docs::StdLibFnArg> {
let mut settings = schemars::gen::SchemaSettings::openapi3();
settings.inline_subschemas = true;
let mut generator = schemars::gen::SchemaGenerator::new(settings);
Some(crate::docs::StdLibFnArg {
name: "".to_string(),
type_: "number".to_string(),
schema: f64::json_schema(&mut generator),
required: true,
})
}
fn unpublished(&self) -> bool {
false
}
fn deprecated(&self) -> bool {
false
}
fn std_lib_fn(&self) -> crate::std::StdFn {
boxed_show
}
fn clone_box(&self) -> Box<dyn crate::docs::StdLibFn> {
Box::new(self.clone())
}
}
fn inner_show(#[doc = r" The args to do shit to."] args: Option<f64>) -> Box<f64> {
args
}

View File

@ -18,8 +18,8 @@ async-trait = "0.1.73"
clap = { version = "4.4.8", features = ["cargo", "derive", "env", "unicode"], optional = true } clap = { version = "4.4.8", features = ["cargo", "derive", "env", "unicode"], optional = true }
dashmap = "5.5.3" dashmap = "5.5.3"
databake = { version = "0.1.7", features = ["derive"] } databake = { version = "0.1.7", features = ["derive"] }
derive-docs = { version = "0.1.4" } # derive-docs = { version = "0.1.4" }
#derive-docs = { path = "../derive-docs" } derive-docs = { path = "../derive-docs" }
kittycad = { workspace = true } kittycad = { workspace = true }
lazy_static = "1.4.0" lazy_static = "1.4.0"
parse-display = "0.8.2" parse-display = "0.8.2"

View File

@ -1,6 +1,6 @@
//! Data types for the AST. //! Data types for the AST.
use std::{collections::HashMap, fmt::Write}; use std::{collections::HashMap, fmt::Write, ops::RangeInclusive};
use anyhow::Result; use anyhow::Result;
use databake::*; use databake::*;
@ -11,6 +11,7 @@ use serde_json::{Map, Value as JValue};
use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind}; use tower_lsp::lsp_types::{CompletionItem, CompletionItemKind, DocumentSymbol, Range as LspRange, SymbolKind};
pub use self::literal_value::LiteralValue; pub use self::literal_value::LiteralValue;
pub use self::none::KclNone;
use crate::{ use crate::{
docs::StdLibFn, docs::StdLibFn,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
@ -20,6 +21,7 @@ use crate::{
}; };
mod literal_value; mod literal_value;
mod none;
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
#[databake(path = kcl_lib::ast::types)] #[databake(path = kcl_lib::ast::types)]
@ -407,6 +409,7 @@ pub enum Value {
ObjectExpression(Box<ObjectExpression>), ObjectExpression(Box<ObjectExpression>),
MemberExpression(Box<MemberExpression>), MemberExpression(Box<MemberExpression>),
UnaryExpression(Box<UnaryExpression>), UnaryExpression(Box<UnaryExpression>),
None(KclNone),
} }
impl Value { impl Value {
@ -423,6 +426,9 @@ impl Value {
Value::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level), Value::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
Value::UnaryExpression(unary_exp) => unary_exp.recast(options), Value::UnaryExpression(unary_exp) => unary_exp.recast(options),
Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(), Value::PipeSubstitution(_) => crate::parser::PIPE_SUBSTITUTION_OPERATOR.to_string(),
Value::None(_) => {
unimplemented!("there is no literal None, see https://github.com/KittyCAD/modeling-app/issues/1115")
}
} }
} }
@ -444,6 +450,7 @@ impl Value {
Value::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value), Value::PipeExpression(ref mut pipe_exp) => pipe_exp.replace_value(source_range, new_value),
Value::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value), Value::UnaryExpression(ref mut unary_exp) => unary_exp.replace_value(source_range, new_value),
Value::PipeSubstitution(_) => {} Value::PipeSubstitution(_) => {}
Value::None(_) => {}
} }
} }
@ -460,6 +467,7 @@ impl Value {
Value::ObjectExpression(object_expression) => object_expression.start(), Value::ObjectExpression(object_expression) => object_expression.start(),
Value::MemberExpression(member_expression) => member_expression.start(), Value::MemberExpression(member_expression) => member_expression.start(),
Value::UnaryExpression(unary_expression) => unary_expression.start(), Value::UnaryExpression(unary_expression) => unary_expression.start(),
Value::None(none) => none.start,
} }
} }
@ -476,6 +484,7 @@ impl Value {
Value::ObjectExpression(object_expression) => object_expression.end(), Value::ObjectExpression(object_expression) => object_expression.end(),
Value::MemberExpression(member_expression) => member_expression.end(), Value::MemberExpression(member_expression) => member_expression.end(),
Value::UnaryExpression(unary_expression) => unary_expression.end(), Value::UnaryExpression(unary_expression) => unary_expression.end(),
Value::None(none) => none.end,
} }
} }
@ -483,19 +492,22 @@ impl Value {
/// This is really recursive so keep that in mind. /// This is really recursive so keep that in mind.
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> { pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
match self { match self {
Value::Literal(_literal) => None,
Value::Identifier(_identifier) => None,
Value::BinaryExpression(binary_expression) => binary_expression.get_hover_value_for_position(pos, code), Value::BinaryExpression(binary_expression) => binary_expression.get_hover_value_for_position(pos, code),
Value::FunctionExpression(function_expression) => { Value::FunctionExpression(function_expression) => {
function_expression.get_hover_value_for_position(pos, code) function_expression.get_hover_value_for_position(pos, code)
} }
Value::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code), Value::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
Value::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code), Value::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code),
Value::PipeSubstitution(_) => None,
Value::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code), Value::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code),
Value::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code), Value::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
Value::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code), Value::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
Value::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code), Value::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
// TODO: LSP hover information for values/types. https://github.com/KittyCAD/modeling-app/issues/1126
Value::None(_) => None,
Value::Literal(_) => None,
Value::Identifier(_) => None,
// TODO: LSP hover information for symbols. https://github.com/KittyCAD/modeling-app/issues/1127
Value::PipeSubstitution(_) => None,
} }
} }
@ -519,6 +531,7 @@ impl Value {
member_expression.rename_identifiers(old_name, new_name) member_expression.rename_identifiers(old_name, new_name)
} }
Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name), Value::UnaryExpression(ref mut unary_expression) => unary_expression.rename_identifiers(old_name, new_name),
Value::None(_) => {}
} }
} }
@ -539,6 +552,7 @@ impl Value {
Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(), Value::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
Value::MemberExpression(member_expression) => member_expression.get_constraint_level(), Value::MemberExpression(member_expression) => member_expression.get_constraint_level(),
Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(), Value::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
Value::None(none) => none.get_constraint_level(),
} }
} }
} }
@ -926,6 +940,7 @@ impl CallExpression {
for arg in &self.arguments { for arg in &self.arguments {
let result: MemoryItem = match arg { let result: MemoryItem = match arg {
Value::None(none) => none.into(),
Value::Literal(literal) => literal.into(), Value::Literal(literal) => literal.into(),
Value::Identifier(identifier) => { Value::Identifier(identifier) => {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
@ -991,7 +1006,7 @@ impl CallExpression {
if fn_args.len() != function_expression.params.len() { if fn_args.len() != function_expression.params.len() {
return Err(KclError::Semantic(KclErrorDetails { return Err(KclError::Semantic(KclErrorDetails {
message: format!( message: format!(
"Expected {} arguments, got {}", "this function expected {} arguments, got {}",
function_expression.params.len(), function_expression.params.len(),
fn_args.len(), fn_args.len(),
), ),
@ -1607,6 +1622,7 @@ impl ArrayExpression {
for element in &self.elements { for element in &self.elements {
let result = match element { let result = match element {
Value::Literal(literal) => literal.into(), Value::Literal(literal) => literal.into(),
Value::None(none) => none.into(),
Value::Identifier(identifier) => { Value::Identifier(identifier) => {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
value.clone() value.clone()
@ -1759,6 +1775,7 @@ impl ObjectExpression {
for property in &self.properties { for property in &self.properties {
let result = match &property.value { let result = match &property.value {
Value::Literal(literal) => literal.into(), Value::Literal(literal) => literal.into(),
Value::None(none) => none.into(),
Value::Identifier(identifier) => { Value::Identifier(identifier) => {
let value = memory.get(&identifier.name, identifier.into())?; let value = memory.get(&identifier.name, identifier.into())?;
value.clone() value.clone()
@ -2646,6 +2663,23 @@ impl FunctionExpression {
} }
} }
/// Required parameters must be declared before optional parameters.
/// This gets all the required parameters.
pub fn required_params(&self) -> &[Parameter] {
let end_of_required_params = self
.params
.iter()
.position(|param| param.optional)
// If there's no optional params, then all the params are required params.
.unwrap_or(self.params.len());
&self.params[..end_of_required_params]
}
/// Minimum and maximum number of arguments this function can take.
pub fn number_of_args(&self) -> RangeInclusive<usize> {
self.required_params().len()..=self.params.len()
}
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Value) { pub fn replace_value(&mut self, source_range: SourceRange, new_value: Value) {
self.body.replace_value(source_range, new_value); self.body.replace_value(source_range, new_value);
} }
@ -3400,4 +3434,107 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
); );
} }
} }
#[test]
fn required_params() {
for (i, (test_name, expected, function_expr)) in [
(
"no params",
(0..=0),
FunctionExpression {
start: 0,
end: 0,
params: vec![],
body: Program {
start: 0,
end: 0,
body: Vec::new(),
non_code_meta: Default::default(),
},
},
),
(
"all required params",
(1..=1),
FunctionExpression {
start: 0,
end: 0,
params: vec![Parameter {
identifier: Identifier {
start: 0,
end: 0,
name: "foo".to_owned(),
},
optional: false,
}],
body: Program {
start: 0,
end: 0,
body: Vec::new(),
non_code_meta: Default::default(),
},
},
),
(
"all optional params",
(0..=1),
FunctionExpression {
start: 0,
end: 0,
params: vec![Parameter {
identifier: Identifier {
start: 0,
end: 0,
name: "foo".to_owned(),
},
optional: true,
}],
body: Program {
start: 0,
end: 0,
body: Vec::new(),
non_code_meta: Default::default(),
},
},
),
(
"mixed params",
(1..=2),
FunctionExpression {
start: 0,
end: 0,
params: vec![
Parameter {
identifier: Identifier {
start: 0,
end: 0,
name: "foo".to_owned(),
},
optional: false,
},
Parameter {
identifier: Identifier {
start: 0,
end: 0,
name: "bar".to_owned(),
},
optional: true,
},
],
body: Program {
start: 0,
end: 0,
body: Vec::new(),
non_code_meta: Default::default(),
},
},
),
]
.into_iter()
.enumerate()
{
let actual = function_expr.number_of_args();
assert_eq!(expected, actual, "failed test #{i} '{test_name}'");
}
}
} }

View File

@ -0,0 +1,58 @@
//! KCL has optional parameters. Their type is [`KclOption`].
//! If an optional parameter is not given, it will have a value of type [`KclNone`].
use databake::*;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::executor::{MemoryItem, SourceRange, UserVal};
use super::ConstraintLevel;
/// KCL value for an optional parameter which was not given an argument.
/// (remember, parameters are in the function declaration,
/// arguments are in the function call/application).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake, Default)]
#[databake(path = kcl_lib::ast::types)]
#[ts(export)]
#[serde(tag = "type")]
pub struct KclNone {
// TODO: Convert this to be an Option<SourceRange>.
pub start: usize,
pub end: usize,
}
impl From<&KclNone> for SourceRange {
fn from(v: &KclNone) -> Self {
Self([v.start, v.end])
}
}
impl From<&KclNone> for UserVal {
fn from(none: &KclNone) -> Self {
UserVal {
value: serde_json::to_value(none).expect("can always serialize a None"),
meta: Default::default(),
}
}
}
impl From<&KclNone> for MemoryItem {
fn from(none: &KclNone) -> Self {
let val = UserVal::from(none);
MemoryItem::UserVal(val)
}
}
impl KclNone {
pub fn source_range(&self) -> SourceRange {
SourceRange([self.start, self.end])
}
/// Get the constraint level.
/// KCL None is never constrained.
pub fn get_constraint_level(&self) -> ConstraintLevel {
ConstraintLevel::None {
source_ranges: vec![self.source_range()],
}
}
}

View File

@ -4,7 +4,7 @@ use tower_lsp::lsp_types::{Diagnostic, DiagnosticSeverity};
use crate::executor::SourceRange; use crate::executor::SourceRange;
#[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone)] #[derive(Error, Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
#[ts(export)] #[ts(export)]
#[serde(tag = "kind", rename_all = "snake_case")] #[serde(tag = "kind", rename_all = "snake_case")]
pub enum KclError { pub enum KclError {
@ -32,7 +32,7 @@ pub enum KclError {
Internal(KclErrorDetails), Internal(KclErrorDetails),
} }
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone)] #[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq)]
#[ts(export)] #[ts(export)]
pub struct KclErrorDetails { pub struct KclErrorDetails {
#[serde(rename = "sourceRanges")] #[serde(rename = "sourceRanges")]

View File

@ -8,10 +8,11 @@ use kittycad::types::{Color, ModelingCmd, Point3D};
use parse_display::{Display, FromStr}; use parse_display::{Display, FromStr};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value as JValue;
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange}; use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
use crate::{ use crate::{
ast::types::{BodyItem, FunctionExpression, Value}, ast::types::{BodyItem, FunctionExpression, KclNone, Value},
engine::{EngineConnection, EngineManager}, engine::{EngineConnection, EngineManager},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
std::{FunctionKind, StdLib}, std::{FunctionKind, StdLib},
@ -49,6 +50,7 @@ impl ProgramMemory {
} }
/// Get a value from the program memory. /// Get a value from the program memory.
/// Return Err if not found.
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> { pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
self.root.get(key).ok_or_else(|| { self.root.get(key).ok_or_else(|| {
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
@ -349,6 +351,40 @@ impl MemoryItem {
} }
} }
/// Get a JSON value and deserialize it into some concrete type.
pub fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> {
let json = self.get_json_value()?;
serde_json::from_value(json).map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: self.clone().into(),
})
})
}
/// Get a JSON value and deserialize it into some concrete type.
/// If it's a KCL None, return None. Otherwise return Some.
pub fn get_json_opt<T: serde::de::DeserializeOwned>(&self) -> Result<Option<T>, KclError> {
let json = self.get_json_value()?;
if let JValue::Object(ref o) = json {
if let Some(JValue::String(s)) = o.get("type") {
if s == "KclNone" {
return Ok(None);
}
}
}
serde_json::from_value(json)
.map_err(|e| {
KclError::Type(KclErrorDetails {
message: format!("Failed to deserialize struct from JSON: {}", e),
source_ranges: self.clone().into(),
})
})
.map(Some)
}
/// If this memory item is a function, call it with the given arguments, return its val as Ok. /// If this memory item is a function, call it with the given arguments, return its val as Ok.
/// If it's not a function, return Err. /// If it's not a function, return Err.
pub async fn call_fn( pub async fn call_fn(
@ -873,6 +909,9 @@ pub async fn execute(
let metadata = Metadata { source_range }; let metadata = Metadata { source_range };
match &declaration.init { match &declaration.init {
Value::None(none) => {
memory.add(&var_name, none.into(), source_range)?;
}
Value::Literal(literal) => { Value::Literal(literal) => {
memory.add(&var_name, literal.into(), source_range)?; memory.add(&var_name, literal.into(), source_range)?;
} }
@ -892,27 +931,8 @@ pub async fn execute(
_metadata: Vec<Metadata>, _metadata: Vec<Metadata>,
ctx: ExecutorContext| { ctx: ExecutorContext| {
Box::pin(async move { Box::pin(async move {
let mut fn_memory = memory.clone(); let mut fn_memory =
assign_args_to_params(&function_expression, args, memory.clone())?;
if args.len() != function_expression.params.len() {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Expected {} arguments, got {}",
function_expression.params.len(),
args.len(),
),
source_ranges: vec![(&function_expression).into()],
}));
}
// Add the arguments to the memory.
for (index, param) in function_expression.params.iter().enumerate() {
fn_memory.add(
&param.identifier.name,
args.get(index).unwrap().clone(),
(&param.identifier).into(),
)?;
}
let result = execute( let result = execute(
function_expression.body.clone(), function_expression.body.clone(),
@ -1010,6 +1030,9 @@ pub async fn execute(
} }
Value::PipeSubstitution(_) => {} Value::PipeSubstitution(_) => {}
Value::FunctionExpression(_) => {} Value::FunctionExpression(_) => {}
Value::None(none) => {
memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none)));
}
}, },
} }
} }
@ -1017,10 +1040,67 @@ pub async fn execute(
Ok(memory.clone()) Ok(memory.clone())
} }
/// For each argument given,
/// assign it to a parameter of the function, in the given block of function memory.
/// Returns Err if too few/too many arguments were given for the function.
fn assign_args_to_params(
function_expression: &FunctionExpression,
args: Vec<MemoryItem>,
mut fn_memory: ProgramMemory,
) -> Result<ProgramMemory, KclError> {
let num_args = function_expression.number_of_args();
let (min_params, max_params) = num_args.into_inner();
let n = args.len();
// Check if the user supplied too many arguments
// (we'll check for too few arguments below).
let err_wrong_number_args = KclError::Semantic(KclErrorDetails {
message: if min_params == max_params {
format!("Expected {min_params} arguments, got {n}")
} else {
format!("Expected {min_params}-{max_params} arguments, got {n}")
},
source_ranges: vec![function_expression.into()],
});
if n > max_params {
return Err(err_wrong_number_args);
}
// Add the arguments to the memory.
for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) {
// Argument was provided.
fn_memory.add(&param.identifier.name, arg.clone(), (&param.identifier).into())?;
} else {
// Argument was not provided.
if param.optional {
// If the corresponding parameter is optional,
// then it's fine, the user doesn't need to supply it.
let none = KclNone {
start: param.identifier.start,
end: param.identifier.end,
};
fn_memory.add(
&param.identifier.name,
MemoryItem::from(&none),
(&param.identifier).into(),
)?;
} else {
// But if the corresponding parameter was required,
// then the user has called with too few arguments.
return Err(err_wrong_number_args);
}
}
}
Ok(fn_memory)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::ast::types::{Identifier, Parameter};
use super::*; use super::*;
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> { pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
@ -1566,4 +1646,122 @@ show(bracket)
"#; "#;
parse_execute(ast).await.unwrap(); parse_execute(ast).await.unwrap();
} }
#[test]
fn test_assign_args_to_params() {
// Set up a little framework for this test.
fn mem(number: usize) -> MemoryItem {
MemoryItem::UserVal(UserVal {
value: number.into(),
meta: Default::default(),
})
}
fn ident(s: &'static str) -> Identifier {
Identifier {
start: 0,
end: 0,
name: s.to_owned(),
}
}
fn opt_param(s: &'static str) -> Parameter {
Parameter {
identifier: ident(s),
optional: true,
}
}
fn req_param(s: &'static str) -> Parameter {
Parameter {
identifier: ident(s),
optional: false,
}
}
// Declare the test cases.
for (test_name, params, args, expected) in [
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())),
(
"all params required, and all given, should be OK",
vec![req_param("x")],
vec![mem(1)],
Ok(ProgramMemory {
return_: None,
root: HashMap::from([("x".to_owned(), mem(1))]),
}),
),
(
"all params required, none given, should error",
vec![req_param("x")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0])],
message: "Expected 1 arguments, got 0".to_owned(),
})),
),
(
"all params optional, none given, should be OK",
vec![opt_param("x")],
vec![],
Ok(ProgramMemory {
return_: None,
root: HashMap::from([("x".to_owned(), MemoryItem::from(&KclNone::default()))]),
}),
),
(
"mixed params, too few given",
vec![req_param("x"), opt_param("y")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0])],
message: "Expected 1-2 arguments, got 0".to_owned(),
})),
),
(
"mixed params, minimum given, should be OK",
vec![req_param("x"), opt_param("y")],
vec![mem(1)],
Ok(ProgramMemory {
return_: None,
root: HashMap::from([
("x".to_owned(), mem(1)),
("y".to_owned(), MemoryItem::from(&KclNone::default())),
]),
}),
),
(
"mixed params, maximum given, should be OK",
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2)],
Ok(ProgramMemory {
return_: None,
root: HashMap::from([("x".to_owned(), mem(1)), ("y".to_owned(), mem(2))]),
}),
),
(
"mixed params, too many given",
vec![req_param("x"), opt_param("y")],
vec![mem(1), mem(2), mem(3)],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange([0, 0])],
message: "Expected 1-2 arguments, got 3".to_owned(),
})),
),
] {
// Run each test.
let func_expr = &FunctionExpression {
start: 0,
end: 0,
params,
body: crate::ast::types::Program {
start: 0,
end: 0,
body: Vec::new(),
non_code_meta: Default::default(),
},
};
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
assert_eq!(
actual, expected,
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
);
}
}
} }

View File

@ -299,6 +299,15 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
message: TODO_783.to_owned(), message: TODO_783.to_owned(),
})) }))
} }
Value::None(_) => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges,
// TODO: Better error message here.
// Once we have ways to use None values (e.g. by replacing with a default value)
// we should suggest one of them here.
message: "cannot use a KCL None value as an operand".to_owned(),
}));
}
Value::UnaryExpression(x) => BinaryPart::UnaryExpression(x), Value::UnaryExpression(x) => BinaryPart::UnaryExpression(x),
Value::Literal(x) => BinaryPart::Literal(x), Value::Literal(x) => BinaryPart::Literal(x),
Value::Identifier(x) => BinaryPart::Identifier(x), Value::Identifier(x) => BinaryPart::Identifier(x),

View File

@ -154,6 +154,7 @@ impl Default for StdLib {
} }
} }
#[derive(Debug)]
pub enum FunctionKind { pub enum FunctionKind {
Core(Box<dyn StdLibFn>), Core(Box<dyn StdLibFn>),
Std(Box<dyn KclStdLibFn>), Std(Box<dyn KclStdLibFn>),

View File

@ -6,6 +6,7 @@ use kittycad::types::{Angle, ModelingCmd, Point3D};
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::executor::SourceRange;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{ executor::{
@ -1122,7 +1123,7 @@ async fn inner_tangential_arc(
path: sketch_group.id, path: sketch_group.id,
segment: kittycad::types::PathSegment::TangentialArc { segment: kittycad::types::PathSegment::TangentialArc {
radius: *radius, radius: *radius,
offset: kittycad::types::Angle { offset: Angle {
unit: kittycad::types::UnitAngle::Degrees, unit: kittycad::types::UnitAngle::Degrees,
value: *offset, value: *offset,
}, },
@ -1178,27 +1179,32 @@ fn tan_arc_to(sketch_group: &SketchGroup, to: &[f64; 2]) -> ModelingCmd {
} }
} }
/// Data to draw a tangential arc to a specific point. fn too_few_args(source_range: SourceRange) -> KclError {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] KclError::Syntax(KclErrorDetails {
#[ts(export)] source_ranges: vec![source_range],
#[serde(rename_all = "camelCase", untagged)] message: "too few arguments".to_owned(),
pub enum TangentialArcToData { })
/// A point with a tag. }
PointWithTag {
/// Where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position. fn get_arg<I: Iterator>(it: &mut I, src: SourceRange) -> Result<I::Item, KclError> {
to: [f64; 2], it.next().ok_or_else(|| too_few_args(src))
/// The tag.
tag: String,
},
/// A point where the arc should end. Must lie in the same plane as the current path pen position. Must not be colinear with current path pen position.
Point([f64; 2]),
} }
/// Draw a tangential arc to a specific point. /// Draw a tangential arc to a specific point.
pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> { pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> {
let (data, sketch_group): (TangentialArcToData, Box<SketchGroup>) = args.get_data_and_sketch_group()?; let src = args.source_range;
let new_sketch_group = inner_tangential_arc_to(data, sketch_group, args).await?; // Get arguments to function call
let mut it = args.args.iter();
let to: [f64; 2] = get_arg(&mut it, src)?.get_json()?;
let sketch_group: Box<SketchGroup> = get_arg(&mut it, src)?.get_json()?;
let tag = if let Ok(memory_item) = get_arg(&mut it, src) {
memory_item.get_json_opt()?
} else {
None
};
let new_sketch_group = inner_tangential_arc_to(to, sketch_group, tag, args).await?;
Ok(MemoryItem::SketchGroup(new_sketch_group)) Ok(MemoryItem::SketchGroup(new_sketch_group))
} }
@ -1207,29 +1213,23 @@ pub async fn tangential_arc_to(args: Args) -> Result<MemoryItem, KclError> {
name = "tangentialArcTo", name = "tangentialArcTo",
}] }]
async fn inner_tangential_arc_to( async fn inner_tangential_arc_to(
data: TangentialArcToData, to: [f64; 2],
sketch_group: Box<SketchGroup>, sketch_group: Box<SketchGroup>,
tag: Option<String>,
args: Args, args: Args,
) -> Result<Box<SketchGroup>, KclError> { ) -> Result<Box<SketchGroup>, KclError> {
let from: Point2d = sketch_group.get_coords_from_paths()?; let from: Point2d = sketch_group.get_coords_from_paths()?;
let to = match &data { let [to_x, to_y] = to;
TangentialArcToData::PointWithTag { to, .. } => to,
TangentialArcToData::Point(to) => to,
};
let delta = [to[0] - from.x, to[1] - from.y]; let delta = [to_x - from.x, to_y - from.y];
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
args.send_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?; args.send_modeling_cmd(id, tan_arc_to(&sketch_group, &delta)).await?;
let current_path = Path::ToPoint { let current_path = Path::ToPoint {
base: BasePath { base: BasePath {
from: from.into(), from: from.into(),
to: *to, to,
name: if let TangentialArcToData::PointWithTag { tag, .. } = data { name: tag.unwrap_or_default(),
tag.to_string()
} else {
"".to_string()
},
geo_meta: GeoMeta { geo_meta: GeoMeta {
id, id,
metadata: args.source_range.into(), metadata: args.source_range.into(),

View File

@ -474,6 +474,24 @@ show(square)
twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999); twenty_twenty::assert_image("tests/executor/outputs/holes.png", &result, 0.999);
} }
#[tokio::test(flavor = "multi_thread")]
async fn optional_params() {
let code = r#"
fn circle = (pos, radius, tag?) => {
const sg = startSketchOn('XY')
|> startProfileAt(pos, %)
|> arc({angle_end: 360, angle_start: 0, radius: radius}, %)
|> close(%)
return sg
}
show(circle([2, 2], 20))
"#;
let result = execute_and_snapshot(code).await.unwrap();
twenty_twenty::assert_image("tests/executor/outputs/optional_params.png", &result, 0.999);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn serial_test_rounded_with_holes() { async fn serial_test_rounded_with_holes() {
let code = r#"fn circle = (pos, radius) => { let code = r#"fn circle = (pos, radius) => {
@ -488,17 +506,21 @@ async fn serial_test_rounded_with_holes() {
return sg return sg
} }
fn tarc = (to, sketchGroup, tag?) => {
return tangentialArcTo(to, sketchGroup, tag)
}
fn roundedRectangle = (pos, w, l, cornerRadius) => { fn roundedRectangle = (pos, w, l, cornerRadius) => {
const rr = startSketchOn('XY') const rr = startSketchOn('XY')
|> startProfileAt([pos[0] - w/2, 0], %) |> startProfileAt([pos[0] - w/2, 0], %)
|> lineTo([pos[0] - w/2, pos[1] - l/2 + cornerRadius], %) |> lineTo([pos[0] - w/2, pos[1] - l/2 + cornerRadius], %)
|> tangentialArcTo([pos[0] - w/2 + cornerRadius, pos[1] - l/2], %) |> tarc([pos[0] - w/2 + cornerRadius, pos[1] - l/2], %, "arc0")
|> lineTo([pos[0] + w/2 - cornerRadius, pos[1] - l/2], %) |> lineTo([pos[0] + w/2 - cornerRadius, pos[1] - l/2], %)
|> tangentialArcTo([pos[0] + w/2, pos[1] - l/2 + cornerRadius], %) |> tarc([pos[0] + w/2, pos[1] - l/2 + cornerRadius], %)
|> lineTo([pos[0] + w/2, pos[1] + l/2 - cornerRadius], %) |> lineTo([pos[0] + w/2, pos[1] + l/2 - cornerRadius], %)
|> tangentialArcTo([pos[0] + w/2 - cornerRadius, pos[1] + l/2], %) |> tarc([pos[0] + w/2 - cornerRadius, pos[1] + l/2], %, "arc2")
|> lineTo([pos[0] - w/2 + cornerRadius, pos[1] + l/2], %) |> lineTo([pos[0] - w/2 + cornerRadius, pos[1] + l/2], %)
|> tangentialArcTo([pos[0] - w/2, pos[1] + l/2 - cornerRadius], %) |> tarc([pos[0] - w/2, pos[1] + l/2 - cornerRadius], %)
|> close(%) |> close(%)
return rr return rr
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB