Compare commits
56 Commits
achalmers/
...
franknoiro
Author | SHA1 | Date | |
---|---|---|---|
f70847c407 | |||
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 | |||
46be4e7eef | |||
9510849a3a | |||
5b208356b4 | |||
412d1b7a99 | |||
cfdd22af74 | |||
68a11e7aa5 | |||
3139e18dc7 | |||
84209e764e | |||
2b85e7abd6 | |||
9155a5efc8 | |||
2ccc27112a | |||
ee160b67f4 | |||
5117b6f5d6 | |||
bc8a7a364d |
28
.github/workflows/build-apps.yml
vendored
@ -362,6 +362,17 @@ jobs:
|
|||||||
- name: List artifacts
|
- name: List artifacts
|
||||||
run: "ls -R out"
|
run: "ls -R out"
|
||||||
|
|
||||||
|
- name: Set more complete nightly release notes
|
||||||
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
|
run: |
|
||||||
|
# Note: prefered 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
|
- name: Authenticate to Google Cloud
|
||||||
if: ${{ env.IS_NIGHTLY == 'true' }}
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
uses: 'google-github-actions/auth@v2.1.7'
|
uses: 'google-github-actions/auth@v2.1.7'
|
||||||
@ -383,12 +394,13 @@ jobs:
|
|||||||
parent: false
|
parent: false
|
||||||
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
destination: 'dl.kittycad.io/releases/modeling-app/nightly'
|
||||||
|
|
||||||
- name: Create draft release
|
- name: Tag nightly commit
|
||||||
uses: softprops/action-gh-release@v2
|
if: ${{ env.IS_NIGHTLY == 'true' }}
|
||||||
if: ${{ env.IS_RELEASE == 'true' }}
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
name: ${{ env.VERSION }}
|
script: |
|
||||||
tag_name: ${{ env.VERSION }}
|
const { VERSION } = process.env
|
||||||
draft: true
|
const { owner, repo } = context.repo
|
||||||
generate_release_notes: true
|
const { sha } = context
|
||||||
files: 'out/Zoo*'
|
const ref = `refs/tags/nightly-${VERSION}`
|
||||||
|
github.rest.git.createRef({ owner, repo, sha, ref })
|
||||||
|
4
.github/workflows/e2e-tests.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
|||||||
- name: Download Wasm Cache
|
- name: Download Wasm Cache
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
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
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
@ -255,7 +255,7 @@ jobs:
|
|||||||
- name: Download Wasm Cache
|
- name: Download Wasm Cache
|
||||||
id: download-wasm
|
id: download-wasm
|
||||||
if: needs.check-rust-changes.outputs.rust-changed == 'false'
|
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
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
github_token: ${{secrets.GITHUB_TOKEN}}
|
github_token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
6
.github/workflows/publish-apps-release.yml
vendored
@ -132,6 +132,12 @@ jobs:
|
|||||||
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-mac.yml" --async
|
||||||
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
gcloud compute url-maps invalidate-cdn-cache dl-url-map --path="/releases/modeling-app/latest.yml" --async
|
||||||
|
|
||||||
|
- name: Upload release files to Github
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
files: 'out/Zoo*'
|
||||||
|
|
||||||
|
|
||||||
announce_release:
|
announce_release:
|
||||||
needs: [publish-apps-release]
|
needs: [publish-apps-release]
|
||||||
|
22
README.md
@ -99,7 +99,7 @@ yarn tron:start
|
|||||||
|
|
||||||
This will start the application and hot-reload on changes.
|
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`.
|
To build, run `yarn tron:package`.
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ https://github.com/KittyCAD/modeling-app/issues/new
|
|||||||
|
|
||||||
#### 2. Push a new tag
|
#### 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)
|
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.
|
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
|
##### 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.).
|
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.
|
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
|
##### 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
|
#### 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
|
#### 5. Close the issue
|
||||||
|
|
||||||
@ -450,3 +450,9 @@ PS: for the debug panel, the following JSON is useful for snapping the camera
|
|||||||
## KCL
|
## KCL
|
||||||
|
|
||||||
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
For how to contribute to KCL, [see our KCL README](https://github.com/KittyCAD/modeling-app/tree/main/src/wasm-lib/kcl).
|
||||||
|
|
||||||
|
### Logging
|
||||||
|
|
||||||
|
To display logging (to the terminal or console) set `ZOO_LOG=1`. This will log some warnings and simple performance metrics. To view these in test runs, use `-- --nocapture`.
|
||||||
|
|
||||||
|
To enable memory metrics, build with `--features dhat-heap`.
|
||||||
|
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 |
@ -58,7 +58,7 @@ mountingPlate = extrude(thickness, mountingPlateSketch)
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Sketch on the face of a chamfer.
|
// Sketch on the face of a chamfer.
|
||||||
fn cube = (pos, scale) => {
|
fn cube(pos, scale) {
|
||||||
sg = startSketchOn('XY')
|
sg = startSketchOn('XY')
|
||||||
|> startProfileAt(pos, %)
|
|> startProfileAt(pos, %)
|
||||||
|> line([0, scale], %)
|
|> line([0, scale], %)
|
||||||
|
@ -37,7 +37,7 @@ assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
|||||||
startSketchOn('XZ')
|
startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle({ center = [0, 0], radius = 2 }, %)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|> patternTransform(n, (id) => {
|
|> patternTransform(n, fn(id) {
|
||||||
return { translate = [4 * id, 0, 0] }
|
return { translate = [4 * id, 0, 0] }
|
||||||
}, %)
|
}, %)
|
||||||
```
|
```
|
||||||
|
@ -29,7 +29,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
r = 10 // radius
|
r = 10 // radius
|
||||||
fn drawCircle = (id) => {
|
fn drawCircle(id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||||
}
|
}
|
||||||
@ -45,7 +45,7 @@ circles = map([1..3], drawCircle)
|
|||||||
```js
|
```js
|
||||||
r = 10 // radius
|
r = 10 // radius
|
||||||
// Call `map`, using an anonymous function instead of a named one.
|
// Call `map`, using an anonymous function instead of a named one.
|
||||||
circles = map([1..3], (id) => {
|
circles = map([1..3], (id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
||||||
})
|
})
|
||||||
|
@ -12,7 +12,7 @@ to other modules.
|
|||||||
|
|
||||||
```
|
```
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment = (x) => {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -37,11 +37,11 @@ Multiple functions can be exported in a file.
|
|||||||
|
|
||||||
```
|
```
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment = (x) => {
|
export fn increment(x) {
|
||||||
return x + 1
|
return x + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn decrement = (x) => {
|
export fn decrement(x) {
|
||||||
return x - 1
|
return x - 1
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -30,7 +30,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Each instance will be shifted along the X axis.
|
// Each instance will be shifted along the X axis.
|
||||||
fn transform = (id) => {
|
fn transform(id) {
|
||||||
return { translate = [4 * id, 0] }
|
return { translate = [4 * id, 0] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// This function adds two numbers.
|
// This function adds two numbers.
|
||||||
fn add = (a, b) => {
|
fn add(a, b) {
|
||||||
return a + b
|
return a + b
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function adds an array of numbers.
|
// This function adds an array of numbers.
|
||||||
// It uses the `reduce` function, to call the `add` function on every
|
// It uses the `reduce` function, to call the `add` function on every
|
||||||
// element of the `arr` parameter. The starting value is 0.
|
// element of the `arr` parameter. The starting value is 0.
|
||||||
fn sum = (arr) => {
|
fn sum(arr) {
|
||||||
return reduce(arr, 0, add)
|
return reduce(arr, 0, add)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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
|
// an anonymous `add` function as its parameter, instead of declaring a
|
||||||
// named function outside.
|
// named function outside.
|
||||||
arr = [1, 2, 3]
|
arr = [1, 2, 3]
|
||||||
sum = reduce(arr, 0, (i, result_so_far) => {
|
sum = reduce(arr, 0, (i, result_so_far) {
|
||||||
return i + result_so_far
|
return i + result_so_far
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
// Declare a function that sketches a decagon.
|
// Declare a function that sketches a decagon.
|
||||||
fn decagon = (radius) => {
|
fn decagon(radius) {
|
||||||
// Each side of the decagon is turned this many degrees from the previous angle.
|
// Each side of the decagon is turned this many degrees from the previous angle.
|
||||||
stepAngle = 1 / 10 * tau()
|
stepAngle = 1 / 10 * tau()
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ fn decagon = (radius) => {
|
|||||||
// Use a `reduce` to draw the remaining decagon sides.
|
// Use a `reduce` to draw the remaining decagon sides.
|
||||||
// For each number in the array 1..10, run the given function,
|
// 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.
|
// 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, (i, partialDecagon) {
|
||||||
// Draw one edge of the decagon.
|
// Draw one edge of the decagon.
|
||||||
x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
|
@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder = (radius, tag) => {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchAt([0, 0])
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
|
@ -36,7 +36,7 @@ cube = startSketchAt([0, 0])
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder = (radius, tag) => {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchAt([0, 0])
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
|
21016
docs/kcl/std.json
@ -41,7 +41,7 @@ If you want to get a value from an array you can use the index like so:
|
|||||||
An object is defined with `{}` braces. Here is an example object:
|
An object is defined with `{}` braces. Here is an example object:
|
||||||
|
|
||||||
```
|
```
|
||||||
myObj = {a: 0, b: "thing"}
|
myObj = { a = 0, b = "thing" }
|
||||||
```
|
```
|
||||||
|
|
||||||
We support two different ways of getting properties from objects, you can call
|
We support two different ways of getting properties from objects, you can call
|
||||||
@ -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:
|
type of argument. Below is an example of the syntax:
|
||||||
|
|
||||||
```
|
```
|
||||||
fn myFn = (x) => {
|
fn myFn(x) {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -90,12 +90,12 @@ startSketchOn('XZ')
|
|||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
@ -118,17 +118,17 @@ use the tag `rectangleSegmentA001` in any function or expression in the file.
|
|||||||
However if the code was written like this:
|
However if the code was written like this:
|
||||||
|
|
||||||
```
|
```
|
||||||
fn rect = (origin) => {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn('XZ')
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
@ -146,17 +146,17 @@ Tags are accessible through the sketch group they are declared in.
|
|||||||
For example the following code works.
|
For example the following code works.
|
||||||
|
|
||||||
```
|
```
|
||||||
fn rect = (origin) => {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn('XZ')
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
|> angledLine([0, 191.26], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %) - 90,
|
segAng(rectangleSegmentA001) - 90,
|
||||||
196.99
|
196.99
|
||||||
], %, $rectangleSegmentB001)
|
], %, $rectangleSegmentB001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
segAng(rectangleSegmentA001, %),
|
segAng(rectangleSegmentA001),
|
||||||
-segLen(rectangleSegmentA001, %)
|
-segLen(rectangleSegmentA001)
|
||||||
], %, $rectangleSegmentC001)
|
], %, $rectangleSegmentC001)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
@ -167,7 +167,10 @@ myRect = rect([20, 0])
|
|||||||
|
|
||||||
myRect
|
myRect
|
||||||
|> extrude(10, %)
|
|> extrude(10, %)
|
||||||
|> fillet({radius: 0.5, tags: [myRect.tags.rectangleSegmentA001]}, %)
|
|> fillet({
|
||||||
|
radius = 0.5,
|
||||||
|
tags = [myRect.tags.rectangleSegmentA001]
|
||||||
|
}, %)
|
||||||
```
|
```
|
||||||
|
|
||||||
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
|
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside
|
||||||
|
@ -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`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -317,7 +317,6 @@ Data for an imported geometry.
|
|||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Function`| | No |
|
| `type` |enum: `Function`| | No |
|
||||||
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
|
|
||||||
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | 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 |
|
|
||||||
|
|
||||||
|
|
@ -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`
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -518,7 +518,10 @@ test.describe('Editor tests', () => {
|
|||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
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)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
@ -45,7 +45,6 @@ test.describe('integrations tests', () => {
|
|||||||
{
|
{
|
||||||
title: 'test-sample',
|
title: 'test-sample',
|
||||||
fileCount: 1,
|
fileCount: 1,
|
||||||
folderCount: 1,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
@ -233,7 +232,6 @@ test.describe('when using the file tree to', () => {
|
|||||||
{
|
{
|
||||||
title: projectName,
|
title: projectName,
|
||||||
fileCount: 2,
|
fileCount: 2,
|
||||||
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
|
@ -4,7 +4,6 @@ import { expect } from '@playwright/test'
|
|||||||
interface ProjectCardState {
|
interface ProjectCardState {
|
||||||
title: string
|
title: string
|
||||||
fileCount: number
|
fileCount: number
|
||||||
folderCount: number
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface HomePageState {
|
interface HomePageState {
|
||||||
@ -61,15 +60,13 @@ export class HomePageFixture {
|
|||||||
const projectCards = await this.projectCard.all()
|
const projectCards = await this.projectCard.all()
|
||||||
const projectCardStates: Array<ProjectCardState> = []
|
const projectCardStates: Array<ProjectCardState> = []
|
||||||
for (const projectCard of projectCards) {
|
for (const projectCard of projectCards) {
|
||||||
const [title, fileCount, folderCount] = await Promise.all([
|
const [title, fileCount] = await Promise.all([
|
||||||
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
(await projectCard.locator(this.projectCardTitle).textContent()) || '',
|
||||||
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
Number(await projectCard.locator(this.projectCardFile).textContent()),
|
||||||
Number(await projectCard.locator(this.projectCardFolder).textContent()),
|
|
||||||
])
|
])
|
||||||
projectCardStates.push({
|
projectCardStates.push({
|
||||||
title: title,
|
title: title,
|
||||||
fileCount,
|
fileCount,
|
||||||
folderCount,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return projectCardStates
|
return projectCardStates
|
||||||
|
@ -28,6 +28,7 @@ type SceneSerialised = {
|
|||||||
|
|
||||||
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||||
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
|
||||||
|
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
|
||||||
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
|
||||||
type DragFromHandler = (
|
type DragFromHandler = (
|
||||||
dragParams: mouseDragFromParams
|
dragParams: mouseDragFromParams
|
||||||
@ -68,7 +69,7 @@ export class SceneFixture {
|
|||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
{ steps }: { steps: number } = { steps: 20 }
|
{ steps }: { steps: number } = { steps: 20 }
|
||||||
): [ClickHandler, MoveHandler] =>
|
): [ClickHandler, MoveHandler, DblClickHandler] =>
|
||||||
[
|
[
|
||||||
(clickParams?: mouseParams) => {
|
(clickParams?: mouseParams) => {
|
||||||
if (clickParams?.pixelDiff) {
|
if (clickParams?.pixelDiff) {
|
||||||
@ -90,6 +91,16 @@ export class SceneFixture {
|
|||||||
}
|
}
|
||||||
return this.page.mouse.move(x, y, { steps })
|
return this.page.mouse.move(x, y, { steps })
|
||||||
},
|
},
|
||||||
|
(clickParams?: mouseParams) => {
|
||||||
|
if (clickParams?.pixelDiff) {
|
||||||
|
return doAndWaitForImageDiff(
|
||||||
|
this.page,
|
||||||
|
() => this.page.mouse.dblclick(x, y),
|
||||||
|
clickParams.pixelDiff
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return this.page.mouse.dblclick(x, y)
|
||||||
|
},
|
||||||
] as const
|
] as const
|
||||||
makeDragHelpers = (
|
makeDragHelpers = (
|
||||||
x: number,
|
x: number,
|
||||||
|
@ -6,6 +6,7 @@ export class ToolbarFixture {
|
|||||||
public page: Page
|
public page: Page
|
||||||
|
|
||||||
extrudeButton!: Locator
|
extrudeButton!: Locator
|
||||||
|
loftButton!: Locator
|
||||||
offsetPlaneButton!: Locator
|
offsetPlaneButton!: Locator
|
||||||
startSketchBtn!: Locator
|
startSketchBtn!: Locator
|
||||||
lineBtn!: Locator
|
lineBtn!: Locator
|
||||||
@ -26,6 +27,7 @@ export class ToolbarFixture {
|
|||||||
reConstruct = (page: Page) => {
|
reConstruct = (page: Page) => {
|
||||||
this.page = page
|
this.page = page
|
||||||
this.extrudeButton = page.getByTestId('extrude')
|
this.extrudeButton = page.getByTestId('extrude')
|
||||||
|
this.loftButton = page.getByTestId('loft')
|
||||||
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
this.offsetPlaneButton = page.getByTestId('plane-offset')
|
||||||
this.startSketchBtn = page.getByTestId('sketch')
|
this.startSketchBtn = page.getByTestId('sketch')
|
||||||
this.lineBtn = page.getByTestId('line')
|
this.lineBtn = page.getByTestId('line')
|
||||||
|
@ -552,6 +552,82 @@ test(`Verify axis, origin, and horizontal snapping`, async ({
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test(`Verify user can double-click to edit a sketch`, async ({
|
||||||
|
app,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
|
scene,
|
||||||
|
}) => {
|
||||||
|
const initialCode = `closedSketch = startSketchOn('XZ')
|
||||||
|
|> circle({ center = [8, 5], radius = 2 }, %)
|
||||||
|
openSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-5, 0], %)
|
||||||
|
|> lineTo([0, 5], %)
|
||||||
|
|> xLine(5, %)
|
||||||
|
|> tangentialArcTo([10, 0], %)
|
||||||
|
`
|
||||||
|
await app.initialise(initialCode)
|
||||||
|
|
||||||
|
const pointInsideCircle = {
|
||||||
|
x: app.viewPortSize.width * 0.63,
|
||||||
|
y: app.viewPortSize.height * 0.5,
|
||||||
|
}
|
||||||
|
const pointOnPathAfterSketching = {
|
||||||
|
x: app.viewPortSize.width * 0.58,
|
||||||
|
y: app.viewPortSize.height * 0.5,
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const [_clickOpenPath, moveToOpenPath, dblClickOpenPath] =
|
||||||
|
scene.makeMouseHelpers(
|
||||||
|
pointOnPathAfterSketching.x,
|
||||||
|
pointOnPathAfterSketching.y
|
||||||
|
)
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const [_clickCircle, moveToCircle, dblClickCircle] = scene.makeMouseHelpers(
|
||||||
|
pointInsideCircle.x,
|
||||||
|
pointInsideCircle.y
|
||||||
|
)
|
||||||
|
|
||||||
|
const exitSketch = async () => {
|
||||||
|
await test.step(`Exit sketch mode`, async () => {
|
||||||
|
await toolbar.exitSketchBtn.click()
|
||||||
|
await expect(toolbar.exitSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.startSketchBtn).toBeEnabled()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await test.step(`Double-click on the closed sketch`, async () => {
|
||||||
|
await moveToCircle()
|
||||||
|
await dblClickCircle()
|
||||||
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`|>circle({center=[8,5],radius=2},%)`],
|
||||||
|
highlightedCode: 'circle({center=[8,5],radius=2},%)',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await exitSketch()
|
||||||
|
|
||||||
|
await test.step(`Double-click on the open sketch`, async () => {
|
||||||
|
await moveToOpenPath()
|
||||||
|
await scene.expectPixelColor([250, 250, 250], pointOnPathAfterSketching, 15)
|
||||||
|
// There is a full execution after exiting sketch that clears the scene.
|
||||||
|
await app.page.waitForTimeout(500)
|
||||||
|
await dblClickOpenPath()
|
||||||
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
|
// Wait for enter sketch mode to complete
|
||||||
|
await app.page.waitForTimeout(500)
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`|>xLine(5,%)`],
|
||||||
|
highlightedCode: 'xLine(5,%)',
|
||||||
|
diagnostics: [],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test(`Offset plane point-and-click`, async ({
|
test(`Offset plane point-and-click`, async ({
|
||||||
app,
|
app,
|
||||||
scene,
|
scene,
|
||||||
@ -601,3 +677,94 @@ test(`Offset plane point-and-click`, async ({
|
|||||||
await scene.expectPixelColor([74, 74, 74], testPoint, 15)
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
@ -550,7 +550,7 @@ sketch001 = startSketchAt([-0, -0])
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Constants and locators
|
// 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 bgColor: [number, number, number] = [27, 27, 27]
|
||||||
const middlePixelIsColor = async (color: [number, number, number]) => {
|
const middlePixelIsColor = async (color: [number, number, number]) => {
|
||||||
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
return u.getGreatestPixDiff({ x: 600, y: 250 }, color)
|
||||||
|
@ -7,6 +7,8 @@ try {
|
|||||||
.split('\n')
|
.split('\n')
|
||||||
.filter((line) => line && line.length > 1)
|
.filter((line) => line && line.length > 1)
|
||||||
.forEach((line) => {
|
.forEach((line) => {
|
||||||
|
// Allow line comments.
|
||||||
|
if (line.trimStart().startsWith('#')) return
|
||||||
const [key, value] = line.split('=')
|
const [key, value] = line.split('=')
|
||||||
// prefer env vars over secrets file
|
// prefer env vars over secrets file
|
||||||
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
secrets[key] = process.env[key] || (value as any).replaceAll('"', '')
|
||||||
|
@ -943,6 +943,110 @@ sketch002 = startSketchOn(extrude001, 'END')
|
|||||||
`.replace(/\s/g, '')
|
`.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 ({
|
test('empty-scene default-planes act as expected', async ({
|
||||||
page,
|
page,
|
||||||
browserName,
|
browserName,
|
||||||
|
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 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 |
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 |
@ -23,7 +23,7 @@ test.describe('Test toggling perspective', () => {
|
|||||||
y: screenHeight * 0.4,
|
y: screenHeight * 0.4,
|
||||||
}
|
}
|
||||||
const backgroundColor: [number, number, number] = [29, 29, 29]
|
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]) => {
|
const locationToHaveColor = async (color: [number, number, number]) => {
|
||||||
return u.getGreatestPixDiff(checkedScreenLocation, color)
|
return u.getGreatestPixDiff(checkedScreenLocation, color)
|
||||||
}
|
}
|
||||||
|
@ -192,7 +192,7 @@
|
|||||||
"eslint-plugin-css-modules": "^2.12.0",
|
"eslint-plugin-css-modules": "^2.12.0",
|
||||||
"eslint-plugin-import": "^2.30.0",
|
"eslint-plugin-import": "^2.30.0",
|
||||||
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
"eslint-plugin-suggest-no-throw": "^1.0.0",
|
||||||
"happy-dom": "^15.10.2",
|
"happy-dom": "^15.11.7",
|
||||||
"http-server": "^14.1.1",
|
"http-server": "^14.1.1",
|
||||||
"husky": "^9.1.5",
|
"husky": "^9.1.5",
|
||||||
"kill-port": "^2.0.1",
|
"kill-port": "^2.0.1",
|
||||||
@ -212,7 +212,7 @@
|
|||||||
"vite-tsconfig-paths": "^4.3.2",
|
"vite-tsconfig-paths": "^4.3.2",
|
||||||
"vitest": "^1.6.0",
|
"vitest": "^1.6.0",
|
||||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||||
"wasm-pack": "^0.13.0",
|
"wasm-pack": "^0.13.1",
|
||||||
"ws": "^8.17.0",
|
"ws": "^8.17.0",
|
||||||
"yarn": "^1.22.22"
|
"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}"
|
39
src/App.tsx
@ -3,7 +3,7 @@ import { useHotKeyListener } from './hooks/useHotKeyListener'
|
|||||||
import { Stream } from './components/Stream'
|
import { Stream } from './components/Stream'
|
||||||
import { AppHeader } from './components/AppHeader'
|
import { AppHeader } from './components/AppHeader'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { useLoaderData, useNavigate } from 'react-router-dom'
|
import { useLoaderData, useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||||
@ -22,7 +22,14 @@ import Gizmo from 'components/Gizmo'
|
|||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import { UnitsMenu } from 'components/UnitsMenu'
|
import { UnitsMenu } from 'components/UnitsMenu'
|
||||||
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
import { CameraProjectionToggle } from 'components/CameraProjectionToggle'
|
||||||
|
import { homeDefaultStatusBarItems } from 'components/statusBar/homeDefaultStatusBarItems'
|
||||||
|
import { StatusBar } from 'components/StatusBar'
|
||||||
|
import { useModelStateStatus } from 'components/ModelStateIndicator'
|
||||||
|
import { useNetworkHealthStatus } from 'components/NetworkHealthIndicator'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { xStateValueToString } from 'lib/xStateValueToString'
|
||||||
import { maybeWriteToDisk } from 'lib/telemetry'
|
import { maybeWriteToDisk } from 'lib/telemetry'
|
||||||
|
import { useNetworkMachineStatus } from 'components/NetworkMachineIndicator'
|
||||||
maybeWriteToDisk()
|
maybeWriteToDisk()
|
||||||
.then(() => {})
|
.then(() => {})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@ -31,11 +38,10 @@ export function App() {
|
|||||||
const { project, file } = useLoaderData() as IndexLoaderData
|
const { project, file } = useLoaderData() as IndexLoaderData
|
||||||
useRefreshSettings(PATHS.FILE + 'SETTINGS')
|
useRefreshSettings(PATHS.FILE + 'SETTINGS')
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const location = useLocation()
|
||||||
const filePath = useAbsoluteFilePath()
|
const filePath = useAbsoluteFilePath()
|
||||||
const { onProjectOpen } = useLspContext()
|
const { onProjectOpen } = useLspContext()
|
||||||
// We need the ref for the outermost div so we can screenshot the app for
|
const { state: modelingState, streamRef } = useModelingContext()
|
||||||
// the coredump.
|
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
const projectName = project?.name || null
|
const projectName = project?.name || null
|
||||||
const projectPath = project?.path || null
|
const projectPath = project?.path || null
|
||||||
@ -77,7 +83,8 @@ export function App() {
|
|||||||
useEngineConnectionSubscriptions()
|
useEngineConnectionSubscriptions()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative h-full flex flex-col" ref={ref}>
|
<div className="h-screen flex flex-col overflow-hidden select-none">
|
||||||
|
<div className="relative flex flex-1 flex-col" ref={streamRef}>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
className={'transition-opacity transition-duration-75 ' + paneOpacity}
|
className={'transition-opacity transition-duration-75 ' + paneOpacity}
|
||||||
project={{ project, file }}
|
project={{ project, file }}
|
||||||
@ -93,5 +100,27 @@ export function App() {
|
|||||||
<CameraProjectionToggle />
|
<CameraProjectionToggle />
|
||||||
</LowerRightControls>
|
</LowerRightControls>
|
||||||
</div>
|
</div>
|
||||||
|
<StatusBar
|
||||||
|
globalItems={[
|
||||||
|
useNetworkHealthStatus(),
|
||||||
|
useNetworkMachineStatus(),
|
||||||
|
...homeDefaultStatusBarItems({ coreDumpManager, location }),
|
||||||
|
]}
|
||||||
|
localItems={[
|
||||||
|
{
|
||||||
|
id: 'modeling-state',
|
||||||
|
element: 'text',
|
||||||
|
label:
|
||||||
|
modelingState.value instanceof Object
|
||||||
|
? xStateValueToString(modelingState.value) ?? ''
|
||||||
|
: modelingState.value,
|
||||||
|
toolTip: {
|
||||||
|
children: 'The current state of the modeler',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
useModelStateStatus(),
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,6 @@ export class CameraControls {
|
|||||||
this.camera.zoom = camProps.zoom || 1
|
this.camera.zoom = camProps.zoom || 1
|
||||||
}
|
}
|
||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
console.log('doing this thing', camProps)
|
|
||||||
this.update(true)
|
this.update(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,14 +272,26 @@ export class CameraControls {
|
|||||||
camSettings.center.y,
|
camSettings.center.y,
|
||||||
camSettings.center.z
|
camSettings.center.z
|
||||||
)
|
)
|
||||||
const quat = new Quaternion(
|
const orientation = new Quaternion(
|
||||||
camSettings.orientation.x,
|
camSettings.orientation.x,
|
||||||
camSettings.orientation.y,
|
camSettings.orientation.y,
|
||||||
camSettings.orientation.z,
|
camSettings.orientation.z,
|
||||||
camSettings.orientation.w
|
camSettings.orientation.w
|
||||||
).invert()
|
).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) {
|
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
|
||||||
this.useOrthographicCamera()
|
this.useOrthographicCamera()
|
||||||
}
|
}
|
||||||
@ -1164,7 +1175,7 @@ export class CameraControls {
|
|||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.syncDirection === 'clientToEngine' || forceUpdate)
|
if (this.syncDirection === 'clientToEngine' || forceUpdate) {
|
||||||
this.throttledUpdateEngineCamera({
|
this.throttledUpdateEngineCamera({
|
||||||
quaternion: this.camera.quaternion,
|
quaternion: this.camera.quaternion,
|
||||||
position: this.camera.position,
|
position: this.camera.position,
|
||||||
@ -1172,6 +1183,7 @@ export class CameraControls {
|
|||||||
isPerspective: this.isPerspective,
|
isPerspective: this.isPerspective,
|
||||||
target: this.target,
|
target: this.target,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
this.deferReactUpdate(this.reactCameraProperties)
|
this.deferReactUpdate(this.reactCameraProperties)
|
||||||
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ import {
|
|||||||
Expr,
|
Expr,
|
||||||
parse,
|
parse,
|
||||||
recast,
|
recast,
|
||||||
|
defaultSourceRange,
|
||||||
|
resultIsOk,
|
||||||
|
ProgramMemory,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
|
||||||
import { ConstrainInfo } from 'lang/std/stdTypes'
|
import { ConstrainInfo } from 'lang/std/stdTypes'
|
||||||
@ -412,14 +415,15 @@ export async function deleteSegment({
|
|||||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
||||||
|
|
||||||
const newCode = recast(modifiedAst)
|
const newCode = recast(modifiedAst)
|
||||||
modifiedAst = parse(newCode)
|
const pResult = parse(newCode)
|
||||||
if (err(modifiedAst)) return Promise.reject(modifiedAst)
|
if (err(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
|
modifiedAst = pResult.program
|
||||||
|
|
||||||
const testExecute = await executeAst({
|
const testExecute = await executeAst({
|
||||||
ast: modifiedAst,
|
ast: modifiedAst,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: engineCommandManager,
|
engineCommandManager: engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
|
programMemoryOverride: ProgramMemory.empty(),
|
||||||
})
|
})
|
||||||
if (testExecute.errors.length) {
|
if (testExecute.errors.length) {
|
||||||
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
toast.error('Segment tag used outside of current Sketch. Could not delete.')
|
||||||
@ -590,7 +594,9 @@ const ConstraintSymbol = ({
|
|||||||
if (err(_node)) return
|
if (err(_node)) return
|
||||||
const node = _node.node
|
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
|
if (_type === 'intersectionTag') return null
|
||||||
|
|
||||||
@ -612,7 +618,7 @@ const ConstraintSymbol = ({
|
|||||||
editorManager.setHighlightRange([range])
|
editorManager.setHighlightRange([range])
|
||||||
}}
|
}}
|
||||||
onMouseLeave={() => {
|
onMouseLeave={() => {
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([defaultSourceRange()])
|
||||||
}}
|
}}
|
||||||
// disabled={isConstrained || !convertToVarEnabled}
|
// disabled={isConstrained || !convertToVarEnabled}
|
||||||
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
// disabled={implicitDesc} TODO why does this change styles that are hard to override?
|
||||||
@ -627,10 +633,12 @@ const ConstraintSymbol = ({
|
|||||||
})
|
})
|
||||||
} else if (isConstrained) {
|
} else if (isConstrained) {
|
||||||
try {
|
try {
|
||||||
const parsed = parse(recast(kclManager.ast))
|
const pResult = parse(recast(kclManager.ast))
|
||||||
if (trap(parsed)) return Promise.reject(parsed)
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(pResult)
|
||||||
|
|
||||||
const _node1 = getNodeFromPath<CallExpression>(
|
const _node1 = getNodeFromPath<CallExpression>(
|
||||||
parsed,
|
pResult.program!,
|
||||||
pathToNode,
|
pathToNode,
|
||||||
'CallExpression',
|
'CallExpression',
|
||||||
true
|
true
|
||||||
|
@ -48,6 +48,9 @@ import {
|
|||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
sketchFromKclValueOptional,
|
sketchFromKclValueOptional,
|
||||||
|
defaultSourceRange,
|
||||||
|
sourceRangeFromRust,
|
||||||
|
resultIsOk,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -495,10 +498,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
const sketch = sketchFromPathToNode({
|
const sketch = sketchFromPathToNode({
|
||||||
@ -530,7 +532,7 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
const segPathToNode = getNodePathFromSourceRange(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
sketch.start.__geoMeta.sourceRange
|
sourceRangeFromRust(sketch.start.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
if (sketch?.paths?.[0]?.type !== 'Circle') {
|
||||||
const _profileStart = createProfileStartHandle({
|
const _profileStart = createProfileStartHandle({
|
||||||
@ -552,7 +554,7 @@ export class SceneEntities {
|
|||||||
sketch.paths.forEach((segment, index) => {
|
sketch.paths.forEach((segment, index) => {
|
||||||
let segPathToNode = getNodePathFromSourceRange(
|
let segPathToNode = getNodePathFromSourceRange(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
segment.__geoMeta.sourceRange
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
draftExpressionsIndices &&
|
draftExpressionsIndices &&
|
||||||
@ -561,12 +563,12 @@ export class SceneEntities {
|
|||||||
const previousSegment = sketch.paths[index - 1] || sketch.start
|
const previousSegment = sketch.paths[index - 1] || sketch.start
|
||||||
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
const previousSegmentPathToNode = getNodePathFromSourceRange(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
previousSegment.__geoMeta.sourceRange
|
sourceRangeFromRust(previousSegment.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
const bodyIndex = previousSegmentPathToNode[1][0]
|
const bodyIndex = previousSegmentPathToNode[1][0]
|
||||||
segPathToNode = getNodePathFromSourceRange(
|
segPathToNode = getNodePathFromSourceRange(
|
||||||
truncatedAst,
|
truncatedAst,
|
||||||
segment.__geoMeta.sourceRange
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
segPathToNode[1][0] = bodyIndex
|
segPathToNode[1][0] = bodyIndex
|
||||||
}
|
}
|
||||||
@ -575,7 +577,10 @@ export class SceneEntities {
|
|||||||
index <= draftExpressionsIndices.end &&
|
index <= draftExpressionsIndices.end &&
|
||||||
index >= draftExpressionsIndices.start
|
index >= draftExpressionsIndices.start
|
||||||
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
const isSelected = selectionRanges?.graphSelections.some((selection) =>
|
||||||
isOverlap(selection?.codeRef?.range, segment.__geoMeta.sourceRange)
|
isOverlap(
|
||||||
|
selection?.codeRef?.range,
|
||||||
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
let seg: Group
|
let seg: Group
|
||||||
@ -657,13 +662,11 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
updateAstAndRejigSketch = async (
|
updateAstAndRejigSketch = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
modifiedAst: Node<Program> | Error,
|
modifiedAst: Node<Program>,
|
||||||
forward: [number, number, number],
|
forward: [number, number, number],
|
||||||
up: [number, number, number],
|
up: [number, number, number],
|
||||||
origin: [number, number, number]
|
origin: [number, number, number]
|
||||||
) => {
|
) => {
|
||||||
if (err(modifiedAst)) return modifiedAst
|
|
||||||
|
|
||||||
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
const nextAst = await kclManager.updateAst(modifiedAst, false)
|
||||||
await this.tearDownSketch({ removeAxis: false })
|
await this.tearDownSketch({ removeAxis: false })
|
||||||
sceneInfra.resetMouseListeners()
|
sceneInfra.resetMouseListeners()
|
||||||
@ -721,8 +724,9 @@ export class SceneEntities {
|
|||||||
pathToNode: sketchPathToNode,
|
pathToNode: sketchPathToNode,
|
||||||
})
|
})
|
||||||
if (trap(mod)) return Promise.reject(mod)
|
if (trap(mod)) return Promise.reject(mod)
|
||||||
const modifiedAst = parse(recast(mod.modifiedAst))
|
const pResult = parse(recast(mod.modifiedAst))
|
||||||
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
|
const modifiedAst = pResult.program
|
||||||
|
|
||||||
const draftExpressionsIndices = { start: index, end: index }
|
const draftExpressionsIndices = { start: index, end: index }
|
||||||
|
|
||||||
@ -914,9 +918,9 @@ export class SceneEntities {
|
|||||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||||
])
|
])
|
||||||
|
|
||||||
let _recastAst = parse(recast(_ast))
|
const pResult = parse(recast(_ast))
|
||||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
_ast = _recastAst
|
_ast = pResult.program
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
@ -950,10 +954,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
@ -998,9 +1001,10 @@ export class SceneEntities {
|
|||||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||||
|
|
||||||
const newCode = recast(_ast)
|
const newCode = recast(_ast)
|
||||||
let _recastAst = parse(newCode)
|
const pResult = parse(newCode)
|
||||||
if (trap(_recastAst)) return
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
_ast = _recastAst
|
return Promise.reject(pResult)
|
||||||
|
_ast = pResult.program
|
||||||
|
|
||||||
// Update the primary AST and unequip the rectangle tool
|
// Update the primary AST and unequip the rectangle tool
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
@ -1013,10 +1017,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
|
|
||||||
@ -1071,9 +1074,9 @@ export class SceneEntities {
|
|||||||
...getRectangleCallExpressions(rectangleOrigin, tags),
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||||
])
|
])
|
||||||
|
|
||||||
let _recastAst = parse(recast(_ast))
|
const pResult = parse(recast(_ast))
|
||||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
_ast = _recastAst
|
_ast = pResult.program
|
||||||
|
|
||||||
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
@ -1114,10 +1117,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
@ -1165,9 +1167,10 @@ export class SceneEntities {
|
|||||||
rectangleOrigin[1]
|
rectangleOrigin[1]
|
||||||
)
|
)
|
||||||
|
|
||||||
let _recastAst = parse(recast(_ast))
|
const pResult = parse(recast(_ast))
|
||||||
if (trap(_recastAst)) return
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
_ast = _recastAst
|
return Promise.reject(pResult)
|
||||||
|
_ast = pResult.program
|
||||||
|
|
||||||
// Update the primary AST and unequip the rectangle tool
|
// Update the primary AST and unequip the rectangle tool
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
@ -1180,10 +1183,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
|
|
||||||
@ -1241,9 +1243,9 @@ export class SceneEntities {
|
|||||||
]),
|
]),
|
||||||
])
|
])
|
||||||
|
|
||||||
let _recastAst = parse(recast(_ast))
|
const pResult = parse(recast(_ast))
|
||||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
if (trap(pResult) || !resultIsOk(pResult)) return Promise.reject(pResult)
|
||||||
_ast = _recastAst
|
_ast = pResult.program
|
||||||
|
|
||||||
// do a quick mock execution to get the program memory up-to-date
|
// do a quick mock execution to get the program memory up-to-date
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
@ -1299,10 +1301,9 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: modded,
|
ast: modded,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
@ -1365,9 +1366,10 @@ export class SceneEntities {
|
|||||||
|
|
||||||
const newCode = recast(modded)
|
const newCode = recast(modded)
|
||||||
if (err(newCode)) return
|
if (err(newCode)) return
|
||||||
let _recastAst = parse(newCode)
|
const pResult = parse(newCode)
|
||||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
_ast = _recastAst
|
return Promise.reject(pResult)
|
||||||
|
_ast = pResult.program
|
||||||
|
|
||||||
// Update the primary AST and unequip the rectangle tool
|
// Update the primary AST and unequip the rectangle tool
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
@ -1660,7 +1662,7 @@ export class SceneEntities {
|
|||||||
kclManager.programMemory,
|
kclManager.programMemory,
|
||||||
{
|
{
|
||||||
type: 'sourceRange',
|
type: 'sourceRange',
|
||||||
sourceRange: [node.start, node.end],
|
sourceRange: [node.start, node.end, true],
|
||||||
},
|
},
|
||||||
getChangeSketchInput()
|
getChangeSketchInput()
|
||||||
)
|
)
|
||||||
@ -1683,10 +1685,9 @@ export class SceneEntities {
|
|||||||
codeManager.updateCodeEditor(code)
|
codeManager.updateCodeEditor(code)
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: truncatedAst,
|
ast: truncatedAst,
|
||||||
useFakeExecutor: true,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride,
|
programMemoryOverride,
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
})
|
})
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
@ -1750,7 +1751,7 @@ export class SceneEntities {
|
|||||||
): (() => SegmentOverlayPayload | null) => {
|
): (() => SegmentOverlayPayload | null) => {
|
||||||
const segPathToNode = getNodePathFromSourceRange(
|
const segPathToNode = getNodePathFromSourceRange(
|
||||||
modifiedAst,
|
modifiedAst,
|
||||||
segment.__geoMeta.sourceRange
|
sourceRangeFromRust(segment.__geoMeta.sourceRange)
|
||||||
)
|
)
|
||||||
const sgPaths = sketch.paths
|
const sgPaths = sketch.paths
|
||||||
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
const originalPathToNodeStr = JSON.stringify(segPathToNode)
|
||||||
@ -1901,8 +1902,10 @@ export class SceneEntities {
|
|||||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||||
)
|
)
|
||||||
if (parent?.userData?.pathToNode) {
|
if (parent?.userData?.pathToNode) {
|
||||||
const updatedAst = parse(recast(kclManager.ast))
|
const pResult = parse(recast(kclManager.ast))
|
||||||
if (trap(updatedAst)) return
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(pResult)
|
||||||
|
const updatedAst = pResult.program
|
||||||
const _node = getNodeFromPath<Node<CallExpression>>(
|
const _node = getNodeFromPath<Node<CallExpression>>(
|
||||||
updatedAst,
|
updatedAst,
|
||||||
parent.userData.pathToNode,
|
parent.userData.pathToNode,
|
||||||
@ -1910,7 +1913,7 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
if (trap(_node, { suppress: true })) return
|
if (trap(_node, { suppress: true })) return
|
||||||
const node = _node.node
|
const node = _node.node
|
||||||
editorManager.setHighlightRange([[node.start, node.end]])
|
editorManager.setHighlightRange([[node.start, node.end, true]])
|
||||||
const yellow = 0xffff00
|
const yellow = 0xffff00
|
||||||
colorSegment(selected, yellow)
|
colorSegment(selected, yellow)
|
||||||
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
const extraSegmentGroup = parent.getObjectByName(EXTRA_SEGMENT_HANDLE)
|
||||||
@ -1955,10 +1958,10 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([defaultSourceRange()])
|
||||||
},
|
},
|
||||||
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
onMouseLeave: ({ selected, ...rest }: OnMouseEnterLeaveArgs) => {
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([defaultSourceRange()])
|
||||||
const parent = getParentGroup(
|
const parent = getParentGroup(
|
||||||
selected,
|
selected,
|
||||||
SEGMENT_BODIES_PLUS_PROFILE_START
|
SEGMENT_BODIES_PLUS_PROFILE_START
|
||||||
@ -2087,8 +2090,10 @@ function prepareTruncatedMemoryAndAst(
|
|||||||
).body.push(newSegment)
|
).body.push(newSegment)
|
||||||
// update source ranges to section we just added.
|
// 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
|
// 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
|
const pResult = parse(recast(_ast)) // get source ranges correct since unfortunately we still rely on them
|
||||||
if (err(updatedSrcRangeAst)) return updatedSrcRangeAst
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Error('Unexpected compilation error')
|
||||||
|
const updatedSrcRangeAst = pResult.program
|
||||||
|
|
||||||
const lastPipeItem = (
|
const lastPipeItem = (
|
||||||
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
(updatedSrcRangeAst.body[bodyIndex] as VariableDeclaration)
|
||||||
|
@ -5,6 +5,7 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { trap } from 'lib/trap'
|
import { trap } from 'lib/trap'
|
||||||
import { codeToIdSelections } from 'lib/selections'
|
import { codeToIdSelections } from 'lib/selections'
|
||||||
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
import { codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
|
import { defaultSourceRange } from 'lang/wasm'
|
||||||
|
|
||||||
export function AstExplorer() {
|
export function AstExplorer() {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
@ -46,7 +47,7 @@ export function AstExplorer() {
|
|||||||
<div
|
<div
|
||||||
className="h-full relative"
|
className="h-full relative"
|
||||||
onMouseLeave={(e) => {
|
onMouseLeave={(e) => {
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([defaultSourceRange()])
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<pre className="text-xs">
|
<pre className="text-xs">
|
||||||
@ -115,15 +116,19 @@ function DisplayObj({
|
|||||||
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
hasCursor ? 'bg-violet-100/80 dark:bg-violet-100/25' : ''
|
||||||
}`}
|
}`}
|
||||||
onMouseEnter={(e) => {
|
onMouseEnter={(e) => {
|
||||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
}}
|
}}
|
||||||
onMouseMove={(e) => {
|
onMouseMove={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
editorManager.setHighlightRange([[obj?.start || 0, obj.end]])
|
editorManager.setHighlightRange([[obj?.start || 0, obj.end, true]])
|
||||||
}}
|
}}
|
||||||
onClick={(e) => {
|
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([
|
const idInfo = codeToIdSelections([
|
||||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||||
])[0]
|
])[0]
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { useEffect, useState, useRef } from 'react'
|
import { useEffect, useState, useRef } from 'react'
|
||||||
import { parse, BinaryPart, Expr, ProgramMemory } from '../lang/wasm'
|
import {
|
||||||
|
parse,
|
||||||
|
BinaryPart,
|
||||||
|
Expr,
|
||||||
|
ProgramMemory,
|
||||||
|
resultIsOk,
|
||||||
|
} from '../lang/wasm'
|
||||||
import {
|
import {
|
||||||
createIdentifier,
|
createIdentifier,
|
||||||
createLiteral,
|
createLiteral,
|
||||||
@ -141,8 +147,9 @@ export function useCalc({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
try {
|
try {
|
||||||
const code = `const __result__ = ${value}`
|
const code = `const __result__ = ${value}`
|
||||||
const ast = parse(code)
|
const pResult = parse(code)
|
||||||
if (trap(ast)) return
|
if (trap(pResult) || !resultIsOk(pResult)) return
|
||||||
|
const ast = pResult.program
|
||||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||||
for (const { key, value } of availableVarInfo.variables) {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
const error = _programMem.set(key, {
|
const error = _programMem.set(key, {
|
||||||
@ -156,9 +163,8 @@ export function useCalc({
|
|||||||
executeAst({
|
executeAst({
|
||||||
ast,
|
ast,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
useFakeExecutor: true,
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
programMemoryOverride: kclManager.programMemory.clone(),
|
programMemoryOverride: kclManager.programMemory.clone(),
|
||||||
idGenerator: kclManager.execState.idGenerator,
|
|
||||||
}).then(({ execState }) => {
|
}).then(({ execState }) => {
|
||||||
const resultDeclaration = ast.body.find(
|
const resultDeclaration = ast.body.find(
|
||||||
(a) =>
|
(a) =>
|
||||||
|
@ -636,6 +636,16 @@ const CustomIconMap = {
|
|||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
|
loading: (
|
||||||
|
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M12.5001 6.25839C11.76 5.76392 10.89 5.5 10 5.5V4.5C11.0878 4.5 12.1512 4.82257 13.0556 5.42692C13.9601 6.03126 14.6651 6.89025 15.0813 7.89524C15.4976 8.90023 15.6065 10.0061 15.3943 11.073C15.1821 12.1399 14.6583 13.1199 13.8891 13.8891C13.1199 14.6583 12.1399 15.1821 11.073 15.3943C10.0061 15.6065 8.90023 15.4976 7.89524 15.0813C6.89025 14.6651 6.03126 13.9601 5.42692 13.0556C4.82257 12.1512 4.5 11.0878 4.5 10H5.5C5.5 10.89 5.76392 11.76 6.25839 12.5001C6.75285 13.2401 7.45566 13.8169 8.27792 14.1575C9.10019 14.4981 10.005 14.5872 10.8779 14.4135C11.7508 14.2399 12.5526 13.8113 13.182 13.182C13.8113 12.5526 14.2399 11.7508 14.4135 10.8779C14.5872 10.005 14.4981 9.10019 14.1575 8.27792C13.8169 7.45566 13.2401 6.75285 12.5001 6.25839Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
lockClosed: (
|
lockClosed: (
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 20 20"
|
viewBox="0 0 20 20"
|
||||||
|
@ -1,141 +1,14 @@
|
|||||||
import { APP_VERSION } from 'routes/Settings'
|
|
||||||
import { CustomIcon } from 'components/CustomIcon'
|
|
||||||
import Tooltip from 'components/Tooltip'
|
|
||||||
import { PATHS } from 'lib/paths'
|
|
||||||
import { NetworkHealthIndicator } from 'components/NetworkHealthIndicator'
|
|
||||||
import { HelpMenu } from './HelpMenu'
|
|
||||||
import { Link, useLocation } from 'react-router-dom'
|
|
||||||
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
|
|
||||||
import { coreDump } from 'lang/wasm'
|
|
||||||
import toast from 'react-hot-toast'
|
|
||||||
import { CoreDumpManager } from 'lib/coredump'
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
import openWindow, { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
|
||||||
import { NetworkMachineIndicator } from './NetworkMachineIndicator'
|
|
||||||
import { ModelStateIndicator } from './ModelStateIndicator'
|
|
||||||
import { reportRejection } from 'lib/trap'
|
|
||||||
|
|
||||||
export function LowerRightControls({
|
export function LowerRightControls({
|
||||||
children,
|
children,
|
||||||
coreDumpManager,
|
|
||||||
}: {
|
}: {
|
||||||
children?: React.ReactNode
|
children?: React.ReactNode
|
||||||
coreDumpManager?: CoreDumpManager
|
coreDumpManager?: CoreDumpManager
|
||||||
}) {
|
}) {
|
||||||
const location = useLocation()
|
|
||||||
const filePath = useAbsoluteFilePath()
|
|
||||||
|
|
||||||
const linkOverrideClassName =
|
|
||||||
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'
|
|
||||||
|
|
||||||
function reportbug(event: {
|
|
||||||
preventDefault: () => void
|
|
||||||
stopPropagation: () => void
|
|
||||||
}) {
|
|
||||||
event?.preventDefault()
|
|
||||||
event?.stopPropagation()
|
|
||||||
|
|
||||||
if (!coreDumpManager) {
|
|
||||||
// open default reporting option
|
|
||||||
openWindow(
|
|
||||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
|
||||||
).catch(reportRejection)
|
|
||||||
} else {
|
|
||||||
toast
|
|
||||||
.promise(
|
|
||||||
coreDump(coreDumpManager, true),
|
|
||||||
{
|
|
||||||
loading: 'Preparing bug report...',
|
|
||||||
success: 'Bug report opened in new window',
|
|
||||||
error: 'Unable to export a core dump. Using default reporting.',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
success: {
|
|
||||||
// Note: this extended duration is especially important for Playwright e2e testing
|
|
||||||
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
|
||||||
duration: 6000,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.catch((err: Error) => {
|
|
||||||
if (err) {
|
|
||||||
openWindow(
|
|
||||||
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
|
||||||
).catch(reportRejection)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="fixed bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
<section className="absolute bottom-2 right-2 flex flex-col items-end gap-3 pointer-events-none">
|
||||||
{children}
|
{children}
|
||||||
<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}`}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className={'!no-underline font-mono text-xs ' + linkOverrideClassName}
|
|
||||||
>
|
|
||||||
v{APP_VERSION}
|
|
||||||
</a>
|
|
||||||
<a
|
|
||||||
onClick={reportbug}
|
|
||||||
href="https://github.com/KittyCAD/modeling-app/issues/new/choose"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
<CustomIcon
|
|
||||||
name="bug"
|
|
||||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
|
||||||
/>
|
|
||||||
<Tooltip position="top" contentClassName="text-xs">
|
|
||||||
Report a bug
|
|
||||||
</Tooltip>
|
|
||||||
</a>
|
|
||||||
<Link
|
|
||||||
to={
|
|
||||||
location.pathname.includes(PATHS.FILE)
|
|
||||||
? filePath + PATHS.TELEMETRY + '?tab=project'
|
|
||||||
: PATHS.HOME + PATHS.TELEMETRY
|
|
||||||
}
|
|
||||||
data-testid="telemetry-link"
|
|
||||||
>
|
|
||||||
<CustomIcon
|
|
||||||
name="stopwatch"
|
|
||||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Telemetry</span>
|
|
||||||
<Tooltip position="top" contentClassName="text-xs">
|
|
||||||
Telemetry
|
|
||||||
</Tooltip>
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to={
|
|
||||||
location.pathname.includes(PATHS.FILE)
|
|
||||||
? filePath + PATHS.SETTINGS + '?tab=project'
|
|
||||||
: PATHS.HOME + PATHS.SETTINGS
|
|
||||||
}
|
|
||||||
data-testid="settings-link"
|
|
||||||
>
|
|
||||||
<CustomIcon
|
|
||||||
name="settings"
|
|
||||||
className={`w-5 h-5 ${linkOverrideClassName}`}
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Settings</span>
|
|
||||||
<Tooltip position="top" contentClassName="text-xs">
|
|
||||||
Settings
|
|
||||||
</Tooltip>
|
|
||||||
</Link>
|
|
||||||
<NetworkMachineIndicator className={linkOverrideClassName} />
|
|
||||||
{!location.pathname.startsWith(PATHS.HOME) && (
|
|
||||||
<NetworkHealthIndicator />
|
|
||||||
)}
|
|
||||||
<HelpMenu />
|
|
||||||
</menu>
|
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,39 @@
|
|||||||
import { useEngineCommands } from './EngineCommands'
|
import { useEngineCommands } from './EngineCommands'
|
||||||
import { Spinner } from './Spinner'
|
import { Spinner } from './Spinner'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||||
|
|
||||||
|
export const useModelStateStatus = (): StatusBarItemType => {
|
||||||
|
const [commands] = useEngineCommands()
|
||||||
|
const lastCommandType = commands[commands.length - 1]?.type
|
||||||
|
|
||||||
|
let icon: StatusBarItemType['icon'] = 'loading'
|
||||||
|
const baseDataTestId = 'model-state-indicator'
|
||||||
|
let dataTestId = baseDataTestId
|
||||||
|
|
||||||
|
if (lastCommandType === 'receive-reliable') {
|
||||||
|
icon = 'checkmark'
|
||||||
|
dataTestId = `${baseDataTestId}-receive-reliable`
|
||||||
|
} else if (lastCommandType === 'execution-done') {
|
||||||
|
icon = 'checkmark'
|
||||||
|
dataTestId = `${baseDataTestId}-execution-done`
|
||||||
|
} else if (lastCommandType === 'export-done') {
|
||||||
|
icon = 'checkmark'
|
||||||
|
dataTestId = `${baseDataTestId}-export-done`
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'model-state-indicator',
|
||||||
|
label: '',
|
||||||
|
icon,
|
||||||
|
toolTip: {
|
||||||
|
children: 'Model state indicator',
|
||||||
|
},
|
||||||
|
element: 'button',
|
||||||
|
onClick: () => {},
|
||||||
|
'data-testid': dataTestId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const ModelStateIndicator = () => {
|
export const ModelStateIndicator = () => {
|
||||||
const [commands] = useEngineCommands()
|
const [commands] = useEngineCommands()
|
||||||
|
@ -50,6 +50,7 @@ import {
|
|||||||
isSketchPipe,
|
isSketchPipe,
|
||||||
Selections,
|
Selections,
|
||||||
updateSelections,
|
updateSelections,
|
||||||
|
canLoftSelection,
|
||||||
} from 'lib/selections'
|
} from 'lib/selections'
|
||||||
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
import { applyConstraintIntersect } from './Toolbar/Intersect'
|
||||||
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
import { applyConstraintAbsDistance } from './Toolbar/SetAbsDistance'
|
||||||
@ -66,7 +67,7 @@ import {
|
|||||||
sketchOnOffsetPlane,
|
sketchOnOffsetPlane,
|
||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { Program, parse, recast } from 'lang/wasm'
|
import { Program, parse, recast, resultIsOk } from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
doesSceneHaveSweepableSketch,
|
doesSceneHaveSweepableSketch,
|
||||||
getNodePathFromSourceRange,
|
getNodePathFromSourceRange,
|
||||||
@ -82,7 +83,7 @@ import { getVarNameModal } from 'hooks/useToolbarGuards'
|
|||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, reportRejection, trap } from 'lib/trap'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { modelingMachineEvent } from 'editor/manager'
|
import { modelingMachineEvent } from 'editor/manager'
|
||||||
import { hasValidFilletSelection } from 'lang/modifyAst/addFillet'
|
import { hasValidEdgeTreatmentSelection } from 'lang/modifyAst/addEdgeTreatment'
|
||||||
import {
|
import {
|
||||||
ExportIntent,
|
ExportIntent,
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
@ -98,6 +99,7 @@ type MachineContext<T extends AnyStateMachine> = {
|
|||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
context: ContextFrom<T>
|
context: ContextFrom<T>
|
||||||
send: Prop<Actor<T>, 'send'>
|
send: Prop<Actor<T>, 'send'>
|
||||||
|
streamRef: React.RefObject<HTMLDivElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ModelingMachineContext = createContext(
|
export const ModelingMachineContext = createContext(
|
||||||
@ -569,6 +571,21 @@ export const ModelingMachineProvider = ({
|
|||||||
if (err(canSweep)) return false
|
if (err(canSweep)) return false
|
||||||
return canSweep
|
return canSweep
|
||||||
},
|
},
|
||||||
|
'has valid loft selection': ({ context: { selectionRanges } }) => {
|
||||||
|
const hasNoSelection =
|
||||||
|
selectionRanges.graphSelections.length === 0 ||
|
||||||
|
isRangeBetweenCharacters(selectionRanges) ||
|
||||||
|
isSelectionLastLine(selectionRanges, codeManager.code)
|
||||||
|
|
||||||
|
if (hasNoSelection) {
|
||||||
|
const count = 2
|
||||||
|
return doesSceneHaveSweepableSketch(kclManager.ast, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
const canLoft = canLoftSelection(selectionRanges)
|
||||||
|
if (err(canLoft)) return false
|
||||||
|
return canLoft
|
||||||
|
},
|
||||||
'has valid selection for deletion': ({
|
'has valid selection for deletion': ({
|
||||||
context: { selectionRanges },
|
context: { selectionRanges },
|
||||||
}) => {
|
}) => {
|
||||||
@ -576,8 +593,10 @@ export const ModelingMachineProvider = ({
|
|||||||
if (selectionRanges.graphSelections.length <= 0) return false
|
if (selectionRanges.graphSelections.length <= 0) return false
|
||||||
return true
|
return true
|
||||||
},
|
},
|
||||||
'has valid fillet selection': ({ context: { selectionRanges } }) => {
|
'has valid edge treatment selection': ({
|
||||||
return hasValidFilletSelection({
|
context: { selectionRanges },
|
||||||
|
}) => {
|
||||||
|
return hasValidEdgeTreatmentSelection({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
ast: kclManager.ast,
|
ast: kclManager.ast,
|
||||||
code: codeManager.code,
|
code: codeManager.code,
|
||||||
@ -594,15 +613,11 @@ export const ModelingMachineProvider = ({
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Has exportable geometry': () => {
|
'Has exportable geometry': () => {
|
||||||
if (
|
if (!kclManager.hasErrors() && kclManager.ast.body.length > 0)
|
||||||
kclManager.kclErrors.length === 0 &&
|
|
||||||
kclManager.ast.body.length > 0
|
|
||||||
)
|
|
||||||
return true
|
return true
|
||||||
else {
|
else {
|
||||||
let errorMessage = 'Unable to Export '
|
let errorMessage = 'Unable to Export '
|
||||||
if (kclManager.kclErrors.length > 0)
|
if (kclManager.hasErrors()) errorMessage += 'due to KCL Errors'
|
||||||
errorMessage += 'due to KCL Errors'
|
|
||||||
else if (kclManager.ast.body.length === 0)
|
else if (kclManager.ast.body.length === 0)
|
||||||
errorMessage += 'due to Empty Scene'
|
errorMessage += 'due to Empty Scene'
|
||||||
console.error(errorMessage)
|
console.error(errorMessage)
|
||||||
@ -720,7 +735,11 @@ export const ModelingMachineProvider = ({
|
|||||||
constraint: 'setHorzDistance',
|
constraint: 'setHorzDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
|
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -761,7 +780,10 @@ export const ModelingMachineProvider = ({
|
|||||||
constraint: 'setVertDistance',
|
constraint: 'setVertDistance',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -809,7 +831,10 @@ export const ModelingMachineProvider = ({
|
|||||||
selectionRanges,
|
selectionRanges,
|
||||||
angleOrLength: 'setAngle',
|
angleOrLength: 'setAngle',
|
||||||
}))
|
}))
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
|
if (err(_modifiedAst)) return Promise.reject(_modifiedAst)
|
||||||
|
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
@ -851,7 +876,10 @@ export const ModelingMachineProvider = ({
|
|||||||
await applyConstraintAngleLength({
|
await applyConstraintAngleLength({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -891,7 +919,10 @@ export const ModelingMachineProvider = ({
|
|||||||
await applyConstraintIntersect({
|
await applyConstraintIntersect({
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -932,7 +963,10 @@ export const ModelingMachineProvider = ({
|
|||||||
constraint: 'xAbs',
|
constraint: 'xAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -973,7 +1007,10 @@ export const ModelingMachineProvider = ({
|
|||||||
constraint: 'yAbs',
|
constraint: 'yAbs',
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
})
|
})
|
||||||
const _modifiedAst = parse(recast(modifiedAst))
|
const pResult = parse(recast(modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
const _modifiedAst = pResult.program
|
||||||
if (!sketchDetails)
|
if (!sketchDetails)
|
||||||
return Promise.reject(new Error('No sketch details'))
|
return Promise.reject(new Error('No sketch details'))
|
||||||
const updatedPathToNode = updatePathToNodeFromMap(
|
const updatedPathToNode = updatePathToNodeFromMap(
|
||||||
@ -1014,9 +1051,10 @@ export const ModelingMachineProvider = ({
|
|||||||
const { variableName } = await getVarNameModal({
|
const { variableName } = await getVarNameModal({
|
||||||
valueName: data?.variableName || 'var',
|
valueName: data?.variableName || 'var',
|
||||||
})
|
})
|
||||||
let parsed = parse(recast(kclManager.ast))
|
let pResult = parse(recast(kclManager.ast))
|
||||||
if (trap(parsed)) return Promise.reject(parsed)
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
parsed = parsed as Node<Program>
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
let parsed = pResult.program
|
||||||
|
|
||||||
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
|
||||||
moveValueIntoNewVariablePath(
|
moveValueIntoNewVariablePath(
|
||||||
@ -1025,7 +1063,11 @@ export const ModelingMachineProvider = ({
|
|||||||
data?.pathToNode || [],
|
data?.pathToNode || [],
|
||||||
variableName
|
variableName
|
||||||
)
|
)
|
||||||
parsed = parse(recast(_modifiedAst))
|
pResult = parse(recast(_modifiedAst))
|
||||||
|
if (trap(pResult) || !resultIsOk(pResult))
|
||||||
|
return Promise.reject(new Error('Unexpected compilation error'))
|
||||||
|
parsed = pResult.program
|
||||||
|
|
||||||
if (trap(parsed)) return Promise.reject(parsed)
|
if (trap(parsed)) return Promise.reject(parsed)
|
||||||
parsed = parsed as Node<Program>
|
parsed = parsed as Node<Program>
|
||||||
if (!pathToReplacedNode)
|
if (!pathToReplacedNode)
|
||||||
@ -1164,13 +1206,10 @@ export const ModelingMachineProvider = ({
|
|||||||
state: modelingState,
|
state: modelingState,
|
||||||
context: modelingState.context,
|
context: modelingState.context,
|
||||||
send: modelingSend,
|
send: modelingSend,
|
||||||
|
streamRef,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* TODO #818: maybe pass reff down to children/app.ts or render app.tsx directly?
|
|
||||||
since realistically it won't ever have generic children that isn't app.tsx */}
|
|
||||||
<div className="h-screen overflow-hidden select-none" ref={streamRef}>
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
|
||||||
</ModelingMachineContext.Provider>
|
</ModelingMachineContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { processMemory } from './MemoryPane'
|
import { processMemory } from './MemoryPane'
|
||||||
import { enginelessExecutor } from '../../../lib/testHelpers'
|
import { enginelessExecutor } from '../../../lib/testHelpers'
|
||||||
import { initPromise, parse, ProgramMemory } from '../../../lang/wasm'
|
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -28,12 +28,16 @@ describe('processMemory', () => {
|
|||||||
|> lineTo([0.98, 5.16], %)
|
|> lineTo([0.98, 5.16], %)
|
||||||
|> lineTo([2.15, 4.32], %)
|
|> lineTo([2.15, 4.32], %)
|
||||||
// |> rx(90, %)`
|
// |> rx(90, %)`
|
||||||
const ast = parse(code)
|
const ast = assertParse(code)
|
||||||
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
|
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
|
||||||
const output = processMemory(execState.memory)
|
const output = processMemory(execState.memory)
|
||||||
expect(output.myVar).toEqual(5)
|
expect(output.myVar).toEqual(5)
|
||||||
expect(output.otherVar).toEqual(3)
|
expect(output.otherVar).toEqual(3)
|
||||||
expect(output).toEqual({
|
expect(output).toEqual({
|
||||||
|
HALF_TURN: 180,
|
||||||
|
QUARTER_TURN: 90,
|
||||||
|
THREE_QUARTER_TURN: 270,
|
||||||
|
ZERO: 0,
|
||||||
myVar: 5,
|
myVar: 5,
|
||||||
myFn: '__function(a)__',
|
myFn: '__function(a)__',
|
||||||
otherVar: 3,
|
otherVar: 3,
|
||||||
|
@ -90,7 +90,7 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
keybinding: 'Shift + C',
|
keybinding: 'Shift + C',
|
||||||
showBadge: {
|
showBadge: {
|
||||||
value: ({ kclContext }) => {
|
value: ({ kclContext }) => {
|
||||||
return kclContext.errors.length
|
return kclContext.diagnostics.length
|
||||||
},
|
},
|
||||||
onClick: (e) => {
|
onClick: (e) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
@ -53,7 +53,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
settings: settings.context,
|
settings: settings.context,
|
||||||
platform: getPlatformString(),
|
platform: getPlatformString(),
|
||||||
}),
|
}),
|
||||||
[kclContext.errors, settings.context]
|
[kclContext.diagnostics, settings.context]
|
||||||
)
|
)
|
||||||
|
|
||||||
const sidebarActions: SidebarAction[] = [
|
const sidebarActions: SidebarAction[] = [
|
||||||
|
@ -6,6 +6,7 @@ import { useNetworkContext } from '../hooks/useNetworkContext'
|
|||||||
import { NetworkHealthState } from '../hooks/useNetworkStatus'
|
import { NetworkHealthState } from '../hooks/useNetworkStatus'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
|
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||||
|
|
||||||
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
export const NETWORK_HEALTH_TEXT: Record<NetworkHealthState, string> = {
|
||||||
[NetworkHealthState.Ok]: 'Connected',
|
[NetworkHealthState.Ok]: 'Connected',
|
||||||
@ -64,14 +65,28 @@ const overallConnectionStateColor: Record<NetworkHealthState, IconColorConfig> =
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const overallConnectionStateIcon: Record<
|
const overallConnectionStateIcon = {
|
||||||
NetworkHealthState,
|
|
||||||
ActionIconProps['icon']
|
|
||||||
> = {
|
|
||||||
[NetworkHealthState.Ok]: 'network',
|
[NetworkHealthState.Ok]: 'network',
|
||||||
[NetworkHealthState.Weak]: 'network',
|
[NetworkHealthState.Weak]: 'network',
|
||||||
[NetworkHealthState.Issue]: 'networkCrossedOut',
|
[NetworkHealthState.Issue]: 'networkCrossedOut',
|
||||||
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
|
[NetworkHealthState.Disconnected]: 'networkCrossedOut',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const useNetworkHealthStatus = (): StatusBarItemType => {
|
||||||
|
const { overallState } = useNetworkContext()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'network-health',
|
||||||
|
label: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
|
||||||
|
hideLabel: true,
|
||||||
|
element: 'popover',
|
||||||
|
className: overallConnectionStateColor[overallState].icon,
|
||||||
|
toolTip: {
|
||||||
|
children: `Network health (${NETWORK_HEALTH_TEXT[overallState]})`,
|
||||||
|
},
|
||||||
|
icon: overallConnectionStateIcon[overallState],
|
||||||
|
popoverContent: <NetworkHealthPopoverContent />,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const NetworkHealthIndicator = () => {
|
export const NetworkHealthIndicator = () => {
|
||||||
@ -109,8 +124,28 @@ export const NetworkHealthIndicator = () => {
|
|||||||
Network health ({NETWORK_HEALTH_TEXT[overallState]})
|
Network health ({NETWORK_HEALTH_TEXT[overallState]})
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Popover.Button>
|
</Popover.Button>
|
||||||
<Popover.Panel
|
<Popover.Panel>
|
||||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
<NetworkHealthPopoverContent />
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const NetworkHealthPopoverContent = () => {
|
||||||
|
const {
|
||||||
|
hasIssues,
|
||||||
|
overallState,
|
||||||
|
internetConnected,
|
||||||
|
steps,
|
||||||
|
issues,
|
||||||
|
error,
|
||||||
|
setHasCopied,
|
||||||
|
hasCopied,
|
||||||
|
} = useNetworkContext()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="absolute left-2 bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||||
data-testid="network-popover"
|
data-testid="network-popover"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -126,19 +161,14 @@ export const NetworkHealthIndicator = () => {
|
|||||||
</div>
|
</div>
|
||||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
{Object.keys(steps).map((name) => (
|
{Object.keys(steps).map((name) => (
|
||||||
<li
|
<li key={name} className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}>
|
||||||
key={name}
|
|
||||||
className={'flex flex-col px-2 py-4 gap-1 last:mb-0 '}
|
|
||||||
>
|
|
||||||
<div className="flex items-center text-left gap-1">
|
<div className="flex items-center text-left gap-1">
|
||||||
<p className="flex-1">{name}</p>
|
<p className="flex-1">{name}</p>
|
||||||
{internetConnected ? (
|
{internetConnected ? (
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
size="lg"
|
size="lg"
|
||||||
icon={
|
icon={
|
||||||
hasIssueToIcon[
|
hasIssueToIcon[String(issues[name as ConnectingTypeGroup])]
|
||||||
String(issues[name as ConnectingTypeGroup])
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
iconClassName={
|
iconClassName={
|
||||||
hasIssueToIconColors[
|
hasIssueToIconColors[
|
||||||
@ -183,7 +213,6 @@ export const NetworkHealthIndicator = () => {
|
|||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</Popover.Panel>
|
</div>
|
||||||
</Popover>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import { components } from 'lib/machine-api'
|
import { components } from 'lib/machine-api'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
|
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||||
|
|
||||||
export const NetworkMachineIndicator = ({
|
export const NetworkMachineIndicator = ({
|
||||||
className,
|
className,
|
||||||
@ -27,12 +28,7 @@ export const NetworkMachineIndicator = ({
|
|||||||
}
|
}
|
||||||
data-testid="network-machine-toggle"
|
data-testid="network-machine-toggle"
|
||||||
>
|
>
|
||||||
<CustomIcon name="printer3d" className="w-5 h-5" />
|
<NetworkMachinesIcon machineCount={machineCount} />
|
||||||
{machineCount > 0 && (
|
|
||||||
<p aria-hidden className="flex items-center justify-center text-xs">
|
|
||||||
{machineCount}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
<Tooltip position="top-right" wrapperClassName="ui-open:hidden">
|
||||||
Network machines ({machineCount}) {reason && `: ${reason}`}
|
Network machines ({machineCount}) {reason && `: ${reason}`}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
@ -41,16 +37,59 @@ export const NetworkMachineIndicator = ({
|
|||||||
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
className="absolute right-0 left-auto bottom-full mb-1 w-64 flex flex-col gap-1 align-stretch bg-chalkboard-10 dark:bg-chalkboard-90 rounded shadow-lg border border-solid border-chalkboard-20/50 dark:border-chalkboard-80/50 text-sm"
|
||||||
data-testid="network-popover"
|
data-testid="network-popover"
|
||||||
>
|
>
|
||||||
|
<NetworkMachinesPopoverContent machines={machines} />
|
||||||
|
</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useNetworkMachineStatus = (): StatusBarItemType => {
|
||||||
|
const {
|
||||||
|
noMachinesReason,
|
||||||
|
machines,
|
||||||
|
machines: { length: machineCount },
|
||||||
|
} = useContext(MachineManagerContext)
|
||||||
|
const reason = noMachinesReason()
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: 'network-machines',
|
||||||
|
label: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
|
||||||
|
hideLabel: true,
|
||||||
|
element: 'popover',
|
||||||
|
toolTip: {
|
||||||
|
children: `Network machines (${machineCount}) ${reason && `: ${reason}`}`,
|
||||||
|
},
|
||||||
|
icon: 'printer3d',
|
||||||
|
popoverContent: <NetworkMachinesPopoverContent machines={machines} />,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function NetworkMachinesIcon({ machineCount }: { machineCount: number }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CustomIcon name="printer3d" className="w-5 h-5" />
|
||||||
|
{machineCount > 0 && (
|
||||||
|
<p aria-hidden className="flex items-center justify-center text-xs">
|
||||||
|
{machineCount}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function NetworkMachinesPopoverContent({ machines }: { machines: components['schemas']['MachineInfoResponse'][] }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
|
<div className="flex items-center justify-between p-2 rounded-t-sm bg-chalkboard-20 dark:bg-chalkboard-80">
|
||||||
<h2 className="text-sm font-sans font-normal">Network machines</h2>
|
<h2 className="text-sm font-sans font-normal">Network machines</h2>
|
||||||
<p
|
<p
|
||||||
data-testid="network"
|
data-testid="network"
|
||||||
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
className="font-bold text-xs uppercase px-2 py-1 rounded-sm"
|
||||||
>
|
>
|
||||||
{machineCount}
|
{machines.length}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{machineCount > 0 && (
|
{machines.length > 0 && (
|
||||||
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
<ul className="divide-y divide-chalkboard-20 dark:divide-chalkboard-80">
|
||||||
{machines.map(
|
{machines.map(
|
||||||
(machine: components['schemas']['MachineInfoResponse']) => {
|
(machine: components['schemas']['MachineInfoResponse']) => {
|
||||||
@ -84,7 +123,6 @@ export const NetworkMachineIndicator = ({
|
|||||||
)}
|
)}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</Popover.Panel>
|
</>
|
||||||
</Popover>
|
)
|
||||||
) : null
|
|
||||||
}
|
}
|
@ -68,8 +68,8 @@ function AppLogoLink({
|
|||||||
data-testid="app-logo"
|
data-testid="app-logo"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onProjectClose(file || null, project?.path || null, false)
|
onProjectClose(file || null, project?.path || null, false)
|
||||||
// Clear the scene and end the session.
|
// Clear the scene.
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.clearScene()
|
||||||
}}
|
}}
|
||||||
to={PATHS.HOME}
|
to={PATHS.HOME}
|
||||||
className={wrapperClassName + ' hover:before:brightness-110'}
|
className={wrapperClassName + ' hover:before:brightness-110'}
|
||||||
@ -190,8 +190,8 @@ function ProjectMenuPopover({
|
|||||||
className: !isDesktop() ? 'hidden' : '',
|
className: !isDesktop() ? 'hidden' : '',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
onProjectClose(file || null, project?.path || null, true)
|
onProjectClose(file || null, project?.path || null, true)
|
||||||
// Clear the scene and end the session.
|
// Clear the scene.
|
||||||
engineCommandManager.endSession()
|
engineCommandManager.clearScene()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].filter(
|
].filter(
|
||||||
|
@ -13,7 +13,7 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import { SettingsFieldInput } from './SettingsFieldInput'
|
import { SettingsFieldInput } from './SettingsFieldInput'
|
||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { APP_VERSION, PACKAGE_NAME } from 'routes/Settings'
|
import { APP_VERSION } from 'lib/appVersion'
|
||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import {
|
import {
|
||||||
createAndOpenNewTutorialProject,
|
createAndOpenNewTutorialProject,
|
||||||
@ -25,6 +25,7 @@ import { useLspContext } from 'components/LspProvider'
|
|||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
|
import { PACKAGE_NAME } from 'routes/Settings'
|
||||||
|
|
||||||
interface AllSettingsFieldsProps {
|
interface AllSettingsFieldsProps {
|
||||||
searchParamTab: SettingsLevel
|
searchParamTab: SettingsLevel
|
||||||
|
148
src/components/StatusBar.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { ActionButton } from './ActionButton'
|
||||||
|
import { StatusBarItemType } from './statusBar/statusBarTypes'
|
||||||
|
import Tooltip, { TooltipProps } from './Tooltip'
|
||||||
|
import { ActionIcon } from './ActionIcon'
|
||||||
|
import { Popover } from '@headlessui/react'
|
||||||
|
|
||||||
|
export function StatusBar({
|
||||||
|
globalItems,
|
||||||
|
localItems,
|
||||||
|
}: {
|
||||||
|
globalItems: StatusBarItemType[]
|
||||||
|
localItems: StatusBarItemType[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<footer
|
||||||
|
id="statusbar"
|
||||||
|
className="relative z-10 flex justify-between items-center bg-chalkboard-20 dark:bg-chalkboard-90 text-chalkboard-80 dark:text-chalkboard-30 border-t border-t-chalkboard-30 dark:border-t-chalkboard-80"
|
||||||
|
>
|
||||||
|
<menu id="statusbar-globals" className="flex items-stretch">
|
||||||
|
{globalItems.map((item) => (
|
||||||
|
<StatusBarItem key={item.id} {...item} position="left" />
|
||||||
|
))}
|
||||||
|
</menu>
|
||||||
|
<menu id="statusbar-locals" className="flex items-stretch">
|
||||||
|
{localItems.map((item) => (
|
||||||
|
<StatusBarItem key={item.id} {...item} position="right" />
|
||||||
|
))}
|
||||||
|
</menu>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function StatusBarItem(
|
||||||
|
props: StatusBarItemType & { position: 'left' | 'middle' | 'right' }
|
||||||
|
) {
|
||||||
|
const defaultClassNames = `px-2 py-1 text-xs text-chalkboard-80 dark:text-chalkboard-30 rounded-none border-none hover:bg-chalkboard-30 dark:hover:bg-chalkboard-80 focus:bg-chalkboard-30 dark:focus:bg-chalkboard-80 hover:text-chalkboard-100 dark:hover:text-chalkboard-10 focustext-chalkboard-100 dark:focus:text-chalkboard-10 focus:outline-none focus-visible:ring-2 focus:ring-primary focus:ring-opacity-50`
|
||||||
|
const tooltipPosition: TooltipProps['position'] =
|
||||||
|
props.position === 'middle' ? 'top' : `top-${props.position}`
|
||||||
|
|
||||||
|
switch (props.element) {
|
||||||
|
case 'button':
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
Element="button"
|
||||||
|
iconStart={
|
||||||
|
props.icon && {
|
||||||
|
icon: props.icon,
|
||||||
|
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
|
||||||
|
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
className={defaultClassNames + ' ' + props.className}
|
||||||
|
data-testid={props['data-testid']}
|
||||||
|
>
|
||||||
|
{props.label && (
|
||||||
|
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||||
|
{props.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{props.toolTip && (
|
||||||
|
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||||
|
)}
|
||||||
|
</ActionButton>
|
||||||
|
)
|
||||||
|
case 'popover':
|
||||||
|
return (
|
||||||
|
<Popover className="relative">
|
||||||
|
<Popover.Button
|
||||||
|
as={ActionButton}
|
||||||
|
Element="button"
|
||||||
|
iconStart={
|
||||||
|
props.icon && {
|
||||||
|
icon: props.icon,
|
||||||
|
iconClassName: props.icon === 'loading' ? 'animate-spin' : '',
|
||||||
|
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
className={defaultClassNames + ' ' + props.className}
|
||||||
|
data-testid={props['data-testid']}
|
||||||
|
>
|
||||||
|
{props.label && (
|
||||||
|
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||||
|
{props.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{props.toolTip && (
|
||||||
|
<Tooltip
|
||||||
|
{...props.toolTip}
|
||||||
|
wrapperClassName={`${
|
||||||
|
props.toolTip?.wrapperClassName || ''
|
||||||
|
} ui-open:hidden`}
|
||||||
|
position={tooltipPosition}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Popover.Button>
|
||||||
|
<Popover.Panel>{props.popoverContent}</Popover.Panel>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
case 'text':
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
role="tooltip"
|
||||||
|
className={defaultClassNames + ' ' + props.className}
|
||||||
|
>
|
||||||
|
{props.icon && (
|
||||||
|
<ActionIcon
|
||||||
|
icon={props.icon}
|
||||||
|
iconClassName={props.icon === 'loading' ? 'animate-spin' : ''}
|
||||||
|
bgClassName="bg-transparent dark:bg-transparent"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{props.label && (
|
||||||
|
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||||
|
{props.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{props.toolTip && (
|
||||||
|
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<ActionButton
|
||||||
|
Element={props.element}
|
||||||
|
to={props.href}
|
||||||
|
iconStart={
|
||||||
|
props.icon && {
|
||||||
|
icon: props.icon,
|
||||||
|
bgClassName: 'bg-transparent dark:bg-transparent',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
className={defaultClassNames + ' ' + props.className}
|
||||||
|
data-testid={props['data-testid']}
|
||||||
|
>
|
||||||
|
{props.label && (
|
||||||
|
<span className={props.hideLabel ? 'sr-only' : ''}>
|
||||||
|
{props.label}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{props.toolTip && (
|
||||||
|
<Tooltip {...props.toolTip} position={tooltipPosition} />
|
||||||
|
)}
|
||||||
|
</ActionButton>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,8 @@ import { useRouteLoaderData } from 'react-router-dom'
|
|||||||
import { PATHS } from 'lib/paths'
|
import { PATHS } from 'lib/paths'
|
||||||
import { IndexLoaderData } from 'lib/types'
|
import { IndexLoaderData } from 'lib/types'
|
||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
|
import { err, reportRejection } from 'lib/trap'
|
||||||
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
|
|
||||||
enum StreamState {
|
enum StreamState {
|
||||||
Playing = 'playing',
|
Playing = 'playing',
|
||||||
@ -280,12 +282,49 @@ export const Stream = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On double-click of sketch entities we automatically enter sketch mode with the selected sketch,
|
||||||
|
* allowing for quick editing of sketches. TODO: This should be moved to a more central place.
|
||||||
|
*/
|
||||||
|
const enterSketchModeIfSelectingSketch: MouseEventHandler<HTMLDivElement> = (
|
||||||
|
e
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
!isNetworkOkay ||
|
||||||
|
!videoRef.current ||
|
||||||
|
state.matches('Sketch') ||
|
||||||
|
state.matches({ idle: 'showPlanes' }) ||
|
||||||
|
sceneInfra.camControls.wasDragging === true ||
|
||||||
|
!btnName(e.nativeEvent).left
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sendSelectEventToEngine(e, videoRef.current)
|
||||||
|
.then(({ entity_id }) => {
|
||||||
|
if (!entity_id) {
|
||||||
|
// No entity selected. This is benign
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const path = getArtifactOfTypes(
|
||||||
|
{ key: entity_id, types: ['path', 'solid2D', 'segment'] },
|
||||||
|
engineCommandManager.artifactGraph
|
||||||
|
)
|
||||||
|
if (err(path)) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
sceneInfra.modelingSend({ type: 'Enter sketch' })
|
||||||
|
})
|
||||||
|
.catch(reportRejection)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
id="stream"
|
id="stream"
|
||||||
data-testid="stream"
|
data-testid="stream"
|
||||||
onClick={handleMouseUp}
|
onClick={handleMouseUp}
|
||||||
|
onDoubleClick={enterSketchModeIfSelectingSketch}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
>
|
>
|
||||||
|
@ -40,7 +40,10 @@ export function removeConstrainingValuesInfo({
|
|||||||
otherSelections: [],
|
otherSelections: [],
|
||||||
graphSelections: nodes.map(
|
graphSelections: nodes.map(
|
||||||
(node): Selection => ({
|
(node): Selection => ({
|
||||||
codeRef: codeRefFromRange([node.start, node.end], kclManager.ast),
|
codeRef: codeRefFromRange(
|
||||||
|
[node.start, node.end, true],
|
||||||
|
kclManager.ast
|
||||||
|
),
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
@ -8,7 +8,7 @@ type LeftOrRight = 'left' | 'right'
|
|||||||
type Corner = `${TopOrBottom}-${LeftOrRight}`
|
type Corner = `${TopOrBottom}-${LeftOrRight}`
|
||||||
type TooltipPosition = TopOrBottom | LeftOrRight | Corner
|
type TooltipPosition = TopOrBottom | LeftOrRight | Corner
|
||||||
|
|
||||||
interface TooltipProps extends React.PropsWithChildren {
|
export interface TooltipProps extends React.PropsWithChildren {
|
||||||
position?: TooltipPosition
|
position?: TooltipPosition
|
||||||
wrapperClassName?: string
|
wrapperClassName?: string
|
||||||
contentClassName?: string
|
contentClassName?: string
|
||||||
|
96
src/components/statusBar/homeDefaultStatusBarItems.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import openWindow from 'lib/openWindow'
|
||||||
|
import { StatusBarItemType } from './statusBarTypes'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
import { CoreDumpManager } from 'lib/coredump'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { coreDump } from 'lang/wasm'
|
||||||
|
import { APP_VERSION } from 'lib/appVersion'
|
||||||
|
import { Location } from 'react-router-dom'
|
||||||
|
import { PATHS } from 'lib/paths'
|
||||||
|
|
||||||
|
export const homeDefaultStatusBarItems = ({
|
||||||
|
coreDumpManager,
|
||||||
|
location,
|
||||||
|
}: {
|
||||||
|
coreDumpManager?: CoreDumpManager
|
||||||
|
location: Location
|
||||||
|
}): StatusBarItemType[] => [
|
||||||
|
{
|
||||||
|
id: 'version',
|
||||||
|
element: 'externalLink',
|
||||||
|
label: `v${APP_VERSION}`,
|
||||||
|
href: `https://github.com/KittyCAD/modeling-app/releases/tag/v${APP_VERSION}`,
|
||||||
|
toolTip: {
|
||||||
|
children: 'View the release notes on GitHub',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'report-bug',
|
||||||
|
element: 'button',
|
||||||
|
icon: 'bug',
|
||||||
|
label: 'Report a bug',
|
||||||
|
onClick: (event) => reportBug(event, { coreDumpManager }),
|
||||||
|
toolTip: {
|
||||||
|
children: 'Send your current app state to the developers for debugging',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'settings',
|
||||||
|
element: 'link',
|
||||||
|
icon: 'settings',
|
||||||
|
href:
|
||||||
|
'.' +
|
||||||
|
PATHS.SETTINGS +
|
||||||
|
(location.pathname.includes(PATHS.FILE) ? '?tab=project' : ''),
|
||||||
|
'data-testid': 'settings-link',
|
||||||
|
label: 'Settings',
|
||||||
|
toolTip: {
|
||||||
|
children: 'Settings',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function reportBug(
|
||||||
|
event: {
|
||||||
|
preventDefault: () => void
|
||||||
|
stopPropagation: () => void
|
||||||
|
},
|
||||||
|
dependencies: {
|
||||||
|
coreDumpManager: CoreDumpManager | undefined
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
event?.preventDefault()
|
||||||
|
event?.stopPropagation()
|
||||||
|
const { coreDumpManager } = dependencies
|
||||||
|
|
||||||
|
if (!coreDumpManager) {
|
||||||
|
// open default reporting option
|
||||||
|
openWindow(
|
||||||
|
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||||
|
).catch(reportRejection)
|
||||||
|
} else {
|
||||||
|
toast
|
||||||
|
.promise(
|
||||||
|
coreDump(coreDumpManager, true),
|
||||||
|
{
|
||||||
|
loading: 'Preparing bug report...',
|
||||||
|
success: 'Bug report opened in new window',
|
||||||
|
error: 'Unable to export a core dump. Using default reporting.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
success: {
|
||||||
|
// Note: this extended duration is especially important for Playwright e2e testing
|
||||||
|
// default duration is 2000 - https://react-hot-toast.com/docs/toast#default-durations
|
||||||
|
duration: 6000,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.catch((err: Error) => {
|
||||||
|
if (err) {
|
||||||
|
openWindow(
|
||||||
|
'https://github.com/KittyCAD/modeling-app/issues/new/choose'
|
||||||
|
).catch(reportRejection)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
28
src/components/statusBar/statusBarTypes.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
|
import { TooltipProps } from 'components/Tooltip'
|
||||||
|
|
||||||
|
export type StatusBarItemType = {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
icon?: CustomIconName
|
||||||
|
hideLabel?: boolean
|
||||||
|
toolTip?: Omit<TooltipProps, 'position'>
|
||||||
|
className?: string
|
||||||
|
['data-testid']?: string
|
||||||
|
} & (
|
||||||
|
| {
|
||||||
|
element: 'button'
|
||||||
|
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
element: 'popover'
|
||||||
|
popoverContent: React.ReactNode
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
element: 'link' | 'externalLink'
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
element: 'text'
|
||||||
|
}
|
||||||
|
)
|
@ -139,7 +139,9 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
setHighlightRange(range: Array<Selection['codeRef']['range']>): void {
|
||||||
this._highlightRange = range
|
this._highlightRange = range.map((s): [number, number] => {
|
||||||
|
return [s[0], s[1]]
|
||||||
|
})
|
||||||
|
|
||||||
const selectionsWithSafeEnds = range.map((s): [number, number] => {
|
const selectionsWithSafeEnds = range.map((s): [number, number] => {
|
||||||
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
const safeEnd = Math.min(s[1], this._editorView?.state.doc.length || s[1])
|
||||||
|
@ -18,7 +18,7 @@ import {
|
|||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
import { DefaultPlaneStr, getFaceDetails } from 'clientSideScene/sceneEntities'
|
||||||
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
|
||||||
import { CallExpression } from 'lang/wasm'
|
import { CallExpression, defaultSourceRange } from 'lang/wasm'
|
||||||
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
import { EdgeCutInfo, ExtrudeFacePlane } from 'machines/modelingMachine'
|
||||||
|
|
||||||
export function useEngineConnectionSubscriptions() {
|
export function useEngineConnectionSubscriptions() {
|
||||||
@ -46,7 +46,7 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
(editorManager.highlightRange[0][0] !== 0 &&
|
(editorManager.highlightRange[0][0] !== 0 &&
|
||||||
editorManager.highlightRange[0][1] !== 0)
|
editorManager.highlightRange[0][1] !== 0)
|
||||||
) {
|
) {
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([defaultSourceRange()])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -201,7 +201,7 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
const { z_axis, y_axis, origin } = faceInfo
|
const { z_axis, y_axis, origin } = faceInfo
|
||||||
const sketchPathToNode = getNodePathFromSourceRange(
|
const sketchPathToNode = getNodePathFromSourceRange(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
err(codeRef) ? [0, 0] : codeRef.range
|
err(codeRef) ? defaultSourceRange() : codeRef.range
|
||||||
)
|
)
|
||||||
|
|
||||||
const getEdgeCutMeta = (): null | EdgeCutInfo => {
|
const getEdgeCutMeta = (): null | EdgeCutInfo => {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { KCLError } from './errors'
|
|
||||||
import { createContext, useContext, useEffect, useState } from 'react'
|
import { createContext, useContext, useEffect, useState } from 'react'
|
||||||
import { type IndexLoaderData } from 'lib/types'
|
import { type IndexLoaderData } from 'lib/types'
|
||||||
import { useLoaderData } from 'react-router-dom'
|
import { useLoaderData } from 'react-router-dom'
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
import { Diagnostic } from '@codemirror/lint'
|
||||||
|
|
||||||
const KclContext = createContext({
|
const KclContext = createContext({
|
||||||
code: codeManager?.code || '',
|
code: codeManager?.code || '',
|
||||||
programMemory: kclManager?.programMemory,
|
programMemory: kclManager?.programMemory,
|
||||||
ast: kclManager?.ast,
|
ast: kclManager?.ast,
|
||||||
isExecuting: kclManager?.isExecuting,
|
isExecuting: kclManager?.isExecuting,
|
||||||
errors: kclManager?.kclErrors,
|
diagnostics: kclManager?.diagnostics,
|
||||||
logs: kclManager?.logs,
|
logs: kclManager?.logs,
|
||||||
wasmInitFailed: kclManager?.wasmInitFailed,
|
wasmInitFailed: kclManager?.wasmInitFailed,
|
||||||
})
|
})
|
||||||
@ -32,7 +32,7 @@ export function KclContextProvider({
|
|||||||
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
|
||||||
const [ast, setAst] = useState(kclManager.ast)
|
const [ast, setAst] = useState(kclManager.ast)
|
||||||
const [isExecuting, setIsExecuting] = useState(false)
|
const [isExecuting, setIsExecuting] = useState(false)
|
||||||
const [errors, setErrors] = useState<KCLError[]>([])
|
const [diagnostics, setErrors] = useState<Diagnostic[]>([])
|
||||||
const [logs, setLogs] = useState<string[]>([])
|
const [logs, setLogs] = useState<string[]>([])
|
||||||
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
const [wasmInitFailed, setWasmInitFailed] = useState(false)
|
||||||
|
|
||||||
@ -57,7 +57,7 @@ export function KclContextProvider({
|
|||||||
programMemory,
|
programMemory,
|
||||||
ast,
|
ast,
|
||||||
isExecuting,
|
isExecuting,
|
||||||
errors,
|
diagnostics,
|
||||||
logs,
|
logs,
|
||||||
wasmInitFailed,
|
wasmInitFailed,
|
||||||
}}
|
}}
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
import { executeAst, lintAst } from 'lang/langHelpers'
|
import { executeAst, lintAst } from 'lang/langHelpers'
|
||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { KCLError, kclErrorsToDiagnostics } from './errors'
|
import {
|
||||||
|
KCLError,
|
||||||
|
complilationErrorsToDiagnostics,
|
||||||
|
kclErrorsToDiagnostics,
|
||||||
|
} from './errors'
|
||||||
import { uuidv4 } from 'lib/utils'
|
import { uuidv4 } from 'lib/utils'
|
||||||
import { EngineCommandManager } from './std/engineConnection'
|
import { EngineCommandManager } from './std/engineConnection'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
@ -51,11 +55,11 @@ export class KclManager {
|
|||||||
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
private _programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
|
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
private _logs: string[] = []
|
private _logs: string[] = []
|
||||||
private _lints: Diagnostic[] = []
|
private _diagnostics: Diagnostic[] = []
|
||||||
private _kclErrors: KCLError[] = []
|
|
||||||
private _isExecuting = false
|
private _isExecuting = false
|
||||||
private _executeIsStale: ExecuteArgs | null = null
|
private _executeIsStale: ExecuteArgs | null = null
|
||||||
private _wasmInitFailed = true
|
private _wasmInitFailed = true
|
||||||
|
private _hasErrors = false
|
||||||
|
|
||||||
engineCommandManager: EngineCommandManager
|
engineCommandManager: EngineCommandManager
|
||||||
|
|
||||||
@ -63,7 +67,7 @@ export class KclManager {
|
|||||||
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
private _astCallBack: (arg: Node<Program>) => void = () => {}
|
||||||
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
|
||||||
private _logsCallBack: (arg: string[]) => void = () => {}
|
private _logsCallBack: (arg: string[]) => void = () => {}
|
||||||
private _kclErrorsCallBack: (arg: KCLError[]) => void = () => {}
|
private _kclErrorsCallBack: (errors: Diagnostic[]) => void = () => {}
|
||||||
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
private _wasmInitFailedCallback: (arg: boolean) => void = () => {}
|
||||||
private _executeCallback: () => void = () => {}
|
private _executeCallback: () => void = () => {}
|
||||||
|
|
||||||
@ -84,7 +88,7 @@ export class KclManager {
|
|||||||
this._programMemoryCallBack(programMemory)
|
this._programMemoryCallBack(programMemory)
|
||||||
}
|
}
|
||||||
|
|
||||||
set execState(execState) {
|
private set execState(execState) {
|
||||||
this._execState = execState
|
this._execState = execState
|
||||||
this.programMemory = execState.memory
|
this.programMemory = execState.memory
|
||||||
}
|
}
|
||||||
@ -101,38 +105,28 @@ export class KclManager {
|
|||||||
this._logsCallBack(logs)
|
this._logsCallBack(logs)
|
||||||
}
|
}
|
||||||
|
|
||||||
get lints() {
|
get diagnostics() {
|
||||||
return this._lints
|
return this._diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
set lints(lints) {
|
set diagnostics(ds) {
|
||||||
if (lints === this._lints) return
|
if (ds === this._diagnostics) return
|
||||||
this._lints = lints
|
this._diagnostics = ds
|
||||||
// Run the lints through the diagnostics.
|
|
||||||
this.kclErrors = this._kclErrors
|
|
||||||
}
|
|
||||||
|
|
||||||
get kclErrors() {
|
|
||||||
return this._kclErrors
|
|
||||||
}
|
|
||||||
set kclErrors(kclErrors) {
|
|
||||||
if (kclErrors === this._kclErrors && this.lints.length === 0) return
|
|
||||||
this._kclErrors = kclErrors
|
|
||||||
this.setDiagnosticsForCurrentErrors()
|
this.setDiagnosticsForCurrentErrors()
|
||||||
this._kclErrorsCallBack(kclErrors)
|
}
|
||||||
|
|
||||||
|
addDiagnostics(ds: Diagnostic[]) {
|
||||||
|
if (ds.length === 0) return
|
||||||
|
this.diagnostics = this.diagnostics.concat(ds)
|
||||||
|
}
|
||||||
|
|
||||||
|
hasErrors(): boolean {
|
||||||
|
return this._hasErrors
|
||||||
}
|
}
|
||||||
|
|
||||||
setDiagnosticsForCurrentErrors() {
|
setDiagnosticsForCurrentErrors() {
|
||||||
let diagnostics = kclErrorsToDiagnostics(this.kclErrors)
|
editorManager?.setDiagnostics(this.diagnostics)
|
||||||
if (this.lints.length > 0) {
|
this._kclErrorsCallBack(this.diagnostics)
|
||||||
diagnostics = diagnostics.concat(this.lints)
|
|
||||||
}
|
|
||||||
editorManager?.setDiagnostics(diagnostics)
|
|
||||||
}
|
|
||||||
|
|
||||||
addKclErrors(kclErrors: KCLError[]) {
|
|
||||||
if (kclErrors.length === 0) return
|
|
||||||
this.kclErrors = this.kclErrors.concat(kclErrors)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get isExecuting() {
|
get isExecuting() {
|
||||||
@ -188,7 +182,7 @@ export class KclManager {
|
|||||||
setProgramMemory: (arg: ProgramMemory) => void
|
setProgramMemory: (arg: ProgramMemory) => void
|
||||||
setAst: (arg: Node<Program>) => void
|
setAst: (arg: Node<Program>) => void
|
||||||
setLogs: (arg: string[]) => void
|
setLogs: (arg: string[]) => void
|
||||||
setKclErrors: (arg: KCLError[]) => void
|
setKclErrors: (errors: Diagnostic[]) => void
|
||||||
setIsExecuting: (arg: boolean) => void
|
setIsExecuting: (arg: boolean) => void
|
||||||
setWasmInitFailed: (arg: boolean) => void
|
setWasmInitFailed: (arg: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
@ -218,19 +212,28 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
safeParse(code: string): Node<Program> | null {
|
safeParse(code: string): Node<Program> | null {
|
||||||
const ast = parse(code)
|
const result = parse(code)
|
||||||
this.lints = []
|
this.diagnostics = []
|
||||||
this.kclErrors = []
|
this._hasErrors = false
|
||||||
if (!err(ast)) return ast
|
|
||||||
const kclerror: KCLError = ast as KCLError
|
|
||||||
|
|
||||||
this.addKclErrors([kclerror])
|
if (err(result)) {
|
||||||
// TODO: re-eval if session should end?
|
const kclerror: KCLError = result as KCLError
|
||||||
if (kclerror.msg === 'file is empty')
|
this.diagnostics = kclErrorsToDiagnostics([kclerror])
|
||||||
this.engineCommandManager?.endSession()
|
this._hasErrors = true
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.addDiagnostics(complilationErrorsToDiagnostics(result.errors))
|
||||||
|
this.addDiagnostics(complilationErrorsToDiagnostics(result.warnings))
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
this._hasErrors = true
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.program
|
||||||
|
}
|
||||||
|
|
||||||
async ensureWasmInit() {
|
async ensureWasmInit() {
|
||||||
try {
|
try {
|
||||||
await initPromise
|
await initPromise
|
||||||
@ -267,19 +270,16 @@ export class KclManager {
|
|||||||
this._cancelTokens.set(currentExecutionId, false)
|
this._cancelTokens.set(currentExecutionId, false)
|
||||||
|
|
||||||
this.isExecuting = true
|
this.isExecuting = true
|
||||||
// Make sure we clear before starting again. End session will do this.
|
|
||||||
this.engineCommandManager?.endSession()
|
|
||||||
await this.ensureWasmInit()
|
await this.ensureWasmInit()
|
||||||
const { logs, errors, execState, isInterrupted } = await executeAst({
|
const { logs, errors, execState, isInterrupted } = await executeAst({
|
||||||
ast,
|
ast,
|
||||||
idGenerator: this.execState.idGenerator,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Program was not interrupted, setup the scene
|
// Program was not interrupted, setup the scene
|
||||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
this.lints = await lintAst({ ast: ast })
|
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||||
|
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||||
@ -321,9 +321,7 @@ export class KclManager {
|
|||||||
|
|
||||||
this.logs = logs
|
this.logs = logs
|
||||||
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
// Do not add the errors since the program was interrupted and the error is not a real KCL error
|
||||||
this.addKclErrors(isInterrupted ? [] : errors)
|
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
|
||||||
// Reset the next ID index so that we reuse the previous IDs next time.
|
|
||||||
execState.idGenerator.nextId = 0
|
|
||||||
this.execState = execState
|
this.execState = execState
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
@ -364,13 +362,13 @@ export class KclManager {
|
|||||||
|
|
||||||
const { logs, errors, execState } = await executeAst({
|
const { logs, errors, execState } = await executeAst({
|
||||||
ast: newAst,
|
ast: newAst,
|
||||||
idGenerator: this.execState.idGenerator,
|
|
||||||
engineCommandManager: this.engineCommandManager,
|
engineCommandManager: this.engineCommandManager,
|
||||||
useFakeExecutor: true,
|
// We make sure to send an empty program memory to denote we mean mock mode.
|
||||||
|
programMemoryOverride: ProgramMemory.empty(),
|
||||||
})
|
})
|
||||||
|
|
||||||
this._logs = logs
|
this._logs = logs
|
||||||
this._kclErrors = errors
|
this.addDiagnostics(kclErrorsToDiagnostics(errors))
|
||||||
this._execState = execState
|
this._execState = execState
|
||||||
this._programMemory = execState.memory
|
this._programMemory = execState.memory
|
||||||
if (!errors.length) {
|
if (!errors.length) {
|
||||||
@ -398,7 +396,7 @@ export class KclManager {
|
|||||||
...artifact,
|
...artifact,
|
||||||
codeRef: {
|
codeRef: {
|
||||||
...artifact.codeRef,
|
...artifact.codeRef,
|
||||||
range: [node.start, node.end],
|
range: [node.start, node.end, true],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -490,7 +488,7 @@ export class KclManager {
|
|||||||
if (start && end) {
|
if (start && end) {
|
||||||
returnVal.graphSelections.push({
|
returnVal.graphSelections.push({
|
||||||
codeRef: {
|
codeRef: {
|
||||||
range: [start, end],
|
range: [start, end, true],
|
||||||
pathToNode: path,
|
pathToNode: path,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { parse, initPromise } from './wasm'
|
import { assertParse, initPromise } from './wasm'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -14,7 +14,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([-1.59, -1.54], %)
|
|> lineTo([-1.59, -1.54], %)
|
||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)`
|
// |> rx(45, %)`
|
||||||
const execState = await enginelessExecutor(parse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = execState.memory.get('mySketch001')
|
const sketch001 = execState.memory.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
@ -67,7 +67,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
|> lineTo([0.46, -5.82], %)
|
|> lineTo([0.46, -5.82], %)
|
||||||
// |> rx(45, %)
|
// |> rx(45, %)
|
||||||
|> extrude(2, %)`
|
|> extrude(2, %)`
|
||||||
const execState = await enginelessExecutor(parse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = execState.memory.get('mySketch001')
|
const sketch001 = execState.memory.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
@ -147,7 +147,7 @@ const sk2 = startSketchOn('XY')
|
|||||||
|> extrude(2, %)
|
|> extrude(2, %)
|
||||||
|
|
||||||
`
|
`
|
||||||
const execState = await enginelessExecutor(parse(code))
|
const execState = await enginelessExecutor(assertParse(code))
|
||||||
const programMemory = execState.memory
|
const programMemory = execState.memory
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
|
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
|
||||||
|
@ -8,20 +8,14 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
message: '',
|
message: '',
|
||||||
kind: 'semantic',
|
kind: 'semantic',
|
||||||
msg: 'Semantic error',
|
msg: 'Semantic error',
|
||||||
sourceRanges: [
|
sourceRange: [0, 1, true],
|
||||||
[0, 1, 0],
|
|
||||||
[2, 3, 0],
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
message: '',
|
message: '',
|
||||||
kind: 'type',
|
kind: 'type',
|
||||||
msg: 'Type error',
|
msg: 'Type error',
|
||||||
sourceRanges: [
|
sourceRange: [4, 5, true],
|
||||||
[4, 5, 0],
|
|
||||||
[6, 7, 0],
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
const diagnostics = kclErrorsToDiagnostics(errors)
|
const diagnostics = kclErrorsToDiagnostics(errors)
|
||||||
@ -32,24 +26,12 @@ describe('test kclErrToDiagnostic', () => {
|
|||||||
message: 'Semantic error',
|
message: 'Semantic error',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
from: 2,
|
|
||||||
to: 3,
|
|
||||||
message: 'Semantic error',
|
|
||||||
severity: 'error',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
from: 4,
|
from: 4,
|
||||||
to: 5,
|
to: 5,
|
||||||
message: 'Type error',
|
message: 'Type error',
|
||||||
severity: 'error',
|
severity: 'error',
|
||||||
},
|
},
|
||||||
{
|
|
||||||
from: 6,
|
|
||||||
to: 7,
|
|
||||||
message: 'Type error',
|
|
||||||
severity: 'error',
|
|
||||||
},
|
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,88 +1,90 @@
|
|||||||
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
import { KclError as RustKclError } from '../wasm-lib/kcl/bindings/KclError'
|
||||||
|
import { CompilationError } from 'wasm-lib/kcl/bindings/CompilationError'
|
||||||
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
|
import { Diagnostic as CodeMirrorDiagnostic } from '@codemirror/lint'
|
||||||
import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
import { posToOffset } from '@kittycad/codemirror-lsp-client'
|
||||||
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
import { Diagnostic as LspDiagnostic } from 'vscode-languageserver-protocol'
|
||||||
import { Text } from '@codemirror/state'
|
import { Text } from '@codemirror/state'
|
||||||
|
import { EditorView } from 'codemirror'
|
||||||
const TOP_LEVEL_MODULE_ID = 0
|
import { SourceRange } from 'lang/wasm'
|
||||||
|
|
||||||
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
type ExtractKind<T> = T extends { kind: infer K } ? K : never
|
||||||
export class KCLError extends Error {
|
export class KCLError extends Error {
|
||||||
kind: ExtractKind<RustKclError> | 'name'
|
kind: ExtractKind<RustKclError> | 'name'
|
||||||
sourceRanges: [number, number, number][]
|
sourceRange: SourceRange
|
||||||
msg: string
|
msg: string
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
kind: ExtractKind<RustKclError> | 'name',
|
kind: ExtractKind<RustKclError> | 'name',
|
||||||
msg: string,
|
msg: string,
|
||||||
sourceRanges: [number, number, number][]
|
sourceRange: SourceRange
|
||||||
) {
|
) {
|
||||||
super()
|
super()
|
||||||
this.kind = kind
|
this.kind = kind
|
||||||
this.msg = msg
|
this.msg = msg
|
||||||
this.sourceRanges = sourceRanges
|
this.sourceRange = sourceRange
|
||||||
Object.setPrototypeOf(this, KCLError.prototype)
|
Object.setPrototypeOf(this, KCLError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLLexicalError extends KCLError {
|
export class KCLLexicalError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('lexical', msg, sourceRanges)
|
super('lexical', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLInternalError extends KCLError {
|
export class KCLInternalError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('internal', msg, sourceRanges)
|
super('internal', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLSyntaxError extends KCLError {
|
export class KCLSyntaxError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('syntax', msg, sourceRanges)
|
super('syntax', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
Object.setPrototypeOf(this, KCLSyntaxError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLSemanticError extends KCLError {
|
export class KCLSemanticError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('semantic', msg, sourceRanges)
|
super('semantic', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
Object.setPrototypeOf(this, KCLSemanticError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLTypeError extends KCLError {
|
export class KCLTypeError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('type', msg, sourceRanges)
|
super('type', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
Object.setPrototypeOf(this, KCLTypeError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUnimplementedError extends KCLError {
|
export class KCLUnimplementedError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('unimplemented', msg, sourceRanges)
|
super('unimplemented', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
Object.setPrototypeOf(this, KCLUnimplementedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUnexpectedError extends KCLError {
|
export class KCLUnexpectedError extends KCLError {
|
||||||
constructor(msg: string, sourceRanges: [number, number, number][]) {
|
constructor(msg: string, sourceRange: SourceRange) {
|
||||||
super('unexpected', msg, sourceRanges)
|
super('unexpected', msg, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
Object.setPrototypeOf(this, KCLUnexpectedError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLValueAlreadyDefined extends KCLError {
|
export class KCLValueAlreadyDefined extends KCLError {
|
||||||
constructor(key: string, sourceRanges: [number, number, number][]) {
|
constructor(key: string, sourceRange: SourceRange) {
|
||||||
super('name', `Key ${key} was already defined elsewhere`, sourceRanges)
|
super('name', `Key ${key} was already defined elsewhere`, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
Object.setPrototypeOf(this, KCLValueAlreadyDefined.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class KCLUndefinedValueError extends KCLError {
|
export class KCLUndefinedValueError extends KCLError {
|
||||||
constructor(key: string, sourceRanges: [number, number, number][]) {
|
constructor(key: string, sourceRange: SourceRange) {
|
||||||
super('name', `Key ${key} has not been defined`, sourceRanges)
|
super('name', `Key ${key} has not been defined`, sourceRange)
|
||||||
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
Object.setPrototypeOf(this, KCLUndefinedValueError.prototype)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,27 +101,14 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
.flatMap(
|
.flatMap(
|
||||||
({ range, message }) =>
|
({ range, message }) =>
|
||||||
new KCLError('unexpected', message, [
|
new KCLError('unexpected', message, [
|
||||||
[
|
|
||||||
posToOffset(doc, range.start)!,
|
posToOffset(doc, range.start)!,
|
||||||
posToOffset(doc, range.end)!,
|
posToOffset(doc, range.end)!,
|
||||||
TOP_LEVEL_MODULE_ID,
|
true,
|
||||||
],
|
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
.filter(({ sourceRanges }) => {
|
|
||||||
const [from, to, moduleId] = sourceRanges[0]
|
|
||||||
return (
|
|
||||||
from !== null &&
|
|
||||||
to !== null &&
|
|
||||||
from !== undefined &&
|
|
||||||
to !== undefined &&
|
|
||||||
// Filter out errors that are not from the top-level module.
|
|
||||||
moduleId === TOP_LEVEL_MODULE_ID
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
.sort((a, b) => {
|
||||||
const c = a.sourceRanges[0][0]
|
const c = a.sourceRange[0]
|
||||||
const d = b.sourceRanges[0][0]
|
const d = b.sourceRange[0]
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case c < d:
|
case c < d:
|
||||||
return -1
|
return -1
|
||||||
@ -137,17 +126,48 @@ export function lspDiagnosticsToKclErrors(
|
|||||||
export function kclErrorsToDiagnostics(
|
export function kclErrorsToDiagnostics(
|
||||||
errors: KCLError[]
|
errors: KCLError[]
|
||||||
): CodeMirrorDiagnostic[] {
|
): CodeMirrorDiagnostic[] {
|
||||||
return errors?.flatMap((err) => {
|
return errors
|
||||||
const sourceRanges: CodeMirrorDiagnostic[] = err.sourceRanges
|
?.filter((err) => err.sourceRange[2])
|
||||||
// Filter out errors that are not from the top-level module.
|
.map((err) => {
|
||||||
.filter(([_start, _end, moduleId]) => moduleId === TOP_LEVEL_MODULE_ID)
|
return {
|
||||||
.map(([from, to]) => {
|
from: err.sourceRange[0],
|
||||||
return { from, to, message: err.msg, severity: 'error' }
|
to: err.sourceRange[1],
|
||||||
})
|
message: err.msg,
|
||||||
// Make sure we didn't filter out all the source ranges.
|
severity: 'error',
|
||||||
if (sourceRanges.length === 0) {
|
}
|
||||||
sourceRanges.push({ from: 0, to: 0, message: err.msg, severity: 'error' })
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export function complilationErrorsToDiagnostics(
|
||||||
|
errors: CompilationError[]
|
||||||
|
): CodeMirrorDiagnostic[] {
|
||||||
|
return errors
|
||||||
|
?.filter((err) => err.sourceRange[2] === 0)
|
||||||
|
.map((err) => {
|
||||||
|
let severity: any = 'error'
|
||||||
|
if (err.severity === 'Warning') {
|
||||||
|
severity = 'warning'
|
||||||
|
}
|
||||||
|
let actions
|
||||||
|
const suggestion = err.suggestion
|
||||||
|
if (suggestion) {
|
||||||
|
actions = [
|
||||||
|
{
|
||||||
|
name: suggestion.title,
|
||||||
|
apply: (view: EditorView, from: number, to: number) => {
|
||||||
|
view.dispatch({
|
||||||
|
changes: { from, to, insert: suggestion.insert },
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
from: err.sourceRange[0],
|
||||||
|
to: err.sourceRange[1],
|
||||||
|
message: err.message,
|
||||||
|
severity,
|
||||||
|
actions,
|
||||||
}
|
}
|
||||||
return sourceRanges
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import fs from 'node:fs'
|
import fs from 'node:fs'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
parse,
|
assertParse,
|
||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
Sketch,
|
Sketch,
|
||||||
initPromise,
|
initPromise,
|
||||||
@ -472,7 +472,7 @@ describe('Testing Errors', () => {
|
|||||||
const theExtrude = startSketchOn('XY')
|
const theExtrude = startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([-2.4, 5], %)
|
|> line([-2.4, 5], %)
|
||||||
|> line([-0.76], myVarZ, %)
|
|> line(myVarZ, %)
|
||||||
|> line([5,5], %)
|
|> line([5,5], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|> extrude(4, %)`
|
|> extrude(4, %)`
|
||||||
@ -480,7 +480,7 @@ const theExtrude = startSketchOn('XY')
|
|||||||
new KCLError(
|
new KCLError(
|
||||||
'undefined_value',
|
'undefined_value',
|
||||||
'memory item key `myVarZ` is not defined',
|
'memory item key `myVarZ` is not defined',
|
||||||
[[129, 135, 0]]
|
[129, 135, true]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -492,7 +492,7 @@ async function exe(
|
|||||||
code: string,
|
code: string,
|
||||||
programMemory: ProgramMemory = ProgramMemory.empty()
|
programMemory: ProgramMemory = ProgramMemory.empty()
|
||||||
) {
|
) {
|
||||||
const ast = parse(code)
|
const ast = assertParse(code)
|
||||||
|
|
||||||
const execState = await enginelessExecutor(ast, programMemory)
|
const execState = await enginelessExecutor(ast, programMemory)
|
||||||
return execState.memory
|
return execState.memory
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
import { getNodePathFromSourceRange, getNodeFromPath } from './queryAst'
|
||||||
import { Identifier, parse, initPromise, Parameter } from './wasm'
|
import { Identifier, assertParse, initPromise, Parameter } from './wasm'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
@ -17,19 +17,19 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
`
|
`
|
||||||
const subStr = 'lineTo([3, 4], %, $yo)'
|
const subStr = 'lineTo([3, 4], %, $yo)'
|
||||||
const lineToSubstringIndex = code.indexOf(subStr)
|
const lineToSubstringIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number] = [
|
const sourceRange: [number, number, boolean] = [
|
||||||
lineToSubstringIndex,
|
lineToSubstringIndex,
|
||||||
lineToSubstringIndex + subStr.length,
|
lineToSubstringIndex + subStr.length,
|
||||||
|
true,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = parse(code)
|
const ast = assertParse(code)
|
||||||
if (err(ast)) throw ast
|
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const _node = getNodeFromPath<any>(ast, nodePath)
|
const _node = getNodeFromPath<any>(ast, nodePath)
|
||||||
if (err(_node)) throw _node
|
if (err(_node)) throw _node
|
||||||
const { node } = _node
|
const { node } = _node
|
||||||
|
|
||||||
expect([node.start, node.end]).toEqual(sourceRange)
|
expect([node.start, node.end, true]).toEqual(sourceRange)
|
||||||
expect(node.type).toBe('CallExpression')
|
expect(node.type).toBe('CallExpression')
|
||||||
})
|
})
|
||||||
it('gets path right for function definition params', () => {
|
it('gets path right for function definition params', () => {
|
||||||
@ -45,13 +45,13 @@ const sk3 = startSketchAt([0, 0])
|
|||||||
const b1 = cube([0,0], 10)`
|
const b1 = cube([0,0], 10)`
|
||||||
const subStr = 'pos, scale'
|
const subStr = 'pos, scale'
|
||||||
const subStrIndex = code.indexOf(subStr)
|
const subStrIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number] = [
|
const sourceRange: [number, number, boolean] = [
|
||||||
subStrIndex,
|
subStrIndex,
|
||||||
subStrIndex + 'pos'.length,
|
subStrIndex + 'pos'.length,
|
||||||
|
true,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = parse(code)
|
const ast = assertParse(code)
|
||||||
if (err(ast)) throw ast
|
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const _node = getNodeFromPath<Parameter>(ast, nodePath)
|
const _node = getNodeFromPath<Parameter>(ast, nodePath)
|
||||||
if (err(_node)) throw _node
|
if (err(_node)) throw _node
|
||||||
@ -82,13 +82,13 @@ const b1 = cube([0,0], 10)`
|
|||||||
const b1 = cube([0,0], 10)`
|
const b1 = cube([0,0], 10)`
|
||||||
const subStr = 'scale, 0'
|
const subStr = 'scale, 0'
|
||||||
const subStrIndex = code.indexOf(subStr)
|
const subStrIndex = code.indexOf(subStr)
|
||||||
const sourceRange: [number, number] = [
|
const sourceRange: [number, number, boolean] = [
|
||||||
subStrIndex,
|
subStrIndex,
|
||||||
subStrIndex + 'scale'.length,
|
subStrIndex + 'scale'.length,
|
||||||
|
true,
|
||||||
]
|
]
|
||||||
|
|
||||||
const ast = parse(code)
|
const ast = assertParse(code)
|
||||||
if (err(ast)) throw ast
|
|
||||||
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
const nodePath = getNodePathFromSourceRange(ast, sourceRange)
|
||||||
const _node = getNodeFromPath<Identifier>(ast, nodePath)
|
const _node = getNodeFromPath<Identifier>(ast, nodePath)
|
||||||
if (err(_node)) throw _node
|
if (err(_node)) throw _node
|
||||||
|