Compare commits

..

7 Commits

380 changed files with 33759 additions and 23433 deletions

View File

@ -21,7 +21,7 @@ if [[ ! -f "test-results/.last-run.json" ]]; then
fi
retry=1
max_retrys=1
max_retrys=5
# retry failed tests, doing our own retries because using inbuilt playwright retries causes connection issues
while [[ $retry -le $max_retrys ]]; do

View File

@ -22,9 +22,6 @@ 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

View File

@ -32,6 +32,7 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'yarn'
- run: yarn install
@ -43,10 +44,6 @@ 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"
@ -123,13 +120,15 @@ jobs:
cp prepared-files/assets/icon.ico assets/icon.ico
cp prepared-files/assets/icon.png assets/icon.png
- uses: actions/setup-node@v4
- name: Sync node version and setup cache
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.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 2
max_attempts: 3
@ -180,7 +179,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.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 10
max_attempts: 3
@ -241,7 +240,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.1
uses: nick-fields/retry@v3.0.0
with:
timeout_minutes: 10
max_attempts: 3

View File

@ -46,7 +46,7 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --retries=2 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

View File

@ -26,21 +26,11 @@ jobs:
const issue_number = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const { data: comments } = await github.rest.issues.listComments({
// Post a comment on the PR
await github.rest.issues.createComment({
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,
repo,
issue_number,
body: message,
});
}
issue_number,
body: message,
});

View File

@ -89,9 +89,6 @@ 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
@ -203,11 +200,9 @@ jobs:
- name: Run playwright/electron flow (with retries)
id: retry
if: ${{ !cancelled() && (success() || failure()) }}
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
shell: bash
run: |
.github/ci-cd-scripts/playwright-electron.sh ${{matrix.shardIndex}} ${{matrix.shardTotal}} ${{matrix.os}}
env:
CI: true
FAIL_ON_CONSOLE_ERRORS: true

View File

@ -37,9 +37,6 @@ 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:
@ -56,9 +53,6 @@ jobs:
with:
workspaces: './src/wasm-lib'
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm
- run: yarn tsc
@ -98,9 +92,6 @@ jobs:
cache: 'yarn'
- run: yarn install
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm
- run: yarn simpleserver:bg
@ -127,9 +118,6 @@ jobs:
cache: 'yarn'
- run: yarn install
- uses: taiki-e/install-action@2dbeb927f58939d3aa13bf06ba0c0a34b76b9bfb
with:
tool: wasm-pack
- run: yarn build:wasm
- run: yarn simpleserver:bg

5
.gitignore vendored
View File

