Compare commits
97 Commits
achalmers/
...
paultag/di
Author | SHA1 | Date | |
---|---|---|---|
23df7e5429 | |||
7ed26e21c6 | |||
c668d40efc | |||
f38c6b90b7 | |||
7bc8bae0ec | |||
3804aca27e | |||
b127680f2f | |||
b7de8e60cf | |||
058fccb5e1 | |||
0006d72973 | |||
00e97257ae | |||
aeb656d176 | |||
ac49ebd6e0 | |||
b40f03ad25 | |||
a8ad86e645 | |||
87f50cd5e9 | |||
0400e6228e | |||
26f150fd6c | |||
3049f405f5 | |||
53d40301dc | |||
671c01e36f | |||
e80151979b | |||
668e2afb99 | |||
ea57de0074 | |||
548c664db0 | |||
d3a3f4410c | |||
22eb343171 | |||
f2cfa4d5cf | |||
3f1f40eeba | |||
ff2d161606 | |||
210c78029d | |||
e27840219b | |||
c943a3f192 | |||
6aa588f09f | |||
59a6333aad | |||
403f1507ae | |||
eac7b83504 | |||
667500d1b9 | |||
b15aac9f48 | |||
54153aa646 | |||
943cf21d34 | |||
5a6728c45a | |||
ff2103d493 | |||
2dfa8f2176 | |||
29ed330326 | |||
ca2cc825a6 | |||
83fe1b7ce0 | |||
157b76cc78 | |||
cf957d880e | |||
dfc3d19677 | |||
dd370a9365 | |||
2274d6459c | |||
32ce857119 | |||
88b51da417 | |||
30d365aeb3 | |||
7af62399ac | |||
441d957228 | |||
9e57034873 | |||
eb96d6539c | |||
513c76ecc8 | |||
51d9449280 | |||
6366bc4766 | |||
7a21918223 | |||
8072f1db63 | |||
18e1855fa9 | |||
7be53c7d4a | |||
2bf20988ef | |||
1495cc6d18 | |||
f876e6ca3c | |||
60a0c811ab | |||
cab0c1e6a1 | |||
417d720b22 | |||
77293952c0 | |||
ea3d604b73 | |||
023a659491 | |||
dd3a2b14f9 | |||
424b409cc1 | |||
82a58e69c2 | |||
776b420031 | |||
1087d4223b | |||
089d6df889 | |||
efb067af58 | |||
2aa27eab01 | |||
9c47ac5b57 | |||
5ae1aecd74 | |||
68ae7e98f9 | |||
56771d561a | |||
f09411817c | |||
bed7ae3b8b | |||
c43510732c | |||
51f0b669a4 | |||
3cbedcd3e7 | |||
5d2fa43150 | |||
ec49b0752e | |||
3b171fb881 | |||
0548409da0 | |||
dd052b35fd |
35
.github/workflows/build-apps.yml
vendored
@ -165,7 +165,6 @@ jobs:
|
||||
- name: Build the app (release)
|
||||
if: ${{ env.IS_RELEASE == 'true' || env.IS_NIGHTLY == 'true' }}
|
||||
env:
|
||||
PUBLISH_FOR_PULL_REQUEST: true
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
@ -173,7 +172,6 @@ jobs:
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
|
||||
@ -229,7 +227,6 @@ jobs:
|
||||
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
|
||||
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
run: yarn electron-builder --config --publish always
|
||||
|
||||
@ -362,6 +359,17 @@ jobs:
|
||||
- name: List artifacts
|
||||
run: "ls -R out"
|
||||
|
||||
- name: Set more complete nightly release notes
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: |
|
||||
# Note: preferred going this way instead of a full clone in the checkout step,
|
||||
# see https://github.com/actions/checkout/issues/1471
|
||||
git fetch --prune --unshallow --tags
|
||||
export TAG="nightly-${VERSION}"
|
||||
export PREVIOUS_TAG=$(git describe --tags --match="nightly-v[0-9]*" --abbrev=0)
|
||||
export NOTES=$(./scripts/get-nightly-changelog.sh)
|
||||
yarn files:set-notes
|
||||
|
||||
- name: Authenticate to Google Cloud
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: 'google-github-actions/auth@v2.1.7'
|
||||
@ -383,12 +391,17 @@ jobs:
|
||||
parent: false
|
||||
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
||||
|
||||
- name: Create draft release
|
||||
uses: softprops/action-gh-release@v2
|
||||
if: ${{ env.IS_RELEASE == 'true' }}
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
run: yarn files:invalidate-bucket:nightly
|
||||
|
||||
- name: Tag nightly commit
|
||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
name: ${{ env.VERSION }}
|
||||
tag_name: ${{ env.VERSION }}
|
||||
draft: true
|
||||
generate_release_notes: true
|
||||
files: 'out/Zoo*'
|
||||
script: |
|
||||
const { VERSION } = process.env
|
||||
const { owner, repo } = context.repo
|
||||
const { sha } = context
|
||||
const ref = `refs/tags/nightly-${VERSION}`
|
||||
github.rest.git.createRef({ owner, repo, sha, ref })
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
@ -71,7 +71,7 @@ jobs:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
- name: Upload to codecov.io
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v5
|
||||
with:
|
||||
token: ${{secrets.CODECOV_TOKEN}}
|
||||
fail_ci_if_error: true
|
||||
|
4
.github/workflows/e2e-tests.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
@ -255,7 +255,7 @@ jobs:
|
||||
- name: Download Wasm Cache
|
||||
id: download-wasm
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v7
|
||||
continue-on-error: true
|
||||
with:
|
||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||
|
12
.github/workflows/publish-apps-release.yml
vendored
@ -126,11 +126,13 @@ jobs:
|
||||
destination: 'dl.kittycad.io/releases/modeling-app'
|
||||
|
||||
- name: Invalidate bucket cache on latest*.yml and last_download.json files
|
||||
run: |
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
||||
run: yarn files:invalidate-bucket
|
||||
|
||||
- name: Upload release files to Github
|
||||
if: ${{ github.event_name == 'release' }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: 'out/Zoo*'
|
||||
|
||||
|
||||
announce_release:
|
||||
|
1
.gitignore
vendored
@ -61,6 +61,7 @@ Mac_App_Distribution.provisionprofile
|
||||
*.tsbuildinfo
|
||||
src/wasm-lib/pkg
|
||||
|
||||
.eslintcache
|
||||
venv
|
||||
.vite/
|
||||
|
||||
|
43
INSTALL.md
Normal file
@ -0,0 +1,43 @@
|
||||
# Setting Up Zoo Modeling App
|
||||
|
||||
Compared to other CAD software, getting Zoo Modeling App up and running is quick and straightforward across platforms. It's about 100MB to download and is quick to install.
|
||||
|
||||
## Windows
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Windows and for your processor type.
|
||||
|
||||
2. Once downloaded, run the installer `Zoo Modeling App-{version}-{arch}-win.exe` which should take a few seconds.
|
||||
|
||||
3. The installation happens at `C:\Program Files\Zoo Modeling App`. A shortcut in the start menu is also created so you can run the app easily by clicking on it.
|
||||
|
||||
## macOS
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for macOS and for your processor type.
|
||||
|
||||
2. Once downloaded, open the disk image `Zoo Modeling App-{version}-{arch}-mac.dmg` and drag the applications to your `Applications` directory.
|
||||
|
||||
3. You can then open your `Applications` directory and double-click on `Zoo Modeling App` to open.
|
||||
|
||||
|
||||
## Linux
|
||||
|
||||
1. Download the [Zoo Modeling App installer](https://zoo.dev/modeling-app/download) for Linux and for your processor type.
|
||||
|
||||
2. Install the dependencies needed to run the [AppImage format](https://appimage.org/).
|
||||
- On Ubuntu, install the FUSE library with these commands in a terminal.
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install libfuse2
|
||||
```
|
||||
- Optionally, follow [these steps](https://github.com/probonopd/go-appimage/blob/master/src/appimaged/README.md#initial-setup) to install `appimaged`. It is a daemon that makes interacting with AppImage files more seamless.
|
||||
- Once installed, copy the downloaded `Zoo Modeling App-{version}-{arch}-linux.AppImage` to the directory of your choice, for instance `~/Applications`.
|
||||
|
||||
- `appimaged` should automatically find it and make it executable. If not, run:
|
||||
```bash
|
||||
chmod a+x ~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
||||
|
||||
3. You can double-click on the AppImage to run it, or in a terminal with this command:
|
||||
```bash
|
||||
~/Applications/Zoo\ Modeling\ App-{version}-{arch}-linux.AppImage
|
||||
```
|
16
README.md
@ -99,7 +99,7 @@ yarn tron:start
|
||||
|
||||
This will start the application and hot-reload on changes.
|
||||
|
||||
Devtools can be opened with the usual Cmd/Ctrl-Shift-I.
|
||||
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
||||
|
||||
To build, run `yarn tron:package`.
|
||||
|
||||
@ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new
|
||||
|
||||
#### 2. Push a new tag
|
||||
|
||||
Create a new tag and push it to the repo (eg. `v0.28.0` for `$VERSION`)
|
||||
Create a new tag and push it to the repo. The `semantic-release.sh` script will automatically bump the minor part, which we use the most. For instance going from `v0.27.0` to `v0.28.0`.
|
||||
|
||||
```
|
||||
VERSION=$(./scripts/semantic-release.sh)
|
||||
@ -146,16 +146,14 @@ git push origin --tags
|
||||
|
||||
This will trigger the `build-apps` workflow, set the version, build & sign the apps, and generate release files as well as updater-test artifacts.
|
||||
|
||||
Once the workflow succeeds, a draft release will be created at https://github.com/KittyCAD/modeling-app/releases.
|
||||
The workflow should be listed right away [in this list](https://github.com/KittyCAD/modeling-app/actions/workflows/build-apps.yml?query=event%3Apush)).
|
||||
|
||||
#### 3. Manually test artifacts from the Cut Release PR
|
||||
#### 3. Manually test artifacts
|
||||
|
||||
##### Release builds
|
||||
|
||||
The release builds can be found under the `out-{arch}-{platform}` zip files, at the very bottom of the `build-apps` summary page for the workflow (triggered by the tag in 2.).
|
||||
|
||||
Alternatively, the draft release will also include these builds.
|
||||
|
||||
Manually test against this [list](https://github.com/KittyCAD/modeling-app/issues/3588) across Windows, MacOS, Linux and posting results as comments in the issue.
|
||||
|
||||
##### Updater-test builds
|
||||
@ -178,9 +176,11 @@ If the prompt doesn't show up, start the app in command line to grab the electro
|
||||
|
||||
#### 4. Publish the release
|
||||
|
||||
Head over to https://github.com/KittyCAD/modeling-app/releases, paste in the changelog discussed in the issue, and publish the draft release created by the `build-apps` workflow from step 2.
|
||||
Head over to https://github.com/KittyCAD/modeling-app/releases/new, pick the newly created tag and type it in the _Release title_ field as well.
|
||||
|
||||
A new Action kicks in at https://github.com/KittyCAD/modeling-app/actions, which can be found under `release` event filter. On success, the files will be uploaded to the public bucket and the announcement on Discord will be sent.
|
||||
Hit _Generate release notes_ as a starting point to discuss the changelog in the issue. Once done, make sure _Set as the latest release_ is checked, and hit _Publish release_.
|
||||
|
||||
A new `publish-apps-release` will kick in and you should be able to find it [here](https://github.com/KittyCAD/modeling-app/actions?query=event%3Arelease). On success, the files will be uploaded to the public bucket as well as to the GitHub release, and the announcement on Discord will be sent.
|
||||
|
||||
#### 5. Close the issue
|
||||
|
||||
|
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 119 KiB |
Before Width: | Height: | Size: 157 KiB After Width: | Height: | Size: 259 KiB |
BIN
assets/icon.ico
Before Width: | Height: | Size: 183 KiB After Width: | Height: | Size: 114 KiB |
BIN
assets/icon.png
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
@ -22,3 +22,5 @@ once fixed in engine will just start working here with no language changes.
|
||||
|
||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
|
239
docs/kcl/appearance.md
Normal file
@ -19,6 +19,7 @@ layout: manual
|
||||
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
||||
* [`angledLineToX`](kcl/angledLineToX)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`appearance`](kcl/appearance)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arcTo`](kcl/arcTo)
|
||||
* [`asin`](kcl/asin)
|
||||
@ -101,6 +102,7 @@ layout: manual
|
||||
* [`startProfileAt`](kcl/startProfileAt)
|
||||
* [`startSketchAt`](kcl/startSketchAt)
|
||||
* [`startSketchOn`](kcl/startSketchOn)
|
||||
* [`sweep`](kcl/sweep)
|
||||
* [`tan`](kcl/tan)
|
||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
|
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
||||
```js
|
||||
r = 10 // radius
|
||||
// Call `map`, using an anonymous function instead of a named one.
|
||||
circles = map([1..3], (id) {
|
||||
circles = map([1..3], fn(id) {
|
||||
return startSketchOn("XY")
|
||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||
})
|
||||
|
@ -12,7 +12,7 @@ to other modules.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
export fn increment(x) {
|
||||
return x + 1
|
||||
}
|
||||
```
|
||||
@ -37,11 +37,11 @@ Multiple functions can be exported in a file.
|
||||
|
||||
```
|
||||
// util.kcl
|
||||
export fn increment = (x) => {
|
||||
export fn increment(x) {
|
||||
return x + 1
|
||||
}
|
||||
|
||||
export fn decrement = (x) => {
|
||||
export fn decrement(x) {
|
||||
return x - 1
|
||||
}
|
||||
```
|
||||
|
@ -61,7 +61,7 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
||||
// an anonymous `add` function as its parameter, instead of declaring a
|
||||
// named function outside.
|
||||
arr = [1, 2, 3]
|
||||
sum = reduce(arr, 0, (i, result_so_far) {
|
||||
sum = reduce(arr, 0, fn(i, result_so_far) {
|
||||
return i + result_so_far
|
||||
})
|
||||
|
||||
@ -84,7 +84,7 @@ fn decagon(radius) {
|
||||
// Use a `reduce` to draw the remaining decagon sides.
|
||||
// For each number in the array 1..10, run the given function,
|
||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, (i, partialDecagon) {
|
||||
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||
// Draw one edge of the decagon.
|
||||
x = cos(stepAngle * i) * radius
|
||||
y = sin(stepAngle * i) * radius
|
||||
|
80990
docs/kcl/std.json
55
docs/kcl/sweep.md
Normal file
@ -54,7 +54,7 @@ We also have support for defining your own functions. Functions can take in any
|
||||
type of argument. Below is an example of the syntax:
|
||||
|
||||
```
|
||||
fn myFn = (x) => {
|
||||
fn myFn(x) {
|
||||
return x
|
||||
}
|
||||
```
|
||||
@ -118,7 +118,7 @@ use the tag `rectangleSegmentA001` in any function or expression in the file.
|
||||
However if the code was written like this:
|
||||
|
||||
```
|
||||
fn rect = (origin) => {
|
||||
fn rect(origin) {
|
||||
return startSketchOn('XZ')
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||
@ -146,7 +146,7 @@ Tags are accessible through the sketch group they are declared in.
|
||||
For example the following code works.
|
||||
|
||||
```
|
||||
fn rect = (origin) => {
|
||||
fn rect(origin) {
|
||||
return startSketchOn('XZ')
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||
|
23
docs/kcl/types/AppearanceData.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "AppearanceData"
|
||||
excerpt: "Data for appearance."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for appearance.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `color` |`string`| Color of the new material, a hex string like "#ff0000". | No |
|
||||
| `metalness` |`number` (**maximum:** 100.0)| Metalness of the new material, a percentage like 95.7. | No |
|
||||
| `roughness` |`number` (**maximum:** 100.0)| Roughness of the new material, a percentage like 95.7. | No |
|
||||
|
||||
|
@ -1,161 +0,0 @@
|
||||
---
|
||||
title: "BinaryOperator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Add two numbers.
|
||||
|
||||
**enum:** `+`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Subtract two numbers.
|
||||
|
||||
**enum:** `-`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Multiply two numbers.
|
||||
|
||||
**enum:** `*`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Divide two numbers.
|
||||
|
||||
**enum:** `/`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Modulo two numbers.
|
||||
|
||||
**enum:** `%`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Raise a number to a power.
|
||||
|
||||
**enum:** `^`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Are two numbers equal?
|
||||
|
||||
**enum:** `==`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Are two numbers not equal?
|
||||
|
||||
**enum:** `!=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left greater than right
|
||||
|
||||
**enum:** `>`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left greater than or equal to right
|
||||
|
||||
**enum:** `>=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left less than right
|
||||
|
||||
**enum:** `<`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Is left less than or equal to right
|
||||
|
||||
**enum:** `<=`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,161 +0,0 @@
|
||||
---
|
||||
title: "BinaryPart"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
|
||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `optional` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
|
||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,97 +0,0 @@
|
||||
---
|
||||
title: "BodyItem"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ImportStatement`| | No |
|
||||
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
|
||||
| `path` |`string`| | No |
|
||||
| `raw_path` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ExpressionStatement`| | No |
|
||||
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `VariableDeclaration`| | No |
|
||||
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
|
||||
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
|
||||
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ReturnStatement`| | No |
|
||||
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
---
|
||||
title: "CommentStyle"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Like // foo
|
||||
|
||||
**enum:** `line`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Like /* foo */
|
||||
|
||||
**enum:** `block`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "ElseIf"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "EnvironmentRef"
|
||||
excerpt: "An index pointing to an environment."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An index pointing to an environment.
|
||||
|
||||
**Type:** `integer` (`uint`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,318 +0,0 @@
|
||||
---
|
||||
title: "Expr"
|
||||
excerpt: "An expression can be evaluated to yield a single KCL value."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
An expression can be evaluated to yield a single KCL value.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `BinaryExpression`| | No |
|
||||
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
|
||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
||||
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CallExpression`| | No |
|
||||
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `optional` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeExpression`| | No |
|
||||
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `PipeSubstitution`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayExpression`| | No |
|
||||
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayRangeExpression`| | No |
|
||||
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ObjectExpression`| | No |
|
||||
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `UnaryExpression`| | No |
|
||||
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `IfExpression`| | No |
|
||||
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
|
||||
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
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).
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `None`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "FunctionExpression"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
|
||||
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: "Identifier"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "ImportItem"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
|
||||
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,16 +0,0 @@
|
||||
---
|
||||
title: "ItemVisibility"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**enum:** `default`, `export`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -12,5 +12,10 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
||||
|
@ -317,7 +317,6 @@ Data for an imported geometry.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Function`| | No |
|
||||
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
|
||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
@ -1,56 +0,0 @@
|
||||
---
|
||||
title: "LiteralIdentifier"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Literal`| | No |
|
||||
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
|
||||
| `raw` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,47 +0,0 @@
|
||||
---
|
||||
title: "LiteralValue"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts any of the following:**
|
||||
|
||||
|
||||
**Type:** `number` (`double`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `string`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `boolean`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,57 +0,0 @@
|
||||
---
|
||||
title: "MemberObject"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `MemberExpression`| | No |
|
||||
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
|
||||
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
|
||||
| `computed` |`boolean`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `name` |`string`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,22 +0,0 @@
|
||||
---
|
||||
title: "NonCodeMeta"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `nonCodeNodes` |`object`| | No |
|
||||
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: "NonCodeNode"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,86 +0,0 @@
|
||||
---
|
||||
title: "NonCodeValue"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
An inline comment. Here are examples: `1 + 1 // This is an inline comment`. `1 + 1 /* Here's another */`.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `inlineComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
A block comment. An example of this is the following: ```python,no_run /* This is a block comment */ 1 + 1 ``` Now this is important. The block comment is attached to the next line. This is always the case. Also the block comment doesn't have a new line above it. If it did it would be a `NewLineBlockComment`.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `blockComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
A block comment that has a new line above it. The user explicitly added a new line above the block comment.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `newLineBlockComment`| | No |
|
||||
| `value` |`string`| | No |
|
||||
| `style` |[`CommentStyle`](/docs/kcl/types/CommentStyle)| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `newLine`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "ObjectProperty"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
|
||||
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: "Parameter"
|
||||
excerpt: "Parameter of a KCL function."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Parameter of a KCL function.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No |
|
||||
| `optional` |`boolean`| Is the parameter optional? | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
@ -1,26 +0,0 @@
|
||||
---
|
||||
title: "Program"
|
||||
excerpt: "A KCL program top level, or function body."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A KCL program top level, or function body.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
|
||||
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
|
||||
| `shebang` |[`Shebang`](/docs/kcl/types/Shebang)| | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
---
|
||||
title: "Shebang"
|
||||
excerpt: "A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```"
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A shebang. This is a special type of comment that is at the top of the file. It looks like this: ```python,no_run #!/usr/bin/env python ```
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `content` |`string`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
23
docs/kcl/types/SweepData.md
Normal file
@ -0,0 +1,23 @@
|
||||
---
|
||||
title: "SweepData"
|
||||
excerpt: "Data for a sweep."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Data for a sweep.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
||||
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "Uint"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `integer` (`uint32`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
---
|
||||
title: "UnaryOperator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Negate a number.
|
||||
|
||||
**enum:** `-`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Negate a boolean.
|
||||
|
||||
**enum:** `!`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,24 +0,0 @@
|
||||
---
|
||||
title: "VariableDeclarator"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
|
||||
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
| `start` |`integer`| | No |
|
||||
| `end` |`integer`| | No |
|
||||
|
||||
|
@ -1,41 +0,0 @@
|
||||
---
|
||||
title: "VariableKind"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
Declare a named constant.
|
||||
|
||||
**enum:** `const`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
Declare a function.
|
||||
|
||||
**enum:** `fn`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -458,8 +458,8 @@ test.describe('Editor tests', () => {
|
||||
|
||||
/* add the following code to the editor ($ error is not a valid line)
|
||||
$ error
|
||||
const topAng = 30
|
||||
const bottomAng = 25
|
||||
topAng = 30
|
||||
bottomAng = 25
|
||||
*/
|
||||
await u.codeLocator.click()
|
||||
await page.keyboard.type('$ error')
|
||||
@ -474,12 +474,14 @@ test.describe('Editor tests', () => {
|
||||
await page.keyboard.type('bottomAng = 25')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// error in guter
|
||||
// error in gutter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
||||
await expect(
|
||||
page.getByText('Tag names must not be empty').first()
|
||||
).toBeVisible()
|
||||
|
||||
// select the line that's causing the error and delete it
|
||||
await page.getByText('$ error').click()
|
||||
@ -518,7 +520,10 @@ test.describe('Editor tests', () => {
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('error with 2 source ranges gets 2 diagnostics', async ({ page }) => {
|
||||
// TODO currently multiple source ranges are not supported
|
||||
test.skip('error with 2 source ranges gets 2 diagnostics', async ({
|
||||
page,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
|
@ -45,7 +45,6 @@ test.describe('integrations tests', () => {
|
||||
{
|
||||
title: 'test-sample',
|
||||
fileCount: 1,
|
||||
folderCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
@ -233,7 +232,6 @@ test.describe('when using the file tree to', () => {
|
||||
{
|
||||
title: projectName,
|
||||
fileCount: 2,
|
||||
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
|
@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
|
||||
interface ProjectCardState {
|
||||
title: string
|
||||
fileCount: number
|
||||
folderCount: number
|
||||
}
|
||||
|
||||
interface HomePageState {
|
||||
@ -61,15 +60,13 @@ export class HomePageFixture {
|
||||
const projectCards = await this.projectCard.all()
|
||||
const projectCardStates: Array<ProjectCardState> = []
|
||||
for (const projectCard of projectCards) {
|
||||
const [title, fileCount, folderCount] = await Promise.all([
|
||||
const [title, fileCount] = await Promise.all([
|
||||
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
||||
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
||||
Number(await projectCard.locator(this.projectCardFolder).textContent()),
|
||||
])
|
||||
projectCardStates.push({
|
||||
title: title,
|
||||
fileCount,
|
||||
folderCount,
|
||||
})
|
||||
}
|
||||
return projectCardStates
|
||||
|
@ -6,6 +6,8 @@ export class ToolbarFixture {
|
||||
public page: Page
|
||||
|
||||
extrudeButton!: Locator
|
||||
loftButton!: Locator
|
||||
shellButton!: Locator
|
||||
offsetPlaneButton!: Locator
|
||||
startSketchBtn!: Locator
|
||||
lineBtn!: Locator
|
||||
@ -26,6 +28,8 @@ export class ToolbarFixture {
|
||||
reConstruct = (page: Page) => {
|
||||
this.page = page
|
||||
this.extrudeButton = page.getByTestId('extrude')
|
||||
this.loftButton = page.getByTestId('loft')
|
||||
this.shellButton = page.getByTestId('shell')
|
||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||
this.startSketchBtn = page.getByTestId('sketch')
|
||||
this.lineBtn = page.getByTestId('line')
|
||||
|
@ -677,3 +677,259 @@ test(`Offset plane point-and-click`, async ({
|
||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
||||
const loftPointAndClickCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
]
|
||||
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
||||
app,
|
||||
page,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnSketch1] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnSketch2] = scene.makeMouseHelpers(
|
||||
testPoint.x,
|
||||
testPoint.y + 80
|
||||
)
|
||||
const loftDeclaration = 'loft001 = loft([sketch001, sketch002])'
|
||||
|
||||
await test.step(`Look for the white of the sketch001 shape`, async () => {
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
|
||||
async function selectSketches() {
|
||||
await clickOnSketch1()
|
||||
await page.keyboard.down('Shift')
|
||||
await clickOnSketch2()
|
||||
await app.page.waitForTimeout(500)
|
||||
await page.keyboard.up('Shift')
|
||||
}
|
||||
|
||||
if (!shouldPreselect) {
|
||||
await test.step(`Go through the command bar flow without preselected sketches`, async () => {
|
||||
await toolbar.loftButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Selection: '' },
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await selectSketches()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Selection: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
} else {
|
||||
await test.step(`Preselect the two sketches`, async () => {
|
||||
await selectSketches()
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow with preselected sketches`, async () => {
|
||||
await toolbar.loftButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Selection: '2 faces' },
|
||||
commandName: 'Loft',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
}
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(loftDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [loftDeclaration],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const shellPointAndClickCapCases = [
|
||||
{ shouldPreselect: true },
|
||||
{ shouldPreselect: false },
|
||||
]
|
||||
shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
||||
test(`Shell point-and-click cap (preselected sketches: ${shouldPreselect})`, async ({
|
||||
app,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
extrude001 = extrude(30, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end'], thickness = 5 }, extrude001)"
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([127, 127, 127], testPoint, 15)
|
||||
})
|
||||
|
||||
if (!shouldPreselect) {
|
||||
await test.step(`Go through the command bar flow without preselected faces`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await app.page.waitForTimeout(500)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
} else {
|
||||
await test.step(`Preselect the cap`, async () => {
|
||||
await clickOnCap()
|
||||
await app.page.waitForTimeout(500)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow with a preselected face (cap)`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
}
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [shellDeclaration],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([146, 146, 146], testPoint, 15)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test('Shell point-and-click wall', async ({
|
||||
app,
|
||||
page,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-20, 20], %)
|
||||
|> xLine(40, %)
|
||||
|> yLine(-60, %)
|
||||
|> xLine(-40, %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(40, sketch001)
|
||||
`
|
||||
await app.initialise(initialCode)
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 580, y: 180 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const [clickOnWall] = scene.makeMouseHelpers(testPoint.x, testPoint.y + 70)
|
||||
const mutatedCode = 'xLine(-40, %, $seg01)'
|
||||
const shellDeclaration =
|
||||
"shell001 = shell({ faces = ['end', seg01], thickness = 5}, extrude001)"
|
||||
const formattedOutLastLine = '}, extrude001)'
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow, selecting a wall and keeping default thickness`, async () => {
|
||||
await toolbar.shellButton.click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'selection',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Selection: '',
|
||||
Thickness: '',
|
||||
},
|
||||
highlightedHeaderArg: 'selection',
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await clickOnCap()
|
||||
await page.keyboard.down('Shift')
|
||||
await clickOnWall()
|
||||
await app.page.waitForTimeout(500)
|
||||
await page.keyboard.up('Shift')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
Selection: '1 cap, 1 face',
|
||||
Thickness: '5',
|
||||
},
|
||||
commandName: 'Shell',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
})
|
||||
|
||||
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
|
||||
await editor.expectEditor.toContain(mutatedCode)
|
||||
await editor.expectEditor.toContain(shellDeclaration)
|
||||
await editor.expectState({
|
||||
diagnostics: [],
|
||||
activeLines: [formattedOutLastLine],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await scene.expectPixelColor([49, 49, 49], testPoint, 15)
|
||||
})
|
||||
})
|
||||
|
@ -136,6 +136,335 @@ test(
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in different project with errors, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const errorDir = join(dir, 'broken-code')
|
||||
await fsp.mkdir(errorDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('broken-code-test.kcl'),
|
||||
join(errorDir, 'main.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening broken code project should clear the scene and show the error', async () => {
|
||||
// Go back home.
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
|
||||
await page.getByText('broken-code').click()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
const crypticErrorText = `Expected a tag declarator`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
|
||||
// black pixel means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in different project that is empty, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
const emptyDir = join(dir, 'empty')
|
||||
await fsp.mkdir(emptyDir, { recursive: true })
|
||||
await fsp.writeFile(join(emptyDir, 'main.kcl'), '')
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await test.step('Clicking the logo takes us back to the projects page / home', async () => {
|
||||
await page.getByTestId('app-logo').click()
|
||||
|
||||
await expect(page.getByRole('link', { name: 'bracket' })).toBeVisible()
|
||||
await expect(page.getByText('empty')).toBeVisible()
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
await expect(page.getByText('New Project')).toBeVisible()
|
||||
})
|
||||
await test.step('opening empty code project should clear the scene', async () => {
|
||||
// Go back home.
|
||||
await expect(page.getByText('empty')).toBeVisible()
|
||||
|
||||
await page.getByText('empty').click()
|
||||
|
||||
// Ensure the code is empty.
|
||||
await expect(u.codeLocator).toContainText('')
|
||||
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
|
||||
|
||||
// planes colors means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open empty file, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
|
||||
await fsp.writeFile(join(bracketDir, 'empty.kcl'), '')
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step('creating a empty file should clear the scene', async () => {
|
||||
// open the file pane.
|
||||
await page.getByTestId('files-pane-button').click()
|
||||
|
||||
// OPen the other file.
|
||||
const file = page.getByRole('button', { name: 'empty.kcl' })
|
||||
await expect(file).toBeVisible()
|
||||
|
||||
await file.click()
|
||||
|
||||
// planes colors means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [92, 53, 53]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
|
||||
// Ensure the code is empty.
|
||||
await expect(u.codeLocator).toContainText('')
|
||||
expect(u.codeLocator.innerHTML.length).toBeLessThan(2)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||
{ tag: '@electron' },
|
||||
async ({ browserName }, testInfo) => {
|
||||
const { electronApp, page } = await setupElectron({
|
||||
testInfo,
|
||||
folderSetupFn: async (dir) => {
|
||||
const bracketDir = join(dir, 'bracket')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.copyFile(
|
||||
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
|
||||
join(bracketDir, 'main.kcl')
|
||||
)
|
||||
await fsp.copyFile(
|
||||
executorInputPath('broken-code-test.kcl'),
|
||||
join(bracketDir, 'broken-code-test.kcl')
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const u = await getUtils(page)
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
const pointOnModel = { x: 630, y: 280 }
|
||||
|
||||
await test.step('Opening the bracket project should load the stream', async () => {
|
||||
// expect to see the text bracket
|
||||
await expect(page.getByText('bracket')).toBeVisible()
|
||||
|
||||
await page.getByText('bracket').click()
|
||||
|
||||
await expect(page.getByTestId('loading')).toBeAttached()
|
||||
await expect(page.getByTestId('loading')).not.toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
// gray at this pixel means the stream has loaded in the most
|
||||
// user way we can verify it (pixel color)
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [85, 85, 85]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
await test.step('opening broken code file should clear the scene and show the error', async () => {
|
||||
// open the file pane.
|
||||
await page.getByTestId('files-pane-button').click()
|
||||
|
||||
// OPen the other file.
|
||||
const file = page.getByRole('button', { name: 'broken-code-test.kcl' })
|
||||
await expect(file).toBeVisible()
|
||||
|
||||
await file.click()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
const crypticErrorText = `Expected a tag declarator`
|
||||
await expect(page.getByText(crypticErrorText).first()).toBeVisible()
|
||||
|
||||
// black pixel means the scene has been cleared.
|
||||
await expect
|
||||
.poll(() => u.getGreatestPixDiff(pointOnModel, [30, 30, 30]), {
|
||||
timeout: 10_000,
|
||||
})
|
||||
.toBeLessThan(15)
|
||||
})
|
||||
|
||||
await electronApp.close()
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'when code with error first loads you get errors in console',
|
||||
{ tag: '@electron' },
|
||||
|
@ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0])
|
||||
const u = await getUtils(page)
|
||||
|
||||
// Constants and locators
|
||||
const planeColor: [number, number, number] = [170, 220, 170]
|
||||
const planeColor: [number, number, number] = [161, 220, 155]
|
||||
const bgColor: [number, number, number] = [27, 27, 27]
|
||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||
|
@ -7,6 +7,8 @@ try {
|
||||
.split('\n')
|
||||
.filter((line) => line && line.length > 1)
|
||||
.forEach((line) => {
|
||||
// Allow line comments.
|
||||
if (line.trimStart().startsWith('#')) return
|
||||
const [key, value] = line.split('=')
|
||||
// prefer env vars over secrets file
|
||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||
|
@ -943,6 +943,110 @@ sketch002 = startSketchOn(extrude001, 'END')
|
||||
`.replace(/\s/g, '')
|
||||
)
|
||||
})
|
||||
|
||||
/* TODO: once we fix bug turn on.
|
||||
test('empty-scene default-planes act as expected when spaces in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})
|
||||
|
||||
test('empty-scene default-planes act as expected when only code comments in file', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
// Fill with spaces
|
||||
await u.codeLocator.fill(`// this is a code comments ya nerds
|
||||
`)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
|
||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
// color should not change for having been hovered
|
||||
expect(
|
||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||
).toBeLessThan(8)
|
||||
})*/
|
||||
|
||||
test('empty-scene default-planes act as expected', async ({
|
||||
page,
|
||||
browserName,
|
||||
|
@ -950,7 +950,75 @@ test(
|
||||
|
||||
test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
// FIXME: Skip on macos its being weird.
|
||||
test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
// test.skip(process.platform === 'darwin', 'Skip on macos')
|
||||
|
||||
test('Grid turned off to on via command bar', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
const stream = page.getByTestId('stream')
|
||||
const mask = [
|
||||
page.locator('#app-header'),
|
||||
page.locator('#sidebar-top-ribbon'),
|
||||
page.locator('#sidebar-bottom-ribbon'),
|
||||
]
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
await page.goto('/')
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
// wait for execution done
|
||||
await expect(
|
||||
page.locator('[data-message-type="execution-done"]')
|
||||
).toHaveCount(1)
|
||||
await u.closeDebugPanel()
|
||||
await u.closeKclCodePanel()
|
||||
// TODO: Find a way to truly know that the objects have finished
|
||||
// rendering, because an execution-done message is not sufficient.
|
||||
await page.waitForTimeout(1000)
|
||||
|
||||
// Open the command bar.
|
||||
await page
|
||||
.getByRole('button', { name: 'Commands', exact: false })
|
||||
.or(page.getByRole('button', { name: '⌘K' }))
|
||||
.click()
|
||||
const commandName = 'show scale grid'
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: commandName,
|
||||
exact: false,
|
||||
})
|
||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
// This selector changes after we set the setting
|
||||
await cmdSearchBar.fill(commandName)
|
||||
await expect(commandOption).toBeVisible()
|
||||
await commandOption.click()
|
||||
|
||||
const toggleInput = page.getByPlaceholder('Off')
|
||||
await expect(toggleInput).toBeVisible()
|
||||
await expect(toggleInput).toBeFocused()
|
||||
|
||||
// Select On
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'Off' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active selected'
|
||||
)
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await expect(page.getByRole('option', { name: 'On' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set show scale grid to "true" as a user default`)
|
||||
).toBeVisible()
|
||||
|
||||
await expect(stream).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
mask,
|
||||
})
|
||||
})
|
||||
|
||||
test('Grid turned off', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
@ -1096,3 +1164,109 @@ test.fixme('theme persists', async ({ page, context }) => {
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||
test('code color goober', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Create a pipe using a sweep.
|
||||
|
||||
// Create a path for the sweep.
|
||||
sweepPath = startSketchOn('XZ')
|
||||
|> startProfileAt([0.05, 0.05], %)
|
||||
|> line([0, 7], %)
|
||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||
|> line([-3, 0], %)
|
||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||
|> line([0, 7], %)
|
||||
|
||||
sweepSketch = startSketchOn('XY')
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
|
||||
test('code color goober opening window', async ({ page, context }) => {
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`// Create a pipe using a sweep.
|
||||
|
||||
// Create a path for the sweep.
|
||||
sweepPath = startSketchOn('XZ')
|
||||
|> startProfileAt([0.05, 0.05], %)
|
||||
|> line([0, 7], %)
|
||||
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||
|> line([-3, 0], %)
|
||||
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||
|> line([0, 7], %)
|
||||
|
||||
sweepSketch = startSketchOn('XY')
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
||||
|
||||
// Click the color widget
|
||||
await page.locator('.cm-css-color-picker-wrapper input').click()
|
||||
|
||||
await expect(
|
||||
page,
|
||||
'expect small color widget to have window open'
|
||||
).toHaveScreenshot({
|
||||
maxDiffPixels: 100,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
@ -14,7 +14,7 @@ export const TEST_SETTINGS = {
|
||||
},
|
||||
modeling: {
|
||||
defaultUnit: 'in',
|
||||
mouseControls: 'KittyCAD',
|
||||
mouseControls: 'Zoo',
|
||||
cameraProjection: 'perspective',
|
||||
showDebugPanel: true,
|
||||
},
|
||||
|
@ -479,4 +479,26 @@ test.describe('Testing Camera Movement', () => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
test('Right-click opens context menu when not dragged', async ({ page }) => {
|
||||
const u = await getUtils(page)
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await test.step(`The menu should not show if we drag the mouse`, async () => {
|
||||
await page.mouse.move(900, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(900, 300)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
|
||||
await expect(page.getByTestId('view-controls-menu')).not.toBeVisible()
|
||||
})
|
||||
|
||||
await test.step(`The menu should show if we don't drag the mouse`, async () => {
|
||||
await page.mouse.move(900, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.up({ button: 'right' })
|
||||
|
||||
await expect(page.getByTestId('view-controls-menu')).toBeVisible()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -26,7 +26,17 @@ test.describe('Testing constraints', () => {
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
// constants and locators
|
||||
const lengthValue = {
|
||||
old: '20',
|
||||
new: '25',
|
||||
}
|
||||
const cmdBarKclInput = page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.getByRole('textbox')
|
||||
const cmdBarSubmitButton = page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
@ -36,26 +46,26 @@ test.describe('Testing constraints', () => {
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// Click the line of code for line.
|
||||
await page.getByText(`line([0, 20], %)`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
||||
// TODO remove this and reinstate `await topHorzSegmentClick()`
|
||||
await page.getByText(`line([0, ${lengthValue.old}], %)`).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// enter sketch again
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
const startXPx = 500
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.click(834, 244)
|
||||
await page.keyboard.up('Shift')
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'dimension Length', exact: true })
|
||||
.click()
|
||||
await page.getByText('Add constraining value').click()
|
||||
await expect(cmdBarKclInput).toHaveText('20')
|
||||
await cmdBarKclInput.fill(lengthValue.new)
|
||||
await expect(
|
||||
page.getByText(`Can't calculate`),
|
||||
`Something went wrong with the KCL expression evaluation`
|
||||
).not.toBeVisible()
|
||||
await cmdBarSubmitButton.click()
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||
`length001 = ${lengthValue.new}sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||
)
|
||||
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
@ -66,7 +76,6 @@ test.describe('Testing constraints', () => {
|
||||
await page.waitForTimeout(500) // wait for animation
|
||||
|
||||
// Exit sketch
|
||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
@ -524,7 +533,7 @@ part002 = startSketchOn('XZ')
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Test Angle/Length constraint single selection', () => {
|
||||
test.describe('Test Angle constraint single selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Angle - Add variable',
|
||||
@ -538,18 +547,6 @@ part002 = startSketchOn('XZ')
|
||||
constraint: 'angle',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
{
|
||||
testName: 'Length - Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'length',
|
||||
value: '83, length001',
|
||||
},
|
||||
{
|
||||
testName: 'Length - No variable',
|
||||
addVariable: false,
|
||||
constraint: 'length',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
@ -608,6 +605,90 @@ part002 = startSketchOn('XZ')
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Test Length constraint single selection', () => {
|
||||
const cases = [
|
||||
{
|
||||
testName: 'Length - Add variable',
|
||||
addVariable: true,
|
||||
constraint: 'length',
|
||||
value: '83, length001',
|
||||
},
|
||||
{
|
||||
testName: 'Length - No variable',
|
||||
addVariable: false,
|
||||
constraint: 'length',
|
||||
value: '83, 78.33',
|
||||
},
|
||||
] as const
|
||||
for (const { testName, addVariable, value, constraint } of cases) {
|
||||
test(`${testName}`, async ({ page }) => {
|
||||
// constants and locators
|
||||
const cmdBarKclInput = page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.getByRole('textbox')
|
||||
const cmdBarKclVariableNameInput =
|
||||
page.getByPlaceholder('Variable name')
|
||||
const cmdBarSubmitButton = page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`yo = 5
|
||||
part001 = startSketchOn('XZ')
|
||||
|> startProfileAt([-7.54, -26.74], %)
|
||||
|> line([74.36, 130.4], %)
|
||||
|> line([78.92, -120.11], %)
|
||||
|> line([9.16, 77.79], %)
|
||||
|> line([51.19, 48.97], %)
|
||||
part002 = startSketchOn('XZ')
|
||||
|> startProfileAt([299.05, 231.45], %)
|
||||
|> xLine(-425.34, %, $seg_what)
|
||||
|> yLine(-264.06, %)
|
||||
|> xLine(segLen(seg_what), %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)`
|
||||
)
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
await u.waitForAuthSkipAppStart()
|
||||
|
||||
await page.getByText('line([74.36, 130.4], %)').click()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
|
||||
const line3 = await u.getSegmentBodyCoords(
|
||||
`[data-overlay-index="${2}"]`
|
||||
)
|
||||
|
||||
await page.mouse.click(line3.x, line3.y)
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'Length: open menu',
|
||||
})
|
||||
.click()
|
||||
await page.getByTestId('dropdown-constraint-' + constraint).click()
|
||||
|
||||
if (!addVariable) {
|
||||
await test.step(`Clear the variable input`, async () => {
|
||||
await cmdBarKclVariableNameInput.clear()
|
||||
await cmdBarKclVariableNameInput.press('Backspace')
|
||||
})
|
||||
}
|
||||
await expect(cmdBarKclInput).toHaveText('78.33')
|
||||
await cmdBarSubmitButton.click()
|
||||
|
||||
const changedCode = `|> angledLine([${value}], %)`
|
||||
await expect(page.locator('.cm-content')).toContainText(changedCode)
|
||||
// checking active assures the cursor is where it should be
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(changedCode)
|
||||
|
||||
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||
})
|
||||
}
|
||||
})
|
||||
test.describe('Many segments - no modal constraints', () => {
|
||||
const cases = [
|
||||
{
|
||||
@ -868,6 +949,15 @@ part002 = startSketchOn('XZ')
|
||||
|> line([3.13, -2.4], %)`
|
||||
)
|
||||
})
|
||||
|
||||
// constants and locators
|
||||
const cmdBarKclInput = page
|
||||
.getByTestId('cmd-bar-arg-value')
|
||||
.getByRole('textbox')
|
||||
const cmdBarSubmitButton = page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
|
||||
@ -928,8 +1018,8 @@ part002 = startSketchOn('XZ')
|
||||
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||
await page.getByTestId('dropdown-constraint-length').click()
|
||||
|
||||
await page.getByLabel('length Value').fill('10')
|
||||
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
||||
await cmdBarKclInput.fill('10')
|
||||
await cmdBarSubmitButton.click()
|
||||
|
||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||
|
@ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => {
|
||||
y: screenHeight * 0.4,
|
||||
}
|
||||
const backgroundColor: [number, number, number] = [29, 29, 29]
|
||||
const xzPlaneColor: [number, number, number] = [50, 50, 99]
|
||||
const xzPlaneColor: [number, number, number] = [82, 55, 96]
|
||||
const locationToHaveColor = async (color: [number, number, number]) => {
|
||||
return u.getGreatestPixDiff(checkedScreenLocation, color)
|
||||
}
|
||||
|
@ -91,7 +91,14 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await unconstrainedLocator.click()
|
||||
await page.getByText('Add variable').click()
|
||||
await expect(
|
||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||
).toBeFocused()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('.cm-content')).toContainText(expectFinal)
|
||||
}
|
||||
|
||||
@ -151,7 +158,14 @@ test.describe('Testing segment overlays', () => {
|
||||
await page.getByTestId('constraint-symbol-popover').count()
|
||||
).toBeGreaterThan(0)
|
||||
await unconstrainedLocator.click()
|
||||
await page.getByText('Add variable').click()
|
||||
await expect(
|
||||
page.getByTestId('cmd-bar-arg-value').getByRole('textbox')
|
||||
).toBeFocused()
|
||||
await page
|
||||
.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
})
|
||||
.click()
|
||||
await expect(page.locator('.cm-content')).toContainText(
|
||||
expectAfterUnconstrained
|
||||
)
|
||||
|
@ -1,20 +1,9 @@
|
||||
import type { ForgeConfig } from '@electron-forge/shared-types'
|
||||
import { MakerSquirrel } from '@electron-forge/maker-squirrel'
|
||||
import { MakerZIP } from '@electron-forge/maker-zip'
|
||||
import { MakerDeb } from '@electron-forge/maker-deb'
|
||||
import { MakerRpm } from '@electron-forge/maker-rpm'
|
||||
import { VitePlugin } from '@electron-forge/plugin-vite'
|
||||
import { MakerWix, MakerWixConfig } from '@electron-forge/maker-wix'
|
||||
import { FusesPlugin } from '@electron-forge/plugin-fuses'
|
||||
import { FuseV1Options, FuseVersion } from '@electron/fuses'
|
||||
import path from 'path'
|
||||
|
||||
interface ExtendedMakerWixConfig extends MakerWixConfig {
|
||||
// see https://github.com/electron/forge/issues/3673
|
||||
// this is an undocumented property of electron-wix-msi
|
||||
associateExtensions?: string
|
||||
}
|
||||
|
||||
const rootDir = process.cwd()
|
||||
|
||||
const config: ForgeConfig = {
|
||||
@ -39,26 +28,7 @@ const config: ForgeConfig = {
|
||||
extendInfo: 'Info.plist', // Information for file associations.
|
||||
},
|
||||
rebuildConfig: {},
|
||||
makers: [
|
||||
new MakerSquirrel({
|
||||
setupIcon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||
}),
|
||||
new MakerWix({
|
||||
icon: path.resolve(rootDir, 'assets', 'icon.ico'),
|
||||
associateExtensions: 'kcl',
|
||||
} as ExtendedMakerWixConfig),
|
||||
new MakerZIP({}, ['darwin']),
|
||||
new MakerRpm({
|
||||
options: {
|
||||
icon: path.resolve(rootDir, 'assets', 'icon.png'),
|
||||
},
|
||||
}),
|
||||
new MakerDeb({
|
||||
options: {
|
||||
icon: path.resolve(rootDir, 'assets', 'icon.png'),
|
||||
},
|
||||
}),
|
||||
],
|
||||
makers: [],
|
||||
plugins: [
|
||||
new VitePlugin({
|
||||
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
||||
|
37
package.json
@ -39,7 +39,6 @@
|
||||
"chokidar": "^4.0.1",
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"electron-squirrel-startup": "^1.0.1",
|
||||
"electron-updater": "6.3.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
@ -69,7 +68,7 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite",
|
||||
"start": "vite --port=3000 --host=0.0.0.0",
|
||||
"start:prod": "vite preview --port=3000",
|
||||
"serve": "vite serve --port=3000",
|
||||
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
|
||||
@ -81,6 +80,7 @@
|
||||
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
|
||||
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
|
||||
"simpleserver:stop": "kill-port 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
@ -95,14 +95,14 @@
|
||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
"xstate:typegen": "yarn xstate typegen \"src/**/*.ts?(x)\"",
|
||||
"make:dev": "make dev",
|
||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||
"tron:start": "electron-forge start",
|
||||
"tron:package": "electron-forge package",
|
||||
"tron:make": "electron-forge make",
|
||||
"tron:publish": "electron-forge publish",
|
||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep=@electron",
|
||||
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||
"tronb:package": "electron-builder --config electron-builder.yml",
|
||||
@ -145,19 +145,13 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@electron-forge/cli": "^7.4.0",
|
||||
"@electron-forge/maker-deb": "^7.4.0",
|
||||
"@electron-forge/maker-rpm": "^7.4.0",
|
||||
"@electron-forge/maker-squirrel": "^7.4.0",
|
||||
"@electron-forge/maker-wix": "^7.5.0",
|
||||
"@electron-forge/maker-zip": "^7.5.0",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.4.0",
|
||||
"@electron-forge/plugin-fuses": "^7.4.0",
|
||||
"@electron-forge/plugin-vite": "^7.4.0",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@electron/rebuild": "^3.6.0",
|
||||
"@electron-forge/cli": "7.4.0",
|
||||
"@electron-forge/plugin-fuses": "7.4.0",
|
||||
"@electron-forge/plugin-vite": "7.4.0",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.1",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
"@playwright/test": "^1.46.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^15.0.2",
|
||||
@ -170,7 +164,7 @@
|
||||
"@types/pixelmatch": "^5.2.6",
|
||||
"@types/pngjs": "^6.0.4",
|
||||
"@types/react": "^18.3.4",
|
||||
"@types/react-dom": "^18.2.25",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@types/react-modal": "^3.16.3",
|
||||
"@types/three": "^0.163.0",
|
||||
"@types/ua-parser-js": "^0.7.39",
|
||||
@ -184,15 +178,15 @@
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"d3-force": "^3.0.0",
|
||||
"electron": "^32.1.2",
|
||||
"electron-builder": "^24.13.3",
|
||||
"electron-notarize": "^1.2.2",
|
||||
"electron": "32.1.2",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-notarize": "1.2.2",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||
"happy-dom": "^15.10.2",
|
||||
"happy-dom": "^15.11.7",
|
||||
"http-server": "^14.1.1",
|
||||
"husky": "^9.1.5",
|
||||
"kill-port": "^2.0.1",
|
||||
@ -207,12 +201,11 @@
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vite": "^5.4.6",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wasm-pack": "^0.13.0",
|
||||
"wasm-pack": "^0.13.1",
|
||||
"ws": "^8.17.0",
|
||||
"yarn": "^1.22.22"
|
||||
}
|
||||
|
5
scripts/get-nightly-changelog.sh
Executable file
@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
echo "## What's Changed"
|
||||
git log ${PREVIOUS_TAG}..HEAD --oneline --pretty=format:%s | grep -v Bump | awk '{print "* "toupper(substr($0,0,1))substr($0,2)}'
|
||||
echo ""
|
||||
echo "**Full Changelog**: https://github.com/KittyCAD/modeling-app/compare/${PREVIOUS_TAG}...${TAG}"
|
11
scripts/invalidate-files-bucket.sh
Executable file
@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
base_dir="/releases/modeling-app"
|
||||
if [[ $1 = "--nightly" ]]; then
|
||||
base_dir="/releases/modeling-app/nightly"
|
||||
fi
|
||||
|
||||
echo "Invalidating json and yml files at $base_dir in the download bucket"
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/last_download.json" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-linux-arm64.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest-mac.yml" --async
|
||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="$base_dir/latest.yml" --async
|
@ -105,7 +105,7 @@ export class CameraControls {
|
||||
pendingZoom: number | null = null
|
||||
pendingRotation: Vector2 | null = null
|
||||
pendingPan: Vector2 | null = null
|
||||
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
||||
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||
isFovAnimationInProgress = false
|
||||
perspectiveFovBeforeOrtho = 45
|
||||
get isPerspective() {
|
||||
@ -155,7 +155,6 @@ export class CameraControls {
|
||||
this.camera.zoom = camProps.zoom || 1
|
||||
}
|
||||
this.camera.updateProjectionMatrix()
|
||||
console.log('doing this thing', camProps)
|
||||
this.update(true)
|
||||
}
|
||||
|
||||
@ -273,14 +272,26 @@ export class CameraControls {
|
||||
camSettings.center.y,
|
||||
camSettings.center.z
|
||||
)
|
||||
const quat = new Quaternion(
|
||||
const orientation = new Quaternion(
|
||||
camSettings.orientation.x,
|
||||
camSettings.orientation.y,
|
||||
camSettings.orientation.z,
|
||||
camSettings.orientation.w
|
||||
).invert()
|
||||
|
||||
this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
|
||||
const newUp = new Vector3(
|
||||
camSettings.up.x,
|
||||
camSettings.up.y,
|
||||
camSettings.up.z
|
||||
)
|
||||
this.camera.quaternion.set(
|
||||
orientation.x,
|
||||
orientation.y,
|
||||
orientation.z,
|
||||
orientation.w
|
||||
)
|
||||
this.camera.up.copy(newUp)
|
||||
this.camera.updateProjectionMatrix()
|
||||
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
|
||||
this.useOrthographicCamera()
|
||||
}
|
||||
@ -1164,7 +1175,7 @@ export class CameraControls {
|
||||
this.camera.updateProjectionMatrix()
|
||||
}
|
||||
|
||||
if (this.syncDirection === 'clientToEngine' || forceUpdate)
|
||||
if (this.syncDirection === 'clientToEngine' || forceUpdate) {
|
||||
this.throttledUpdateEngineCamera({
|
||||
quaternion: this.camera.quaternion,
|
||||
position: this.camera.position,
|
||||
@ -1172,6 +1183,7 @@ export class CameraControls {
|
||||
isPerspective: this.isPerspective,
|
||||
target: this.target,
|
||||
})
|
||||
}
|
||||
this.deferReactUpdate(this.reactCameraProperties)
|
||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||
}
|
||||
|
@ -29,6 +29,9 @@ import {
|
||||
Expr,
|
||||
parse,
|
||||
recast,
|
||||
defaultSourceRange,
|
||||
resultIsOk,
|
||||
ProgramMemory,
|
||||
} from 'lang/wasm'
|
||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||
import { ConstrainInfo } from 'lang/std/stdTypes'
|
||||
@ -412,14 +415,15 @@ export async function deleteSegment({
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
|
||||
const newCode = recast(modifiedAst)
|
||||
modifiedAst = parse(newCode)
|
||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
const pResult = parse(newCode)
|
||||
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
modifiedAst = pResult.program
|
||||
|
||||
const testExecute = await executeAst({
|
||||
ast: modifiedAst,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: ProgramMemory.empty(),
|
||||
})
|
||||
if (testExecute.errors.length) {
|
||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||
@ -501,7 +505,8 @@ const ConstraintSymbol = ({
|
||||
constrainInfo: ConstrainInfo
|
||||
verticalPosition: 'top' | 'bottom'
|
||||
}) => {
|
||||
const { context, send } = useModelingContext()
|
||||
const { commandBarSend } = useCommandsContext()
|
||||
const { context } = useModelingContext()
|
||||
const varNameMap: {
|
||||
[key in ConstrainInfo['type']]: {
|
||||
varName: string
|
||||
@ -590,7 +595,9 @@ const ConstraintSymbol = ({
|
||||
if (err(_node)) return
|
||||
const node = _node.node
|
||||
|
||||
const range: SourceRange = node ? [node.start, node.end] : [0, 0]
|
||||
const range: SourceRange = node
|
||||
? [node.start, node.end, true]
|
||||
: defaultSourceRange()
|
||||
|
||||
if (_type === 'intersectionTag') return null
|
||||
|
||||
@ -612,25 +619,34 @@ const ConstraintSymbol = ({
|
||||
editorManager.setHighlightRange([range])
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
}}
|
||||
// disabled={isConstrained || !convertToVarEnabled}
|
||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||
onClick={toSync(async () => {
|
||||
if (!isConstrained) {
|
||||
send({
|
||||
type: 'Convert to variable',
|
||||
commandBarSend({
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
pathToNode,
|
||||
variableName: varName,
|
||||
name: 'Constrain with named value',
|
||||
groupId: 'modeling',
|
||||
argDefaultValues: {
|
||||
currentValue: {
|
||||
pathToNode,
|
||||
variableName: varName,
|
||||
valueText: value,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
} else if (isConstrained) {
|
||||
try {
|
||||
const parsed = parse(recast(kclManager.ast))
|
||||
if (trap(parsed)) return Promise.reject(parsed)
|
||||
const pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
|
||||
const _node1 = getNodeFromPath<CallExpression>(
|
||||
parsed,
|
||||
pResult.program!,
|
||||
pathToNode,
|
||||
'CallExpression',
|
||||
true
|
||||
|
@ -48,6 +48,9 @@ import {
|
||||
VariableDeclarator,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
defaultSourceRange,
|
||||
sourceRangeFromRust,
|
||||
resultIsOk,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
engineCommandManager,
|
||||
@ -495,10 +498,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
const sketch = sketchFromPathToNode({
|
||||
@ -530,7 +532,7 @@ export class SceneEntities {
|
||||
|
||||
const segPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
sketch.start.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
||||
)
|
||||
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
||||
const _profileStart = createProfileStartHandle({
|
||||
@ -552,7 +554,7 @@ export class SceneEntities {
|
||||
sketch.paths.forEach((segment, index) => {
|
||||
let segPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
if (
|
||||
draftExpressionsIndices &&
|
||||
@ -561,12 +563,12 @@ export class SceneEntities {
|
||||
const previousSegment = sketch.paths[index - 1] || sketch.start
|
||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||
maybeModdedAst,
|
||||
previousSegment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
||||
)
|
||||
const bodyIndex = previousSegmentPathToNode[1][0]
|
||||
segPathToNode = getNodePathFromSourceRange(
|
||||
truncatedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
segPathToNode[1][0] = bodyIndex
|
||||
}
|
||||
@ -575,7 +577,10 @@ export class SceneEntities {
|
||||
index <= draftExpressionsIndices.end &&
|
||||
index >= draftExpressionsIndices.start
|
||||
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
||||
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
|
||||
isOverlap(
|
||||
selection?.codeRef?.range,
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
)
|
||||
|
||||
let seg: Group
|
||||
@ -657,13 +662,11 @@ export class SceneEntities {
|
||||
}
|
||||
updateAstAndRejigSketch = async (
|
||||
sketchPathToNode: PathToNode,
|
||||
modifiedAst: Node<Program> | Error,
|
||||
modifiedAst: Node<Program>,
|
||||
forward: [number, number, number],
|
||||
up: [number, number, number],
|
||||
origin: [number, number, number]
|
||||
) => {
|
||||
if (err(modifiedAst)) return modifiedAst
|
||||
|
||||
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
||||
await this.tearDownSketch({ removeAxis: false })
|
||||
sceneInfra.resetMouseListeners()
|
||||
@ -698,8 +701,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
|
||||
const sg = sketchFromKclValue(
|
||||
kclManager.programMemory.get(variableDeclarationName),
|
||||
@ -721,8 +723,9 @@ export class SceneEntities {
|
||||
pathToNode: sketchPathToNode,
|
||||
})
|
||||
if (trap(mod)) return Promise.reject(mod)
|
||||
const modifiedAst = parse(recast(mod.modifiedAst))
|
||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
||||
const pResult = parse(recast(mod.modifiedAst))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
const modifiedAst = pResult.program
|
||||
|
||||
const draftExpressionsIndices = { start: index, end: index }
|
||||
|
||||
@ -898,10 +901,9 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -909,14 +911,14 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
@ -939,7 +941,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -950,10 +952,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -989,7 +990,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
if (sketchInit.type !== 'PipeExpression') {
|
||||
return
|
||||
@ -998,9 +999,10 @@ export class SceneEntities {
|
||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||
|
||||
const newCode = recast(_ast)
|
||||
let _recastAst = parse(newCode)
|
||||
if (trap(_recastAst)) return
|
||||
_ast = _recastAst
|
||||
const pResult = parse(newCode)
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1013,10 +1015,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
@ -1055,10 +1056,9 @@ export class SceneEntities {
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
|
||||
// startSketchOn already exists
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
const tags: [string, string, string] = [
|
||||
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||
@ -1066,14 +1066,14 @@ export class SceneEntities {
|
||||
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||
]
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||
sketchPathToNode,
|
||||
@ -1096,7 +1096,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return Promise.reject(_node)
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||
@ -1114,10 +1114,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1153,7 +1152,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
updateCenterRectangleSketch(
|
||||
@ -1165,9 +1164,10 @@ export class SceneEntities {
|
||||
rectangleOrigin[1]
|
||||
)
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1180,10 +1180,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: _ast,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
|
||||
@ -1222,12 +1221,11 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node1)) return Promise.reject(_node1)
|
||||
const variableDeclarationName =
|
||||
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declarations
|
||||
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||
const variableDeclarationName = _node1.node?.declaration.id?.name || ''
|
||||
const startSketchOn = _node1.node?.declaration
|
||||
const startSketchOnInit = startSketchOn?.init
|
||||
|
||||
startSketchOn[0].init = createPipeExpression([
|
||||
startSketchOn.init = createPipeExpression([
|
||||
startSketchOnInit,
|
||||
createCallExpressionStdLib('circle', [
|
||||
createObjectExpression({
|
||||
@ -1241,9 +1239,9 @@ export class SceneEntities {
|
||||
]),
|
||||
])
|
||||
|
||||
let _recastAst = parse(recast(_ast))
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(recast(_ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// do a quick mock execution to get the program memory up-to-date
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1269,7 +1267,7 @@ export class SceneEntities {
|
||||
)
|
||||
let modded = structuredClone(truncatedAst)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node.declaration.init
|
||||
|
||||
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
||||
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
||||
@ -1299,10 +1297,9 @@ export class SceneEntities {
|
||||
|
||||
const { execState } = await executeAst({
|
||||
ast: modded,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1338,7 +1335,7 @@ export class SceneEntities {
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (trap(_node)) return
|
||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||
const sketchInit = _node.node?.declaration.init
|
||||
|
||||
let modded = structuredClone(_ast)
|
||||
if (sketchInit.type === 'PipeExpression') {
|
||||
@ -1365,9 +1362,10 @@ export class SceneEntities {
|
||||
|
||||
const newCode = recast(modded)
|
||||
if (err(newCode)) return
|
||||
let _recastAst = parse(newCode)
|
||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||
_ast = _recastAst
|
||||
const pResult = parse(newCode)
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
_ast = pResult.program
|
||||
|
||||
// Update the primary AST and unequip the rectangle tool
|
||||
await kclManager.executeAstMock(_ast)
|
||||
@ -1660,7 +1658,7 @@ export class SceneEntities {
|
||||
kclManager.programMemory,
|
||||
{
|
||||
type: 'sourceRange',
|
||||
sourceRange: [node.start, node.end],
|
||||
sourceRange: [node.start, node.end, true],
|
||||
},
|
||||
getChangeSketchInput()
|
||||
)
|
||||
@ -1683,10 +1681,9 @@ export class SceneEntities {
|
||||
codeManager.updateCodeEditor(code)
|
||||
const { execState } = await executeAst({
|
||||
ast: truncatedAst,
|
||||
useFakeExecutor: true,
|
||||
engineCommandManager: this.engineCommandManager,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride,
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
})
|
||||
const programMemory = execState.memory
|
||||
this.sceneProgramMemory = programMemory
|
||||
@ -1750,7 +1747,7 @@ export class SceneEntities {
|
||||
): (() => SegmentOverlayPayload | null) => {
|
||||
const segPathToNode = getNodePathFromSourceRange(
|
||||
modifiedAst,
|
||||
segment.__geoMeta.sourceRange
|
||||
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||
)
|
||||
const sgPaths = sketch.paths
|
||||
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
||||
@ -1901,8 +1898,10 @@ export class SceneEntities {
|
||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||
)
|
||||
if (parent?.userData?.pathToNode) {
|
||||
const updatedAst = parse(recast(kclManager.ast))
|
||||
if (trap(updatedAst)) return
|
||||
const pResult = parse(recast(kclManager.ast))
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Promise.reject(pResult)
|
||||
const updatedAst = pResult.program
|
||||
const _node = getNodeFromPath<Node<CallExpression>>(
|
||||
updatedAst,
|
||||
parent.userData.pathToNode,
|
||||
@ -1910,7 +1909,7 @@ export class SceneEntities {
|
||||
)
|
||||
if (trap(_node, { suppress: true })) return
|
||||
const node = _node.node
|
||||
editorManager.setHighlightRange([[node.start, node.end]])
|
||||
editorManager.setHighlightRange([[node.start, node.end, true]])
|
||||
const yellow = 0xffff00
|
||||
colorSegment(selected, yellow)
|
||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||
@ -1955,10 +1954,10 @@ export class SceneEntities {
|
||||
})
|
||||
return
|
||||
}
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
},
|
||||
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
const parent = getParentGroup(
|
||||
selected,
|
||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||
@ -2057,7 +2056,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(_node)) return _node
|
||||
const variableDeclarationName = _node.node?.declarations?.[0]?.id?.name || ''
|
||||
const variableDeclarationName = _node.node?.declaration.id?.name || ''
|
||||
const sg = sketchFromKclValue(
|
||||
programMemory.get(variableDeclarationName),
|
||||
variableDeclarationName
|
||||
@ -2082,28 +2081,30 @@ function prepareTruncatedMemoryAndAst(
|
||||
])
|
||||
}
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.push(newSegment)
|
||||
// update source ranges to section we just added.
|
||||
// hacks like this wouldn't be needed if the AST put pathToNode info in memory/sketch segments
|
||||
const updatedSrcRangeAst = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
|
||||
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||
if (trap(pResult) || !resultIsOk(pResult))
|
||||
return Error('Unexpected compilation error')
|
||||
const updatedSrcRangeAst = pResult.program
|
||||
|
||||
const lastPipeItem = (
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
||||
.declarations[0].init as PipeExpression
|
||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.slice(-1)[0]
|
||||
|
||||
;(
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declarations[0]
|
||||
(_ast.body[bodyIndex] as VariableDeclaration).declaration
|
||||
.init as PipeExpression
|
||||
).body.slice(-1)[0].start = lastPipeItem.start
|
||||
|
||||
_ast.end = lastPipeItem.end
|
||||
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
|
||||
varDec.end = lastPipeItem.end
|
||||
const declarator = varDec.declarations[0]
|
||||
const declarator = varDec.declaration
|
||||
declarator.end = lastPipeItem.end
|
||||
const init = declarator.init as Node<PipeExpression>
|
||||
init.end = lastPipeItem.end
|
||||
@ -2140,7 +2141,7 @@ function prepareTruncatedMemoryAndAst(
|
||||
if (node.type !== 'VariableDeclaration') {
|
||||
continue
|
||||
}
|
||||
const name = node.declarations[0].id.name
|
||||
const name = node.declaration.id.name
|
||||
const memoryItem = programMemory.get(name)
|
||||
if (!memoryItem) {
|
||||
continue
|
||||
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
||||
import { trap } from 'lib/trap'
|
||||
import { codeToIdSelections } from 'lib/selections'
|
||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||
import { defaultSourceRange } from 'lang/wasm'
|
||||
|
||||
export function AstExplorer() {
|
||||
const { context } = useModelingContext()
|
||||
@ -46,7 +47,7 @@ export function AstExplorer() {
|
||||
<div
|
||||
className="h-full relative"
|
||||
onMouseLeave={(e) => {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
editorManager.setHighlightRange([defaultSourceRange()])
|
||||
}}
|
||||
>
|
||||
<pre className="text-xs">
|
||||
@ -115,15 +116,19 @@ function DisplayObj({
|
||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||
}`}
|
||||
onMouseEnter={(e) => {
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
e.stopPropagation()
|
||||
}}
|
||||
onMouseMove={(e) => {
|
||||
e.stopPropagation()
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||
}}
|
||||
onClick={(e) => {
|
||||
const range: [number, number] = [obj?.start || 0, obj.end || 0]
|
||||
const range: [number, number, boolean] = [
|
||||
obj?.start || 0,
|
||||
obj.end || 0,
|
||||
true,
|
||||
]
|
||||
const idInfo = codeToIdSelections([
|
||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||
])[0]
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { useEffect, useState, useRef } from 'react'
|
||||
import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
|
||||
import {
|
||||
parse,
|
||||
BinaryPart,
|
||||
Expr,
|
||||
ProgramMemory,
|
||||
resultIsOk,
|
||||
} from '../lang/wasm'
|
||||
import {
|
||||
createIdentifier,
|
||||
createLiteral,
|
||||
@ -141,8 +147,9 @@ export function useCalc({
|
||||
useEffect(() => {
|
||||
try {
|
||||
const code = `const __result__ = ${value}`
|
||||
const ast = parse(code)
|
||||
if (trap(ast)) return
|
||||
const pResult = parse(code)
|
||||
if (trap(pResult) || !resultIsOk(pResult)) return
|
||||
const ast = pResult.program
|
||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||
for (const { key, value } of availableVarInfo.variables) {
|
||||
const error = _programMem.set(key, {
|
||||
@ -156,18 +163,17 @@ export function useCalc({
|
||||
executeAst({
|
||||
ast,
|
||||
engineCommandManager,
|
||||
useFakeExecutor: true,
|
||||
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||
programMemoryOverride: kclManager.programMemory.clone(),
|
||||
idGenerator: kclManager.execState.idGenerator,
|
||||
}).then(({ execState }) => {
|
||||
const resultDeclaration = ast.body.find(
|
||||
(a) =>
|
||||
a.type === 'VariableDeclaration' &&
|
||||
a.declarations?.[0]?.id?.name === '__result__'
|
||||
a.declaration.id?.name === '__result__'
|
||||
)
|
||||
const init =
|
||||
resultDeclaration?.type === 'VariableDeclaration' &&
|
||||
resultDeclaration?.declarations?.[0]?.init
|
||||
resultDeclaration?.declaration.init
|
||||
const result = execState.memory?.get('__result__')?.value
|
||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||
init && setValueNode(init)
|
||||
|
@ -8,11 +8,16 @@ import { getSystemTheme } from 'lib/theme'
|
||||
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
|
||||
import { roundOff } from 'lib/utils'
|
||||
import { varMentions } from 'lib/varCompletionExtension'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import styles from './CommandBarKclInput.module.css'
|
||||
import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
import { useSelector } from '@xstate/react'
|
||||
|
||||
const machineContextSelector = (snapshot?: {
|
||||
context: Record<string, unknown>
|
||||
}) => snapshot?.context
|
||||
|
||||
function CommandBarKclInput({
|
||||
arg,
|
||||
@ -31,12 +36,44 @@ function CommandBarKclInput({
|
||||
arg.name
|
||||
] as KclCommandValue | undefined
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const defaultValue = (arg.defaultValue as string) || ''
|
||||
const argMachineContext = useSelector(
|
||||
arg.machineActor,
|
||||
machineContextSelector
|
||||
)
|
||||
const defaultValue = useMemo(
|
||||
() =>
|
||||
arg.defaultValue
|
||||
? arg.defaultValue instanceof Function
|
||||
? arg.defaultValue(commandBarState.context, argMachineContext)
|
||||
: arg.defaultValue
|
||||
: '',
|
||||
[arg.defaultValue, commandBarState.context, argMachineContext]
|
||||
)
|
||||
const initialVariableName = useMemo(() => {
|
||||
// Use the configured variable name if it exists
|
||||
if (arg.variableName !== undefined) {
|
||||
return arg.variableName instanceof Function
|
||||
? arg.variableName(commandBarState.context, argMachineContext)
|
||||
: arg.variableName
|
||||
}
|
||||
// or derive it from the previously set value or the argument name
|
||||
return previouslySetValue && 'variableName' in previouslySetValue
|
||||
? previouslySetValue.variableName
|
||||
: arg.name
|
||||
}, [
|
||||
arg.variableName,
|
||||
commandBarState.context,
|
||||
argMachineContext,
|
||||
arg.name,
|
||||
previouslySetValue,
|
||||
])
|
||||
const [value, setValue] = useState(
|
||||
previouslySetValue?.valueText || defaultValue || ''
|
||||
)
|
||||
const [createNewVariable, setCreateNewVariable] = useState(
|
||||
previouslySetValue && 'variableName' in previouslySetValue
|
||||
(previouslySetValue && 'variableName' in previouslySetValue) ||
|
||||
arg.createVariableByDefault ||
|
||||
false
|
||||
)
|
||||
const [canSubmit, setCanSubmit] = useState(true)
|
||||
useHotkeys('mod + k, mod + /', () => commandBarSend({ type: 'Close' }))
|
||||
@ -52,10 +89,7 @@ function CommandBarKclInput({
|
||||
isNewVariableNameUnique,
|
||||
} = useCalculateKclExpression({
|
||||
value,
|
||||
initialVariableName:
|
||||
previouslySetValue && 'variableName' in previouslySetValue
|
||||
? previouslySetValue.variableName
|
||||
: arg.name,
|
||||
initialVariableName,
|
||||
})
|
||||
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
||||
label: v.key,
|
||||
|
@ -1,13 +1,23 @@
|
||||
import toast from 'react-hot-toast'
|
||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
MouseEvent,
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
import { Dialog } from '@headlessui/react'
|
||||
|
||||
interface ContextMenuProps
|
||||
export interface ContextMenuProps
|
||||
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
||||
items?: React.ReactElement[]
|
||||
menuTargetElement?: RefObject<HTMLElement>
|
||||
guard?: (e: globalThis.MouseEvent) => boolean
|
||||
event?: 'contextmenu' | 'mouseup'
|
||||
}
|
||||
|
||||
const DefaultContextMenuItems = [
|
||||
@ -20,6 +30,8 @@ export function ContextMenu({
|
||||
items = DefaultContextMenuItems,
|
||||
menuTargetElement,
|
||||
className,
|
||||
guard,
|
||||
event = 'contextmenu',
|
||||
...props
|
||||
}: ContextMenuProps) {
|
||||
const dialogRef = useRef<HTMLDivElement>(null)
|
||||
@ -32,6 +44,15 @@ export function ContextMenu({
|
||||
useHotkeys('esc', () => setOpen(false), {
|
||||
enabled: open,
|
||||
})
|
||||
const handleContextMenu = useCallback(
|
||||
(e: globalThis.MouseEvent) => {
|
||||
if (guard && !guard(e)) return
|
||||
e.preventDefault()
|
||||
setPosition({ x: e.clientX, y: e.clientY })
|
||||
setOpen(true)
|
||||
},
|
||||
[guard, setPosition, setOpen]
|
||||
)
|
||||
|
||||
const dialogPositionStyle = useMemo(() => {
|
||||
if (!dialogRef.current)
|
||||
@ -78,21 +99,9 @@ export function ContextMenu({
|
||||
|
||||
// Add context menu listener to target once mounted
|
||||
useEffect(() => {
|
||||
const handleContextMenu = (e: MouseEvent) => {
|
||||
console.log('context menu', e)
|
||||
e.preventDefault()
|
||||
setPosition({ x: e.x, y: e.y })
|
||||
setOpen(true)
|
||||
}
|
||||
menuTargetElement?.current?.addEventListener(
|
||||
'contextmenu',
|
||||
handleContextMenu
|
||||
)
|
||||
menuTargetElement?.current?.addEventListener(event, handleContextMenu)
|
||||
return () => {
|
||||
menuTargetElement?.current?.removeEventListener(
|
||||
'contextmenu',
|
||||
handleContextMenu
|
||||
)
|
||||
menuTargetElement?.current?.removeEventListener(event, handleContextMenu)
|
||||
}
|
||||
}, [menuTargetElement?.current])
|
||||
|
||||
@ -100,7 +109,10 @@ export function ContextMenu({
|
||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||
<div
|
||||
className="fixed inset-0 z-50 w-screen h-screen"
|
||||
onContextMenu={(e) => e.preventDefault()}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault()
|
||||
setPosition({ x: e.clientX, y: e.clientY })
|
||||
}}
|
||||
>
|
||||
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
||||
<Dialog.Panel
|
||||
|
@ -266,6 +266,7 @@ const FileTreeItem = ({
|
||||
// Let the lsp servers know we closed a file.
|
||||
onFileClose(currentFile?.path || null, project?.path || null)
|
||||
onFileOpen(fileOrDir.path, project?.path || null)
|
||||
kclManager.switchedFiles = true
|
||||
|
||||
// Open kcl files
|
||||
navigate(`${PATHS.FILE}/${encodeURIComponent(fileOrDir.path)}`)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||
import { sceneInfra } from 'lib/singletons'
|
||||
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
|
||||
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||
import {
|
||||
WebGLRenderer,
|
||||
Scene,
|
||||
@ -19,16 +19,14 @@ import {
|
||||
Intersection,
|
||||
Object3D,
|
||||
} from 'three'
|
||||
import {
|
||||
ContextMenu,
|
||||
ContextMenuDivider,
|
||||
ContextMenuItem,
|
||||
ContextMenuItemRefresh,
|
||||
} from './ContextMenu'
|
||||
import { Popover } from '@headlessui/react'
|
||||
import { CustomIcon } from './CustomIcon'
|
||||
import { reportRejection } from 'lib/trap'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
import {
|
||||
useViewControlMenuItems,
|
||||
ViewControlContextMenu,
|
||||
} from './ViewControlMenu'
|
||||
import { AxisNames } from 'lib/constants'
|
||||
|
||||
const CANVAS_SIZE = 80
|
||||
const FRUSTUM_SIZE = 0.5
|
||||
@ -40,64 +38,14 @@ enum AxisColors {
|
||||
Z = '#6689ef',
|
||||
Gray = '#c6c7c2',
|
||||
}
|
||||
enum AxisNames {
|
||||
X = 'x',
|
||||
Y = 'y',
|
||||
Z = 'z',
|
||||
NEG_X = '-x',
|
||||
NEG_Y = '-y',
|
||||
NEG_Z = '-z',
|
||||
}
|
||||
const axisNamesSemantic: Record<AxisNames, string> = {
|
||||
[AxisNames.X]: 'Right',
|
||||
[AxisNames.Y]: 'Back',
|
||||
[AxisNames.Z]: 'Top',
|
||||
[AxisNames.NEG_X]: 'Left',
|
||||
[AxisNames.NEG_Y]: 'Front',
|
||||
[AxisNames.NEG_Z]: 'Bottom',
|
||||
}
|
||||
|
||||
export default function Gizmo() {
|
||||
const menuItems = useViewControlMenuItems()
|
||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||
const cameraPassiveUpdateTimer = useRef(0)
|
||||
const raycasterPassiveUpdateTimer = useRef(0)
|
||||
const { send: modelingSend } = useModelingContext()
|
||||
const menuItems = useMemo(
|
||||
() => [
|
||||
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
|
||||
<ContextMenuItem
|
||||
key={axisName}
|
||||
onClick={() => {
|
||||
sceneInfra.camControls
|
||||
.updateCameraToAxis(axisName as AxisNames)
|
||||
.catch(reportRejection)
|
||||
}}
|
||||
>
|
||||
{axisSemantic} view
|
||||
</ContextMenuItem>
|
||||
)),
|
||||
<ContextMenuDivider />,
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||
}}
|
||||
>
|
||||
Reset view
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
modelingSend({ type: 'Center camera on selection' })
|
||||
}}
|
||||
>
|
||||
Center view on selection
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuDivider />,
|
||||
<ContextMenuItemRefresh />,
|
||||
],
|
||||
[axisNamesSemantic]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
@ -161,7 +109,7 @@ export default function Gizmo() {
|
||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||
>
|
||||
<canvas ref={canvasRef} />
|
||||
<ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
|
||||
<ViewControlContextMenu menuTargetElement={wrapperRef} />
|
||||
</div>
|
||||
<GizmoDropdown items={menuItems} />
|
||||
</div>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { APP_VERSION } from 'routes/Settings'
|
||||
import { APP_VERSION, getReleaseUrl } from 'routes/Settings'
|
||||
import { CustomIcon } from 'components/CustomIcon'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { PATHS } from 'lib/paths'
|
||||
@ -72,10 +72,8 @@ export function LowerRightControls({
|
||||
<menu className="flex items-center justify-end gap-3 pointer-events-auto">
|
||||
{!location.pathname.startsWith(PATHS.HOME) && <ModelStateIndicator />}
|
||||
<a
|
||||
onClick={openExternalBrowserIfDesktop(
|
||||
`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`
|
||||
)}
|
||||
href={`https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`}
|
||||
onClick={openExternalBrowserIfDesktop(getReleaseUrl())}
|
||||
href={getReleaseUrl()}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
||||
|
@ -69,14 +69,7 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const [isKclLspReady, setIsKclLspReady] = useState(false)
|
||||
const [isCopilotLspReady, setIsCopilotLspReady] = useState(false)
|
||||
|
||||
const {
|
||||
auth,
|
||||
settings: {
|
||||
context: {
|
||||
modeling: { defaultUnit },
|
||||
},
|
||||
},
|
||||
} = useSettingsAuthContext()
|
||||
const { auth } = useSettingsAuthContext()
|
||||
const token = auth?.context.token
|
||||
const navigate = useNavigate()
|
||||
|
||||
@ -92,7 +85,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
const initEvent: KclWorkerOptions = {
|
||||
wasmUrl: wasmUrl(),
|
||||
token: token,
|
||||
baseUnit: defaultUnit.current,
|
||||
apiBaseUrl: VITE_KC_API_BASE_URL,
|
||||
}
|
||||
lspWorker.postMessage({
|
||||
|