Compare commits
25 Commits
achalmers/
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
2a415012d7 | |||
d39dd5697c | |||
c9f7275bc4 | |||
4297dc43ae | |||
935b4ee7f5 | |||
2523242bb1 | |||
309e4fadf0 | |||
842054de09 | |||
af146284b6 | |||
5ac40e6849 | |||
d3e0625e8a | |||
484da28374 | |||
fa9e6ccea9 | |||
77fbb68419 | |||
253a7910b0 | |||
88c6be810f | |||
2860926571 | |||
fb81c02877 | |||
d78648f0f8 | |||
fe31769be5 | |||
388371b05d | |||
9db69007e5 | |||
f2a6492ab7 | |||
3ddce116e5 | |||
57f7d022ca |
2
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||
fi
|
||||
|
||||
retry=1
|
||||
max_retrys=5
|
||||
max_retrys=1
|
||||
|
||||
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retrys ]]; do
|
||||
|
3
.github/workflows/build-and-store-wasm.yml
vendored
@ -22,6 +22,9 @@ jobs:
|
||||
uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- name: build wasm
|
||||
run: yarn build:wasm
|
||||
|
||||
|
15
.github/workflows/build-apps.yml
vendored
@ -32,7 +32,6 @@ jobs:
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
|
||||
@ -44,6 +43,10 @@ jobs:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
# TODO: see if we can fetch from main instead if no diff at src/wasm-lib
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
|
||||
- name: Run build:wasm
|
||||
run: "yarn build:wasm"
|
||||
|
||||
@ -120,15 +123,13 @@ jobs:
|
||||
cp prepared-files/assets/icon.ico assets/icon.ico
|
||||
cp prepared-files/assets/icon.png assets/icon.png
|
||||
|
||||
- name: Sync node version and setup cache
|
||||
uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn' # Set this to npm, yarn or pnpm.
|
||||
|
||||
- name: yarn install
|
||||
# Windows is picky sometimes and fails on fetch. Step takes about ~30s
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
uses: nick-fields/retry@v3.0.1
|
||||
with:
|
||||
timeout_minutes: 2
|
||||
max_attempts: 3
|
||||
@ -179,7 +180,7 @@ jobs:
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
DEBUG: "electron-notarize*"
|
||||
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
uses: nick-fields/retry@v3.0.1
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
@ -240,7 +241,7 @@ jobs:
|
||||
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
|
||||
DEBUG: "electron-notarize*"
|
||||
# TODO: Fix electron-notarize flakes. The logs above should help gather more data on failures
|
||||
uses: nick-fields/retry@v3.0.0
|
||||
uses: nick-fields/retry@v3.0.1
|
||||
with:
|
||||
timeout_minutes: 10
|
||||
max_attempts: 3
|
||||
|
2
.github/workflows/cargo-test.yml
vendored
@ -46,7 +46,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |-
|
||||
cd "${{ matrix.dir }}"
|
||||
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
|
||||
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
|
||||
env:
|
||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||
RUST_MIN_STACK: 10485760000
|
||||
|
10
.github/workflows/check-exampleKcl.yml
vendored
@ -27,6 +27,15 @@ jobs:
|
||||
const owner = context.repo.owner;
|
||||
const repo = context.repo.repo;
|
||||
|
||||
const { data: comments } = await github.rest.issues.listComments({
|
||||
owner,
|
||||
repo,
|
||||
issue_number
|
||||
});
|
||||
|
||||
const commentExists = comments.some(comment => comment.body === message);
|
||||
|
||||
if (!commentExists) {
|
||||
// Post a comment on the PR
|
||||
await github.rest.issues.createComment({
|
||||
owner,
|
||||
@ -34,3 +43,4 @@ jobs:
|
||||
issue_number,
|
||||
body: message,
|
||||
});
|
||||
}
|
11
.github/workflows/e2e-tests.yml
vendored
@ -89,6 +89,9 @@ jobs:
|
||||
continue-on-error: true
|
||||
- name: Setup Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- name: Cache Wasm (because rust diff)
|
||||
if: needs.check-rust-changes.outputs.rust-changed == 'true'
|
||||
uses: Swatinem/rust-cache@v2
|
||||
@ -200,9 +203,11 @@ jobs:
|
||||
- name: Run playwright/electron flow (with retries)
|
||||
id: retry
|
||||
if: ${{ !cancelled() && (success() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
uses: nick-fields/retry@v3.0.1
|
||||
with:
|
||||
command: .github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
|
||||
timeout_minutes: 30
|
||||
max_attempts: 25
|
||||
env:
|
||||
CI: true
|
||||
FAIL_ON_CONSOLE_ERRORS: true
|
||||
|
12
.github/workflows/static-analysis.yml
vendored
@ -37,6 +37,9 @@ jobs:
|
||||
node-version-file: '.nvmrc'
|
||||
cache: 'yarn'
|
||||
- run: yarn install
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
|
||||
yarn-tsc:
|
||||
@ -53,6 +56,9 @@ jobs:
|
||||
with:
|
||||
workspaces: './src/wasm-lib'
|
||||
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
- run: yarn tsc
|
||||
|
||||
@ -92,6 +98,9 @@ jobs:
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:bg
|
||||
@ -118,6 +127,9 @@ jobs:
|
||||
cache: 'yarn'
|
||||
|
||||
- run: yarn install
|
||||
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
|
||||
with:
|
||||
tool: wasm-pack
|
||||
- run: yarn build:wasm
|
||||
|
||||
- run: yarn simpleserver:bg
|
||||
|
2
.gitignore
vendored
@ -24,7 +24,7 @@ yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
# .vscode
|
||||
.helix
|
||||
src/wasm-lib/.idea
|
||||
src/wasm-lib/.vscode
|
||||
|
5
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"rust-analyzer.linkedProjects": [
|
||||
"src/wasm-lib/Cargo.toml"
|
||||
]
|
||||
}
|
41
README.md
@ -48,22 +48,49 @@ We recommend downloading the latest application binary from [our Releases page](
|
||||
|
||||
## Running a development build
|
||||
|
||||
First, [install Rust via `rustup`](https://www.rust-lang.org/tools/install). This project uses a lot of Rust compiled to [WASM](https://webassembly.org/) within it. We always use the latest stable version of Rust, so you may need to run `rustup update stable`. Then, run:
|
||||
Install a node version manager such as [fnm](https://github.com/Schniz/fnm?tab=readme-ov-#installation).
|
||||
|
||||
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.
|
||||
```
|
||||
fnm install --corepack-enabled
|
||||
fnm use
|
||||
```
|
||||
|
||||
Install the NPM dependencies with:
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
followed by:
|
||||
|
||||
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
|
||||
yarn install:rust
|
||||
yarn install:wasm-pack:sh
|
||||
|
||||
# Windows
|
||||
yarn install:rust:windows
|
||||
yarn install:wasm-pack:cargo
|
||||
```
|
||||
|
||||
Then to build the WASM layer, run:
|
||||
```
|
||||
# macOS/Linux
|
||||
yarn build:wasm
|
||||
|
||||
# Windows
|
||||
yarn build:wasm:windows
|
||||
```
|
||||
|
||||
or if you have the gh cli installed
|
||||
Or if you have the `gh` cli installed and want to download the latest main wasm bundle. Note that on Windows, you need to associate .ps1 files with PowerShell, which can be done via the right click menu, selecting `C:\Program Files\PowerShell\7\pwsh.exe`, and you can install tools like `gh` via `yarn install:tools:windows`.
|
||||
|
||||
```
|
||||
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
|
||||
# macOS/Linux
|
||||
yarn fetch:wasm
|
||||
|
||||
# Windows
|
||||
yarn fetch:wasm:windows
|
||||
```
|
||||
|
||||
That will build the WASM binary and put in the `public` dir (though gitignored).
|
||||
@ -74,7 +101,7 @@ Finally, to run the web app only, run:
|
||||
yarn start
|
||||
```
|
||||
|
||||
If you're not an KittyCAD employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again.
|
||||
If you're not a Zoo employee you won't be able to access the dev environment, you should copy everything from `.env.production` to `.env.development` to make it point to production instead, then when you navigate to `localhost:3000` the easiest way to sign in is to paste `localStorage.setItem('TOKEN_PERSIST_KEY', "your-token-from-https://zoo.dev/account/api-tokens")` replacing the with a real token from https://zoo.dev/account/api-tokens of course, then navigate to localhost:3000 again. Note that navigating to `localhost:3000/signin` removes your token so you will need to set the token again.
|
||||
|
||||
### Development environment variables
|
||||
|
||||
@ -101,7 +128,7 @@ This will start the application and hot-reload on changes.
|
||||
|
||||
Devtools can be opened with the usual Cmd-Opt-I (Mac) or Ctrl-Shift-I (Linux and Windows).
|
||||
|
||||
To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
|
||||
To package the app for your platform with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
|
||||
|
||||
## Checking out commits / Bisecting
|
||||
|
||||
|
@ -9,7 +9,7 @@ Set the appearance of a solid. This only works on solids, not sketches or indivi
|
||||
This will work on any solid, including extruded solids, revolved solids, and shelled solids.
|
||||
|
||||
```js
|
||||
appearance(solid_set: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet
|
||||
appearance(solidSet: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ appearance(solid_set: SolidSet, color: String, metalness?: number, roughness?: n
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes |
|
||||
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes |
|
||||
| `color` | `String` | Color of the new material, a hex string like '#ff0000' | Yes |
|
||||
| `metalness` | `number` | Metalness of the new material, a percentage like 95.7. | No |
|
||||
| `roughness` | `number` | Roughness of the new material, a percentage like 95.7. | No |
|
||||
|
@ -9,7 +9,7 @@ Construct a 2-dimensional circle, of the specified radius, centered at
|
||||
the provided (x, y) origin point.
|
||||
|
||||
```js
|
||||
circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
circle(data: CircleData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDecl
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes |
|
||||
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
||||
|
||||
### Returns
|
||||
|
@ -9,7 +9,7 @@ Construct a circle derived from 3 points.
|
||||
|
||||
|
||||
```js
|
||||
circleThreePoint(p1: [number], p2: [number], p3: [number], sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
circleThreePoint(p1: [number], p2: [number], p3: [number], sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ circleThreePoint(p1: [number], p2: [number], p3: [number], sketch_surface_or_gro
|
||||
| `p1` | `[number]` | 1st point to derive the circle. | Yes |
|
||||
| `p2` | `[number]` | 2nd point to derive the circle. | Yes |
|
||||
| `p3` | `[number]` | 3rd point to derive the circle. | Yes |
|
||||
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
|
||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
|
||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Identifier for the circle to reference elsewhere. | No |
|
||||
|
||||
### Returns
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::prelude::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::prelude::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::prelude::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::prelude::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::prelude::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::prelude::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
@ -1,15 +0,0 @@
|
||||
---
|
||||
title: "std::prelude::ZERO"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::prelude::ZERO: number = 0
|
||||
```
|
||||
|
||||
|
15
docs/kcl/const_std-HALF_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::HALF_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::HALF_TURN: number(deg) = 180deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/const_std-QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::QUARTER_TURN: number(deg) = 90deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/const_std-THREE_QUARTER_TURN.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::THREE_QUARTER_TURN"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::THREE_QUARTER_TURN: number(deg) = 270deg
|
||||
```
|
||||
|
||||
|
15
docs/kcl/const_std-ZERO.md
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: "std::ZERO"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
```js
|
||||
std::ZERO: number = 0
|
||||
```
|
||||
|
||||
|
@ -9,7 +9,7 @@ Extend a 2-dimensional sketch through a third dimension in order to
|
||||
create new 3-dimensional volume, or if extruded into an existing volume, cut into an existing solid.
|
||||
|
||||
```js
|
||||
extrude(sketch_set: SketchSet, length: number) -> SolidSet
|
||||
extrude(sketchSet: SketchSet, length: number) -> SolidSet
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ extrude(sketch_set: SketchSet, length: number) -> SolidSet
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
|
||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
|
||||
| `length` | `number` | How far to extrude the given sketches | Yes |
|
||||
|
||||
### Returns
|
||||
|
@ -9,7 +9,7 @@ Create a helix.
|
||||
|
||||
|
||||
```js
|
||||
helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue
|
||||
helix(revolutions: number, angleStart: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `revolutions` | `number` | Number of revolutions. | Yes |
|
||||
| `angle_start` | `number` | Start angle (in degrees). | Yes |
|
||||
| `angleStart` | `number` | Start angle (in degrees). | Yes |
|
||||
| `ccw` | `bool` | Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `radius` | `number` | Radius of the helix. | Yes |
|
||||
| `axis` | [`Axis3dOrEdgeReference`](/docs/kcl/types/Axis3dOrEdgeReference) | Axis to use for the helix. | Yes |
|
||||
|
@ -9,7 +9,7 @@ Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
|
||||
|
||||
|
||||
```js
|
||||
hole(hole_sketch: SketchSet, sketch: Sketch) -> Sketch
|
||||
hole(holeSketch: SketchSet, sketch: Sketch) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ hole(hole_sketch: SketchSet, sketch: Sketch) -> Sketch
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `hole_sketch` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `holeSketch` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
|
||||
|
||||
### Returns
|
||||
|
@ -15,7 +15,7 @@ For formats lacking unit data (such as STL, OBJ, or PLY files), the default unit
|
||||
Note: The import command currently only works when using the native Modeling App.
|
||||
|
||||
```js
|
||||
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||
import(filePath: String, options?: ImportFormat) -> ImportedGeometry
|
||||
```
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ import(file_path: String, options?: ImportFormat) -> ImportedGeometry
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `file_path` | `String` | | Yes |
|
||||
| `filePath` | `String` | | Yes |
|
||||
| `options` | [`ImportFormat`](/docs/kcl/types/ImportFormat) | Import format specifier | No |
|
||||
|
||||
### Returns
|
||||
|
@ -10,6 +10,10 @@ layout: manual
|
||||
* [Modules](kcl/modules)
|
||||
* [Known Issues](kcl/KNOWN-ISSUES)
|
||||
* **`std`**
|
||||
* [`HALF_TURN`](kcl/const_std-HALF_TURN)
|
||||
* [`QUARTER_TURN`](kcl/const_std-QUARTER_TURN)
|
||||
* [`THREE_QUARTER_TURN`](kcl/const_std-THREE_QUARTER_TURN)
|
||||
* [`ZERO`](kcl/const_std-ZERO)
|
||||
* [`abs`](kcl/abs)
|
||||
* [`acos`](kcl/acos)
|
||||
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
|
||||
@ -118,8 +122,3 @@ layout: manual
|
||||
* [`cos`](kcl/std-math-cos)
|
||||
* [`sin`](kcl/std-math-sin)
|
||||
* [`tan`](kcl/std-math-tan)
|
||||
* **`std::prelude`**
|
||||
* [`HALF_TURN`](kcl/const_std-prelude-HALF_TURN)
|
||||
* [`QUARTER_TURN`](kcl/const_std-prelude-QUARTER_TURN)
|
||||
* [`THREE_QUARTER_TURN`](kcl/const_std-prelude-THREE_QUARTER_TURN)
|
||||
* [`ZERO`](kcl/const_std-prelude-ZERO)
|
||||
|
@ -9,7 +9,7 @@ Extend the current sketch with a new straight line.
|
||||
|
||||
|
||||
```js
|
||||
line(sketch: Sketch, end_absolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch
|
||||
line(sketch: Sketch, endAbsolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ line(sketch: Sketch, end_absolute?: [number], end?: [number], tag?: TagDeclarato
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
|
||||
| `end_absolute` | `[number]` | Which absolute point should this line go to? Incompatible with `end`. | No |
|
||||
| `endAbsolute` | `[number]` | Which absolute point should this line go to? Incompatible with `end`. | No |
|
||||
| `end` | `[number]` | How far away (along the X and Y axes) should this line go? Incompatible with `endAbsolute`. | No |
|
||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |
|
||||
|
||||
|
@ -9,7 +9,7 @@ Create a 3D surface or solid by interpolating between two or more sketches.
|
||||
The sketches need to closed and on the same plane.
|
||||
|
||||
```js
|
||||
loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, base_curve_index?: integer, tolerance?: number) -> Solid
|
||||
loft(sketches: [Sketch], vDegree: NonZeroU32, bezApproximateRational: bool, baseCurveIndex?: integer, tolerance?: number) -> Solid
|
||||
```
|
||||
|
||||
|
||||
@ -18,9 +18,9 @@ loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, b
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes |
|
||||
| `v_degree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes |
|
||||
| `bez_approximate_rational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes |
|
||||
| `base_curve_index` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
|
||||
| `vDegree` | `NonZeroU32` | Degree of the interpolation. Must be greater than zero. For example, use 2 for quadratic, or 3 for cubic interpolation in the V direction. This defaults to 2, if not specified. | Yes |
|
||||
| `bezApproximateRational` | `bool` | Attempt to approximate rational curves (such as arcs) using a bezier. This will remove banding around interpolations between arcs and non-arcs. It may produce errors in other scenarios Over time, this field won't be necessary. | Yes |
|
||||
| `baseCurveIndex` | `integer` | This can be set to override the automatically determined topological base curve, which is usually the first section encountered. | No |
|
||||
| `tolerance` | `number` | Tolerance for the loft operation. | No |
|
||||
|
||||
### Returns
|
||||
|
@ -9,7 +9,7 @@ Apply a function to every element of a list.
|
||||
Given a list like `[a, b, c]`, and a function like `f`, returns `[f(a), f(b), f(c)]`
|
||||
|
||||
```js
|
||||
map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
|
||||
map(array: [KclValue], mapFn: FunctionSource) -> [KclValue]
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
|
||||
| `map_fn` | `FunctionParam` | | Yes |
|
||||
| `mapFn` | `FunctionSource` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -11,7 +11,7 @@ Only works on unclosed sketches for now.
|
||||
Mirror occurs around a local sketch axis rather than a global axis.
|
||||
|
||||
```js
|
||||
mirror2d(data: Mirror2dData, sketch_set: SketchSet) -> [Sketch]
|
||||
mirror2d(data: Mirror2dData, sketchSet: SketchSet) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ mirror2d(data: Mirror2dData, sketch_set: SketchSet) -> [Sketch]
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`Mirror2dData`](/docs/kcl/types/Mirror2dData) | Data for a mirror. | Yes |
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or
|
||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||
|
||||
```js
|
||||
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch]
|
||||
patternCircular2d(sketchSet: SketchSet, instances: integer, center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,12 +17,12 @@ patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], a
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
|
||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `arcDegrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotateDuplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Repeat a 3-dimensional solid some number of times along a partial or
|
||||
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
|
||||
|
||||
```js
|
||||
patternCircular3d(solid_set: SolidSet, instances: integer, axis: [number], center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Solid]
|
||||
patternCircular3d(solidSet: SolidSet, instances: integer, axis: [number], center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -17,13 +17,13 @@ patternCircular3d(solid_set: SolidSet, instances: integer, axis: [number], cente
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes |
|
||||
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `axis` | `[number]` | The axis around which to make the pattern. This is a 3D vector | Yes |
|
||||
| `center` | `[number]` | The center about which to make the pattern. This is a 3D vector. | Yes |
|
||||
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `arcDegrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
||||
| `rotateDuplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch]
|
||||
patternLinear2d(sketchSet: SketchSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,11 +17,11 @@ patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axi
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
|
||||
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Repeat a 3-dimensional solid along a linear path, with a dynamic amount
|
||||
of distance between each repetition, some specified number of times.
|
||||
|
||||
```js
|
||||
patternLinear3d(solid_set: SolidSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Solid]
|
||||
patternLinear3d(solidSet: SolidSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -17,11 +17,11 @@ patternLinear3d(solid_set: SolidSet, instances: integer, distance: number, axis:
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
|
||||
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
|
||||
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -35,7 +35,7 @@ The transform function returns a transform object. All properties of the object
|
||||
- `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
|
||||
|
||||
```js
|
||||
patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Solid]
|
||||
patternTransform(solidSet: SolidSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Solid]
|
||||
```
|
||||
|
||||
|
||||
@ -43,10 +43,10 @@ patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionPar
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
|
||||
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
||||
|
||||
|
||||
```js
|
||||
patternTransform2d(sketch_set: SketchSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Sketch]
|
||||
patternTransform2d(sketchSet: SketchSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Sketch]
|
||||
```
|
||||
|
||||
|
||||
@ -17,10 +17,10 @@ patternTransform2d(sketch_set: SketchSet, instances: integer, transform: Functio
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
|
||||
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||
| `transform` | `FunctionParam` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
||||
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
|
||||
| `useOriginal` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Create a regular polygon with the specified number of sides that is either inscr
|
||||
|
||||
|
||||
```js
|
||||
polygon(data: PolygonData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
polygon(data: PolygonData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ polygon(data: PolygonData, sketch_surface_or_group: SketchOrSurface, tag?: TagDe
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
|
||||
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
||||
|
||||
### Returns
|
||||
|
@ -9,7 +9,7 @@ Take a starting value. Then, for each element of an array, calculate the next va
|
||||
using the previous value and the element.
|
||||
|
||||
```js
|
||||
reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
||||
reduce(array: [KclValue], start: KclValue, reduceFn: FunctionSource) -> KclValue
|
||||
```
|
||||
|
||||
|
||||
@ -19,7 +19,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
||||
|----------|------|-------------|----------|
|
||||
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
|
||||
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
|
||||
| `reduce_fn` | `FunctionParam` | | Yes |
|
||||
| `reduceFn` | `FunctionSource` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Remove volume from a 3-dimensional shape such that a wall of the
|
||||
provided thickness remains, taking volume starting at the provided face, leaving it open in that direction.
|
||||
|
||||
```js
|
||||
shell(solid_set: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
|
||||
shell(solidSet: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ shell(solid_set: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes |
|
||||
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes |
|
||||
| `thickness` | `number` | The thickness of the shell | Yes |
|
||||
| `faces` | [`[FaceTag]`](/docs/kcl/types/FaceTag) | The faces you want removed | Yes |
|
||||
|
||||
|
@ -9,7 +9,7 @@ Start a new profile at a given point.
|
||||
|
||||
|
||||
```js
|
||||
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator) -> Sketch
|
||||
startProfileAt(to: [number], sketchSurface: SketchSurface, tag?: TagDeclarator) -> Sketch
|
||||
```
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator)
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `to` | `[number]` | | Yes |
|
||||
| `sketch_surface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
|
||||
| `sketchSurface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
|
||||
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
||||
|
||||
### Returns
|
||||
|
@ -38863,7 +38863,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "solid_set",
|
||||
"name": "solidSet",
|
||||
"type": "SolidSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -70936,7 +70936,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "sketch_surface_or_group",
|
||||
"name": "sketchSurfaceOrGroup",
|
||||
"type": "SketchOrSurface",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -75887,7 +75887,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "sketch_surface_or_group",
|
||||
"name": "sketchSurfaceOrGroup",
|
||||
"type": "SketchOrSurface",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -85796,7 +85796,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "sketch_set",
|
||||
"name": "sketchSet",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -104094,7 +104094,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "angle_start",
|
||||
"name": "angleStart",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -110158,7 +110158,7 @@
|
||||
"keywordArguments": false,
|
||||
"args": [
|
||||
{
|
||||
"name": "hole_sketch",
|
||||
"name": "holeSketch",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -118587,7 +118587,7 @@
|
||||
"keywordArguments": false,
|
||||
"args": [
|
||||
{
|
||||
"name": "file_path",
|
||||
"name": "filePath",
|
||||
"type": "String",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -124304,7 +124304,7 @@
|
||||
"labelRequired": false
|
||||
},
|
||||
{
|
||||
"name": "end_absolute",
|
||||
"name": "endAbsolute",
|
||||
"type": "[number]",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -132449,7 +132449,7 @@
|
||||
"labelRequired": false
|
||||
},
|
||||
{
|
||||
"name": "v_degree",
|
||||
"name": "vDegree",
|
||||
"type": "NonZeroU32",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -134051,7 +134051,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "bez_approximate_rational",
|
||||
"name": "bezApproximateRational",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -135651,7 +135651,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "base_curve_index",
|
||||
"name": "baseCurveIndex",
|
||||
"type": "integer",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -141138,14 +141138,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -143279,28 +143271,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -143331,11 +143301,11 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "map_fn",
|
||||
"type": "FunctionParam",
|
||||
"name": "mapFn",
|
||||
"type": "FunctionSource",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
"title": "FunctionParam",
|
||||
"title": "FunctionSource",
|
||||
"type": "null",
|
||||
"definitions": {
|
||||
"KclValue": {
|
||||
@ -143756,14 +143726,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -145897,28 +145859,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -146378,14 +146318,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -148519,28 +148451,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -149897,7 +149807,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "sketch_set",
|
||||
"name": "sketchSet",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -153793,7 +153703,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "sketch_set",
|
||||
"name": "sketchSet",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -158703,7 +158613,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "arc_degrees",
|
||||
"name": "arcDegrees",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -160304,7 +160214,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "rotate_duplicates",
|
||||
"name": "rotateDuplicates",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -161904,7 +161814,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -165120,7 +165030,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "solid_set",
|
||||
"name": "solidSet",
|
||||
"type": "SolidSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -171640,7 +171550,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "arc_degrees",
|
||||
"name": "arcDegrees",
|
||||
"type": "number",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -173241,7 +173151,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "rotate_duplicates",
|
||||
"name": "rotateDuplicates",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -174841,7 +174751,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -178057,7 +177967,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "sketch_set",
|
||||
"name": "sketchSet",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -184568,7 +184478,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -187784,7 +187694,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "solid_set",
|
||||
"name": "solidSet",
|
||||
"type": "SolidSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -194299,7 +194209,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -197515,7 +197425,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "solid_set",
|
||||
"name": "solidSet",
|
||||
"type": "SolidSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -200824,10 +200734,10 @@
|
||||
},
|
||||
{
|
||||
"name": "transform",
|
||||
"type": "FunctionParam",
|
||||
"type": "FunctionSource",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
"title": "FunctionParam",
|
||||
"title": "FunctionSource",
|
||||
"type": "null",
|
||||
"definitions": {
|
||||
"ArtifactId": {
|
||||
@ -202423,7 +202333,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -205644,7 +205554,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "sketch_set",
|
||||
"name": "sketchSet",
|
||||
"type": "SketchSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -208949,10 +208859,10 @@
|
||||
},
|
||||
{
|
||||
"name": "transform",
|
||||
"type": "FunctionParam",
|
||||
"type": "FunctionSource",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
"title": "FunctionParam",
|
||||
"title": "FunctionSource",
|
||||
"type": "null",
|
||||
"definitions": {
|
||||
"Path": {
|
||||
@ -210548,7 +210458,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "use_original",
|
||||
"name": "useOriginal",
|
||||
"type": "bool",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -214272,7 +214182,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "sketch_surface_or_group",
|
||||
"name": "sketchSurfaceOrGroup",
|
||||
"type": "SketchOrSurface",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -219594,14 +219504,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -221735,28 +221637,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -222211,14 +222091,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -223102,14 +222974,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -224863,28 +224727,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -230504,14 +230346,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -232645,28 +232479,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -233119,14 +232931,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -233630,14 +233434,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -235771,28 +235567,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -236246,14 +236020,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -237137,14 +236903,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -238898,28 +238656,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -239391,14 +239127,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -241532,28 +241260,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -242006,14 +241712,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -242517,14 +242215,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -244658,28 +244348,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -244710,11 +244378,11 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "reduce_fn",
|
||||
"type": "FunctionParam",
|
||||
"name": "reduceFn",
|
||||
"type": "FunctionSource",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
"title": "FunctionParam",
|
||||
"title": "FunctionSource",
|
||||
"type": "null",
|
||||
"definitions": {
|
||||
"KclValue": {
|
||||
@ -245135,14 +244803,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -247276,28 +246936,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -247751,14 +247389,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -248642,14 +248272,6 @@
|
||||
"Function"
|
||||
]
|
||||
},
|
||||
"memory": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvironmentRef"
|
||||
}
|
||||
],
|
||||
"nullable": true
|
||||
},
|
||||
"__meta": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@ -250403,28 +250025,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvironmentRef": {
|
||||
"description": "An index pointing to an environment at a point in time (either a snapshot or the current version, see the module docs).",
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SnapshotRef"
|
||||
}
|
||||
],
|
||||
"maxItems": 2,
|
||||
"minItems": 2
|
||||
},
|
||||
"SnapshotRef": {
|
||||
"description": "An index pointing to a snapshot within a specific (unspecified) environment.",
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"ModuleId": {
|
||||
"description": "Identifier of a source file. Uses a u32 to keep the size small.",
|
||||
"type": "integer",
|
||||
@ -264274,7 +263874,7 @@
|
||||
"keywordArguments": true,
|
||||
"args": [
|
||||
{
|
||||
"name": "solid_set",
|
||||
"name": "solidSet",
|
||||
"type": "SolidSet",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
@ -271003,7 +270603,7 @@
|
||||
"labelRequired": true
|
||||
},
|
||||
{
|
||||
"name": "sketch_surface",
|
||||
"name": "sketchSurface",
|
||||
"type": "SketchSurface",
|
||||
"schema": {
|
||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||
|
@ -295,7 +295,6 @@ Data for an imported geometry.
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Function`| | No |
|
||||
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
|
@ -219,7 +219,11 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
}
|
||||
)
|
||||
|
||||
test('Can extrude from the command bar', async ({ page, homePage }) => {
|
||||
test('Can extrude from the command bar', async ({
|
||||
page,
|
||||
homePage,
|
||||
cmdBar,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
@ -254,7 +258,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
|
||||
// Search for extrude command and choose it
|
||||
await page.getByRole('option', { name: 'Extrude' }).click()
|
||||
await cmdBar.cmdOptions.getByText('Extrude').click()
|
||||
|
||||
// Assert that we're on the selection step
|
||||
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()
|
||||
|
@ -59,18 +59,25 @@ export interface Fixtures {
|
||||
homePage: HomePageFixture
|
||||
}
|
||||
export class AuthenticatedTronApp {
|
||||
public readonly _page: Page
|
||||
public originalPage: Page
|
||||
public page: Page
|
||||
public browserContext: BrowserContext
|
||||
public context: BrowserContext
|
||||
public readonly testInfo: TestInfo
|
||||
public electronApp: ElectronApplication | undefined
|
||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||
public dir: string = ''
|
||||
|
||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||
this._page = page
|
||||
this.page = page
|
||||
this.context = context
|
||||
constructor(
|
||||
browserContext: BrowserContext,
|
||||
originalPage: Page,
|
||||
testInfo: TestInfo
|
||||
) {
|
||||
this.page = originalPage
|
||||
this.originalPage = originalPage
|
||||
this.browserContext = browserContext
|
||||
// Will be overwritten in the initializer
|
||||
this.context = browserContext
|
||||
this.testInfo = testInfo
|
||||
}
|
||||
async initialise(
|
||||
@ -86,9 +93,17 @@ export class AuthenticatedTronApp {
|
||||
folderSetupFn: arg.folderSetupFn,
|
||||
cleanProjectDir: arg.cleanProjectDir,
|
||||
appSettings: arg.appSettings,
|
||||
viewport: this.viewPortSize,
|
||||
})
|
||||
this.page = page
|
||||
|
||||
// These assignments "fix" some brokenness in the Playwright Workbench when
|
||||
// running against electron applications.
|
||||
// The timeline is still broken but failure screenshots work again.
|
||||
this.context = context
|
||||
// TODO: try to get this to work again for screenshots, but it messed with test ends when enabled
|
||||
// Object.assign(this.browserContext, this.context)
|
||||
|
||||
this.electronApp = electronApp
|
||||
this.dir = dir
|
||||
|
||||
|
@ -1064,7 +1064,7 @@ openSketch = startSketchOn('XY')
|
||||
0
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||
})
|
||||
})
|
||||
@ -1125,11 +1125,53 @@ openSketch = startSketchOn('XY')
|
||||
await scene.expectPixelColor([250, 250, 250], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step('Delete offset plane via feature tree selection', async () => {
|
||||
await test.step(`Edit helix through the feature tree`, async () => {
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.dblclick()
|
||||
const initialInput = '5'
|
||||
const newInput = '50'
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Helix',
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'length',
|
||||
currentArgValue: initialInput,
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: initialInput,
|
||||
Radius: '5',
|
||||
Revolutions: '1',
|
||||
},
|
||||
highlightedHeaderArg: 'length',
|
||||
})
|
||||
await expect(cmdBar.currentArgumentInput).toBeVisible()
|
||||
await cmdBar.currentArgumentInput.locator('.cm-content').fill(newInput)
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
AngleStart: '360',
|
||||
Axis: 'X',
|
||||
CounterClockWise: '',
|
||||
Length: newInput,
|
||||
Radius: '5',
|
||||
Revolutions: '1',
|
||||
},
|
||||
commandName: 'Helix',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closeFeatureTreePane()
|
||||
await editor.openPane()
|
||||
await editor.expectEditor.toContain('length = ' + newInput)
|
||||
})
|
||||
|
||||
await test.step('Delete helix via feature tree selection', async () => {
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
// Red plane is back
|
||||
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
|
||||
})
|
||||
@ -1221,7 +1263,7 @@ openSketch = startSketchOn('XY')
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
})
|
||||
@ -1264,7 +1306,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
`)
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for sketch 1
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
@ -1275,7 +1317,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
`)
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for plane001
|
||||
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
|
||||
})
|
||||
@ -1286,7 +1328,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
`)
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
// Check for sketch 1
|
||||
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
|
||||
})
|
||||
@ -1380,7 +1422,7 @@ sketch002 = startSketchOn('XZ')
|
||||
await page.waitForTimeout(500)
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
await toolbar.closePane('feature-tree')
|
||||
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
|
||||
@ -1686,7 +1728,7 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
1
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
|
||||
await editor.expectEditor.not.toContain(secondFilletDeclaration)
|
||||
@ -1788,7 +1830,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|
||||
0
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => {
|
||||
@ -1818,7 +1860,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|
||||
1
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => {
|
||||
@ -2057,7 +2099,7 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
1
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
|
||||
await scene.expectPixelColor(chamferColor, firstEdgeLocation, 15) // stayed
|
||||
@ -2160,7 +2202,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|
||||
0
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => {
|
||||
@ -2192,7 +2234,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|
||||
1
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await page.waitForTimeout(500)
|
||||
})
|
||||
await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => {
|
||||
@ -2396,7 +2438,7 @@ extrude001 = extrude(sketch001, length = 40)
|
||||
await editor.closePane()
|
||||
const operationButton = await toolbar.getFeatureTreeOperation('Shell', 0)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
|
||||
})
|
||||
})
|
||||
@ -2535,7 +2577,7 @@ profile001 = startProfileAt([-20, 20], sketch001)
|
||||
const deleteOperation = async (operationButton: Locator) => {
|
||||
if (shouldUseKeyboard) {
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
} else {
|
||||
await operationButton.click({ button: 'right' })
|
||||
const editButton = page.getByTestId('context-menu-delete')
|
||||
@ -2796,4 +2838,107 @@ radius = 8.69
|
||||
expect(editor.expectEditor.toContain(newCodeToFind)).toBeTruthy()
|
||||
})
|
||||
})
|
||||
|
||||
test(`Set appearance`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
profile001 = circle({
|
||||
center = [0, 0],
|
||||
radius = 100
|
||||
}, sketch001)
|
||||
extrude001 = extrude(profile001, length = 100)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 500, y: 250 }
|
||||
const initialColor: [number, number, number] = [135, 135, 135]
|
||||
|
||||
await test.step(`Confirm extrude exists with default appearance`, async () => {
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor(initialColor, testPoint, 15)
|
||||
})
|
||||
|
||||
async function setApperanceAndCheck(
|
||||
option: string,
|
||||
hex: string,
|
||||
shapeColor: [number, number, number]
|
||||
) {
|
||||
await toolbar.openPane('feature-tree')
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Extrude',
|
||||
0
|
||||
)
|
||||
await operationButton.click({ button: 'right' })
|
||||
const menuButton = page.getByTestId('context-menu-set-appearance')
|
||||
await menuButton.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Appearance',
|
||||
currentArgKey: 'color',
|
||||
currentArgValue: '',
|
||||
headerArguments: {
|
||||
Color: '',
|
||||
},
|
||||
highlightedHeaderArg: 'color',
|
||||
stage: 'arguments',
|
||||
})
|
||||
const item = page.getByText(option, { exact: true })
|
||||
await item.click()
|
||||
await cmdBar.expectState({
|
||||
commandName: 'Appearance',
|
||||
headerArguments: {
|
||||
Color: hex,
|
||||
},
|
||||
stage: 'review',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await toolbar.closePane('feature-tree')
|
||||
await scene.expectPixelColor(shapeColor, testPoint, 40)
|
||||
await toolbar.openPane('code')
|
||||
if (hex === 'default') {
|
||||
const anyAppearanceDeclaration = `|> appearance(`
|
||||
await editor.expectEditor.not.toContain(anyAppearanceDeclaration)
|
||||
} else {
|
||||
const declaration = `|> appearance(%, color = '${hex}')`
|
||||
await editor.expectEditor.toContain(declaration)
|
||||
// TODO: fix selection range after appearance update
|
||||
// await editor.expectState({
|
||||
// diagnostics: [],
|
||||
// activeLines: [declaration],
|
||||
// highlightedCode: '',
|
||||
// })
|
||||
}
|
||||
await toolbar.closePane('code')
|
||||
}
|
||||
|
||||
await test.step(`Go through the Set Appearance flow for all options`, async () => {
|
||||
await setApperanceAndCheck('Red', '#FF0000', [180, 0, 0])
|
||||
await setApperanceAndCheck('Green', '#00FF00', [0, 180, 0])
|
||||
await setApperanceAndCheck('Blue', '#0000FF', [0, 0, 180])
|
||||
await setApperanceAndCheck('Turquoise', '#00FFFF', [0, 180, 180])
|
||||
await setApperanceAndCheck('Purple', '#FF00FF', [180, 0, 180])
|
||||
await setApperanceAndCheck('Yellow', '#FFFF00', [180, 180, 0])
|
||||
await setApperanceAndCheck('Black', '#000000', [0, 0, 0])
|
||||
await setApperanceAndCheck('Dark Grey', '#080808', [10, 10, 10])
|
||||
await setApperanceAndCheck('Light Grey', '#D3D3D3', [190, 190, 190])
|
||||
await setApperanceAndCheck('White', '#FFFFFF', [200, 200, 200])
|
||||
await setApperanceAndCheck(
|
||||
'Default (clear appearance)',
|
||||
'default',
|
||||
initialColor
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 143 KiB After Width: | Height: | Size: 145 KiB |
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 129 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
@ -937,11 +937,16 @@ export async function setupElectron({
|
||||
testInfo,
|
||||
cleanProjectDir = true,
|
||||
appSettings,
|
||||
viewport,
|
||||
}: {
|
||||
testInfo: TestInfo
|
||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
||||
cleanProjectDir?: boolean
|
||||
appSettings?: Partial<SaveSettingsPayload>
|
||||
viewport: {
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
}): Promise<{
|
||||
electronApp: ElectronApplication
|
||||
context: BrowserContext
|
||||
@ -972,6 +977,14 @@ export async function setupElectron({
|
||||
...(process.env.ELECTRON_OVERRIDE_DIST_PATH
|
||||
? { executablePath: process.env.ELECTRON_OVERRIDE_DIST_PATH + 'electron' }
|
||||
: {}),
|
||||
...(process.env.PLAYWRIGHT_RECORD_VIDEO
|
||||
? {
|
||||
recordVideo: {
|
||||
dir: testInfo.snapshotPath(),
|
||||
size: viewport,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}
|
||||
|
||||
// Do this once and then reuse window on subsequent calls.
|
||||
|
@ -94,6 +94,8 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
||||
await bakeInRetries(async () => {
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
|
||||
expect(appLogoBBox).not.toBeNull()
|
||||
if (!appLogoBBox) throw new Error('app logo not found')
|
||||
@ -101,7 +103,9 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
||||
appLogoBBox.x + appLogoBBox.width / 2,
|
||||
appLogoBBox.y + appLogoBBox.height / 2
|
||||
)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.move(600, 303)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
}, [4, -10.5, -120])
|
||||
|
||||
|
@ -382,7 +382,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
'|> line(end = [0, -pipeLength])'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
|
||||
@ -439,7 +439,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
'|> startProfileAt([23.24, 136.52], %)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
|
||||
@ -453,7 +453,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
' |> line(end = [20.91, -28.61])'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await page.waitForTimeout(200)
|
||||
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
|
||||
@ -518,7 +518,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
await page.keyboard.press('Delete')
|
||||
|
||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||
}
|
||||
|
@ -68,13 +68,6 @@ type PWFunction = (
|
||||
|
||||
let firstUrl = ''
|
||||
|
||||
// The below error is due to the extreme type spaghetti going on. playwright/
|
||||
// types/test.d.ts does not export 2 functions (below is one of them) but tsc
|
||||
// is trying to use a interface name it can't see.
|
||||
// e2e/playwright/zoo-test.ts:64:14 - error TS4023: Exported variable 'test' has
|
||||
// or is using name 'TestFunction' from external module
|
||||
// "/home/lee/Code/Zoo/modeling-app/dirty2/node_modules/playwright/types/test"
|
||||
// but cannot be named.
|
||||
export const test = (
|
||||
desc: string,
|
||||
objOrFn: PWFunction | TestDetails,
|
||||
|
23
package.json
@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"@codemirror/autocomplete": "^6.17.0",
|
||||
"@codemirror/commands": "^6.6.0",
|
||||
"@codemirror/language": "^6.10.3",
|
||||
"@codemirror/language": "^6.10.8",
|
||||
"@codemirror/lint": "^6.8.4",
|
||||
"@codemirror/search": "^6.5.6",
|
||||
"@codemirror/state": "^6.4.1",
|
||||
@ -40,7 +40,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "^6.5.0",
|
||||
"electron-updater": "^6.6.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -69,10 +69,15 @@
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"scripts": {
|
||||
"install:rust": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y",
|
||||
"install:rust:windows": "winget install Microsoft.VisualStudio.2022.Community --silent --override \"--wait --quiet --add ProductLang En-us --add Microsoft.VisualStudio.Workload.NativeDesktop --includeRecommended\" && winget install Rustlang.Rustup",
|
||||
"install:wasm-pack:sh": ". $HOME/.cargo/env && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y",
|
||||
"install:wasm-pack:cargo": "cargo install wasm-pack",
|
||||
"install:tools:windows": "winget install jqlang.jq MikeFarah.yq GitHub.cli",
|
||||
"start": "vite --port=3000 --host=0.0.0.0",
|
||||
"start:prod": "vite preview --port=3000",
|
||||
"serve": "vite serve --port=3000",
|
||||
"build": "curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && source \"$HOME/.cargo/env\" && curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh -s -- -y && yarn build:wasm && vite build",
|
||||
"build": "yarn install:rust && . $HOME/.cargo/env && yarn install:wasm-pack:sh && yarn build:wasm && vite build",
|
||||
"build:local": "vite build",
|
||||
"build:both": "vite build",
|
||||
"build:both:local": "yarn build:wasm && vite build",
|
||||
@ -84,11 +89,13 @@
|
||||
"simpleserver:stop": "kill-port 3000",
|
||||
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
|
||||
"fetch:wasm": "./get-latest-wasm-bundle.sh",
|
||||
"fetch:wasm": "./scripts/get-latest-wasm-bundle.sh",
|
||||
"fetch:wasm:windows": "./scripts/get-latest-wasm-bundle.ps1",
|
||||
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/next/manifest.json",
|
||||
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
|
||||
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",
|
||||
"build:wasm:nocopy": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings",
|
||||
"build:wasm": "yarn build:wasm:nocopy && cp src/wasm-lib/pkg/wasm_lib_bg.wasm public && yarn fmt",
|
||||
"build:wasm:windows": "yarn install:wasm-pack:cargo && yarn build:wasm:nocopy && copy src\\wasm-lib\\pkg\\wasm_lib_bg.wasm public && yarn fmt",
|
||||
"remove-importmeta": "sed -i 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\"; sed -i '' 's/import.meta.url/window.location.origin/g' \"./src/wasm-lib/pkg/wasm_lib.js\" || echo \"sed for both mac and linux\"",
|
||||
"wasm-prep": "rimraf src/wasm-lib/pkg && mkdirp src/wasm-lib/pkg && rimraf src/wasm-lib/kcl/bindings",
|
||||
"lint-fix": "eslint --fix --ext .ts --ext .tsx src e2e packages/codemirror-lsp-client/src",
|
||||
@ -96,6 +103,7 @@
|
||||
"files:set-version": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 2)\" > package.json",
|
||||
"files:set-notes": "./scripts/set-files-notes.sh",
|
||||
"files:flip-to-nightly": "./scripts/flip-files-to-nightly.sh",
|
||||
"files:flip-to-nightly:windows": "./scripts/flip-files-to-nightly.ps1",
|
||||
"files:invalidate-bucket": "./scripts/invalidate-files-bucket.sh",
|
||||
"files:invalidate-bucket:nightly": "./scripts/invalidate-files-bucket.sh --nightly",
|
||||
"postinstall": "yarn fetch:samples && yarn xstate:typegen && ./node_modules/.bin/electron-rebuild",
|
||||
@ -198,7 +206,7 @@
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"setimmediate": "^1.0.5",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.23.0",
|
||||
@ -207,7 +215,6 @@
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.1",
|
||||
"vitest-webgl-canvas-mock": "^1.1.0",
|
||||
"wasm-pack": "^0.13.1",
|
||||
"ws": "^8.17.0",
|
||||
"yarn": "^1.22.22"
|
||||
},
|
||||
|
20
scripts/flip-files-to-nightly.ps1
Normal file
@ -0,0 +1,20 @@
|
||||
$VERSION=$(Get-Date -Format "yy.M.d")
|
||||
$COMMIT=$(git rev-parse --short HEAD)
|
||||
$PRODUCT_NAME="Zoo Modeling App (Nightly)"
|
||||
|
||||
# package.json
|
||||
yq -i '.version = env(VERSION)' -p=json -o=json package.json
|
||||
yq -i '.productName = env(PRODUCT_NAME)' -p=json -o=json package.json
|
||||
yq -i '.name = "zoo-modeling-app-nightly"' -p=json -o=json package.json
|
||||
|
||||
# electron-builder.yml
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
||||
yq -i '.appId = "dev.zoo.modeling-app-nightly"' electron-builder.yml
|
||||
yq -i '.nsis.include = "./scripts/installer-nightly.nsh"' electron-builder.yml
|
||||
|
||||
# Release notes
|
||||
echo "Nightly build $VERSION (commit $COMMIT)" > release-notes.md
|
||||
|
||||
# icons
|
||||
cp assets/icon-nightly.png assets/icon.png
|
||||
cp assets/icon-nightly.ico assets/icon.ico
|
13
scripts/get-latest-wasm-bundle.ps1
Normal file
@ -0,0 +1,13 @@
|
||||
$REPO_OWNER = "KittyCAD"
|
||||
$REPO_NAME = "modeling-app"
|
||||
$WORKFLOW_NAME = "build-and-store-wasm.yml"
|
||||
$ARTIFACT_NAME = "wasm-bundle"
|
||||
|
||||
# Fetch the latest completed workflow run ID for the specified workflow
|
||||
$RUN_ID = (gh run list -w $WORKFLOW_NAME --repo $REPO_OWNER/$REPO_NAME --limit 1 --json databaseId -s completed --jq 'first | .databaseId') -join [Environment]::NewLine
|
||||
|
||||
$PKG_PATH="./src/wasm-lib/pkg/"
|
||||
rm -r $PKG_PATH/*
|
||||
gh run download $RUN_ID --repo $REPO_OWNER/$REPO_NAME --name $ARTIFACT_NAME --dir $PKG_PATH
|
||||
cp $PKG_PATH/wasm_lib_bg.wasm public
|
||||
echo "latest wasm copied to public folder"
|
0
get-latest-wasm-bundle.sh → scripts/get-latest-wasm-bundle.sh
Executable file → Normal file
@ -133,9 +133,11 @@ function DisplayObj({
|
||||
}}
|
||||
onClick={(e) => {
|
||||
const range = topLevelRange(obj?.start || 0, obj.end || 0)
|
||||
const idInfo = codeToIdSelections([
|
||||
{ codeRef: codeRefFromRange(range, kclManager.ast) },
|
||||
])[0]
|
||||
const idInfo = codeToIdSelections(
|
||||
[{ codeRef: codeRefFromRange(range, kclManager.ast) }],
|
||||
engineCommandManager.artifactGraph,
|
||||
engineCommandManager.artifactIndex
|
||||
)[0]
|
||||
const artifact = engineCommandManager.artifactGraph.get(
|
||||
idInfo?.id || ''
|
||||
)
|
||||
|
@ -110,6 +110,7 @@ import { commandBarActor } from 'machines/commandBarMachine'
|
||||
import { useToken } from 'machines/appMachine'
|
||||
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
|
||||
import { useSettings } from 'machines/appMachine'
|
||||
import { isDesktop } from 'lib/isDesktop'
|
||||
|
||||
type MachineContext<T extends AnyStateMachine> = {
|
||||
state: StateFrom<T>
|
||||
@ -802,7 +803,7 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plan
|
||||
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plane
|
||||
// but still works if the user selected a plane/face by defaulting to the first path
|
||||
const mainPath =
|
||||
artifact?.type === 'segment' || artifact?.type === 'solid2d'
|
||||
@ -1717,8 +1718,12 @@ export const ModelingMachineProvider = ({
|
||||
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
|
||||
}, [allowOrbitInSketchMode])
|
||||
|
||||
// Allow using the delete key to delete solids
|
||||
useHotkeys(['backspace', 'delete', 'del'], () => {
|
||||
// Allow using the delete key to delete solids. Backspace only on macOS as Windows and Linux have dedicated Delete
|
||||
const deleteKeys =
|
||||
isDesktop() && window.electron.os.isMac
|
||||
? ['backspace', 'delete', 'del']
|
||||
: ['delete', 'del']
|
||||
useHotkeys(deleteKeys, () => {
|
||||
modelingSend({ type: 'Delete selection' })
|
||||
})
|
||||
|
||||
|
@ -324,6 +324,18 @@ const OperationItem = (props: {
|
||||
}
|
||||
}
|
||||
|
||||
function enterAppearanceFlow() {
|
||||
if (props.item.type === 'StdLibCall') {
|
||||
props.send({
|
||||
type: 'enterAppearanceFlow',
|
||||
data: {
|
||||
targetSourceRange: sourceRangeFromRust(props.item.sourceRange),
|
||||
currentOperation: props.item,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function deleteOperation() {
|
||||
if (
|
||||
props.item.type === 'StdLibCall' ||
|
||||
@ -380,6 +392,13 @@ const OperationItem = (props: {
|
||||
: []),
|
||||
...(props.item.type === 'StdLibCall'
|
||||
? [
|
||||
<ContextMenuItem
|
||||
disabled={!stdLibMap[props.item.name]?.supportsAppearance}
|
||||
onClick={enterAppearanceFlow}
|
||||
data-testid="context-menu-set-appearance"
|
||||
>
|
||||
Set appearance
|
||||
</ContextMenuItem>,
|
||||
<ContextMenuItem
|
||||
disabled={!stdLibMap[props.item.name]?.prepareToEdit}
|
||||
onClick={enterEditFlow}
|
||||
|
@ -33,7 +33,7 @@ describe('processMemory', () => {
|
||||
const output = processMemory(execState.variables)
|
||||
expect(output.myVar).toEqual(5)
|
||||
expect(output.otherVar).toEqual(3)
|
||||
expect(output.myFn).toEqual('__function(a)__')
|
||||
expect(output.myFn).toEqual('__function__')
|
||||
expect(output.theExtrude).toEqual([
|
||||
{
|
||||
type: 'extrudePlane',
|
||||
|
@ -107,9 +107,7 @@ export const processMemory = (variables: VariableMap) => {
|
||||
}
|
||||
//@ts-ignore
|
||||
} else if (val.type === 'Function') {
|
||||
processedMemory[key] = `__function(${(val as any)?.expression?.params
|
||||
?.map?.(({ identifier }: any) => identifier?.name || '')
|
||||
.join(', ')})__`
|
||||
processedMemory[key] = `__function__`
|
||||
}
|
||||
}
|
||||
return processedMemory
|
||||
|
@ -374,6 +374,7 @@ export default class EditorManager {
|
||||
selectionRanges: this._selectionRanges,
|
||||
isShiftDown: this._isShiftDown,
|
||||
ast: kclManager.ast,
|
||||
artifactGraph: engineCommandManager.artifactGraph,
|
||||
})
|
||||
|
||||
if (!eventInfo) {
|
||||
|
@ -285,7 +285,11 @@ export function complilationErrorsToDiagnostics(
|
||||
name: suggestion.title,
|
||||
apply: (view: EditorView, from: number, to: number) => {
|
||||
view.dispatch({
|
||||
changes: { from, to, insert: suggestion.insert },
|
||||
changes: {
|
||||
from: suggestion.source_range[0],
|
||||
to: suggestion.source_range[1],
|
||||
insert: suggestion.insert,
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
|
@ -717,6 +717,8 @@ export function addHelix({
|
||||
radius,
|
||||
axis,
|
||||
length,
|
||||
insertIndex,
|
||||
variableName,
|
||||
}: {
|
||||
node: Node<Program>
|
||||
revolutions: Expr
|
||||
@ -725,9 +727,12 @@ export function addHelix({
|
||||
radius: Expr
|
||||
axis: string
|
||||
length: Expr
|
||||
insertIndex?: number
|
||||
variableName?: string
|
||||
}): { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||
const modifiedAst = structuredClone(node)
|
||||
const name = findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX)
|
||||
const name =
|
||||
variableName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.HELIX)
|
||||
const variable = createVariableDeclaration(
|
||||
name,
|
||||
createCallExpressionStdLibKw(
|
||||
@ -744,12 +749,20 @@ export function addHelix({
|
||||
)
|
||||
)
|
||||
|
||||
// TODO: figure out smart insertion than just appending at the end
|
||||
const insertAt =
|
||||
insertIndex !== undefined
|
||||
? insertIndex
|
||||
: modifiedAst.body.length
|
||||
? modifiedAst.body.length
|
||||
: 0
|
||||
|
||||
modifiedAst.body.length
|
||||
? modifiedAst.body.splice(insertAt, 0, variable)
|
||||
: modifiedAst.body.push(variable)
|
||||
const argIndex = 0
|
||||
modifiedAst.body.push(variable)
|
||||
const pathToNode: PathToNode = [
|
||||
['body', ''],
|
||||
[modifiedAst.body.length - 1, 'index'],
|
||||
[insertAt, 'index'],
|
||||
['declaration', 'VariableDeclaration'],
|
||||
['init', 'VariableDeclarator'],
|
||||
['arguments', 'CallExpressionKw'],
|
||||
|
@ -397,10 +397,10 @@ export function getEdgeTagCall(
|
||||
return tagCall
|
||||
}
|
||||
|
||||
function locateExtrudeDeclarator(
|
||||
export function locateExtrudeDeclarator(
|
||||
node: Program,
|
||||
pathToExtrudeNode: PathToNode
|
||||
): { extrudeDeclarator: VariableDeclarator } | Error {
|
||||
): { extrudeDeclarator: VariableDeclarator; shallowPath: PathToNode } | Error {
|
||||
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
|
||||
node,
|
||||
pathToExtrudeNode,
|
||||
@ -427,7 +427,7 @@ function locateExtrudeDeclarator(
|
||||
return new Error('Extrude must be a PipeExpression or CallExpression')
|
||||
}
|
||||
|
||||
return { extrudeDeclarator }
|
||||
return { extrudeDeclarator, shallowPath: nodeOfExtrudeCall.shallowPath }
|
||||
}
|
||||
|
||||
function getPathToNodeOfEdgeTreatmentLiteral(
|
||||
|
70
src/lang/modifyAst/setAppearance.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { PathToNode, Program } from 'lang/wasm'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { locateExtrudeDeclarator } from './addEdgeTreatment'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
createCallExpressionStdLibKw,
|
||||
createLabeledArg,
|
||||
createLiteral,
|
||||
createPipeExpression,
|
||||
} from 'lang/modifyAst'
|
||||
import { createPipeSubstitution } from 'lang/modifyAst'
|
||||
import { COMMAND_APPEARANCE_COLOR_DEFAULT } from 'lib/commandBarConfigs/modelingCommandConfig'
|
||||
|
||||
export function setAppearance({
|
||||
ast,
|
||||
nodeToEdit,
|
||||
color,
|
||||
}: {
|
||||
ast: Node<Program>
|
||||
nodeToEdit: PathToNode
|
||||
color: string
|
||||
}): Error | { modifiedAst: Node<Program>; pathToNode: PathToNode } {
|
||||
const modifiedAst = structuredClone(ast)
|
||||
|
||||
// Locate the call (not necessarily an extrude here)
|
||||
const result = locateExtrudeDeclarator(modifiedAst, nodeToEdit)
|
||||
if (err(result)) {
|
||||
return result
|
||||
}
|
||||
|
||||
const declarator = result.extrudeDeclarator
|
||||
const call = createCallExpressionStdLibKw(
|
||||
'appearance',
|
||||
createPipeSubstitution(),
|
||||
[createLabeledArg('color', createLiteral(color))]
|
||||
)
|
||||
// Modify the expression
|
||||
if (
|
||||
declarator.init.type === 'CallExpression' ||
|
||||
declarator.init.type === 'CallExpressionKw'
|
||||
) {
|
||||
// 1. case when no appearance exists, mutate in place
|
||||
declarator.init = createPipeExpression([declarator.init, call])
|
||||
} else if (declarator.init.type === 'PipeExpression') {
|
||||
// 2. case when appearance exists or extrude in sketch pipe
|
||||
const existingIndex = declarator.init.body.findIndex(
|
||||
(v) =>
|
||||
v.type === 'CallExpressionKw' &&
|
||||
v.callee.type === 'Identifier' &&
|
||||
v.callee.name === 'appearance'
|
||||
)
|
||||
if (existingIndex > -1) {
|
||||
if (color === COMMAND_APPEARANCE_COLOR_DEFAULT) {
|
||||
// Special case of unsetting the appearance aka deleting the node
|
||||
declarator.init.body.splice(existingIndex, 1)
|
||||
} else {
|
||||
declarator.init.body[existingIndex] = call
|
||||
}
|
||||
} else {
|
||||
declarator.init.body.push(call)
|
||||
}
|
||||
} else {
|
||||
return new Error('Unsupported operation type.')
|
||||
}
|
||||
|
||||
return {
|
||||
modifiedAst,
|
||||
pathToNode: result.shallowPath,
|
||||
}
|
||||
}
|
@ -31,6 +31,8 @@ import { markOnce } from 'lib/performance'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
import { DefaultPlaneStr } from 'lib/planes'
|
||||
import { defaultPlaneStrToKey } from 'lib/planes'
|
||||
import { buildArtifactIndex } from 'lib/artifactIndex'
|
||||
import { ArtifactIndex } from 'lib/artifactIndex'
|
||||
|
||||
// TODO(paultag): This ought to be tweakable.
|
||||
const pingIntervalMs = 5_000
|
||||
@ -240,6 +242,20 @@ export enum EngineConnectionEvents {
|
||||
NewTrack = 'new-track', // (track: NewTrackArgs) => void
|
||||
}
|
||||
|
||||
function toRTCSessionDescriptionInit(
|
||||
desc: Models['RtcSessionDescription_type']
|
||||
): RTCSessionDescriptionInit | undefined {
|
||||
if (desc.type === 'unspecified') {
|
||||
console.error('Invalid SDP answer: type is "unspecified".')
|
||||
return undefined
|
||||
}
|
||||
return {
|
||||
sdp: desc.sdp,
|
||||
// Force the type to be one of the valid RTCSdpType values
|
||||
type: desc.type as RTCSdpType,
|
||||
}
|
||||
}
|
||||
|
||||
// EngineConnection encapsulates the connection(s) to the Engine
|
||||
// for the EngineCommandManager; namely, the underlying WebSocket
|
||||
// and WebRTC connections.
|
||||
@ -250,7 +266,7 @@ class EngineConnection extends EventTarget {
|
||||
mediaStream?: MediaStream
|
||||
idleMode: boolean = false
|
||||
promise?: Promise<void>
|
||||
sdpAnswer?: Models['RtcSessionDescription_type']
|
||||
sdpAnswer?: RTCSessionDescriptionInit
|
||||
triggeredStart = false
|
||||
|
||||
onIceCandidate = function (
|
||||
@ -549,6 +565,50 @@ class EngineConnection extends EventTarget {
|
||||
this.disconnectAll()
|
||||
}
|
||||
|
||||
initiateConnectionExclusive(): boolean {
|
||||
// Only run if:
|
||||
// - A peer connection exists,
|
||||
// - ICE gathering is complete,
|
||||
// - We have an SDP answer,
|
||||
// - And we haven’t already triggered this connection.
|
||||
if (!this.pc || this.triggeredStart || !this.sdpAnswer) {
|
||||
return false
|
||||
}
|
||||
this.triggeredStart = true
|
||||
|
||||
// Transition to the connecting state
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: { type: ConnectingType.WebRTCConnecting },
|
||||
}
|
||||
|
||||
// Attempt to set the remote description to initiate connection
|
||||
this.pc
|
||||
.setRemoteDescription(this.sdpAnswer)
|
||||
.then(() => {
|
||||
// Update state once the remote description has been set
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: { type: ConnectingType.SetRemoteDescription },
|
||||
}
|
||||
})
|
||||
.catch((error: Error) => {
|
||||
console.error('Failed to set remote description:', error)
|
||||
this.state = {
|
||||
type: EngineConnectionStateType.Disconnecting,
|
||||
value: {
|
||||
type: DisconnectingType.Error,
|
||||
value: {
|
||||
error: ConnectionError.LocalDescriptionInvalid,
|
||||
context: error,
|
||||
},
|
||||
},
|
||||
}
|
||||
this.disconnectAll()
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the Engine over a WebSocket, and
|
||||
* establish the WebRTC connections.
|
||||
@ -588,38 +648,13 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
const initiateConnectingExclusive = () => {
|
||||
if (that.triggeredStart) return
|
||||
that.triggeredStart = true
|
||||
|
||||
// Start connecting.
|
||||
that.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
type: ConnectingType.WebRTCConnecting,
|
||||
},
|
||||
}
|
||||
|
||||
// As soon as this is set, RTCPeerConnection tries to
|
||||
// establish a connection.
|
||||
// @ts-expect-error: Have to ignore because dom.ts doesn't have the right type
|
||||
void that.pc?.setRemoteDescription(that.sdpAnswer)
|
||||
|
||||
that.state = {
|
||||
type: EngineConnectionStateType.Connecting,
|
||||
value: {
|
||||
type: ConnectingType.SetRemoteDescription,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
this.onIceCandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||
console.log('icecandidate', event.candidate)
|
||||
|
||||
// This is null when the ICE gathering state is done.
|
||||
// Windows ONLY uses this to signal it's done!
|
||||
if (event.candidate === null) {
|
||||
initiateConnectingExclusive()
|
||||
that.initiateConnectionExclusive()
|
||||
return
|
||||
}
|
||||
|
||||
@ -643,7 +678,9 @@ class EngineConnection extends EventTarget {
|
||||
// Sometimes the remote end doesn't report the end of candidates.
|
||||
// They have 3 seconds to.
|
||||
setTimeout(() => {
|
||||
initiateConnectingExclusive()
|
||||
if (that.initiateConnectionExclusive()) {
|
||||
console.warn('connected after 3 second delay')
|
||||
}
|
||||
}, 3000)
|
||||
}
|
||||
this.pc?.addEventListener?.('icecandidate', this.onIceCandidate)
|
||||
@ -653,7 +690,7 @@ class EngineConnection extends EventTarget {
|
||||
console.log('icegatheringstatechange', this.iceGatheringState)
|
||||
|
||||
if (this.iceGatheringState !== 'complete') return
|
||||
initiateConnectingExclusive()
|
||||
that.initiateConnectionExclusive()
|
||||
}
|
||||
)
|
||||
|
||||
@ -1192,8 +1229,11 @@ class EngineConnection extends EventTarget {
|
||||
},
|
||||
}
|
||||
|
||||
this.sdpAnswer = answer
|
||||
this.sdpAnswer = toRTCSessionDescriptionInit(answer)
|
||||
|
||||
// We might have received this after ice candidates finish
|
||||
// Make sure we attempt to connect when we do.
|
||||
this.initiateConnectionExclusive()
|
||||
break
|
||||
|
||||
case 'trickle_ice':
|
||||
@ -1369,6 +1409,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
* see: src/lang/std/artifactGraph-README.md for a full explanation.
|
||||
*/
|
||||
artifactGraph: ArtifactGraph = new Map()
|
||||
artifactIndex: ArtifactIndex = []
|
||||
/**
|
||||
* The pendingCommands object is a map of the commands that have been sent to the engine that are still waiting on a reply
|
||||
*/
|
||||
@ -2146,6 +2187,7 @@ export class EngineCommandManager extends EventTarget {
|
||||
}
|
||||
updateArtifactGraph(execStateArtifactGraph: ExecState['artifactGraph']) {
|
||||
this.artifactGraph = execStateArtifactGraph
|
||||
this.artifactIndex = buildArtifactIndex(execStateArtifactGraph)
|
||||
// TODO check if these still need to be deferred once e2e tests are working again.
|
||||
if (this.artifactGraph.size) {
|
||||
this.deferredArtifactEmptied(null)
|
||||
|
29
src/lib/artifactIndex.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { ArtifactGraph, ArtifactId, SourceRange, Artifact } from 'lang/wasm'
|
||||
import { getFaceCodeRef } from 'lang/std/artifactGraph'
|
||||
|
||||
// Index artifacts in an ordered list for binary search
|
||||
export type ArtifactEntry = { artifact: Artifact; id: ArtifactId }
|
||||
/** Index artifacts by their codeRef range, ordered by start position */
|
||||
export type ArtifactIndex = Array<{
|
||||
range: SourceRange
|
||||
entry: ArtifactEntry
|
||||
}>
|
||||
|
||||
/** Creates an array of artifacts, only those with codeRefs, orders them by start range,
|
||||
* to be used later by binary search */
|
||||
export function buildArtifactIndex(
|
||||
artifactGraph: ArtifactGraph
|
||||
): ArtifactIndex {
|
||||
const index: ArtifactIndex = []
|
||||
|
||||
Array.from(artifactGraph).forEach(([id, artifact]) => {
|
||||
const codeRef = getFaceCodeRef(artifact)
|
||||
if (!codeRef?.range) return
|
||||
|
||||
const entry = { artifact, id }
|
||||
index.push({ range: codeRef.range, entry })
|
||||
})
|
||||
|
||||
// Sort by start position for binary search
|
||||
return index.sort((a, b) => a.range[0] - b.range[0])
|
||||
}
|
@ -3,7 +3,11 @@ import { angleLengthInfo } from 'components/Toolbar/setAngleLength'
|
||||
import { transformAstSketchLines } from 'lang/std/sketchcombos'
|
||||
import { PathToNode } from 'lang/wasm'
|
||||
import { StateMachineCommandSetConfig, KclCommandValue } from 'lib/commandTypes'
|
||||
import { KCL_DEFAULT_LENGTH, KCL_DEFAULT_DEGREE } from 'lib/constants'
|
||||
import {
|
||||
KCL_DEFAULT_LENGTH,
|
||||
KCL_DEFAULT_DEGREE,
|
||||
KCL_DEFAULT_COLOR,
|
||||
} from 'lib/constants'
|
||||
import { components } from 'lib/machine-api'
|
||||
import { Selections } from 'lib/selections'
|
||||
import { kclManager } from 'lib/singletons'
|
||||
@ -28,6 +32,8 @@ export const EXTRUSION_RESULTS = [
|
||||
'intersect',
|
||||
] as const
|
||||
|
||||
export const COMMAND_APPEARANCE_COLOR_DEFAULT = 'default'
|
||||
|
||||
export type ModelingCommandSchema = {
|
||||
'Enter sketch': {}
|
||||
Export: {
|
||||
@ -77,6 +83,9 @@ export type ModelingCommandSchema = {
|
||||
distance: KclCommandValue
|
||||
}
|
||||
Helix: {
|
||||
// Enables editing workflow
|
||||
nodeToEdit?: PathToNode
|
||||
// KCL stdlib arguments
|
||||
revolutions: KclCommandValue
|
||||
angleStart: KclCommandValue
|
||||
counterClockWise: boolean
|
||||
@ -107,6 +116,10 @@ export type ModelingCommandSchema = {
|
||||
selection: Selections
|
||||
}
|
||||
'Delete selection': {}
|
||||
Appearance: {
|
||||
nodeToEdit?: PathToNode
|
||||
color: string
|
||||
}
|
||||
}
|
||||
|
||||
export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
@ -462,6 +475,13 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
status: 'development',
|
||||
needsReview: true,
|
||||
args: {
|
||||
nodeToEdit: {
|
||||
description:
|
||||
'Path to the node in the AST to edit. Never shown to the user.',
|
||||
skip: true,
|
||||
inputType: 'text',
|
||||
required: false,
|
||||
},
|
||||
revolutions: {
|
||||
inputType: 'kcl',
|
||||
defaultValue: '1',
|
||||
@ -477,9 +497,10 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
counterClockWise: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
defaultValue: false,
|
||||
options: [
|
||||
{ name: 'True', isCurrent: false, value: true },
|
||||
{ name: 'False', isCurrent: true, value: false },
|
||||
{ name: 'False', value: false },
|
||||
{ name: 'True', value: true },
|
||||
],
|
||||
},
|
||||
radius: {
|
||||
@ -490,10 +511,11 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
axis: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
defaultValue: 'X',
|
||||
options: [
|
||||
{ name: 'X Axis', isCurrent: true, value: 'X' },
|
||||
{ name: 'Y Axis', isCurrent: false, value: 'Y' },
|
||||
{ name: 'Z Axis', isCurrent: false, value: 'Z' },
|
||||
{ name: 'X Axis', value: 'X' },
|
||||
{ name: 'Y Axis', value: 'Y' },
|
||||
{ name: 'Z Axis', value: 'Z' },
|
||||
],
|
||||
},
|
||||
length: {
|
||||
@ -664,4 +686,40 @@ export const modelingMachineCommandConfig: StateMachineCommandSetConfig<
|
||||
},
|
||||
},
|
||||
},
|
||||
Appearance: {
|
||||
description:
|
||||
'Set the appearance of a solid. This only works on solids, not sketches or individual paths.',
|
||||
icon: 'extrude',
|
||||
needsReview: true,
|
||||
args: {
|
||||
nodeToEdit: {
|
||||
description:
|
||||
'Path to the node in the AST to edit. Never shown to the user.',
|
||||
skip: true,
|
||||
inputType: 'text',
|
||||
required: false,
|
||||
},
|
||||
color: {
|
||||
inputType: 'options',
|
||||
required: true,
|
||||
options: [
|
||||
{ name: 'Red', value: '#FF0000' },
|
||||
{ name: 'Green', value: '#00FF00' },
|
||||
{ name: 'Blue', value: '#0000FF' },
|
||||
{ name: 'Turquoise', value: '#00FFFF' },
|
||||
{ name: 'Purple', value: '#FF00FF' },
|
||||
{ name: 'Yellow', value: '#FFFF00' },
|
||||
{ name: 'Black', value: '#000000' },
|
||||
{ name: 'Dark Grey', value: '#080808' },
|
||||
{ name: 'Light Grey', value: '#D3D3D3' },
|
||||
{ name: 'White', value: '#FFFFFF' },
|
||||
{
|
||||
name: 'Default (clear appearance)',
|
||||
value: COMMAND_APPEARANCE_COLOR_DEFAULT,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Add more fields
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -66,6 +66,9 @@ export const KCL_DEFAULT_LENGTH = `5`
|
||||
/** The default KCL degree expression */
|
||||
export const KCL_DEFAULT_DEGREE = `360`
|
||||
|
||||
/** The default KCL color expression */
|
||||
export const KCL_DEFAULT_COLOR = `#3c73ff`
|
||||
|
||||
/** localStorage key for the playwright test-specific app settings file */
|
||||
export const TEST_SETTINGS_FILE_KEY = 'playwright-test-settings'
|
||||
|
||||
|
@ -33,6 +33,7 @@ interface StdLibCallInfo {
|
||||
| ExecuteCommandEventPayload
|
||||
| PrepareToEditCallback
|
||||
| PrepareToEditFailurePayload
|
||||
supportsAppearance?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@ -190,6 +191,114 @@ const prepareToEditOffsetPlane: PrepareToEditCallback = async ({
|
||||
}
|
||||
}
|
||||
|
||||
const prepareToEditHelix: PrepareToEditCallback = async ({ operation }) => {
|
||||
const baseCommand = {
|
||||
name: 'Helix',
|
||||
groupId: 'modeling',
|
||||
}
|
||||
if (operation.type !== 'StdLibCall' || !operation.labeledArgs) {
|
||||
return baseCommand
|
||||
}
|
||||
|
||||
// TODO: find a way to loop over the arguments while keeping it safe
|
||||
// revolutions kcl arg
|
||||
if (
|
||||
!('revolutions' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.revolutions
|
||||
)
|
||||
return baseCommand
|
||||
const revolutions = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.revolutions.sourceRange[0],
|
||||
operation.labeledArgs.revolutions.sourceRange[1]
|
||||
),
|
||||
{}
|
||||
)
|
||||
if (err(revolutions) || 'errors' in revolutions) return baseCommand
|
||||
|
||||
// angleStart kcl arg
|
||||
if (
|
||||
!('angleStart' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.angleStart
|
||||
)
|
||||
return baseCommand
|
||||
const angleStart = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.angleStart.sourceRange[0],
|
||||
operation.labeledArgs.angleStart.sourceRange[1]
|
||||
),
|
||||
{}
|
||||
)
|
||||
if (err(angleStart) || 'errors' in angleStart) return baseCommand
|
||||
|
||||
// counterClockWise options boolean arg
|
||||
if (
|
||||
!('counterClockWise' in operation.labeledArgs) ||
|
||||
!operation.labeledArgs.counterClockWise
|
||||
)
|
||||
return baseCommand
|
||||
const counterClockWise =
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.counterClockWise.sourceRange[0],
|
||||
operation.labeledArgs.counterClockWise.sourceRange[1]
|
||||
) === 'true'
|
||||
|
||||
// radius kcl arg
|
||||
if (!('radius' in operation.labeledArgs) || !operation.labeledArgs.radius)
|
||||
return baseCommand
|
||||
const radius = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.radius.sourceRange[0],
|
||||
operation.labeledArgs.radius.sourceRange[1]
|
||||
),
|
||||
{}
|
||||
)
|
||||
if (err(radius) || 'errors' in radius) return baseCommand
|
||||
|
||||
// axis options string arg
|
||||
if (!('axis' in operation.labeledArgs) || !operation.labeledArgs.axis)
|
||||
return baseCommand
|
||||
const axis = codeManager.code
|
||||
.slice(
|
||||
operation.labeledArgs.axis.sourceRange[0],
|
||||
operation.labeledArgs.axis.sourceRange[1]
|
||||
)
|
||||
.replaceAll("'", '') // TODO: fix this crap
|
||||
|
||||
// length kcl arg
|
||||
if (!('length' in operation.labeledArgs) || !operation.labeledArgs.length)
|
||||
return baseCommand
|
||||
const length = await stringToKclExpression(
|
||||
codeManager.code.slice(
|
||||
operation.labeledArgs.length.sourceRange[0],
|
||||
operation.labeledArgs.length.sourceRange[1]
|
||||
),
|
||||
{}
|
||||
)
|
||||
if (err(length) || 'errors' in length) return baseCommand
|
||||
|
||||
// Assemble the default argument values for the Offset Plane command,
|
||||
// with `nodeToEdit` set, which will let the Offset Plane actor know
|
||||
// to edit the node that corresponds to the StdLibCall.
|
||||
const argDefaultValues: ModelingCommandSchema['Helix'] = {
|
||||
revolutions,
|
||||
angleStart,
|
||||
counterClockWise,
|
||||
radius,
|
||||
axis,
|
||||
length,
|
||||
nodeToEdit: getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRangeFromRust(operation.sourceRange)
|
||||
),
|
||||
}
|
||||
|
||||
return {
|
||||
...baseCommand,
|
||||
argDefaultValues,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A map of standard library calls to their corresponding information
|
||||
* for use in the feature tree UI.
|
||||
@ -204,6 +313,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
label: 'Extrude',
|
||||
icon: 'extrude',
|
||||
prepareToEdit: prepareToEditExtrude,
|
||||
supportsAppearance: true,
|
||||
},
|
||||
fillet: {
|
||||
label: 'Fillet',
|
||||
@ -212,6 +322,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
helix: {
|
||||
label: 'Helix',
|
||||
icon: 'helix',
|
||||
prepareToEdit: prepareToEditHelix,
|
||||
},
|
||||
hole: {
|
||||
label: 'Hole',
|
||||
@ -228,6 +339,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
loft: {
|
||||
label: 'Loft',
|
||||
icon: 'loft',
|
||||
supportsAppearance: true,
|
||||
},
|
||||
offsetPlane: {
|
||||
label: 'Offset Plane',
|
||||
@ -253,10 +365,12 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
revolve: {
|
||||
label: 'Revolve',
|
||||
icon: 'revolve',
|
||||
supportsAppearance: true,
|
||||
},
|
||||
shell: {
|
||||
label: 'Shell',
|
||||
icon: 'shell',
|
||||
supportsAppearance: true,
|
||||
},
|
||||
startSketchOn: {
|
||||
label: 'Sketch',
|
||||
@ -280,6 +394,7 @@ export const stdLibMap: Record<string, StdLibCallInfo> = {
|
||||
sweep: {
|
||||
label: 'Sweep',
|
||||
icon: 'sweep',
|
||||
supportsAppearance: true,
|
||||
},
|
||||
}
|
||||
|
||||
@ -432,3 +547,37 @@ export async function enterEditFlow({
|
||||
'Feature tree editing not yet supported for this operation. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
|
||||
export async function enterAppearanceFlow({
|
||||
operation,
|
||||
artifact,
|
||||
}: EnterEditFlowProps): Promise<Error | CommandBarMachineEvent> {
|
||||
if (operation.type !== 'StdLibCall') {
|
||||
return new Error(
|
||||
'Appearance setting not yet supported for user-defined functions. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
const stdLibInfo = stdLibMap[operation.name]
|
||||
|
||||
if (stdLibInfo && stdLibInfo.supportsAppearance) {
|
||||
const argDefaultValues = {
|
||||
nodeToEdit: getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRangeFromRust(operation.sourceRange)
|
||||
),
|
||||
}
|
||||
console.log('argDefaultValues', argDefaultValues)
|
||||
return {
|
||||
type: 'Find and select command',
|
||||
data: {
|
||||
name: 'Appearance',
|
||||
groupId: 'modeling',
|
||||
argDefaultValues,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return new Error(
|
||||
'Appearance setting not yet supported for this operation. Please edit in the code editor.'
|
||||
)
|
||||
}
|
||||
|
1298
src/lib/selections.test.ts
Normal file
@ -11,6 +11,7 @@ import {
|
||||
Expr,
|
||||
defaultSourceRange,
|
||||
topLevelRange,
|
||||
ArtifactGraph,
|
||||
} from 'lang/wasm'
|
||||
import { ModelingMachineEvent } from 'machines/modelingMachine'
|
||||
import { isNonNullable, uuidv4 } from 'lib/utils'
|
||||
@ -31,19 +32,13 @@ import { PathToNodeMap } from 'lang/std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
import {
|
||||
Artifact,
|
||||
getArtifactOfTypes,
|
||||
getArtifactsOfTypes,
|
||||
getCapCodeRef,
|
||||
getSweepEdgeCodeRef,
|
||||
getSolid2dCodeRef,
|
||||
getWallCodeRef,
|
||||
CodeRef,
|
||||
getCodeRefsByArtifactId,
|
||||
ArtifactId,
|
||||
getFaceCodeRef,
|
||||
} from 'lang/std/artifactGraph'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
import { DefaultPlaneStr } from './planes'
|
||||
import { ArtifactEntry, ArtifactIndex } from './artifactIndex'
|
||||
|
||||
export const X_AXIS_UUID = 'ad792545-7fd3-482a-a602-a93924e3055b'
|
||||
export const Y_AXIS_UUID = '680fd157-266f-4b8a-984f-cdf46b8bdf01'
|
||||
@ -54,38 +49,7 @@ export type DefaultPlaneSelection = {
|
||||
id: string
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link Artifact} instead. */
|
||||
type Selection__old =
|
||||
| {
|
||||
type:
|
||||
| 'default'
|
||||
| 'line-end'
|
||||
| 'line-mid'
|
||||
| 'extrude-wall'
|
||||
| 'solid2d'
|
||||
| 'start-cap'
|
||||
| 'end-cap'
|
||||
| 'point'
|
||||
| 'edge'
|
||||
| 'adjacent-edge'
|
||||
| 'line'
|
||||
| 'arc'
|
||||
| 'all'
|
||||
range: SourceRange
|
||||
}
|
||||
| {
|
||||
type: 'opposite-edgeCut' | 'adjacent-edgeCut' | 'base-edgeCut'
|
||||
range: SourceRange
|
||||
// TODO this is a temporary measure that well be made redundant with: https://github.com/KittyCAD/modeling-app/pull/3836
|
||||
secondaryRange: SourceRange
|
||||
}
|
||||
export type NonCodeSelection = Axis | DefaultPlaneSelection
|
||||
|
||||
/** @deprecated Use {@link Selection} instead. */
|
||||
export type Selections__old = {
|
||||
otherSelections: NonCodeSelection[]
|
||||
codeBasedSelections: Selection__old[]
|
||||
}
|
||||
export interface Selection {
|
||||
artifact?: Artifact
|
||||
codeRef: CodeRef
|
||||
@ -95,76 +59,6 @@ export type Selections = {
|
||||
graphSelections: Array<Selection>
|
||||
}
|
||||
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
function convertSelectionToOld(selection: Selection): Selection__old | null {
|
||||
// return {} as Selection__old
|
||||
// TODO implementation
|
||||
const _artifact = selection.artifact
|
||||
if (_artifact?.type === 'solid2d') {
|
||||
const codeRef = getSolid2dCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'solid2d' }
|
||||
}
|
||||
if (_artifact?.type === 'cap') {
|
||||
const codeRef = getCapCodeRef(_artifact, engineCommandManager.artifactGraph)
|
||||
if (err(codeRef)) return null
|
||||
return {
|
||||
range: codeRef.range,
|
||||
type: _artifact?.subType === 'end' ? 'end-cap' : 'start-cap',
|
||||
}
|
||||
}
|
||||
if (_artifact?.type === 'wall') {
|
||||
const codeRef = getWallCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
return { range: codeRef.range, type: 'extrude-wall' }
|
||||
}
|
||||
if (_artifact?.type === 'segment' || _artifact?.type === 'path') {
|
||||
return { range: _artifact.codeRef.range, type: 'default' }
|
||||
}
|
||||
if (_artifact?.type === 'sweepEdge') {
|
||||
const codeRef = getSweepEdgeCodeRef(
|
||||
_artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(codeRef)) return null
|
||||
if (_artifact?.subType === 'adjacent') {
|
||||
return { range: codeRef.range, type: 'adjacent-edge' }
|
||||
}
|
||||
return { range: codeRef.range, type: 'edge' }
|
||||
}
|
||||
if (_artifact?.type === 'edgeCut') {
|
||||
const codeRef = _artifact.codeRef
|
||||
return { range: codeRef.range, type: 'default' }
|
||||
}
|
||||
if (selection?.codeRef?.range) {
|
||||
return { range: selection.codeRef.range, type: 'default' }
|
||||
}
|
||||
return null
|
||||
}
|
||||
/** @deprecated If you're writing a new function, it should use {@link Selection} and not {@link Selection__old}
|
||||
* this function should only be used for backwards compatibility with old functions.
|
||||
*/
|
||||
export function convertSelectionsToOld(selection: Selections): Selections__old {
|
||||
const selections: Selection__old[] = []
|
||||
for (const artifact of selection.graphSelections) {
|
||||
const converted = convertSelectionToOld(artifact)
|
||||
if (converted) selections.push(converted)
|
||||
}
|
||||
const selectionsOld: Selections__old = {
|
||||
otherSelections: selection.otherSelections,
|
||||
codeBasedSelections: selections,
|
||||
}
|
||||
return selectionsOld
|
||||
}
|
||||
|
||||
export async function getEventForSelectWithPoint({
|
||||
data,
|
||||
}: Extract<
|
||||
@ -310,7 +204,6 @@ export function handleSelectionBatch({
|
||||
selections.graphSelections.forEach(({ artifact }) => {
|
||||
artifact?.id &&
|
||||
selectionToEngine.push({
|
||||
type: 'default',
|
||||
id: artifact?.id,
|
||||
range:
|
||||
getCodeRefsByArtifactId(
|
||||
@ -350,7 +243,6 @@ export function handleSelectionBatch({
|
||||
}
|
||||
|
||||
type SelectionToEngine = {
|
||||
type: Selection__old['type']
|
||||
id?: string
|
||||
range: SourceRange
|
||||
}
|
||||
@ -360,11 +252,13 @@ export function processCodeMirrorRanges({
|
||||
selectionRanges,
|
||||
isShiftDown,
|
||||
ast,
|
||||
artifactGraph,
|
||||
}: {
|
||||
codeMirrorRanges: readonly SelectionRange[]
|
||||
selectionRanges: Selections
|
||||
isShiftDown: boolean
|
||||
ast: Program
|
||||
artifactGraph: ArtifactGraph
|
||||
}): null | {
|
||||
modelingEvent: ModelingMachineEvent
|
||||
engineEvents: Models['WebSocketRequest_type'][]
|
||||
@ -392,8 +286,11 @@ export function processCodeMirrorRanges({
|
||||
},
|
||||
}
|
||||
})
|
||||
const idBasedSelections: SelectionToEngine[] =
|
||||
codeToIdSelections(codeBasedSelections)
|
||||
const idBasedSelections: SelectionToEngine[] = codeToIdSelections(
|
||||
codeBasedSelections,
|
||||
artifactGraph,
|
||||
engineCommandManager.artifactIndex
|
||||
)
|
||||
const selections: Selection[] = []
|
||||
for (const { id, range } of idBasedSelections) {
|
||||
if (!id) {
|
||||
@ -406,11 +303,8 @@ export function processCodeMirrorRanges({
|
||||
})
|
||||
continue
|
||||
}
|
||||
const artifact = engineCommandManager.artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(
|
||||
id,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const artifact = artifactGraph.get(id)
|
||||
const codeRefs = getCodeRefsByArtifactId(id, artifactGraph)
|
||||
if (artifact && codeRefs) {
|
||||
selections.push({ artifact, codeRef: codeRefs[0] })
|
||||
} else if (codeRefs) {
|
||||
@ -601,234 +495,150 @@ export function canSubmitSelectionArg(
|
||||
)
|
||||
}
|
||||
|
||||
export function codeToIdSelections(
|
||||
selections: Selection[]
|
||||
): SelectionToEngine[] {
|
||||
const selectionsOld = convertSelectionsToOld({
|
||||
graphSelections: selections,
|
||||
otherSelections: [],
|
||||
}).codeBasedSelections
|
||||
return selectionsOld
|
||||
.flatMap((selection): null | SelectionToEngine[] => {
|
||||
const { type } = selection
|
||||
// TODO #868: loops over all artifacts will become inefficient at a large scale
|
||||
const overlappingEntries = Array.from(engineCommandManager.artifactGraph)
|
||||
.map(([id, artifact]) => {
|
||||
const codeRef = getFaceCodeRef(artifact)
|
||||
if (!codeRef) return null
|
||||
return isOverlap(codeRef.range, selection.range)
|
||||
? {
|
||||
artifact,
|
||||
selection,
|
||||
id,
|
||||
}
|
||||
: null
|
||||
})
|
||||
.filter(isNonNullable)
|
||||
|
||||
/** TODO refactor
|
||||
* selections in our app is a sourceRange plus some metadata
|
||||
* The metadata is just a union type string of different types of artifacts or 3d features 'extrude-wall' 'segment' etc
|
||||
* Because the source range is not enough to figure out what the user selected, so here we're using filtering through all the artifacts
|
||||
* to find something that matches both the source range and the metadata.
|
||||
*
|
||||
* What we should migrate to is just storing what the user selected by what it matched in the artifactGraph it will simply the below a lot.
|
||||
*
|
||||
* In the case of a user moving the cursor them, we will still need to figure out what artifact from the graph matches best, but we will just need sane defaults
|
||||
* and most of the time we can expect the user to be clicking in the 3d scene instead.
|
||||
/**
|
||||
* Find the index of the last range where range[0] < targetStart
|
||||
* This is used as a starting point for linear search of overlapping ranges
|
||||
* @param index The sorted array of ranges to search through
|
||||
* @param targetStart The start position to compare against
|
||||
* @returns The index of the last range where range[0] < targetStart
|
||||
*/
|
||||
let bestCandidate:
|
||||
| {
|
||||
id: ArtifactId
|
||||
artifact: unknown
|
||||
selection: Selection__old
|
||||
export function findLastRangeStartingBefore(
|
||||
index: ArtifactIndex,
|
||||
targetStart: number
|
||||
): number {
|
||||
let left = 0
|
||||
let right = index.length - 1
|
||||
let lastValidIndex = 0
|
||||
|
||||
while (left <= right) {
|
||||
const mid = left + Math.floor((right - left) / 2)
|
||||
const midRange = index[mid].range
|
||||
|
||||
if (midRange[0] < targetStart) {
|
||||
// This range starts before our selection, look in right half for later ones
|
||||
lastValidIndex = mid
|
||||
left = mid + 1
|
||||
} else {
|
||||
// This range starts at or after our selection, look in left half
|
||||
right = mid - 1
|
||||
}
|
||||
| undefined
|
||||
overlappingEntries.forEach((entry) => {
|
||||
// TODO probably need to remove much of the `type === 'xyz'` below
|
||||
if (type === 'default' && entry.artifact.type === 'segment') {
|
||||
bestCandidate = entry
|
||||
return
|
||||
}
|
||||
|
||||
return lastValidIndex
|
||||
}
|
||||
|
||||
function findOverlappingArtifactsFromIndex(
|
||||
selection: Selection,
|
||||
index: ArtifactIndex
|
||||
): ArtifactEntry[] {
|
||||
if (!selection.codeRef?.range) {
|
||||
console.warn('Selection missing code reference range')
|
||||
return []
|
||||
}
|
||||
|
||||
const selectionRange = selection.codeRef.range
|
||||
const results: ArtifactEntry[] = []
|
||||
|
||||
// Binary search to find the last range where range[0] < selectionRange[0]
|
||||
// This search does not take into consideration the end range, so it's possible
|
||||
// the index it finds dose not have any overlap (depending on the end range)
|
||||
// but it's main purpose is to act as a starting point for the linear part of the search
|
||||
// so a tiny loss in efficiency is acceptable to keep the code simple
|
||||
const startIndex = findLastRangeStartingBefore(index, selectionRange[0])
|
||||
|
||||
// Check all potential overlaps from the found position
|
||||
for (let i = startIndex; i < index.length; i++) {
|
||||
const { range, entry } = index[i]
|
||||
// Stop if we've gone past possible overlaps
|
||||
if (range[0] > selectionRange[1]) break
|
||||
|
||||
if (isOverlap(range, selectionRange)) {
|
||||
results.push(entry)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
function getBestCandidate(
|
||||
entries: ArtifactEntry[],
|
||||
artifactGraph: ArtifactGraph
|
||||
): ArtifactEntry | undefined {
|
||||
if (!entries.length) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
// Segments take precedence
|
||||
if (entry.artifact.type === 'segment') {
|
||||
return entry
|
||||
}
|
||||
|
||||
// Handle paths and their solid2d references
|
||||
if (entry.artifact.type === 'path') {
|
||||
const artifact = engineCommandManager.artifactGraph.get(
|
||||
entry.artifact.solid2dId || ''
|
||||
)
|
||||
if (artifact?.type !== 'solid2d') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
}
|
||||
if (!entry.artifact.solid2dId) {
|
||||
console.error(
|
||||
'Expected PathArtifact to have solid2dId, but none found'
|
||||
)
|
||||
return
|
||||
}
|
||||
bestCandidate = {
|
||||
artifact: artifact,
|
||||
selection,
|
||||
id: entry.artifact.solid2dId,
|
||||
}
|
||||
}
|
||||
if (entry.artifact.type === 'plane') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
}
|
||||
if (entry.artifact.type === 'cap') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
}
|
||||
if (entry.artifact.type === 'wall') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
}
|
||||
if (type === 'extrude-wall' && entry.artifact.type === 'segment') {
|
||||
if (!entry.artifact.surfaceId) return
|
||||
const wall = engineCommandManager.artifactGraph.get(
|
||||
entry.artifact.surfaceId
|
||||
)
|
||||
if (wall?.type !== 'wall') return
|
||||
bestCandidate = {
|
||||
artifact: wall,
|
||||
selection,
|
||||
id: entry.artifact.surfaceId,
|
||||
}
|
||||
return
|
||||
}
|
||||
if (type === 'edge' && entry.artifact.type === 'segment') {
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const edge = [...edges].find(([_, edge]) => edge.type === 'sweepEdge')
|
||||
if (!edge) return
|
||||
bestCandidate = {
|
||||
artifact: edge[1],
|
||||
selection,
|
||||
id: edge[0],
|
||||
}
|
||||
}
|
||||
if (type === 'adjacent-edge' && entry.artifact.type === 'segment') {
|
||||
const edges = getArtifactsOfTypes(
|
||||
{ keys: entry.artifact.edgeIds, types: ['sweepEdge'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const edge = [...edges].find(
|
||||
([_, edge]) =>
|
||||
edge.type === 'sweepEdge' && edge.subType === 'adjacent'
|
||||
)
|
||||
if (!edge) return
|
||||
bestCandidate = {
|
||||
artifact: edge[1],
|
||||
selection,
|
||||
id: edge[0],
|
||||
}
|
||||
}
|
||||
if (
|
||||
(type === 'end-cap' || type === 'start-cap') &&
|
||||
entry.artifact.type === 'path'
|
||||
) {
|
||||
if (!entry.artifact.sweepId) return
|
||||
const extrusion = getArtifactOfTypes(
|
||||
{
|
||||
key: entry.artifact.sweepId,
|
||||
types: ['sweep'],
|
||||
},
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(extrusion)) return
|
||||
const caps = getArtifactsOfTypes(
|
||||
{ keys: extrusion.surfaceIds, types: ['cap'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
const cap = [...caps].find(
|
||||
([_, cap]) => cap.subType === (type === 'end-cap' ? 'end' : 'start')
|
||||
)
|
||||
if (!cap) return
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: cap[0],
|
||||
}
|
||||
return
|
||||
}
|
||||
if (entry.artifact.type === 'edgeCut') {
|
||||
const consumedEdge = getArtifactOfTypes(
|
||||
{
|
||||
key: entry.artifact.consumedEdgeId,
|
||||
types: ['segment', 'sweepEdge'],
|
||||
},
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(consumedEdge)) return
|
||||
if (
|
||||
consumedEdge.type === 'segment' &&
|
||||
type === 'base-edgeCut' &&
|
||||
isOverlap(
|
||||
consumedEdge.codeRef.range,
|
||||
selection.secondaryRange || [0, 0]
|
||||
)
|
||||
) {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
} else if (
|
||||
consumedEdge.type === 'sweepEdge' &&
|
||||
((type === 'adjacent-edgeCut' &&
|
||||
consumedEdge.subType === 'adjacent') ||
|
||||
(type === 'opposite-edgeCut' &&
|
||||
consumedEdge.subType === 'opposite'))
|
||||
) {
|
||||
const seg = getArtifactOfTypes(
|
||||
{ key: consumedEdge.segId, types: ['segment'] },
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(seg)) return
|
||||
if (
|
||||
isOverlap(seg.codeRef.range, selection.secondaryRange || [0, 0])
|
||||
) {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
}
|
||||
const solid2dId = entry.artifact.solid2dId
|
||||
if (!solid2dId) {
|
||||
return entry
|
||||
}
|
||||
const solid2d = artifactGraph.get(solid2dId)
|
||||
if (solid2d?.type === 'solid2d') {
|
||||
return { id: solid2dId, artifact: solid2d }
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (entry.artifact.type === 'sweep') {
|
||||
bestCandidate = {
|
||||
artifact: entry.artifact,
|
||||
selection,
|
||||
id: entry.id,
|
||||
// Other valid artifact types
|
||||
if (['plane', 'cap', 'wall', 'sweep'].includes(entry.artifact.type)) {
|
||||
return entry
|
||||
}
|
||||
}
|
||||
})
|
||||
return undefined
|
||||
}
|
||||
|
||||
if (bestCandidate) {
|
||||
return [
|
||||
{
|
||||
type,
|
||||
id: bestCandidate.id,
|
||||
range: bestCandidate.selection.range,
|
||||
},
|
||||
]
|
||||
function createSelectionToEngine(
|
||||
selection: Selection,
|
||||
candidateId?: ArtifactId
|
||||
): SelectionToEngine {
|
||||
return {
|
||||
...(candidateId && { id: candidateId }),
|
||||
range: selection.codeRef.range,
|
||||
}
|
||||
return [selection]
|
||||
}
|
||||
|
||||
export function codeToIdSelections(
|
||||
selections: Selection[],
|
||||
artifactGraph: ArtifactGraph,
|
||||
artifactIndex: ArtifactIndex
|
||||
): SelectionToEngine[] {
|
||||
if (!selections?.length) {
|
||||
return []
|
||||
}
|
||||
|
||||
if (!artifactGraph) {
|
||||
console.warn('Artifact graph is missing or empty')
|
||||
return selections.map((selection) => createSelectionToEngine(selection))
|
||||
}
|
||||
|
||||
return selections
|
||||
.flatMap((selection): SelectionToEngine[] => {
|
||||
if (!selection) {
|
||||
console.warn('Null or undefined selection encountered')
|
||||
return []
|
||||
}
|
||||
|
||||
// Direct artifact case
|
||||
if (selection.artifact?.id) {
|
||||
return [createSelectionToEngine(selection, selection.artifact.id)]
|
||||
}
|
||||
|
||||
// Find matching artifacts by code range overlap
|
||||
const overlappingEntries = findOverlappingArtifactsFromIndex(
|
||||
selection,
|
||||
artifactIndex
|
||||
)
|
||||
const bestCandidate = getBestCandidate(overlappingEntries, artifactGraph)
|
||||
|
||||
return [createSelectionToEngine(selection, bestCandidate?.id)]
|
||||
})
|
||||
.filter(isNonNullable)
|
||||
}
|
||||
|
@ -312,7 +312,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'sparkles',
|
||||
status: 'available',
|
||||
title: 'Text-to-CAD',
|
||||
title: 'Create with AI',
|
||||
description: 'Generate geometry from a text prompt.',
|
||||
links: [
|
||||
{
|
||||
@ -330,7 +330,7 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
||||
}),
|
||||
icon: 'sparkles',
|
||||
status: 'available',
|
||||
title: 'Prompt-to-Edit',
|
||||
title: 'Edit with AI',
|
||||
description: 'Edit geometry based on a text prompt.',
|
||||
links: [],
|
||||
},
|
||||
|
@ -5,7 +5,6 @@ import {
|
||||
CommandArgumentWithName,
|
||||
KclCommandValue,
|
||||
} from 'lib/commandTypes'
|
||||
import { Selections__old } from 'lib/selections'
|
||||
import { getCommandArgumentKclValuesOnly } from 'lib/commandUtils'
|
||||
import { MachineManager } from 'components/MachineManagerProvider'
|
||||
import toast from 'react-hot-toast'
|
||||
@ -16,7 +15,6 @@ export type CommandBarContext = {
|
||||
commands: Command[]
|
||||
selectedCommand?: Command
|
||||
currentArgument?: CommandArgument<unknown> & { name: string }
|
||||
selectionRanges: Selections__old
|
||||
argumentsToSubmit: { [x: string]: unknown }
|
||||
machineManager: MachineManager
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ import {
|
||||
import { getPathsFromPlaneArtifact } from 'lang/std/artifactGraph'
|
||||
import { createProfileStartHandle } from 'clientSideScene/segments'
|
||||
import { DRAFT_POINT } from 'clientSideScene/sceneInfra'
|
||||
import { setAppearance } from 'lang/modifyAst/setAppearance'
|
||||
|
||||
export const MODELING_PERSIST_KEY = 'MODELING_PERSIST_KEY'
|
||||
|
||||
@ -314,6 +315,7 @@ export type ModelingMachineEvent =
|
||||
type: 'Delete selection'
|
||||
data: ModelingCommandSchema['Delete selection']
|
||||
}
|
||||
| { type: 'Appearance'; data: ModelingCommandSchema['Appearance'] }
|
||||
| {
|
||||
type: 'Add rectangle origin'
|
||||
data: [x: number, y: number]
|
||||
@ -1799,8 +1801,32 @@ export const modelingMachine = setup({
|
||||
radius,
|
||||
axis,
|
||||
length,
|
||||
nodeToEdit,
|
||||
} = input
|
||||
|
||||
let opInsertIndex: number | undefined = undefined
|
||||
let opVariableName: string | undefined = undefined
|
||||
|
||||
// If this is an edit flow, first we're going to remove the old one
|
||||
if (nodeToEdit && typeof nodeToEdit[1][0] === 'number') {
|
||||
// Extract the old name from the node to edit
|
||||
const oldNode = getNodeFromPath<VariableDeclaration>(
|
||||
ast,
|
||||
nodeToEdit,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(oldNode)) {
|
||||
console.error('Error extracting plane name')
|
||||
} else {
|
||||
opVariableName = oldNode.node.declaration.id.name
|
||||
}
|
||||
|
||||
const newBody = [...ast.body]
|
||||
newBody.splice(nodeToEdit[1][0], 1)
|
||||
ast.body = newBody
|
||||
opInsertIndex = nodeToEdit[1][0]
|
||||
}
|
||||
|
||||
for (const variable of [revolutions, angleStart, radius, length]) {
|
||||
// Insert the variable if it exists
|
||||
if (
|
||||
@ -1831,6 +1857,8 @@ export const modelingMachine = setup({
|
||||
radius: valueOrVariable(radius),
|
||||
axis,
|
||||
length: valueOrVariable(length),
|
||||
insertIndex: opInsertIndex,
|
||||
variableName: opVariableName,
|
||||
})
|
||||
|
||||
const updateAstResult = await kclManager.updateAst(
|
||||
@ -2172,6 +2200,47 @@ export const modelingMachine = setup({
|
||||
})
|
||||
}
|
||||
),
|
||||
appearanceAstMod: fromPromise(
|
||||
async ({
|
||||
input,
|
||||
}: {
|
||||
input: ModelingCommandSchema['Appearance'] | undefined
|
||||
}) => {
|
||||
if (!input) return new Error('No input provided')
|
||||
// Extract inputs
|
||||
const ast = kclManager.ast
|
||||
const { color, nodeToEdit } = input
|
||||
if (!(nodeToEdit && typeof nodeToEdit[1][0] === 'number')) {
|
||||
return new Error('Appearance is only an edit flow')
|
||||
}
|
||||
|
||||
const result = setAppearance({
|
||||
ast,
|
||||
nodeToEdit,
|
||||
color,
|
||||
})
|
||||
|
||||
if (err(result)) {
|
||||
return err(result)
|
||||
}
|
||||
|
||||
const updateAstResult = await kclManager.updateAst(
|
||||
result.modifiedAst,
|
||||
true,
|
||||
{
|
||||
focusPath: [result.pathToNode],
|
||||
}
|
||||
)
|
||||
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||
updateAstResult.newAst
|
||||
)
|
||||
|
||||
if (updateAstResult?.selections) {
|
||||
editorManager.selectRange(updateAstResult?.selections)
|
||||
}
|
||||
}
|
||||
),
|
||||
},
|
||||
// end actors
|
||||
}).createMachine({
|
||||
@ -2267,6 +2336,11 @@ export const modelingMachine = setup({
|
||||
},
|
||||
|
||||
'Prompt-to-edit': 'Applying Prompt-to-edit',
|
||||
|
||||
Appearance: {
|
||||
target: 'Applying appearance',
|
||||
reenter: true,
|
||||
},
|
||||
},
|
||||
|
||||
entry: 'reset client scene mouse handlers',
|
||||
@ -3389,6 +3463,19 @@ export const modelingMachine = setup({
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
'Applying appearance': {
|
||||
invoke: {
|
||||
src: 'appearanceAstMod',
|
||||
id: 'appearanceAstMod',
|
||||
input: ({ event }) => {
|
||||
if (event.type !== 'Appearance') return undefined
|
||||
return event.data
|
||||
},
|
||||
onDone: ['idle'],
|
||||
onError: ['idle'],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
initial: 'idle',
|
||||
|
@ -538,7 +538,16 @@ export const settingsMachine = setup({
|
||||
src: 'loadUserSettings',
|
||||
onDone: {
|
||||
target: 'idle',
|
||||
actions: 'setAllSettings',
|
||||
actions: [
|
||||
'setAllSettings',
|
||||
'setThemeClass',
|
||||
'setEngineTheme',
|
||||
'setClientSideSceneUnits',
|
||||
'setThemeColor',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
'sendThemeToWatcher',
|
||||
],
|
||||
},
|
||||
onError: {
|
||||
target: 'idle',
|
||||
@ -561,8 +570,14 @@ export const settingsMachine = setup({
|
||||
target: 'idle',
|
||||
actions: [
|
||||
'setAllSettings',
|
||||
'setThemeClass',
|
||||
'setEngineTheme',
|
||||
'setClientSideSceneUnits',
|
||||
'setThemeColor',
|
||||
'Execute AST',
|
||||
'setClientTheme',
|
||||
'setAllowOrbitInSketchMode',
|
||||
'sendThemeToWatcher',
|
||||
sendTo('registerCommands', { type: 'update' }),
|
||||
],
|
||||
},
|
||||
|
24
src/wasm-lib/Cargo.lock
generated
@ -122,9 +122,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.95"
|
||||
version = "1.0.96"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
|
||||
checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
@ -1858,9 +1858,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kittycad-modeling-cmds"
|
||||
version = "0.2.97"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c37ad10b8a2afdcd1852d027f123cf4e38864ea93e0fda5c7ee1e8a49af49fb"
|
||||
checksum = "828a0c74476533e6258ea7dd70cfc7d63a5df4b37753d30ef198e0689eaac4eb"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
@ -3245,9 +3245,9 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
||||
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
@ -3263,9 +3263,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.217"
|
||||
version = "1.0.218"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
||||
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -3285,9 +3285,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.138"
|
||||
version = "1.0.139"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
||||
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||
dependencies = [
|
||||
"indexmap 2.7.1",
|
||||
"itoa",
|
||||
@ -4208,9 +4208,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.13.1"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
|
||||
checksum = "93d59ca99a559661b96bf898d8fce28ed87935fd2bea9f05983c1464dd6c71b1"
|
||||
dependencies = [
|
||||
"getrandom 0.3.1",
|
||||
"js-sys",
|
||||
|
@ -80,7 +80,7 @@ members = [
|
||||
[workspace.dependencies]
|
||||
http = "1"
|
||||
kittycad = { version = "0.3.28", default-features = false, features = ["js", "requests"] }
|
||||
kittycad-modeling-cmds = { version = "0.2.97", features = [
|
||||
kittycad-modeling-cmds = { version = "0.2.99", features = [
|
||||
"ts-rs",
|
||||
"websocket",
|
||||
] }
|
||||
|
@ -9,7 +9,7 @@ mod unbox;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use convert_case::Casing;
|
||||
use inflector::Inflector;
|
||||
use inflector::{cases::camelcase::to_camel_case, Inflector};
|
||||
use once_cell::sync::Lazy;
|
||||
use quote::{format_ident, quote, quote_spanned, ToTokens};
|
||||
use regex::Regex;
|
||||
@ -326,13 +326,14 @@ fn do_stdlib_inner(
|
||||
};
|
||||
let include_in_snippet = required || arg_meta.map(|arg| arg.include_in_snippet).unwrap_or_default();
|
||||
let label_required = !(i == 0 && metadata.unlabeled_first);
|
||||
let camel_case_arg_name = to_camel_case(&arg_name);
|
||||
if ty_string != "ExecState" && ty_string != "Args" {
|
||||
let schema = quote! {
|
||||
#docs_crate::cleanup_number_tuples_root(generator.root_schema_for::<#ty_ident>())
|
||||
};
|
||||
arg_types.push(quote! {
|
||||
#docs_crate::StdLibFnArg {
|
||||
name: #arg_name.to_string(),
|
||||
name: #camel_case_arg_name.to_string(),
|
||||
type_: #ty_string.to_string(),
|
||||
schema: #schema,
|
||||
required: #required,
|
||||
|
@ -457,7 +457,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String) -> Result<()> {
|
||||
});
|
||||
|
||||
let output = hbs.render("const", &data)?;
|
||||
expectorate::assert_contents(format!("../../../docs/kcl/const_{}.md", file_name), &output);
|
||||
expectorate::assert_contents(format!("../../../docs/kcl/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -49,12 +49,15 @@ impl CollectionVisitor {
|
||||
}
|
||||
}
|
||||
crate::parsing::ast::types::BodyItem::VariableDeclaration(var) if !var.visibility.is_default() => {
|
||||
let qual_name = if self.name == "prelude" {
|
||||
"std::".to_owned()
|
||||
} else {
|
||||
format!("std::{}::", self.name)
|
||||
};
|
||||
let mut dd = match var.kind {
|
||||
// TODO metadata for args
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, format!("std::{}::", self.name))),
|
||||
VariableKind::Const => {
|
||||
DocData::Const(ConstData::from_ast(var, format!("std::{}::", self.name)))
|
||||
}
|
||||
VariableKind::Fn => DocData::Fn(FnData::from_ast(var, qual_name)),
|
||||
VariableKind::Const => DocData::Const(ConstData::from_ast(var, qual_name)),
|
||||
};
|
||||
|
||||
// FIXME this association of metadata with items is pretty flaky.
|
||||
@ -359,6 +362,7 @@ impl FnData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn to_autocomplete_snippet(&self) -> String {
|
||||
if self.name == "loft" {
|
||||
return "loft([${0:sketch000}, ${1:sketch001}])${}".to_owned();
|
||||
|
@ -473,6 +473,7 @@ pub trait StdLibFn: std::fmt::Debug + Send + Sync {
|
||||
})
|
||||
}
|
||||
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn to_autocomplete_snippet(&self) -> Result<String> {
|
||||
if self.name() == "loft" {
|
||||
return Ok("loft([${0:sketch000}, ${1:sketch001}])${}".to_string());
|
||||
@ -926,6 +927,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_line() {
|
||||
let line_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Line);
|
||||
let snippet = line_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -933,6 +935,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_extrude() {
|
||||
let extrude_fn: Box<dyn StdLibFn> = Box::new(crate::std::extrude::Extrude);
|
||||
let snippet = extrude_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -940,6 +943,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_fillet() {
|
||||
let fillet_fn: Box<dyn StdLibFn> = Box::new(crate::std::fillet::Fillet);
|
||||
let snippet = fillet_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -957,13 +961,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_pattern_circular_3d() {
|
||||
// We test this one specifically because it has ints and floats and strings.
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternCircular3D);
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arc_degrees = ${8:3.14}, rotate_duplicates = ${9:false})${}"#
|
||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
@ -980,6 +985,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_circle() {
|
||||
let circle_fn: Box<dyn StdLibFn> = Box::new(crate::std::shapes::Circle);
|
||||
let snippet = circle_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -993,6 +999,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_arc() {
|
||||
let arc_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Arc);
|
||||
let snippet = arc_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1014,6 +1021,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_pattern_linear_2d() {
|
||||
let pattern_fn: Box<dyn StdLibFn> = Box::new(crate::std::patterns::PatternLinear2D);
|
||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1034,6 +1042,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_loft() {
|
||||
let loft_fn: Box<dyn StdLibFn> = Box::new(crate::std::loft::Loft);
|
||||
let snippet = loft_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1041,6 +1050,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_sweep() {
|
||||
let sweep_fn: Box<dyn StdLibFn> = Box::new(crate::std::sweep::Sweep);
|
||||
let snippet = sweep_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1048,6 +1058,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_hole() {
|
||||
let hole_fn: Box<dyn StdLibFn> = Box::new(crate::std::sketch::Hole);
|
||||
let snippet = hole_fn.to_autocomplete_snippet().unwrap();
|
||||
@ -1055,16 +1066,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_helix() {
|
||||
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::Helix);
|
||||
let snippet = helix_fn.to_autocomplete_snippet().unwrap();
|
||||
assert_eq!(
|
||||
snippet,
|
||||
r#"helix(revolutions = ${0:3.14}, angle_start = ${1:3.14}, radius = ${2:3.14}, axis = ${3:"X"}, length = ${4:3.14})${}"#
|
||||
r#"helix(revolutions = ${0:3.14}, angleStart = ${1:3.14}, radius = ${2:3.14}, axis = ${3:"X"}, length = ${4:3.14})${}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::literal_string_with_formatting_args)]
|
||||
fn get_autocomplete_snippet_helix_revolutions() {
|
||||
let helix_fn: Box<dyn StdLibFn> = Box::new(crate::std::helix::HelixRevolutions);
|
||||
let snippet = helix_fn.to_autocomplete_snippet().unwrap();
|
||||
|
@ -402,12 +402,15 @@ impl CompilationError {
|
||||
self,
|
||||
suggestion_title: impl ToString,
|
||||
suggestion_insert: impl ToString,
|
||||
// Will use the error source range if none is supplied
|
||||
source_range: Option<SourceRange>,
|
||||
tag: Tag,
|
||||
) -> CompilationError {
|
||||
CompilationError {
|
||||
suggestion: Some(Suggestion {
|
||||
title: suggestion_title.to_string(),
|
||||
insert: suggestion_insert.to_string(),
|
||||
source_range: source_range.unwrap_or(self.source_range),
|
||||
}),
|
||||
tag,
|
||||
..self
|
||||
@ -419,9 +422,9 @@ impl CompilationError {
|
||||
let suggestion = self.suggestion.as_ref()?;
|
||||
Some(format!(
|
||||
"{}{}{}",
|
||||
&src[0..self.source_range.start()],
|
||||
&src[0..suggestion.source_range.start()],
|
||||
suggestion.insert,
|
||||
&src[self.source_range.end()..]
|
||||
&src[suggestion.source_range.end()..]
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -465,4 +468,5 @@ pub enum Tag {
|
||||
pub struct Suggestion {
|
||||
pub title: String,
|
||||
pub insert: String,
|
||||
pub source_range: SourceRange,
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ pub(super) const SIGNIFICANT_ATTRS: [&str; 2] = [SETTINGS, NO_PRELUDE];
|
||||
pub(crate) const SETTINGS: &str = "settings";
|
||||
pub(crate) const SETTINGS_UNIT_LENGTH: &str = "defaultLengthUnit";
|
||||
pub(crate) const SETTINGS_UNIT_ANGLE: &str = "defaultAngleUnit";
|
||||
pub(super) const NO_PRELUDE: &str = "no_prelude";
|
||||
pub(super) const NO_PRELUDE: &str = "no_std";
|
||||
|
||||
pub(super) const IMPORT_FORMAT: &str = "format";
|
||||
pub(super) const IMPORT_FORMAT_VALUES: [&str; 9] = ["fbx", "gltf", "glb", "obj", "ply", "sldprt", "stp", "step", "stl"];
|
||||
|
@ -6,7 +6,7 @@ use itertools::{EitherOrBoth, Itertools};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
execution::{annotations, memory::ProgramMemory, ExecState, ExecutorSettings},
|
||||
execution::{annotations, memory::ProgramMemory, EnvironmentRef, ExecState, ExecutorSettings},
|
||||
parsing::ast::types::{Annotation, Node, Program},
|
||||
walk::Node as WalkNode,
|
||||
};
|
||||
@ -65,6 +65,7 @@ pub struct OldAstState {
|
||||
pub exec_state: ExecState,
|
||||
/// The last settings used for execution.
|
||||
pub settings: crate::execution::ExecutorSettings,
|
||||
pub result_env: EnvironmentRef,
|
||||
}
|
||||
|
||||
/// The result of a cache check.
|
||||
@ -267,7 +268,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(new).await.unwrap();
|
||||
let (program, _, ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
let result = get_changed_program(
|
||||
CacheInformation {
|
||||
@ -310,7 +311,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program_old, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let (program_old, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
@ -355,7 +356,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let (program, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
@ -404,7 +405,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(old).await.unwrap();
|
||||
let (program, _, ctx, _) = parse_execute(old).await.unwrap();
|
||||
|
||||
let program_new = crate::Program::parse_no_errs(new).unwrap();
|
||||
|
||||
@ -438,7 +439,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings to cm.
|
||||
ctx.settings.units = crate::UnitLength::Cm;
|
||||
@ -480,7 +481,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings.
|
||||
ctx.settings.show_grid = !ctx.settings.show_grid;
|
||||
@ -515,7 +516,7 @@ firstSketch = startSketchOn('XY')
|
||||
// Remove the end face for the extrusion.
|
||||
shell(firstSketch, faces = ['end'], thickness = 0.25)"#;
|
||||
|
||||
let (program, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
let (program, _, mut ctx, _) = parse_execute(new).await.unwrap();
|
||||
|
||||
// Change the settings.
|
||||
ctx.settings.highlight_edges = !ctx.settings.highlight_edges;
|
||||
@ -582,7 +583,7 @@ startSketchOn('XY')
|
||||
startSketchOn('XY')
|
||||
"#;
|
||||
|
||||
let (program, ctx, _) = parse_execute(old_code).await.unwrap();
|
||||
let (program, _, ctx, _) = parse_execute(old_code).await.unwrap();
|
||||
|
||||
let mut new_program = crate::Program::parse_no_errs(new_code).unwrap();
|
||||
new_program.compute_digest();
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_recursion::async_recursion;
|
||||
use schemars::JsonSchema;
|
||||
|
||||
use crate::{
|
||||
engine::ExecutionKind,
|
||||
@ -9,7 +8,7 @@ use crate::{
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::{OpArg, OpKclValue, Operation},
|
||||
kcl_value::NumericType,
|
||||
kcl_value::{FunctionSource, NumericType},
|
||||
memory,
|
||||
state::ModuleState,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, TagEngineInfo, TagIdentifier,
|
||||
@ -54,7 +53,7 @@ impl ExecutorContext {
|
||||
let mut no_prelude = false;
|
||||
for annotation in annotations {
|
||||
if annotation.name() == Some(annotations::SETTINGS) {
|
||||
if matches!(body_type, BodyType::Root(_)) {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
let old_units = exec_state.length_unit();
|
||||
exec_state.mod_local.settings.update_from_annotation(annotation)?;
|
||||
let new_units = exec_state.length_unit();
|
||||
@ -70,12 +69,12 @@ impl ExecutorContext {
|
||||
));
|
||||
}
|
||||
} else if annotation.name() == Some(annotations::NO_PRELUDE) {
|
||||
if matches!(body_type, BodyType::Root(_)) {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
no_prelude = true;
|
||||
} else {
|
||||
exec_state.err(CompilationError::err(
|
||||
annotation.as_source_range(),
|
||||
"Prelude can only be skipped at the top level scope of a file",
|
||||
"The standard library can only be skipped at the top level scope of a file",
|
||||
));
|
||||
}
|
||||
} else {
|
||||
@ -88,51 +87,56 @@ impl ExecutorContext {
|
||||
Ok(no_prelude)
|
||||
}
|
||||
|
||||
pub(super) async fn exec_module_body(
|
||||
&self,
|
||||
program: &Node<Program>,
|
||||
exec_state: &mut ExecState,
|
||||
exec_kind: ExecutionKind,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef), KclError> {
|
||||
let old_units = exec_state.length_unit();
|
||||
|
||||
let no_prelude = self
|
||||
.handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
|
||||
.await?;
|
||||
|
||||
if !preserve_mem {
|
||||
exec_state.mut_memory().push_new_root_env(!no_prelude);
|
||||
}
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
|
||||
|
||||
let result = self
|
||||
.exec_block(program, exec_state, crate::execution::BodyType::Root)
|
||||
.await;
|
||||
|
||||
let new_units = exec_state.length_unit();
|
||||
let env_ref = if preserve_mem {
|
||||
exec_state.mut_memory().pop_and_preserve_env()
|
||||
} else {
|
||||
exec_state.mut_memory().pop_env()
|
||||
};
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution).await;
|
||||
|
||||
result.map(|result| (result, env_ref))
|
||||
}
|
||||
|
||||
/// Execute an AST's program.
|
||||
#[async_recursion]
|
||||
pub(super) async fn exec_program<'a>(
|
||||
pub(super) async fn exec_block<'a>(
|
||||
&'a self,
|
||||
program: NodeRef<'a, crate::parsing::ast::types::Program>,
|
||||
exec_state: &mut ExecState,
|
||||
body_type: BodyType,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let no_prelude = self
|
||||
.handle_annotations(program.inner_attrs.iter(), body_type, exec_state)
|
||||
.await?;
|
||||
|
||||
if !no_prelude && body_type == BodyType::Root(true) {
|
||||
// Import std::prelude
|
||||
let prelude_range = SourceRange::from(program).start_as_range();
|
||||
let id = self
|
||||
.open_module(
|
||||
&ImportPath::Std {
|
||||
path: vec!["std".to_owned(), "prelude".to_owned()],
|
||||
},
|
||||
&[],
|
||||
exec_state,
|
||||
prelude_range,
|
||||
)
|
||||
.await?;
|
||||
let (module_memory, module_exports) = self
|
||||
.exec_module_for_items(id, exec_state, ExecutionKind::Isolated, prelude_range)
|
||||
.await
|
||||
.unwrap();
|
||||
for name in module_exports {
|
||||
let item = exec_state
|
||||
.memory()
|
||||
.get_from(&name, module_memory, prelude_range)
|
||||
.cloned()
|
||||
.unwrap();
|
||||
exec_state.mut_memory().add(name, item, prelude_range)?;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_expr = None;
|
||||
// Iterate over the body of the program.
|
||||
for statement in &program.body {
|
||||
match statement {
|
||||
BodyItem::ImportStatement(import_stmt) => {
|
||||
if !matches!(body_type, BodyType::Root(_)) {
|
||||
if !matches!(body_type, BodyType::Root) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Imports are only supported at the top-level of a file.".to_owned(),
|
||||
source_ranges: vec![import_stmt.into()],
|
||||
@ -263,7 +267,7 @@ impl ExecutorContext {
|
||||
BodyItem::ReturnStatement(return_statement) => {
|
||||
let metadata = Metadata::from(return_statement);
|
||||
|
||||
if matches!(body_type, BodyType::Root(_)) {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "Cannot return from outside a function.".to_owned(),
|
||||
source_ranges: vec![metadata.source_range],
|
||||
@ -293,7 +297,7 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
if matches!(body_type, BodyType::Root(_)) {
|
||||
if matches!(body_type, BodyType::Root) {
|
||||
// Flush the batch queue.
|
||||
self.engine
|
||||
.flush_batch(
|
||||
@ -308,7 +312,7 @@ impl ExecutorContext {
|
||||
Ok(last_expr)
|
||||
}
|
||||
|
||||
async fn open_module(
|
||||
pub(super) async fn open_module(
|
||||
&self,
|
||||
path: &ImportPath,
|
||||
attrs: &[Node<Annotation>],
|
||||
@ -358,7 +362,7 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
async fn exec_module_for_items(
|
||||
pub(super) async fn exec_module_for_items(
|
||||
&self,
|
||||
module_id: ModuleId,
|
||||
exec_state: &mut ExecState,
|
||||
@ -425,25 +429,14 @@ impl ExecutorContext {
|
||||
exec_kind: ExecutionKind,
|
||||
source_range: SourceRange,
|
||||
) -> Result<(Option<KclValue>, EnvironmentRef, Vec<String>), KclError> {
|
||||
let old_units = exec_state.length_unit();
|
||||
let mut local_state = ModuleState::new(&self.settings, path.std_path());
|
||||
exec_state.global.mod_loader.enter_module(path);
|
||||
let mut local_state = ModuleState::new(&self.settings, path.std_path());
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
exec_state.mut_memory().push_new_root_env();
|
||||
let original_execution = self.engine.replace_execution_kind(exec_kind).await;
|
||||
|
||||
let result = self
|
||||
.exec_program(program, exec_state, crate::execution::BodyType::Root(true))
|
||||
.await;
|
||||
let result = self.exec_module_body(program, exec_state, exec_kind, false).await;
|
||||
|
||||
let new_units = exec_state.length_unit();
|
||||
std::mem::swap(&mut exec_state.mod_local, &mut local_state);
|
||||
let env_ref = exec_state.mut_memory().pop_env();
|
||||
exec_state.global.mod_loader.leave_module(path);
|
||||
if !exec_kind.is_isolated() && new_units != old_units {
|
||||
self.engine.set_units(old_units.into(), Default::default()).await?;
|
||||
}
|
||||
self.engine.replace_execution_kind(original_execution).await;
|
||||
|
||||
result
|
||||
.map_err(|err| {
|
||||
@ -461,7 +454,7 @@ impl ExecutorContext {
|
||||
})
|
||||
}
|
||||
})
|
||||
.map(|result| (result, env_ref, local_state.module_exports))
|
||||
.map(|(val, env)| (val, env, local_state.module_exports))
|
||||
}
|
||||
|
||||
#[async_recursion]
|
||||
@ -520,11 +513,10 @@ impl ExecutorContext {
|
||||
|
||||
if rust_impl {
|
||||
if let Some(std_path) = &exec_state.mod_local.settings.std_path {
|
||||
let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
|
||||
KclValue::Function {
|
||||
expression: function_expression.clone(),
|
||||
value: FunctionSource::Std { func, props },
|
||||
meta: vec![metadata.to_owned()],
|
||||
func: Some(crate::std::std_fn(std_path, statement_kind.expect_name())),
|
||||
memory: None,
|
||||
}
|
||||
} else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -534,14 +526,15 @@ impl ExecutorContext {
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// Cloning memory here is crucial for semantics so that we close
|
||||
// Snapshotting memory here is crucial for semantics so that we close
|
||||
// over variables. Variables defined lexically later shouldn't
|
||||
// be available to the function body.
|
||||
KclValue::Function {
|
||||
expression: function_expression.clone(),
|
||||
value: FunctionSource::User {
|
||||
ast: function_expression.clone(),
|
||||
memory: exec_state.mut_memory().snapshot(),
|
||||
},
|
||||
meta: vec![metadata.to_owned()],
|
||||
func: None,
|
||||
memory: Some(exec_state.mut_memory().snapshot()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -996,6 +989,12 @@ impl Node<CallExpressionKw> {
|
||||
);
|
||||
match ctx.stdlib.get_either(fn_name) {
|
||||
FunctionKind::Core(func) => {
|
||||
if func.deprecated() {
|
||||
exec_state.warn(CompilationError::err(
|
||||
self.callee.as_source_range(),
|
||||
format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
|
||||
));
|
||||
}
|
||||
let op = if func.feature_tree_operation() {
|
||||
let op_labeled_args = args
|
||||
.kw_args
|
||||
@ -1120,6 +1119,12 @@ impl Node<CallExpression> {
|
||||
|
||||
match ctx.stdlib.get_either(fn_name) {
|
||||
FunctionKind::Core(func) => {
|
||||
if func.deprecated() {
|
||||
exec_state.warn(CompilationError::err(
|
||||
self.callee.as_source_range(),
|
||||
format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
|
||||
));
|
||||
}
|
||||
let op = if func.feature_tree_operation() {
|
||||
let op_labeled_args = func
|
||||
.args(false)
|
||||
@ -1207,7 +1212,7 @@ impl Node<CallExpression> {
|
||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||
};
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||
message: format!("Result of function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
})
|
||||
})?;
|
||||
@ -1472,7 +1477,7 @@ impl Node<IfExpression> {
|
||||
.await?
|
||||
.get_bool()?;
|
||||
if cond {
|
||||
let block_result = ctx.exec_program(&self.then_val, exec_state, BodyType::Block).await?;
|
||||
let block_result = ctx.exec_block(&self.then_val, exec_state, BodyType::Block).await?;
|
||||
// Block must end in an expression, so this has to be Some.
|
||||
// Enforced by the parser.
|
||||
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
@ -1492,7 +1497,7 @@ impl Node<IfExpression> {
|
||||
.await?
|
||||
.get_bool()?;
|
||||
if cond {
|
||||
let block_result = ctx.exec_program(&else_if.then_val, exec_state, BodyType::Block).await?;
|
||||
let block_result = ctx.exec_block(&else_if.then_val, exec_state, BodyType::Block).await?;
|
||||
// Block must end in an expression, so this has to be Some.
|
||||
// Enforced by the parser.
|
||||
// See https://github.com/KittyCAD/modeling-app/issues/4015
|
||||
@ -1501,7 +1506,7 @@ impl Node<IfExpression> {
|
||||
}
|
||||
|
||||
// Run the final `else` branch.
|
||||
ctx.exec_program(&self.final_else, exec_state, BodyType::Block)
|
||||
ctx.exec_block(&self.final_else, exec_state, BodyType::Block)
|
||||
.await
|
||||
.map(|expr| expr.unwrap())
|
||||
}
|
||||
@ -1734,7 +1739,7 @@ pub(crate) async fn call_user_defined_function(
|
||||
|
||||
// Execute the function body using the memory we just created.
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.exec_block(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
let result = result.map(|_| {
|
||||
exec_state
|
||||
@ -1767,7 +1772,7 @@ pub(crate) async fn call_user_defined_function_kw(
|
||||
|
||||
// Execute the function body using the memory we just created.
|
||||
let result = ctx
|
||||
.exec_program(&function_expression.body, exec_state, BodyType::Block)
|
||||
.exec_block(&function_expression.body, exec_state, BodyType::Block)
|
||||
.await;
|
||||
let result = result.map(|_| {
|
||||
exec_state
|
||||
@ -1782,45 +1787,39 @@ pub(crate) async fn call_user_defined_function_kw(
|
||||
result
|
||||
}
|
||||
|
||||
/// A function being used as a parameter into a stdlib function. This is a
|
||||
/// closure, plus everything needed to execute it.
|
||||
pub struct FunctionParam<'a> {
|
||||
pub inner: Option<&'a crate::std::StdFn>,
|
||||
pub memory: Option<EnvironmentRef>,
|
||||
pub fn_expr: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
pub ctx: ExecutorContext,
|
||||
}
|
||||
|
||||
impl FunctionParam<'_> {
|
||||
impl FunctionSource {
|
||||
pub async fn call(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
args: Vec<Arg>,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
if let Some(inner) = self.inner {
|
||||
match self {
|
||||
FunctionSource::Std { func, props } => {
|
||||
if props.deprecated {
|
||||
exec_state.warn(CompilationError::err(
|
||||
source_range,
|
||||
format!(
|
||||
"`{}` is deprecated, see the docs for a recommended replacement",
|
||||
props.name
|
||||
),
|
||||
));
|
||||
}
|
||||
let args = crate::std::Args::new(
|
||||
args,
|
||||
source_range,
|
||||
self.ctx.clone(),
|
||||
ctx.clone(),
|
||||
exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
|
||||
);
|
||||
|
||||
inner(exec_state, args).await.map(Some)
|
||||
} else {
|
||||
call_user_defined_function(args, self.memory.unwrap(), self.fn_expr.as_ref(), exec_state, &self.ctx).await
|
||||
func(exec_state, args).await.map(Some)
|
||||
}
|
||||
FunctionSource::User { ast, memory } => {
|
||||
call_user_defined_function(args, *memory, ast, exec_state, ctx).await
|
||||
}
|
||||
}
|
||||
|
||||
impl JsonSchema for FunctionParam<'_> {
|
||||
fn schema_name() -> String {
|
||||
"FunctionParam".to_owned()
|
||||
FunctionSource::None => unreachable!(),
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
// TODO: Actually generate a reasonable schema.
|
||||
gen.subschema_for::<()>()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1868,6 +1867,7 @@ mod test {
|
||||
}
|
||||
fn additional_program_memory(items: &[(String, KclValue)]) -> ProgramMemory {
|
||||
let mut program_memory = ProgramMemory::new();
|
||||
program_memory.init_for_tests();
|
||||
for (name, item) in items {
|
||||
program_memory
|
||||
.add(name.clone(), item.clone(), SourceRange::default())
|
||||
@ -1877,7 +1877,7 @@ mod test {
|
||||
}
|
||||
// Declare the test cases.
|
||||
for (test_name, params, args, expected) in [
|
||||
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())),
|
||||
("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
|
||||
(
|
||||
"all params required, and all given, should be OK",
|
||||
vec![req_param("x")],
|
||||
@ -1945,6 +1945,7 @@ mod test {
|
||||
});
|
||||
let args = args.into_iter().map(Arg::synthetic).collect();
|
||||
let mut exec_state = ExecState::new(&Default::default());
|
||||
exec_state.mut_memory().init_for_tests();
|
||||
let actual = assign_args_to_params(func_expr, args, &mut exec_state).map(|_| exec_state.global.memory);
|
||||
assert_eq!(
|
||||
actual, expected,
|
||||
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||
use kcmc::{
|
||||
coord::{System, KITTYCAD},
|
||||
each_cmd as mcmd,
|
||||
format::InputFormat,
|
||||
format::InputFormat3d,
|
||||
ok_response::OkModelingCmdResponse,
|
||||
shared::FileImportFormat,
|
||||
units::UnitLength,
|
||||
@ -32,7 +32,7 @@ pub const ZOO_COORD_SYSTEM: System = *KITTYCAD;
|
||||
|
||||
pub async fn import_foreign(
|
||||
file_path: &Path,
|
||||
format: Option<InputFormat>,
|
||||
format: Option<InputFormat3d>,
|
||||
exec_state: &mut ExecState,
|
||||
ctxt: &ExecutorContext,
|
||||
source_range: SourceRange,
|
||||
@ -98,7 +98,7 @@ pub async fn import_foreign(
|
||||
|
||||
// In the case of a gltf importing a bin file we need to handle that! and figure out where the
|
||||
// file is relative to our current file.
|
||||
if let InputFormat::Gltf(..) = format {
|
||||
if let InputFormat3d::Gltf(..) = format {
|
||||
// Check if the file is a binary gltf file, in that case we don't need to import the bin
|
||||
// file.
|
||||
if !file_contents.starts_with(b"glTF") {
|
||||
@ -158,7 +158,7 @@ pub(super) fn format_from_annotations(
|
||||
annotations: &[Node<Annotation>],
|
||||
path: &Path,
|
||||
import_source_range: SourceRange,
|
||||
) -> Result<Option<InputFormat>, KclError> {
|
||||
) -> Result<Option<InputFormat3d>, KclError> {
|
||||
if annotations.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
@ -220,7 +220,7 @@ pub(super) fn format_from_annotations(
|
||||
Ok(Some(result))
|
||||
}
|
||||
|
||||
fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let mut coords = None;
|
||||
for (name, val) in annotations::IMPORT_COORDS_VALUES {
|
||||
if coords_str == name {
|
||||
@ -243,9 +243,9 @@ fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange
|
||||
};
|
||||
|
||||
match fmt {
|
||||
InputFormat::Obj(opts) => opts.coords = coords,
|
||||
InputFormat::Ply(opts) => opts.coords = coords,
|
||||
InputFormat::Stl(opts) => opts.coords = coords,
|
||||
InputFormat3d::Obj(opts) => opts.coords = coords,
|
||||
InputFormat3d::Ply(opts) => opts.coords = coords,
|
||||
InputFormat3d::Stl(opts) => opts.coords = coords,
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
@ -260,13 +260,13 @@ fn set_coords(fmt: &mut InputFormat, coords_str: &str, source_range: SourceRange
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn set_length_unit(fmt: &mut InputFormat, units_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: SourceRange) -> Result<(), KclError> {
|
||||
let units = UnitLen::from_str(units_str, source_range)?;
|
||||
|
||||
match fmt {
|
||||
InputFormat::Obj(opts) => opts.units = units.into(),
|
||||
InputFormat::Ply(opts) => opts.units = units.into(),
|
||||
InputFormat::Stl(opts) => opts.units = units.into(),
|
||||
InputFormat3d::Obj(opts) => opts.units = units.into(),
|
||||
InputFormat3d::Ply(opts) => opts.units = units.into(),
|
||||
InputFormat3d::Stl(opts) => opts.units = units.into(),
|
||||
_ => {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: format!(
|
||||
@ -320,7 +320,7 @@ pub async fn send_to_engine(pre: PreImportedGeometry, ctxt: &ExecutorContext) ->
|
||||
}
|
||||
|
||||
/// Get the source format from the extension.
|
||||
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
|
||||
fn get_import_format_from_extension(ext: &str) -> Result<InputFormat3d> {
|
||||
let format = match FileImportFormat::from_str(ext) {
|
||||
Ok(format) => format,
|
||||
Err(_) => {
|
||||
@ -343,44 +343,44 @@ fn get_import_format_from_extension(ext: &str) -> Result<InputFormat> {
|
||||
// * Up: +Z
|
||||
// * Handedness: Right
|
||||
match format {
|
||||
FileImportFormat::Step => Ok(InputFormat::Step(kcmc::format::step::import::Options {
|
||||
FileImportFormat::Step => Ok(InputFormat3d::Step(kcmc::format::step::import::Options {
|
||||
split_closed_faces: false,
|
||||
})),
|
||||
FileImportFormat::Stl => Ok(InputFormat::Stl(kcmc::format::stl::import::Options {
|
||||
FileImportFormat::Stl => Ok(InputFormat3d::Stl(kcmc::format::stl::import::Options {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
})),
|
||||
FileImportFormat::Obj => Ok(InputFormat::Obj(kcmc::format::obj::import::Options {
|
||||
FileImportFormat::Obj => Ok(InputFormat3d::Obj(kcmc::format::obj::import::Options {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
})),
|
||||
FileImportFormat::Gltf => Ok(InputFormat::Gltf(kcmc::format::gltf::import::Options {})),
|
||||
FileImportFormat::Ply => Ok(InputFormat::Ply(kcmc::format::ply::import::Options {
|
||||
FileImportFormat::Gltf => Ok(InputFormat3d::Gltf(kcmc::format::gltf::import::Options {})),
|
||||
FileImportFormat::Ply => Ok(InputFormat3d::Ply(kcmc::format::ply::import::Options {
|
||||
coords: ZOO_COORD_SYSTEM,
|
||||
units: ul,
|
||||
})),
|
||||
FileImportFormat::Fbx => Ok(InputFormat::Fbx(kcmc::format::fbx::import::Options {})),
|
||||
FileImportFormat::Sldprt => Ok(InputFormat::Sldprt(kcmc::format::sldprt::import::Options {
|
||||
FileImportFormat::Fbx => Ok(InputFormat3d::Fbx(kcmc::format::fbx::import::Options {})),
|
||||
FileImportFormat::Sldprt => Ok(InputFormat3d::Sldprt(kcmc::format::sldprt::import::Options {
|
||||
split_closed_faces: false,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()> {
|
||||
if let InputFormat::Stl(_) = ext {
|
||||
if let InputFormat::Stl(_) = given {
|
||||
fn validate_extension_format(ext: InputFormat3d, given: InputFormat3d) -> Result<()> {
|
||||
if let InputFormat3d::Stl(_) = ext {
|
||||
if let InputFormat3d::Stl(_) = given {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let InputFormat::Obj(_) = ext {
|
||||
if let InputFormat::Obj(_) = given {
|
||||
if let InputFormat3d::Obj(_) = ext {
|
||||
if let InputFormat3d::Obj(_) = given {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
if let InputFormat::Ply(_) = ext {
|
||||
if let InputFormat::Ply(_) = given {
|
||||
if let InputFormat3d::Ply(_) = ext {
|
||||
if let InputFormat3d::Ply(_) = given {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@ -396,15 +396,15 @@ fn validate_extension_format(ext: InputFormat, given: InputFormat) -> Result<()>
|
||||
)
|
||||
}
|
||||
|
||||
fn get_name_of_format(type_: InputFormat) -> &'static str {
|
||||
fn get_name_of_format(type_: InputFormat3d) -> &'static str {
|
||||
match type_ {
|
||||
InputFormat::Fbx(_) => "fbx",
|
||||
InputFormat::Gltf(_) => "gltf",
|
||||
InputFormat::Obj(_) => "obj",
|
||||
InputFormat::Ply(_) => "ply",
|
||||
InputFormat::Sldprt(_) => "sldprt",
|
||||
InputFormat::Step(_) => "step",
|
||||
InputFormat::Stl(_) => "stl",
|
||||
InputFormat3d::Fbx(_) => "fbx",
|
||||
InputFormat3d::Gltf(_) => "gltf",
|
||||
InputFormat3d::Obj(_) => "obj",
|
||||
InputFormat3d::Ply(_) => "ply",
|
||||
InputFormat3d::Sldprt(_) => "sldprt",
|
||||
InputFormat3d::Step(_) => "step",
|
||||
InputFormat3d::Stl(_) => "stl",
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,7 +430,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, no options
|
||||
@ -442,7 +442,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, no extension (wouldn't parse but might some day)
|
||||
@ -451,7 +451,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
InputFormat3d::Gltf(kittycad_modeling_cmds::format::gltf::import::Options {})
|
||||
);
|
||||
|
||||
// format, options
|
||||
@ -463,7 +463,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
InputFormat3d::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::VULKAN,
|
||||
units: kittycad_modeling_cmds::units::UnitLength::Feet,
|
||||
})
|
||||
@ -478,7 +478,7 @@ mod test {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
fmt,
|
||||
InputFormat::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
InputFormat3d::Obj(kittycad_modeling_cmds::format::obj::import::Options {
|
||||
coords: *kittycad_modeling_cmds::coord::VULKAN,
|
||||
units: kittycad_modeling_cmds::units::UnitLength::Feet,
|
||||
})
|
||||
|
@ -17,8 +17,8 @@ use crate::{
|
||||
},
|
||||
token::NumericSuffix,
|
||||
},
|
||||
std::{args::Arg, FnAsArg},
|
||||
KclError, ModuleId, SourceRange,
|
||||
std::{args::Arg, StdFnProps},
|
||||
CompilationError, KclError, ModuleId, SourceRange,
|
||||
};
|
||||
|
||||
pub type KclObjectFields = HashMap<String, KclValue>;
|
||||
@ -86,11 +86,7 @@ pub enum KclValue {
|
||||
#[ts(skip)]
|
||||
Function {
|
||||
#[serde(skip)]
|
||||
func: Option<crate::std::StdFn>,
|
||||
#[schemars(skip)]
|
||||
expression: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
// Invariant: Always Some except for std lib functions
|
||||
memory: Option<EnvironmentRef>,
|
||||
value: FunctionSource,
|
||||
#[serde(rename = "__meta")]
|
||||
meta: Vec<Metadata>,
|
||||
},
|
||||
@ -112,6 +108,31 @@ pub enum KclValue {
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Default)]
|
||||
pub enum FunctionSource {
|
||||
#[default]
|
||||
None,
|
||||
Std {
|
||||
func: crate::std::StdFn,
|
||||
props: StdFnProps,
|
||||
},
|
||||
User {
|
||||
ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
|
||||
memory: EnvironmentRef,
|
||||
},
|
||||
}
|
||||
|
||||
impl JsonSchema for FunctionSource {
|
||||
fn schema_name() -> String {
|
||||
"FunctionSource".to_owned()
|
||||
}
|
||||
|
||||
fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
|
||||
// TODO: Actually generate a reasonable schema.
|
||||
gen.subschema_for::<()>()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SketchSet> for KclValue {
|
||||
fn from(sg: SketchSet) -> Self {
|
||||
match sg {
|
||||
@ -230,12 +251,16 @@ impl KclValue {
|
||||
}
|
||||
|
||||
pub(crate) fn function_def_source_range(&self) -> Option<SourceRange> {
|
||||
let KclValue::Function { expression, .. } = self else {
|
||||
let KclValue::Function {
|
||||
value: FunctionSource::User { ast, .. },
|
||||
..
|
||||
} = self
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
// TODO: It would be nice if we could extract the source range starting
|
||||
// at the fn, but that's the variable declaration.
|
||||
Some(expression.as_source_range())
|
||||
Some(ast.as_source_range())
|
||||
}
|
||||
|
||||
pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
|
||||
@ -322,7 +347,7 @@ impl KclValue {
|
||||
pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
|
||||
let mut result = self.clone();
|
||||
if let KclValue::Function {
|
||||
memory: Some(ref mut memory),
|
||||
value: FunctionSource::User { ref mut memory, .. },
|
||||
..
|
||||
} = result
|
||||
{
|
||||
@ -486,21 +511,11 @@ impl KclValue {
|
||||
}
|
||||
|
||||
/// If this value is of type function, return it.
|
||||
pub fn get_function(&self) -> Option<FnAsArg<'_>> {
|
||||
let KclValue::Function {
|
||||
func,
|
||||
expression,
|
||||
memory,
|
||||
meta: _,
|
||||
} = &self
|
||||
else {
|
||||
return None;
|
||||
};
|
||||
Some(FnAsArg {
|
||||
func: func.as_ref(),
|
||||
expr: expression.to_owned(),
|
||||
memory: *memory,
|
||||
})
|
||||
pub fn get_function(&self) -> Option<&FunctionSource> {
|
||||
match self {
|
||||
KclValue::Function { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a tag identifier from a memory item.
|
||||
@ -556,19 +571,20 @@ impl KclValue {
|
||||
ctx: ExecutorContext,
|
||||
source_range: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let KclValue::Function {
|
||||
func,
|
||||
expression,
|
||||
memory: closure_memory,
|
||||
match self {
|
||||
KclValue::Function {
|
||||
value: FunctionSource::Std { func, props },
|
||||
..
|
||||
} = &self
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "not an in-memory function".to_string(),
|
||||
source_ranges: vec![],
|
||||
}));
|
||||
};
|
||||
if let Some(func) = func {
|
||||
} => {
|
||||
if props.deprecated {
|
||||
exec_state.warn(CompilationError::err(
|
||||
source_range,
|
||||
format!(
|
||||
"`{}` is deprecated, see the docs for a recommended replacement",
|
||||
props.name
|
||||
),
|
||||
));
|
||||
}
|
||||
exec_state.mut_memory().push_new_env_for_rust_call();
|
||||
let args = crate::std::Args::new(
|
||||
args,
|
||||
@ -579,15 +595,15 @@ impl KclValue {
|
||||
let result = func(exec_state, args).await.map(Some);
|
||||
exec_state.mut_memory().pop_env();
|
||||
result
|
||||
} else {
|
||||
crate::execution::exec_ast::call_user_defined_function(
|
||||
args,
|
||||
closure_memory.unwrap(),
|
||||
expression.as_ref(),
|
||||
exec_state,
|
||||
&ctx,
|
||||
)
|
||||
.await
|
||||
}
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User { ast, memory },
|
||||
..
|
||||
} => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "cannot call this because it isn't a function".to_string(),
|
||||
source_ranges: vec![source_range],
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
@ -600,29 +616,33 @@ impl KclValue {
|
||||
ctx: ExecutorContext,
|
||||
callsite: SourceRange,
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
let KclValue::Function {
|
||||
func,
|
||||
expression,
|
||||
memory: closure_memory,
|
||||
meta: _,
|
||||
} = &self
|
||||
else {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
match self {
|
||||
KclValue::Function {
|
||||
value: FunctionSource::Std { func: _, props },
|
||||
..
|
||||
} => {
|
||||
if props.deprecated {
|
||||
exec_state.warn(CompilationError::err(
|
||||
callsite,
|
||||
format!(
|
||||
"`{}` is deprecated, see the docs for a recommended replacement",
|
||||
props.name
|
||||
),
|
||||
));
|
||||
}
|
||||
todo!("Implement KCL stdlib fns with keyword args");
|
||||
}
|
||||
KclValue::Function {
|
||||
value: FunctionSource::User { ast, memory },
|
||||
..
|
||||
} => {
|
||||
crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
|
||||
.await
|
||||
}
|
||||
_ => Err(KclError::Semantic(KclErrorDetails {
|
||||
message: "cannot call this because it isn't a function".to_string(),
|
||||
source_ranges: vec![callsite],
|
||||
}));
|
||||
};
|
||||
if let Some(_func) = func {
|
||||
todo!("Implement calling KCL stdlib fns that are aliased. Part of https://github.com/KittyCAD/modeling-app/issues/4600");
|
||||
} else {
|
||||
crate::execution::exec_ast::call_user_defined_function_kw(
|
||||
args.kw_args,
|
||||
closure_memory.unwrap(),
|
||||
expression.as_ref(),
|
||||
exec_state,
|
||||
&ctx,
|
||||
)
|
||||
.await
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
|