@ -24,7 +24,7 @@ yarn-debug.log*
yarn-error.log*
.idea
# .vscode
.vscode
.helix
src/wasm-lib/.idea
src/wasm-lib/.vscode
@ -41,12 +41,9 @@ e2e/playwright/playwright-secrets.env
e2e/playwright/temp1.png
e2e/playwright/temp2.png
e2e/playwright/temp3.png
# this will be overridden for specific directories
e2e/playwright/**/*.png
# exports from snapshot-tests.spec.ts "exports of each format should work"
e2e/playwright/export-snapshots/*
!e2e/playwright/export-snapshots/*.png
!e2e/playwright/snapshot-tests.spec.ts-snapshots/*.png
/kcl-samples
/test-results/

View File

@ -1,5 +0,0 @@
{
"rust-analyzer.linkedProjects": [
"src/wasm-lib/Cargo.toml"
]
}

View File

@ -48,49 +48,22 @@ We recommend downloading the latest application binary from [our Releases page](
## Running a development build
Install a node version manager such as [fnm](https://github.com/Schniz/fnm?tab=readme-ov-#installation).
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:
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
```
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
followed by:
# 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 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`.
or if you have the gh cli installed
```
# macOS/Linux
yarn fetch:wasm
# Windows
yarn fetch:wasm:windows
./get-latest-wasm-bundle.sh # this will download the latest main wasm bundle
```
That will build the WASM binary and put in the `public` dir (though gitignored).
@ -101,7 +74,7 @@ Finally, to run the web app only, run:
yarn start
```
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.
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.
### Development environment variables
@ -128,7 +101,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 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)
To build with electron-builder, run `yarn tronb:package:dev` (or `yarn tronb:package:prod` to point to the .env.production variables)
## Checking out commits / Bisecting

View File

@ -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(solidSet: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet
appearance(solid_set: SolidSet, color: String, metalness?: number, roughness?: number) -> SolidSet
```
@ -17,7 +17,7 @@ appearance(solidSet: SolidSet, color: String, metalness?: number, roughness?: nu
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) whose appearance is being set | Yes |
| `solid_set` | [`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 |

File diff suppressed because one or more lines are too long

View File

@ -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, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
```
@ -18,7 +18,7 @@ circle(data: CircleData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclara
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Construct a circle derived from 3 points.
```js
circleThreePoint(p1: [number], p2: [number], p3: [number], sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
circleThreePoint(p1: [number], p2: [number], p3: [number], sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
```
@ -20,7 +20,7 @@ circleThreePoint(p1: [number], p2: [number], p3: [number], sketchSurfaceOrGroup:
| `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 |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on. | Yes |
| `sketch_surface_or_group` | [`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

View File

@ -0,0 +1,15 @@
---
title: "std::prelude::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::HALF_TURN: number(deg) = 180deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::prelude::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::prelude::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::prelude::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -0,0 +1,15 @@
---
title: "std::prelude::ZERO"
excerpt: ""
layout: manual
---
```js
std::prelude::ZERO: number = 0
```

View File

@ -1,15 +0,0 @@
---
title: "std::HALF_TURN"
excerpt: ""
layout: manual
---
```js
std::HALF_TURN: number(deg) = 180deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::QUARTER_TURN: number(deg) = 90deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::THREE_QUARTER_TURN"
excerpt: ""
layout: manual
---
```js
std::THREE_QUARTER_TURN: number(deg) = 270deg
```

View File

@ -1,15 +0,0 @@
---
title: "std::ZERO"
excerpt: ""
layout: manual
---
```js
std::ZERO: number = 0
```

View File

@ -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(sketchSet: SketchSet, length: number) -> SolidSet
extrude(sketch_set: SketchSet, length: number) -> SolidSet
```
@ -17,7 +17,7 @@ extrude(sketchSet: SketchSet, length: number) -> SolidSet
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketches should be extruded | Yes |
| `length` | `number` | How far to extrude the given sketches | Yes |
### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Create a helix.
```js
helix(revolutions: number, angleStart: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue
helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis: Axis3dOrEdgeReference, length?: number) -> HelixValue
```
@ -18,7 +18,7 @@ helix(revolutions: number, angleStart: number, ccw?: bool, radius: number, axis:
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `revolutions` | `number` | Number of revolutions. | Yes |
| `angleStart` | `number` | Start angle (in degrees). | Yes |
| `angle_start` | `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 |
@ -82,11 +82,11 @@ helixPath = helix(
length = 10,
radius = 5,
axis = {
custom = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
},
custom = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
},
)
// Create a spring by sweeping around the helix path.

View File

@ -9,7 +9,7 @@ Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
```js
hole(holeSketch: SketchSet, sketch: Sketch) -> Sketch
hole(hole_sketch: SketchSet, sketch: Sketch) -> Sketch
```
@ -17,7 +17,7 @@ hole(holeSketch: SketchSet, sketch: Sketch) -> Sketch
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `holeSketch` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `hole_sketch` | [`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

View File

@ -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(filePath: String, options?: ImportFormat) -> ImportedGeometry
import(file_path: String, options?: ImportFormat) -> ImportedGeometry
```
@ -23,7 +23,7 @@ import(filePath: String, options?: ImportFormat) -> ImportedGeometry
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `filePath` | `String` | | Yes |
| `file_path` | `String` | | Yes |
| `options` | [`ImportFormat`](/docs/kcl/types/ImportFormat) | Import format specifier | No |
### Returns

View File

@ -10,10 +10,6 @@ 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)
@ -122,3 +118,8 @@ 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)

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Extend the current sketch with a new straight line.
```js
line(sketch: Sketch, endAbsolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch
line(sketch: Sketch, end_absolute?: [number], end?: [number], tag?: TagDeclarator) -> Sketch
```
@ -18,7 +18,7 @@ line(sketch: Sketch, endAbsolute?: [number], end?: [number], tag?: TagDeclarator
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
| `endAbsolute` | `[number]` | Which absolute point should this line go to? Incompatible with `end`. | No |
| `end_absolute` | `[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 |

View File

@ -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], vDegree: NonZeroU32, bezApproximateRational: bool, baseCurveIndex?: integer, tolerance?: number) -> Solid
loft(sketches: [Sketch], v_degree: NonZeroU32, bez_approximate_rational: bool, base_curve_index?: integer, tolerance?: number) -> Solid
```
@ -18,9 +18,9 @@ loft(sketches: [Sketch], vDegree: NonZeroU32, bezApproximateRational: bool, base
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl/types/Sketch) | Which sketches to loft. Must include at least 2 sketches. | Yes |
| `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 |
| `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 |
| `tolerance` | `number` | Tolerance for the loft operation. | No |
### Returns
@ -95,10 +95,10 @@ circleSketch1 = startSketchOn(offsetPlane('XY', offset = 150))
loft(
[
squareSketch,
circleSketch0,
circleSketch1
],
squareSketch,
circleSketch0,
circleSketch1
],
baseCurveIndex = 0,
bezApproximateRational = false,
tolerance = 0.000001,

View File

@ -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], mapFn: FunctionSource) -> [KclValue]
map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
```
@ -18,7 +18,7 @@ map(array: [KclValue], mapFn: FunctionSource) -> [KclValue]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `mapFn` | `FunctionSource` | | Yes |
| `map_fn` | `FunctionParam` | | Yes |
### Returns

View File

@ -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, sketchSet: SketchSet) -> [Sketch]
mirror2d(data: Mirror2dData, sketch_set: SketchSet) -> [Sketch]
```
@ -20,7 +20,7 @@ mirror2d(data: Mirror2dData, sketchSet: SketchSet) -> [Sketch]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`Mirror2dData`](/docs/kcl/types/Mirror2dData) | Data for a mirror. | Yes |
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
### Returns

View File

@ -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(sketchSet: SketchSet, instances: integer, center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Sketch]
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch]
```
@ -17,12 +17,12 @@ patternCircular2d(sketchSet: SketchSet, instances: integer, center: [number], ar
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
| `sketch_set` | [`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 |
| `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 |
| `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 |
### Returns

View File

@ -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(solidSet: SolidSet, instances: integer, axis: [number], center: [number], arcDegrees: number, rotateDuplicates: bool, useOriginal?: bool) -> [Solid]
patternCircular3d(solid_set: SolidSet, instances: integer, axis: [number], center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Solid]
```
@ -17,13 +17,13 @@ patternCircular3d(solidSet: SolidSet, instances: integer, axis: [number], center
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid(s) to pattern | Yes |
| `solid_set` | [`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 |
| `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 |
| `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 |
### Returns

View File

@ -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(sketchSet: SketchSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Sketch]
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch]
```
@ -17,11 +17,11 @@ patternLinear2d(sketchSet: SketchSet, instances: integer, distance: number, axis
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `sketch_set` | [`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 |
| `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 |
| `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 |
### Returns

View File

@ -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(solidSet: SolidSet, instances: integer, distance: number, axis: [number], useOriginal?: bool) -> [Solid]
patternLinear3d(solid_set: SolidSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Solid]
```
@ -17,11 +17,11 @@ patternLinear3d(solidSet: SolidSet, instances: integer, distance: number, axis:
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `solid_set` | [`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 |
| `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 |
| `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 |
### Returns

View File

@ -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(solidSet: SolidSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Solid]
patternTransform(solid_set: SolidSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Solid]
```
@ -43,10 +43,10 @@ patternTransform(solidSet: SolidSet, instances: integer, transform: FunctionSour
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | The solid(s) to duplicate | Yes |
| `solid_set` | [`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` | `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 |
| `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 |
### Returns

View File

@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
```js
patternTransform2d(sketchSet: SketchSet, instances: integer, transform: FunctionSource, useOriginal?: bool) -> [Sketch]
patternTransform2d(sketch_set: SketchSet, instances: integer, transform: FunctionParam, use_original?: bool) -> [Sketch]
```
@ -17,10 +17,10 @@ patternTransform2d(sketchSet: SketchSet, instances: integer, transform: Function
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketchSet` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `sketch_set` | [`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` | `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 |
| `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 |
### Returns

View File

@ -9,7 +9,7 @@ Create a regular polygon with the specified number of sides that is either inscr
```js
polygon(data: PolygonData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDeclarator) -> Sketch
polygon(data: PolygonData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
```
@ -18,7 +18,7 @@ polygon(data: PolygonData, sketchSurfaceOrGroup: SketchOrSurface, tag?: TagDecla
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns

View File

@ -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, reduceFn: FunctionSource) -> KclValue
reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
```
@ -19,7 +19,7 @@ reduce(array: [KclValue], start: KclValue, reduceFn: FunctionSource) -> KclValue
|----------|------|-------------|----------|
| `array` | [`[KclValue]`](/docs/kcl/types/KclValue) | | Yes |
| `start` | [`KclValue`](/docs/kcl/types/KclValue) | Any KCL value. | Yes |
| `reduceFn` | `FunctionSource` | | Yes |
| `reduce_fn` | `FunctionParam` | | Yes |
### Returns

View File

@ -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(solidSet: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
shell(solid_set: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
```
@ -17,7 +17,7 @@ shell(solidSet: SolidSet, thickness: number, faces: [FaceTag]) -> SolidSet
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `solidSet` | [`SolidSet`](/docs/kcl/types/SolidSet) | Which solid (or solids) to shell out | Yes |
| `solid_set` | [`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 |

View File

@ -9,7 +9,7 @@ Start a new profile at a given point.
```js
startProfileAt(to: [number], sketchSurface: SketchSurface, tag?: TagDeclarator) -> Sketch
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator) -> Sketch
```
@ -18,7 +18,7 @@ startProfileAt(to: [number], sketchSurface: SketchSurface, tag?: TagDeclarator)
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `to` | `[number]` | | Yes |
| `sketchSurface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
| `sketch_surface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
### Returns

File diff suppressed because it is too large Load Diff

View File

@ -266,10 +266,10 @@ myRect = rect([20, 0])
myRect
|> extrude(10, %)
|> fillet(
|> fillet({
radius = 0.5,
tags = [myRect.tags.rectangleSegmentA001]
)
}, %)
```
See how we use the tag `rectangleSegmentA001` in the `fillet` function outside

View File

@ -295,6 +295,7 @@ 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 |

View File

@ -82,7 +82,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await page.keyboard.press('Enter') // submit
await page.waitForTimeout(100)
await expect(page.locator('.cm-activeLine')).toContainText(
`fillet( radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] )`
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
)
})
@ -219,11 +219,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
}
)
test('Can extrude from the command bar', async ({
page,
homePage,
cmdBar,
}) => {
test('Can extrude from the command bar', async ({ page, homePage }) => {
await page.addInitScript(async () => {
localStorage.setItem(
'persistCode',
@ -258,7 +254,7 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
await expect(cmdSearchBar).toBeVisible()
// Search for extrude command and choose it
await cmdBar.cmdOptions.getByText('Extrude').click()
await page.getByRole('option', { name: 'Extrude' }).click()
// Assert that we're on the selection step
await expect(page.getByRole('button', { name: 'selection' })).toBeDisabled()

View File

@ -59,25 +59,18 @@ export interface Fixtures {
homePage: HomePageFixture
}
export class AuthenticatedTronApp {
public originalPage: Page
public readonly _page: 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(
browserContext: BrowserContext,
originalPage: Page,
testInfo: TestInfo
) {
this.page = originalPage
this.originalPage = originalPage
this.browserContext = browserContext
// Will be overwritten in the initializer
this.context = browserContext
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this._page = page
this.page = page
this.context = context
this.testInfo = testInfo
}
async initialise(
@ -93,17 +86,9 @@ 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

View File

@ -13,8 +13,8 @@ import {
import * as TOML from '@iarna/toml'
import { expectPixelColor } from './fixtures/sceneFixture'
// Because our default test settings have the onboardingStatus set to 'dismissed',
// we must set it to empty for the tests where we want to see the onboarding immediately.
// Because onboarding relies on an app setting we need to set it as incompletel
// for all these tests.
test.describe('Onboarding tests', () => {
test(
@ -22,7 +22,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
tag: '@electron',
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -106,6 +106,11 @@ test.describe('Onboarding tests', () => {
test(
'Code resets after confirmation',
{
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
},
async ({ context, page, homePage }) => {
@ -153,7 +158,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
},
@ -314,7 +319,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
@ -387,7 +392,7 @@ test.describe('Onboarding tests', () => {
{
appSettings: {
app: {
onboardingStatus: '',
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,

View File

@ -211,13 +211,12 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer(length = 30,tags = [
chamfer({length = 30,tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01)
],
)`,
]}, %)`,
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
@ -237,14 +236,14 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
beforeChamferSnippet: `angledLine([
segAng(rectangleSegmentA001) - 90,
217.26
], %, $seg01)chamfer(
], %, $seg01)chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
]
)`,
}, %)`,
afterChamferSelectSnippet:
'sketch003 = startSketchOn(extrude001, seg04)',
@ -261,13 +260,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
clickCoords: { x: 677, y: 87 },
cameraPos: { x: -6200, y: 1500, z: 6200 },
cameraTarget: { x: 8300, y: 1100, z: 4800 },
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer(
beforeChamferSnippet: `angledLine([0, 268.43], %, $rectangleSegmentA001)chamfer({
length = 30,
tags = [
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
]
)`,
}, %)`,
afterChamferSelectSnippet:
'sketch004 = startSketchOn(extrude001, seg05)',
afterRectangle1stClickSnippet:
@ -283,9 +282,10 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
clickCoords: { x: 620, y: 300 },
cameraPos: { x: -1100, y: -7700, z: 1600 },
cameraTarget: { x: 1450, y: 670, z: 4000 },
beforeChamferSnippet: `chamfer(length = 30, tags = [getNextAdjacentEdge(yo)])`,
beforeChamferSnippetEnd:
'|> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)])',
beforeChamferSnippet: `chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %)`,
afterChamferSelectSnippet:
'sketch005 = startSketchOn(extrude001, seg06)',
afterRectangle1stClickSnippet:
@ -313,16 +313,31 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer(length = 30, tags = [getOppositeEdge(seg01)], tag = $seg03)
|> chamfer(length = 30, tags = [seg01], tag = $seg04)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(seg02)], tag = $seg05)
|> chamfer(length = 30, tags = [getNextAdjacentEdge(yo)], tag = $seg06)
|> chamfer({
length = 30,
tags = [getOppositeEdge(seg01)]
}, %, $seg03)
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(seg02)]
}, %, $seg05)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %, $seg06)
sketch005 = startSketchOn(extrude001, seg06)
profile004=startProfileAt([-23.43,19.69], sketch005)
profile004 = startProfileAt([-23.43, 19.69], sketch005)
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([segAng(rectangleSegmentA005) - 90, 84.07], %)
|> angledLine([segAng(rectangleSegmentA005), -segLen(rectangleSegmentA005)], %)
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> angledLine([
segAng(rectangleSegmentA005) - 90,
84.07
], %)
|> angledLine([
segAng(rectangleSegmentA005),
-segLen(rectangleSegmentA005)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch004 = startSketchOn(extrude001, seg05)
profile003 = startProfileAt([82.57, 322.96], sketch004)
@ -363,6 +378,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
`,
{ shouldNormalise: true }
)
@ -399,13 +415,13 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
cameraPos: { x: 16020, y: -2000, z: 10500 },
cameraTarget: { x: -150, y: -4500, z: -80 },
beforeChamferSnippet: `angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)
chamfer(extrude001,length=30,tags=[
chamfer({length=30,tags=[
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01),
])`,
beforeChamferSnippetEnd: ')',
getOppositeEdge(seg01)
]}, extrude001)`,
beforeChamferSnippetEnd: '}, extrude001)',
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet:
@ -431,20 +447,18 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
chamf = chamfer(
extrude001,
chamf = chamfer({
length = 30,
tags = [getOppositeEdge(seg01)],
tag = $seg03,
)
|> chamfer(
tags = [getOppositeEdge(seg01)]
}, extrude001, $seg03)
|> chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02)
],
)
]
}, %)
sketch002 = startSketchOn(extrude001, seg03)
profile001 = startProfileAt([205.96, 254.59], sketch002)
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
@ -456,7 +470,7 @@ profile001 = startProfileAt([205.96, 254.59], sketch002)
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> close()
`,
{ shouldNormalise: true }
@ -1064,7 +1078,7 @@ openSketch = startSketchOn('XY')
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
})
})
@ -1125,53 +1139,11 @@ openSketch = startSketchOn('XY')
await scene.expectPixelColor([250, 250, 250], testPoint, 15)
})
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 test.step('Delete offset plane via feature tree selection', async () => {
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Helix', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
// Red plane is back
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
})
@ -1263,7 +1235,7 @@ openSketch = startSketchOn('XY')
await editor.closePane()
const operationButton = await toolbar.getFeatureTreeOperation('Loft', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
})
@ -1306,7 +1278,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 30 }, %)
`)
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
@ -1317,7 +1289,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(`
|> circle({ center = [0, 0], radius = 20 }, %)
`)
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
// Check for plane001
await scene.expectPixelColor([228, 228, 228], testPoint, 15)
})
@ -1328,7 +1300,7 @@ loft001 = loft([sketch001, sketch002])
await expect(page.locator('.cm-activeLine')).toHaveText(`
plane001 = offsetPlane('XZ', offset = 50)
`)
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
// Check for sketch 1
await scene.expectPixelColor([254, 254, 254], testPoint, 15)
})
@ -1422,7 +1394,7 @@ sketch002 = startSketchOn('XZ')
await page.waitForTimeout(500)
const operationButton = await toolbar.getFeatureTreeOperation('Sweep', 0)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
await toolbar.closePane('feature-tree')
await scene.expectPixelColor([53, 53, 53], testPoint, 15)
@ -1520,9 +1492,9 @@ sketch002 = startSketchOn('XZ')
|> close()
extrude001 = extrude(sketch001, length = -12)
`
const firstFilletDeclaration = 'fillet(radius = 5, tags = [seg01])'
const firstFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
const secondFilletDeclaration =
'fillet(radius = 5, tags = [getOppositeEdge(seg01)])'
'fillet({ radius = 5, tags = [getOppositeEdge(seg01)] }, %)'
// Locators
const firstEdgeLocation = { x: 600, y: 193 }
@ -1622,7 +1594,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(firstFilletDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|> fillet(radius = 5, tags = [seg01])'],
activeLines: ['|>fillet({radius=5,tags=[seg01]},%)'],
highlightedCode: '',
})
})
@ -1702,7 +1674,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(secondFilletDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>fillet(radius=5,tags=[getOppositeEdge(seg01)])'],
activeLines: ['radius=5,'],
highlightedCode: '',
})
})
@ -1728,7 +1700,7 @@ extrude001 = extrude(sketch001, length = -12)
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
await editor.expectEditor.not.toContain(secondFilletDeclaration)
@ -1754,17 +1726,18 @@ extrude001 = extrude(sketch001, length = -12)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
extrude001 = extrude(sketch001, length = -12)
|> fillet(radius = 5, tags = [seg01]) // fillet01
|> fillet(radius = 5, tags = [seg02]) // fillet02
fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])
fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|> fillet({ radius = 5, tags = [seg01] }, %) // fillet01
|> fillet({ radius = 5, tags = [seg02] }, %) // fillet02
fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
`
const pipedFilletDeclaration = 'fillet(radius = 5, tags = [seg01])'
const secondPipedFilletDeclaration = 'fillet(radius = 5, tags = [seg02])'
const pipedFilletDeclaration = 'fillet({ radius = 5, tags = [seg01] }, %)'
const secondPipedFilletDeclaration =
'fillet({ radius = 5, tags = [seg02] }, %)'
const standaloneFilletDeclaration =
'fillet03 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg01)])'
'fillet03 = fillet({ radius = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
const secondStandaloneFilletDeclaration =
'fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])'
'fillet04 = fillet({ radius = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
// Locators
const pipedFilletEdgeLocation = { x: 600, y: 193 }
@ -1830,7 +1803,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
})
await test.step('Verify piped fillet is deleted but other fillets are not (in the editor)', async () => {
@ -1860,7 +1833,7 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
})
await test.step('Verify non-piped fillet is deleted but other two fillets are not (in the editor)', async () => {
@ -1898,9 +1871,9 @@ fillet04 = fillet(extrude001, radius = 5, tags = [getOppositeEdge(seg02)])
|> close()
extrude001 = extrude(sketch001, length = -12)
`
const firstChamferDeclaration = 'chamfer(length = 5, tags = [seg01])'
const firstChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
const secondChamferDeclaration =
'chamfer(length = 5, tags = [getOppositeEdge(seg01)])'
'chamfer({ length = 5, tags = [getOppositeEdge(seg01)] }, %)'
// Locators
const firstEdgeLocation = { x: 600, y: 193 }
@ -1991,7 +1964,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(firstChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>chamfer(length=5,tags=[seg01])'],
activeLines: ['|>chamfer({length=5,tags=[seg01]},%)'],
highlightedCode: '',
})
})
@ -2075,7 +2048,7 @@ extrude001 = extrude(sketch001, length = -12)
await editor.expectEditor.toContain(secondChamferDeclaration)
await editor.expectState({
diagnostics: [],
activeLines: ['|>chamfer(length=5,tags=[getOppositeEdge(seg01)])'],
activeLines: ['length=5,'],
highlightedCode: '',
})
})
@ -2099,7 +2072,7 @@ extrude001 = extrude(sketch001, length = -12)
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
await scene.expectPixelColor(edgeColorWhite, secondEdgeLocation, 15) // deleted
await scene.expectPixelColor(chamferColor, firstEdgeLocation, 15) // stayed
@ -2123,17 +2096,18 @@ extrude001 = extrude(sketch001, length = -12)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
extrude001 = extrude(sketch001, length = -12)
|> chamfer(length = 5, tags = [seg01]) // chamfer01
|> chamfer(length = 5, tags = [seg02]) // chamfer02
chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])
chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
|> chamfer({ length = 5, tags = [seg01] }, %) // chamfer01
|> chamfer({ length = 5, tags = [seg02] }, %) // chamfer02
chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)
chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)
`
const pipedChamferDeclaration = 'chamfer(length = 5, tags = [seg01])'
const secondPipedChamferDeclaration = 'chamfer(length = 5, tags = [seg02])'
const pipedChamferDeclaration = 'chamfer({ length = 5, tags = [seg01] }, %)'
const secondPipedChamferDeclaration =
'chamfer({ length = 5, tags = [seg02] }, %)'
const standaloneChamferDeclaration =
'chamfer03 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg01)])'
'chamfer03 = chamfer({ length = 5, tags = [getOppositeEdge(seg01)]}, extrude001)'
const secondStandaloneChamferDeclaration =
'chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])'
'chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001)'
// Locators
const pipedChamferEdgeLocation = { x: 600, y: 193 }
@ -2202,7 +2176,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
0
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
})
await test.step('Verify piped chamfer is deleted but other chamfers are not (in the editor)', async () => {
@ -2234,7 +2208,7 @@ chamfer04 = chamfer(extrude001, length = 5, tags = [getOppositeEdge(seg02)])
1
)
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
await page.waitForTimeout(500)
})
await test.step('Verify non-piped chamfer is deleted but other two chamfers are not (in the editor)', async () => {
@ -2438,7 +2412,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('Delete')
await page.keyboard.press('Backspace')
await scene.expectPixelColor([99, 99, 99], testPoint, 15)
})
})
@ -2577,7 +2551,7 @@ profile001 = startProfileAt([-20, 20], sketch001)
const deleteOperation = async (operationButton: Locator) => {
if (shouldUseKeyboard) {
await operationButton.click({ button: 'left' })
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
} else {
await operationButton.click({ button: 'right' })
const editButton = page.getByTestId('context-menu-delete')
@ -2838,107 +2812,4 @@ 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
)
})
})
})

View File

@ -424,7 +424,7 @@ test(
test(
'when code with error first loads you get errors in console',
{ tag: '@electron' },
async ({ context, page, editor }, testInfo) => {
async ({ context, page }, testInfo) => {
await context.folderSetupFn(async (dir) => {
await fsp.mkdir(path.join(dir, 'broken-code'), { recursive: true })
await fsp.copyFile(
@ -434,19 +434,16 @@ test(
})
await page.setBodyDimensions({ width: 1200, height: 500 })
await expect(page.getByText('broken-code')).toBeVisible()
await page.getByText('broken-code').click()
// Gotcha: You can not use scene.waitForExecutionDone() since the KCL code is going to fail
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
})
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
await editor.scrollToText(
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
)
// error in guter
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -937,16 +937,11 @@ 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
@ -977,14 +972,6 @@ 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.

View File

@ -362,7 +362,9 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
exact: true,
})
const userSettingsTab = page.getByRole('radio', { name: 'User' })
const mouseControlsSetting = () => page.locator('#camera-controls').first()
const mouseControlsSetting = page
.locator('#mouseControls')
.getByRole('combobox')
const mouseControlSuccesToast = page.getByText(
'Set mouse controls to "Solidworks"'
)
@ -392,14 +394,7 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
await settingsLink.click()
await expect(settingsDialogHeading).toBeVisible()
await userSettingsTab.click()
const setting = mouseControlsSetting()
await expect(setting).toBeAttached()
await setting.scrollIntoViewIfNeeded()
await setting.selectOption({ label: 'Solidworks' })
await expect(setting, 'Setting value did not change').toHaveValue(
'Solidworks',
{ timeout: 120_000 }
)
await mouseControlsSetting.selectOption({ label: 'Solidworks' })
await expect(mouseControlSuccesToast).toBeVisible()
await settingsCloseButton.click()
})

View File

@ -382,7 +382,7 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
'|> line(end = [0, -pipeLength])'
)
await u.clearCommandLogs()
await page.keyboard.press('Delete')
await page.keyboard.press('Backspace')
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('Delete')
await page.keyboard.press('Backspace')
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('Delete')
await page.keyboard.press('Backspace')
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('Delete')
await page.keyboard.press('Backspace')
await expect(page.getByText('Unable to delete selection')).toBeVisible()
}
@ -764,15 +764,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer(
|> chamfer({
length = 30,
tags = [
seg01,
getNextAdjacentEdge(yo),
getNextAdjacentEdge(seg02),
getOppositeEdge(seg01)
],
)
]
}, %)
`)
await expect(
page.getByTestId('model-state-indicator-execution-done')
@ -810,15 +810,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await checkCodeAtHoverPosition(
'oppositeChamfer',
oppositeChamfer,
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await checkCodeAtHoverPosition(
'baseChamfer',
baseChamfer,
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await u.openAndClearDebugPanel()
@ -848,15 +848,15 @@ profile003 = startProfileAt([40.16, -120.48], sketch006)
await checkCodeAtHoverPosition(
'adjacentChamfer1',
adjacentChamfer1,
`line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
await checkCodeAtHoverPosition(
'adjacentChamfer2',
adjacentChamfer2,
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer(length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)],)`,
' )'
`angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
'}, %)'
)
})
test("Extrude button should be disabled if there's no extrudable geometry when nothing is selected", async ({

View File

@ -633,7 +633,6 @@ test.describe('Testing settings', () => {
`Set default unit to "${unitOfMeasure}" as a user default`
)
await expect(toastMessage).toBeVisible()
await expect(toastMessage).not.toBeVisible()
})
}
await changeUnitOfMeasureInUserTab('in')
@ -946,76 +945,4 @@ fn cube`
).toBeVisible()
})
})
/**
* This test assumes that the default value of the "highlight edges" setting is "on".
*/
test(`Toggle stream settings multiple times`, async ({
page,
scene,
homePage,
context,
toolbar,
cmdBar,
}, testInfo) => {
await context.folderSetupFn(async (dir) => {
const projectDir = join(dir, 'project-000')
await fsp.mkdir(projectDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.kcl'),
join(projectDir, 'main.kcl')
)
})
await test.step(`First snapshot`, async () => {
await homePage.openProject('project-000')
await toolbar.closePane('code')
await expect(toolbar.startSketchBtn).toBeEnabled({ timeout: 20_000 })
await scene.clickNoWhere()
})
const toast = (value: boolean) =>
page.getByText(
`Set highlight edges to "${String(value)}" as a user default`
)
const initialPath = testInfo.snapshotPath('toggle-settings-initial.png')
const initialScreenshot = await scene.streamWrapper.screenshot({
path: initialPath,
mask: [page.getByTestId('model-state-indicator')],
})
await test.step(`Toggle highlightEdges off`, async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · modeling · highlight edges')
await cmdBar.selectOption({ name: 'off' }).click()
const falseToast = toast(false)
await expect(falseToast).toBeVisible()
await falseToast.waitFor({ state: 'detached' })
})
await expect(scene.streamWrapper).not.toHaveScreenshot(
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
}
)
await test.step(`Toggle highlightEdges on`, async () => {
await cmdBar.openCmdBar()
await cmdBar.chooseCommand('Settings · modeling · highlight edges')
await cmdBar.selectOption({ name: 'on' }).click()
const trueToast = toast(true)
await expect(trueToast).toBeVisible()
await trueToast.waitFor({ state: 'detached' })
})
await expect(scene.streamWrapper).toHaveScreenshot(
'toggle-settings-initial.png',
{
maxDiffPixels: 15,
mask: [page.getByTestId('model-state-indicator')],
}
)
})
})

