Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
dce1dca7c7 | |||
343f6572a2 | |||
c8e58d49e0 | |||
c18546fb2f | |||
466c23a9d8 | |||
8403025c77 | |||
9ce591a1a1 | |||
f238f3882b | |||
a91208eb1c | |||
b92b0a9afe | |||
964347f43a |
2
.github/workflows/cargo-test.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |-
|
run: |-
|
||||||
cd "${{ matrix.dir }}"
|
cd "${{ matrix.dir }}"
|
||||||
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --retries=2 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --retries=2 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||||
env:
|
env:
|
||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
|
1
.github/workflows/e2e-tests.yml
vendored
@ -50,7 +50,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version-file: '.nvmrc'
|
node-version-file: '.nvmrc'
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- uses: KittyCAD/action-install-cli@main
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
run: yarn
|
run: yarn
|
||||||
|
24
README.md
@ -40,7 +40,7 @@ The 3D view in Modeling App is just a video stream from our hosted geometry engi
|
|||||||
|
|
||||||
[Original demo video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
[Original demo video](https://drive.google.com/file/d/183_wjqGdzZ8EEZXSqZ3eDcJocYPCyOdC/view?pli=1)
|
||||||
|
|
||||||
[Original demo slides](https://github.com/KittyCAD/Eng/files/10398178/demo.pdf)
|
[Original demo slides](https://github.com/user-attachments/files/19010536/demo.pdf)
|
||||||
|
|
||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
@ -52,18 +52,21 @@ Install a node version manager such as [fnm](https://github.com/Schniz/fnm?tab=r
|
|||||||
|
|
||||||
On Windows, it's also recommended to [upgrade your PowerShell version](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5#winget), we're using 7.
|
On Windows, it's also recommended to [upgrade your PowerShell version](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.5#winget), we're using 7.
|
||||||
|
|
||||||
Then in the repo run the following to install and use the node version specified in `.nvmrc`. You might need to specify your processor architecture with `--arch arm64` or `--arch x64` if it's not autodetected.
|
Then in the repo run the following to install and use the node version specified in `.nvmrc`. You might need to specify your processor architecture with `--arch arm64` or `--arch x64` if it's not autodetected.
|
||||||
|
|
||||||
```
|
```
|
||||||
fnm install --corepack-enabled
|
fnm install --corepack-enabled
|
||||||
fnm use
|
fnm use
|
||||||
```
|
```
|
||||||
|
|
||||||
Install the NPM dependencies with:
|
Install the NPM dependencies with:
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn install
|
yarn install
|
||||||
```
|
```
|
||||||
|
|
||||||
This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We have package scripts to run rustup, see `package.json` for reference:
|
This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We have package scripts to run rustup, see `package.json` for reference:
|
||||||
|
|
||||||
```
|
```
|
||||||
# macOS/Linux
|
# macOS/Linux
|
||||||
yarn install:rust
|
yarn install:rust
|
||||||
@ -75,6 +78,7 @@ yarn install:wasm-pack:cargo
|
|||||||
```
|
```
|
||||||
|
|
||||||
Then to build the WASM layer, run:
|
Then to build the WASM layer, run:
|
||||||
|
|
||||||
```
|
```
|
||||||
# macOS/Linux
|
# macOS/Linux
|
||||||
yarn build:wasm
|
yarn build:wasm
|
||||||
@ -135,6 +139,7 @@ To package the app for your platform with electron-builder, run `yarn tronb:pack
|
|||||||
Which commands from setup are one off vs need to be run every time?
|
Which commands from setup are one off vs need to be run every time?
|
||||||
|
|
||||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:wasm
|
yarn build:wasm
|
||||||
@ -185,11 +190,12 @@ Manually test against this [list](https://github.com/KittyCAD/modeling-app/issue
|
|||||||
|
|
||||||
##### Updater-test builds
|
##### Updater-test builds
|
||||||
|
|
||||||
The other `build-apps` output in the release `build-apps` workflow (triggered by 2.) is `updater-test-{arch}-{platform}`. It's a semi-automated process: for macOS, Windows, and Linux, download the corresponding updater-test artifact file, install the app, run it, expect an updater prompt to a dummy v0.255.255, install it and check that the app comes back at that version.
|
The other `build-apps` output in the release `build-apps` workflow (triggered by 2.) is `updater-test-{arch}-{platform}`. It's a semi-automated process: for macOS, Windows, and Linux, download the corresponding updater-test artifact file, install the app, run it, expect an updater prompt to a dummy v0.255.255, install it and check that the app comes back at that version.
|
||||||
|
|
||||||
The only difference with these builds is that they point to a different update location on the release bucket, with this dummy v0.255.255 always available. This helps ensuring that the version we release will be able to update to the next one available.
|
The only difference with these builds is that they point to a different update location on the release bucket, with this dummy v0.255.255 always available. This helps ensuring that the version we release will be able to update to the next one available.
|
||||||
|
|
||||||
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing (or the updater-test location in the storage bucket).
|
If the prompt doesn't show up, start the app in command line to grab the electron-updater logs. This is likely an issue with the current build that needs addressing (or the updater-test location in the storage bucket).
|
||||||
|
|
||||||
```
|
```
|
||||||
# Windows (PowerShell)
|
# Windows (PowerShell)
|
||||||
& 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
|
& 'C:\Program Files\Zoo Modeling App\Zoo Modeling App.exe'
|
||||||
@ -205,15 +211,14 @@ If the prompt doesn't show up, start the app in command line to grab the electro
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
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_.
|
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.
|
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
|
||||||
|
|
||||||
If everything is well and the release is out to the public, the issue tracking the release shall be closed.
|
If everything is well and the release is out to the public, the issue tracking the release shall be closed.
|
||||||
|
|
||||||
|
|
||||||
## Fuzzing the parser
|
## Fuzzing the parser
|
||||||
|
|
||||||
Make sure you install cargo fuzz:
|
Make sure you install cargo fuzz:
|
||||||
@ -251,6 +256,7 @@ snapshottoken=<your-snapshot-token>
|
|||||||
For a portable way to run Playwright you'll need Docker.
|
For a portable way to run Playwright you'll need Docker.
|
||||||
|
|
||||||
#### Generic example
|
#### Generic example
|
||||||
|
|
||||||
After that, open a terminal and run:
|
After that, open a terminal and run:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -263,7 +269,6 @@ and in another terminal, run:
|
|||||||
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project="Google Chrome" <test suite>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### Specific example
|
#### Specific example
|
||||||
|
|
||||||
open a terminal and run:
|
open a terminal and run:
|
||||||
@ -281,7 +286,6 @@ PW_TEST_CONNECT_WS_ENDPOINT=ws://127.0.0.1:4444/ yarn playwright test --project=
|
|||||||
run a specific test change the test from `test('...` to `test.only('...`
|
run a specific test change the test from `test('...` to `test.only('...`
|
||||||
(note if you commit this, the tests will instantly fail without running any of the tests)
|
(note if you commit this, the tests will instantly fail without running any of the tests)
|
||||||
|
|
||||||
|
|
||||||
**Gotcha**: running the docker container with a mismatched image against your `./node_modules/playwright` will cause a failure. Make sure the versions are matched and up to date.
|
**Gotcha**: running the docker container with a mismatched image against your `./node_modules/playwright` will cause a failure. Make sure the versions are matched and up to date.
|
||||||
|
|
||||||
run headed
|
run headed
|
||||||
@ -375,6 +379,7 @@ Which will run our suite of [Vitest unit](https://vitest.dev/) and [React Testin
|
|||||||
- `just`
|
- `just`
|
||||||
|
|
||||||
#### Setting KITTYCAD_API_TOKEN
|
#### Setting KITTYCAD_API_TOKEN
|
||||||
|
|
||||||
Use the production zoo.dev token, set this environment variable before running the tests
|
Use the production zoo.dev token, set this environment variable before running the tests
|
||||||
|
|
||||||
#### Installing cargonextest
|
#### Installing cargonextest
|
||||||
@ -386,6 +391,7 @@ cargo install cargo-nextest
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### just
|
#### just
|
||||||
|
|
||||||
install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries)
|
install [`just`](https://github.com/casey/just?tab=readme-ov-file#pre-built-binaries)
|
||||||
|
|
||||||
#### Running the tests
|
#### Running the tests
|
||||||
@ -419,6 +425,7 @@ KITTYCAD_API_TOKEN=XXX cargo run nextest
|
|||||||
### Mapping CI CD jobs to local commands
|
### Mapping CI CD jobs to local commands
|
||||||
|
|
||||||
When you see the CI CD fail on jobs you may wonder three things
|
When you see the CI CD fail on jobs you may wonder three things
|
||||||
|
|
||||||
- Do I have a bug in my code?
|
- Do I have a bug in my code?
|
||||||
- Is the test flaky?
|
- Is the test flaky?
|
||||||
- Is there a bug in `main`?
|
- Is there a bug in `main`?
|
||||||
@ -437,7 +444,6 @@ yarn test-setup
|
|||||||
|
|
||||||
> Gotcha, are packages up to date and is the wasm built?
|
> Gotcha, are packages up to date and is the wasm built?
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
yarn tsc
|
yarn tsc
|
||||||
yarn fmt-check
|
yarn fmt-check
|
||||||
|
@ -53,7 +53,7 @@ example = extrude(exampleSketch, length = 5)
|
|||||||
```js
|
```js
|
||||||
// Add color to a revolved solid.
|
// Add color to a revolved solid.
|
||||||
sketch001 = startSketchOn('XY')
|
sketch001 = startSketchOn('XY')
|
||||||
|> circle({ center = [15, 0], radius = 5 }, %)
|
|> circle(center = [15, 0], radius = 5)
|
||||||
|> revolve({ angle = 360, axis = 'y' }, %)
|
|> revolve({ angle = 360, axis = 'y' }, %)
|
||||||
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
|
|> appearance(color = '#ff0000', metalness = 90, roughness = 90)
|
||||||
```
|
```
|
||||||
@ -195,10 +195,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|> line(end = [0, 7])
|
|> line(end = [0, 7])
|
||||||
|
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> appearance(color = "#ff0000", metalness = 50, roughness = 50)
|
|> appearance(color = "#ff0000", metalness = 50, roughness = 50)
|
||||||
|
@ -10,8 +10,9 @@ the provided (x, y) origin point.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
circle(
|
circle(
|
||||||
data: CircleData,
|
sketchOrSurface: SketchOrSurface,
|
||||||
sketchSurfaceOrGroup: SketchOrSurface,
|
center: [number],
|
||||||
|
radius: number,
|
||||||
tag?: TagDeclarator,
|
tag?: TagDeclarator,
|
||||||
) -> Sketch
|
) -> Sketch
|
||||||
```
|
```
|
||||||
@ -21,9 +22,10 @@ circle(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes |
|
| `sketchOrSurface` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
|
||||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
| `center` | `[number]` | The center of the circle. | Yes |
|
||||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
| `radius` | `number` | The radius of the circle. | Yes |
|
||||||
|
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this circle | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ circle(
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn("-XZ")
|
exampleSketch = startSketchOn("-XZ")
|
||||||
|> circle({ center = [0, 0], radius = 10 }, %)
|
|> circle(center = [0, 0], radius = 10)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
```
|
```
|
||||||
@ -48,7 +50,7 @@ exampleSketch = startSketchOn("XZ")
|
|||||||
|> line(end = [0, 30])
|
|> line(end = [0, 30])
|
||||||
|> line(end = [-30, 0])
|
|> line(end = [-30, 0])
|
||||||
|> close()
|
|> close()
|
||||||
|> hole(circle({ center = [0, 15], radius = 5 }, %), %)
|
|> hole(circle(center = [0, 15], radius = 5), %)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
```
|
```
|
||||||
|
@ -18,7 +18,7 @@ std::math::PI: number = 3.14159265358979323846264338327950288_
|
|||||||
circumference = 70
|
circumference = 70
|
||||||
|
|
||||||
exampleSketch = startSketchOn("XZ")
|
exampleSketch = startSketchOn("XZ")
|
||||||
|> circle({ center = [0, 0], radius = circumference/ (2 * PI) }, %)
|
|> circle(center = [0, 0], radius = circumference/ (2 * PI))
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
```
|
```
|
||||||
|
@ -51,7 +51,7 @@ helixPath = helix(
|
|||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('YZ')
|
springSketch = startSketchOn('YZ')
|
||||||
|> circle({ center = [0, 0], radius = 0.5 }, %)
|
|> circle(center = [0, 0], radius = 0.5)
|
||||||
|> sweep(path = helixPath)
|
|> sweep(path = helixPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ helixPath = helix(
|
|||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('XY')
|
springSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 0.5 }, %)
|
|> circle(center = [0, 0], radius = 0.5)
|
||||||
|> sweep(path = helixPath)
|
|> sweep(path = helixPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -98,7 +98,7 @@ helixPath = helix(
|
|||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('XY')
|
springSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|> sweep(path = helixPath)
|
|> sweep(path = helixPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ helixRevolutions(
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
part001 = startSketchOn('XY')
|
part001 = startSketchOn('XY')
|
||||||
|> circle({ center = [5, 5], radius = 10 }, %)
|
|> circle(center = [5, 5], radius = 10)
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
|> helixRevolutions({
|
|> helixRevolutions({
|
||||||
angleStart = 0,
|
angleStart = 0,
|
||||||
|
@ -37,8 +37,8 @@ exampleSketch = startSketchOn(XY)
|
|||||||
|> line(end = [5, 0])
|
|> line(end = [5, 0])
|
||||||
|> line(end = [0, -5])
|
|> line(end = [0, -5])
|
||||||
|> close()
|
|> close()
|
||||||
|> hole(circle({ center = [1, 1], radius = .25 }, %), %)
|
|> hole(circle(center = [1, 1], radius = .25), %)
|
||||||
|> hole(circle({ center = [1, 4], radius = .25 }, %), %)
|
|> hole(circle(center = [1, 4], radius = .25), %)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 1)
|
example = extrude(exampleSketch, length = 1)
|
||||||
```
|
```
|
||||||
@ -57,7 +57,7 @@ fn squareHoleSketch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
exampleSketch = startSketchOn(-XZ)
|
exampleSketch = startSketchOn(-XZ)
|
||||||
|> circle({ center = [0, 0], radius = 3 }, %)
|
|> circle(center = [0, 0], radius = 3)
|
||||||
|> hole(squareHoleSketch(), %)
|
|> hole(squareHoleSketch(), %)
|
||||||
example = extrude(exampleSketch, length = 1)
|
example = extrude(exampleSketch, length = 1)
|
||||||
```
|
```
|
||||||
|
@ -70,17 +70,11 @@ case = startSketchOn('-XZ')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
thing2 = startSketchOn(case, 'end')
|
thing2 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [size / 2, -size / 2], radius = 25)
|
||||||
center = [size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
hollow(0.5, case)
|
hollow(0.5, case)
|
||||||
|
@ -37,7 +37,7 @@ n = int(ceil(5 / 2))
|
|||||||
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||||
// Draw n cylinders.
|
// Draw n cylinders.
|
||||||
startSketchOn('XZ')
|
startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> extrude(length = 5)
|
|> extrude(length = 5)
|
||||||
|> patternTransform(
|
|> patternTransform(
|
||||||
instances = n,
|
instances = n,
|
||||||
|
@ -69,10 +69,10 @@ squareSketch = startSketchOn('XY')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch0 = startSketchOn(offsetPlane('XY', offset = 75))
|
circleSketch0 = startSketchOn(offsetPlane('XY', offset = 75))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
|
circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
|
||||||
|> circle({ center = [0, 100], radius = 20 }, %)
|
|> circle(center = [0, 100], radius = 20)
|
||||||
|
|
||||||
loft([
|
loft([
|
||||||
squareSketch,
|
squareSketch,
|
||||||
@ -94,10 +94,10 @@ squareSketch = startSketchOn('XY')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch0 = startSketchOn(offsetPlane('XY', offset = 75))
|
circleSketch0 = startSketchOn(offsetPlane('XY', offset = 75))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
|
circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
|
||||||
|> circle({ center = [0, 100], radius = 20 }, %)
|
|> circle(center = [0, 100], radius = 20)
|
||||||
|
|
||||||
loft(
|
loft(
|
||||||
[
|
[
|
||||||
|
@ -34,7 +34,7 @@ map(
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call `drawCircle`, passing in each element of the array.
|
// Call `drawCircle`, passing in each element of the array.
|
||||||
@ -50,7 +50,7 @@ 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], fn(id) {
|
circles = map([1..3], fn(id) {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
|> circle({ center = [id * 2 * r, 0], radius = r }, %)
|
|> circle(center = [id * 2 * r, 0], radius = r)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -41,7 +41,7 @@ squareSketch = startSketchOn('XY')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch = startSketchOn(offsetPlane('XY', offset = 150))
|
circleSketch = startSketchOn(offsetPlane('XY', offset = 150))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
loft([squareSketch, circleSketch])
|
loft([squareSketch, circleSketch])
|
||||||
```
|
```
|
||||||
@ -59,7 +59,7 @@ squareSketch = startSketchOn('XZ')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch = startSketchOn(offsetPlane('XZ', offset = 150))
|
circleSketch = startSketchOn(offsetPlane('XZ', offset = 150))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
loft([squareSketch, circleSketch])
|
loft([squareSketch, circleSketch])
|
||||||
```
|
```
|
||||||
@ -77,7 +77,7 @@ squareSketch = startSketchOn('YZ')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch = startSketchOn(offsetPlane('YZ', offset = 150))
|
circleSketch = startSketchOn(offsetPlane('YZ', offset = 150))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
loft([squareSketch, circleSketch])
|
loft([squareSketch, circleSketch])
|
||||||
```
|
```
|
||||||
@ -95,7 +95,7 @@ squareSketch = startSketchOn('-XZ')
|
|||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
circleSketch = startSketchOn(offsetPlane('-XZ', offset = -150))
|
circleSketch = startSketchOn(offsetPlane('-XZ', offset = -150))
|
||||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
|> circle(center = [0, 100], radius = 50)
|
||||||
|
|
||||||
loft([squareSketch, circleSketch])
|
loft([squareSketch, circleSketch])
|
||||||
```
|
```
|
||||||
@ -106,7 +106,7 @@ loft([squareSketch, circleSketch])
|
|||||||
// A circle on the XY plane
|
// A circle on the XY plane
|
||||||
startSketchOn("XY")
|
startSketchOn("XY")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({ radius = 10, center = [0, 0] }, %)
|
|> circle(radius = 10, center = [0, 0])
|
||||||
|
|
||||||
// Triangle on the plane 4 units above
|
// Triangle on the plane 4 units above
|
||||||
startSketchOn(offsetPlane("XY", offset = 4))
|
startSketchOn(offsetPlane("XY", offset = 4))
|
||||||
|
@ -42,7 +42,7 @@ patternCircular3d(
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn('XZ')
|
exampleSketch = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = -5)
|
example = extrude(exampleSketch, length = -5)
|
||||||
|> patternCircular3d(
|
|> patternCircular3d(
|
||||||
|
@ -38,7 +38,7 @@ patternLinear2d(
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn('XZ')
|
exampleSketch = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
|
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 1)
|
example = extrude(exampleSketch, length = 1)
|
||||||
|
@ -64,17 +64,11 @@ case = startSketchOn('XY')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
thing2 = startSketchOn(case, 'end')
|
thing2 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [size / 2, -size / 2], radius = 25)
|
||||||
center = [size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
// We pass in the "case" here since we want to pattern the whole sketch.
|
// We pass in the "case" here since we want to pattern the whole sketch.
|
||||||
@ -101,10 +95,7 @@ case = startSketchOn('XY')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
// We pass in `thing1` here with `useOriginal` since we want to pattern just this object on the face.
|
// We pass in `thing1` here with `useOriginal` since we want to pattern just this object on the face.
|
||||||
|
@ -68,7 +68,7 @@ fn transform(id) {
|
|||||||
|
|
||||||
// Sketch 4 cylinders.
|
// Sketch 4 cylinders.
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> extrude(length = 5)
|
|> extrude(length = 5)
|
||||||
|> patternTransform(instances = 4, transform = transform)
|
|> patternTransform(instances = 4, transform = transform)
|
||||||
```
|
```
|
||||||
@ -84,7 +84,7 @@ fn transform(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> extrude(length = 5)
|
|> extrude(length = 5)
|
||||||
|> patternTransform(instances = 4, transform = transform)
|
|> patternTransform(instances = 4, transform = transform)
|
||||||
```
|
```
|
||||||
@ -184,7 +184,7 @@ fn transform(replicaId) {
|
|||||||
fn layer() {
|
fn layer() {
|
||||||
return startSketchOn("XY")
|
return startSketchOn("XY")
|
||||||
// or some other plane idk
|
// or some other plane idk
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %, $tag1)
|
|> circle(center = [0, 0], radius = 1, tag = $tag1)
|
||||||
|> extrude(length = h)
|
|> extrude(length = h)
|
||||||
}
|
}
|
||||||
// The vase is 100 layers tall.
|
// The vase is 100 layers tall.
|
||||||
|
@ -42,7 +42,7 @@ fn transform(id) {
|
|||||||
|
|
||||||
// Sketch 4 circles.
|
// Sketch 4 circles.
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> patternTransform2d(instances = 4, transform = transform)
|
|> patternTransform2d(instances = 4, transform = transform)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -31,10 +31,7 @@ pi() -> number
|
|||||||
circumference = 70
|
circumference = 70
|
||||||
|
|
||||||
exampleSketch = startSketchOn("XZ")
|
exampleSketch = startSketchOn("XZ")
|
||||||
|> circle({
|
|> circle(center = [0, 0], radius = circumference / (2 * pi()))
|
||||||
center = [0, 0],
|
|
||||||
radius = circumference / (2 * pi())
|
|
||||||
}, %)
|
|
||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
```
|
```
|
||||||
|
@ -51,7 +51,7 @@ part001 = startSketchOn('XY')
|
|||||||
```js
|
```js
|
||||||
// A donut shape.
|
// A donut shape.
|
||||||
sketch001 = startSketchOn('XY')
|
sketch001 = startSketchOn('XY')
|
||||||
|> circle({ center = [15, 0], radius = 5 }, %)
|
|> circle(center = [15, 0], radius = 5)
|
||||||
|> revolve({ angle = 360, axis = 'y' }, %)
|
|> revolve({ angle = 360, axis = 'y' }, %)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ box = startSketchOn('XY')
|
|||||||
|> extrude(length = 20)
|
|> extrude(length = 20)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, "END")
|
sketch001 = startSketchOn(box, "END")
|
||||||
|> circle({ center = [10, 10], radius = 4 }, %)
|
|> circle(center = [10, 10], radius = 4)
|
||||||
|> revolve({ angle = -90, axis = 'y' }, %)
|
|> revolve({ angle = -90, axis = 'y' }, %)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ box = startSketchOn('XY')
|
|||||||
|> extrude(length = 20)
|
|> extrude(length = 20)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, "END")
|
sketch001 = startSketchOn(box, "END")
|
||||||
|> circle({ center = [10, 10], radius = 4 }, %)
|
|> circle(center = [10, 10], radius = 4)
|
||||||
|> revolve({
|
|> revolve({
|
||||||
angle = 90,
|
angle = 90,
|
||||||
axis = getOppositeEdge(revolveAxis)
|
axis = getOppositeEdge(revolveAxis)
|
||||||
@ -141,7 +141,7 @@ box = startSketchOn('XY')
|
|||||||
|> extrude(length = 20)
|
|> extrude(length = 20)
|
||||||
|
|
||||||
sketch001 = startSketchOn(box, "END")
|
sketch001 = startSketchOn(box, "END")
|
||||||
|> circle({ center = [10, 10], radius = 4 }, %)
|
|> circle(center = [10, 10], radius = 4)
|
||||||
|> revolve({
|
|> revolve({
|
||||||
angle = 90,
|
angle = 90,
|
||||||
axis = getOppositeEdge(revolveAxis),
|
axis = getOppositeEdge(revolveAxis),
|
||||||
|
@ -69,10 +69,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|
|
||||||
// Create a hole for the pipe.
|
// Create a hole for the pipe.
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> rotate(roll = 10, pitch = 10, yaw = 90)
|
|> rotate(roll = 10, pitch = 10, yaw = 90)
|
||||||
@ -95,10 +95,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|
|
||||||
// Create a hole for the pipe.
|
// Create a hole for the pipe.
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> rotate(axis = [0, 0, 1.0], angle = 90)
|
|> rotate(axis = [0, 0, 1.0], angle = 90)
|
||||||
|
@ -49,10 +49,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|
|
||||||
// Create a hole for the pipe.
|
// Create a hole for the pipe.
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> scale(scale = [1.0, 1.0, 2.5])
|
|> scale(scale = [1.0, 1.0, 2.5])
|
||||||
|
@ -40,10 +40,7 @@ cube = startSketchOn('XY')
|
|||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchOn('XY')
|
return startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle(radius = radius, center = segEnd(tag))
|
||||||
radius = radius,
|
|
||||||
center = segEnd(tag)
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = radius)
|
|> extrude(length = radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,10 +40,7 @@ cube = startSketchOn('XY')
|
|||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchOn('XY')
|
return startSketchOn('XY')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle(radius = radius, center = segStart(tag))
|
||||||
radius = radius,
|
|
||||||
center = segStart(tag)
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = radius)
|
|> extrude(length = radius)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,17 +108,11 @@ case = startSketchOn('-XZ')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
thing2 = startSketchOn(case, 'end')
|
thing2 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [size / 2, -size / 2], radius = 25)
|
||||||
center = [size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
// We put "case" in the shell function to shell the entire object.
|
// We put "case" in the shell function to shell the entire object.
|
||||||
@ -139,17 +133,11 @@ case = startSketchOn('XY')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
thing2 = startSketchOn(case, 'end')
|
thing2 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [size / 2, -size / 2], radius = 25)
|
||||||
center = [size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
// We put "thing1" in the shell function to shell the end face of the object.
|
// We put "thing1" in the shell function to shell the end face of the object.
|
||||||
@ -173,17 +161,11 @@ case = startSketchOn('XY')
|
|||||||
|> extrude(length = 65)
|
|> extrude(length = 65)
|
||||||
|
|
||||||
thing1 = startSketchOn(case, 'end')
|
thing1 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [-size / 2, -size / 2], radius = 25)
|
||||||
center = [-size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
thing2 = startSketchOn(case, 'end')
|
thing2 = startSketchOn(case, 'end')
|
||||||
|> circle({
|
|> circle(center = [size / 2, -size / 2], radius = 25)
|
||||||
center = [size / 2, -size / 2],
|
|
||||||
radius = 25
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
|
|
||||||
// We put "thing1" and "thing2" in the shell function to shell the end face of the object.
|
// We put "thing1" and "thing2" in the shell function to shell the end face of the object.
|
||||||
|
3353
docs/kcl/std.json
@ -49,10 +49,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|
|
||||||
// Create a hole for the pipe.
|
// Create a hole for the pipe.
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
```
|
```
|
||||||
@ -75,7 +75,7 @@ helixPath = helix(
|
|||||||
|
|
||||||
// Create a spring by sweeping around the helix path.
|
// Create a spring by sweeping around the helix path.
|
||||||
springSketch = startSketchOn('YZ')
|
springSketch = startSketchOn('YZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|> sweep(path = helixPath)
|
|> sweep(path = helixPath)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ bottom = startSketchOn("XY")
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
circSketch = startSketchOn("XY")
|
circSketch = startSketchOn("XY")
|
||||||
|> circle({ center = [0, 0], radius = 3 }, %, $circ)
|
|> circle(center = [0, 0], radius = 3, tag = $circ)
|
||||||
|
|
||||||
triangleSketch = startSketchOn("XY")
|
triangleSketch = startSketchOn("XY")
|
||||||
|> startProfileAt([-5, 0], %)
|
|> startProfileAt([-5, 0], %)
|
||||||
|
@ -47,10 +47,10 @@ sweepPath = startSketchOn('XZ')
|
|||||||
|
|
||||||
// Create a hole for the pipe.
|
// Create a hole for the pipe.
|
||||||
pipeHole = startSketchOn('XY')
|
pipeHole = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 1.5 }, %)
|
|> circle(center = [0, 0], radius = 1.5)
|
||||||
|
|
||||||
sweepSketch = startSketchOn('XY')
|
sweepSketch = startSketchOn('XY')
|
||||||
|> circle({ center = [0, 0], radius = 2 }, %)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|> hole(pipeHole, %)
|
|> hole(pipeHole, %)
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> translate(translate = [1.0, 1.0, 2.5])
|
|> translate(translate = [1.0, 1.0, 2.5])
|
||||||
|
@ -689,7 +689,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('Enter')
|
await page.keyboard.press('Enter')
|
||||||
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
await page.keyboard.type(`extrusion = startSketchOn('XY')
|
||||||
|> circle({ center: [0, 0], radius: dia/2 }, %)
|
|> circle(center: [0, 0], radius: dia/2)
|
||||||
|> hole(squareHole(length, width, height), %)
|
|> hole(squareHole(length, width, height), %)
|
||||||
|> extrude(length = height)`)
|
|> extrude(length = height)`)
|
||||||
|
|
||||||
|
@ -50,13 +50,13 @@ const FEATURE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(sketch001, length = 10)
|
extrude001 = extrude(sketch001, length = 10)
|
||||||
sketch002 = startSketchOn(extrude001, rectangleSegmentB001)
|
sketch002 = startSketchOn(extrude001, rectangleSegmentB001)
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [-1, 2],
|
center = [-1, 2],
|
||||||
radius = .5
|
radius = .5
|
||||||
}, %)
|
)
|
||||||
plane001 = offsetPlane('XZ', offset = -5)
|
plane001 = offsetPlane('XZ', offset = -5)
|
||||||
sketch003 = startSketchOn(plane001)
|
sketch003 = startSketchOn(plane001)
|
||||||
|> circle({ center = [0, 0], radius = 5 }, %)
|
|> circle(center = [0, 0], radius = 5)
|
||||||
`
|
`
|
||||||
|
|
||||||
test.describe('Feature Tree pane', () => {
|
test.describe('Feature Tree pane', () => {
|
||||||
@ -201,7 +201,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
await toolbar.exitSketchBtn.click()
|
await toolbar.exitSketchBtn.click()
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('On an offset plane should *not* work', async () => {
|
await test.step('On an offset plane should work', async () => {
|
||||||
// Tooltip is getting in the way of clicking, so I'm first closing the pane
|
// Tooltip is getting in the way of clicking, so I'm first closing the pane
|
||||||
await toolbar.closeFeatureTreePane()
|
await toolbar.closeFeatureTreePane()
|
||||||
await (await toolbar.getFeatureTreeOperation('Sketch', 2)).dblclick()
|
await (await toolbar.getFeatureTreeOperation('Sketch', 2)).dblclick()
|
||||||
@ -212,13 +212,7 @@ test.describe('Feature Tree pane', () => {
|
|||||||
})
|
})
|
||||||
await expect(
|
await expect(
|
||||||
toolbar.exitSketchBtn,
|
toolbar.exitSketchBtn,
|
||||||
'We should not be in sketch mode now'
|
'We should be in sketch mode now'
|
||||||
).not.toBeVisible()
|
|
||||||
await expect(
|
|
||||||
page.getByText(
|
|
||||||
'Editing sketches on faces or offset planes through the feature tree is not yet supported'
|
|
||||||
),
|
|
||||||
'We should see a toast message about this'
|
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -234,11 +228,11 @@ test.describe('Feature Tree pane', () => {
|
|||||||
}) => {
|
}) => {
|
||||||
const initialInput = '23'
|
const initialInput = '23'
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 5 }, %)
|
|> circle(center = [0, 0], radius = 5)
|
||||||
renamedExtrude = extrude(sketch001, length = ${initialInput})`
|
renamedExtrude = extrude(sketch001, length = ${initialInput})`
|
||||||
const newConstantName = 'distance001'
|
const newConstantName = 'distance001'
|
||||||
const expectedCode = `sketch001 = startSketchOn('XZ')
|
const expectedCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 5 }, %)
|
|> circle(center = [0, 0], radius = 5)
|
||||||
${newConstantName} = 23
|
${newConstantName} = 23
|
||||||
renamedExtrude = extrude(sketch001, length = ${newConstantName})`
|
renamedExtrude = extrude(sketch001, length = ${newConstantName})`
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ export class CmdBarFixture {
|
|||||||
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
private _serialiseCmdBar = async (): Promise<CmdBarSerialised> => {
|
||||||
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
if (!(await this.page.getByTestId('command-bar-wrapper').isVisible())) {
|
||||||
return { stage: 'commandBarClosed' }
|
return { stage: 'commandBarClosed' }
|
||||||
|
} else if (await this.page.getByTestId('cmd-bar-search').isVisible()) {
|
||||||
|
return { stage: 'pickCommand' }
|
||||||
}
|
}
|
||||||
const reviewForm = this.page.locator('#review-form')
|
const reviewForm = this.page.locator('#review-form')
|
||||||
const getHeaderArgs = async () => {
|
const getHeaderArgs = async () => {
|
||||||
|
@ -61,7 +61,7 @@ export class ToolbarFixture {
|
|||||||
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
this.rectangleBtn = page.getByTestId('corner-rectangle')
|
||||||
this.lengthConstraintBtn = page.getByTestId('constraint-length')
|
this.lengthConstraintBtn = page.getByTestId('constraint-length')
|
||||||
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
this.exitSketchBtn = page.getByTestId('sketch-exit')
|
||||||
this.editSketchBtn = page.getByText('Edit Sketch')
|
this.editSketchBtn = page.locator('[name="Edit Sketch"]')
|
||||||
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
|
||||||
this.createFileBtn = page.getByTestId('create-file-button')
|
this.createFileBtn = page.getByTestId('create-file-button')
|
||||||
this.treeInputField = page.getByTestId('tree-input-field')
|
this.treeInputField = page.getByTestId('tree-input-field')
|
||||||
|
@ -42,27 +42,25 @@ test.describe('Point-and-click tests', () => {
|
|||||||
|
|
||||||
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
||||||
await moveToCircle()
|
await moveToCircle()
|
||||||
const circleSnippet =
|
const circleSnippet = 'circle(center = [318.33, 168.1], radius = 182.8)'
|
||||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: ["constsketch002=startSketchOn('XZ')"],
|
activeLines: ["sketch002=startSketchOn('XZ')"],
|
||||||
highlightedCode: circleSnippet,
|
highlightedCode: circleSnippet,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
await test.step('check code model connection works and that button is still enable once circle is selected ', async () => {
|
||||||
await moveToCircle()
|
await moveToCircle()
|
||||||
const circleSnippet =
|
const circleSnippet = 'circle(center = [318.33, 168.1], radius = 182.8)'
|
||||||
'circle({ center = [318.33, 168.1], radius = 182.8 }, %)'
|
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: ["constsketch002=startSketchOn('XZ')"],
|
activeLines: ["sketch002=startSketchOn('XZ')"],
|
||||||
highlightedCode: circleSnippet,
|
highlightedCode: circleSnippet,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
|
|
||||||
await clickCircle()
|
await clickCircle()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [circleSnippet.slice(-5)],
|
activeLines: ['|>' + circleSnippet],
|
||||||
highlightedCode: circleSnippet,
|
highlightedCode: circleSnippet,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
@ -581,7 +579,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|
|||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const initialCode = `closedSketch = startSketchOn('XZ')
|
const initialCode = `closedSketch = startSketchOn('XZ')
|
||||||
|> circle({ center = [8, 5], radius = 2 }, %)
|
|> circle(center = [8, 5], radius = 2)
|
||||||
openSketch = startSketchOn('XY')
|
openSketch = startSketchOn('XY')
|
||||||
|> startProfileAt([-5, 0], %)
|
|> startProfileAt([-5, 0], %)
|
||||||
|> line(endAbsolute = [0, 5])
|
|> line(endAbsolute = [0, 5])
|
||||||
@ -633,8 +631,8 @@ openSketch = startSketchOn('XY')
|
|||||||
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
await expect(toolbar.startSketchBtn).not.toBeVisible()
|
||||||
await expect(toolbar.exitSketchBtn).toBeVisible()
|
await expect(toolbar.exitSketchBtn).toBeVisible()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [`|>circle({center=[8,5],radius=2},%)`],
|
activeLines: [`|>circle(center=[8,5],radius=2)`],
|
||||||
highlightedCode: 'circle({center=[8,5],radius=2},%)',
|
highlightedCode: 'circle(center=[8,5],radius=2)',
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -1192,10 +1190,10 @@ openSketch = startSketchOn('XY')
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle(center = [0, 0], radius = 30)
|
||||||
plane001 = offsetPlane('XZ', offset = 50)
|
plane001 = offsetPlane('XZ', offset = 50)
|
||||||
sketch002 = startSketchOn(plane001)
|
sketch002 = startSketchOn(plane001)
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|> circle(center = [0, 0], radius = 20)
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
@ -1278,10 +1276,10 @@ openSketch = startSketchOn('XY')
|
|||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle(center = [0, 0], radius = 30)
|
||||||
plane001 = offsetPlane('XZ', offset = 50)
|
plane001 = offsetPlane('XZ', offset = 50)
|
||||||
sketch002 = startSketchOn(plane001)
|
sketch002 = startSketchOn(plane001)
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|> circle(center = [0, 0], radius = 20)
|
||||||
loft001 = loft([sketch001, sketch002])
|
loft001 = loft([sketch001, sketch002])
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
@ -1304,7 +1302,7 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
await scene.expectPixelColor([89, 89, 89], testPoint, 15)
|
||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle(center = [0, 0], radius = 30)
|
||||||
`)
|
`)
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
// Check for sketch 1
|
// Check for sketch 1
|
||||||
@ -1315,7 +1313,7 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await clickOnSketch2()
|
await clickOnSketch2()
|
||||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|> circle(center = [0, 0], radius = 20)
|
||||||
`)
|
`)
|
||||||
await page.keyboard.press('Delete')
|
await page.keyboard.press('Delete')
|
||||||
// Check for plane001
|
// Check for plane001
|
||||||
@ -1344,10 +1342,10 @@ loft001 = loft([sketch001, sketch002])
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('YZ')
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = 500
|
radius = 500
|
||||||
}, %)
|
)
|
||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> xLine(-500, %)
|
|> xLine(-500, %)
|
||||||
@ -1438,10 +1436,10 @@ sketch002 = startSketchOn('XZ')
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('YZ')
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = 500
|
radius = 500
|
||||||
}, %)
|
)
|
||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> xLine(-500, %)
|
|> xLine(-500, %)
|
||||||
@ -2270,7 +2268,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle(center = [0, 0], radius = 30)
|
||||||
extrude001 = extrude(sketch001, length = 30)
|
extrude001 = extrude(sketch001, length = 30)
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
@ -2445,19 +2443,19 @@ extrude001 = extrude(sketch001, length = 40)
|
|||||||
|
|
||||||
const shellSketchOnFacesCases = [
|
const shellSketchOnFacesCases = [
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|> circle(center = [0, 0], radius = 100)
|
||||||
|> extrude(length = 100)
|
|> extrude(length = 100)
|
||||||
|
|
||||||
sketch002 = startSketchOn(sketch001, 'END')
|
sketch002 = startSketchOn(sketch001, 'END')
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|> circle(center = [0, 0], radius = 50)
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
`,
|
`,
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 100 }, %)
|
|> circle(center = [0, 0], radius = 100)
|
||||||
extrude001 = extrude(sketch001, length = 100)
|
extrude001 = extrude(sketch001, length = 100)
|
||||||
|
|
||||||
sketch002 = startSketchOn(extrude001, 'END')
|
sketch002 = startSketchOn(extrude001, 'END')
|
||||||
|> circle({ center = [0, 0], radius = 50 }, %)
|
|> circle(center = [0, 0], radius = 50)
|
||||||
extrude002 = extrude(sketch002, length = 50)
|
extrude002 = extrude(sketch002, length = 50)
|
||||||
`,
|
`,
|
||||||
]
|
]
|
||||||
@ -2631,10 +2629,10 @@ profile001 = startProfileAt([-20, 20], sketch001)
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('YZ')
|
const initialCode = `sketch001 = startSketchOn('YZ')
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = 500
|
radius = 500
|
||||||
}, %)
|
)
|
||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> xLine(-2000, %)
|
|> xLine(-2000, %)
|
||||||
@ -2761,10 +2759,10 @@ segAng(rectangleSegmentA001),
|
|||||||
|> close()
|
|> close()
|
||||||
extrude001 = extrude(sketch001, length = 50)
|
extrude001 = extrude(sketch001, length = 50)
|
||||||
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|
sketch002 = startSketchOn(extrude001, rectangleSegmentA001)
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [-11.34, 10.0],
|
center = [-11.34, 10.0],
|
||||||
radius = 8.69
|
radius = 8.69
|
||||||
}, %)
|
)
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
localStorage.setItem('persistCode', initialCode)
|
localStorage.setItem('persistCode', initialCode)
|
||||||
@ -2811,10 +2809,10 @@ radius = 8.69
|
|||||||
|> close()
|
|> close()
|
||||||
extrude001 = extrude(sketch001, length = 5)
|
extrude001 = extrude(sketch001, length = 5)
|
||||||
sketch003 = startSketchOn(extrude001, 'START')
|
sketch003 = startSketchOn(extrude001, 'START')
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [-0.69, 0.56],
|
center = [-0.69, 0.56],
|
||||||
radius = 0.28
|
radius = 0.28
|
||||||
}, %)
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
@ -2849,10 +2847,11 @@ radius = 8.69
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
profile001 = circle({
|
profile001 = circle(
|
||||||
|
sketch001,
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
radius = 100
|
radius = 100
|
||||||
}, sketch001)
|
)
|
||||||
extrude001 = extrude(profile001, length = 100)
|
extrude001 = extrude(profile001, length = 100)
|
||||||
`
|
`
|
||||||
await context.addInitScript((initialCode) => {
|
await context.addInitScript((initialCode) => {
|
||||||
@ -2864,7 +2863,7 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 500, y: 250 }
|
const testPoint = { x: 500, y: 250 }
|
||||||
const initialColor: [number, number, number] = [135, 135, 135]
|
const initialColor: [number, number, number] = [123, 123, 123]
|
||||||
|
|
||||||
await test.step(`Confirm extrude exists with default appearance`, async () => {
|
await test.step(`Confirm extrude exists with default appearance`, async () => {
|
||||||
await toolbar.closePane('code')
|
await toolbar.closePane('code')
|
||||||
@ -2877,23 +2876,41 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
shapeColor: [number, number, number]
|
shapeColor: [number, number, number]
|
||||||
) {
|
) {
|
||||||
await toolbar.openPane('feature-tree')
|
await toolbar.openPane('feature-tree')
|
||||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
const enterAppearanceFlow = async (stepName: string) =>
|
||||||
'Extrude',
|
test.step(stepName, async () => {
|
||||||
0
|
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||||
)
|
'Extrude',
|
||||||
await operationButton.click({ button: 'right' })
|
0
|
||||||
const menuButton = page.getByTestId('context-menu-set-appearance')
|
)
|
||||||
await menuButton.click()
|
await operationButton.click({ button: 'right' })
|
||||||
await cmdBar.expectState({
|
const menuButton = page.getByTestId('context-menu-set-appearance')
|
||||||
commandName: 'Appearance',
|
await menuButton.click()
|
||||||
currentArgKey: 'color',
|
await cmdBar.expectState({
|
||||||
currentArgValue: '',
|
commandName: 'Appearance',
|
||||||
headerArguments: {
|
currentArgKey: 'color',
|
||||||
Color: '',
|
currentArgValue: '',
|
||||||
},
|
headerArguments: {
|
||||||
highlightedHeaderArg: 'color',
|
Color: '',
|
||||||
stage: 'arguments',
|
},
|
||||||
|
highlightedHeaderArg: 'color',
|
||||||
|
stage: 'arguments',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await enterAppearanceFlow(`Open Set Appearance flow`)
|
||||||
|
|
||||||
|
await test.step(`Validate hidden argument "nodeToEdit" can't be reached with Backspace`, async () => {
|
||||||
|
await page.keyboard.press('Backspace')
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'pickCommand',
|
||||||
|
})
|
||||||
|
await page.keyboard.press('Escape')
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'commandBarClosed',
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await enterAppearanceFlow(`Restart Appearance flow`)
|
||||||
const item = page.getByText(option, { exact: true })
|
const item = page.getByText(option, { exact: true })
|
||||||
await item.click()
|
await item.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -2905,7 +2922,7 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await toolbar.closePane('feature-tree')
|
await toolbar.closePane('feature-tree')
|
||||||
await scene.expectPixelColor(shapeColor, testPoint, 40)
|
await scene.expectPixelColor(shapeColor, testPoint, 10)
|
||||||
await toolbar.openPane('code')
|
await toolbar.openPane('code')
|
||||||
if (hex === 'default') {
|
if (hex === 'default') {
|
||||||
const anyAppearanceDeclaration = `|> appearance(`
|
const anyAppearanceDeclaration = `|> appearance(`
|
||||||
@ -2931,9 +2948,9 @@ extrude001 = extrude(profile001, length = 100)
|
|||||||
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
|
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
|
||||||
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
|
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
|
||||||
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
|
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
|
||||||
await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
|
await setApperanceAndCheck('Dark Grey', '#080808', [0x33, 0x33, 0x33])
|
||||||
await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
|
await setApperanceAndCheck('Light Grey', '#D3D3D3', [176, 176, 176])
|
||||||
await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
|
await setApperanceAndCheck('White', '#FFFFFF', [184, 184, 184])
|
||||||
await setApperanceAndCheck(
|
await setApperanceAndCheck(
|
||||||
'Default (clear appearance)',
|
'Default (clear appearance)',
|
||||||
'default',
|
'default',
|
||||||
|
@ -12,10 +12,10 @@ profile001 = startProfileAt([57.81, 250.51], sketch001)
|
|||||||
|> close()
|
|> close()
|
||||||
extrude001 = extrude(profile001, length = 200)
|
extrude001 = extrude(profile001, length = 200)
|
||||||
sketch002 = startSketchOn('XZ')
|
sketch002 = startSketchOn('XZ')
|
||||||
|> startProfileAt([-73.64, -42.89], %)
|
|> startProfileAt([-114, 85.52], %)
|
||||||
|> xLine(173.71, %)
|
|> xLine(265.36, %)
|
||||||
|> line(end = [-22.12, -94.4])
|
|> line(end = [33.17, -261.22])
|
||||||
|> xLine(-156.98, %)
|
|> xLine(-297.25, %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
extrude002 = extrude(sketch002, length = 50)
|
extrude002 = extrude(sketch002, length = 50)
|
||||||
@ -62,8 +62,8 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const greenCheckCoords = { x: 565, y: 345 }
|
const greenCheckCoords = { x: 565, y: 305 }
|
||||||
const body2WallCoords = { x: 609, y: 153 }
|
const body2WallCoords = { x: 609, y: 153 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
body1CapCoords.x,
|
body1CapCoords.x,
|
||||||
@ -88,7 +88,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
await scene.expectPixelColor(yellow, body1CapCoords, 20)
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
activeLines: ['|>startProfileAt([-114,85.52],%)'],
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -157,7 +157,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await scene.waitForExecutionDone()
|
await scene.waitForExecutionDone()
|
||||||
|
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
body1CapCoords.x,
|
body1CapCoords.x,
|
||||||
body1CapCoords.y
|
body1CapCoords.y
|
||||||
@ -176,7 +176,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
|
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
activeLines: ['|>startProfileAt([-114,85.52],%)'],
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -204,7 +204,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
|
|
||||||
await context.addInitScript((file) => {
|
await context.addInitScript((file) => {
|
||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
@ -263,7 +263,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
const body1CapCoords = { x: 571, y: 351 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const body2WallCoords = { x: 620, y: 152 }
|
const body2WallCoords = { x: 620, y: 152 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
body1CapCoords.x,
|
body1CapCoords.x,
|
||||||
@ -299,7 +299,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
// Hold shift and select second body
|
// Hold shift and select second body
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
highlightedCode: '',
|
highlightedCode: '',
|
||||||
activeLines: ['|>startProfileAt([-73.64,-42.89],%)'],
|
activeLines: ['|>startProfileAt([-114,85.52],%)'],
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
@ -310,7 +310,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
'line(end=[121.13,56.63],tag=$seg02)extrude(profile001,length=200)',
|
'line(end=[121.13,56.63],tag=$seg02)extrude(profile001,length=200)',
|
||||||
activeLines: [
|
activeLines: [
|
||||||
'|>line(end=[121.13,56.63],tag=$seg02)',
|
'|>line(end=[121.13,56.63],tag=$seg02)',
|
||||||
'|>startProfileAt([-73.64,-42.89],%)',
|
'|>startProfileAt([-114,85.52],%)',
|
||||||
],
|
],
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
|
@ -367,7 +367,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [4.61, -5.01], radius = 8 }, %)`
|
|> circle(center = [4.61, -5.01], radius = 8)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -403,9 +403,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
|
|
||||||
const dragPX = 40
|
const dragPX = 40
|
||||||
|
|
||||||
await page
|
await page.getByText('circle(center = [4.61, -5.01], radius = 8)').click()
|
||||||
.getByText('circle({ center = [4.61, -5.01], radius = 8 }, %)')
|
|
||||||
.click()
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
@ -444,7 +442,7 @@ sketch001 = startProfileAt([12.34, -12.34], sketch002)
|
|||||||
// expect the code to have changed
|
// expect the code to have changed
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [7.26, -2.37], radius = 11.44 }, %)`,
|
|> circle(center = [7.26, -2.37], radius = 11.44)`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -1390,12 +1388,14 @@ profile002 = startProfileAt([117.2, 56.08], sketch001)
|
|||||||
await toolbar.circleBtn.click()
|
await toolbar.circleBtn.click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await circlePoint1()
|
await circlePoint1()
|
||||||
await editor.expectEditor.toContain('profile003 = circle({ center = [')
|
await editor.expectEditor.toContain(
|
||||||
|
'profile003 = circle(sketch001, center = ['
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('equip line tool and verify circle code is removed', async () => {
|
await test.step('equip line tool and verify circle code is removed', async () => {
|
||||||
await toolbar.lineBtn.click()
|
await toolbar.lineBtn.click()
|
||||||
await editor.expectEditor.not.toContain('profile003 = circle({')
|
await editor.expectEditor.not.toContain('profile003 = circle(')
|
||||||
})
|
})
|
||||||
|
|
||||||
const [circle3Point1] = scene.makeMouseHelpers(650, 200)
|
const [circle3Point1] = scene.makeMouseHelpers(650, 200)
|
||||||
@ -1649,7 +1649,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
|||||||
await circle1Radius({ delay: 500 })
|
await circle1Radius({ delay: 500 })
|
||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`profile003 = circle({ center = [23.19, 6.98], radius = 2.5 }, sketch001)`
|
`profile003 = circle(sketch001, center = [23.19, 6.98], radius = 2.5)`
|
||||||
)
|
)
|
||||||
|
|
||||||
await test.step('hover in empty space to wait for overlays to get out of the way', async () => {
|
await test.step('hover in empty space to wait for overlays to get out of the way', async () => {
|
||||||
@ -1661,7 +1661,7 @@ profile003 = startProfileAt([206.63, -56.73], sketch001)
|
|||||||
await page.waitForTimeout(300)
|
await page.waitForTimeout(300)
|
||||||
await circle2Radius()
|
await circle2Radius()
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`profile004 = circle({ center = [23.74, 1.9], radius = 0.72 }, sketch001)`
|
`profile004 = circle(sketch001, center = [23.74, 1.9], radius = 0.72)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
await test.step('create two corner rectangles in a row without unequip', async () => {
|
await test.step('create two corner rectangles in a row without unequip', async () => {
|
||||||
@ -1855,7 +1855,7 @@ profile002 = startProfileAt([11.19, 5.02], sketch001)
|
|||||||
], %)
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
|
profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
||||||
profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07], p3 = [18.75, -4.41])
|
profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07], p3 = [18.75, -4.41])
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@ -1931,7 +1931,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
|
|||||||
await dragCircleTo()
|
await dragCircleTo()
|
||||||
await page.mouse.up()
|
await page.mouse.up()
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`profile003 = circle({ center = [6.92, -4.2], radius = 4.81 }, sketch001)`
|
`profile003 = circle(sketch001, center = [6.92, -4.2], radius = 4.81)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -2000,7 +2000,7 @@ profile002 = startProfileAt([11.19, 5.02], sketch001)
|
|||||||
], %)
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile003 = circle({ center = [6.92, -4.2], radius = 3.16 }, sketch001)
|
profile003 = circle(sketch001, center = [6.92, -4.2], radius = 3.16)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -2108,10 +2108,11 @@ profile003 = startProfileAt([16.79, 38.24], sketch001)
|
|||||||
], %)
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile004 = circle({
|
profile004 = circle(
|
||||||
|
sketch001,
|
||||||
center = [280.45, 47.57],
|
center = [280.45, 47.57],
|
||||||
radius = 55.26
|
radius = 55.26
|
||||||
}, sketch001)
|
)
|
||||||
extrude002 = extrude(profile001, length = 50)
|
extrude002 = extrude(profile001, length = 50)
|
||||||
extrude001 = extrude(profile003, length = 5)
|
extrude001 = extrude(profile003, length = 5)
|
||||||
`
|
`
|
||||||
@ -2173,10 +2174,11 @@ extrude001 = extrude(profile003, length = 5)
|
|||||||
'myVar = 5',
|
'myVar = 5',
|
||||||
`myVar = 5
|
`myVar = 5
|
||||||
sketch001 = startSketchOn('XZ')
|
sketch001 = startSketchOn('XZ')
|
||||||
profile001 = circle({
|
profile001 = circle(
|
||||||
|
sketch001,
|
||||||
center = [12.41, 3.87],
|
center = [12.41, 3.87],
|
||||||
radius = myVar
|
radius = myVar
|
||||||
}, sketch001)`
|
)`
|
||||||
)
|
)
|
||||||
|
|
||||||
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
await scene.expectPixelColor([255, 255, 255], { x: 633, y: 211 }, 15)
|
||||||
@ -2320,7 +2322,7 @@ profile004 = startProfileAt([3.15, 9.39], sketch002)
|
|||||||
|> line(end = [-7.41, -2.85])
|
|> line(end = [-7.41, -2.85])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile005 = circle({ center = [5.15, 4.34], radius = 1.66 }, sketch002)
|
profile005 = circle(sketch002, center = [5.15, 4.34], radius = 1.66)
|
||||||
profile006 = startProfileAt([9.65, 3.82], sketch002)
|
profile006 = startProfileAt([9.65, 3.82], sketch002)
|
||||||
|> line(end = [2.38, 5.62])
|
|> line(end = [2.38, 5.62])
|
||||||
|> line(end = [2.13, -5.57])
|
|> line(end = [2.13, -5.57])
|
||||||
@ -2345,10 +2347,11 @@ profile009 = startProfileAt([5.23, 1.95], sketch003)
|
|||||||
|> line(end = [7.34, -2.75])
|
|> line(end = [7.34, -2.75])
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile010 = circle({
|
profile010 = circle(
|
||||||
|
sketch003,
|
||||||
center = [7.18, -2.11],
|
center = [7.18, -2.11],
|
||||||
radius = 2.67
|
radius = 2.67
|
||||||
}, sketch003)
|
)
|
||||||
profile011 = startProfileAt([5.07, -6.39], sketch003)
|
profile011 = startProfileAt([5.07, -6.39], sketch003)
|
||||||
|> angledLine([0, 4.54], %, $rectangleSegmentA002)
|
|> angledLine([0, 4.54], %, $rectangleSegmentA002)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
@ -2417,8 +2420,8 @@ extrude003 = extrude(profile011, length = 2.5)
|
|||||||
},
|
},
|
||||||
// TODO keeps failing
|
// TODO keeps failing
|
||||||
// {
|
// {
|
||||||
// title: 'select cap circle',
|
// title: 'select cap circle',
|
||||||
// selectClick: scene.makeMouseHelpers(679, 290)[0],
|
// selectClick: scene.makeMouseHelpers(679, 290)[0],
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: 'select cap extrude wall',
|
title: 'select cap extrude wall',
|
||||||
@ -2468,7 +2471,7 @@ extrude003 = extrude(profile011, length = 2.5)
|
|||||||
})
|
})
|
||||||
|
|
||||||
const verifyCapProfilesAreDrawn = async () =>
|
const verifyCapProfilesAreDrawn = async () =>
|
||||||
test.step('verify wall profiles are drawn', async () => {
|
test.step('verify cap profiles are drawn', async () => {
|
||||||
// open polygon
|
// open polygon
|
||||||
await scene.expectPixelColor(
|
await scene.expectPixelColor(
|
||||||
TEST_COLORS.WHITE,
|
TEST_COLORS.WHITE,
|
||||||
@ -2519,13 +2522,14 @@ extrude003 = extrude(profile011, length = 2.5)
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('select cap profiles', async () => {
|
/* FIXME: the cap part of this test is insanely flaky, and I'm not sure
|
||||||
|
* why.
|
||||||
|
* await test.step('select cap profiles', async () => {
|
||||||
for (const { title, selectClick } of capSelectionOptions) {
|
for (const { title, selectClick } of capSelectionOptions) {
|
||||||
await test.step(title, async () => {
|
await test.step(title, async () => {
|
||||||
await camPositionForSelectingSketchOnCapProfiles()
|
await camPositionForSelectingSketchOnCapProfiles()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await selectClick()
|
await selectClick()
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await toolbar.editSketch()
|
await toolbar.editSketch()
|
||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(600)
|
||||||
await verifyCapProfilesAreDrawn()
|
await verifyCapProfilesAreDrawn()
|
||||||
@ -2533,7 +2537,7 @@ extrude003 = extrude(profile011, length = 2.5)
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
}) */
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
test(
|
test(
|
||||||
|
@ -599,7 +599,7 @@ test(
|
|||||||
mask: [page.getByTestId('model-state-indicator')],
|
mask: [page.getByTestId('model-state-indicator')],
|
||||||
})
|
})
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`sketch001 = startSketchOn('XZ')profile001 = circle({ center = [14.44, -2.44], radius = 1 }, sketch001)`
|
`sketch001 = startSketchOn('XZ')profile001 = circle(sketch001, center = [14.44, -2.44], radius = 1)`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
@ -29,5 +29,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"kcl_version": "0.2.44"
|
"kcl_version": "0.2.45"
|
||||||
}
|
}
|
@ -10,13 +10,13 @@ export const TEST_SETTINGS = {
|
|||||||
theme: Themes.Dark,
|
theme: Themes.Dark,
|
||||||
onboardingStatus: 'dismissed',
|
onboardingStatus: 'dismissed',
|
||||||
projectDirectory: '',
|
projectDirectory: '',
|
||||||
enableSSAO: false,
|
showDebugPanel: true,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
|
enableSSAO: false,
|
||||||
defaultUnit: 'in',
|
defaultUnit: 'in',
|
||||||
mouseControls: 'Zoo',
|
mouseControls: 'Zoo',
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
showDebugPanel: true,
|
|
||||||
},
|
},
|
||||||
projects: {
|
projects: {
|
||||||
defaultProjectName: 'project-$nnn',
|
defaultProjectName: 'project-$nnn',
|
||||||
@ -59,12 +59,12 @@ export const TEST_SETTINGS_CORRUPTED = {
|
|||||||
theme: Themes.Dark,
|
theme: Themes.Dark,
|
||||||
onboardingStatus: 'dismissed',
|
onboardingStatus: 'dismissed',
|
||||||
projectDirectory: 123 as any,
|
projectDirectory: 123 as any,
|
||||||
|
showDebugPanel: true,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: 'invalid' as any,
|
defaultUnit: 'invalid' as any,
|
||||||
mouseControls: `() => alert('hack the planet')` as any,
|
mouseControls: `() => alert('hack the planet')` as any,
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
showDebugPanel: true,
|
|
||||||
},
|
},
|
||||||
projects: {
|
projects: {
|
||||||
defaultProjectName: false as any,
|
defaultProjectName: false as any,
|
||||||
|
@ -243,7 +243,7 @@ test.describe('Testing Gizmo', { tag: ['@skipWin'] }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe(`Testing gizmo, fixture-based`, () => {
|
test.describe(`Testing gizmo, fixture-based`, () => {
|
||||||
test('Center on selection from menu', async ({
|
test('Center on selection from menu, disable interaction in sketch mode', async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
@ -269,10 +269,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
], %)
|
], %)
|
||||||
|> close()
|
|> close()
|
||||||
const sketch001 = startSketchOn('XZ')
|
const sketch001 = startSketchOn('XZ')
|
||||||
|> circle({
|
|> circle(center = [818.33, 168.1], radius = 182.8)
|
||||||
center: [818.33, 168.1],
|
|
||||||
radius: 182.8
|
|
||||||
}, %)
|
|
||||||
|> extrude(length = 50)
|
|> extrude(length = 50)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
@ -295,12 +292,11 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||||
|
|
||||||
await test.step(`Select an edge of this circle`, async () => {
|
await test.step(`Select an edge of this circle`, async () => {
|
||||||
const circleSnippet =
|
const circleSnippet = 'circle(center = [818.33, 168.1], radius = 182.8)'
|
||||||
'circle({ center: [818.33, 168.1], radius: 182.8 }, %)'
|
|
||||||
await moveToCircle()
|
await moveToCircle()
|
||||||
await clickCircle()
|
await clickCircle()
|
||||||
await editor.expectState({
|
await editor.expectState({
|
||||||
activeLines: [circleSnippet.slice(-5)],
|
activeLines: ['|>' + circleSnippet],
|
||||||
highlightedCode: circleSnippet,
|
highlightedCode: circleSnippet,
|
||||||
diagnostics: [],
|
diagnostics: [],
|
||||||
})
|
})
|
||||||
@ -318,5 +314,26 @@ test.describe(`Testing gizmo, fixture-based`, () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await test.step(`Gizmo should be disabled when in sketch mode`, async () => {
|
||||||
|
const sketchModeButton = page.getByRole('button', {
|
||||||
|
name: 'Edit sketch',
|
||||||
|
})
|
||||||
|
const exitSketchButton = page.getByRole('button', {
|
||||||
|
name: 'Exit sketch',
|
||||||
|
})
|
||||||
|
|
||||||
|
await sketchModeButton.click()
|
||||||
|
await expect(exitSketchButton).toBeVisible()
|
||||||
|
const gizmoPopoverButton = page.getByRole('button', {
|
||||||
|
name: 'view settings',
|
||||||
|
})
|
||||||
|
await gizmoPopoverButton.click()
|
||||||
|
const buttonToTest = page.getByRole('button', {
|
||||||
|
name: 'right view',
|
||||||
|
})
|
||||||
|
await expect(buttonToTest).toBeVisible()
|
||||||
|
await expect(buttonToTest).toBeDisabled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -825,7 +825,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
|||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`part001 = startSketchOn('XZ')
|
`part001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [1 + 0, 0], radius = 8 }, %)
|
|> circle(center = [1 + 0, 0], radius = 8)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
@ -840,9 +840,7 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
|||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page
|
await page.getByText('circle(center = [1 + 0, 0], radius = 8)').click()
|
||||||
.getByText('circle({ center = [1 + 0, 0], radius = 8 }, %)')
|
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
@ -859,11 +857,9 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
|||||||
await clickConstrained({
|
await clickConstrained({
|
||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'xAbsolute',
|
constraintType: 'xAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained: 'circle(center = [1 + 0, 0], radius = 8)',
|
||||||
'circle({ center = [1 + 0, 0], radius = 8 }, %)',
|
expectAfterUnconstrained: 'circle(center = [1, 0], radius = 8)',
|
||||||
expectAfterUnconstrained:
|
expectFinal: 'circle(center = [xAbs001, 0], radius = 8)',
|
||||||
'circle({ center = [1, 0], radius = 8 }, %)',
|
|
||||||
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
|
||||||
ang: ang + 105,
|
ang: ang + 105,
|
||||||
steps: 6,
|
steps: 6,
|
||||||
locator: '[data-overlay-toolbar-index="0"]',
|
locator: '[data-overlay-toolbar-index="0"]',
|
||||||
@ -873,10 +869,10 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
|||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'yAbsolute',
|
constraintType: 'yAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
'circle(center = [xAbs001, 0], radius = 8)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'circle({ center = [xAbs001, yAbs001], radius = 8 }, %)',
|
'circle(center = [xAbs001, yAbs001], radius = 8)',
|
||||||
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
expectFinal: 'circle(center = [xAbs001, 0], radius = 8)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
steps: 30,
|
steps: 30,
|
||||||
locator: '[data-overlay-toolbar-index="0"]',
|
locator: '[data-overlay-toolbar-index="0"]',
|
||||||
@ -886,10 +882,10 @@ test.describe('Testing segment overlays', { tag: ['@skipWin'] }, () => {
|
|||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'radius',
|
constraintType: 'radius',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
'circle(center = [xAbs001, 0], radius = 8)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'circle({ center = [xAbs001, 0], radius = radius001 }, %)',
|
'circle(center = [xAbs001, 0], radius = radius001)',
|
||||||
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
expectFinal: 'circle(center = [xAbs001, 0], radius = 8)',
|
||||||
ang: ang + 105,
|
ang: ang + 105,
|
||||||
steps: 10,
|
steps: 10,
|
||||||
locator: '[data-overlay-toolbar-index="0"]',
|
locator: '[data-overlay-toolbar-index="0"]',
|
||||||
|
@ -320,10 +320,11 @@ part009 = startSketchOn('XY')
|
|||||||
|> close()
|
|> close()
|
||||||
rev = revolve({ axis = 'y' }, part009)
|
rev = revolve({ axis = 'y' }, part009)
|
||||||
sketch006 = startSketchOn('XY')
|
sketch006 = startSketchOn('XY')
|
||||||
profile001 = circle({
|
profile001 = circle(
|
||||||
|
sketch006,
|
||||||
center = [42.91, -70.42],
|
center = [42.91, -70.42],
|
||||||
radius = 17.96
|
radius = 17.96
|
||||||
}, sketch006)
|
)
|
||||||
profile002 = startProfileAt([86.92, -63.81], sketch006)
|
profile002 = startProfileAt([86.92, -63.81], sketch006)
|
||||||
|> angledLine([0, 63.81], %, $rectangleSegmentA001)
|
|> angledLine([0, 63.81], %, $rectangleSegmentA001)
|
||||||
|> angledLine([
|
|> angledLine([
|
||||||
|
@ -62,7 +62,7 @@ test.describe('Testing settings', () => {
|
|||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
exact: true,
|
exact: true,
|
||||||
})
|
})
|
||||||
const inputLocator = page.locator('input[name="modeling-showDebugPanel"]')
|
const inputLocator = page.locator('input[name="app-showDebugPanel"]')
|
||||||
|
|
||||||
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
await test.step('Open settings dialog and set "Show debug panel" to on', async () => {
|
||||||
await page.keyboard.press('ControlOrMeta+,')
|
await page.keyboard.press('ControlOrMeta+,')
|
||||||
@ -125,7 +125,7 @@ test.describe('Testing settings', () => {
|
|||||||
// Check that the project setting did not change
|
// Check that the project setting did not change
|
||||||
await page.getByRole('radio', { name: 'Project' }).click()
|
await page.getByRole('radio', { name: 'Project' }).click()
|
||||||
await expect(
|
await expect(
|
||||||
page.locator('input[name="modeling-showDebugPanel"]')
|
page.locator('input[name="app-showDebugPanel"]')
|
||||||
).not.toBeChecked()
|
).not.toBeChecked()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -833,7 +833,8 @@ test.describe('Testing settings', () => {
|
|||||||
// but "show debug panel" set to false
|
// but "show debug panel" set to false
|
||||||
appSettings: {
|
appSettings: {
|
||||||
...TEST_SETTINGS,
|
...TEST_SETTINGS,
|
||||||
modeling: { ...TEST_SETTINGS.modeling, showDebugPanel: false },
|
app: { ...TEST_SETTINGS.app, showDebugPanel: false },
|
||||||
|
modeling: { ...TEST_SETTINGS.modeling },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async ({ context, page, homePage }) => {
|
async ({ context, page, homePage }) => {
|
||||||
@ -853,7 +854,7 @@ test.describe('Testing settings', () => {
|
|||||||
const debugPaneButton = page.getByTestId('debug-pane-button')
|
const debugPaneButton = page.getByTestId('debug-pane-button')
|
||||||
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
const commandsButton = page.getByRole('button', { name: 'Commands' })
|
||||||
const debugPaneOption = page.getByRole('option', {
|
const debugPaneOption = page.getByRole('option', {
|
||||||
name: 'Settings · modeling · show debug panel',
|
name: 'Settings · app · show debug panel',
|
||||||
})
|
})
|
||||||
|
|
||||||
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
async function setShowDebugPanelTo(value: 'On' | 'Off') {
|
||||||
|
@ -125,6 +125,34 @@
|
|||||||
"title": "100mm Gear Rack",
|
"title": "100mm Gear Rack",
|
||||||
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
"description": "A flat bar or rail that is engraved with teeth along its length. These teeth are designed to mesh with the teeth of a gear, known as a pinion. When the pinion, a small cylindrical gear, rotates, its teeth engage with the teeth on the rack, causing the rack to move linearly. Conversely, linear motion applied to the rack will cause the pinion to rotate."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gridfinity-baseplate/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Gridfinity Baseplate",
|
||||||
|
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gridfinity-baseplate-magnets/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Gridfinity Baseplate With Magnets",
|
||||||
|
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This baseplate version includes holes for magnet placement"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gridfinity-bins/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Gridfinity Bins",
|
||||||
|
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "gridfinity-bins-stacking-lip/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Gridfinity Bins With A Stacking Lip",
|
||||||
|
"description": "Gridfinity is a system to help you work more efficiently. This is a system invented by Zack Freedman. There are two main components the baseplate and the bins. The components are comprised of a matrix of squares. Allowing easy stacking and expansion. This Gridfinity bins version includes a lip to allowable stacking Gridfinity bins"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "hex-nut/main.kcl",
|
||||||
|
@ -287,12 +287,14 @@ export class CameraControls {
|
|||||||
camSettings.up.y,
|
camSettings.up.y,
|
||||||
camSettings.up.z
|
camSettings.up.z
|
||||||
)
|
)
|
||||||
|
|
||||||
this.camera.quaternion.set(
|
this.camera.quaternion.set(
|
||||||
orientation.x,
|
orientation.x,
|
||||||
orientation.y,
|
orientation.y,
|
||||||
orientation.z,
|
orientation.z,
|
||||||
orientation.w
|
orientation.w
|
||||||
)
|
)
|
||||||
|
|
||||||
this.camera.up.copy(newUp)
|
this.camera.up.copy(newUp)
|
||||||
this.camera.updateProjectionMatrix()
|
this.camera.updateProjectionMatrix()
|
||||||
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
|
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
|
||||||
|
@ -1607,15 +1607,15 @@ export class SceneEntities {
|
|||||||
const varName = findUniqueName(_ast, 'profile')
|
const varName = findUniqueName(_ast, 'profile')
|
||||||
const newExpression = createVariableDeclaration(
|
const newExpression = createVariableDeclaration(
|
||||||
varName,
|
varName,
|
||||||
createCallExpressionStdLib('circle', [
|
createCallExpressionStdLibKw('circle', varDec.node.id, [
|
||||||
createObjectExpression({
|
createLabeledArg(
|
||||||
center: createArrayExpression([
|
'center',
|
||||||
|
createArrayExpression([
|
||||||
createLiteral(roundOff(circleCenter[0])),
|
createLiteral(roundOff(circleCenter[0])),
|
||||||
createLiteral(roundOff(circleCenter[1])),
|
createLiteral(roundOff(circleCenter[1])),
|
||||||
]),
|
])
|
||||||
radius: createLiteral(1),
|
),
|
||||||
}),
|
createLabeledArg('radius', createLiteral(1)),
|
||||||
createIdentifier(varDec.node.id.name),
|
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1666,7 +1666,7 @@ export class SceneEntities {
|
|||||||
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
const x = (args.intersectionPoint.twoD.x || 0) - circleCenter[0]
|
||||||
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
const y = (args.intersectionPoint.twoD.y || 0) - circleCenter[1]
|
||||||
|
|
||||||
if (sketchInit.type === 'CallExpression') {
|
if (sketchInit.type === 'CallExpressionKw') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.variables,
|
kclManager.variables,
|
||||||
@ -1731,7 +1731,7 @@ export class SceneEntities {
|
|||||||
const sketchInit = _node.node?.declaration.init
|
const sketchInit = _node.node?.declaration.init
|
||||||
|
|
||||||
let modded = structuredClone(_ast)
|
let modded = structuredClone(_ast)
|
||||||
if (sketchInit.type === 'CallExpression') {
|
if (sketchInit.type === 'CallExpressionKw') {
|
||||||
const moddedResult = changeSketchArguments(
|
const moddedResult = changeSketchArguments(
|
||||||
modded,
|
modded,
|
||||||
kclManager.variables,
|
kclManager.variables,
|
||||||
|
@ -168,37 +168,43 @@ function CommandArgOptionInput({
|
|||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Combobox.Options
|
{filteredOptions?.length ? (
|
||||||
static
|
<Combobox.Options
|
||||||
className="overflow-y-auto max-h-96 cursor-pointer"
|
static
|
||||||
onMouseDown={() => {
|
className="overflow-y-auto max-h-96 cursor-pointer"
|
||||||
setShouldSubmitOnChange(true)
|
onMouseDown={() => {
|
||||||
}}
|
setShouldSubmitOnChange(true)
|
||||||
>
|
}}
|
||||||
{filteredOptions?.map((option) => (
|
>
|
||||||
<Combobox.Option
|
{filteredOptions?.map((option) => (
|
||||||
key={option.name}
|
<Combobox.Option
|
||||||
value={option}
|
key={option.name}
|
||||||
disabled={option.disabled}
|
value={option}
|
||||||
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
disabled={option.disabled}
|
||||||
>
|
className="flex items-center gap-2 px-4 py-1 first:mt-2 last:mb-2 ui-active:bg-primary/10 dark:ui-active:bg-chalkboard-90"
|
||||||
<p
|
|
||||||
className={`flex-grow ${
|
|
||||||
(option.disabled &&
|
|
||||||
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
|
|
||||||
''
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{option.name}
|
<p
|
||||||
</p>
|
className={`flex-grow ${
|
||||||
{option.value === currentOption?.value && (
|
(option.disabled &&
|
||||||
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
'text-chalkboard-70 dark:text-chalkboard-50 cursor-not-allowed') ||
|
||||||
current
|
''
|
||||||
</small>
|
}`}
|
||||||
)}
|
>
|
||||||
</Combobox.Option>
|
{option.name}
|
||||||
))}
|
</p>
|
||||||
</Combobox.Options>
|
{option.value === currentOption?.value && (
|
||||||
|
<small className="text-chalkboard-70 dark:text-chalkboard-50">
|
||||||
|
current
|
||||||
|
</small>
|
||||||
|
)}
|
||||||
|
</Combobox.Option>
|
||||||
|
))}
|
||||||
|
</Combobox.Options>
|
||||||
|
) : (
|
||||||
|
<p className="px-4 pt-2 text-chalkboard-60 dark:text-chalkboard-50">
|
||||||
|
No results found
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</Combobox>
|
</Combobox>
|
||||||
</form>
|
</form>
|
||||||
)
|
)
|
||||||
|
@ -43,9 +43,10 @@ export const CommandBar = () => {
|
|||||||
if (commandBarState.matches('Review')) {
|
if (commandBarState.matches('Review')) {
|
||||||
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
||||||
([_, argConfig]) =>
|
([_, argConfig]) =>
|
||||||
typeof argConfig.required === 'function'
|
!argConfig.hidden &&
|
||||||
|
(typeof argConfig.required === 'function'
|
||||||
? argConfig.required(commandBarState.context)
|
? argConfig.required(commandBarState.context)
|
||||||
: argConfig.required
|
: argConfig.required)
|
||||||
)
|
)
|
||||||
|
|
||||||
const currentArgName = entries[entries.length - 1][0]
|
const currentArgName = entries[entries.length - 1][0]
|
||||||
@ -64,7 +65,9 @@ export const CommandBar = () => {
|
|||||||
commandBarActor.send({ type: 'Deselect command' })
|
commandBarActor.send({ type: 'Deselect command' })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const entries = Object.entries(selectedCommand?.args || {})
|
const entries = Object.entries(selectedCommand?.args || {}).filter(
|
||||||
|
(a) => !a[1].hidden
|
||||||
|
)
|
||||||
const index = entries.findIndex(
|
const index = entries.findIndex(
|
||||||
([key, _]) => key === currentArgument.name
|
([key, _]) => key === currentArgument.name
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { CustomIcon } from '../CustomIcon'
|
import { CustomIcon } from '../CustomIcon'
|
||||||
import React, { useState } from 'react'
|
import React, { useMemo, useState } from 'react'
|
||||||
import { ActionButton } from '../ActionButton'
|
import { ActionButton } from '../ActionButton'
|
||||||
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
|
import { Selections, getSelectionTypeDisplayText } from 'lib/selections'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
@ -13,6 +13,14 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
const {
|
const {
|
||||||
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
context: { selectedCommand, currentArgument, argumentsToSubmit },
|
||||||
} = commandBarState
|
} = commandBarState
|
||||||
|
const nonHiddenArgs = useMemo(() => {
|
||||||
|
if (!selectedCommand?.args) return undefined
|
||||||
|
const s = { ...selectedCommand.args }
|
||||||
|
for (const [name, arg] of Object.entries(s)) {
|
||||||
|
if (arg.hidden) delete s[name]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}, [selectedCommand])
|
||||||
const isReviewing = commandBarState.matches('Review')
|
const isReviewing = commandBarState.matches('Review')
|
||||||
const [showShortcuts, setShowShortcuts] = useState(false)
|
const [showShortcuts, setShowShortcuts] = useState(false)
|
||||||
|
|
||||||
@ -43,11 +51,9 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
],
|
],
|
||||||
(_, b) => {
|
(_, b) => {
|
||||||
if (b.keys && !Number.isNaN(parseInt(b.keys[0], 10))) {
|
if (b.keys && !Number.isNaN(parseInt(b.keys[0], 10))) {
|
||||||
if (!selectedCommand?.args) return
|
if (!nonHiddenArgs) return
|
||||||
const argName = Object.keys(selectedCommand.args)[
|
const argName = Object.keys(nonHiddenArgs)[parseInt(b.keys[0], 10) - 1]
|
||||||
parseInt(b.keys[0], 10) - 1
|
const arg = nonHiddenArgs[argName]
|
||||||
]
|
|
||||||
const arg = selectedCommand?.args[argName]
|
|
||||||
if (!argName || !arg) return
|
if (!argName || !arg) return
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Change current argument',
|
type: 'Change current argument',
|
||||||
@ -78,7 +84,7 @@ function CommandBarHeader({ children }: React.PropsWithChildren<{}>) {
|
|||||||
{selectedCommand.displayName || selectedCommand.name}
|
{selectedCommand.displayName || selectedCommand.name}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
{Object.entries(selectedCommand?.args || {})
|
{Object.entries(nonHiddenArgs || {})
|
||||||
.filter(([_, argConfig]) =>
|
.filter(([_, argConfig]) =>
|
||||||
typeof argConfig.required === 'function'
|
typeof argConfig.required === 'function'
|
||||||
? argConfig.required(commandBarState.context)
|
? argConfig.required(commandBarState.context)
|
||||||
|
@ -4,6 +4,7 @@ import { type IndexLoaderData } from 'lib/types'
|
|||||||
import { BROWSER_PATH, PATHS } from 'lib/paths'
|
import { BROWSER_PATH, PATHS } from 'lib/paths'
|
||||||
import React, { createContext, useEffect, useMemo } from 'react'
|
import React, { createContext, useEffect, useMemo } from 'react'
|
||||||
import { toast } from 'react-hot-toast'
|
import { toast } from 'react-hot-toast'
|
||||||
|
import { DEV } from 'env'
|
||||||
import {
|
import {
|
||||||
Actor,
|
Actor,
|
||||||
AnyStateMachine,
|
AnyStateMachine,
|
||||||
@ -32,6 +33,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
|||||||
import { settingsActor, useSettings } from 'machines/appMachine'
|
import { settingsActor, useSettings } from 'machines/appMachine'
|
||||||
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
|
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
|
||||||
import { useToken } from 'machines/appMachine'
|
import { useToken } from 'machines/appMachine'
|
||||||
|
import { createNamedViewsCommand } from 'lib/commandBarConfigs/namedViewsConfig'
|
||||||
|
|
||||||
type MachineContext<T extends AnyStateMachine> = {
|
type MachineContext<T extends AnyStateMachine> = {
|
||||||
state: StateFrom<T>
|
state: StateFrom<T>
|
||||||
@ -58,6 +60,38 @@ export const FileMachineProvider = ({
|
|||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// TODO: Engine feature is not deployed
|
||||||
|
if (DEV) {
|
||||||
|
const {
|
||||||
|
createNamedViewCommand,
|
||||||
|
deleteNamedViewCommand,
|
||||||
|
loadNamedViewCommand,
|
||||||
|
} = createNamedViewsCommand()
|
||||||
|
|
||||||
|
const commands = [
|
||||||
|
createNamedViewCommand,
|
||||||
|
deleteNamedViewCommand,
|
||||||
|
loadNamedViewCommand,
|
||||||
|
]
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Add commands',
|
||||||
|
data: {
|
||||||
|
commands,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
// Remove commands if you go to the home page
|
||||||
|
commandBarActor.send({
|
||||||
|
type: 'Remove commands',
|
||||||
|
data: {
|
||||||
|
commands,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
|
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
|
||||||
// This will register the commands to route to Telemetry, Home, and Settings.
|
// This will register the commands to route to Telemetry, Home, and Settings.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -27,6 +27,8 @@ import {
|
|||||||
ViewControlContextMenu,
|
ViewControlContextMenu,
|
||||||
} from './ViewControlMenu'
|
} from './ViewControlMenu'
|
||||||
import { AxisNames } from 'lib/constants'
|
import { AxisNames } from 'lib/constants'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { useSettings } from 'machines/appMachine'
|
||||||
|
|
||||||
const CANVAS_SIZE = 80
|
const CANVAS_SIZE = 80
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
@ -40,12 +42,33 @@ enum AxisColors {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function Gizmo() {
|
export default function Gizmo() {
|
||||||
|
const { state: modelingState } = useModelingContext()
|
||||||
|
const settings = useSettings()
|
||||||
const menuItems = useViewControlMenuItems()
|
const menuItems = useViewControlMenuItems()
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||||
const cameraPassiveUpdateTimer = useRef(0)
|
const cameraPassiveUpdateTimer = useRef(0)
|
||||||
const raycasterPassiveUpdateTimer = useRef(0)
|
const raycasterPassiveUpdateTimer = useRef(0)
|
||||||
|
const disableOrbitRef = useRef(false)
|
||||||
|
|
||||||
|
// Temporary fix for #4040:
|
||||||
|
// Disable gizmo orbiting in sketch mode
|
||||||
|
// This effect updates disableOrbitRef whenever the user
|
||||||
|
// toggles between Sketch mode and 3D mode
|
||||||
|
useEffect(() => {
|
||||||
|
disableOrbitRef.current =
|
||||||
|
modelingState.matches('Sketch') &&
|
||||||
|
!settings.app.allowOrbitInSketchMode.current
|
||||||
|
if (wrapperRef.current) {
|
||||||
|
wrapperRef.current.style.filter = disableOrbitRef.current
|
||||||
|
? 'grayscale(100%)'
|
||||||
|
: 'none'
|
||||||
|
wrapperRef.current.style.cursor = disableOrbitRef.current
|
||||||
|
? 'not-allowed'
|
||||||
|
: 'auto'
|
||||||
|
}
|
||||||
|
}, [modelingState, settings.app.allowOrbitInSketchMode.current])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
@ -64,7 +87,8 @@ export default function Gizmo() {
|
|||||||
const { mouse, disposeMouseEvents } = initializeMouseEvents(
|
const { mouse, disposeMouseEvents } = initializeMouseEvents(
|
||||||
canvas,
|
canvas,
|
||||||
raycasterIntersect,
|
raycasterIntersect,
|
||||||
sceneInfra
|
sceneInfra,
|
||||||
|
disableOrbitRef
|
||||||
)
|
)
|
||||||
const raycasterObjects = [...gizmoAxisHeads]
|
const raycasterObjects = [...gizmoAxisHeads]
|
||||||
|
|
||||||
@ -81,15 +105,21 @@ export default function Gizmo() {
|
|||||||
delta,
|
delta,
|
||||||
cameraPassiveUpdateTimer
|
cameraPassiveUpdateTimer
|
||||||
)
|
)
|
||||||
updateRayCaster(
|
// If orbits are disabled, skip click logic
|
||||||
raycasterObjects,
|
if (!disableOrbitRef.current) {
|
||||||
raycaster,
|
updateRayCaster(
|
||||||
mouse,
|
raycasterObjects,
|
||||||
camera,
|
raycaster,
|
||||||
raycasterIntersect,
|
mouse,
|
||||||
delta,
|
camera,
|
||||||
raycasterPassiveUpdateTimer
|
raycasterIntersect,
|
||||||
)
|
delta,
|
||||||
|
raycasterPassiveUpdateTimer
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
raycasterObjects.forEach((object) => object.scale.set(1, 1, 1)) // Reset scales
|
||||||
|
raycasterIntersect.current = null // Clear intersection
|
||||||
|
}
|
||||||
renderer.render(scene, camera)
|
renderer.render(scene, camera)
|
||||||
requestAnimationFrame(animate)
|
requestAnimationFrame(animate)
|
||||||
}
|
}
|
||||||
@ -246,7 +276,8 @@ const quaternionsEqual = (
|
|||||||
const initializeMouseEvents = (
|
const initializeMouseEvents = (
|
||||||
canvas: HTMLCanvasElement,
|
canvas: HTMLCanvasElement,
|
||||||
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
|
raycasterIntersect: MutableRefObject<Intersection<Object3D> | null>,
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra,
|
||||||
|
disableOrbitRef: MutableRefObject<boolean>
|
||||||
): { mouse: Vector2; disposeMouseEvents: () => void } => {
|
): { mouse: Vector2; disposeMouseEvents: () => void } => {
|
||||||
const mouse = new Vector2()
|
const mouse = new Vector2()
|
||||||
mouse.x = 1 // fix initial mouse position issue
|
mouse.x = 1 // fix initial mouse position issue
|
||||||
@ -258,10 +289,10 @@ const initializeMouseEvents = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (raycasterIntersect.current) {
|
// If orbits are disabled, skip click logic
|
||||||
const axisName = raycasterIntersect.current.object.name as AxisNames
|
if (disableOrbitRef.current || !raycasterIntersect.current) return
|
||||||
sceneInfra.camControls.updateCameraToAxis(axisName).catch(reportRejection)
|
const axisName = raycasterIntersect.current.object.name as AxisNames
|
||||||
}
|
sceneInfra.camControls.updateCameraToAxis(axisName).catch(reportRejection)
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('mousemove', handleMouseMove)
|
window.addEventListener('mousemove', handleMouseMove)
|
||||||
|
@ -132,13 +132,14 @@ export const ModelingMachineProvider = ({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
app: { theme, enableSSAO, allowOrbitInSketchMode },
|
app: { theme, allowOrbitInSketchMode },
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit,
|
defaultUnit,
|
||||||
cameraProjection,
|
cameraProjection,
|
||||||
highlightEdges,
|
highlightEdges,
|
||||||
showScaleGrid,
|
showScaleGrid,
|
||||||
cameraOrbit,
|
cameraOrbit,
|
||||||
|
enableSSAO,
|
||||||
},
|
},
|
||||||
} = useSettings()
|
} = useSettings()
|
||||||
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
|
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
|
||||||
@ -666,6 +667,13 @@ export const ModelingMachineProvider = ({
|
|||||||
if (event.data?.forceNewSketch) return false
|
if (event.data?.forceNewSketch) return false
|
||||||
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
if (artifactIsPlaneWithPaths(selectionRanges)) {
|
||||||
return true
|
return true
|
||||||
|
} else if (selectionRanges.graphSelections[0]?.artifact) {
|
||||||
|
// See if the selection is "close enough" to be coerced to the plane later
|
||||||
|
const maybePlane = getPlaneFromArtifact(
|
||||||
|
selectionRanges.graphSelections[0].artifact,
|
||||||
|
engineCommandManager.artifactGraph
|
||||||
|
)
|
||||||
|
return !err(maybePlane)
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
isCursorInFunctionDefinition(
|
isCursorInFunctionDefinition(
|
||||||
|
@ -234,6 +234,6 @@ export const sidebarPanes: SidebarPane[] = [
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
keybinding: 'Shift + D',
|
keybinding: 'Shift + D',
|
||||||
hide: ({ settings }) => !settings.modeling.showDebugPanel.current,
|
hide: ({ settings }) => !settings.app.showDebugPanel.current,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
@ -48,7 +48,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
context.store?.openPanes.length === 0
|
context.store?.openPanes.length === 0
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
const showDebugPanel = settings.modeling.showDebugPanel
|
const showDebugPanel = settings.app.showDebugPanel
|
||||||
|
|
||||||
const paneCallbackProps = useMemo(
|
const paneCallbackProps = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
@ -149,7 +149,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [settings.modeling.showDebugPanel])
|
}, [settings.app.showDebugPanel])
|
||||||
|
|
||||||
const togglePane = useCallback(
|
const togglePane = useCallback(
|
||||||
(newPane: SidebarType) => {
|
(newPane: SidebarType) => {
|
||||||
|
@ -33,12 +33,50 @@ import { EventFrom } from 'xstate'
|
|||||||
import { fileMachine } from 'machines/fileMachine'
|
import { fileMachine } from 'machines/fileMachine'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { codeManager, kclManager } from 'lib/singletons'
|
import { codeManager, kclManager } from 'lib/singletons'
|
||||||
|
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
|
||||||
|
|
||||||
const CANVAS_SIZE = 128
|
const CANVAS_SIZE = 128
|
||||||
const PROMPT_TRUNCATE_LENGTH = 128
|
const PROMPT_TRUNCATE_LENGTH = 128
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
const OUTPUT_KEY = 'source.glb'
|
const OUTPUT_KEY = 'source.glb'
|
||||||
|
|
||||||
|
function TextToCadImprovementMessage({
|
||||||
|
label,
|
||||||
|
...rest
|
||||||
|
}: React.HTMLAttributes<HTMLDetailsElement> & { label: string }) {
|
||||||
|
return (
|
||||||
|
<details {...rest}>
|
||||||
|
<summary className="text-chalkboard-70 dark:text-chalkboard-30">
|
||||||
|
{label}
|
||||||
|
</summary>
|
||||||
|
<p className="text-sm text-chalkboard-70 dark:text-chalkboard-30">
|
||||||
|
Text-to-CAD is a new ML model. There will be prompts that work and
|
||||||
|
prompts that don't and prompts that generate something a little bit off.
|
||||||
|
Sometimes even a small tweak to your prompt will make it better on the
|
||||||
|
next run. Try our prompt-to-edit feature to iterate on your result with
|
||||||
|
AI. We look at all the failures to make the model better and see our
|
||||||
|
weaknesses. Over time the model will get better. See our{' '}
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/JQEpHR7Nt2"
|
||||||
|
onClick={openExternalBrowserIfDesktop(
|
||||||
|
'https://discord.gg/JQEpHR7Nt2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Discord
|
||||||
|
</a>{' '}
|
||||||
|
or{' '}
|
||||||
|
<a
|
||||||
|
href="https://community.zoo.dev/"
|
||||||
|
onClick={openExternalBrowserIfDesktop('https://community.zoo.dev/')}
|
||||||
|
>
|
||||||
|
Discourse
|
||||||
|
</a>{' '}
|
||||||
|
or some prompting tips from the community or our team.
|
||||||
|
</p>
|
||||||
|
</details>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export function ToastTextToCadError({
|
export function ToastTextToCadError({
|
||||||
toastId,
|
toastId,
|
||||||
message,
|
message,
|
||||||
@ -286,6 +324,10 @@ export function ToastTextToCadSuccess({
|
|||||||
: data.prompt}
|
: data.prompt}
|
||||||
"
|
"
|
||||||
</p>
|
</p>
|
||||||
|
<TextToCadImprovementMessage
|
||||||
|
className="text-sm mt-2"
|
||||||
|
label="Not what you expected?"
|
||||||
|
/>
|
||||||
</section>
|
</section>
|
||||||
<div className="flex justify-between gap-8">
|
<div className="flex justify-between gap-8">
|
||||||
<ActionButton
|
<ActionButton
|
||||||
@ -436,6 +478,10 @@ export function ToastPromptToEditCadSuccess({
|
|||||||
: data.prompt}
|
: data.prompt}
|
||||||
"
|
"
|
||||||
</p>
|
</p>
|
||||||
|
<TextToCadImprovementMessage
|
||||||
|
className="text-sm mt-2"
|
||||||
|
label="Not what you expected?"
|
||||||
|
/>
|
||||||
<p>Do you want to keep the change?</p>
|
<p>Do you want to keep the change?</p>
|
||||||
</section>
|
</section>
|
||||||
<div className="flex justify-between gap-8">
|
<div className="flex justify-between gap-8">
|
||||||
|
@ -10,9 +10,14 @@ import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
|
|||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
|
import { useSettings } from 'machines/appMachine'
|
||||||
|
|
||||||
export function useViewControlMenuItems() {
|
export function useViewControlMenuItems() {
|
||||||
const { send: modelingSend } = useModelingContext()
|
const { state: modelingState, send: modelingSend } = useModelingContext()
|
||||||
|
const settings = useSettings()
|
||||||
|
const shouldLockView =
|
||||||
|
modelingState.matches('Sketch') &&
|
||||||
|
!settings.app.allowOrbitInSketchMode.current
|
||||||
const menuItems = useMemo(
|
const menuItems = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
||||||
@ -23,6 +28,7 @@ export function useViewControlMenuItems() {
|
|||||||
.updateCameraToAxis(axisName as AxisNames)
|
.updateCameraToAxis(axisName as AxisNames)
|
||||||
.catch(reportRejection)
|
.catch(reportRejection)
|
||||||
}}
|
}}
|
||||||
|
disabled={shouldLockView}
|
||||||
>
|
>
|
||||||
{axisSemantic} view
|
{axisSemantic} view
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
@ -32,6 +38,7 @@ export function useViewControlMenuItems() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||||
}}
|
}}
|
||||||
|
disabled={shouldLockView}
|
||||||
>
|
>
|
||||||
Reset view
|
Reset view
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
@ -39,13 +46,14 @@ export function useViewControlMenuItems() {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
modelingSend({ type: 'Center camera on selection' })
|
modelingSend({ type: 'Center camera on selection' })
|
||||||
}}
|
}}
|
||||||
|
disabled={shouldLockView}
|
||||||
>
|
>
|
||||||
Center view on selection
|
Center view on selection
|
||||||
</ContextMenuItem>,
|
</ContextMenuItem>,
|
||||||
<ContextMenuDivider />,
|
<ContextMenuDivider />,
|
||||||
<ContextMenuItemRefresh />,
|
<ContextMenuItemRefresh />,
|
||||||
],
|
],
|
||||||
[VIEW_NAMES_SEMANTIC]
|
[VIEW_NAMES_SEMANTIC, shouldLockView]
|
||||||
)
|
)
|
||||||
return menuItems
|
return menuItems
|
||||||
}
|
}
|
||||||
|
@ -581,10 +581,10 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
})
|
})
|
||||||
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
|
it('finds sketch001 and sketch002 pipes to be lofted', async () => {
|
||||||
const exampleCode = `sketch001 = startSketchOn('XZ')
|
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
plane001 = offsetPlane('XZ', offset = 2)
|
plane001 = offsetPlane('XZ', offset = 2)
|
||||||
sketch002 = startSketchOn(plane001)
|
sketch002 = startSketchOn(plane001)
|
||||||
|> circle({ center = [0, 0], radius = 3 }, %)
|
|> circle(center = [0, 0], radius = 3)
|
||||||
`
|
`
|
||||||
const ast = assertParse(exampleCode)
|
const ast = assertParse(exampleCode)
|
||||||
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
|
const extrudable = doesSceneHaveSweepableSketch(ast, 2)
|
||||||
@ -608,7 +608,7 @@ extrude001 = extrude(sketch001, length = 10)
|
|||||||
describe('Testing doesSceneHaveExtrudedSketch', () => {
|
describe('Testing doesSceneHaveExtrudedSketch', () => {
|
||||||
it('finds extruded sketch as variable', async () => {
|
it('finds extruded sketch as variable', async () => {
|
||||||
const exampleCode = `sketch001 = startSketchOn('XZ')
|
const exampleCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
extrude001 = extrude(sketch001, length = 1)
|
extrude001 = extrude(sketch001, length = 1)
|
||||||
`
|
`
|
||||||
const ast = assertParse(exampleCode)
|
const ast = assertParse(exampleCode)
|
||||||
@ -618,7 +618,7 @@ extrude001 = extrude(sketch001, length = 1)
|
|||||||
})
|
})
|
||||||
it('finds extruded sketch in pipe', async () => {
|
it('finds extruded sketch in pipe', async () => {
|
||||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
|> extrude(length = 1)
|
|> extrude(length = 1)
|
||||||
`
|
`
|
||||||
const ast = assertParse(exampleCode)
|
const ast = assertParse(exampleCode)
|
||||||
@ -628,7 +628,7 @@ extrude001 = extrude(sketch001, length = 1)
|
|||||||
})
|
})
|
||||||
it('finds no extrusion with sketch only', async () => {
|
it('finds no extrusion with sketch only', async () => {
|
||||||
const exampleCode = `extrude001 = startSketchOn('XZ')
|
const exampleCode = `extrude001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 1 }, %)
|
|> circle(center = [0, 0], radius = 1)
|
||||||
`
|
`
|
||||||
const ast = assertParse(exampleCode)
|
const ast = assertParse(exampleCode)
|
||||||
if (err(ast)) throw ast
|
if (err(ast)) throw ast
|
||||||
|
@ -39,7 +39,7 @@ import {
|
|||||||
import { err, Reason } from 'lib/trap'
|
import { err, Reason } from 'lib/trap'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { findKwArg } from './util'
|
import { findKwArg } from './util'
|
||||||
import { codeRefFromRange } from './std/artifactGraph'
|
import { codeRefFromRange, getPlaneFromArtifact } from './std/artifactGraph'
|
||||||
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
|
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
|
||||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
|
||||||
|
@ -20,7 +20,13 @@ import { Models } from '@kittycad/lib'
|
|||||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||||
import { Selection } from 'lib/selections'
|
import { Selection } from 'lib/selections'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
import { Cap, Plane, Wall } from 'wasm-lib/kcl/bindings/Artifact'
|
import {
|
||||||
|
Cap,
|
||||||
|
Plane,
|
||||||
|
StartSketchOnFace,
|
||||||
|
StartSketchOnPlane,
|
||||||
|
Wall,
|
||||||
|
} from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
import { CapSubType } from 'wasm-lib/kcl/bindings/Artifact'
|
import { CapSubType } from 'wasm-lib/kcl/bindings/Artifact'
|
||||||
|
|
||||||
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
|
||||||
@ -610,6 +616,26 @@ function getPlaneFromSweepEdge(edge: SweepEdge, graph: ArtifactGraph) {
|
|||||||
if (err(path)) return path
|
if (err(path)) return path
|
||||||
return getPlaneFromPath(path, graph)
|
return getPlaneFromPath(path, graph)
|
||||||
}
|
}
|
||||||
|
function getPlaneFromStartSketchOnFace(
|
||||||
|
sketch: StartSketchOnFace,
|
||||||
|
graph: ArtifactGraph
|
||||||
|
) {
|
||||||
|
const plane = getArtifactOfTypes(
|
||||||
|
{ key: sketch.faceId, types: ['plane'] },
|
||||||
|
graph
|
||||||
|
)
|
||||||
|
return plane
|
||||||
|
}
|
||||||
|
function getPlaneFromStartSketchOnPlane(
|
||||||
|
sketch: StartSketchOnPlane,
|
||||||
|
graph: ArtifactGraph
|
||||||
|
) {
|
||||||
|
const plane = getArtifactOfTypes(
|
||||||
|
{ key: sketch.planeId, types: ['plane'] },
|
||||||
|
graph
|
||||||
|
)
|
||||||
|
return plane
|
||||||
|
}
|
||||||
|
|
||||||
export function getPlaneFromArtifact(
|
export function getPlaneFromArtifact(
|
||||||
artifact: Artifact | undefined,
|
artifact: Artifact | undefined,
|
||||||
@ -631,6 +657,10 @@ export function getPlaneFromArtifact(
|
|||||||
if (artifact.type === 'wall') return getPlaneFromWall(artifact, graph)
|
if (artifact.type === 'wall') return getPlaneFromWall(artifact, graph)
|
||||||
if (artifact.type === 'sweepEdge')
|
if (artifact.type === 'sweepEdge')
|
||||||
return getPlaneFromSweepEdge(artifact, graph)
|
return getPlaneFromSweepEdge(artifact, graph)
|
||||||
|
if (artifact.type === 'startSketchOnFace')
|
||||||
|
return getPlaneFromStartSketchOnFace(artifact, graph)
|
||||||
|
if (artifact.type === 'startSketchOnPlane')
|
||||||
|
return getPlaneFromStartSketchOnPlane(artifact, graph)
|
||||||
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
return new Error(`Artifact type ${artifact.type} does not have a plane`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import {
|
|||||||
VariableMap,
|
VariableMap,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
|
ARG_INDEX_FIELD,
|
||||||
getNodeFromPath,
|
getNodeFromPath,
|
||||||
getNodeFromPathCurry,
|
getNodeFromPathCurry,
|
||||||
getObjExprProperty,
|
getObjExprProperty,
|
||||||
@ -77,6 +78,8 @@ import {
|
|||||||
export const ARG_TAG = 'tag'
|
export const ARG_TAG = 'tag'
|
||||||
export const ARG_END = 'end'
|
export const ARG_END = 'end'
|
||||||
export const ARG_END_ABSOLUTE = 'endAbsolute'
|
export const ARG_END_ABSOLUTE = 'endAbsolute'
|
||||||
|
export const ARG_CIRCLE_CENTER = 'center'
|
||||||
|
export const ARG_CIRCLE_RADIUS = 'radius'
|
||||||
|
|
||||||
const STRAIGHT_SEGMENT_ERR = new Error(
|
const STRAIGHT_SEGMENT_ERR = new Error(
|
||||||
'Invalid input, expected "straight-segment"'
|
'Invalid input, expected "straight-segment"'
|
||||||
@ -1077,7 +1080,7 @@ export const tangentialArcTo: SketchLineHelper = {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
export const circle: SketchLineHelper = {
|
export const circle: SketchLineHelperKw = {
|
||||||
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
|
||||||
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR
|
if (segmentInput.type !== 'arc-segment') return ARC_SEGMENT_ERR
|
||||||
|
|
||||||
@ -1138,51 +1141,45 @@ export const circle: SketchLineHelper = {
|
|||||||
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR
|
if (input.type !== 'arc-segment') return ARC_SEGMENT_ERR
|
||||||
const { center, radius } = input
|
const { center, radius } = input
|
||||||
const _node = { ...node }
|
const _node = { ...node }
|
||||||
const nodeMeta = getNodeFromPath<CallExpression>(_node, pathToNode)
|
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
|
||||||
if (err(nodeMeta)) return nodeMeta
|
if (err(nodeMeta)) return nodeMeta
|
||||||
|
|
||||||
const { node: callExpression, shallowPath } = nodeMeta
|
const { node: callExpression } = nodeMeta
|
||||||
|
|
||||||
const firstArg = callExpression.arguments?.[0]
|
|
||||||
const newCenter = createArrayExpression([
|
const newCenter = createArrayExpression([
|
||||||
createLiteral(roundOff(center[0])),
|
createLiteral(roundOff(center[0])),
|
||||||
createLiteral(roundOff(center[1])),
|
createLiteral(roundOff(center[1])),
|
||||||
])
|
])
|
||||||
mutateObjExpProp(firstArg, newCenter, 'center')
|
mutateKwArg(ARG_CIRCLE_CENTER, callExpression, newCenter)
|
||||||
const newRadius = createLiteral(roundOff(radius))
|
const newRadius = createLiteral(roundOff(radius))
|
||||||
mutateObjExpProp(firstArg, newRadius, 'radius')
|
mutateKwArg(ARG_CIRCLE_RADIUS, callExpression, newRadius)
|
||||||
return {
|
return {
|
||||||
modifiedAst: _node,
|
modifiedAst: _node,
|
||||||
pathToNode: shallowPath,
|
pathToNode,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getTag: getTag(),
|
getTag: getTagKwArg(),
|
||||||
addTag: addTag(),
|
addTag: addTagKw(),
|
||||||
getConstraintInfo: (callExp: CallExpression, code, pathToNode) => {
|
getConstraintInfo: (callExp: CallExpressionKw, code, pathToNode) => {
|
||||||
if (callExp.type !== 'CallExpression') return []
|
if (callExp.type !== 'CallExpressionKw') return []
|
||||||
const firstArg = callExp.arguments?.[0]
|
const firstArg = callExp.arguments?.[0]
|
||||||
if (firstArg.type !== 'ObjectExpression') return []
|
if (firstArg.type !== 'LabeledArg') return []
|
||||||
const centerDetails = getObjExprProperty(firstArg, 'center')
|
let centerInfo = findKwArgWithIndex(ARG_CIRCLE_CENTER, callExp)
|
||||||
const radiusDetails = getObjExprProperty(firstArg, 'radius')
|
let radiusInfo = findKwArgWithIndex(ARG_CIRCLE_RADIUS, callExp)
|
||||||
if (!centerDetails || !radiusDetails) return []
|
if (!centerInfo || !radiusInfo) return []
|
||||||
if (centerDetails.expr.type !== 'ArrayExpression') return []
|
if (centerInfo?.expr.type !== 'ArrayExpression') return []
|
||||||
|
|
||||||
const pathToCenterArrayExpression: PathToNode = [
|
const pathToCenterArrayExpression: PathToNode = [
|
||||||
...pathToNode,
|
...pathToNode,
|
||||||
['arguments', 'CallExpression'],
|
['arguments', 'CallExpressionKw'],
|
||||||
[0, 'index'],
|
[centerInfo.argIndex, ARG_INDEX_FIELD],
|
||||||
['properties', 'ObjectExpression'],
|
['arg', LABELED_ARG_FIELD],
|
||||||
[centerDetails.index, 'index'],
|
|
||||||
['value', 'Property'],
|
|
||||||
['elements', 'ArrayExpression'],
|
|
||||||
]
|
]
|
||||||
const pathToRadiusLiteral: PathToNode = [
|
const pathToRadiusLiteral: PathToNode = [
|
||||||
...pathToNode,
|
...pathToNode,
|
||||||
['arguments', 'CallExpression'],
|
['arguments', 'CallExpressionKw'],
|
||||||
[0, 'index'],
|
[radiusInfo.argIndex, ARG_INDEX_FIELD],
|
||||||
['properties', 'ObjectExpression'],
|
['arg', LABELED_ARG_FIELD],
|
||||||
[radiusDetails.index, 'index'],
|
|
||||||
['value', 'Property'],
|
|
||||||
]
|
]
|
||||||
const pathToXArg: PathToNode = [
|
const pathToXArg: PathToNode = [
|
||||||
...pathToCenterArrayExpression,
|
...pathToCenterArrayExpression,
|
||||||
@ -1196,27 +1193,25 @@ export const circle: SketchLineHelper = {
|
|||||||
return [
|
return [
|
||||||
constrainInfo(
|
constrainInfo(
|
||||||
'radius',
|
'radius',
|
||||||
isNotLiteralArrayOrStatic(radiusDetails.expr),
|
isNotLiteralArrayOrStatic(radiusInfo.expr),
|
||||||
code.slice(radiusDetails.expr.start, radiusDetails.expr.end),
|
code.slice(radiusInfo.expr.start, radiusInfo.expr.end),
|
||||||
'circle',
|
'circle',
|
||||||
'radius',
|
'radius',
|
||||||
topLevelRange(radiusDetails.expr.start, radiusDetails.expr.end),
|
topLevelRange(radiusInfo.expr.start, radiusInfo.expr.end),
|
||||||
pathToRadiusLiteral
|
pathToRadiusLiteral
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
stdLibFnName: 'circle',
|
stdLibFnName: 'circle',
|
||||||
type: 'xAbsolute',
|
type: 'xAbsolute',
|
||||||
isConstrained: isNotLiteralArrayOrStatic(
|
isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[0]),
|
||||||
centerDetails.expr.elements[0]
|
|
||||||
),
|
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
centerDetails.expr.elements[0].start,
|
centerInfo.expr.elements[0].start,
|
||||||
centerDetails.expr.elements[0].end
|
centerInfo.expr.elements[0].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToXArg,
|
pathToNode: pathToXArg,
|
||||||
value: code.slice(
|
value: code.slice(
|
||||||
centerDetails.expr.elements[0].start,
|
centerInfo.expr.elements[0].start,
|
||||||
centerDetails.expr.elements[0].end
|
centerInfo.expr.elements[0].end
|
||||||
),
|
),
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'arrayInObject',
|
||||||
@ -1227,17 +1222,15 @@ export const circle: SketchLineHelper = {
|
|||||||
{
|
{
|
||||||
stdLibFnName: 'circle',
|
stdLibFnName: 'circle',
|
||||||
type: 'yAbsolute',
|
type: 'yAbsolute',
|
||||||
isConstrained: isNotLiteralArrayOrStatic(
|
isConstrained: isNotLiteralArrayOrStatic(centerInfo.expr.elements[1]),
|
||||||
centerDetails.expr.elements[1]
|
|
||||||
),
|
|
||||||
sourceRange: topLevelRange(
|
sourceRange: topLevelRange(
|
||||||
centerDetails.expr.elements[1].start,
|
centerInfo.expr.elements[1].start,
|
||||||
centerDetails.expr.elements[1].end
|
centerInfo.expr.elements[1].end
|
||||||
),
|
),
|
||||||
pathToNode: pathToYArg,
|
pathToNode: pathToYArg,
|
||||||
value: code.slice(
|
value: code.slice(
|
||||||
centerDetails.expr.elements[1].start,
|
centerInfo.expr.elements[1].start,
|
||||||
centerDetails.expr.elements[1].end
|
centerInfo.expr.elements[1].end
|
||||||
),
|
),
|
||||||
argPosition: {
|
argPosition: {
|
||||||
type: 'arrayInObject',
|
type: 'arrayInObject',
|
||||||
@ -2295,13 +2288,13 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
|
|||||||
angledLineToY,
|
angledLineToY,
|
||||||
angledLineThatIntersects,
|
angledLineThatIntersects,
|
||||||
tangentialArcTo,
|
tangentialArcTo,
|
||||||
circle,
|
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
|
||||||
line,
|
line,
|
||||||
lineTo,
|
lineTo,
|
||||||
circleThreePoint,
|
circleThreePoint,
|
||||||
|
circle,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function changeSketchArguments(
|
export function changeSketchArguments(
|
||||||
@ -2967,8 +2960,8 @@ function getFirstArgValuesForXYLineFns(callExpression: CallExpression): {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getCircle = (
|
export const getCircle = (
|
||||||
callExp: CallExpression
|
callExp: CallExpressionKw
|
||||||
):
|
):
|
||||||
| {
|
| {
|
||||||
val: [Expr, Expr, Expr]
|
val: [Expr, Expr, Expr]
|
||||||
@ -2976,22 +2969,24 @@ const getCircle = (
|
|||||||
}
|
}
|
||||||
| Error => {
|
| Error => {
|
||||||
const firstArg = callExp.arguments[0]
|
const firstArg = callExp.arguments[0]
|
||||||
if (firstArg.type === 'ObjectExpression') {
|
if (firstArg.type === 'LabeledArg') {
|
||||||
const centerDetails = getObjExprProperty(firstArg, 'center')
|
let centerInfo = findKwArgWithIndex(ARG_CIRCLE_CENTER, callExp)
|
||||||
const radiusDetails = getObjExprProperty(firstArg, 'radius')
|
let radiusInfo = findKwArgWithIndex(ARG_CIRCLE_RADIUS, callExp)
|
||||||
const tag = callExp.arguments[2]
|
let tagInfo = findKwArgWithIndex(ARG_TAG, callExp)
|
||||||
if (centerDetails?.expr?.type === 'ArrayExpression' && radiusDetails) {
|
if (centerInfo && radiusInfo) {
|
||||||
return {
|
if (centerInfo.expr?.type === 'ArrayExpression' && radiusInfo.expr) {
|
||||||
val: [
|
return {
|
||||||
centerDetails?.expr.elements[0],
|
val: [
|
||||||
centerDetails?.expr.elements[1],
|
centerInfo.expr?.elements[0],
|
||||||
radiusDetails.expr,
|
centerInfo.expr?.elements[1],
|
||||||
],
|
radiusInfo.expr,
|
||||||
tag,
|
],
|
||||||
|
tag: tagInfo?.expr,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Error('expected ArrayExpression or ObjectExpression')
|
return new Error('expected the arguments to be for a circle')
|
||||||
}
|
}
|
||||||
const getAngledLineThatIntersects = (
|
const getAngledLineThatIntersects = (
|
||||||
callExp: CallExpression
|
callExp: CallExpression
|
||||||
@ -3050,6 +3045,10 @@ export function getArgForEnd(lineCall: CallExpressionKw):
|
|||||||
}
|
}
|
||||||
| Error {
|
| Error {
|
||||||
const name = lineCall?.callee?.name
|
const name = lineCall?.callee?.name
|
||||||
|
|
||||||
|
if (name == 'circle') {
|
||||||
|
return getCircle(lineCall)
|
||||||
|
}
|
||||||
let arg
|
let arg
|
||||||
if (name === 'line') {
|
if (name === 'line') {
|
||||||
arg = findKwArgAny([ARG_END, ARG_END_ABSOLUTE], lineCall)
|
arg = findKwArgAny([ARG_END, ARG_END_ABSOLUTE], lineCall)
|
||||||
@ -3093,8 +3092,5 @@ export function getFirstArg(callExp: CallExpression):
|
|||||||
// TODO probably needs it's own implementation
|
// TODO probably needs it's own implementation
|
||||||
return getFirstArgValuesForXYFns(callExp)
|
return getFirstArgValuesForXYFns(callExp)
|
||||||
}
|
}
|
||||||
if (name === 'circle') {
|
|
||||||
return getCircle(callExp)
|
|
||||||
}
|
|
||||||
return new Error('unexpected call expression: ' + name)
|
return new Error('unexpected call expression: ' + name)
|
||||||
}
|
}
|
||||||
|
@ -51,6 +51,7 @@ import {
|
|||||||
ARG_END_ABSOLUTE,
|
ARG_END_ABSOLUTE,
|
||||||
getConstraintInfoKw,
|
getConstraintInfoKw,
|
||||||
isAbsoluteLine,
|
isAbsoluteLine,
|
||||||
|
getCircle,
|
||||||
} from './sketch'
|
} from './sketch'
|
||||||
import {
|
import {
|
||||||
getSketchSegmentFromPathToNode,
|
getSketchSegmentFromPathToNode,
|
||||||
@ -2154,6 +2155,9 @@ export function getConstraintLevelFromSourceRange(
|
|||||||
case 'CallExpression':
|
case 'CallExpression':
|
||||||
return getFirstArg(nodeMeta.node)
|
return getFirstArg(nodeMeta.node)
|
||||||
case 'CallExpressionKw':
|
case 'CallExpressionKw':
|
||||||
|
if (name === 'circle') {
|
||||||
|
return getCircle(nodeMeta.node)
|
||||||
|
}
|
||||||
const arg = findKwArgAny([ARG_END, ARG_END_ABSOLUTE], nodeMeta.node)
|
const arg = findKwArgAny([ARG_END, ARG_END_ABSOLUTE], nodeMeta.node)
|
||||||
if (arg === undefined) {
|
if (arg === undefined) {
|
||||||
return new Error('unexpected call expression: ' + name)
|
return new Error('unexpected call expression: ' + name)
|
||||||
|
@ -311,6 +311,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
skip: true,
|
skip: true,
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
selection: {
|
selection: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
@ -454,6 +455,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
skip: true,
|
skip: true,
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
plane: {
|
plane: {
|
||||||
inputType: 'selection',
|
inputType: 'selection',
|
||||||
@ -481,6 +483,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
skip: true,
|
skip: true,
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
revolutions: {
|
revolutions: {
|
||||||
inputType: 'kcl',
|
inputType: 'kcl',
|
||||||
@ -702,6 +705,7 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
|||||||
skip: true,
|
skip: true,
|
||||||
inputType: 'text',
|
inputType: 'text',
|
||||||
required: false,
|
required: false,
|
||||||
|
hidden: true,
|
||||||
},
|
},
|
||||||
color: {
|
color: {
|
||||||
inputType: 'options',
|
inputType: 'options',
|
||||||
|
229
src/lib/commandBarConfigs/namedViewsConfig.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { NamedView } from 'wasm-lib/kcl/bindings/NamedView'
|
||||||
|
import { Command, CommandArgumentOption } from '../commandTypes'
|
||||||
|
import toast from 'react-hot-toast'
|
||||||
|
import { engineCommandManager } from 'lib/singletons'
|
||||||
|
import { uuidv4 } from 'lib/utils'
|
||||||
|
import { settingsActor } from 'machines/appMachine'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
|
export function createNamedViewsCommand() {
|
||||||
|
// Creates a command to be registered in the command bar.
|
||||||
|
// The createNamedViewsCommand will prompt the user for a name and then
|
||||||
|
// hit the engine for the camera properties and write them back to disk
|
||||||
|
// in project.toml.
|
||||||
|
const createNamedViewCommand: Command = {
|
||||||
|
name: 'Create named view',
|
||||||
|
displayName: `Create named view`,
|
||||||
|
description:
|
||||||
|
'Saves a named view based on your current view to load again later',
|
||||||
|
groupId: 'namedViews',
|
||||||
|
icon: 'settings',
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: (data) => {
|
||||||
|
const invokeAndForgetCreateNamedView = async () => {
|
||||||
|
if (!data) {
|
||||||
|
return toast.error('Unable to create named view, missing name')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve camera view state from the engine
|
||||||
|
const cameraGetViewResponse =
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
// @ts-ignore TODO: Not in production yet.
|
||||||
|
cmd: { type: 'default_camera_get_view' },
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!(cameraGetViewResponse && 'resp' in cameraGetViewResponse)) {
|
||||||
|
return toast.error('Unable to create named view, websocket failure')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('modeling_response' in cameraGetViewResponse.resp.data) {
|
||||||
|
// @ts-ignore TODO: Not in production yet.
|
||||||
|
const view = cameraGetViewResponse.resp.data.modeling_response.data
|
||||||
|
// Create a new named view
|
||||||
|
const requestedView: NamedView = {
|
||||||
|
name: data.name,
|
||||||
|
...view.view,
|
||||||
|
}
|
||||||
|
// Retrieve application state for namedViews
|
||||||
|
const namedViews = {
|
||||||
|
...settingsActor.getSnapshot().context.app.namedViews.current,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create and set namedViews application state
|
||||||
|
const uniqueUuidV4 = uuidv4()
|
||||||
|
const requestedNamedViews = {
|
||||||
|
...namedViews,
|
||||||
|
[uniqueUuidV4]: requestedView,
|
||||||
|
}
|
||||||
|
settingsActor.send({
|
||||||
|
type: `set.app.namedViews`,
|
||||||
|
data: {
|
||||||
|
level: 'project',
|
||||||
|
value: requestedNamedViews,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
toast.success(`Named view ${requestedView.name} created.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invokeAndForgetCreateNamedView().catch(reportRejection)
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
name: {
|
||||||
|
required: true,
|
||||||
|
inputType: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Given a named view selection from the command bar, this will
|
||||||
|
// find it in the setting state, remove it from the array and
|
||||||
|
// rewrite the project.toml settings to disk to delete the named view
|
||||||
|
const deleteNamedViewCommand: Command = {
|
||||||
|
name: 'Delete named view',
|
||||||
|
displayName: `Delete named view`,
|
||||||
|
description: 'Deletes the named view from settings',
|
||||||
|
groupId: 'namedViews',
|
||||||
|
icon: 'settings',
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: (data) => {
|
||||||
|
if (!data) {
|
||||||
|
return toast.error('Unable to delete named view, missing name')
|
||||||
|
}
|
||||||
|
const idToDelete = data.name
|
||||||
|
|
||||||
|
// Retrieve application state for namedViews
|
||||||
|
|
||||||
|
const namedViews = {
|
||||||
|
...settingsActor.getSnapshot().context.app.namedViews.current,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { [idToDelete]: viewToDelete, ...rest } = namedViews
|
||||||
|
|
||||||
|
// Find the named view in the array
|
||||||
|
if (idToDelete && viewToDelete) {
|
||||||
|
// Update global state with the new computed state
|
||||||
|
settingsActor.send({
|
||||||
|
type: `set.app.namedViews`,
|
||||||
|
data: {
|
||||||
|
level: 'project',
|
||||||
|
value: rest,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
toast.success(`Named view ${viewToDelete.name} removed.`)
|
||||||
|
} else {
|
||||||
|
toast.error(`Unable to delete, could not find the named view`)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
name: {
|
||||||
|
required: true,
|
||||||
|
inputType: 'options',
|
||||||
|
options: () => {
|
||||||
|
const namedViews = {
|
||||||
|
...settingsActor.getSnapshot().context.app.namedViews.current,
|
||||||
|
}
|
||||||
|
const options: CommandArgumentOption<any>[] = []
|
||||||
|
Object.entries(namedViews).forEach(([key, view]) => {
|
||||||
|
if (view) {
|
||||||
|
options.push({
|
||||||
|
name: view.name,
|
||||||
|
isCurrent: false,
|
||||||
|
value: key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the named view from settings state and pass that camera information to the engine command to set the view of the engine camera
|
||||||
|
const loadNamedViewCommand: Command = {
|
||||||
|
name: 'Load named view',
|
||||||
|
displayName: `Load named view`,
|
||||||
|
description: 'Loads your camera to the named view',
|
||||||
|
groupId: 'namedViews',
|
||||||
|
icon: 'settings',
|
||||||
|
needsReview: false,
|
||||||
|
onSubmit: (data) => {
|
||||||
|
const invokeAndForgetLoadNamedView = async () => {
|
||||||
|
if (!data) {
|
||||||
|
return toast.error('Unable to load named view')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve application state for namedViews
|
||||||
|
const namedViews = {
|
||||||
|
...settingsActor.getSnapshot().context.app.namedViews.current,
|
||||||
|
}
|
||||||
|
|
||||||
|
const idToLoad = data.name
|
||||||
|
const viewToLoad = namedViews[idToLoad]
|
||||||
|
if (viewToLoad) {
|
||||||
|
// Split into the name and the engine data
|
||||||
|
const { name, version, ...engineViewData } = viewToLoad
|
||||||
|
|
||||||
|
// Only send the specific camera information, the NamedView itself
|
||||||
|
// is not directly compatible with the engine API
|
||||||
|
await engineCommandManager.sendSceneCommand({
|
||||||
|
type: 'modeling_cmd_req',
|
||||||
|
cmd_id: uuidv4(),
|
||||||
|
cmd: {
|
||||||
|
// @ts-ignore TODO: Not in production yet.
|
||||||
|
type: 'default_camera_set_view',
|
||||||
|
view: {
|
||||||
|
...engineViewData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const isPerpsective = !engineViewData.is_ortho
|
||||||
|
|
||||||
|
// Update the GUI for orthographic and projection
|
||||||
|
settingsActor.send({
|
||||||
|
type: 'set.modeling.cameraProjection',
|
||||||
|
data: {
|
||||||
|
level: 'user',
|
||||||
|
value: isPerpsective ? 'perspective' : 'orthographic',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
toast.success(`Named view ${name} loaded.`)
|
||||||
|
} else {
|
||||||
|
toast.error(`Unable to load named view, could not find named view`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
invokeAndForgetLoadNamedView().catch(reportRejection)
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
name: {
|
||||||
|
required: true,
|
||||||
|
inputType: 'options',
|
||||||
|
options: () => {
|
||||||
|
const namedViews = {
|
||||||
|
...settingsActor.getSnapshot().context.app.namedViews.current,
|
||||||
|
}
|
||||||
|
const options: CommandArgumentOption<any>[] = []
|
||||||
|
Object.entries(namedViews).forEach(([key, view]) => {
|
||||||
|
if (view) {
|
||||||
|
options.push({
|
||||||
|
name: view.name,
|
||||||
|
isCurrent: false,
|
||||||
|
value: key,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return options
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
createNamedViewCommand,
|
||||||
|
deleteNamedViewCommand,
|
||||||
|
loadNamedViewCommand,
|
||||||
|
}
|
||||||
|
}
|
@ -119,6 +119,8 @@ export type CommandArgumentConfig<
|
|||||||
machineContext?: C
|
machineContext?: C
|
||||||
) => boolean)
|
) => boolean)
|
||||||
warningMessage?: string
|
warningMessage?: string
|
||||||
|
/** If `true`, arg is used as passed-through data, never for user input */
|
||||||
|
hidden?: boolean
|
||||||
skip?: boolean
|
skip?: boolean
|
||||||
/** For showing a summary display of the current value, such as in
|
/** For showing a summary display of the current value, such as in
|
||||||
* the command bar's header
|
* the command bar's header
|
||||||
@ -233,6 +235,8 @@ export type CommandArgument<
|
|||||||
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
commandBarContext: { argumentsToSubmit: Record<string, unknown> }, // Should be the commandbarMachine's context, but it creates a circular dependency
|
||||||
machineContext?: ContextFrom<T>
|
machineContext?: ContextFrom<T>
|
||||||
) => boolean)
|
) => boolean)
|
||||||
|
/** If `true`, arg is used as passed-through data, never for user input */
|
||||||
|
hidden?: boolean
|
||||||
skip?: boolean
|
skip?: boolean
|
||||||
machineActor?: Actor<T>
|
machineActor?: Actor<T>
|
||||||
warningMessage?: string
|
warningMessage?: string
|
||||||
|
@ -162,6 +162,7 @@ export function buildCommandArgument<
|
|||||||
const baseCommandArgument = {
|
const baseCommandArgument = {
|
||||||
description: arg.description,
|
description: arg.description,
|
||||||
required: arg.required,
|
required: arg.required,
|
||||||
|
hidden: arg.hidden,
|
||||||
skip: arg.skip,
|
skip: arg.skip,
|
||||||
machineActor,
|
machineActor,
|
||||||
valueSummary: arg.valueSummary,
|
valueSummary: arg.valueSummary,
|
||||||
|
@ -47,10 +47,10 @@ sketch001 = startSketchOn('XZ')
|
|||||||
)
|
)
|
||||||
|
|
||||||
sketch002 = startSketchOn(sketch001, seg03)
|
sketch002 = startSketchOn(sketch001, seg03)
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [-1.25, 1],
|
center = [-1.25, 1],
|
||||||
radius = mountingHoleDiameter / 2,
|
radius = mountingHoleDiameter / 2,
|
||||||
}, %)
|
)
|
||||||
|> patternLinear2d(
|
|> patternLinear2d(
|
||||||
instances = 2,
|
instances = 2,
|
||||||
distance = 2.5,
|
distance = 2.5,
|
||||||
@ -64,10 +64,10 @@ sketch002 = startSketchOn(sketch001, seg03)
|
|||||||
|> extrude(%, length = -thickness-.01)
|
|> extrude(%, length = -thickness-.01)
|
||||||
|
|
||||||
sketch003 = startSketchOn(sketch001, seg04)
|
sketch003 = startSketchOn(sketch001, seg04)
|
||||||
|> circle({
|
|> circle(
|
||||||
center = [1, -1],
|
center = [1, -1],
|
||||||
radius = mountingHoleDiameter / 2,
|
radius = mountingHoleDiameter / 2,
|
||||||
}, %)
|
)
|
||||||
|> patternLinear2d(
|
|> patternLinear2d(
|
||||||
instances = 2,
|
instances = 2,
|
||||||
distance = 4,
|
distance = 4,
|
||||||
|
@ -31,10 +31,10 @@ profile002 = startProfileAt([-321.34, 361.76], sketch002)
|
|||||||
|> close()
|
|> close()
|
||||||
extrude002 = extrude(profile002, length = 500)
|
extrude002 = extrude(profile002, length = 500)
|
||||||
sketch005 = startSketchOn(extrude002, 'END')
|
sketch005 = startSketchOn(extrude002, 'END')
|
||||||
profile006 = circle({
|
profile006 = circle(sketch005,
|
||||||
center = [-292.57, 302.55],
|
center = [-292.57, 302.55],
|
||||||
radius = 25.89
|
radius = 25.89
|
||||||
}, sketch005)
|
)
|
||||||
sketch004 = startSketchOn(extrude001, seg02)
|
sketch004 = startSketchOn(extrude001, seg02)
|
||||||
profile005 = startProfileAt([36.1, 174.49], sketch004)
|
profile005 = startProfileAt([36.1, 174.49], sketch004)
|
||||||
|> angledLine([0, 22.33], %, $rectangleSegmentA003)
|
|> angledLine([0, 22.33], %, $rectangleSegmentA003)
|
||||||
@ -61,10 +61,10 @@ profile003 = startProfileAt([-115.59, 439.4], sketch003)
|
|||||||
], %)
|
], %)
|
||||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|> close()
|
|> close()
|
||||||
profile004 = circle({
|
profile004 = circle(sketch003,
|
||||||
center = [-88.54, 209.41],
|
center = [-88.54, 209.41],
|
||||||
radius = 42.72
|
radius = 42.72
|
||||||
}, sketch003)
|
)
|
||||||
`
|
`
|
||||||
const ___artifactGraph = new Map([
|
const ___artifactGraph = new Map([
|
||||||
[
|
[
|
||||||
|
@ -20,6 +20,7 @@ import { isArray, toSync } from 'lib/utils'
|
|||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
||||||
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
import { OnboardingStatus } from 'wasm-lib/kcl/bindings/OnboardingStatus'
|
||||||
|
import { NamedView } from 'wasm-lib/kcl/bindings/NamedView'
|
||||||
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
|
import { CameraOrbitType } from 'wasm-lib/kcl/bindings/CameraOrbitType'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,12 +174,17 @@ export function createSettings() {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
enableSSAO: new Setting<boolean>({
|
/**
|
||||||
defaultValue: true,
|
* Whether to show the debug panel, which lets you see
|
||||||
description:
|
* various states of the app to aid in development
|
||||||
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled',
|
*/
|
||||||
|
showDebugPanel: new Setting<boolean>({
|
||||||
|
defaultValue: false,
|
||||||
|
description: 'Whether to show the debug panel, a development tool',
|
||||||
validate: (v) => typeof v === 'boolean',
|
validate: (v) => typeof v === 'boolean',
|
||||||
hideOnPlatform: 'both', //for now
|
commandConfig: {
|
||||||
|
inputType: 'boolean',
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
/**
|
/**
|
||||||
* Stream resource saving behavior toggle
|
* Stream resource saving behavior toggle
|
||||||
@ -263,6 +269,11 @@ export function createSettings() {
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
namedViews: new Setting<{ [key in string]: NamedView }>({
|
||||||
|
defaultValue: {},
|
||||||
|
validate: (v) => true,
|
||||||
|
hideOnLevel: 'user',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Settings that affect the behavior while modeling.
|
* Settings that affect the behavior while modeling.
|
||||||
@ -291,6 +302,13 @@ export function createSettings() {
|
|||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
enableSSAO: new Setting<boolean>({
|
||||||
|
defaultValue: true,
|
||||||
|
description:
|
||||||
|
'Whether or not Screen Space Ambient Occlusion (SSAO) is enabled',
|
||||||
|
validate: (v) => typeof v === 'boolean',
|
||||||
|
hideOnPlatform: 'both', //for now
|
||||||
|
}),
|
||||||
/**
|
/**
|
||||||
* The controls for how to navigate the 3D view
|
* The controls for how to navigate the 3D view
|
||||||
*/
|
*/
|
||||||
@ -429,18 +447,6 @@ export function createSettings() {
|
|||||||
},
|
},
|
||||||
hideOnLevel: 'project',
|
hideOnLevel: 'project',
|
||||||
}),
|
}),
|
||||||
/**
|
|
||||||
* Whether to show the debug panel, which lets you see
|
|
||||||
* various states of the app to aid in development
|
|
||||||
*/
|
|
||||||
showDebugPanel: new Setting<boolean>({
|
|
||||||
defaultValue: false,
|
|
||||||
description: 'Whether to show the debug panel, a development tool',
|
|
||||||
validate: (v) => typeof v === 'boolean',
|
|
||||||
commandConfig: {
|
|
||||||
inputType: 'boolean',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
/**
|
/**
|
||||||
* TODO: This setting is not yet implemented.
|
* TODO: This setting is not yet implemented.
|
||||||
* Whether to turn off animations and other motion effects
|
* Whether to turn off animations and other motion effects
|
||||||
|
@ -23,6 +23,7 @@ import { err } from 'lib/trap'
|
|||||||
import { DeepPartial } from 'lib/types'
|
import { DeepPartial } from 'lib/types'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
|
import { NamedView } from 'wasm-lib/kcl/bindings/NamedView'
|
||||||
import { SaveSettingsPayload, SettingsLevel } from './settingsTypes'
|
import { SaveSettingsPayload, SettingsLevel } from './settingsTypes'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,7 +46,7 @@ export function configurationToSettingsPayload(
|
|||||||
allowOrbitInSketchMode:
|
allowOrbitInSketchMode:
|
||||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
projectDirectory: configuration?.settings?.project?.directory,
|
projectDirectory: configuration?.settings?.project?.directory,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
showDebugPanel: configuration?.settings?.app?.show_debug_panel,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: configuration?.settings?.modeling?.base_unit,
|
defaultUnit: configuration?.settings?.modeling?.base_unit,
|
||||||
@ -55,7 +56,7 @@ export function configurationToSettingsPayload(
|
|||||||
configuration?.settings?.modeling?.mouse_controls
|
configuration?.settings?.modeling?.mouse_controls
|
||||||
),
|
),
|
||||||
highlightEdges: configuration?.settings?.modeling?.highlight_edges,
|
highlightEdges: configuration?.settings?.modeling?.highlight_edges,
|
||||||
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
showScaleGrid: configuration?.settings?.modeling?.show_scale_grid,
|
showScaleGrid: configuration?.settings?.modeling?.show_scale_grid,
|
||||||
},
|
},
|
||||||
textEditor: {
|
textEditor: {
|
||||||
@ -72,6 +73,43 @@ export function configurationToSettingsPayload(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isNamedView(
|
||||||
|
namedView: DeepPartial<NamedView> | undefined
|
||||||
|
): namedView is NamedView {
|
||||||
|
const namedViewKeys = [
|
||||||
|
'name',
|
||||||
|
'eye_offset',
|
||||||
|
'fov_y',
|
||||||
|
'ortho_scale_enabled',
|
||||||
|
'ortho_scale_factor',
|
||||||
|
'pivot_position',
|
||||||
|
'pivot_rotation',
|
||||||
|
'world_coord_system',
|
||||||
|
'version',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
return namedViewKeys.every((key) => {
|
||||||
|
return namedView && namedView[key]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function deepPartialNamedViewsToNamedViews(
|
||||||
|
maybeViews: { [key: string]: NamedView | undefined } | undefined
|
||||||
|
): { [key: string]: NamedView } {
|
||||||
|
const namedViews: { [key: string]: NamedView } = {}
|
||||||
|
|
||||||
|
if (!maybeViews) {
|
||||||
|
return namedViews
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(maybeViews)?.forEach(([key, maybeView]) => {
|
||||||
|
if (isNamedView(maybeView)) {
|
||||||
|
namedViews[key] = maybeView
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return namedViews
|
||||||
|
}
|
||||||
|
|
||||||
export function projectConfigurationToSettingsPayload(
|
export function projectConfigurationToSettingsPayload(
|
||||||
configuration: DeepPartial<ProjectConfiguration>
|
configuration: DeepPartial<ProjectConfiguration>
|
||||||
): DeepPartial<SaveSettingsPayload> {
|
): DeepPartial<SaveSettingsPayload> {
|
||||||
@ -86,15 +124,15 @@ export function projectConfigurationToSettingsPayload(
|
|||||||
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
streamIdleMode: configuration?.settings?.app?.stream_idle_mode,
|
||||||
allowOrbitInSketchMode:
|
allowOrbitInSketchMode:
|
||||||
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
configuration?.settings?.app?.allow_orbit_in_sketch_mode,
|
||||||
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
namedViews: deepPartialNamedViewsToNamedViews(
|
||||||
|
configuration?.settings?.app?.named_views
|
||||||
|
),
|
||||||
|
showDebugPanel: configuration?.settings?.app?.show_debug_panel,
|
||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: configuration?.settings?.modeling?.base_unit,
|
defaultUnit: configuration?.settings?.modeling?.base_unit,
|
||||||
mouseControls: mouseControlsToCameraSystem(
|
|
||||||
configuration?.settings?.modeling?.mouse_controls
|
|
||||||
),
|
|
||||||
highlightEdges: configuration?.settings?.modeling?.highlight_edges,
|
highlightEdges: configuration?.settings?.modeling?.highlight_edges,
|
||||||
showDebugPanel: configuration?.settings?.modeling?.show_debug_panel,
|
enableSSAO: configuration?.settings?.modeling?.enable_ssao,
|
||||||
},
|
},
|
||||||
textEditor: {
|
textEditor: {
|
||||||
textWrapping: configuration?.settings?.text_editor?.text_wrapping,
|
textWrapping: configuration?.settings?.text_editor?.text_wrapping,
|
||||||
|
@ -132,14 +132,15 @@ export const commandBarMachine = setup({
|
|||||||
|
|
||||||
// Find the first argument that is not to be skipped:
|
// Find the first argument that is not to be skipped:
|
||||||
// that is, the first argument that is not already in the argumentsToSubmit
|
// that is, the first argument that is not already in the argumentsToSubmit
|
||||||
// or that is not undefined, or that is not marked as "skippable".
|
// or hidden, or that is not undefined, or that is not marked as "skippable".
|
||||||
// TODO validate the type of the existing arguments
|
// TODO validate the type of the existing arguments
|
||||||
|
const nonHiddenArgs = Object.entries(selectedCommand.args).filter(
|
||||||
|
(a) => !a[1].hidden
|
||||||
|
)
|
||||||
let argIndex = 0
|
let argIndex = 0
|
||||||
|
|
||||||
while (argIndex < Object.keys(selectedCommand.args).length) {
|
while (argIndex < nonHiddenArgs.length) {
|
||||||
const [argName, argConfig] = Object.entries(selectedCommand.args)[
|
const [argName, argConfig] = nonHiddenArgs[argIndex]
|
||||||
argIndex
|
|
||||||
]
|
|
||||||
const argIsRequired =
|
const argIsRequired =
|
||||||
typeof argConfig.required === 'function'
|
typeof argConfig.required === 'function'
|
||||||
? argConfig.required(context)
|
? argConfig.required(context)
|
||||||
@ -155,7 +156,7 @@ export const commandBarMachine = setup({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
mustNotSkipArg === true ||
|
mustNotSkipArg === true ||
|
||||||
argIndex + 1 === Object.keys(selectedCommand.args).length
|
argIndex + 1 === Object.keys(nonHiddenArgs).length
|
||||||
) {
|
) {
|
||||||
// If we have reached the end of the arguments and none are skippable,
|
// If we have reached the end of the arguments and none are skippable,
|
||||||
// return the last argument.
|
// return the last argument.
|
||||||
@ -259,7 +260,7 @@ export const commandBarMachine = setup({
|
|||||||
},
|
},
|
||||||
'All arguments are skippable': ({ context }) => {
|
'All arguments are skippable': ({ context }) => {
|
||||||
return Object.values(context.selectedCommand!.args!).every(
|
return Object.values(context.selectedCommand!.args!).every(
|
||||||
(argConfig) => argConfig.skip
|
(argConfig) => argConfig.skip || argConfig.hidden
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
'Has selected command': ({ context }) => !!context.selectedCommand,
|
'Has selected command': ({ context }) => !!context.selectedCommand,
|
||||||
|
@ -3526,18 +3526,13 @@ export function isEditingExistingSketch({
|
|||||||
if (variableDeclaration.node.type !== 'VariableDeclarator') return false
|
if (variableDeclaration.node.type !== 'VariableDeclarator') return false
|
||||||
const maybePipeExpression = variableDeclaration.node.init
|
const maybePipeExpression = variableDeclaration.node.init
|
||||||
if (
|
if (
|
||||||
maybePipeExpression.type === 'CallExpression' &&
|
(maybePipeExpression.type === 'CallExpression' ||
|
||||||
|
maybePipeExpression.type === 'CallExpressionKw') &&
|
||||||
(maybePipeExpression.callee.name === 'startProfileAt' ||
|
(maybePipeExpression.callee.name === 'startProfileAt' ||
|
||||||
maybePipeExpression.callee.name === 'circle' ||
|
maybePipeExpression.callee.name === 'circle' ||
|
||||||
maybePipeExpression.callee.name === 'circleThreePoint')
|
maybePipeExpression.callee.name === 'circleThreePoint')
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
if (
|
|
||||||
maybePipeExpression.type === 'CallExpressionKw' &&
|
|
||||||
(maybePipeExpression.callee.name === 'startProfileAt' ||
|
|
||||||
maybePipeExpression.callee.name === 'circleThreePoint')
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
if (maybePipeExpression.type !== 'PipeExpression') return false
|
if (maybePipeExpression.type !== 'PipeExpression') return false
|
||||||
const hasStartProfileAt = maybePipeExpression.body.some(
|
const hasStartProfileAt = maybePipeExpression.body.some(
|
||||||
(item) =>
|
(item) =>
|
||||||
@ -3545,7 +3540,8 @@ export function isEditingExistingSketch({
|
|||||||
)
|
)
|
||||||
const hasCircle =
|
const hasCircle =
|
||||||
maybePipeExpression.body.some(
|
maybePipeExpression.body.some(
|
||||||
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
|
(item) =>
|
||||||
|
item.type === 'CallExpressionKw' && item.callee.name === 'circle'
|
||||||
) ||
|
) ||
|
||||||
maybePipeExpression.body.some(
|
maybePipeExpression.body.some(
|
||||||
(item) =>
|
(item) =>
|
||||||
@ -3570,7 +3566,7 @@ export function pipeHasCircle({
|
|||||||
const pipeExpression = variableDeclaration.node.init
|
const pipeExpression = variableDeclaration.node.init
|
||||||
if (pipeExpression.type !== 'PipeExpression') return false
|
if (pipeExpression.type !== 'PipeExpression') return false
|
||||||
const hasCircle = pipeExpression.body.some(
|
const hasCircle = pipeExpression.body.some(
|
||||||
(item) => item.type === 'CallExpression' && item.callee.name === 'circle'
|
(item) => item.type === 'CallExpressionKw' && item.callee.name === 'circle'
|
||||||
)
|
)
|
||||||
return hasCircle
|
return hasCircle
|
||||||
}
|
}
|
||||||
@ -3608,7 +3604,7 @@ export function isClosedSketch({
|
|||||||
if (node.node?.declaration?.init?.type !== 'PipeExpression') return false
|
if (node.node?.declaration?.init?.type !== 'PipeExpression') return false
|
||||||
return node.node.declaration.init.body.some(
|
return node.node.declaration.init.body.some(
|
||||||
(node) =>
|
(node) =>
|
||||||
node.type === 'CallExpression' &&
|
(node.type === 'CallExpression' || node.type === 'CallExpressionKw') &&
|
||||||
(node.callee.name === 'close' || node.callee.name === 'circle')
|
(node.callee.name === 'close' || node.callee.name === 'circle')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import {
|
|||||||
saveSettings,
|
saveSettings,
|
||||||
setSettingsAtLevel,
|
setSettingsAtLevel,
|
||||||
} from 'lib/settings/settingsUtils'
|
} from 'lib/settings/settingsUtils'
|
||||||
|
import { NamedView } from 'wasm-lib/kcl/bindings/NamedView'
|
||||||
import {
|
import {
|
||||||
codeManager,
|
codeManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -77,6 +78,7 @@ export const settingsMachine = setup({
|
|||||||
level: SettingsLevel
|
level: SettingsLevel
|
||||||
}
|
}
|
||||||
| { type: 'Set all settings'; settings: typeof settings }
|
| { type: 'Set all settings'; settings: typeof settings }
|
||||||
|
| { type: 'set.app.namedViews'; value: NamedView }
|
||||||
| { type: 'load.project'; project?: Project }
|
| { type: 'load.project'; project?: Project }
|
||||||
| { type: 'clear.project' }
|
| { type: 'clear.project' }
|
||||||
) & { doNotPersist?: boolean },
|
) & { doNotPersist?: boolean },
|
||||||
@ -151,6 +153,7 @@ export const settingsMachine = setup({
|
|||||||
type: 'Add commands',
|
type: 'Add commands',
|
||||||
data: { commands: commands },
|
data: { commands: commands },
|
||||||
})
|
})
|
||||||
|
|
||||||
const removeCommands = () =>
|
const removeCommands = () =>
|
||||||
commandBarActor.send({
|
commandBarActor.send({
|
||||||
type: 'Remove commands',
|
type: 'Remove commands',
|
||||||
@ -391,6 +394,12 @@ export const settingsMachine = setup({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'set.app.namedViews': {
|
||||||
|
target: 'persisting settings',
|
||||||
|
|
||||||
|
actions: ['setSettingAtLevel'],
|
||||||
|
},
|
||||||
|
|
||||||
'set.app.onboardingStatus': {
|
'set.app.onboardingStatus': {
|
||||||
target: 'persisting settings',
|
target: 'persisting settings',
|
||||||
|
|
||||||
|
14
src/wasm-lib/Cargo.lock
generated
@ -730,7 +730,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1724,7 +1724,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
version = "0.2.44"
|
version = "0.2.45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"approx 0.5.1",
|
"approx 0.5.1",
|
||||||
@ -1791,7 +1791,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"hyper 0.14.32",
|
"hyper 0.14.32",
|
||||||
@ -4220,9 +4220,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "validator"
|
name = "validator"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0b4a29d8709210980a09379f27ee31549b73292c87ab9899beee1c0d3be6303"
|
checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"idna",
|
"idna",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -4236,9 +4236,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "validator_derive"
|
name = "validator_derive"
|
||||||
version = "0.19.0"
|
version = "0.20.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bac855a2ce6f843beb229757e6e570a42e837bcb15e5f449dd48d5747d41bf77"
|
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling",
|
"darling",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -3,7 +3,7 @@ cita := "cargo insta test --accept"
|
|||||||
|
|
||||||
# Run the same lint checks we run in CI.
|
# Run the same lint checks we run in CI.
|
||||||
lint:
|
lint:
|
||||||
cargo clippy --workspace --all-targets -- -D warnings
|
cargo clippy --workspace --all-targets --tests --all-features --examples --benches -- -D warnings
|
||||||
|
|
||||||
# Run the stdlib docs generation
|
# Run the stdlib docs generation
|
||||||
redo-kcl-stdlib-docs-no-imgs:
|
redo-kcl-stdlib-docs-no-imgs:
|
||||||
@ -40,9 +40,9 @@ overwrite-sim-test test_name:
|
|||||||
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::parse
|
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::parse
|
||||||
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
EXPECTORATE=overwrite {{cita}} -p kcl-lib -- simulation_tests::{{test_name}}::unparse
|
||||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
{{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute
|
||||||
|
|
||||||
test:
|
test:
|
||||||
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --test-threads=1
|
export RUST_BRACKTRACE="full" && cargo nextest run --workspace --no-fail-fast
|
||||||
|
|
||||||
publish-kcl version:
|
publish-kcl version:
|
||||||
git tag {{version}}
|
git tag {{version}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
description = "A test server for KCL"
|
description = "A test server for KCL"
|
||||||
version = "0.1.44"
|
version = "0.1.45"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "kcl-lib"
|
name = "kcl-lib"
|
||||||
description = "KittyCAD Language implementation and tools"
|
description = "KittyCAD Language implementation and tools"
|
||||||
version = "0.2.44"
|
version = "0.2.45"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
@ -72,7 +72,7 @@ ts-rs = { version = "10.1.0", features = [
|
|||||||
url = { version = "2.5.4", features = ["serde"] }
|
url = { version = "2.5.4", features = ["serde"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
uuid = { version = "1.11.0", features = ["v4", "js", "serde"] }
|
||||||
validator = { version = "0.19.0", features = ["derive"] }
|
validator = { version = "0.20.0", features = ["derive"] }
|
||||||
web-time = "1.1"
|
web-time = "1.1"
|
||||||
winnow = "0.6.22"
|
winnow = "0.6.22"
|
||||||
zip = { version = "2.2.2", default-features = false }
|
zip = { version = "2.2.2", default-features = false }
|
||||||
@ -140,4 +140,3 @@ required-features = ["lsp-test-util"]
|
|||||||
[[bench]]
|
[[bench]]
|
||||||
name = "executor_benchmark_criterion"
|
name = "executor_benchmark_criterion"
|
||||||
harness = false
|
harness = false
|
||||||
|
|
||||||
|
@ -1014,10 +1014,7 @@ mod tests {
|
|||||||
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
|
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet,
|
snippet,
|
||||||
r#"circle({
|
r#"circle(${0:%}, center = [${1:3.14}, ${2:3.14}], radius = ${3:3.14})${}"#
|
||||||
center = [${0:3.14}, ${1:3.14}],
|
|
||||||
radius = ${2:3.14},
|
|
||||||
}, ${3:%})${}"#
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,6 +106,15 @@ pub struct CodeRef {
|
|||||||
pub path_to_node: DummyPathToNode,
|
pub path_to_node: DummyPathToNode,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CodeRef {
|
||||||
|
pub fn placeholder(range: SourceRange) -> Self {
|
||||||
|
Self {
|
||||||
|
range,
|
||||||
|
path_to_node: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export_to = "Artifact.ts")]
|
#[ts(export_to = "Artifact.ts")]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -178,6 +187,24 @@ pub struct Solid2d {
|
|||||||
pub path_id: ArtifactId,
|
pub path_id: ArtifactId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
|
#[ts(export_to = "Artifact.ts")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct StartSketchOnFace {
|
||||||
|
pub id: ArtifactId,
|
||||||
|
pub face_id: ArtifactId,
|
||||||
|
pub code_ref: CodeRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
|
#[ts(export_to = "Artifact.ts")]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct StartSketchOnPlane {
|
||||||
|
pub id: ArtifactId,
|
||||||
|
pub plane_id: ArtifactId,
|
||||||
|
pub code_ref: CodeRef,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export_to = "Artifact.ts")]
|
#[ts(export_to = "Artifact.ts")]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -295,18 +322,8 @@ pub enum Artifact {
|
|||||||
Path(Path),
|
Path(Path),
|
||||||
Segment(Segment),
|
Segment(Segment),
|
||||||
Solid2d(Solid2d),
|
Solid2d(Solid2d),
|
||||||
#[serde(rename_all = "camelCase")]
|
StartSketchOnFace(StartSketchOnFace),
|
||||||
StartSketchOnFace {
|
StartSketchOnPlane(StartSketchOnPlane),
|
||||||
id: ArtifactId,
|
|
||||||
face_id: Uuid,
|
|
||||||
source_range: SourceRange,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
StartSketchOnPlane {
|
|
||||||
id: ArtifactId,
|
|
||||||
plane_id: Uuid,
|
|
||||||
source_range: SourceRange,
|
|
||||||
},
|
|
||||||
Sweep(Sweep),
|
Sweep(Sweep),
|
||||||
Wall(Wall),
|
Wall(Wall),
|
||||||
Cap(Cap),
|
Cap(Cap),
|
||||||
@ -323,8 +340,8 @@ impl Artifact {
|
|||||||
Artifact::Path(a) => a.id,
|
Artifact::Path(a) => a.id,
|
||||||
Artifact::Segment(a) => a.id,
|
Artifact::Segment(a) => a.id,
|
||||||
Artifact::Solid2d(a) => a.id,
|
Artifact::Solid2d(a) => a.id,
|
||||||
Artifact::StartSketchOnFace { id, .. } => *id,
|
Artifact::StartSketchOnFace(a) => a.id,
|
||||||
Artifact::StartSketchOnPlane { id, .. } => *id,
|
Artifact::StartSketchOnPlane(a) => a.id,
|
||||||
Artifact::Sweep(a) => a.id,
|
Artifact::Sweep(a) => a.id,
|
||||||
Artifact::Wall(a) => a.id,
|
Artifact::Wall(a) => a.id,
|
||||||
Artifact::Cap(a) => a.id,
|
Artifact::Cap(a) => a.id,
|
||||||
@ -342,9 +359,8 @@ impl Artifact {
|
|||||||
Artifact::Path(a) => Some(&a.code_ref),
|
Artifact::Path(a) => Some(&a.code_ref),
|
||||||
Artifact::Segment(a) => Some(&a.code_ref),
|
Artifact::Segment(a) => Some(&a.code_ref),
|
||||||
Artifact::Solid2d(_) => None,
|
Artifact::Solid2d(_) => None,
|
||||||
// TODO: We should add code refs for these.
|
Artifact::StartSketchOnFace(a) => Some(&a.code_ref),
|
||||||
Artifact::StartSketchOnFace { .. } => None,
|
Artifact::StartSketchOnPlane(a) => Some(&a.code_ref),
|
||||||
Artifact::StartSketchOnPlane { .. } => None,
|
|
||||||
Artifact::Sweep(a) => Some(&a.code_ref),
|
Artifact::Sweep(a) => Some(&a.code_ref),
|
||||||
Artifact::Wall(_) => None,
|
Artifact::Wall(_) => None,
|
||||||
Artifact::Cap(_) => None,
|
Artifact::Cap(_) => None,
|
||||||
@ -507,6 +523,10 @@ pub(super) fn build_artifact_graph(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for exec_artifact in exec_artifacts.values() {
|
||||||
|
merge_artifact_into_map(&mut map, exec_artifact.clone());
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ArtifactGraph { map })
|
Ok(ArtifactGraph { map })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -841,15 +861,15 @@ fn artifacts_to_update(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let extra_artifact = exec_artifacts.values().find(|a| {
|
let extra_artifact = exec_artifacts.values().find(|a| {
|
||||||
if let Artifact::StartSketchOnFace { face_id: id, .. } = a {
|
if let Artifact::StartSketchOnFace(s) = a {
|
||||||
*id == face_id.0
|
s.face_id == face_id
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let sketch_on_face_source_range = extra_artifact
|
let sketch_on_face_source_range = extra_artifact
|
||||||
.and_then(|a| match a {
|
.and_then(|a| match a {
|
||||||
Artifact::StartSketchOnFace { source_range, .. } => Some(*source_range),
|
Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
|
||||||
// TODO: If we didn't find it, it's probably a bug.
|
// TODO: If we didn't find it, it's probably a bug.
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
@ -894,15 +914,15 @@ fn artifacts_to_update(
|
|||||||
})
|
})
|
||||||
})?;
|
})?;
|
||||||
let extra_artifact = exec_artifacts.values().find(|a| {
|
let extra_artifact = exec_artifacts.values().find(|a| {
|
||||||
if let Artifact::StartSketchOnFace { face_id: id, .. } = a {
|
if let Artifact::StartSketchOnFace(s) = a {
|
||||||
*id == face_id.0
|
s.face_id == face_id
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let sketch_on_face_source_range = extra_artifact
|
let sketch_on_face_source_range = extra_artifact
|
||||||
.and_then(|a| match a {
|
.and_then(|a| match a {
|
||||||
Artifact::StartSketchOnFace { source_range, .. } => Some(*source_range),
|
Artifact::StartSketchOnFace(s) => Some(s.code_ref.range),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
@ -71,8 +71,8 @@ impl Artifact {
|
|||||||
Artifact::Path(a) => vec![a.plane_id],
|
Artifact::Path(a) => vec![a.plane_id],
|
||||||
Artifact::Segment(a) => vec![a.path_id],
|
Artifact::Segment(a) => vec![a.path_id],
|
||||||
Artifact::Solid2d(a) => vec![a.path_id],
|
Artifact::Solid2d(a) => vec![a.path_id],
|
||||||
Artifact::StartSketchOnFace { face_id, .. } => vec![face_id.into()],
|
Artifact::StartSketchOnFace(a) => vec![a.face_id],
|
||||||
Artifact::StartSketchOnPlane { plane_id, .. } => vec![plane_id.into()],
|
Artifact::StartSketchOnPlane(a) => vec![a.plane_id],
|
||||||
Artifact::Sweep(a) => vec![a.path_id],
|
Artifact::Sweep(a) => vec![a.path_id],
|
||||||
Artifact::Wall(a) => vec![a.seg_id, a.sweep_id],
|
Artifact::Wall(a) => vec![a.seg_id, a.sweep_id],
|
||||||
Artifact::Cap(a) => vec![a.sweep_id],
|
Artifact::Cap(a) => vec![a.sweep_id],
|
||||||
@ -115,8 +115,14 @@ impl Artifact {
|
|||||||
// Note: Don't include these since they're parents: path_id.
|
// Note: Don't include these since they're parents: path_id.
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
Artifact::StartSketchOnFace { .. } => Vec::new(),
|
Artifact::StartSketchOnFace { .. } => {
|
||||||
Artifact::StartSketchOnPlane { .. } => Vec::new(),
|
// Note: Don't include these since they're parents: face_id.
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
Artifact::StartSketchOnPlane { .. } => {
|
||||||
|
// Note: Don't include these since they're parents: plane_id.
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
Artifact::Sweep(a) => {
|
Artifact::Sweep(a) => {
|
||||||
// Note: Don't include these since they're parents: path_id.
|
// Note: Don't include these since they're parents: path_id.
|
||||||
let mut ids = Vec::new();
|
let mut ids = Vec::new();
|
||||||
@ -267,9 +273,7 @@ impl ArtifactGraph {
|
|||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
// For now, only showing the source range.
|
// For now, only showing the source range.
|
||||||
fn code_ref_display(code_ref: &CodeRef) -> [usize; 3] {
|
fn code_ref_display(code_ref: &CodeRef) -> [usize; 3] {
|
||||||
range_display(code_ref.range)
|
let range = code_ref.range;
|
||||||
}
|
|
||||||
fn range_display(range: SourceRange) -> [usize; 3] {
|
|
||||||
[range.start(), range.end(), range.module_id().as_usize()]
|
[range.start(), range.end(), range.module_id().as_usize()]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,20 +305,20 @@ impl ArtifactGraph {
|
|||||||
Artifact::Solid2d(_solid2d) => {
|
Artifact::Solid2d(_solid2d) => {
|
||||||
writeln!(output, "{prefix}{}[Solid2d]", id)?;
|
writeln!(output, "{prefix}{}[Solid2d]", id)?;
|
||||||
}
|
}
|
||||||
Artifact::StartSketchOnFace { source_range, .. } => {
|
Artifact::StartSketchOnFace(StartSketchOnFace { code_ref, .. }) => {
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"{prefix}{}[\"StartSketchOnFace<br>{:?}\"]",
|
"{prefix}{}[\"StartSketchOnFace<br>{:?}\"]",
|
||||||
id,
|
id,
|
||||||
range_display(*source_range)
|
code_ref_display(code_ref)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Artifact::StartSketchOnPlane { source_range, .. } => {
|
Artifact::StartSketchOnPlane(StartSketchOnPlane { code_ref, .. }) => {
|
||||||
writeln!(
|
writeln!(
|
||||||
output,
|
output,
|
||||||
"{prefix}{}[\"StartSketchOnPlane<br>{:?}\"]",
|
"{prefix}{}[\"StartSketchOnPlane<br>{:?}\"]",
|
||||||
id,
|
id,
|
||||||
range_display(*source_range)
|
code_ref_display(code_ref)
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
Artifact::Sweep(sweep) => {
|
Artifact::Sweep(sweep) => {
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
use std::{path::PathBuf, sync::Arc};
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
pub use artifact::{
|
||||||
|
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane,
|
||||||
|
};
|
||||||
use cache::OldAstState;
|
use cache::OldAstState;
|
||||||
pub use cache::{bust_cache, clear_mem_cache};
|
pub use cache::{bust_cache, clear_mem_cache};
|
||||||
pub use cad_op::Operation;
|
pub use cad_op::Operation;
|
||||||
@ -277,7 +279,7 @@ impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSet
|
|||||||
units: config.settings.modeling.base_unit,
|
units: config.settings.modeling.base_unit,
|
||||||
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
||||||
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
||||||
show_grid: config.settings.modeling.show_scale_grid,
|
show_grid: Default::default(),
|
||||||
replay: None,
|
replay: None,
|
||||||
project_directory: None,
|
project_directory: None,
|
||||||
current_file: None,
|
current_file: None,
|
||||||
@ -624,6 +626,8 @@ impl ExecutorContext {
|
|||||||
let mut exec_state = old_state;
|
let mut exec_state = old_state;
|
||||||
exec_state.reset(&self.settings);
|
exec_state.reset(&self.settings);
|
||||||
|
|
||||||
|
exec_state.add_root_module_contents(&program);
|
||||||
|
|
||||||
// We don't do this in mock mode since there is no engine connection
|
// We don't do this in mock mode since there is no engine connection
|
||||||
// anyways and from the TS side we override memory and don't want to clear it.
|
// anyways and from the TS side we override memory and don't want to clear it.
|
||||||
self.send_clear_scene(&mut exec_state, Default::default())
|
self.send_clear_scene(&mut exec_state, Default::default())
|
||||||
@ -633,12 +637,15 @@ impl ExecutorContext {
|
|||||||
(exec_state, false)
|
(exec_state, false)
|
||||||
} else {
|
} else {
|
||||||
old_state.mut_memory().restore_env(result_env);
|
old_state.mut_memory().restore_env(result_env);
|
||||||
|
old_state.add_root_module_contents(&program);
|
||||||
|
|
||||||
(old_state, true)
|
(old_state, true)
|
||||||
};
|
};
|
||||||
|
|
||||||
(program, exec_state, preserve_mem)
|
(program, exec_state, preserve_mem)
|
||||||
} else {
|
} else {
|
||||||
let mut exec_state = ExecState::new(&self.settings);
|
let mut exec_state = ExecState::new(&self.settings);
|
||||||
|
exec_state.add_root_module_contents(&program);
|
||||||
self.send_clear_scene(&mut exec_state, Default::default())
|
self.send_clear_scene(&mut exec_state, Default::default())
|
||||||
.await
|
.await
|
||||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||||
@ -694,6 +701,7 @@ impl ExecutorContext {
|
|||||||
program: &crate::Program,
|
program: &crate::Program,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||||
|
exec_state.add_root_module_contents(program);
|
||||||
self.send_clear_scene(exec_state, Default::default())
|
self.send_clear_scene(exec_state, Default::default())
|
||||||
.await
|
.await
|
||||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||||
@ -709,7 +717,6 @@ impl ExecutorContext {
|
|||||||
preserve_mem: bool,
|
preserve_mem: bool,
|
||||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||||
exec_state.add_root_module_contents(program);
|
|
||||||
|
|
||||||
// Re-apply the settings, in case the cache was busted.
|
// Re-apply the settings, in case the cache was busted.
|
||||||
self.engine
|
self.engine
|
||||||
@ -879,11 +886,20 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) async fn parse_execute(code: &str) -> Result<(crate::Program, EnvironmentRef, ExecutorContext, ExecState)> {
|
pub(crate) async fn parse_execute(
|
||||||
|
code: &str,
|
||||||
|
) -> Result<(crate::Program, EnvironmentRef, ExecutorContext, ExecState), KclError> {
|
||||||
let program = crate::Program::parse_no_errs(code)?;
|
let program = crate::Program::parse_no_errs(code)?;
|
||||||
|
|
||||||
let ctx = ExecutorContext {
|
let ctx = ExecutorContext {
|
||||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
engine: Arc::new(Box::new(
|
||||||
|
crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| {
|
||||||
|
KclError::Internal(crate::errors::KclErrorDetails {
|
||||||
|
message: format!("Failed to create mock engine connection: {}", err),
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
})
|
||||||
|
})?,
|
||||||
|
)),
|
||||||
fs: Arc::new(crate::fs::FileManager::new()),
|
fs: Arc::new(crate::fs::FileManager::new()),
|
||||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
@ -1286,7 +1302,7 @@ const x = 5
|
|||||||
const answer = returnX()"#;
|
const answer = returnX()"#;
|
||||||
|
|
||||||
let result = parse_execute(ast).await;
|
let result = parse_execute(ast).await;
|
||||||
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
let err = result.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err,
|
err,
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
@ -1319,7 +1335,7 @@ foo
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = parse_execute(ast).await;
|
let result = parse_execute(ast).await;
|
||||||
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
let err = result.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err,
|
err,
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Syntax(KclErrorDetails {
|
||||||
@ -1343,7 +1359,7 @@ fn transform = (replicaId) => {
|
|||||||
|
|
||||||
fn layer = () => {
|
fn layer = () => {
|
||||||
return startSketchOn(XY)
|
return startSketchOn(XY)
|
||||||
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|
|> circle( center= [0, 0], radius= 1 , tag =$tag1)
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1354,7 +1370,7 @@ let shape = layer() |> patternTransform(instances = 10, transform = transform)
|
|||||||
"#;
|
"#;
|
||||||
|
|
||||||
let result = parse_execute(ast).await;
|
let result = parse_execute(ast).await;
|
||||||
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
let err = result.unwrap_err();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
err,
|
err,
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
@ -1505,7 +1521,7 @@ let myNull = 0 / 0
|
|||||||
let notNull = !myNull
|
let notNull = !myNull
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code1).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code1).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(56, 63, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(56, 63, ModuleId::default())],
|
||||||
@ -1514,7 +1530,7 @@ let notNull = !myNull
|
|||||||
|
|
||||||
let code2 = "let notZero = !0";
|
let code2 = "let notZero = !0";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code2).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code2).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(14, 16, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(14, 16, ModuleId::default())],
|
||||||
@ -1525,7 +1541,7 @@ let notNull = !myNull
|
|||||||
let notEmptyString = !""
|
let notEmptyString = !""
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code3).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code3).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: string (text)".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: string (text)".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(22, 25, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(22, 25, ModuleId::default())],
|
||||||
@ -1537,7 +1553,7 @@ let obj = { a: 1 }
|
|||||||
let notMember = !obj.a
|
let notMember = !obj.a
|
||||||
"#;
|
"#;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code4).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code4).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: number".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(36, 42, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(36, 42, ModuleId::default())],
|
||||||
@ -1548,7 +1564,7 @@ let notMember = !obj.a
|
|||||||
let a = []
|
let a = []
|
||||||
let notArray = !a";
|
let notArray = !a";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code5).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code5).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: array (list)".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: array (list)".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(27, 29, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(27, 29, ModuleId::default())],
|
||||||
@ -1559,7 +1575,7 @@ let notArray = !a";
|
|||||||
let x = {}
|
let x = {}
|
||||||
let notObject = !x";
|
let notObject = !x";
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_execute(code6).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code6).await.unwrap_err(),
|
||||||
KclError::Semantic(KclErrorDetails {
|
KclError::Semantic(KclErrorDetails {
|
||||||
message: "Cannot apply unary operator ! to non-boolean value: object".to_owned(),
|
message: "Cannot apply unary operator ! to non-boolean value: object".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(28, 30, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(28, 30, ModuleId::default())],
|
||||||
@ -1569,7 +1585,7 @@ let notObject = !x";
|
|||||||
let code7 = "
|
let code7 = "
|
||||||
fn x = () => { return 1 }
|
fn x = () => { return 1 }
|
||||||
let notFunction = !x";
|
let notFunction = !x";
|
||||||
let fn_err = parse_execute(code7).await.unwrap_err().downcast::<KclError>().unwrap();
|
let fn_err = parse_execute(code7).await.unwrap_err();
|
||||||
// These are currently printed out as JSON objects, so we don't want to
|
// These are currently printed out as JSON objects, so we don't want to
|
||||||
// check the full error.
|
// check the full error.
|
||||||
assert!(
|
assert!(
|
||||||
@ -1583,7 +1599,7 @@ let notFunction = !x";
|
|||||||
let code8 = "
|
let code8 = "
|
||||||
let myTagDeclarator = $myTag
|
let myTagDeclarator = $myTag
|
||||||
let notTagDeclarator = !myTagDeclarator";
|
let notTagDeclarator = !myTagDeclarator";
|
||||||
let tag_declarator_err = parse_execute(code8).await.unwrap_err().downcast::<KclError>().unwrap();
|
let tag_declarator_err = parse_execute(code8).await.unwrap_err();
|
||||||
// These are currently printed out as JSON objects, so we don't want to
|
// These are currently printed out as JSON objects, so we don't want to
|
||||||
// check the full error.
|
// check the full error.
|
||||||
assert!(
|
assert!(
|
||||||
@ -1597,7 +1613,7 @@ let notTagDeclarator = !myTagDeclarator";
|
|||||||
let code9 = "
|
let code9 = "
|
||||||
let myTagDeclarator = $myTag
|
let myTagDeclarator = $myTag
|
||||||
let notTagIdentifier = !myTag";
|
let notTagIdentifier = !myTag";
|
||||||
let tag_identifier_err = parse_execute(code9).await.unwrap_err().downcast::<KclError>().unwrap();
|
let tag_identifier_err = parse_execute(code9).await.unwrap_err();
|
||||||
// These are currently printed out as JSON objects, so we don't want to
|
// These are currently printed out as JSON objects, so we don't want to
|
||||||
// check the full error.
|
// check the full error.
|
||||||
assert!(
|
assert!(
|
||||||
@ -1612,7 +1628,7 @@ let notTagIdentifier = !myTag";
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
// TODO: We don't currently parse this, but we should. It should be
|
// TODO: We don't currently parse this, but we should. It should be
|
||||||
// a runtime error instead.
|
// a runtime error instead.
|
||||||
parse_execute(code10).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code10).await.unwrap_err(),
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Syntax(KclErrorDetails {
|
||||||
message: "Unexpected token: !".to_owned(),
|
message: "Unexpected token: !".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
|
||||||
@ -1625,7 +1641,7 @@ let notPipeSub = 1 |> identity(!%))";
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
// TODO: We don't currently parse this, but we should. It should be
|
// TODO: We don't currently parse this, but we should. It should be
|
||||||
// a runtime error instead.
|
// a runtime error instead.
|
||||||
parse_execute(code11).await.unwrap_err().downcast::<KclError>().unwrap(),
|
parse_execute(code11).await.unwrap_err(),
|
||||||
KclError::Syntax(KclErrorDetails {
|
KclError::Syntax(KclErrorDetails {
|
||||||
message: "Unexpected token: |>".to_owned(),
|
message: "Unexpected token: |>".to_owned(),
|
||||||
source_ranges: vec![SourceRange::new(54, 56, ModuleId::default())],
|
source_ranges: vec![SourceRange::new(54, 56, ModuleId::default())],
|
||||||
|
@ -6,7 +6,7 @@ expression: actual
|
|||||||
"body": [
|
"body": [
|
||||||
{
|
{
|
||||||
"declaration": {
|
"declaration": {
|
||||||
"end": 113,
|
"end": 106,
|
||||||
"id": {
|
"id": {
|
||||||
"end": 14,
|
"end": 14,
|
||||||
"name": "cylinder",
|
"name": "cylinder",
|
||||||
@ -40,81 +40,59 @@ expression: actual
|
|||||||
{
|
{
|
||||||
"arguments": [
|
"arguments": [
|
||||||
{
|
{
|
||||||
"end": 81,
|
"type": "LabeledArg",
|
||||||
"properties": [
|
"label": {
|
||||||
{
|
"type": "Identifier",
|
||||||
"end": 67,
|
"name": "center"
|
||||||
"key": {
|
},
|
||||||
"end": 59,
|
"arg": {
|
||||||
"name": "center",
|
"elements": [
|
||||||
"start": 53,
|
{
|
||||||
"type": "Identifier"
|
"end": 61,
|
||||||
},
|
"raw": "0",
|
||||||
"start": 53,
|
"start": 60,
|
||||||
"type": "ObjectProperty",
|
|
||||||
"value": {
|
|
||||||
"elements": [
|
|
||||||
{
|
|
||||||
"end": 63,
|
|
||||||
"raw": "0",
|
|
||||||
"start": 62,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 0.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"end": 66,
|
|
||||||
"raw": "0",
|
|
||||||
"start": 65,
|
|
||||||
"type": "Literal",
|
|
||||||
"type": "Literal",
|
|
||||||
"value": {
|
|
||||||
"value": 0.0,
|
|
||||||
"suffix": "None"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"end": 67,
|
|
||||||
"start": 61,
|
|
||||||
"type": "ArrayExpression",
|
|
||||||
"type": "ArrayExpression"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"end": 79,
|
|
||||||
"key": {
|
|
||||||
"end": 75,
|
|
||||||
"name": "radius",
|
|
||||||
"start": 69,
|
|
||||||
"type": "Identifier"
|
|
||||||
},
|
|
||||||
"start": 69,
|
|
||||||
"type": "ObjectProperty",
|
|
||||||
"value": {
|
|
||||||
"end": 79,
|
|
||||||
"raw": "22",
|
|
||||||
"start": 77,
|
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"value": {
|
"value": {
|
||||||
"value": 22.0,
|
"value": 0.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"end": 64,
|
||||||
|
"raw": "0",
|
||||||
|
"start": 63,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 0.0,
|
||||||
"suffix": "None"
|
"suffix": "None"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
],
|
||||||
],
|
"end": 65,
|
||||||
"start": 51,
|
"start": 59,
|
||||||
"type": "ObjectExpression",
|
"type": "ArrayExpression",
|
||||||
"type": "ObjectExpression"
|
"type": "ArrayExpression"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"end": 84,
|
"type": "LabeledArg",
|
||||||
"start": 83,
|
"label": {
|
||||||
"type": "PipeSubstitution",
|
"type": "Identifier",
|
||||||
"type": "PipeSubstitution"
|
"name": "radius"
|
||||||
|
},
|
||||||
|
"arg": {
|
||||||
|
"end": 77,
|
||||||
|
"raw": "22",
|
||||||
|
"start": 75,
|
||||||
|
"type": "Literal",
|
||||||
|
"type": "Literal",
|
||||||
|
"value": {
|
||||||
|
"value": 22.0,
|
||||||
|
"suffix": "None"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"callee": {
|
"callee": {
|
||||||
@ -123,10 +101,11 @@ expression: actual
|
|||||||
"start": 44,
|
"start": 44,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"end": 85,
|
"end": 78,
|
||||||
"start": 44,
|
"start": 44,
|
||||||
"type": "CallExpression",
|
"type": "CallExpressionKw",
|
||||||
"type": "CallExpression"
|
"type": "CallExpressionKw",
|
||||||
|
"unlabeled": null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"arguments": [
|
"arguments": [
|
||||||
@ -137,9 +116,9 @@ expression: actual
|
|||||||
"name": "length"
|
"name": "length"
|
||||||
},
|
},
|
||||||
"arg": {
|
"arg": {
|
||||||
"end": 112,
|
"end": 105,
|
||||||
"raw": "14",
|
"raw": "14",
|
||||||
"start": 110,
|
"start": 103,
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"type": "Literal",
|
"type": "Literal",
|
||||||
"value": {
|
"value": {
|
||||||
@ -150,19 +129,19 @@ expression: actual
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"callee": {
|
"callee": {
|
||||||
"end": 100,
|
"end": 93,
|
||||||
"name": "extrude",
|
"name": "extrude",
|
||||||
"start": 93,
|
"start": 86,
|
||||||
"type": "Identifier"
|
"type": "Identifier"
|
||||||
},
|
},
|
||||||
"end": 113,
|
"end": 106,
|
||||||
"start": 93,
|
"start": 86,
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
"type": "CallExpressionKw",
|
"type": "CallExpressionKw",
|
||||||
"unlabeled": null
|
"unlabeled": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end": 113,
|
"end": 106,
|
||||||
"start": 17,
|
"start": 17,
|
||||||
"type": "PipeExpression",
|
"type": "PipeExpression",
|
||||||
"type": "PipeExpression"
|
"type": "PipeExpression"
|
||||||
@ -170,13 +149,13 @@ expression: actual
|
|||||||
"start": 6,
|
"start": 6,
|
||||||
"type": "VariableDeclarator"
|
"type": "VariableDeclarator"
|
||||||
},
|
},
|
||||||
"end": 113,
|
"end": 106,
|
||||||
"kind": "const",
|
"kind": "const",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
"type": "VariableDeclaration",
|
"type": "VariableDeclaration",
|
||||||
"type": "VariableDeclaration"
|
"type": "VariableDeclaration"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"end": 114,
|
"end": 107,
|
||||||
"start": 0
|
"start": 0
|
||||||
}
|
}
|
||||||
|
@ -55,6 +55,11 @@ impl Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
|
||||||
|
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
|
||||||
|
settings.settings.modeling.show_debug_panel = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
settings.validate()?;
|
settings.validate()?;
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
@ -120,10 +125,14 @@ pub struct AppSettings {
|
|||||||
pub dismiss_web_banner: bool,
|
pub dismiss_web_banner: bool,
|
||||||
/// When the user is idle, and this is true, the stream will be torn down.
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||||
stream_idle_mode: bool,
|
pub stream_idle_mode: bool,
|
||||||
/// When the user is idle, and this is true, the stream will be torn down.
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
||||||
allow_orbit_in_sketch_mode: bool,
|
pub allow_orbit_in_sketch_mode: bool,
|
||||||
|
/// Whether to show the debug panel, which lets you see various states
|
||||||
|
/// of the app to aid in development.
|
||||||
|
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
|
||||||
|
pub show_debug_panel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
// TODO: When we remove backwards compatibility with the old settings file, we can remove this.
|
||||||
@ -270,6 +279,7 @@ pub struct ModelingSettings {
|
|||||||
pub highlight_edges: DefaultTrue,
|
pub highlight_edges: DefaultTrue,
|
||||||
/// Whether to show the debug panel, which lets you see various states
|
/// Whether to show the debug panel, which lets you see various states
|
||||||
/// of the app to aid in development.
|
/// of the app to aid in development.
|
||||||
|
/// Remove this when we remove backwards compatibility with the old settings file.
|
||||||
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
|
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
|
||||||
pub show_debug_panel: bool,
|
pub show_debug_panel: bool,
|
||||||
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
||||||
@ -609,14 +619,15 @@ textWrapping = true
|
|||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
|
show_debug_panel: true,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::In,
|
base_unit: UnitLength::In,
|
||||||
camera_projection: CameraProjectionType::Orthographic,
|
camera_projection: CameraProjectionType::Orthographic,
|
||||||
camera_orbit: Default::default(),
|
camera_orbit: Default::default(),
|
||||||
mouse_controls: Default::default(),
|
mouse_controls: Default::default(),
|
||||||
|
show_debug_panel: Default::default(),
|
||||||
highlight_edges: Default::default(),
|
highlight_edges: Default::default(),
|
||||||
show_debug_panel: true,
|
|
||||||
enable_ssao: false.into(),
|
enable_ssao: false.into(),
|
||||||
show_scale_grid: false,
|
show_scale_grid: false,
|
||||||
},
|
},
|
||||||
@ -670,6 +681,7 @@ includeSettings = false
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
show_debug_panel: true,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
@ -679,9 +691,9 @@ includeSettings = false
|
|||||||
camera_orbit: Default::default(),
|
camera_orbit: Default::default(),
|
||||||
mouse_controls: Default::default(),
|
mouse_controls: Default::default(),
|
||||||
highlight_edges: Default::default(),
|
highlight_edges: Default::default(),
|
||||||
show_debug_panel: true,
|
|
||||||
enable_ssao: true.into(),
|
enable_ssao: true.into(),
|
||||||
show_scale_grid: false,
|
show_scale_grid: false,
|
||||||
|
show_debug_panel: Default::default(),
|
||||||
},
|
},
|
||||||
text_editor: TextEditorSettings {
|
text_editor: TextEditorSettings {
|
||||||
text_wrapping: false.into(),
|
text_wrapping: false.into(),
|
||||||
@ -740,6 +752,7 @@ defaultProjectName = "projects-$nnn"
|
|||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
|
show_debug_panel: true,
|
||||||
},
|
},
|
||||||
modeling: ModelingSettings {
|
modeling: ModelingSettings {
|
||||||
base_unit: UnitLength::Yd,
|
base_unit: UnitLength::Yd,
|
||||||
@ -747,7 +760,7 @@ defaultProjectName = "projects-$nnn"
|
|||||||
camera_orbit: CameraOrbitType::Spherical,
|
camera_orbit: CameraOrbitType::Spherical,
|
||||||
mouse_controls: Default::default(),
|
mouse_controls: Default::default(),
|
||||||
highlight_edges: Default::default(),
|
highlight_edges: Default::default(),
|
||||||
show_debug_panel: true,
|
show_debug_panel: Default::default(),
|
||||||
enable_ssao: true.into(),
|
enable_ssao: true.into(),
|
||||||
show_scale_grid: false,
|
show_scale_grid: false,
|
||||||
},
|
},
|
||||||
@ -772,6 +785,7 @@ defaultProjectName = "projects-$nnn"
|
|||||||
serialized,
|
serialized,
|
||||||
r#"[settings.app]
|
r#"[settings.app]
|
||||||
onboarding_status = "dismissed"
|
onboarding_status = "dismissed"
|
||||||
|
show_debug_panel = true
|
||||||
|
|
||||||
[settings.app.appearance]
|
[settings.app.appearance]
|
||||||
theme = "dark"
|
theme = "dark"
|
||||||
@ -779,7 +793,6 @@ color = 138.0
|
|||||||
|
|
||||||
[settings.modeling]
|
[settings.modeling]
|
||||||
base_unit = "yd"
|
base_unit = "yd"
|
||||||
show_debug_panel = true
|
|
||||||
|
|
||||||
[settings.text_editor]
|
[settings.text_editor]
|
||||||
text_wrapping = false
|
text_wrapping = false
|
||||||
@ -818,6 +831,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
|||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
|
show_debug_panel: false,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
},
|
||||||
@ -827,7 +841,7 @@ projectDirectory = "/Users/macinatormax/Documents/kittycad-modeling-projects""#;
|
|||||||
camera_orbit: Default::default(),
|
camera_orbit: Default::default(),
|
||||||
mouse_controls: Default::default(),
|
mouse_controls: Default::default(),
|
||||||
highlight_edges: true.into(),
|
highlight_edges: true.into(),
|
||||||
show_debug_panel: false,
|
show_debug_panel: Default::default(),
|
||||||
enable_ssao: true.into(),
|
enable_ssao: true.into(),
|
||||||
show_scale_grid: false,
|
show_scale_grid: false,
|
||||||
},
|
},
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
//! Types specific for modeling-app projects.
|
//! Types specific for modeling-app projects.
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::settings::types::{
|
use crate::settings::types::{
|
||||||
AppColor, AppSettings, AppTheme, CommandBarSettings, ModelingSettings, TextEditorSettings,
|
is_default, AppColor, CommandBarSettings, DefaultTrue, FloatOrInt, OnboardingStatus, TextEditorSettings, UnitLength,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// High level project configuration.
|
/// High level project configuration.
|
||||||
@ -24,14 +25,6 @@ impl ProjectConfiguration {
|
|||||||
// TODO: remove this when we remove backwards compatibility with the old settings file.
|
// TODO: remove this when we remove backwards compatibility with the old settings file.
|
||||||
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
|
pub fn backwards_compatible_toml_parse(toml_str: &str) -> Result<Self> {
|
||||||
let mut settings = toml::from_str::<Self>(toml_str)?;
|
let mut settings = toml::from_str::<Self>(toml_str)?;
|
||||||
settings.settings.app.project_directory = None;
|
|
||||||
|
|
||||||
if let Some(theme) = &settings.settings.app.theme {
|
|
||||||
if settings.settings.app.appearance.theme == AppTheme::default() {
|
|
||||||
settings.settings.app.appearance.theme = *theme;
|
|
||||||
settings.settings.app.theme = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(theme_color) = &settings.settings.app.theme_color {
|
if let Some(theme_color) = &settings.settings.app.theme_color {
|
||||||
if settings.settings.app.appearance.color == AppColor::default() {
|
if settings.settings.app.appearance.color == AppColor::default() {
|
||||||
@ -47,6 +40,11 @@ impl ProjectConfiguration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if settings.settings.modeling.show_debug_panel && !settings.settings.app.show_debug_panel {
|
||||||
|
settings.settings.app.show_debug_panel = settings.settings.modeling.show_debug_panel;
|
||||||
|
settings.settings.modeling.show_debug_panel = Default::default();
|
||||||
|
}
|
||||||
|
|
||||||
settings.validate()?;
|
settings.validate()?;
|
||||||
|
|
||||||
Ok(settings)
|
Ok(settings)
|
||||||
@ -61,11 +59,11 @@ pub struct PerProjectSettings {
|
|||||||
/// The settings for the modeling app.
|
/// The settings for the modeling app.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[validate(nested)]
|
#[validate(nested)]
|
||||||
pub app: AppSettings,
|
pub app: ProjectAppSettings,
|
||||||
/// Settings that affect the behavior while modeling.
|
/// Settings that affect the behavior while modeling.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[validate(nested)]
|
#[validate(nested)]
|
||||||
pub modeling: ModelingSettings,
|
pub modeling: ProjectModelingSettings,
|
||||||
/// Settings that affect the behavior of the KCL text editor.
|
/// Settings that affect the behavior of the KCL text editor.
|
||||||
#[serde(default, alias = "textEditor")]
|
#[serde(default, alias = "textEditor")]
|
||||||
#[validate(nested)]
|
#[validate(nested)]
|
||||||
@ -76,32 +74,144 @@ pub struct PerProjectSettings {
|
|||||||
pub command_bar: CommandBarSettings,
|
pub command_bar: CommandBarSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Project specific application settings.
|
||||||
|
// TODO: When we remove backwards compatibility with the old settings file, we can remove the
|
||||||
|
// aliases to camelCase (and projects plural) from everywhere.
|
||||||
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct ProjectAppSettings {
|
||||||
|
/// The settings for the appearance of the app.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
#[validate(nested)]
|
||||||
|
pub appearance: ProjectAppearanceSettings,
|
||||||
|
/// The onboarding status of the app.
|
||||||
|
#[serde(default, alias = "onboardingStatus", skip_serializing_if = "is_default")]
|
||||||
|
pub onboarding_status: OnboardingStatus,
|
||||||
|
/// The hue of the primary theme color for the app.
|
||||||
|
#[serde(default, skip_serializing_if = "Option::is_none", alias = "themeColor")]
|
||||||
|
pub theme_color: Option<FloatOrInt>,
|
||||||
|
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
||||||
|
#[serde(default, alias = "enableSSAO", skip_serializing_if = "Option::is_none")]
|
||||||
|
pub enable_ssao: Option<bool>,
|
||||||
|
/// Permanently dismiss the banner warning to download the desktop app.
|
||||||
|
/// This setting only applies to the web app. And is temporary until we have Linux support.
|
||||||
|
#[serde(default, alias = "dismissWebBanner", skip_serializing_if = "is_default")]
|
||||||
|
pub dismiss_web_banner: bool,
|
||||||
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
|
#[serde(default, alias = "streamIdleMode", skip_serializing_if = "is_default")]
|
||||||
|
pub stream_idle_mode: bool,
|
||||||
|
/// When the user is idle, and this is true, the stream will be torn down.
|
||||||
|
#[serde(default, alias = "allowOrbitInSketchMode", skip_serializing_if = "is_default")]
|
||||||
|
pub allow_orbit_in_sketch_mode: bool,
|
||||||
|
/// Whether to show the debug panel, which lets you see various states
|
||||||
|
/// of the app to aid in development.
|
||||||
|
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
|
||||||
|
pub show_debug_panel: bool,
|
||||||
|
/// Settings that affect the behavior of the command bar.
|
||||||
|
#[serde(default, alias = "namedViews", skip_serializing_if = "IndexMap::is_empty")]
|
||||||
|
pub named_views: IndexMap<uuid::Uuid, NamedView>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Project specific appearance settings.
|
||||||
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Validate)]
|
||||||
|
#[ts(export)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct ProjectAppearanceSettings {
|
||||||
|
/// The hue of the primary theme color for the app.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
#[validate(nested)]
|
||||||
|
pub color: AppColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Project specific settings that affect the behavior while modeling.
|
||||||
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq, Eq, Validate)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct ProjectModelingSettings {
|
||||||
|
/// The default unit to use in modeling dimensions.
|
||||||
|
#[serde(default, alias = "defaultUnit", skip_serializing_if = "is_default")]
|
||||||
|
pub base_unit: UnitLength,
|
||||||
|
/// Highlight edges of 3D objects?
|
||||||
|
#[serde(default, alias = "highlightEdges", skip_serializing_if = "is_default")]
|
||||||
|
pub highlight_edges: DefaultTrue,
|
||||||
|
/// Whether to show the debug panel, which lets you see various states
|
||||||
|
/// of the app to aid in development.
|
||||||
|
/// Remove this when we remove backwards compatibility with the old settings file.
|
||||||
|
#[serde(default, alias = "showDebugPanel", skip_serializing_if = "is_default")]
|
||||||
|
pub show_debug_panel: bool,
|
||||||
|
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
||||||
|
#[serde(default, skip_serializing_if = "is_default")]
|
||||||
|
pub enable_ssao: DefaultTrue,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn named_view_point_version_one() -> f64 {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, ts_rs::TS, PartialEq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[ts(export)]
|
||||||
|
pub struct NamedView {
|
||||||
|
/// User defined name to identify the named view. A label.
|
||||||
|
#[serde(default, alias = "name", skip_serializing_if = "is_default")]
|
||||||
|
pub name: String,
|
||||||
|
/// Engine camera eye off set
|
||||||
|
#[serde(default, alias = "eyeOffset", skip_serializing_if = "is_default")]
|
||||||
|
pub eye_offset: f64,
|
||||||
|
/// Engine camera vertical FOV
|
||||||
|
#[serde(default, alias = "fovY", skip_serializing_if = "is_default")]
|
||||||
|
pub fov_y: f64,
|
||||||
|
// Engine camera is orthographic or perspective projection
|
||||||
|
#[serde(default, alias = "isOrtho")]
|
||||||
|
pub is_ortho: bool,
|
||||||
|
/// Engine camera is orthographic camera scaling enabled
|
||||||
|
#[serde(default, alias = "orthoScaleEnabled")]
|
||||||
|
pub ortho_scale_enabled: bool,
|
||||||
|
/// Engine camera orthographic scaling factor
|
||||||
|
#[serde(default, alias = "orthoScaleFactor", skip_serializing_if = "is_default")]
|
||||||
|
pub ortho_scale_factor: f64,
|
||||||
|
/// Engine camera position that the camera pivots around
|
||||||
|
#[serde(default, alias = "pivotPosition", skip_serializing_if = "is_default")]
|
||||||
|
pub pivot_position: [f64; 3],
|
||||||
|
/// Engine camera orientation in relation to the pivot position
|
||||||
|
#[serde(default, alias = "pivotRotation", skip_serializing_if = "is_default")]
|
||||||
|
pub pivot_rotation: [f64; 4],
|
||||||
|
/// Engine camera world coordinate system orientation
|
||||||
|
#[serde(default, alias = "worldCoordSystem", skip_serializing_if = "is_default")]
|
||||||
|
pub world_coord_system: String,
|
||||||
|
/// Version number of the view point if the engine camera API changes
|
||||||
|
#[serde(default = "named_view_point_version_one")]
|
||||||
|
pub version: f64,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
AppSettings, AppTheme, CommandBarSettings, ModelingSettings, PerProjectSettings, ProjectConfiguration,
|
CommandBarSettings, NamedView, PerProjectSettings, ProjectAppSettings, ProjectAppearanceSettings,
|
||||||
TextEditorSettings,
|
ProjectConfiguration, ProjectModelingSettings, TextEditorSettings,
|
||||||
};
|
};
|
||||||
use crate::settings::types::{AppearanceSettings, UnitLength};
|
use crate::settings::types::UnitLength;
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Test that we can deserialize a project file from the old format.
|
// Test that we can deserialize a project file from the old format.
|
||||||
// TODO: We can remove this functionality after a few versions.
|
// TODO: We can remove this functionality after a few versions.
|
||||||
fn test_backwards_compatible_project_settings_file() {
|
fn test_backwards_compatible_project_settings_file() {
|
||||||
let old_project_file = r#"[settings.app]
|
let old_project_file = r#"[settings.app]
|
||||||
theme = "dark"
|
|
||||||
themeColor = "138"
|
themeColor = "138"
|
||||||
|
|
||||||
[settings.modeling]
|
|
||||||
defaultUnit = "yd"
|
|
||||||
showDebugPanel = true
|
|
||||||
|
|
||||||
[settings.textEditor]
|
[settings.textEditor]
|
||||||
textWrapping = false
|
textWrapping = false
|
||||||
blinkingCursor = false
|
blinkingCursor = false
|
||||||
|
|
||||||
|
[settings.modeling]
|
||||||
|
showDebugPanel = true
|
||||||
|
|
||||||
[settings.commandBar]
|
[settings.commandBar]
|
||||||
includeSettings = false
|
includeSettings = false
|
||||||
#"#;
|
#"#;
|
||||||
@ -112,29 +222,22 @@ includeSettings = false
|
|||||||
parsed,
|
parsed,
|
||||||
ProjectConfiguration {
|
ProjectConfiguration {
|
||||||
settings: PerProjectSettings {
|
settings: PerProjectSettings {
|
||||||
app: AppSettings {
|
app: ProjectAppSettings {
|
||||||
appearance: AppearanceSettings {
|
appearance: ProjectAppearanceSettings { color: 138.0.into() },
|
||||||
theme: AppTheme::Dark,
|
|
||||||
color: 138.0.into()
|
|
||||||
},
|
|
||||||
onboarding_status: Default::default(),
|
onboarding_status: Default::default(),
|
||||||
project_directory: None,
|
|
||||||
theme: None,
|
|
||||||
theme_color: None,
|
theme_color: None,
|
||||||
dismiss_web_banner: false,
|
dismiss_web_banner: false,
|
||||||
enable_ssao: None,
|
enable_ssao: None,
|
||||||
stream_idle_mode: false,
|
stream_idle_mode: false,
|
||||||
allow_orbit_in_sketch_mode: false,
|
allow_orbit_in_sketch_mode: false,
|
||||||
},
|
|
||||||
modeling: ModelingSettings {
|
|
||||||
base_unit: UnitLength::Yd,
|
|
||||||
camera_projection: Default::default(),
|
|
||||||
camera_orbit: Default::default(),
|
|
||||||
mouse_controls: Default::default(),
|
|
||||||
highlight_edges: Default::default(),
|
|
||||||
show_debug_panel: true,
|
show_debug_panel: true,
|
||||||
|
named_views: IndexMap::default()
|
||||||
|
},
|
||||||
|
modeling: ProjectModelingSettings {
|
||||||
|
base_unit: UnitLength::Mm,
|
||||||
|
highlight_edges: Default::default(),
|
||||||
|
show_debug_panel: Default::default(),
|
||||||
enable_ssao: true.into(),
|
enable_ssao: true.into(),
|
||||||
show_scale_grid: false,
|
|
||||||
},
|
},
|
||||||
text_editor: TextEditorSettings {
|
text_editor: TextEditorSettings {
|
||||||
text_wrapping: false.into(),
|
text_wrapping: false.into(),
|
||||||
@ -146,6 +249,27 @@ includeSettings = false
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Write the file back out.
|
||||||
|
let serialized = toml::to_string(&parsed).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
serialized,
|
||||||
|
r#"[settings.app]
|
||||||
|
show_debug_panel = true
|
||||||
|
|
||||||
|
[settings.app.appearance]
|
||||||
|
color = 138.0
|
||||||
|
|
||||||
|
[settings.modeling]
|
||||||
|
|
||||||
|
[settings.text_editor]
|
||||||
|
text_wrapping = false
|
||||||
|
blinking_cursor = false
|
||||||
|
|
||||||
|
[settings.command_bar]
|
||||||
|
include_settings = false
|
||||||
|
"#
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -189,4 +313,159 @@ color = 1567.4"#;
|
|||||||
.to_string()
|
.to_string()
|
||||||
.contains("color: Validation error: color"));
|
.contains("color: Validation error: color"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_view_serde_json() {
|
||||||
|
let json = r#"
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"dog",
|
||||||
|
"pivot_rotation":[0.53809947,0.0,0.0,0.8428814],
|
||||||
|
"pivot_position":[0.5,0,0.5],
|
||||||
|
"eye_offset":231.52048,
|
||||||
|
"fov_y":45,
|
||||||
|
"ortho_scale_factor":1.574129,
|
||||||
|
"is_ortho":true,
|
||||||
|
"ortho_scale_enabled":true,
|
||||||
|
"world_coord_system":"RightHandedUpZ"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
// serde_json to a NamedView will produce default values
|
||||||
|
let named_views: Vec<NamedView> = serde_json::from_str(json).unwrap();
|
||||||
|
let version = named_views[0].version;
|
||||||
|
assert_eq!(version, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn named_view_serde_json_string() {
|
||||||
|
let json = r#"
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"name":"dog",
|
||||||
|
"pivot_rotation":[0.53809947,0.0,0.0,0.8428814],
|
||||||
|
"pivot_position":[0.5,0,0.5],
|
||||||
|
"eye_offset":231.52048,
|
||||||
|
"fov_y":45,
|
||||||
|
"ortho_scale_factor":1.574129,
|
||||||
|
"is_ortho":true,
|
||||||
|
"ortho_scale_enabled":true,
|
||||||
|
"world_coord_system":"RightHandedUpZ"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
|
||||||
|
// serde_json to string does not produce default values
|
||||||
|
let named_views: Value = match serde_json::from_str(json) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => return,
|
||||||
|
};
|
||||||
|
println!("{}", named_views);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_settings_named_views() {
|
||||||
|
let conf = ProjectConfiguration {
|
||||||
|
settings: PerProjectSettings {
|
||||||
|
app: ProjectAppSettings {
|
||||||
|
appearance: ProjectAppearanceSettings { color: 138.0.into() },
|
||||||
|
onboarding_status: Default::default(),
|
||||||
|
theme_color: None,
|
||||||
|
dismiss_web_banner: false,
|
||||||
|
enable_ssao: None,
|
||||||
|
stream_idle_mode: false,
|
||||||
|
allow_orbit_in_sketch_mode: false,
|
||||||
|
show_debug_panel: true,
|
||||||
|
named_views: IndexMap::from([
|
||||||
|
(
|
||||||
|
uuid::uuid!("323611ea-66e3-43c9-9d0d-1091ba92948c"),
|
||||||
|
NamedView {
|
||||||
|
name: String::from("Hello"),
|
||||||
|
eye_offset: 1236.4015,
|
||||||
|
fov_y: 45.0,
|
||||||
|
is_ortho: false,
|
||||||
|
ortho_scale_enabled: false,
|
||||||
|
ortho_scale_factor: 45.0,
|
||||||
|
pivot_position: [-100.0, 100.0, 100.0],
|
||||||
|
pivot_rotation: [-0.16391756, 0.9862819, -0.01956843, 0.0032552152],
|
||||||
|
world_coord_system: String::from("RightHandedUpZ"),
|
||||||
|
version: 1.0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
(
|
||||||
|
uuid::uuid!("423611ea-66e3-43c9-9d0d-1091ba92948c"),
|
||||||
|
NamedView {
|
||||||
|
name: String::from("Goodbye"),
|
||||||
|
eye_offset: 1236.4015,
|
||||||
|
fov_y: 45.0,
|
||||||
|
is_ortho: false,
|
||||||
|
ortho_scale_enabled: false,
|
||||||
|
ortho_scale_factor: 45.0,
|
||||||
|
pivot_position: [-100.0, 100.0, 100.0],
|
||||||
|
pivot_rotation: [-0.16391756, 0.9862819, -0.01956843, 0.0032552152],
|
||||||
|
world_coord_system: String::from("RightHandedUpZ"),
|
||||||
|
version: 1.0,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
},
|
||||||
|
modeling: ProjectModelingSettings {
|
||||||
|
base_unit: UnitLength::Yd,
|
||||||
|
highlight_edges: Default::default(),
|
||||||
|
show_debug_panel: Default::default(),
|
||||||
|
enable_ssao: true.into(),
|
||||||
|
},
|
||||||
|
text_editor: TextEditorSettings {
|
||||||
|
text_wrapping: false.into(),
|
||||||
|
blinking_cursor: false.into(),
|
||||||
|
},
|
||||||
|
command_bar: CommandBarSettings {
|
||||||
|
include_settings: false.into(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let serialized = toml::to_string(&conf).unwrap();
|
||||||
|
let old_project_file = r#"[settings.app]
|
||||||
|
show_debug_panel = true
|
||||||
|
|
||||||
|
[settings.app.appearance]
|
||||||
|
color = 138.0
|
||||||
|
|
||||||
|
[settings.app.named_views.323611ea-66e3-43c9-9d0d-1091ba92948c]
|
||||||
|
name = "Hello"
|
||||||
|
eye_offset = 1236.4015
|
||||||
|
fov_y = 45.0
|
||||||
|
is_ortho = false
|
||||||
|
ortho_scale_enabled = false
|
||||||
|
ortho_scale_factor = 45.0
|
||||||
|
pivot_position = [-100.0, 100.0, 100.0]
|
||||||
|
pivot_rotation = [-0.16391756, 0.9862819, -0.01956843, 0.0032552152]
|
||||||
|
world_coord_system = "RightHandedUpZ"
|
||||||
|
version = 1.0
|
||||||
|
|
||||||
|
[settings.app.named_views.423611ea-66e3-43c9-9d0d-1091ba92948c]
|
||||||
|
name = "Goodbye"
|
||||||
|
eye_offset = 1236.4015
|
||||||
|
fov_y = 45.0
|
||||||
|
is_ortho = false
|
||||||
|
ortho_scale_enabled = false
|
||||||
|
ortho_scale_factor = 45.0
|
||||||
|
pivot_position = [-100.0, 100.0, 100.0]
|
||||||
|
pivot_rotation = [-0.16391756, 0.9862819, -0.01956843, 0.0032552152]
|
||||||
|
world_coord_system = "RightHandedUpZ"
|
||||||
|
version = 1.0
|
||||||
|
|
||||||
|
[settings.modeling]
|
||||||
|
base_unit = "yd"
|
||||||
|
|
||||||
|
[settings.text_editor]
|
||||||
|
text_wrapping = false
|
||||||
|
blinking_cursor = false
|
||||||
|
|
||||||
|
[settings.command_bar]
|
||||||
|
include_settings = false
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(serialized, old_project_file)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Add color to a revolved solid.
|
/// // Add color to a revolved solid.
|
||||||
/// sketch001 = startSketchOn('XY')
|
/// sketch001 = startSketchOn('XY')
|
||||||
/// |> circle({ center = [15, 0], radius = 5 }, %)
|
/// |> circle( center = [15, 0], radius = 5 )
|
||||||
/// |> revolve({ angle = 360, axis = 'y' }, %)
|
/// |> revolve({ angle = 360, axis = 'y' }, %)
|
||||||
/// |> appearance(
|
/// |> appearance(
|
||||||
/// color = '#ff0000',
|
/// color = '#ff0000',
|
||||||
@ -253,16 +253,16 @@ pub async fn appearance(_exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
|||||||
/// |> line(end = [0, 7])
|
/// |> line(end = [0, 7])
|
||||||
///
|
///
|
||||||
/// pipeHole = startSketchOn('XY')
|
/// pipeHole = startSketchOn('XY')
|
||||||
/// |> circle({
|
/// |> circle(
|
||||||
/// center = [0, 0],
|
/// center = [0, 0],
|
||||||
/// radius = 1.5,
|
/// radius = 1.5,
|
||||||
/// }, %)
|
/// )
|
||||||
///
|
///
|
||||||
/// sweepSketch = startSketchOn('XY')
|
/// sweepSketch = startSketchOn('XY')
|
||||||
/// |> circle({
|
/// |> circle(
|
||||||
/// center = [0, 0],
|
/// center = [0, 0],
|
||||||
/// radius = 2,
|
/// radius = 2,
|
||||||
/// }, %)
|
/// )
|
||||||
/// |> hole(pipeHole, %)
|
/// |> hole(pipeHole, %)
|
||||||
/// |> sweep(path = sweepPath)
|
/// |> sweep(path = sweepPath)
|
||||||
/// |> appearance(
|
/// |> appearance(
|
||||||
|
@ -444,19 +444,6 @@ impl Args {
|
|||||||
Ok((a.n, b.n, ty))
|
Ok((a.n, b.n, ty))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_circle_args(
|
|
||||||
&self,
|
|
||||||
) -> Result<
|
|
||||||
(
|
|
||||||
crate::std::shapes::CircleData,
|
|
||||||
crate::std::shapes::SketchOrSurface,
|
|
||||||
Option<TagNode>,
|
|
||||||
),
|
|
||||||
KclError,
|
|
||||||
> {
|
|
||||||
FromArgs::from_args(self, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_sketches(&self) -> Result<(SketchSet, Sketch), KclError> {
|
pub(crate) fn get_sketches(&self) -> Result<(SketchSet, Sketch), KclError> {
|
||||||
FromArgs::from_args(self, 0)
|
FromArgs::from_args(self, 0)
|
||||||
}
|
}
|
||||||
@ -1080,15 +1067,6 @@ impl<'a> FromKclValue<'a> for super::revolve::RevolveData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for super::shapes::CircleData {
|
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
|
||||||
let obj = arg.as_object()?;
|
|
||||||
let_field_of!(obj, center);
|
|
||||||
let_field_of!(obj, radius);
|
|
||||||
Some(Self { center, radius })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
|
impl<'a> FromKclValue<'a> for super::sketch::TangentialArcData {
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
let obj = arg.as_object()?;
|
let obj = arg.as_object()?;
|
||||||
|
@ -30,7 +30,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// 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)
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// // Call `drawCircle`, passing in each element of the array.
|
/// // Call `drawCircle`, passing in each element of the array.
|
||||||
@ -48,7 +48,7 @@ pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// [1..3],
|
/// [1..3],
|
||||||
/// fn(id) {
|
/// fn(id) {
|
||||||
/// return startSketchOn("XY")
|
/// return startSketchOn("XY")
|
||||||
/// |> circle({ center: [id * 2 * r, 0], radius: r}, %)
|
/// |> circle( center= [id * 2 * r, 0], radius= r)
|
||||||
/// }
|
/// }
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -25,7 +25,7 @@ pub async fn int(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
/// assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
/// assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||||
/// // Draw n cylinders.
|
/// // Draw n cylinders.
|
||||||
/// startSketchOn('XZ')
|
/// startSketchOn('XZ')
|
||||||
/// |> circle({ center = [0, 0], radius = 2 }, %)
|
/// |> circle(center = [0, 0], radius = 2 )
|
||||||
/// |> extrude(length = 5)
|
/// |> extrude(length = 5)
|
||||||
/// |> patternTransform(instances = n, transform = fn(id) {
|
/// |> patternTransform(instances = n, transform = fn(id) {
|
||||||
/// return { translate = [4 * id, 0, 0] }
|
/// return { translate = [4 * id, 0, 0] }
|
||||||
|