View File

@ -68,6 +68,13 @@ 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,

1
exp
View File

@ -1 +0,0 @@
sketch001=startSketchOn('XZ')|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]|>angledLine([0,268.43],%,$rectangleSegmentA001)|>angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)|>angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)|>line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)|>close()extrude001=extrude(sketch001,length=100)|>chamfer(length=30,tags=[getOppositeEdge(seg01)],tag=$seg03)|>chamfer(length=30,tags=[seg01],tag=$seg04)|>chamfer(length=30,tags=[getNextAdjacentEdge(seg02)],tag=$seg05)|>chamfer(length=30,tags=[getNextAdjacentEdge(yo)],tag=$seg06)sketch004=startSketchOn(extrude001,seg05)profile003=startProfileAt([82.57,322.96],sketch004)|>angledLine([0,11.16],%,$rectangleSegmentA004)|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch003=startSketchOn(extrude001,seg04)profile002=startProfileAt([-209.64,255.28],sketch003)|>angledLine([0,11.56],%,$rectangleSegmentA003)|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch002=startSketchOn(extrude001,seg03)profile001=startProfileAt([205.96,254.59],sketch002)|>angledLine([0,11.39],%,$rectangleSegmentA002)|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()

View File

1
got
View File

@ -1 +0,0 @@
sketch001=startSketchOn('XZ')|>startProfileAt([75.8,317.2],%)//[$startCapTag,$EndCapTag]|>angledLine([0,268.43],%,$rectangleSegmentA001)|>angledLine([segAng(rectangleSegmentA001)-90,217.26],%,$seg01)|>angledLine([segAng(rectangleSegmentA001),-segLen(rectangleSegmentA001)],%,$yo)|>line(endAbsolute=[profileStartX(%),profileStartY(%)],tag=$seg02)|>close()extrude001=extrude(sketch001,length=100)|>chamfer(length=30,tags=[getOppositeEdge(seg01)],tag=$seg03)|>chamfer(length=30,tags=[seg01],tag=$seg04)|>chamfer(length=30,tags=[getNextAdjacentEdge(seg02)],tag=$seg05)|>chamfer(length=30,tags=[getNextAdjacentEdge(yo)],tag=$seg06)sketch005=startSketchOn(extrude001,seg06)profile004=startProfileAt([-23.43,19.69],sketch005)|>angledLine([0,9.1],%,$rectangleSegmentA005)|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch004=startSketchOn(extrude001,seg05)profile003=startProfileAt([82.57,322.96],sketch004)|>angledLine([0,11.16],%,$rectangleSegmentA004)|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch003=startSketchOn(extrude001,seg04)profile002=startProfileAt([-209.64,255.28],sketch003)|>angledLine([0,11.56],%,$rectangleSegmentA003)|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()sketch002=startSketchOn(extrude001,seg03)profile001=startProfileAt([205.96,254.59],sketch002)|>angledLine([0,11.39],%,$rectangleSegmentA002)|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)|>line(endAbsolute=[profileStartX(%),profileStartY(%)])|>close()

View File

@ -8,13 +8,13 @@
"email": "info@zoo.dev",
"url": "https://zoo.dev"
},
"description": "Zoo Modeling App",
"description": "Edit CAD visually or with code",
"main": ".vite/build/main.js",
"license": "MIT",
"dependencies": {
"@codemirror/autocomplete": "^6.17.0",
"@codemirror/commands": "^6.6.0",
"@codemirror/language": "^6.10.8",
"@codemirror/language": "^6.10.3",
"@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.6.0",
"electron-updater": "^6.5.0",
"fuse.js": "^7.0.0",
"html2canvas-pro": "^1.5.8",
"isomorphic-fetch": "^3.0.0",
@ -69,15 +69,10 @@
"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": "yarn install:rust && . $HOME/.cargo/env && yarn install:wasm-pack:sh && yarn build:wasm && vite build",
"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:local": "vite build",
"build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build",
@ -89,13 +84,11 @@
"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": "./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",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/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: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",
"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",
"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",
@ -103,7 +96,6 @@
"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",
@ -206,7 +198,7 @@
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.8.8",
"setimmediate": "^1.0.5",
"tailwindcss": "^3.4.17",
"tailwindcss": "^3.4.1",
"ts-node": "^10.0.0",
"typescript": "^5.7.3",
"typescript-eslint": "^8.23.0",
@ -215,6 +207,7 @@
"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"
},

View File

@ -1,20 +0,0 @@
$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

View File

@ -1,13 +0,0 @@
$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"

View File

@ -6,12 +6,14 @@ import { useHotkeys } from 'react-hotkeys-hook'
import { useLoaderData, useNavigate } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { PATHS } from 'lib/paths'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useEngineConnectionSubscriptions } from 'hooks/useEngineConnectionSubscriptions'
import { codeManager, engineCommandManager } from 'lib/singletons'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { isDesktop } from 'lib/isDesktop'
import { useLspContext } from 'components/LspProvider'
import { useRefreshSettings } from 'hooks/useRefreshSettings'
import { ModelingSidebar } from 'components/ModelingSidebar/ModelingSidebar'
import { LowerRightControls } from 'components/LowerRightControls'
import ModalContainer from 'react-modal-promise'
@ -28,7 +30,6 @@ import { useRouteLoaderData } from 'react-router-dom'
import { useEngineCommands } from 'components/EngineCommands'
import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine'
import { useSettings } from 'machines/appMachine'
maybeWriteToDisk()
.then(() => {})
.catch(() => {})
@ -48,6 +49,7 @@ export function App() {
})
})
useRefreshSettings(PATHS.FILE + 'SETTINGS')
const navigate = useNavigate()
const filePath = useAbsoluteFilePath()
const { onProjectOpen } = useLspContext()
@ -69,7 +71,7 @@ export function App() {
useHotKeyListener()
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const token = useToken()
const coreDumpManager = useMemo(
@ -79,7 +81,7 @@ export function App() {
const {
app: { onboardingStatus },
} = settings
} = settings.context
useHotkeys('backspace', (e) => {
e.preventDefault()

View File

@ -28,8 +28,10 @@ import {
fileLoader,
homeLoader,
onboardingRedirectLoader,
settingsLoader,
telemetryLoader,
} from 'lib/routeLoaders'
import SettingsAuthProvider from 'components/SettingsAuthProvider'
import LspProvider from 'components/LspProvider'
import { KclContextProvider } from 'lang/KclProvider'
import { ASK_TO_OPEN_QUERY_PARAM, BROWSER_PROJECT_NAME } from 'lib/constants'
@ -43,28 +45,34 @@ import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap'
import { RouteProvider } from 'components/RouteProvider'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
import { useToken } from 'machines/appMachine'
import { OpenInDesktopAppHandler } from 'components/OpenInDesktopAppHandler'
import { useToken } from 'machines/appMachine'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
const router = createRouter([
{
loader: settingsLoader,
id: PATHS.INDEX,
// TODO: Re-evaluate if this is true
/* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */
element: (
<OpenInDesktopAppHandler>
<RouteProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
<SettingsAuthProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
</LspProvider>
</SettingsAuthProvider>
</RouteProvider>
</OpenInDesktopAppHandler>
),
@ -112,6 +120,7 @@ const router = createRouter([
children: [
{
id: PATHS.FILE + 'SETTINGS',
loader: settingsLoader,
children: [
{
loader: onboardingRedirectLoader,
@ -157,9 +166,11 @@ const router = createRouter([
index: true,
element: <></>,
id: PATHS.HOME + 'SETTINGS',
loader: settingsLoader,
},
{
path: makeUrlPathRelative(PATHS.SETTINGS),
loader: settingsLoader,
element: <Settings />,
},
{

View File

@ -2,6 +2,7 @@ import { useRef, useEffect, useState, useMemo, Fragment } from 'react'
import { useModelingContext } from 'hooks/useModelingContext'
import { cameraMouseDragGuards } from 'lib/cameraControls'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ARROWHEAD, DEBUG_SHOW_BOTH_SCENES } from './sceneInfra'
import { ReactCameraProperties } from './CameraControls'
import { throttle, toSync } from 'lib/utils'
@ -47,7 +48,6 @@ import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)
@ -76,8 +76,8 @@ export const ClientSideScene = ({
cameraControls,
}: {
cameraControls: ReturnType<
typeof useSettings
>['modeling']['mouseControls']['current']
typeof useSettingsAuthContext
>['settings']['context']['modeling']['mouseControls']['current']
}) => {
const canvasRef = useRef<HTMLDivElement>(null)
const { state, send, context } = useModelingContext()

View File

@ -133,11 +133,9 @@ function DisplayObj({
}}
onClick={(e) => {
const range = topLevelRange(obj?.start || 0, obj.end || 0)
const idInfo = codeToIdSelections(
[{ codeRef: codeRefFromRange(range, kclManager.ast) }],
engineCommandManager.artifactGraph,
engineCommandManager.artifactIndex
)[0]
const idInfo = codeToIdSelections([
{ codeRef: codeRefFromRange(range, kclManager.ast) },
])[0]
const artifact = engineCommandManager.artifactGraph.get(
idInfo?.id || ''
)

View File

@ -1,22 +1,24 @@
import { Switch } from '@headlessui/react'
import { settingsActor, useSettings } from 'machines/appMachine'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useEffect, useState } from 'react'
export function CameraProjectionToggle() {
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const isCameraProjectionPerspective =
settings.modeling.cameraProjection.current === 'perspective'
settings.context.modeling.cameraProjection.current === 'perspective'
const [checked, setChecked] = useState(isCameraProjectionPerspective)
useEffect(() => {
setChecked(settings.modeling.cameraProjection.current === 'perspective')
}, [settings.modeling.cameraProjection.current])
setChecked(
settings.context.modeling.cameraProjection.current === 'perspective'
)
}, [settings.context.modeling.cameraProjection.current])
return (
<Switch
checked={checked}
onChange={(newValue) => {
settingsActor.send({
settings.send({
type: 'set.modeling.cameraProjection',
data: {
level: 'user',

View File

@ -7,6 +7,7 @@ import {
} from '@codemirror/autocomplete'
import { EditorView, keymap, ViewUpdate } from '@codemirror/view'
import { CustomIcon } from 'components/CustomIcon'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CommandArgument, KclCommandValue } from 'lib/commandTypes'
import { getSystemTheme } from 'lib/theme'
import { useCalculateKclExpression } from 'lib/useCalculateKclExpression'
@ -19,7 +20,6 @@ import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
import { useSelector } from '@xstate/react'
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
import toast from 'react-hot-toast'
const machineContextSelector = (snapshot?: {
@ -42,7 +42,7 @@ function CommandBarKclInput({
const previouslySetValue = commandBarState.context.argumentsToSubmit[
arg.name
] as KclCommandValue | undefined
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const argMachineContext = useSelector(
arg.machineActor,
machineContextSelector
@ -117,9 +117,9 @@ function CommandBarKclInput({
: defaultValue.length,
},
theme:
settings.app.theme.current === 'system'
settings.context.app.theme.current === 'system'
? getSystemTheme()
: settings.app.theme.current,
: settings.context.app.theme.current,
extensions: [
varMentionsExtension,
EditorView.updateListener.of((vu: ViewUpdate) => {

View File

@ -1,16 +1,16 @@
import { Dialog } from '@headlessui/react'
import { ActionButton } from './ActionButton'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { CREATE_FILE_URL_PARAM } from 'lib/constants'
import { useSettings } from 'machines/appMachine'
const DownloadAppBanner = () => {
const [searchParams] = useSearchParams()
const hasCreateFileParam = searchParams.has(CREATE_FILE_URL_PARAM)
const settings = useSettings()
const { settings } = useSettingsAuthContext()
const [isBannerDismissed, setIsBannerDismissed] = useState(
settings.app.dismissWebBanner.current
settings.context.app.dismissWebBanner.current || hasCreateFileParam
)
return (

View File

@ -1,7 +1,7 @@
import { useMachine } from '@xstate/react'
import { useLocation, useNavigate, useRouteLoaderData } from 'react-router-dom'
import { useNavigate, useRouteLoaderData } from 'react-router-dom'
import { type IndexLoaderData } from 'lib/types'
import { BROWSER_PATH, PATHS } from 'lib/paths'
import { PATHS } from 'lib/paths'
import React, { createContext, useEffect, useMemo } from 'react'
import { toast } from 'react-hot-toast'
import {
@ -27,10 +27,9 @@ import {
getKclSamplesManifest,
KclSamplesManifestItem,
} from 'lib/getKclSamplesManifest'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { markOnce } from 'lib/performance'
import { commandBarActor } from 'machines/commandBarMachine'
import { settingsActor, useSettings } from 'machines/appMachine'
import { createRouteCommands } from 'lib/commandBarConfigs/routeCommandConfig'
import { useToken } from 'machines/appMachine'
type MachineContext<T extends AnyStateMachine> = {
@ -49,51 +48,14 @@ export const FileMachineProvider = ({
children: React.ReactNode
}) => {
const navigate = useNavigate()
const location = useLocation()
const { settings } = useSettingsAuthContext()
const token = useToken()
const settings = useSettings()
const projectData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { project, file } = projectData
const [kclSamples, setKclSamples] = React.useState<KclSamplesManifestItem[]>(
[]
)
// Due to the route provider, i've moved this to the FileMachineProvider instead of CommandBarProvider
// This will register the commands to route to Telemetry, Home, and Settings.
useEffect(() => {
const filePath =
PATHS.FILE + '/' + encodeURIComponent(file?.path || BROWSER_PATH)
const { RouteTelemetryCommand, RouteHomeCommand, RouteSettingsCommand } =
createRouteCommands(navigate, location, filePath)
commandBarActor.send({
type: 'Remove commands',
data: {
commands: [
RouteTelemetryCommand,
RouteHomeCommand,
RouteSettingsCommand,
],
},
})
if (location.pathname === PATHS.HOME) {
commandBarActor.send({
type: 'Add commands',
data: { commands: [RouteTelemetryCommand, RouteSettingsCommand] },
})
} else if (location.pathname.includes(PATHS.FILE)) {
commandBarActor.send({
type: 'Add commands',
data: {
commands: [
RouteTelemetryCommand,
RouteSettingsCommand,
RouteHomeCommand,
],
},
})
}
}, [location])
useEffect(() => {
markOnce('code/didLoadFile')
async function fetchKclSamples() {
@ -361,7 +323,7 @@ export const FileMachineProvider = ({
authToken: token ?? '',
projectData,
settings: {
defaultUnit: settings.modeling.defaultUnit.current ?? 'mm',
defaultUnit: settings?.context?.modeling.defaultUnit.current ?? 'mm',
},
specialPropsForSampleCommand: {
onSubmit: async (data) => {
@ -383,7 +345,7 @@ export const FileMachineProvider = ({
// Either way, we want to overwrite the defaultUnit project setting
// with the sample's setting.
if (data.sampleUnits) {
settingsActor.send({
settings.send({
type: 'set.modeling.defaultUnit',
data: {
level: 'project',

View File

@ -1,5 +1,6 @@
import { Popover } from '@headlessui/react'
import Tooltip from './Tooltip'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths'
@ -8,7 +9,6 @@ import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { reportRejection } from 'lib/trap'
import { settingsActor } from 'machines/appMachine'
const HelpMenuDivider = () => (
<div className="h-[1px] bg-chalkboard-110 dark:bg-chalkboard-80" />
@ -20,6 +20,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
const filePath = useAbsoluteFilePath()
const isInProject = location.pathname.includes(PATHS.FILE)
const navigate = useNavigate()
const { settings } = useSettingsAuthContext()
return (
<Popover className="relative">
@ -105,7 +106,7 @@ export function HelpMenu(props: React.PropsWithChildren) {
<HelpMenuItem
as="button"
onClick={() => {
settingsActor.send({
settings.send({
type: 'set.app.onboardingStatus',
data: {
value: '',

View File

@ -10,6 +10,7 @@ import {
import { TEST, VITE_KC_API_BASE_URL } from 'env'
import { kcl } from 'editor/plugins/lsp/kcl/language'
import { copilotPlugin } from 'editor/plugins/lsp/copilot'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Extension } from '@codemirror/state'
import { LanguageSupport } from '@codemirror/language'
import { useNavigate } from 'react-router-dom'

View File

@ -22,6 +22,7 @@ import {
modelingMachineDefaultContext,
} from 'machines/modelingMachine'
import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import {
isCursorInSketchCommandRange,
updateSketchDetailsNodePaths,
@ -109,8 +110,6 @@ import { kclEditorActor } from 'machines/kclEditorMachine'
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>
@ -132,15 +131,19 @@ export const ModelingMachineProvider = ({
children: React.ReactNode
}) => {
const {
app: { theme, enableSSAO, allowOrbitInSketchMode },
modeling: {
defaultUnit,
cameraProjection,
highlightEdges,
showScaleGrid,
cameraOrbit,
settings: {
context: {
app: { theme, enableSSAO, allowOrbitInSketchMode },
modeling: {
defaultUnit,
cameraProjection,
highlightEdges,
showScaleGrid,
cameraOrbit,
},
},
},
} = useSettings()
} = useSettingsAuthContext()
const previousAllowOrbitInSketchMode = useRef(allowOrbitInSketchMode.current)
const navigate = useNavigate()
const { context, send: fileMachineSend } = useFileContext()
@ -803,7 +806,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 plane
// if the user selected a segment, make sure we enter the right sketch as there can be multiple on a plan
// but still works if the user selected a plane/face by defaulting to the first path
const mainPath =
artifact?.type === 'segment' || artifact?.type === 'solid2d'
@ -1718,12 +1721,8 @@ export const ModelingMachineProvider = ({
previousAllowOrbitInSketchMode.current = allowOrbitInSketchMode.current
}, [allowOrbitInSketchMode])
// 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, () => {
// Allow using the delete key to delete solids
useHotkeys(['backspace', 'delete', 'del'], () => {
modelingSend({ type: 'Delete selection' })
})

View File

@ -1,12 +1,12 @@
import { ReactNode } from 'react'
import styles from './ModelingPane.module.css'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { ActionButton } from 'components/ActionButton'
import Tooltip from 'components/Tooltip'
import { CustomIconName } from 'components/CustomIcon'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { ActionIcon } from 'components/ActionIcon'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { useSettings } from 'machines/appMachine'
export interface ModelingPaneProps {
id: string
@ -68,8 +68,8 @@ export const ModelingPane = ({
title,
...props
}: ModelingPaneProps) => {
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus
const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus
const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA
? 'pointer-events-none '

View File

@ -324,18 +324,6 @@ 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' ||
@ -392,13 +380,6 @@ 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}

View File

@ -1,4 +1,5 @@
import { TEST } from 'env'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Themes, getSystemTheme } from 'lib/theme'
import { useEffect, useMemo, useRef } from 'react'
import { highlightSelectionMatches, searchKeymap } from '@codemirror/search'
@ -50,7 +51,6 @@ import {
} from 'machines/kclEditorMachine'
import { useSelector } from '@xstate/react'
import { modelingMachineEvent } from 'editor/manager'
import { useSettings } from 'machines/appMachine'
export const editorShortcutMeta = {
formatCode: {
@ -63,7 +63,9 @@ export const editorShortcutMeta = {
}
export const KclEditorPane = () => {
const context = useSettings()
const {
settings: { context },
} = useSettingsAuthContext()
const lastSelectionEvent = useSelector(kclEditorActor, selectionEventSelector)
const editorIsMounted = useSelector(kclEditorActor, editorIsMountedSelector)
const theme =

View File

@ -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__')
expect(output.myFn).toEqual('__function(a)__')
expect(output.theExtrude).toEqual([
{
type: 'extrudePlane',

View File

@ -107,7 +107,9 @@ export const processMemory = (variables: VariableMap) => {
}
//@ts-ignore
} else if (val.type === 'Function') {
processedMemory[key] = `__function__`
processedMemory[key] = `__function(${(val as any)?.expression?.params
?.map?.(({ identifier }: any) => identifier?.name || '')
.join(', ')})__`
}
}
return processedMemory

View File

@ -1,3 +1,4 @@
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { Resizable } from 're-resizable'
import {
MouseEventHandler,
@ -20,7 +21,6 @@ import { MachineManagerContext } from 'components/MachineManagerProvider'
import { onboardingPaths } from 'routes/Onboarding/paths'
import { SIDEBAR_BUTTON_SUFFIX } from 'lib/constants'
import { commandBarActor } from 'machines/commandBarMachine'
import { useSettings } from 'machines/appMachine'
interface ModelingSidebarProps {
paneOpacity: '' | 'opacity-20' | 'opacity-40'
@ -38,23 +38,23 @@ function getPlatformString(): 'web' | 'desktop' {
export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
const machineManager = useContext(MachineManagerContext)
const kclContext = useKclContext()
const settings = useSettings()
const onboardingStatus = settings.app.onboardingStatus
const { settings } = useSettingsAuthContext()
const onboardingStatus = settings.context.app.onboardingStatus
const { send, context } = useModelingContext()
const pointerEventsCssClass =
onboardingStatus.current === onboardingPaths.CAMERA ||
context.store?.openPanes.length === 0
? 'pointer-events-none '
: 'pointer-events-auto '
const showDebugPanel = settings.modeling.showDebugPanel
const showDebugPanel = settings.context.modeling.showDebugPanel
const paneCallbackProps = useMemo(
() => ({
kclContext,
settings,
settings: settings.context,
platform: getPlatformString(),
}),
[kclContext.diagnostics, settings]
[kclContext.diagnostics, settings.context]
)
const sidebarActions: SidebarAction[] = [
@ -144,7 +144,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
},
})
}
}, [settings.modeling.showDebugPanel])
}, [settings.context])
const togglePane = useCallback(
(newPane: SidebarType) => {

View File

@ -1,5 +1,6 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import {
NETWORK_HEALTH_TEXT,
NetworkHealthIndicator,
@ -8,7 +9,11 @@ import { NetworkHealthState } from 'hooks/useNetworkStatus'
function TestWrap({ children }: { children: React.ReactNode }) {
// wrap in router and xState context
return <BrowserRouter>{children}</BrowserRouter>
return (
<BrowserRouter>
<SettingsAuthProviderJest>{children}</SettingsAuthProviderJest>
</BrowserRouter>
)
}
// Our Playwright tests for this are much more comprehensive.

View File

@ -1,6 +1,7 @@
import { render, screen } from '@testing-library/react'
import { BrowserRouter } from 'react-router-dom'
import ProjectSidebarMenu from './ProjectSidebarMenu'
import { SettingsAuthProviderJest } from './SettingsAuthProvider'
import { Project } from 'lib/project'
const now = new Date()
@ -31,7 +32,9 @@ describe('ProjectSidebarMenu tests', () => {
test('Disables popover menu by default', () => {
render(
<BrowserRouter>
<ProjectSidebarMenu project={projectWellFormed} />
<SettingsAuthProviderJest>
<ProjectSidebarMenu project={projectWellFormed} />
</SettingsAuthProviderJest>
</BrowserRouter>
)

Some files were not shown because too many files have changed in this diff Show More