Compare commits
45 Commits
move-tests
...
v0.34.0
Author | SHA1 | Date | |
---|---|---|---|
872b196a86 | |||
d535a2862d | |||
63a3bc7bc6 | |||
02055a8b31 | |||
93891422f7 | |||
7193b4110a | |||
76e7d80a55 | |||
b816df21d2 | |||
3630696848 | |||
f165d19fda | |||
3dd98ae1d5 | |||
a46e0a0fe7 | |||
8f9dc06228 | |||
fa22c14723 | |||
1d39983b08 | |||
da301ba862 | |||
efe8089b08 | |||
49de3b0ac9 | |||
2b2ed470c1 | |||
96652a0c48 | |||
04e586d07b | |||
fe5f574a77 | |||
e787495ad0 | |||
8bb9be7a5e | |||
00892464e8 | |||
05ed2a3367 | |||
10cc5bce59 | |||
a32f150fc1 | |||
ac60082e67 | |||
d44dc1b21a | |||
813962ea4c | |||
738443a6ab | |||
4b6bbbe2c5 | |||
6ff8addc8b | |||
da05c38b9e | |||
191b9b71fd | |||
05163fdded | |||
7ed26e21c6 | |||
c668d40efc | |||
f38c6b90b7 | |||
7bc8bae0ec | |||
3804aca27e | |||
b127680f2f | |||
b7de8e60cf | |||
058fccb5e1 |
12
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -6,11 +6,11 @@ set -euo pipefail
|
|||||||
if [[ ! -f "test-results/.last-run.json" ]]; then
|
if [[ ! -f "test-results/.last-run.json" ]]; then
|
||||||
# if no last run artifact, than run plawright normally
|
# if no last run artifact, than run plawright normally
|
||||||
echo "run playwright normally"
|
echo "run playwright normally"
|
||||||
if [[ "$3" == ubuntu-latest* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --shard=$1/$2 || true
|
||||||
elif [[ "$3" == windows-latest* ]]; then
|
elif [[ "$3" == *windows* ]]; then
|
||||||
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
yarn test:playwright:electron:windows -- --shard=$1/$2 || true
|
||||||
elif [[ "$3" == macos-14* ]]; then
|
elif [[ "$3" == *macos* ]]; then
|
||||||
yarn test:playwright:electron:macos -- --shard=$1/$2 || true
|
yarn test:playwright:electron:macos -- --shard=$1/$2 || true
|
||||||
else
|
else
|
||||||
echo "Do not run playwright. Unable to detect os runtime."
|
echo "Do not run playwright. Unable to detect os runtime."
|
||||||
@ -30,11 +30,11 @@ while [[ $retry -le $max_retrys ]]; do
|
|||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ $failed_tests -gt 0 ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
if [[ "$3" == ubuntu-latest* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true
|
||||||
elif [[ "$3" == windows-latest* ]]; then
|
elif [[ "$3" == *windows* ]]; then
|
||||||
yarn test:playwright:electron:windows -- --last-failed || true
|
yarn test:playwright:electron:windows -- --last-failed || true
|
||||||
elif [[ "$3" == macos-14* ]]; then
|
elif [[ "$3" == *macos* ]]; then
|
||||||
yarn test:playwright:electron:macos -- --last-failed || true
|
yarn test:playwright:electron:macos -- --last-failed || true
|
||||||
else
|
else
|
||||||
echo "Do not run playwright. Unable to detect os runtime."
|
echo "Do not run playwright. Unable to detect os runtime."
|
||||||
|
22
.github/workflows/cargo-test.yml
vendored
@ -2,28 +2,8 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
|
||||||
- 'src/wasm-lib/**.rs'
|
|
||||||
- 'src/wasm-lib/**.hbs'
|
|
||||||
- 'src/wasm-lib/**.gen'
|
|
||||||
- 'src/wasm-lib/**.snap'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- 'src/wasm-lib/**.kcl'
|
|
||||||
- .github/workflows/cargo-test.yml
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
paths:
|
|
||||||
- 'src/wasm-lib/**.rs'
|
|
||||||
- 'src/wasm-lib/**.hbs'
|
|
||||||
- 'src/wasm-lib/**.gen'
|
|
||||||
- 'src/wasm-lib/**.snap'
|
|
||||||
- '**/Cargo.toml'
|
|
||||||
- '**/Cargo.lock'
|
|
||||||
- '**/rust-toolchain.toml'
|
|
||||||
- 'src/wasm-lib/**.kcl'
|
|
||||||
- .github/workflows/cargo-test.yml
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
permissions: read-all
|
permissions: read-all
|
||||||
concurrency:
|
concurrency:
|
||||||
@ -71,7 +51,7 @@ jobs:
|
|||||||
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
|
||||||
RUST_MIN_STACK: 10485760000
|
RUST_MIN_STACK: 10485760000
|
||||||
- name: Upload to codecov.io
|
- name: Upload to codecov.io
|
||||||
uses: codecov/codecov-action@v4
|
uses: codecov/codecov-action@v5
|
||||||
with:
|
with:
|
||||||
token: ${{secrets.CODECOV_TOKEN}}
|
token: ${{secrets.CODECOV_TOKEN}}
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
|
12
.github/workflows/e2e-tests.yml
vendored
@ -18,6 +18,7 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
check-rust-changes:
|
check-rust-changes:
|
||||||
|
if: github.event.pull_request.draft == false
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
outputs:
|
outputs:
|
||||||
rust-changed: ${{ steps.filter.outputs.rust }}
|
rust-changed: ${{ steps.filter.outputs.rust }}
|
||||||
@ -34,19 +35,19 @@ jobs:
|
|||||||
- 'src/wasm-lib/**'
|
- 'src/wasm-lib/**'
|
||||||
|
|
||||||
electron:
|
electron:
|
||||||
timeout-minutes: ${{ matrix.os == 'macos-14' && 60 || 50 }}
|
if: github.event.pull_request.draft == false
|
||||||
|
timeout-minutes: 60
|
||||||
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
name: playwright:electron:${{ matrix.os }} ${{ matrix.shardIndex }} ${{ matrix.shardTotal }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large]
|
# TODO: enable self-hosted-windows-8-cores once available
|
||||||
|
os: [namespace-profile-ubuntu-8-cores, namespace-profile-macos-8-cores, windows-16-cores]
|
||||||
shardIndex: [1, 2, 3, 4]
|
shardIndex: [1, 2, 3, 4]
|
||||||
shardTotal: [4]
|
shardTotal: [4]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
needs: check-rust-changes
|
needs: check-rust-changes
|
||||||
steps:
|
steps:
|
||||||
- name: Tune GitHub-hosted runner network
|
|
||||||
uses: smorimoto/tune-github-hosted-runner-network@v1
|
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
@ -101,7 +102,8 @@ jobs:
|
|||||||
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH
|
||||||
- name: Install vector
|
- name: Install vector
|
||||||
shell: bash
|
shell: bash
|
||||||
if: ${{ !startsWith(matrix.os, 'windows') }}
|
# TODO: figure out what to do with this, it's failing
|
||||||
|
if: false
|
||||||
run: |
|
run: |
|
||||||
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh
|
||||||
chmod +x /tmp/vector.sh
|
chmod +x /tmp/vector.sh
|
||||||
|
49
docs/kcl/atan2.md
Normal file
@ -30,6 +30,7 @@ layout: manual
|
|||||||
* [`assertLessThan`](kcl/assertLessThan)
|
* [`assertLessThan`](kcl/assertLessThan)
|
||||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||||
* [`atan`](kcl/atan)
|
* [`atan`](kcl/atan)
|
||||||
|
* [`atan2`](kcl/atan2)
|
||||||
* [`bezierCurve`](kcl/bezierCurve)
|
* [`bezierCurve`](kcl/bezierCurve)
|
||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
@ -100,8 +101,8 @@ layout: manual
|
|||||||
* [`sin`](kcl/sin)
|
* [`sin`](kcl/sin)
|
||||||
* [`sqrt`](kcl/sqrt)
|
* [`sqrt`](kcl/sqrt)
|
||||||
* [`startProfileAt`](kcl/startProfileAt)
|
* [`startProfileAt`](kcl/startProfileAt)
|
||||||
* [`startSketchAt`](kcl/startSketchAt)
|
|
||||||
* [`startSketchOn`](kcl/startSketchOn)
|
* [`startSketchOn`](kcl/startSketchOn)
|
||||||
|
* [`sweep`](kcl/sweep)
|
||||||
* [`tan`](kcl/tan)
|
* [`tan`](kcl/tan)
|
||||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||||
* [`tangentialArc`](kcl/tangentialArc)
|
* [`tangentialArc`](kcl/tangentialArc)
|
||||||
|
@ -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")
|
- `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
|
```js
|
||||||
patternTransform(total_instances: u32, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
patternTransform(total_instances: integer, transform_function: FunctionParam, solid_set: SolidSet) -> [Solid]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -43,7 +43,7 @@ patternTransform(total_instances: u32, transform_function: FunctionParam, solid_
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `total_instances` | `u32` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
| `solid_set` | [`SolidSet`](/docs/kcl/types/SolidSet) | A solid or a group of solids. | Yes |
|
||||||
|
|
||||||
@ -95,7 +95,8 @@ fn cube(length, center) {
|
|||||||
p2 = [l + x, l + y]
|
p2 = [l + x, l + y]
|
||||||
p3 = [l + x, -l + y]
|
p3 = [l + x, -l + y]
|
||||||
|
|
||||||
return startSketchAt(p0)
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt(p0, %)
|
||||||
|> lineTo(p1, %)
|
|> lineTo(p1, %)
|
||||||
|> lineTo(p2, %)
|
|> lineTo(p2, %)
|
||||||
|> lineTo(p3, %)
|
|> lineTo(p3, %)
|
||||||
@ -132,7 +133,8 @@ fn cube(length, center) {
|
|||||||
p2 = [l + x, l + y]
|
p2 = [l + x, l + y]
|
||||||
p3 = [l + x, -l + y]
|
p3 = [l + x, -l + y]
|
||||||
|
|
||||||
return startSketchAt(p0)
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt(p0, %)
|
||||||
|> lineTo(p1, %)
|
|> lineTo(p1, %)
|
||||||
|> lineTo(p2, %)
|
|> lineTo(p2, %)
|
||||||
|> lineTo(p3, %)
|
|> lineTo(p3, %)
|
||||||
@ -195,7 +197,8 @@ fn transform(i) {
|
|||||||
{ rotation = { angle = 45 * i } }
|
{ rotation = { angle = 45 * i } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
startSketchAt([0, 0])
|
startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> polygon({
|
|> polygon({
|
||||||
radius = 10,
|
radius = 10,
|
||||||
numSides = 4,
|
numSides = 4,
|
||||||
|
@ -9,7 +9,7 @@ Just like patternTransform, but works on 2D sketches not 3D solids.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
patternTransform2d(total_instances: u32, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
patternTransform2d(total_instances: integer, transform_function: FunctionParam, solid_set: SketchSet) -> [Sketch]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ patternTransform2d(total_instances: u32, transform_function: FunctionParam, soli
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `total_instances` | `u32` | | Yes |
|
| `total_instances` | `integer` | | Yes |
|
||||||
| `transform_function` | `FunctionParam` | | Yes |
|
| `transform_function` | `FunctionParam` | | Yes |
|
||||||
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
| `solid_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ fn sum(arr) {
|
|||||||
|
|
||||||
/* The above is basically like this pseudo-code:
|
/* The above is basically like this pseudo-code:
|
||||||
fn sum(arr):
|
fn sum(arr):
|
||||||
let sumSoFar = 0
|
sumSoFar = 0
|
||||||
for i in arr:
|
for i in arr:
|
||||||
sumSoFar = add(sumSoFar, i)
|
sumSoFar = add(sumSoFar, i)
|
||||||
return sumSoFar */
|
return sumSoFar */
|
||||||
@ -79,10 +79,11 @@ fn decagon(radius) {
|
|||||||
stepAngle = 1 / 10 * tau()
|
stepAngle = 1 / 10 * tau()
|
||||||
|
|
||||||
// Start the decagon sketch at this point.
|
// Start the decagon sketch at this point.
|
||||||
startOfDecagonSketch = startSketchAt([cos(0) * radius, sin(0) * radius])
|
startOfDecagonSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([cos(0) * radius, sin(0) * radius], %)
|
||||||
|
|
||||||
// Use a `reduce` to draw the remaining decagon sides.
|
// Use a `reduce` to draw the remaining decagon sides.
|
||||||
// For each number in the array 1..10, run the given function,
|
// For each number in the array 1..10, run the given function,
|
||||||
// which takes a partially-sketched decagon and adds one more edge to it.
|
// which takes a partially-sketched decagon and adds one more edge to it.
|
||||||
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {
|
||||||
// Draw one edge of the decagon.
|
// Draw one edge of the decagon.
|
||||||
@ -96,14 +97,15 @@ fn decagon(radius) {
|
|||||||
|
|
||||||
/* The `decagon` above is basically like this pseudo-code:
|
/* The `decagon` above is basically like this pseudo-code:
|
||||||
fn decagon(radius):
|
fn decagon(radius):
|
||||||
let stepAngle = (1/10) * tau()
|
stepAngle = (1/10) * tau()
|
||||||
let startOfDecagonSketch = startSketchAt([(cos(0)*radius), (sin(0) * radius)])
|
plane = startSketchOn('XY')
|
||||||
|
startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)
|
||||||
|
|
||||||
// Here's the reduce part.
|
// Here's the reduce part.
|
||||||
let partialDecagon = startOfDecagonSketch
|
partialDecagon = startOfDecagonSketch
|
||||||
for i in [1..10]:
|
for i in [1..10]:
|
||||||
let x = cos(stepAngle * i) * radius
|
x = cos(stepAngle * i) * radius
|
||||||
let y = sin(stepAngle * i) * radius
|
y = sin(stepAngle * i) * radius
|
||||||
partialDecagon = lineTo([x, y], partialDecagon)
|
partialDecagon = lineTo([x, y], partialDecagon)
|
||||||
fullDecagon = partialDecagon // it's now full
|
fullDecagon = partialDecagon // it's now full
|
||||||
return fullDecagon */
|
return fullDecagon */
|
||||||
|
@ -28,7 +28,8 @@ segEnd(tag: TagIdentifier) -> [number]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
w = 15
|
w = 15
|
||||||
cube = startSketchAt([0, 0])
|
cube = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([w, 0], %, $line1)
|
|> line([w, 0], %, $line1)
|
||||||
|> line([0, w], %, $line2)
|
|> line([0, w], %, $line2)
|
||||||
|> line([-w, 0], %, $line3)
|
|> line([-w, 0], %, $line3)
|
||||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
|||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
center = segEnd(tag)
|
center = segEnd(tag)
|
||||||
|
@ -28,7 +28,8 @@ segStart(tag: TagIdentifier) -> [number]
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
w = 15
|
w = 15
|
||||||
cube = startSketchAt([0, 0])
|
cube = startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> line([w, 0], %, $line1)
|
|> line([w, 0], %, $line1)
|
||||||
|> line([0, w], %, $line2)
|
|> line([0, w], %, $line2)
|
||||||
|> line([-w, 0], %, $line3)
|
|> line([-w, 0], %, $line3)
|
||||||
@ -37,7 +38,8 @@ cube = startSketchAt([0, 0])
|
|||||||
|> extrude(5, %)
|
|> extrude(5, %)
|
||||||
|
|
||||||
fn cylinder(radius, tag) {
|
fn cylinder(radius, tag) {
|
||||||
return startSketchAt([0, 0])
|
return startSketchOn('XY')
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|> circle({
|
|> circle({
|
||||||
radius = radius,
|
radius = radius,
|
||||||
center = segStart(tag)
|
center = segStart(tag)
|
||||||
|
@ -4,6 +4,8 @@ excerpt: "Start a new 2-dimensional sketch at a given point on the 'XY' plane."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
Start a new 2-dimensional sketch at a given point on the 'XY' plane.
|
||||||
|
|
||||||
|
|
||||||
|
8759
docs/kcl/std.json
55
docs/kcl/sweep.md
Normal file
@ -13,13 +13,18 @@ Data to draw an angled line.
|
|||||||
|
|
||||||
An angle and length with explicitly named parameters
|
An angle and length with explicitly named parameters
|
||||||
|
|
||||||
[`PolarCoordsData`](/docs/kcl/types/PolarCoordsData)
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `angle` |`number`| The angle of the line (in degrees). | No |
|
||||||
|
| `length` |`number`| The length of the line. | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -12,5 +12,10 @@ KCL value for an optional parameter which was not given an argument. (remember,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
@ -329,6 +329,23 @@ Data for an imported geometry.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Module`| | No |
|
||||||
|
| `value` |[`ModuleId`](/docs/kcl/types/ModuleId)| Any KCL value. | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Properties
|
## Properties
|
||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|
16
docs/kcl/types/ModuleId.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "ModuleId"
|
||||||
|
excerpt: "Identifier of a source file. Uses a u32 to keep the size small."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Identifier of a source file. Uses a u32 to keep the size small.
|
||||||
|
|
||||||
|
**Type:** `integer` (`uint32`)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
23
docs/kcl/types/SweepData.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: "SweepData"
|
||||||
|
excerpt: "Data for a sweep."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Data for a sweep.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `path` |[`Sketch`](/docs/kcl/types/Sketch)| The path to sweep along. | No |
|
||||||
|
| `sectional` |`boolean`| If true, the sweep will be broken up into sub-sweeps (extrusions, revolves, sweeps) based on the trajectory path components. | No |
|
||||||
|
| `tolerance` |`number`| Tolerance for the sweep operation. | No |
|
||||||
|
|
||||||
|
|
@ -20,7 +20,7 @@ async function doBasicSketch(
|
|||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
await page.waitForTimeout()
|
await page.waitForTimeout(1000)
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
// If we have the code pane open, we should see the code.
|
// If we have the code pane open, we should see the code.
|
||||||
|
@ -45,7 +45,8 @@ test.describe('Command bar tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Fillet from command bar', async ({ page, homePage }) => {
|
// TODO: fix this test after the electron migration
|
||||||
|
test.fixme('Fillet from command bar', async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
@ -80,7 +81,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await page.keyboard.press('Enter') // submit
|
await page.keyboard.press('Enter') // submit
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-activeLine')).toContainText(
|
await expect(page.locator('.cm-activeLine')).toContainText(
|
||||||
`fillet({ radius: ${KCL_DEFAULT_LENGTH}, tags: [seg01] }, %)`
|
`fillet({ radius = ${KCL_DEFAULT_LENGTH}, tags = [seg01] }, %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -54,6 +54,108 @@ test.describe('Editor tests', () => {
|
|||||||
|> close(%)`)
|
|> close(%)`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('ensure we use the cache, and do not re-execute', async ({
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// Ensure we execute the first time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
// Add whitespace to the end of the code.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('ArrowUp')
|
||||||
|
await page.keyboard.press('Home')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type(' ')
|
||||||
|
|
||||||
|
// Ensure we don't execute the second time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
// Make sure we didn't clear the scene.
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(3)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('ensure we use the cache, and do not clear on append', async ({
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, -10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, 20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> close(%)`)
|
||||||
|
|
||||||
|
// Ensure we execute the first time.
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(2)
|
||||||
|
|
||||||
|
// Add whitespace to the end of the code.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('ArrowDown')
|
||||||
|
await page.keyboard.press('End')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
await page.keyboard.type('const x = 1')
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-message-type="execution-done"]')
|
||||||
|
).toHaveCount(3)
|
||||||
|
await expect(
|
||||||
|
page.locator('[data-receive-command-type="scene_clear_all"]')
|
||||||
|
).toHaveCount(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('if you click the format button it formats your code', async ({
|
test('if you click the format button it formats your code', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
@ -453,20 +555,22 @@ test.describe('Editor tests', () => {
|
|||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// check no error to begin with
|
// check no error to begin with
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
|
|
||||||
/* add the following code to the editor ($ error is not a valid line)
|
/* add the following code to the editor (~ error is not a valid line)
|
||||||
$ error
|
* the old check here used $ but this is for tags so it changed meaning.
|
||||||
|
* hopefully ~ doesn't change meaning
|
||||||
|
~ error
|
||||||
const topAng = 30
|
const topAng = 30
|
||||||
const bottomAng = 25
|
const bottomAng = 25
|
||||||
*/
|
*/
|
||||||
await u.codeLocator.click()
|
await u.codeLocator.click()
|
||||||
await page.keyboard.type('$ error')
|
await page.keyboard.type('~ error')
|
||||||
|
|
||||||
// press arrows to clear autocomplete
|
// press arrows to clear autocomplete
|
||||||
await page.keyboard.press('ArrowLeft')
|
await page.keyboard.press('ArrowLeft')
|
||||||
@ -483,10 +587,12 @@ test.describe('Editor tests', () => {
|
|||||||
|
|
||||||
// error text on hover
|
// error text on hover
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.hover('.cm-lint-marker-error')
|
||||||
await expect(page.getByText('Unexpected token: $').first()).toBeVisible()
|
await expect(
|
||||||
|
page.getByText("found unknown token '~'").first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
// select the line that's causing the error and delete it
|
// select the line that's causing the error and delete it
|
||||||
await page.getByText('$ error').click()
|
await page.getByText('~ error').click()
|
||||||
await page.keyboard.press('End')
|
await page.keyboard.press('End')
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.keyboard.press('Home')
|
await page.keyboard.press('Home')
|
||||||
|
@ -92,6 +92,8 @@ test.describe('when using the file tree to', () => {
|
|||||||
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
`rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
|
||||||
await getUtils(page, test)
|
await getUtils(page, test)
|
||||||
|
|
||||||
@ -134,6 +136,8 @@ test.describe('when using the file tree to', () => {
|
|||||||
`create many new files of the same name, incrementing their names`,
|
`create many new files of the same name, incrementing their names`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page }, testInfo) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const { panesOpen, createNewFile } = await getUtils(page, test)
|
const { panesOpen, createNewFile } = await getUtils(page, test)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
@ -1014,6 +1018,8 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
|||||||
`open a file, change something, open a different file, hitting undo should do nothing`,
|
`open a file, change something, open a different file, hitting undo should do nothing`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'testProject')
|
const testDir = join(dir, 'testProject')
|
||||||
await fsp.mkdir(testDir, { recursive: true })
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
@ -1082,6 +1088,8 @@ test.describe('Undo and redo do not keep history when navigating between files',
|
|||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
// Skip on windows i think the keybindings are different for redo.
|
// Skip on windows i think the keybindings are different for redo.
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const testDir = join(dir, 'testProject')
|
const testDir = join(dir, 'testProject')
|
||||||
await fsp.mkdir(testDir, { recursive: true })
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
|
@ -147,20 +147,28 @@ export class EditorFixture {
|
|||||||
openPane() {
|
openPane() {
|
||||||
return openPane(this.page, this.paneButtonTestId)
|
return openPane(this.page, this.paneButtonTestId)
|
||||||
}
|
}
|
||||||
scrollToText(text: string) {
|
scrollToText(text: string, placeCursor?: boolean) {
|
||||||
return this.page.evaluate((scrollToText: string) => {
|
return this.page.evaluate(
|
||||||
// editorManager is available on the window object.
|
(args: { text: string; placeCursor?: boolean }) => {
|
||||||
// @ts-ignore
|
// error TS2339: Property 'docView' does not exist on type 'EditorView'.
|
||||||
let index = editorManager._editorView.docView.view.state.doc
|
// Except it does so :shrug:
|
||||||
.toString()
|
// @ts-ignore
|
||||||
.indexOf(scrollToText)
|
let index = window.editorManager._editorView?.docView.view.state.doc
|
||||||
// @ts-ignore
|
.toString()
|
||||||
editorManager._editorView.dispatch({
|
.indexOf(args.text)
|
||||||
selection: {
|
window.editorManager._editorView?.focus()
|
||||||
anchor: index,
|
window.editorManager._editorView?.dispatch({
|
||||||
},
|
selection: window.EditorSelection.create([
|
||||||
scrollIntoView: true,
|
window.EditorSelection.cursor(index),
|
||||||
})
|
]),
|
||||||
}, text)
|
effects: [
|
||||||
|
window.EditorView.scrollIntoView(
|
||||||
|
window.EditorSelection.range(index, index + 1)
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ text, placeCursor }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,8 @@ export class AuthenticatedApp {
|
|||||||
public readonly context: BrowserContext
|
public readonly context: BrowserContext
|
||||||
public readonly testInfo: TestInfo
|
public readonly testInfo: TestInfo
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||||
|
public electronApp: undefined | ElectronApplication
|
||||||
|
public dir: string = ''
|
||||||
|
|
||||||
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
|
||||||
this.context = context
|
this.context = context
|
||||||
@ -61,7 +63,7 @@ export class AuthenticatedTronApp {
|
|||||||
public page: Page
|
public page: Page
|
||||||
public context: BrowserContext
|
public context: BrowserContext
|
||||||
public readonly testInfo: TestInfo
|
public readonly testInfo: TestInfo
|
||||||
public electronApp?: ElectronApplication
|
public electronApp: ElectronApplication | undefined
|
||||||
public readonly viewPortSize = { width: 1200, height: 500 }
|
public readonly viewPortSize = { width: 1200, height: 500 }
|
||||||
public dir: string = ''
|
public dir: string = ''
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ export class AuthenticatedTronApp {
|
|||||||
appSettings?: Partial<SaveSettingsPayload>
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
} = { fixtures: {} }
|
} = { fixtures: {} }
|
||||||
) {
|
) {
|
||||||
const { electronApp, page, context, dir, options } = await setupElectron({
|
const { electronApp, page, context, dir } = await setupElectron({
|
||||||
testInfo: this.testInfo,
|
testInfo: this.testInfo,
|
||||||
folderSetupFn: arg.folderSetupFn,
|
folderSetupFn: arg.folderSetupFn,
|
||||||
cleanProjectDir: arg.cleanProjectDir,
|
cleanProjectDir: arg.cleanProjectDir,
|
||||||
|
@ -218,23 +218,7 @@ export class SceneFixture {
|
|||||||
coords: { x: number; y: number },
|
coords: { x: number; y: number },
|
||||||
diff: number
|
diff: number
|
||||||
) => {
|
) => {
|
||||||
let finalValue = colour
|
await expectPixelColor(this.page, colour, coords, diff)
|
||||||
await expect
|
|
||||||
.poll(async () => {
|
|
||||||
const pixel = (await getPixelRGBs(this.page)(coords, 1))[0]
|
|
||||||
if (!pixel) return null
|
|
||||||
finalValue = pixel
|
|
||||||
return pixel.every(
|
|
||||||
(channel, index) => Math.abs(channel - colour[index]) < diff
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.toBeTruthy()
|
|
||||||
.catch((cause) => {
|
|
||||||
throw new Error(
|
|
||||||
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
|
||||||
{ cause }
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get gizmo() {
|
get gizmo() {
|
||||||
@ -251,3 +235,28 @@ export class SceneFixture {
|
|||||||
await buttonToTest.click()
|
await buttonToTest.click()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function expectPixelColor(
|
||||||
|
page: Page,
|
||||||
|
colour: [number, number, number],
|
||||||
|
coords: { x: number; y: number },
|
||||||
|
diff: number
|
||||||
|
) {
|
||||||
|
let finalValue = colour
|
||||||
|
await expect
|
||||||
|
.poll(async () => {
|
||||||
|
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
|
||||||
|
if (!pixel) return null
|
||||||
|
finalValue = pixel
|
||||||
|
return pixel.every(
|
||||||
|
(channel, index) => Math.abs(channel - colour[index]) < diff
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.toBeTruthy()
|
||||||
|
.catch((cause) => {
|
||||||
|
throw new Error(
|
||||||
|
`ExpectPixelColor: expecting ${colour} got ${finalValue}`,
|
||||||
|
{ cause }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -11,6 +11,7 @@ import {
|
|||||||
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
TEST_SETTINGS_ONBOARDING_USER_MENU,
|
||||||
} from './storageStates'
|
} from './storageStates'
|
||||||
import * as TOML from '@iarna/toml'
|
import * as TOML from '@iarna/toml'
|
||||||
|
import { expectPixelColor } from './fixtures/sceneFixture'
|
||||||
|
|
||||||
// Because onboarding relies on an app setting we need to set it as incompletel
|
// Because onboarding relies on an app setting we need to set it as incompletel
|
||||||
// for all these tests.
|
// for all these tests.
|
||||||
@ -27,6 +28,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
cleanProjectDir: true,
|
cleanProjectDir: true,
|
||||||
},
|
},
|
||||||
async ({ context, page, homePage }) => {
|
async ({ context, page, homePage }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -35,10 +37,23 @@ test.describe('Onboarding tests', () => {
|
|||||||
page.getByText('Welcome to Modeling App! This')
|
page.getByText('Welcome to Modeling App! This')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
|
// Test that the onboarding pane loaded
|
||||||
|
await expect(
|
||||||
|
page.getByText('Welcome to Modeling App! This')
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
// *and* that the code is shown in the editor
|
// *and* that the code is shown in the editor
|
||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
'// Shelf Bracket'
|
'// Shelf Bracket'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Make sure the model loaded
|
||||||
|
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||||
|
const modelColor: [number, number, number] = [45, 45, 45]
|
||||||
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
expect(await u.getGreatestPixDiff(XYPlanePoint, modelColor)).toBeLessThan(
|
||||||
|
8
|
||||||
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -76,6 +91,14 @@ test.describe('Onboarding tests', () => {
|
|||||||
await expect(page.locator('.cm-content')).toContainText(
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
'// Shelf Bracket'
|
'// Shelf Bracket'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// TODO: jess make less shit
|
||||||
|
// Make sure the model loaded
|
||||||
|
//const XYPlanePoint = { x: 986, y: 522 } as const
|
||||||
|
//const modelColor: [number, number, number] = [76, 76, 76]
|
||||||
|
//await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
|
||||||
|
//await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -125,7 +148,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
// There used to be old code here that checked if we stored the reset
|
// There used to be old code here that checked if we stored the reset
|
||||||
// code into localStorage but that isnt the case on desktop. It gets
|
// code into localStorage but that isn't the case on desktop. It gets
|
||||||
// saved to the file system, which we have other tests for.
|
// saved to the file system, which we have other tests for.
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -414,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
'Restarting onboarding on desktop takes one attempt',
|
'Restarting onboarding on desktop takes one attempt',
|
||||||
{
|
{
|
||||||
appSettings: {
|
appSettings: {
|
||||||
@ -486,7 +509,15 @@ test(
|
|||||||
await test.step('Confirm that the onboarding has restarted', async () => {
|
await test.step('Confirm that the onboarding has restarted', async () => {
|
||||||
await expect(tutorialProjectIndicator).toBeVisible()
|
await expect(tutorialProjectIndicator).toBeVisible()
|
||||||
await expect(tutorialModalText).toBeVisible()
|
await expect(tutorialModalText).toBeVisible()
|
||||||
|
// Make sure the model loaded
|
||||||
|
const XYPlanePoint = { x: 988, y: 523 } as const
|
||||||
|
const modelColor: [number, number, number] = [76, 76, 76]
|
||||||
|
|
||||||
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
|
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
await tutorialDismissButton.click()
|
await tutorialDismissButton.click()
|
||||||
|
// Make sure model still there.
|
||||||
|
await expectPixelColor(page, modelColor, XYPlanePoint, 8)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Clear code and restart onboarding from settings', async () => {
|
await test.step('Clear code and restart onboarding from settings', async () => {
|
||||||
|
@ -16,6 +16,8 @@ test('verify extruding circle works', async ({
|
|||||||
toolbar,
|
toolbar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const file = await fs.readFile(
|
const file = await fs.readFile(
|
||||||
path.resolve(
|
path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -95,6 +97,8 @@ test('verify extruding circle works', async ({
|
|||||||
})
|
})
|
||||||
|
|
||||||
test.describe('verify sketch on chamfer works', () => {
|
test.describe('verify sketch on chamfer works', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const _sketchOnAChamfer =
|
const _sketchOnAChamfer =
|
||||||
(
|
(
|
||||||
page: Page,
|
page: Page,
|
||||||
@ -760,8 +764,9 @@ const loftPointAndClickCases = [
|
|||||||
]
|
]
|
||||||
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
||||||
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
test(`Loft point-and-click (preselected sketches: ${shouldPreselect})`, async ({
|
||||||
app,
|
context,
|
||||||
page,
|
page,
|
||||||
|
homePage,
|
||||||
scene,
|
scene,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
@ -773,7 +778,11 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
sketch002 = startSketchOn(plane001)
|
sketch002 = startSketchOn(plane001)
|
||||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||||
`
|
`
|
||||||
await app.initialise(initialCode)
|
await context.addInitScript((initialCode) => {
|
||||||
|
localStorage.setItem('persistCode', initialCode)
|
||||||
|
}, initialCode)
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// One dumb hardcoded screen pixel value
|
// One dumb hardcoded screen pixel value
|
||||||
const testPoint = { x: 575, y: 200 }
|
const testPoint = { x: 575, y: 200 }
|
||||||
@ -792,7 +801,7 @@ loftPointAndClickCases.forEach(({ shouldPreselect }) => {
|
|||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await clickOnSketch2()
|
await clickOnSketch2()
|
||||||
await app.page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -859,6 +868,8 @@ shellPointAndClickCapCases.forEach(({ shouldPreselect }) => {
|
|||||||
toolbar,
|
toolbar,
|
||||||
cmdBar,
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||||
extrude001 = extrude(30, sketch001)
|
extrude001 = extrude(30, sketch001)
|
||||||
|
@ -342,10 +342,10 @@ test(
|
|||||||
)
|
)
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'xxxxx open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
'open a file in a project works and renders, open another file in the same project with errors, it should clear the scene',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
const { dir } = await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = path.join(dir, 'bracket')
|
const bracketDir = path.join(dir, 'bracket')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await fsp.copyFile(
|
await fsp.copyFile(
|
||||||
|
@ -560,6 +560,8 @@ extrude001 = extrude(50, sketch001)
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
// Constants and locators
|
// Constants and locators
|
||||||
@ -586,7 +588,7 @@ extrude001 = extrude(50, sketch001)
|
|||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
message: 'Plane color is visible',
|
message: 'Plane color is visible',
|
||||||
})
|
})
|
||||||
.toBeLessThan(15)
|
.toBeLessThanOrEqual(15)
|
||||||
|
|
||||||
let maxZoomOuts = 10
|
let maxZoomOuts = 10
|
||||||
let middlePixelIsBackgroundColor =
|
let middlePixelIsBackgroundColor =
|
||||||
@ -604,7 +606,7 @@ extrude001 = extrude(50, sketch001)
|
|||||||
}
|
}
|
||||||
|
|
||||||
expect(middlePixelIsBackgroundColor, {
|
expect(middlePixelIsBackgroundColor, {
|
||||||
message: 'We no longer the default planes',
|
message: 'We should not see the default planes',
|
||||||
}).toBeTruthy()
|
}).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -637,6 +637,8 @@ test.describe('Sketch tests', () => {
|
|||||||
|> revolve({ axis = "X" }, %)`)
|
|> revolve({ axis = "X" }, %)`)
|
||||||
})
|
})
|
||||||
test('Can add multiple sketches', async ({ page, homePage }) => {
|
test('Can add multiple sketches', async ({ page, homePage }) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
@ -834,6 +836,8 @@ test.describe('Sketch tests', () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
// this was a regression https://github.com/KittyCAD/modeling-app/issues/2832
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -949,107 +953,108 @@ test.describe('Sketch tests', () => {
|
|||||||
`.replace(/\s/g, '')
|
`.replace(/\s/g, '')
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
test('empty-scene default-planes act as expected', async ({
|
// TODO: fix after electron migration is merged
|
||||||
page,
|
test.fixme(
|
||||||
homePage,
|
'empty-scene default-planes act as expected',
|
||||||
}) => {
|
async ({ page, homePage }) => {
|
||||||
/**
|
/**
|
||||||
* Tests the following things
|
* Tests the following things
|
||||||
* 1) The the planes are there on load because the scene is empty
|
* 1) The the planes are there on load because the scene is empty
|
||||||
* 2) The planes don't changes color when hovered initially
|
* 2) The planes don't changes color when hovered initially
|
||||||
* 3) Putting something in the scene makes the planes hidden
|
* 3) Putting something in the scene makes the planes hidden
|
||||||
* 4) Removing everything from the scene shows the plans again
|
* 4) Removing everything from the scene shows the plans again
|
||||||
* 3) Once "start sketch" is click, the planes do respond to hovers
|
* 3) Once "start sketch" is click, the planes do respond to hovers
|
||||||
* 4) Selecting a plan works as expected, i.e. sketch mode
|
* 4) Selecting a plan works as expected, i.e. sketch mode
|
||||||
* 5) Reloading the scene with something already in the scene means the planes are hidden
|
* 5) Reloading the scene with something already in the scene means the planes are hidden
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
const XYPlanePoint = { x: 774, y: 116 } as const
|
const XYPlanePoint = { x: 774, y: 116 } as const
|
||||||
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
const unHoveredColor: [number, number, number] = [47, 47, 93]
|
||||||
expect(
|
expect(
|
||||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||||
).toBeLessThan(8)
|
).toBeLessThan(8)
|
||||||
|
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
// color should not change for having been hovered
|
// color should not change for having been hovered
|
||||||
expect(
|
expect(
|
||||||
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
await u.getGreatestPixDiff(XYPlanePoint, unHoveredColor)
|
||||||
).toBeLessThan(8)
|
).toBeLessThan(8)
|
||||||
|
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
|
|
||||||
await u.codeLocator.fill(`sketch001 = startSketchOn('XY')
|
await u.codeLocator.fill(`sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-10, -10], %)
|
|> startProfileAt([-10, -10], %)
|
||||||
|> line([20, 0], %)
|
|> line([20, 0], %)
|
||||||
|> line([0, 20], %)
|
|> line([0, 20], %)
|
||||||
|> xLine(-20, %)
|
|> xLine(-20, %)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
const noPlanesColor: [number, number, number] = [30, 30, 30]
|
const noPlanesColor: [number, number, number] = [30, 30, 30]
|
||||||
expect(
|
expect(
|
||||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||||
).toBeLessThan(3)
|
).toBeLessThan(3)
|
||||||
|
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await u.removeCurrentCode()
|
await u.removeCurrentCode()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(XYPlanePoint, unHoveredColor), {
|
.poll(() => u.getGreatestPixDiff(XYPlanePoint, unHoveredColor), {
|
||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
})
|
})
|
||||||
.toBeLessThan(8)
|
.toBeLessThan(8)
|
||||||
|
|
||||||
// click start Sketch
|
// click start Sketch
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 })
|
await page.mouse.move(XYPlanePoint.x, XYPlanePoint.y, { steps: 50 })
|
||||||
const hoveredColor: [number, number, number] = [93, 93, 127]
|
const hoveredColor: [number, number, number] = [93, 93, 127]
|
||||||
// now that we're expecting the user to select a plan, it does respond to hover
|
// now that we're expecting the user to select a plan, it does respond to hover
|
||||||
await expect
|
await expect
|
||||||
.poll(() => u.getGreatestPixDiff(XYPlanePoint, hoveredColor))
|
.poll(() => u.getGreatestPixDiff(XYPlanePoint, hoveredColor))
|
||||||
.toBeLessThan(8)
|
.toBeLessThan(8)
|
||||||
|
|
||||||
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
await page.waitForTimeout(600)
|
await page.waitForTimeout(600)
|
||||||
|
|
||||||
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
await page.mouse.click(XYPlanePoint.x, XYPlanePoint.y)
|
||||||
await page.waitForTimeout(200)
|
await page.waitForTimeout(200)
|
||||||
await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
|
await page.mouse.click(XYPlanePoint.x + 50, XYPlanePoint.y + 50)
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([11.8, 9.09], %)
|
|> startProfileAt([11.8, 9.09], %)
|
||||||
|> line([3.39, -3.39], %)
|
|> line([3.39, -3.39], %)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([11.8, 9.09], %)
|
|> startProfileAt([11.8, 9.09], %)
|
||||||
|> line([3.39, -3.39], %)
|
|> line([3.39, -3.39], %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// expect there to be no planes on load since there's something in the scene
|
// expect there to be no planes on load since there's something in the scene
|
||||||
expect(
|
expect(
|
||||||
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
await u.getGreatestPixDiff(XYPlanePoint, noPlanesColor)
|
||||||
).toBeLessThan(3)
|
).toBeLessThan(3)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
test('Can attempt to sketch on revolved face', async ({ page, homePage }) => {
|
test('Can attempt to sketch on revolved face', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
@ -1168,3 +1168,109 @@ test.fixme('theme persists', async ({ page, context }) => {
|
|||||||
maxDiffPixels: 100,
|
maxDiffPixels: 100,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||||
|
test('code color goober', async ({ page, context }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`// Create a pipe using a sweep.
|
||||||
|
|
||||||
|
// Create a path for the sweep.
|
||||||
|
sweepPath = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.05, 0.05], %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||||
|
|> line([-3, 0], %)
|
||||||
|
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|
||||||
|
sweepSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([2, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angleEnd = 360,
|
||||||
|
angleStart = 0,
|
||||||
|
radius = 2
|
||||||
|
}, %)
|
||||||
|
|> sweep({
|
||||||
|
path = sweepPath,
|
||||||
|
}, %)
|
||||||
|
|> appearance({
|
||||||
|
color = "#bb00ff",
|
||||||
|
metalness = 90,
|
||||||
|
roughness = 90
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await expect(page, 'expect small color widget').toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('code color goober opening window', async ({ page, context }) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await context.addInitScript(async () => {
|
||||||
|
localStorage.setItem(
|
||||||
|
'persistCode',
|
||||||
|
`// Create a pipe using a sweep.
|
||||||
|
|
||||||
|
// Create a path for the sweep.
|
||||||
|
sweepPath = startSketchOn('XZ')
|
||||||
|
|> startProfileAt([0.05, 0.05], %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|> tangentialArc({ offset = 90, radius = 5 }, %)
|
||||||
|
|> line([-3, 0], %)
|
||||||
|
|> tangentialArc({ offset = -90, radius = 5 }, %)
|
||||||
|
|> line([0, 7], %)
|
||||||
|
|
||||||
|
sweepSketch = startSketchOn('XY')
|
||||||
|
|> startProfileAt([2, 0], %)
|
||||||
|
|> arc({
|
||||||
|
angleEnd = 360,
|
||||||
|
angleStart = 0,
|
||||||
|
radius = 2
|
||||||
|
}, %)
|
||||||
|
|> sweep({
|
||||||
|
path = sweepPath,
|
||||||
|
}, %)
|
||||||
|
|> appearance({
|
||||||
|
color = "#bb00ff",
|
||||||
|
metalness = 90,
|
||||||
|
roughness = 90
|
||||||
|
}, %)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
await page.setViewportSize({ width: 1200, height: 1000 })
|
||||||
|
|
||||||
|
await u.waitForAuthSkipAppStart()
|
||||||
|
|
||||||
|
await u.openDebugPanel()
|
||||||
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
|
await u.clearAndCloseDebugPanel()
|
||||||
|
|
||||||
|
await expect(page.locator('.cm-css-color-picker-wrapper')).toBeVisible()
|
||||||
|
|
||||||
|
// Click the color widget
|
||||||
|
await page.locator('.cm-css-color-picker-wrapper input').click()
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page,
|
||||||
|
'expect small color widget to have window open'
|
||||||
|
).toHaveScreenshot({
|
||||||
|
maxDiffPixels: 100,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 148 KiB |
After Width: | Height: | Size: 144 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 132 KiB |
After Width: | Height: | Size: 128 KiB |
After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 42 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 41 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 38 KiB |
@ -14,7 +14,7 @@ export const TEST_SETTINGS = {
|
|||||||
},
|
},
|
||||||
modeling: {
|
modeling: {
|
||||||
defaultUnit: 'in',
|
defaultUnit: 'in',
|
||||||
mouseControls: 'KittyCAD',
|
mouseControls: 'Zoo',
|
||||||
cameraProjection: 'perspective',
|
cameraProjection: 'perspective',
|
||||||
showDebugPanel: true,
|
showDebugPanel: true,
|
||||||
},
|
},
|
||||||
|
@ -3,6 +3,7 @@ import {
|
|||||||
BrowserContext,
|
BrowserContext,
|
||||||
TestInfo,
|
TestInfo,
|
||||||
_electron as electron,
|
_electron as electron,
|
||||||
|
ElectronApplication,
|
||||||
Locator,
|
Locator,
|
||||||
} from '@playwright/test'
|
} from '@playwright/test'
|
||||||
import { test, Page } from './zoo-test'
|
import { test, Page } from './zoo-test'
|
||||||
@ -28,130 +29,6 @@ import { isErrorWhitelisted } from './lib/console-error-whitelist'
|
|||||||
import { isArray } from 'lib/utils'
|
import { isArray } from 'lib/utils'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
// The below is copied from playwright-core because it exports none of them :(
|
|
||||||
import { Env, BrowserContextOptions } from 'playwright-core'
|
|
||||||
import type * as channels from '@protocol/channels'
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
function envObjectToArray(env: Env): { name: string; value: string }[] {
|
|
||||||
const result: { name: string; value: string }[] = []
|
|
||||||
for (const name in env) {
|
|
||||||
if (!Object.is(env[name], undefined))
|
|
||||||
result.push({ name, value: String(env[name]) })
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
export async function toClientCertificatesProtocol(
|
|
||||||
certs?: BrowserContextOptions['clientCertificates']
|
|
||||||
): Promise<channels.PlaywrightNewRequestParams['clientCertificates']> {
|
|
||||||
if (!certs) return undefined
|
|
||||||
|
|
||||||
const bufferizeContent = async (
|
|
||||||
value?: Buffer,
|
|
||||||
path?: string
|
|
||||||
): Promise<Buffer | undefined> => {
|
|
||||||
if (value) return value
|
|
||||||
if (path) return await fs.promises.readFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return await Promise.all(
|
|
||||||
certs.map(async (cert) => ({
|
|
||||||
origin: cert.origin,
|
|
||||||
cert: await bufferizeContent(cert.cert, cert.certPath),
|
|
||||||
key: await bufferizeContent(cert.key, cert.keyPath),
|
|
||||||
pfx: await bufferizeContent(cert.pfx, cert.pfxPath),
|
|
||||||
passphrase: cert.passphrase,
|
|
||||||
}))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
function toAcceptDownloadsProtocol(acceptDownloads?: boolean) {
|
|
||||||
if (acceptDownloads === undefined) return undefined
|
|
||||||
if (acceptDownloads) return 'accept'
|
|
||||||
return 'deny'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
function prepareRecordHarOptions(
|
|
||||||
options: BrowserContextOptions['recordHar']
|
|
||||||
): channels.RecordHarOptions | undefined {
|
|
||||||
if (!options) return
|
|
||||||
return {
|
|
||||||
path: options.path,
|
|
||||||
content: options.content || (options.omitContent ? 'omit' : undefined),
|
|
||||||
urlGlob: isString(options.urlFilter) ? options.urlFilter : undefined,
|
|
||||||
urlRegexSource: isRegExp(options.urlFilter)
|
|
||||||
? options.urlFilter.source
|
|
||||||
: undefined,
|
|
||||||
urlRegexFlags: isRegExp(options.urlFilter)
|
|
||||||
? options.urlFilter.flags
|
|
||||||
: undefined,
|
|
||||||
mode: options.mode,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
async function prepareStorageState(
|
|
||||||
options: BrowserContextOptions
|
|
||||||
): Promise<channels.BrowserNewContextParams['storageState']> {
|
|
||||||
if (typeof options.storageState !== 'string') return options.storageState
|
|
||||||
try {
|
|
||||||
return JSON.parse(await fs.promises.readFile(options.storageState, 'utf8'))
|
|
||||||
} catch (e) {
|
|
||||||
rewriteErrorMessage(
|
|
||||||
e,
|
|
||||||
`Error reading storage state from ${options.storageState}:\n` + e.message
|
|
||||||
)
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied from playwright-core
|
|
||||||
async function prepareBrowserContextParams(
|
|
||||||
options: BrowserContextOptions
|
|
||||||
): Promise<channels.BrowserNewContextParams> {
|
|
||||||
if (options.videoSize && !options.videosPath)
|
|
||||||
throw new Error(`"videoSize" option requires "videosPath" to be specified`)
|
|
||||||
if (options.extraHTTPHeaders)
|
|
||||||
network.validateHeaders(options.extraHTTPHeaders)
|
|
||||||
const contextParams: channels.BrowserNewContextParams = {
|
|
||||||
...options,
|
|
||||||
viewport: options.viewport === null ? undefined : options.viewport,
|
|
||||||
noDefaultViewport: options.viewport === null,
|
|
||||||
extraHTTPHeaders: options.extraHTTPHeaders
|
|
||||||
? headersObjectToArray(options.extraHTTPHeaders)
|
|
||||||
: undefined,
|
|
||||||
storageState: await prepareStorageState(options),
|
|
||||||
serviceWorkers: options.serviceWorkers,
|
|
||||||
recordHar: prepareRecordHarOptions(options.recordHar),
|
|
||||||
colorScheme:
|
|
||||||
options.colorScheme === null ? 'no-override' : options.colorScheme,
|
|
||||||
reducedMotion:
|
|
||||||
options.reducedMotion === null ? 'no-override' : options.reducedMotion,
|
|
||||||
forcedColors:
|
|
||||||
options.forcedColors === null ? 'no-override' : options.forcedColors,
|
|
||||||
acceptDownloads: toAcceptDownloadsProtocol(options.acceptDownloads),
|
|
||||||
clientCertificates: await toClientCertificatesProtocol(
|
|
||||||
options.clientCertificates
|
|
||||||
),
|
|
||||||
}
|
|
||||||
if (!contextParams.recordVideo && options.videosPath) {
|
|
||||||
contextParams.recordVideo = {
|
|
||||||
dir: options.videosPath,
|
|
||||||
size: options.videoSize,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (contextParams.recordVideo && contextParams.recordVideo.dir)
|
|
||||||
contextParams.recordVideo.dir = path.resolve(
|
|
||||||
process.cwd(),
|
|
||||||
contextParams.recordVideo.dir
|
|
||||||
)
|
|
||||||
return contextParams
|
|
||||||
}
|
|
||||||
|
|
||||||
const toNormalizedCode = (text: string) => {
|
const toNormalizedCode = (text: string) => {
|
||||||
return text.replace(/\s+/g, '')
|
return text.replace(/\s+/g, '')
|
||||||
}
|
}
|
||||||
@ -1042,9 +919,9 @@ export async function setup(
|
|||||||
// await page.reload()
|
// await page.reload()
|
||||||
}
|
}
|
||||||
|
|
||||||
let electronApp = undefined
|
let electronApp: ElectronApplication | undefined = undefined
|
||||||
let context = undefined
|
let context: BrowserContext | undefined = undefined
|
||||||
let page = undefined
|
let page: Page | undefined = undefined
|
||||||
|
|
||||||
export async function setupElectron({
|
export async function setupElectron({
|
||||||
testInfo,
|
testInfo,
|
||||||
@ -1055,7 +932,12 @@ export async function setupElectron({
|
|||||||
folderSetupFn?: (projectDirName: string) => Promise<void>
|
folderSetupFn?: (projectDirName: string) => Promise<void>
|
||||||
cleanProjectDir?: boolean
|
cleanProjectDir?: boolean
|
||||||
appSettings?: Partial<SaveSettingsPayload>
|
appSettings?: Partial<SaveSettingsPayload>
|
||||||
}) {
|
}): Promise<{
|
||||||
|
electronApp: ElectronApplication
|
||||||
|
context: BrowserContext
|
||||||
|
page: Page
|
||||||
|
dir: string
|
||||||
|
}> {
|
||||||
// create or otherwise clear the folder
|
// create or otherwise clear the folder
|
||||||
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
const projectDirName = testInfo.outputPath('electron-test-projects-dir')
|
||||||
try {
|
try {
|
||||||
@ -1122,7 +1004,7 @@ export async function setupElectron({
|
|||||||
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
await fsp.writeFile(tempSettingsFilePath, settingsOverrides)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { electronApp, page, context, dir: projectDirName, options }
|
return { electronApp, page, context, dir: projectDirName }
|
||||||
}
|
}
|
||||||
|
|
||||||
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
function failOnConsoleErrors(page: Page, testInfo?: TestInfo) {
|
||||||
|
@ -5,10 +5,16 @@ import { getUtils } from './test-utils'
|
|||||||
|
|
||||||
test.describe('Testing Camera Movement', () => {
|
test.describe('Testing Camera Movement', () => {
|
||||||
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
test('Can move camera reliably', async ({ page, context, homePage }) => {
|
||||||
|
// TODO: fix this test on windows too after the electron migration
|
||||||
|
const winOrMac =
|
||||||
|
process.platform === 'win32' || process.platform === 'darwin'
|
||||||
|
// eslint-disable-next-line
|
||||||
|
test.skip(winOrMac, 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
|
|
||||||
@ -172,174 +178,177 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
}, [0, -85, -85])
|
}, [0, -85, -85])
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Zoom should be consistent when exiting or entering sketches', async ({
|
// TODO: fix after electron migration is merged
|
||||||
page,
|
test.fixme(
|
||||||
homePage,
|
'Zoom should be consistent when exiting or entering sketches',
|
||||||
}) => {
|
async ({ page, homePage }) => {
|
||||||
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
// start new sketch pan and zoom before exiting, when exiting the sketch should stay in the same place
|
||||||
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
// than zoom and pan outside of sketch mode and enter again and it should not change from where it is
|
||||||
// than again for sketching
|
// than again for sketching
|
||||||
|
|
||||||
test.skip(process.platform !== 'darwin', 'Zoom should be consistent')
|
const u = await getUtils(page)
|
||||||
const u = await getUtils(page)
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.openDebugPanel()
|
await u.waitForPageLoad()
|
||||||
|
await u.openDebugPanel()
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
|
|
||||||
// click on "Start Sketch" button
|
// click on "Start Sketch" button
|
||||||
await u.clearCommandLogs()
|
await u.clearCommandLogs()
|
||||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
await page.waitForTimeout(100)
|
|
||||||
|
|
||||||
// select a plane
|
|
||||||
await page.mouse.click(700, 325)
|
|
||||||
|
|
||||||
let code = `sketch001 = startSketchOn('XY')`
|
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.closeDebugPanel()
|
|
||||||
|
|
||||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
|
||||||
|
|
||||||
// move the camera slightly
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.move(700, 300)
|
|
||||||
await page.mouse.down({ button: 'right' })
|
|
||||||
await page.mouse.move(800, 200)
|
|
||||||
await page.mouse.up({ button: 'right' })
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
|
|
||||||
let y = 350,
|
|
||||||
x = 948
|
|
||||||
|
|
||||||
await u.canvasLocator.click({ position: { x: 783, y } })
|
|
||||||
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.canvasLocator.click({ position: { x, y } })
|
|
||||||
code += `\n |> line([11.18, 0], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
await u.canvasLocator.click({ position: { x, y: 275 } })
|
|
||||||
code += `\n |> line([0, 6.99], %)`
|
|
||||||
// await expect(u.codeLocator).toHaveText(code)
|
|
||||||
|
|
||||||
// click the line button
|
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
|
||||||
|
|
||||||
const hoverOverNothing = async () => {
|
|
||||||
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
|
||||||
await page.mouse.move(700, 325)
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
|
||||||
|
// select a plane
|
||||||
|
await page.mouse.click(700, 325)
|
||||||
|
|
||||||
|
let code = `sketch001 = startSketchOn('XY')`
|
||||||
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
|
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||||
|
|
||||||
|
// move the camera slightly
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.mouse.move(700, 300)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(800, 200)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
let y = 350,
|
||||||
|
x = 948
|
||||||
|
|
||||||
|
await u.canvasLocator.click({ position: { x: 783, y } })
|
||||||
|
code += `\n |> startProfileAt([8.12, -12.98], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y } })
|
||||||
|
code += `\n |> line([11.18, 0], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
await u.canvasLocator.click({ position: { x, y: 275 } })
|
||||||
|
code += `\n |> line([0, 6.99], %)`
|
||||||
|
// await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
|
// click the line button
|
||||||
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
|
|
||||||
|
const hoverOverNothing = async () => {
|
||||||
|
// await u.canvasLocator.hover({position: {x: 700, y: 325}})
|
||||||
|
await page.mouse.move(700, 325)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||||
|
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await u.canvasLocator.hover({ position: { x: 800, y } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over vertical line
|
||||||
|
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// click exit sketch
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
// hover over horizontal line
|
||||||
|
await page.mouse.move(858, y, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(x, 325)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
// hover over vertical line
|
||||||
|
await page.mouse.move(857, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
// now click it
|
||||||
|
await page.mouse.click(857, y)
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
await page.waitForTimeout(400)
|
||||||
|
|
||||||
|
x = 975
|
||||||
|
y = 468
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.mouse.move(x, 419, { steps: 5 })
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
await page.waitForTimeout(200)
|
||||||
|
|
||||||
|
await page.mouse.move(x, 419)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
|
await hoverOverNothing()
|
||||||
|
|
||||||
|
await page.mouse.move(855, y)
|
||||||
|
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
)
|
||||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
|
||||||
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over horizontal line
|
|
||||||
await u.canvasLocator.hover({ position: { x: 800, y } })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over vertical line
|
|
||||||
await u.canvasLocator.hover({ position: { x, y: 325 } })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// click exit sketch
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
// hover over horizontal line
|
|
||||||
await page.mouse.move(858, y, { steps: 5 })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// hover over vertical line
|
|
||||||
await page.mouse.move(x, 325)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
// hover over vertical line
|
|
||||||
await page.mouse.move(857, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
// now click it
|
|
||||||
await page.mouse.click(857, y)
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
|
||||||
).toBeVisible()
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
|
|
||||||
await page.waitForTimeout(400)
|
|
||||||
|
|
||||||
x = 975
|
|
||||||
y = 468
|
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
|
||||||
await page.mouse.move(x, 419, { steps: 5 })
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.mouse.move(855, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
await page.waitForTimeout(200)
|
|
||||||
|
|
||||||
await page.mouse.move(x, 419)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
|
|
||||||
await hoverOverNothing()
|
|
||||||
|
|
||||||
await page.mouse.move(855, y)
|
|
||||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible({
|
|
||||||
timeout: 10_000,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test(`Zoom by scroll should not fire while orbiting`, async ({
|
test(`Zoom by scroll should not fire while orbiting`, async ({
|
||||||
page,
|
|
||||||
homePage,
|
homePage,
|
||||||
|
page,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
/**
|
/**
|
||||||
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
* Currently we only allow zooming by scroll when no other camera movement is happening,
|
||||||
* set within cameraMouseDragGuards in cameraControls.ts,
|
* set within cameraMouseDragGuards in cameraControls.ts,
|
||||||
@ -379,6 +388,7 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
|
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
await u.closeKclCodePanel()
|
await u.closeKclCodePanel()
|
||||||
// This test requires the mouse controls to be set to Solidworks
|
// This test requires the mouse controls to be set to Solidworks
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
@ -474,4 +484,31 @@ test.describe('Testing Camera Movement', () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Right-click opens context menu when not dragged', async ({
|
||||||
|
homePage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
const u = await getUtils(page)
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await test.step(`The menu should not show if we drag the mouse`, async () => {
|
||||||
|
await page.mouse.move(900, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.move(900, 300)
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
|
await expect(page.getByTestId('view-controls-menu')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step(`The menu should show if we don't drag the mouse`, async () => {
|
||||||
|
await page.mouse.move(900, 200)
|
||||||
|
await page.mouse.down({ button: 'right' })
|
||||||
|
await page.mouse.up({ button: 'right' })
|
||||||
|
|
||||||
|
await expect(page.getByTestId('view-controls-menu')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import { test, expect } from './zoo-test'
|
import { test, expect } from './zoo-test'
|
||||||
|
import * as fsp from 'fs/promises'
|
||||||
import {
|
import {
|
||||||
getUtils,
|
getUtils,
|
||||||
TEST_COLORS,
|
TEST_COLORS,
|
||||||
pollEditorLinesSelectedLength,
|
pollEditorLinesSelectedLength,
|
||||||
|
executorInputPath,
|
||||||
} from './test-utils'
|
} from './test-utils'
|
||||||
import { XOR } from 'lib/utils'
|
import { XOR } from 'lib/utils'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
test.describe('Testing constraints', () => {
|
test.describe('Testing constraints', () => {
|
||||||
test('Can constrain line length', async ({ page, homePage }) => {
|
test('Can constrain line length', async ({ page, homePage }) => {
|
||||||
@ -23,7 +25,7 @@ test.describe('Testing constraints', () => {
|
|||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
@ -43,15 +45,16 @@ test.describe('Testing constraints', () => {
|
|||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
const startXPx = 500
|
const startXPx = 500
|
||||||
await page.mouse.move(startXPx + PUR * 15, 250 - PUR * 10)
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
await page.mouse.click(834, 244)
|
|
||||||
await page.keyboard.up('Shift')
|
|
||||||
|
|
||||||
|
await page.getByText(`line([0, 20], %)`).click()
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await page.getByTestId('constraint-length').click()
|
||||||
|
await page.getByTestId('cmd-bar-arg-value').getByRole('textbox').fill('20')
|
||||||
await page
|
await page
|
||||||
.getByRole('button', { name: 'dimension Length', exact: true })
|
.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
.click()
|
.click()
|
||||||
await page.getByText('Add constraining value').click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-content')).toHaveText(
|
await expect(page.locator('.cm-content')).toHaveText(
|
||||||
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
`length001 = 20sketch001 = startSketchOn('XY') |> startProfileAt([-10, -10], %) |> line([20, 0], %) |> angledLine([90, length001], %) |> xLine(-20, %)`
|
||||||
@ -71,7 +74,7 @@ test.describe('Testing constraints', () => {
|
|||||||
await page.keyboard.press('Escape', { delay: 500 })
|
await page.keyboard.press('Escape', { delay: 500 })
|
||||||
return page.getByRole('button', { name: 'Exit Sketch' }).isVisible()
|
return page.getByRole('button', { name: 'Exit Sketch' }).isVisible()
|
||||||
})
|
})
|
||||||
.toBe(true)
|
.toBe(false)
|
||||||
})
|
})
|
||||||
test(`Remove constraints`, async ({ page, homePage }) => {
|
test(`Remove constraints`, async ({ page, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
@ -124,6 +127,8 @@ test.describe('Testing constraints', () => {
|
|||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(4)
|
||||||
})
|
})
|
||||||
test.describe('Test perpendicular distance constraint', () => {
|
test.describe('Test perpendicular distance constraint', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -244,6 +249,8 @@ test.describe('Testing constraints', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test distance between constraint', () => {
|
test.describe('Test distance between constraint', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -463,6 +470,8 @@ test.describe('Testing constraints', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test Angle constraint double segment selection', () => {
|
test.describe('Test Angle constraint double segment selection', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Add variable',
|
testName: 'Add variable',
|
||||||
@ -653,6 +662,8 @@ test.describe('Testing constraints', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Test Length constraint single selection', () => {
|
test.describe('Test Length constraint single selection', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testName: 'Length - Add variable',
|
testName: 'Length - Add variable',
|
||||||
@ -838,6 +849,8 @@ part002 = startSketchOn('XZ')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
test.describe('Two segment - no modal constraints', () => {
|
test.describe('Two segment - no modal constraints', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
codeAfter: `|> angledLine([83, segLen(seg01)], %)`,
|
||||||
@ -998,104 +1011,162 @@ part002 = startSketchOn('XZ')
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
test('Horizontally constrained line remains selected after applying constraint', async ({
|
test.fixme(
|
||||||
page,
|
'Horizontally constrained line remains selected after applying constraint',
|
||||||
homePage,
|
async ({ page, homePage }) => {
|
||||||
}) => {
|
test.setTimeout(70_000)
|
||||||
test.setTimeout(70_000)
|
await page.addInitScript(async () => {
|
||||||
await page.addInitScript(async () => {
|
localStorage.setItem(
|
||||||
localStorage.setItem(
|
'persistCode',
|
||||||
'persistCode',
|
`sketch001 = startSketchOn('XY')
|
||||||
`sketch001 = startSketchOn('XY')
|
|
||||||
|> startProfileAt([-1.05, -1.07], %)
|
|> startProfileAt([-1.05, -1.07], %)
|
||||||
|> line([3.79, 2.68], %, $seg01)
|
|> line([3.79, 2.68], %, $seg01)
|
||||||
|> line([3.13, -2.4], %)`
|
|> line([3.13, -2.4], %)`
|
||||||
|
)
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
|
await homePage.goToModelingScene()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeEnabled({ timeout: 10_000 })
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
|
||||||
|
// Wait for overlays to populate
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
const lineBefore = await u.getSegmentBodyCoords(
|
||||||
|
`[data-overlay-index="1"]`,
|
||||||
|
0
|
||||||
)
|
)
|
||||||
})
|
expect(
|
||||||
const u = await getUtils(page)
|
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.WHITE)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
).toBeLessThan(3)
|
||||||
|
await page.mouse.move(lineBefore.x, lineBefore.y)
|
||||||
|
await page.waitForTimeout(50)
|
||||||
|
await page.mouse.click(lineBefore.x, lineBefore.y)
|
||||||
|
expect(
|
||||||
|
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.BLUE)
|
||||||
|
).toBeLessThan(3)
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await page
|
||||||
await u.waitForPageLoad()
|
.getByRole('button', {
|
||||||
|
name: 'Length: open menu',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'Horizontal', exact: true })
|
||||||
|
.click()
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
await page.getByText('line([3.79, 2.68], %, $seg01)').click()
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
{ timeout: 10_000 }
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
||||||
)
|
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
|
||||||
|
|
||||||
// Wait for overlays to populate
|
// Wait for code editor to settle.
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(2000)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
// If the overlay-angle is updated the THREE.js scene is in a good state
|
||||||
const lineBefore = await u.getSegmentBodyCoords(
|
await expect(
|
||||||
`[data-overlay-index="1"]`,
|
await page.locator('[data-overlay-index="1"]')
|
||||||
0
|
).toHaveAttribute('data-overlay-angle', '0')
|
||||||
)
|
|
||||||
expect(
|
|
||||||
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.WHITE)
|
|
||||||
).toBeLessThan(3)
|
|
||||||
await page.mouse.move(lineBefore.x, lineBefore.y)
|
|
||||||
await page.waitForTimeout(50)
|
|
||||||
await page.mouse.click(lineBefore.x, lineBefore.y)
|
|
||||||
expect(
|
|
||||||
await u.getGreatestPixDiff(lineBefore, TEST_COLORS.BLUE)
|
|
||||||
).toBeLessThan(3)
|
|
||||||
|
|
||||||
await page
|
const lineAfter = await u.getSegmentBodyCoords(
|
||||||
.getByRole('button', {
|
`[data-overlay-index="1"]`,
|
||||||
name: 'Length: open menu',
|
0
|
||||||
})
|
)
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
await page.getByRole('button', { name: 'Horizontal', exact: true }).click()
|
|
||||||
await page.waitForTimeout(500)
|
|
||||||
|
|
||||||
await pollEditorLinesSelectedLength(page, 1)
|
const linebb = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||||
let activeLinesContent = await page.locator('.cm-activeLine').all()
|
await page.mouse.move(linebb.x, linebb.y, { steps: 25 })
|
||||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(3.13, %)`)
|
await page.mouse.click(linebb.x, linebb.y)
|
||||||
|
|
||||||
// Wait for code editor to settle.
|
await expect
|
||||||
await page.waitForTimeout(2000)
|
.poll(
|
||||||
|
async () => await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE)
|
||||||
|
)
|
||||||
|
.toBeLessThan(3)
|
||||||
|
|
||||||
// If the overlay-angle is updated the THREE.js scene is in a good state
|
await page.waitForTimeout(500)
|
||||||
await expect(
|
|
||||||
await page.locator('[data-overlay-index="1"]')
|
|
||||||
).toHaveAttribute('data-overlay-angle', '0')
|
|
||||||
|
|
||||||
const lineAfter = await u.getSegmentBodyCoords(
|
// await expect(page.getByRole('button', { name: 'length', exact: true })).toBeVisible()
|
||||||
`[data-overlay-index="1"]`,
|
await page.waitForTimeout(200)
|
||||||
0
|
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
||||||
)
|
await page.getByTestId('constraint-length').click()
|
||||||
|
|
||||||
const linebb = await u.getBoundingBox('[data-overlay-index="1"]')
|
await page
|
||||||
await page.mouse.move(linebb.x, linebb.y, { steps: 25 })
|
.getByTestId('cmd-bar-arg-value')
|
||||||
await page.mouse.click(linebb.x, linebb.y)
|
.getByRole('textbox')
|
||||||
|
.fill('10')
|
||||||
|
await page
|
||||||
|
.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
.click()
|
||||||
|
|
||||||
await expect
|
await pollEditorLinesSelectedLength(page, 1)
|
||||||
.poll(async () => await u.getGreatestPixDiff(lineAfter, TEST_COLORS.BLUE))
|
activeLinesContent = await page.locator('.cm-activeLine').all()
|
||||||
.toBeLessThan(3)
|
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(500)
|
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
||||||
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||||
await page
|
}
|
||||||
.getByRole('button', {
|
)
|
||||||
name: 'Length: open menu',
|
})
|
||||||
})
|
test.describe('Electron constraint tests', () => {
|
||||||
.click()
|
test(
|
||||||
// await expect(page.getByRole('button', { name: 'length', exact: true })).toBeVisible()
|
'Able to double click label to set constraint',
|
||||||
await page.waitForTimeout(200)
|
{ tag: '@electron' },
|
||||||
// await page.getByRole('button', { name: 'length', exact: true }).click()
|
async ({ page, context, homePage, scene, editor, toolbar }) => {
|
||||||
await page.getByTestId('dropdown-constraint-length').click()
|
await context.folderSetupFn(async (dir) => {
|
||||||
|
const bracketDir = path.join(dir, 'test-sample')
|
||||||
await page.getByLabel('length Value').fill('10')
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
await page.getByRole('button', { name: 'Add constraining value' }).click()
|
await fsp.copyFile(
|
||||||
|
executorInputPath('angled_line.kcl'),
|
||||||
await pollEditorLinesSelectedLength(page, 1)
|
path.join(bracketDir, 'main.kcl')
|
||||||
activeLinesContent = await page.locator('.cm-activeLine').all()
|
)
|
||||||
await expect(activeLinesContent[0]).toHaveText(`|> xLine(length001, %)`)
|
})
|
||||||
|
const [clickHandler] = scene.makeMouseHelpers(600, 300)
|
||||||
// checking the count of the overlays is a good proxy check that the client sketch scene is in a good state
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
await test.step('setup test', async () => {
|
||||||
})
|
await homePage.expectState({
|
||||||
|
projectCards: [
|
||||||
|
{
|
||||||
|
title: 'test-sample',
|
||||||
|
fileCount: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await homePage.openProject('test-sample')
|
||||||
|
await scene.waitForExecutionDone()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Double click to constrain', async () => {
|
||||||
|
await clickHandler()
|
||||||
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
|
const child = page
|
||||||
|
.locator('.segment-length-label-text')
|
||||||
|
.first()
|
||||||
|
.locator('xpath=..')
|
||||||
|
await child.dblclick()
|
||||||
|
const cmdBarSubmitButton = page.getByRole('button', {
|
||||||
|
name: 'arrow right Continue',
|
||||||
|
})
|
||||||
|
await cmdBarSubmitButton.click()
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
'length001 = 15.3'
|
||||||
|
)
|
||||||
|
await expect(page.locator('.cm-content')).toContainText(
|
||||||
|
'|> angledLine([9, length001], %)'
|
||||||
|
)
|
||||||
|
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -4,6 +4,8 @@ import { uuidv4 } from 'lib/utils'
|
|||||||
import { TEST_CODE_GIZMO } from './storageStates'
|
import { TEST_CODE_GIZMO } from './storageStates'
|
||||||
|
|
||||||
test.describe('Testing Gizmo', () => {
|
test.describe('Testing Gizmo', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
testDescription: 'top view',
|
testDescription: 'top view',
|
||||||
|
@ -6,7 +6,11 @@ import { uuidv4 } from 'lib/utils'
|
|||||||
import { EditorFixture } from './fixtures/editorFixture'
|
import { EditorFixture } from './fixtures/editorFixture'
|
||||||
|
|
||||||
test.describe('Testing segment overlays', () => {
|
test.describe('Testing segment overlays', () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
test.describe('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => {
|
test.describe('Hover over a segment should show its overlay, hovering over the input overlays should show its popover, clicking the input overlay should constrain/unconstrain it:\nfor the following segments', () => {
|
||||||
|
// TODO: fix this test on mac after the electron migration
|
||||||
|
test.skip(process.platform === 'darwin', 'Skip on mac')
|
||||||
/**
|
/**
|
||||||
* Clicks on an constrained element
|
* Clicks on an constrained element
|
||||||
* @param {Page} page - The page to perform the action on
|
* @param {Page} page - The page to perform the action on
|
||||||
@ -226,7 +230,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -299,9 +303,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLine.x, y: angledLine.y },
|
hoverPos: { x: angledLine.x, y: angledLine.y },
|
||||||
constraintType: 'angle',
|
constraintType: 'angle',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLine({ angle: 3 + 0, length: 32 + 0 }, %)',
|
'angledLine({ angle = 3 + 0, length = 32 + 0 }, %)',
|
||||||
expectAfterUnconstrained: 'angledLine({ angle: 3, length: 32 + 0 }, %)',
|
expectAfterUnconstrained:
|
||||||
expectFinal: 'angledLine({ angle: angle001, length: 32 + 0 }, %)',
|
'angledLine({ angle = 3, length = 32 + 0 }, %)',
|
||||||
|
expectFinal: 'angledLine({ angle = angle001, length = 32 + 0 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="1"]',
|
locator: '[data-overlay-toolbar-index="1"]',
|
||||||
})
|
})
|
||||||
@ -310,10 +315,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLine.x, y: angledLine.y },
|
hoverPos: { x: angledLine.x, y: angledLine.y },
|
||||||
constraintType: 'length',
|
constraintType: 'length',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLine({ angle: angle001, length: 32 + 0 }, %)',
|
'angledLine({ angle = angle001, length = 32 + 0 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLine({ angle: angle001, length: 32 }, %)',
|
'angledLine({ angle = angle001, length = 32 }, %)',
|
||||||
expectFinal: 'angledLine({ angle: angle001, length: len001 }, %)',
|
expectFinal: 'angledLine({ angle = angle001, length = len001 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="1"]',
|
locator: '[data-overlay-toolbar-index="1"]',
|
||||||
})
|
})
|
||||||
@ -360,15 +365,15 @@ test.describe('Testing segment overlays', () => {
|
|||||||
locator: '[data-overlay-toolbar-index="3"]',
|
locator: '[data-overlay-toolbar-index="3"]',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
test('for segments [yLineTo, xLine]', async ({
|
|
||||||
page,
|
// Broken on main at time of writing!
|
||||||
editor,
|
test.fixme(
|
||||||
homePage,
|
'for segments [yLineTo, xLine]',
|
||||||
}) => {
|
async ({ page, editor, homePage }) => {
|
||||||
await page.addInitScript(async () => {
|
await page.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'persistCode',
|
'persistCode',
|
||||||
`yRel001 = -14
|
`yRel001 = -14
|
||||||
xRel001 = 0.5
|
xRel001 = 0.5
|
||||||
angle001 = 3
|
angle001 = 3
|
||||||
len001 = 32
|
len001 = 32
|
||||||
@ -386,59 +391,60 @@ test.describe('Testing segment overlays', () => {
|
|||||||
|> yLine(21.14 + 0, %)
|
|> yLine(21.14 + 0, %)
|
||||||
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
|> angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)
|
||||||
`
|
`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page.getByText('xLine(26.04, %)').click()
|
await page.getByText('xLine(26.04, %)').click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(8)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(8)
|
||||||
|
|
||||||
const clickUnconstrained = _clickUnconstrained(page, editor)
|
const clickUnconstrained = _clickUnconstrained(page, editor)
|
||||||
|
|
||||||
await page.mouse.move(700, 250)
|
await page.mouse.move(700, 250)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
let ang = 0
|
let ang = 0
|
||||||
|
|
||||||
const yLineTo = await u.getBoundingBox(`[data-overlay-index="4"]`)
|
const yLineTo = await u.getBoundingBox(`[data-overlay-index="4"]`)
|
||||||
ang = await u.getAngle(`[data-overlay-index="4"]`)
|
ang = await u.getAngle(`[data-overlay-index="4"]`)
|
||||||
console.log('ylineTo1')
|
console.log('ylineTo1')
|
||||||
await clickUnconstrained({
|
await clickUnconstrained({
|
||||||
hoverPos: { x: yLineTo.x, y: yLineTo.y },
|
hoverPos: { x: yLineTo.x, y: yLineTo.y - 200 },
|
||||||
constraintType: 'yAbsolute',
|
constraintType: 'yAbsolute',
|
||||||
expectBeforeUnconstrained: 'yLineTo(-10.77, %, $a)',
|
expectBeforeUnconstrained: 'yLineTo(-10.77, %, $a)',
|
||||||
expectAfterUnconstrained: 'yLineTo(yAbs002, %, $a)',
|
expectAfterUnconstrained: 'yLineTo(yAbs002, %, $a)',
|
||||||
expectFinal: 'yLineTo(-10.77, %, $a)',
|
expectFinal: 'yLineTo(-10.77, %, $a)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="4"]',
|
locator: '[data-overlay-toolbar-index="4"]',
|
||||||
})
|
})
|
||||||
|
|
||||||
const xLine = await u.getBoundingBox(`[data-overlay-index="5"]`)
|
const xLine = await u.getBoundingBox(`[data-overlay-index="5"]`)
|
||||||
ang = await u.getAngle(`[data-overlay-index="5"]`)
|
ang = await u.getAngle(`[data-overlay-index="5"]`)
|
||||||
console.log('xline')
|
console.log('xline')
|
||||||
await clickUnconstrained({
|
await clickUnconstrained({
|
||||||
hoverPos: { x: xLine.x, y: xLine.y },
|
hoverPos: { x: xLine.x, y: xLine.y },
|
||||||
constraintType: 'xRelative',
|
constraintType: 'xRelative',
|
||||||
expectBeforeUnconstrained: 'xLine(26.04, %)',
|
expectBeforeUnconstrained: 'xLine(26.04, %)',
|
||||||
expectAfterUnconstrained: 'xLine(xRel002, %)',
|
expectAfterUnconstrained: 'xLine(xRel002, %)',
|
||||||
expectFinal: 'xLine(26.04, %)',
|
expectFinal: 'xLine(26.04, %)',
|
||||||
steps: 10,
|
steps: 10,
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="5"]',
|
locator: '[data-overlay-toolbar-index="5"]',
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
)
|
||||||
test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({
|
test('for segments [yLine, angledLineOfXLength, angledLineOfYLength]', async ({
|
||||||
page,
|
page,
|
||||||
editor,
|
editor,
|
||||||
@ -471,7 +477,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -515,11 +521,11 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
||||||
constraintType: 'angle',
|
constraintType: 'angle',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineOfXLength({ angle: 181 + 0, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineOfXLength({ angle: -179, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = -179, length = 23.14 }, %)',
|
||||||
expectFinal:
|
expectFinal:
|
||||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = angle001, length = 23.14 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="7"]',
|
locator: '[data-overlay-toolbar-index="7"]',
|
||||||
})
|
})
|
||||||
@ -528,11 +534,11 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
hoverPos: { x: angledLineOfXLength.x, y: angledLineOfXLength.y },
|
||||||
constraintType: 'xRelative',
|
constraintType: 'xRelative',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = angle001, length = 23.14 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineOfXLength({ angle: angle001, length: xRel001 }, %)',
|
'angledLineOfXLength({ angle = angle001, length = xRel001 }, %)',
|
||||||
expectFinal:
|
expectFinal:
|
||||||
'angledLineOfXLength({ angle: angle001, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = angle001, length = 23.14 }, %)',
|
||||||
steps: 7,
|
steps: 7,
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="7"]',
|
locator: '[data-overlay-toolbar-index="7"]',
|
||||||
@ -547,10 +553,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
||||||
constraintType: 'angle',
|
constraintType: 'angle',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
'angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineOfYLength({ angle: angle002, length: 19 + 0 }, %)',
|
'angledLineOfYLength({ angle = angle002, length = 19 + 0 }, %)',
|
||||||
expectFinal: 'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
expectFinal: 'angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
steps: 6,
|
steps: 6,
|
||||||
locator: '[data-overlay-toolbar-index="8"]',
|
locator: '[data-overlay-toolbar-index="8"]',
|
||||||
@ -560,10 +566,11 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
hoverPos: { x: angledLineOfYLength.x, y: angledLineOfYLength.y },
|
||||||
constraintType: 'yRelative',
|
constraintType: 'yRelative',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
'angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineOfYLength({ angle: -91, length: 19 }, %)',
|
'angledLineOfYLength({ angle = -91, length = 19 }, %)',
|
||||||
expectFinal: 'angledLineOfYLength({ angle: -91, length: yRel002 }, %)',
|
expectFinal:
|
||||||
|
'angledLineOfYLength({ angle = -91, length = yRel002 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
steps: 7,
|
steps: 7,
|
||||||
locator: '[data-overlay-toolbar-index="8"]',
|
locator: '[data-overlay-toolbar-index="8"]',
|
||||||
@ -601,7 +608,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -628,9 +635,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await clickConstrained({
|
await clickConstrained({
|
||||||
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
||||||
constraintType: 'angle',
|
constraintType: 'angle',
|
||||||
expectBeforeUnconstrained: 'angledLineToX({ angle: 3 + 0, to: 26 }, %)',
|
expectBeforeUnconstrained:
|
||||||
expectAfterUnconstrained: 'angledLineToX({ angle: 3, to: 26 }, %)',
|
'angledLineToX({ angle = 3 + 0, to = 26 }, %)',
|
||||||
expectFinal: 'angledLineToX({ angle: angle001, to: 26 }, %)',
|
expectAfterUnconstrained: 'angledLineToX({ angle = 3, to = 26 }, %)',
|
||||||
|
expectFinal: 'angledLineToX({ angle = angle001, to = 26 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="9"]',
|
locator: '[data-overlay-toolbar-index="9"]',
|
||||||
})
|
})
|
||||||
@ -639,10 +647,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
hoverPos: { x: angledLineToX.x, y: angledLineToX.y },
|
||||||
constraintType: 'xAbsolute',
|
constraintType: 'xAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineToX({ angle: angle001, to: 26 }, %)',
|
'angledLineToX({ angle = angle001, to = 26 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineToX({ angle: angle001, to: xAbs001 }, %)',
|
'angledLineToX({ angle = angle001, to = xAbs001 }, %)',
|
||||||
expectFinal: 'angledLineToX({ angle: angle001, to: 26 }, %)',
|
expectFinal: 'angledLineToX({ angle = angle001, to = 26 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="9"]',
|
locator: '[data-overlay-toolbar-index="9"]',
|
||||||
})
|
})
|
||||||
@ -654,10 +662,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
||||||
constraintType: 'angle',
|
constraintType: 'angle',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
'angledLineToY({ angle = 89, to = 9.14 + 0 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'angledLineToY({ angle: angle002, to: 9.14 + 0 }, %)',
|
'angledLineToY({ angle = angle002, to = 9.14 + 0 }, %)',
|
||||||
expectFinal: 'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
expectFinal: 'angledLineToY({ angle = 89, to = 9.14 + 0 }, %)',
|
||||||
steps: process.platform === 'darwin' ? 8 : 9,
|
steps: process.platform === 'darwin' ? 8 : 9,
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="10"]',
|
locator: '[data-overlay-toolbar-index="10"]',
|
||||||
@ -667,9 +675,9 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
hoverPos: { x: angledLineToY.x, y: angledLineToY.y },
|
||||||
constraintType: 'yAbsolute',
|
constraintType: 'yAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
'angledLineToY({ angle = 89, to = 9.14 + 0 }, %)',
|
||||||
expectAfterUnconstrained: 'angledLineToY({ angle: 89, to: 9.14 }, %)',
|
expectAfterUnconstrained: 'angledLineToY({ angle = 89, to = 9.14 }, %)',
|
||||||
expectFinal: 'angledLineToY({ angle: 89, to: yAbs001 }, %)',
|
expectFinal: 'angledLineToY({ angle = 89, to = yAbs001 }, %)',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="10"]',
|
locator: '[data-overlay-toolbar-index="10"]',
|
||||||
})
|
})
|
||||||
@ -761,7 +769,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -818,7 +826,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
@ -828,7 +836,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
await page
|
await page
|
||||||
.getByText('circle({ center: [1 + 0, 0], radius: 8 }, %)')
|
.getByText('circle({ center = [1 + 0, 0], radius = 8 }, %)')
|
||||||
.click()
|
.click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
@ -847,9 +855,9 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'xAbsolute',
|
constraintType: 'xAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'circle({ center: [1 + 0, 0], radius: 8 }, %)',
|
'circle({ center = [1 + 0, 0], radius = 8 }, %)',
|
||||||
expectAfterUnconstrained: 'circle({ center: [1, 0], radius: 8 }, %)',
|
expectAfterUnconstrained: 'circle({ center = [1, 0], radius = 8 }, %)',
|
||||||
expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)',
|
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||||
ang: ang + 105,
|
ang: ang + 105,
|
||||||
steps: 6,
|
steps: 6,
|
||||||
locator: '[data-overlay-toolbar-index="0"]',
|
locator: '[data-overlay-toolbar-index="0"]',
|
||||||
@ -859,7 +867,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'yAbsolute',
|
constraintType: 'yAbsolute',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'circle({ center: [xAbs001, 0], radius: 8 }, %)',
|
'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'circle({ center = [xAbs001, yAbs001], radius = 8 }, %)',
|
'circle({ center = [xAbs001, yAbs001], radius = 8 }, %)',
|
||||||
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||||
@ -872,10 +880,10 @@ test.describe('Testing segment overlays', () => {
|
|||||||
hoverPos,
|
hoverPos,
|
||||||
constraintType: 'radius',
|
constraintType: 'radius',
|
||||||
expectBeforeUnconstrained:
|
expectBeforeUnconstrained:
|
||||||
'circle({ center: [xAbs001, 0], radius: 8 }, %)',
|
'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||||
expectAfterUnconstrained:
|
expectAfterUnconstrained:
|
||||||
'circle({ center: [xAbs001, 0], radius: radius001 }, %)',
|
'circle({ center = [xAbs001, 0], radius = radius001 }, %)',
|
||||||
expectFinal: 'circle({ center: [xAbs001, 0], radius: 8 }, %)',
|
expectFinal: 'circle({ center = [xAbs001, 0], radius = 8 }, %)',
|
||||||
ang: ang + 105,
|
ang: ang + 105,
|
||||||
steps: 10,
|
steps: 10,
|
||||||
locator: '[data-overlay-toolbar-index="0"]',
|
locator: '[data-overlay-toolbar-index="0"]',
|
||||||
@ -951,7 +959,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
localStorage.setItem('disableAxis', 'true')
|
localStorage.setItem('disableAxis', 'true')
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
@ -1003,7 +1011,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
ang = await u.getAngle(`[data-overlay-index="${10}"]`)
|
ang = await u.getAngle(`[data-overlay-index="${10}"]`)
|
||||||
await deleteSegmentSequence({
|
await deleteSegmentSequence({
|
||||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||||
codeToBeDeleted: 'angledLineToY({ angle: 89, to: 9.14 + 0 }, %)',
|
codeToBeDeleted: 'angledLineToY({ angle = 89, to = 9.14 + 0 }, %)',
|
||||||
stdLibFnName: 'angledLineToY',
|
stdLibFnName: 'angledLineToY',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="10"]',
|
locator: '[data-overlay-toolbar-index="10"]',
|
||||||
@ -1013,7 +1021,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
ang = await u.getAngle(`[data-overlay-index="${9}"]`)
|
ang = await u.getAngle(`[data-overlay-index="${9}"]`)
|
||||||
await deleteSegmentSequence({
|
await deleteSegmentSequence({
|
||||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||||
codeToBeDeleted: 'angledLineToX({ angle: 3 + 0, to: 26 }, %)',
|
codeToBeDeleted: 'angledLineToX({ angle = 3 + 0, to = 26 }, %)',
|
||||||
stdLibFnName: 'angledLineToX',
|
stdLibFnName: 'angledLineToX',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="9"]',
|
locator: '[data-overlay-toolbar-index="9"]',
|
||||||
@ -1024,7 +1032,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await deleteSegmentSequence({
|
await deleteSegmentSequence({
|
||||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||||
codeToBeDeleted:
|
codeToBeDeleted:
|
||||||
'angledLineOfYLength({ angle: -91, length: 19 + 0 }, %)',
|
'angledLineOfYLength({ angle = -91, length = 19 + 0 }, %)',
|
||||||
stdLibFnName: 'angledLineOfYLength',
|
stdLibFnName: 'angledLineOfYLength',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="8"]',
|
locator: '[data-overlay-toolbar-index="8"]',
|
||||||
@ -1035,7 +1043,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
await deleteSegmentSequence({
|
await deleteSegmentSequence({
|
||||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||||
codeToBeDeleted:
|
codeToBeDeleted:
|
||||||
'angledLineOfXLength({ angle: 181 + 0, length: 23.14 }, %)',
|
'angledLineOfXLength({ angle = 181 + 0, length = 23.14 }, %)',
|
||||||
stdLibFnName: 'angledLineOfXLength',
|
stdLibFnName: 'angledLineOfXLength',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="7"]',
|
locator: '[data-overlay-toolbar-index="7"]',
|
||||||
@ -1118,7 +1126,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
ang = await u.getAngle(`[data-overlay-index="${1}"]`)
|
ang = await u.getAngle(`[data-overlay-index="${1}"]`)
|
||||||
await deleteSegmentSequence({
|
await deleteSegmentSequence({
|
||||||
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
hoverPos: { x: segmentToDelete.x, y: segmentToDelete.y },
|
||||||
codeToBeDeleted: 'angledLine({ angle: 3 + 0, length: 32 + 0 }, %)',
|
codeToBeDeleted: 'angledLine({ angle = 3 + 0, length = 32 + 0 }, %)',
|
||||||
stdLibFnName: 'angledLine',
|
stdLibFnName: 'angledLine',
|
||||||
ang: ang + 180,
|
ang: ang + 180,
|
||||||
locator: '[data-overlay-toolbar-index="1"]',
|
locator: '[data-overlay-toolbar-index="1"]',
|
||||||
@ -1177,15 +1185,31 @@ test.describe('Testing segment overlays', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await page.waitForTimeout(300)
|
await u.waitForPageLoad()
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
|
||||||
await page.getByText(lineOfInterest).click()
|
await expect
|
||||||
await page.waitForTimeout(100)
|
.poll(async () => {
|
||||||
|
await editor.scrollToText(lineOfInterest)
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
|
await page.keyboard.press('ArrowRight')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
await page.keyboard.press('ArrowLeft')
|
||||||
|
await page.waitForTimeout(500)
|
||||||
|
try {
|
||||||
|
await expect(
|
||||||
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
|
).toBeVisible()
|
||||||
|
return true
|
||||||
|
} catch (_) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toBe(true)
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(500)
|
|
||||||
|
|
||||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
|
await expect(page.getByTestId('segment-overlay')).toHaveCount(3)
|
||||||
const segmentToDelete = await u.getBoundingBox(
|
const segmentToDelete = await u.getBoundingBox(
|
||||||
@ -1282,22 +1306,6 @@ test.describe('Testing segment overlays', () => {
|
|||||||
before: `yLineTo(-4 + 0, %, $seg01)`,
|
before: `yLineTo(-4 + 0, %, $seg01)`,
|
||||||
after: `line([0, -10], %, $seg01)`,
|
after: `line([0, -10], %, $seg01)`,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
before: `angledLineOfXLength([3 + 0, 30 + 0], %, $seg01)`,
|
|
||||||
after: `line([30, 1.57], %, $seg01)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
before: `angledLineOfYLength([3 + 0, 1.5 + 0], %, $seg01)`,
|
|
||||||
after: `line([28.62, 1.5], %, $seg01)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
before: `angledLineToX([3 + 0, 30 + 0], %, $seg01)`,
|
|
||||||
after: `line([25, 1.31], %, $seg01)`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
before: `angledLineToY([3 + 0, 7 + 0], %, $seg01)`,
|
|
||||||
after: `line([19.08, 1], %, $seg01)`,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
before: `angledLineOfXLength({ angle = 3 + 0, length = 30 + 0 }, %, $seg01)`,
|
before: `angledLineOfXLength({ angle = 3 + 0, length = 30 + 0 }, %, $seg01)`,
|
||||||
after: `line([30, 1.57], %, $seg01)`,
|
after: `line([30, 1.57], %, $seg01)`,
|
||||||
@ -1317,7 +1325,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
for (const { before, after } of cases) {
|
for (const { before, after } of cases) {
|
||||||
const isObj = before.includes('{ angle: 3')
|
const isObj = before.includes('{ angle = 3')
|
||||||
test(`${before.split('(')[0]}${isObj ? '-[obj-input]' : ''}`, async ({
|
test(`${before.split('(')[0]}${isObj ? '-[obj-input]' : ''}`, async ({
|
||||||
page,
|
page,
|
||||||
editor,
|
editor,
|
||||||
@ -1339,7 +1347,7 @@ test.describe('Testing segment overlays', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
@ -492,6 +492,8 @@ test.describe('Testing selections', () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await page.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -644,7 +646,7 @@ test.describe('Testing selections', () => {
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'flatExtrusionFace',
|
'flatExtrusionFace',
|
||||||
flatExtrusionFace,
|
flatExtrusionFace,
|
||||||
`angledLineThatIntersects({angle:3.14,intersectTag:a,offset:0},%)extrude(5+7,%)`,
|
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)extrude(5+7,%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -701,19 +703,19 @@ test.describe('Testing selections', () => {
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentEdge',
|
'straightSegmentEdge',
|
||||||
straightSegmentEdge,
|
straightSegmentEdge,
|
||||||
`angledLineToY({angle:30,to:11.14},%)`,
|
`angledLineToY({angle=30,to=11.14},%)`,
|
||||||
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
'angledLineToY({ angle = 30, to = 11.14 }, %)'
|
||||||
)
|
)
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentOppositeEdge',
|
'straightSegmentOppositeEdge',
|
||||||
straightSegmentOppositeEdge,
|
straightSegmentOppositeEdge,
|
||||||
`angledLineToY({angle:30,to:11.14},%)`,
|
`angledLineToY({angle=30,to=11.14},%)`,
|
||||||
'angledLineToY({ angle: 30, to: 11.14 }, %)'
|
'angledLineToY({ angle = 30, to = 11.14 }, %)'
|
||||||
)
|
)
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'straightSegmentAdjacentEdge',
|
'straightSegmentAdjacentEdge',
|
||||||
straightSegmentAdjacentEdge,
|
straightSegmentAdjacentEdge,
|
||||||
`angledLineThatIntersects({angle:3.14,intersectTag:a,offset:0},%)`,
|
`angledLineThatIntersects({angle=3.14,intersectTag=a,offset=0},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -780,14 +782,14 @@ test.describe('Testing selections', () => {
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'oppositeChamfer',
|
'oppositeChamfer',
|
||||||
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(
|
await checkCodeAtHoverPosition(
|
||||||
'baseChamfer',
|
'baseChamfer',
|
||||||
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)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -818,14 +820,14 @@ test.describe('Testing selections', () => {
|
|||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'adjacentChamfer1',
|
'adjacentChamfer1',
|
||||||
adjacentChamfer1,
|
adjacentChamfer1,
|
||||||
`lineTo([profileStartX(%),profileStartY(%)],%,$seg02)chamfer({length:30,tags:[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
`lineTo([profileStartX(%),profileStartY(%)],%,$seg02)chamfer({length=30,tags=[seg01,getNextAdjacentEdge(yo),getNextAdjacentEdge(seg02),getOppositeEdge(seg01)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
|
|
||||||
await checkCodeAtHoverPosition(
|
await checkCodeAtHoverPosition(
|
||||||
'adjacentChamfer2',
|
'adjacentChamfer2',
|
||||||
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)]},%)`,
|
||||||
'}, %)'
|
'}, %)'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
@ -35,7 +35,7 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
// Check that the invalid settings were changed to good defaults
|
// Check that the invalid settings were changed to good defaults
|
||||||
expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in')
|
expect(storedSettings.settings?.modeling?.defaultUnit).toBe('in')
|
||||||
expect(storedSettings.settings?.modeling?.mouseControls).toBe('KittyCAD')
|
expect(storedSettings.settings?.modeling?.mouseControls).toBe('Zoo')
|
||||||
expect(storedSettings.settings?.app?.projectDirectory).toBe('')
|
expect(storedSettings.settings?.app?.projectDirectory).toBe('')
|
||||||
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
expect(storedSettings.settings?.projects?.defaultProjectName).toBe(
|
||||||
'project-$nnn'
|
'project-$nnn'
|
||||||
@ -134,6 +134,8 @@ test.describe('Testing settings', () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
}) => {
|
}) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
@ -492,6 +494,8 @@ test.describe('Testing settings', () => {
|
|||||||
`Closing settings modal should go back to the original file being viewed`,
|
`Closing settings modal should go back to the original file being viewed`,
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
await context.folderSetupFn(async (dir) => {
|
await context.folderSetupFn(async (dir) => {
|
||||||
const bracketDir = join(dir, 'project-000')
|
const bracketDir = join(dir, 'project-000')
|
||||||
await fsp.mkdir(bracketDir, { recursive: true })
|
await fsp.mkdir(bracketDir, { recursive: true })
|
||||||
@ -554,6 +558,8 @@ test.describe('Testing settings', () => {
|
|||||||
|
|
||||||
test('Changing modeling default unit', async ({ page, homePage }) => {
|
test('Changing modeling default unit', async ({ page, homePage }) => {
|
||||||
await test.step(`Test setup`, async () => {
|
await test.step(`Test setup`, async () => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
const toastMessage = page.getByText(`Successfully created "testDefault"`)
|
const toastMessage = page.getByText(`Successfully created "testDefault"`)
|
||||||
@ -700,6 +706,8 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Changing theme in sketch mode', async ({ context, page, homePage }) => {
|
test('Changing theme in sketch mode', async ({ context, page, homePage }) => {
|
||||||
|
// TODO: fix this test on windows after the electron migration
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(() => {
|
await context.addInitScript(() => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
|
@ -185,8 +185,8 @@ export const test = (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a consistent way to resize the page across electron and web.
|
// Create a consistent way to resize the page across electron and web.
|
||||||
// (lee) I had to do everyhting in the book to make electron change its
|
// (lee) I had to do everything in the book to make electron change its
|
||||||
// damn window size. I succeded in making it consistently and reliably
|
// damn window size. I succeeded in making it consistently and reliably
|
||||||
// do it after a whole afternoon.
|
// do it after a whole afternoon.
|
||||||
tronApp.page.setBodyDimensions = async function (dims: {
|
tronApp.page.setBodyDimensions = async function (dims: {
|
||||||
width: number
|
width: number
|
||||||
@ -242,8 +242,8 @@ export const test = (
|
|||||||
// return app.reuseWindowForTest();
|
// return app.reuseWindowForTest();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
await tronApp.electronApp.evaluate(({ app }, projectDirName) => {
|
await tronApp.electronApp?.evaluate(({ app }, projectDirName) => {
|
||||||
console.log('ABCDEFGHI', app.testProperty['TEST_SETTINGS_FILE_KEY'])
|
// @ts-ignore can't declaration merge see main.ts
|
||||||
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
app.testProperty['TEST_SETTINGS_FILE_KEY'] = projectDirName
|
||||||
}, tronApp.dir)
|
}, tronApp.dir)
|
||||||
|
|
||||||
|
1
interface.d.ts
vendored
@ -80,6 +80,7 @@ export interface IElectronAPI {
|
|||||||
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
|
onUpdateError: (callback: (value: { error: Error }) => void) => Electron
|
||||||
appRestart: () => void
|
appRestart: () => void
|
||||||
getArgvParsed: () => any
|
getArgvParsed: () => any
|
||||||
|
getAppTestProperty: (propertyName: string) => any
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
@ -103,8 +103,6 @@
|
|||||||
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
"generate:machine-api": "npx openapi-typescript ./openapi/machine-api.json -o src/lib/machine-api.d.ts",
|
||||||
"tron:start": "electron-forge start",
|
"tron:start": "electron-forge start",
|
||||||
"tron:package": "electron-forge package",
|
"tron:package": "electron-forge package",
|
||||||
"tron:make": "electron-forge make",
|
|
||||||
"tron:publish": "electron-forge publish",
|
|
||||||
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
|
"chrome:test": "PLATFORM=web NODE_ENV=development yarn playwright test --config=playwright.config.ts --project='Google Chrome' --grep-invert='@snapshot'",
|
||||||
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"tron:test": "NODE_ENV=development yarn playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
"tronb:vite": "vite build -c vite.main.config.ts && vite build -c vite.preload.config.ts && vite build -c vite.renderer.config.ts",
|
||||||
@ -114,11 +112,11 @@
|
|||||||
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
"test:unit": "vitest run --mode development --exclude **/kclSamples.test.ts",
|
||||||
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
"test:unit:kcl-samples": "vitest run --mode development ./src/lang/kclSamples.test.ts",
|
||||||
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"test:playwright:electron": "playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipWin|@snapshot'",
|
"test:playwright:electron:windows": "playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||||
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
"test:playwright:electron:ubuntu": "playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
"test:playwright:electron:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@snapshot'",
|
||||||
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipWin|@snapshot'",
|
"test:playwright:electron:windows:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||||
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
"test:playwright:electron:macos:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||||
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
"test:playwright:electron:ubuntu:local": "yarn tron:package && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||||
|
@ -13,7 +13,7 @@ export default defineConfig({
|
|||||||
/* Do not retry */
|
/* Do not retry */
|
||||||
retries: 0,
|
retries: 0,
|
||||||
/* Different amount of parallelism on CI and local. */
|
/* Different amount of parallelism on CI and local. */
|
||||||
workers: 30,
|
workers: 8,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: [
|
reporter: [
|
||||||
['dot'],
|
['dot'],
|
||||||
|
@ -28,6 +28,7 @@ data = {
|
|||||||
"content":
|
"content":
|
||||||
f'''
|
f'''
|
||||||
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
|
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/modeling-app/download>
|
||||||
|
|
||||||
{modified_release_body}
|
{modified_release_body}
|
||||||
''',
|
''',
|
||||||
"username": "Modeling App Release Updates",
|
"username": "Modeling App Release Updates",
|
||||||
|
@ -105,7 +105,7 @@ export class CameraControls {
|
|||||||
pendingZoom: number | null = null
|
pendingZoom: number | null = null
|
||||||
pendingRotation: Vector2 | null = null
|
pendingRotation: Vector2 | null = null
|
||||||
pendingPan: Vector2 | null = null
|
pendingPan: Vector2 | null = null
|
||||||
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
interactionGuards: MouseGuard = cameraMouseDragGuards.Zoo
|
||||||
isFovAnimationInProgress = false
|
isFovAnimationInProgress = false
|
||||||
perspectiveFovBeforeOrtho = 45
|
perspectiveFovBeforeOrtho = 45
|
||||||
get isPerspective() {
|
get isPerspective() {
|
||||||
|
@ -174,8 +174,13 @@ export const ClientSideScene = ({
|
|||||||
const Overlays = () => {
|
const Overlays = () => {
|
||||||
const { context } = useModelingContext()
|
const { context } = useModelingContext()
|
||||||
if (context.mouseState.type === 'isDragging') return null
|
if (context.mouseState.type === 'isDragging') return null
|
||||||
|
// Set a large zIndex, the overlay for hover dropdown menu on line segments needs to render
|
||||||
|
// over the length labels on the line segments
|
||||||
return (
|
return (
|
||||||
<div className="absolute inset-0 pointer-events-none">
|
<div
|
||||||
|
className="absolute inset-0 pointer-events-none"
|
||||||
|
style={{ zIndex: '99999999' }}
|
||||||
|
>
|
||||||
{Object.entries(context.segmentOverlays)
|
{Object.entries(context.segmentOverlays)
|
||||||
.filter((a) => a[1].visible)
|
.filter((a) => a[1].visible)
|
||||||
.map(([pathToNodeString, overlay], index) => {
|
.map(([pathToNodeString, overlay], index) => {
|
||||||
|
@ -51,6 +51,7 @@ import {
|
|||||||
defaultSourceRange,
|
defaultSourceRange,
|
||||||
sourceRangeFromRust,
|
sourceRangeFromRust,
|
||||||
resultIsOk,
|
resultIsOk,
|
||||||
|
SourceRange,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -102,6 +103,7 @@ import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
|||||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
import { radToDeg } from 'three/src/math/MathUtils'
|
import { radToDeg } from 'three/src/math/MathUtils'
|
||||||
|
import { getArtifactFromRange, codeRefFromRange } from 'lang/std/artifactGraph'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -584,11 +586,12 @@ export class SceneEntities {
|
|||||||
)
|
)
|
||||||
|
|
||||||
let seg: Group
|
let seg: Group
|
||||||
const _node1 = getNodeFromPath<CallExpression>(
|
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||||
maybeModdedAst,
|
maybeModdedAst,
|
||||||
segPathToNode,
|
segPathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
|
|
||||||
if (err(_node1)) return
|
if (err(_node1)) return
|
||||||
const callExpName = _node1.node?.callee?.name
|
const callExpName = _node1.node?.callee?.name
|
||||||
|
|
||||||
@ -611,6 +614,15 @@ export class SceneEntities {
|
|||||||
from: segment.from,
|
from: segment.from,
|
||||||
to: segment.to,
|
to: segment.to,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startRange = _node1.node.start
|
||||||
|
const endRange = _node1.node.end
|
||||||
|
const sourceRange: SourceRange = [startRange, endRange, true]
|
||||||
|
const selection: Selections = computeSelectionFromSourceRangeAndAST(
|
||||||
|
sourceRange,
|
||||||
|
maybeModdedAst
|
||||||
|
)
|
||||||
|
|
||||||
const result = initSegment({
|
const result = initSegment({
|
||||||
prevSegment: sketch.paths[index - 1],
|
prevSegment: sketch.paths[index - 1],
|
||||||
callExpName,
|
callExpName,
|
||||||
@ -623,6 +635,7 @@ export class SceneEntities {
|
|||||||
theme: sceneInfra._theme,
|
theme: sceneInfra._theme,
|
||||||
isSelected,
|
isSelected,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
|
selection,
|
||||||
})
|
})
|
||||||
if (err(result)) return
|
if (err(result)) return
|
||||||
const { group: _group, updateOverlaysCallback } = result
|
const { group: _group, updateOverlaysCallback } = result
|
||||||
@ -2351,3 +2364,27 @@ export function getQuaternionFromZAxis(zAxis: Vector3): Quaternion {
|
|||||||
function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
function massageFormats(a: Vec3Array | Point3d): Vector3 {
|
||||||
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
|
return isArray(a) ? new Vector3(a[0], a[1], a[2]) : new Vector3(a.x, a.y, a.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a SourceRange [x,y,boolean] create a Selections object which contains
|
||||||
|
* graphSelections with the artifact and codeRef.
|
||||||
|
* This can be passed to 'Set selection' to internally set the selection of the
|
||||||
|
* modelingMachine from code.
|
||||||
|
*/
|
||||||
|
function computeSelectionFromSourceRangeAndAST(
|
||||||
|
sourceRange: SourceRange,
|
||||||
|
ast: Node<Program>
|
||||||
|
): Selections {
|
||||||
|
const artifactGraph = engineCommandManager.artifactGraph
|
||||||
|
const artifact = getArtifactFromRange(sourceRange, artifactGraph) || undefined
|
||||||
|
const selection: Selections = {
|
||||||
|
graphSelections: [
|
||||||
|
{
|
||||||
|
artifact,
|
||||||
|
codeRef: codeRefFromRange(sourceRange, ast),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
otherSelections: [],
|
||||||
|
}
|
||||||
|
return selection
|
||||||
|
}
|
||||||
|
@ -229,7 +229,6 @@ export class SceneInfra {
|
|||||||
const vector = new Vector3(0, 0, 0)
|
const vector = new Vector3(0, 0, 0)
|
||||||
|
|
||||||
// Get the position of the object3D in world space
|
// Get the position of the object3D in world space
|
||||||
// console.log('arrowGroup', arrowGroup)
|
|
||||||
arrowGroup.getWorldPosition(vector)
|
arrowGroup.getWorldPosition(vector)
|
||||||
|
|
||||||
// Project that position to screen space
|
// Project that position to screen space
|
||||||
@ -347,7 +346,6 @@ export class SceneInfra {
|
|||||||
requestAnimationFrame(this.animate)
|
requestAnimationFrame(this.animate)
|
||||||
TWEEN.update() // This will update all tweens during the animation loop
|
TWEEN.update() // This will update all tweens during the animation loop
|
||||||
if (!this.isFovAnimationInProgress) {
|
if (!this.isFovAnimationInProgress) {
|
||||||
// console.log('animation frame', this.cameraControls.camera)
|
|
||||||
this.camControls.update()
|
this.camControls.update()
|
||||||
this.renderer.render(this.scene, this.camControls.camera)
|
this.renderer.render(this.scene, this.camControls.camera)
|
||||||
this.labelRenderer.render(this.scene, this.camControls.camera)
|
this.labelRenderer.render(this.scene, this.camControls.camera)
|
||||||
@ -434,7 +432,6 @@ export class SceneInfra {
|
|||||||
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
if (!this.selected.hasBeenDragged && hasBeenDragged) {
|
||||||
this.selected.hasBeenDragged = true
|
this.selected.hasBeenDragged = true
|
||||||
// this is where we could fire a onDragStart event
|
// this is where we could fire a onDragStart event
|
||||||
// console.log('onDragStart', this.selected)
|
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
hasBeenDragged &&
|
hasBeenDragged &&
|
||||||
|
@ -56,6 +56,8 @@ import { normaliseAngle, roundOff } from 'lib/utils'
|
|||||||
import { SegmentOverlayPayload } from 'machines/modelingMachine'
|
import { SegmentOverlayPayload } from 'machines/modelingMachine'
|
||||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||||
import { err } from 'lib/trap'
|
import { err } from 'lib/trap'
|
||||||
|
import { editorManager, sceneInfra } from 'lib/singletons'
|
||||||
|
import { Selections } from 'lib/selections'
|
||||||
|
|
||||||
interface CreateSegmentArgs {
|
interface CreateSegmentArgs {
|
||||||
input: SegmentInputs
|
input: SegmentInputs
|
||||||
@ -69,6 +71,7 @@ interface CreateSegmentArgs {
|
|||||||
theme: Themes
|
theme: Themes
|
||||||
isSelected?: boolean
|
isSelected?: boolean
|
||||||
sceneInfra: SceneInfra
|
sceneInfra: SceneInfra
|
||||||
|
selection?: Selections
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateSegmentArgs {
|
interface UpdateSegmentArgs {
|
||||||
@ -118,6 +121,7 @@ class StraightSegment implements SegmentUtils {
|
|||||||
isSelected = false,
|
isSelected = false,
|
||||||
sceneInfra,
|
sceneInfra,
|
||||||
prevSegment,
|
prevSegment,
|
||||||
|
selection,
|
||||||
}) => {
|
}) => {
|
||||||
if (input.type !== 'straight-segment')
|
if (input.type !== 'straight-segment')
|
||||||
return new Error('Invalid segment type')
|
return new Error('Invalid segment type')
|
||||||
@ -156,6 +160,7 @@ class StraightSegment implements SegmentUtils {
|
|||||||
isSelected,
|
isSelected,
|
||||||
callExpName,
|
callExpName,
|
||||||
baseColor,
|
baseColor,
|
||||||
|
selection,
|
||||||
}
|
}
|
||||||
|
|
||||||
// All segment types get an extra segment handle,
|
// All segment types get an extra segment handle,
|
||||||
@ -823,8 +828,37 @@ function createLengthIndicator({
|
|||||||
lengthIndicatorText.innerText = roundOff(length).toString()
|
lengthIndicatorText.innerText = roundOff(length).toString()
|
||||||
const lengthIndicatorWrapper = document.createElement('div')
|
const lengthIndicatorWrapper = document.createElement('div')
|
||||||
|
|
||||||
|
// Double click workflow
|
||||||
|
lengthIndicatorWrapper.ondblclick = () => {
|
||||||
|
const selection = lengthIndicatorGroup.parent?.userData.selection
|
||||||
|
if (!selection) {
|
||||||
|
console.error('Unable to dimension segment when clicking the label.')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Set selection',
|
||||||
|
data: {
|
||||||
|
selectionType: 'singleCodeCursor',
|
||||||
|
selection: selection.graphSelections[0],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Command Bar
|
||||||
|
editorManager.commandBarSend({
|
||||||
|
type: 'Find and select command',
|
||||||
|
data: {
|
||||||
|
name: 'Constrain length',
|
||||||
|
groupId: 'modeling',
|
||||||
|
argDefaultValues: {
|
||||||
|
selection,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Style the elements
|
// Style the elements
|
||||||
lengthIndicatorWrapper.style.position = 'absolute'
|
lengthIndicatorWrapper.style.position = 'absolute'
|
||||||
|
lengthIndicatorWrapper.style.pointerEvents = 'auto'
|
||||||
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
|
lengthIndicatorWrapper.appendChild(lengthIndicatorText)
|
||||||
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
|
const cssObject = new CSS2DObject(lengthIndicatorWrapper)
|
||||||
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT
|
cssObject.name = SEGMENT_LENGTH_LABEL_TEXT
|
||||||
|
@ -109,6 +109,7 @@ function DisplayObj({
|
|||||||
setHasCursor(false)
|
setHasCursor(false)
|
||||||
}
|
}
|
||||||
}, [node.start, node.end, node.type])
|
}, [node.start, node.end, node.type])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<pre
|
<pre
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
@ -1,13 +1,23 @@
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
import { ActionIcon, ActionIconProps } from './ActionIcon'
|
||||||
import { RefObject, useEffect, useMemo, useRef, useState } from 'react'
|
import {
|
||||||
|
MouseEvent,
|
||||||
|
RefObject,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import { useHotkeys } from 'react-hotkeys-hook'
|
import { useHotkeys } from 'react-hotkeys-hook'
|
||||||
import { Dialog } from '@headlessui/react'
|
import { Dialog } from '@headlessui/react'
|
||||||
|
|
||||||
interface ContextMenuProps
|
export interface ContextMenuProps
|
||||||
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
extends Omit<React.HTMLAttributes<HTMLUListElement>, 'children'> {
|
||||||
items?: React.ReactElement[]
|
items?: React.ReactElement[]
|
||||||
menuTargetElement?: RefObject<HTMLElement>
|
menuTargetElement?: RefObject<HTMLElement>
|
||||||
|
guard?: (e: globalThis.MouseEvent) => boolean
|
||||||
|
event?: 'contextmenu' | 'mouseup'
|
||||||
}
|
}
|
||||||
|
|
||||||
const DefaultContextMenuItems = [
|
const DefaultContextMenuItems = [
|
||||||
@ -20,6 +30,8 @@ export function ContextMenu({
|
|||||||
items = DefaultContextMenuItems,
|
items = DefaultContextMenuItems,
|
||||||
menuTargetElement,
|
menuTargetElement,
|
||||||
className,
|
className,
|
||||||
|
guard,
|
||||||
|
event = 'contextmenu',
|
||||||
...props
|
...props
|
||||||
}: ContextMenuProps) {
|
}: ContextMenuProps) {
|
||||||
const dialogRef = useRef<HTMLDivElement>(null)
|
const dialogRef = useRef<HTMLDivElement>(null)
|
||||||
@ -32,6 +44,15 @@ export function ContextMenu({
|
|||||||
useHotkeys('esc', () => setOpen(false), {
|
useHotkeys('esc', () => setOpen(false), {
|
||||||
enabled: open,
|
enabled: open,
|
||||||
})
|
})
|
||||||
|
const handleContextMenu = useCallback(
|
||||||
|
(e: globalThis.MouseEvent) => {
|
||||||
|
if (guard && !guard(e)) return
|
||||||
|
e.preventDefault()
|
||||||
|
setPosition({ x: e.clientX, y: e.clientY })
|
||||||
|
setOpen(true)
|
||||||
|
},
|
||||||
|
[guard, setPosition, setOpen]
|
||||||
|
)
|
||||||
|
|
||||||
const dialogPositionStyle = useMemo(() => {
|
const dialogPositionStyle = useMemo(() => {
|
||||||
if (!dialogRef.current)
|
if (!dialogRef.current)
|
||||||
@ -78,21 +99,9 @@ export function ContextMenu({
|
|||||||
|
|
||||||
// Add context menu listener to target once mounted
|
// Add context menu listener to target once mounted
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleContextMenu = (e: MouseEvent) => {
|
menuTargetElement?.current?.addEventListener(event, handleContextMenu)
|
||||||
console.log('context menu', e)
|
|
||||||
e.preventDefault()
|
|
||||||
setPosition({ x: e.x, y: e.y })
|
|
||||||
setOpen(true)
|
|
||||||
}
|
|
||||||
menuTargetElement?.current?.addEventListener(
|
|
||||||
'contextmenu',
|
|
||||||
handleContextMenu
|
|
||||||
)
|
|
||||||
return () => {
|
return () => {
|
||||||
menuTargetElement?.current?.removeEventListener(
|
menuTargetElement?.current?.removeEventListener(event, handleContextMenu)
|
||||||
'contextmenu',
|
|
||||||
handleContextMenu
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}, [menuTargetElement?.current])
|
}, [menuTargetElement?.current])
|
||||||
|
|
||||||
@ -100,7 +109,10 @@ export function ContextMenu({
|
|||||||
<Dialog open={open} onClose={() => setOpen(false)}>
|
<Dialog open={open} onClose={() => setOpen(false)}>
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 z-50 w-screen h-screen"
|
className="fixed inset-0 z-50 w-screen h-screen"
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
setPosition({ x: e.clientX, y: e.clientY })
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
<Dialog.Backdrop className="fixed z-10 inset-0" />
|
||||||
<Dialog.Panel
|
<Dialog.Panel
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
import { SceneInfra } from 'clientSideScene/sceneInfra'
|
||||||
import { sceneInfra } from 'lib/singletons'
|
import { sceneInfra } from 'lib/singletons'
|
||||||
import { MutableRefObject, useEffect, useMemo, useRef } from 'react'
|
import { MutableRefObject, useEffect, useRef } from 'react'
|
||||||
import {
|
import {
|
||||||
WebGLRenderer,
|
WebGLRenderer,
|
||||||
Scene,
|
Scene,
|
||||||
@ -19,16 +19,14 @@ import {
|
|||||||
Intersection,
|
Intersection,
|
||||||
Object3D,
|
Object3D,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import {
|
|
||||||
ContextMenu,
|
|
||||||
ContextMenuDivider,
|
|
||||||
ContextMenuItem,
|
|
||||||
ContextMenuItemRefresh,
|
|
||||||
} from './ContextMenu'
|
|
||||||
import { Popover } from '@headlessui/react'
|
import { Popover } from '@headlessui/react'
|
||||||
import { CustomIcon } from './CustomIcon'
|
import { CustomIcon } from './CustomIcon'
|
||||||
import { reportRejection } from 'lib/trap'
|
import { reportRejection } from 'lib/trap'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import {
|
||||||
|
useViewControlMenuItems,
|
||||||
|
ViewControlContextMenu,
|
||||||
|
} from './ViewControlMenu'
|
||||||
|
import { AxisNames } from 'lib/constants'
|
||||||
|
|
||||||
const CANVAS_SIZE = 80
|
const CANVAS_SIZE = 80
|
||||||
const FRUSTUM_SIZE = 0.5
|
const FRUSTUM_SIZE = 0.5
|
||||||
@ -40,64 +38,14 @@ enum AxisColors {
|
|||||||
Z = '#6689ef',
|
Z = '#6689ef',
|
||||||
Gray = '#c6c7c2',
|
Gray = '#c6c7c2',
|
||||||
}
|
}
|
||||||
enum AxisNames {
|
|
||||||
X = 'x',
|
|
||||||
Y = 'y',
|
|
||||||
Z = 'z',
|
|
||||||
NEG_X = '-x',
|
|
||||||
NEG_Y = '-y',
|
|
||||||
NEG_Z = '-z',
|
|
||||||
}
|
|
||||||
const axisNamesSemantic: Record<AxisNames, string> = {
|
|
||||||
[AxisNames.X]: 'Right',
|
|
||||||
[AxisNames.Y]: 'Back',
|
|
||||||
[AxisNames.Z]: 'Top',
|
|
||||||
[AxisNames.NEG_X]: 'Left',
|
|
||||||
[AxisNames.NEG_Y]: 'Front',
|
|
||||||
[AxisNames.NEG_Z]: 'Bottom',
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Gizmo() {
|
export default function Gizmo() {
|
||||||
|
const menuItems = useViewControlMenuItems()
|
||||||
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
const wrapperRef = useRef<HTMLDivElement | null>(null)
|
||||||
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
const canvasRef = useRef<HTMLCanvasElement | null>(null)
|
||||||
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
const raycasterIntersect = useRef<Intersection<Object3D> | null>(null)
|
||||||
const cameraPassiveUpdateTimer = useRef(0)
|
const cameraPassiveUpdateTimer = useRef(0)
|
||||||
const raycasterPassiveUpdateTimer = useRef(0)
|
const raycasterPassiveUpdateTimer = useRef(0)
|
||||||
const { send: modelingSend } = useModelingContext()
|
|
||||||
const menuItems = useMemo(
|
|
||||||
() => [
|
|
||||||
...Object.entries(axisNamesSemantic).map(([axisName, axisSemantic]) => (
|
|
||||||
<ContextMenuItem
|
|
||||||
key={axisName}
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls
|
|
||||||
.updateCameraToAxis(axisName as AxisNames)
|
|
||||||
.catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{axisSemantic} view
|
|
||||||
</ContextMenuItem>
|
|
||||||
)),
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reset view
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuItem
|
|
||||||
onClick={() => {
|
|
||||||
modelingSend({ type: 'Center camera on selection' })
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Center view on selection
|
|
||||||
</ContextMenuItem>,
|
|
||||||
<ContextMenuDivider />,
|
|
||||||
<ContextMenuItemRefresh />,
|
|
||||||
],
|
|
||||||
[axisNamesSemantic]
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canvasRef.current) return
|
if (!canvasRef.current) return
|
||||||
@ -161,7 +109,7 @@ export default function Gizmo() {
|
|||||||
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
className="grid place-content-center rounded-full overflow-hidden border border-solid border-primary/50 pointer-events-auto bg-chalkboard-10/70 dark:bg-chalkboard-100/80 backdrop-blur-sm"
|
||||||
>
|
>
|
||||||
<canvas ref={canvasRef} />
|
<canvas ref={canvasRef} />
|
||||||
<ContextMenu menuTargetElement={wrapperRef} items={menuItems} />
|
<ViewControlContextMenu menuTargetElement={wrapperRef} />
|
||||||
</div>
|
</div>
|
||||||
<GizmoDropdown items={menuItems} />
|
<GizmoDropdown items={menuItems} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -625,7 +625,6 @@ export const ModelingMachineProvider = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const canShell = canShellSelection(selectionRanges)
|
const canShell = canShellSelection(selectionRanges)
|
||||||
console.log('canShellSelection', canShellSelection(selectionRanges))
|
|
||||||
if (err(canShell)) return false
|
if (err(canShell)) return false
|
||||||
return canShell
|
return canShell
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import Tooltip from 'components/Tooltip'
|
|||||||
import { CustomIconName } from 'components/CustomIcon'
|
import { CustomIconName } from 'components/CustomIcon'
|
||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { ActionIcon } from 'components/ActionIcon'
|
import { ActionIcon } from 'components/ActionIcon'
|
||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
export interface ModelingPaneProps {
|
export interface ModelingPaneProps {
|
||||||
id: string
|
id: string
|
||||||
@ -70,7 +71,7 @@ export const ModelingPane = ({
|
|||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === 'camera'
|
onboardingStatus.current === onboardingPaths.CAMERA
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
return (
|
return (
|
||||||
|
@ -19,6 +19,7 @@ import { useCommandsContext } from 'hooks/useCommandsContext'
|
|||||||
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
import { MachineManagerContext } from 'components/MachineManagerProvider'
|
||||||
|
import { onboardingPaths } from 'routes/Onboarding/paths'
|
||||||
|
|
||||||
interface ModelingSidebarProps {
|
interface ModelingSidebarProps {
|
||||||
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
paneOpacity: '' | 'opacity-20' | 'opacity-40'
|
||||||
@ -41,7 +42,7 @@ export function ModelingSidebar({ paneOpacity }: ModelingSidebarProps) {
|
|||||||
const onboardingStatus = settings.context.app.onboardingStatus
|
const onboardingStatus = settings.context.app.onboardingStatus
|
||||||
const { send, context } = useModelingContext()
|
const { send, context } = useModelingContext()
|
||||||
const pointerEventsCssClass =
|
const pointerEventsCssClass =
|
||||||
onboardingStatus.current === 'camera' ||
|
onboardingStatus.current === onboardingPaths.CAMERA ||
|
||||||
context.store?.openPanes.length === 0
|
context.store?.openPanes.length === 0
|
||||||
? 'pointer-events-none '
|
? 'pointer-events-none '
|
||||||
: 'pointer-events-auto '
|
: 'pointer-events-auto '
|
||||||
|
@ -20,6 +20,7 @@ import { IndexLoaderData } from 'lib/types'
|
|||||||
import { useCommandsContext } from 'hooks/useCommandsContext'
|
import { useCommandsContext } from 'hooks/useCommandsContext'
|
||||||
import { err, reportRejection } from 'lib/trap'
|
import { err, reportRejection } from 'lib/trap'
|
||||||
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
import { getArtifactOfTypes } from 'lang/std/artifactGraph'
|
||||||
|
import { ViewControlContextMenu } from './ViewControlMenu'
|
||||||
|
|
||||||
enum StreamState {
|
enum StreamState {
|
||||||
Playing = 'playing',
|
Playing = 'playing',
|
||||||
@ -30,6 +31,7 @@ enum StreamState {
|
|||||||
|
|
||||||
export const Stream = () => {
|
export const Stream = () => {
|
||||||
const [isLoading, setIsLoading] = useState(true)
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
const videoWrapperRef = useRef<HTMLDivElement>(null)
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
const { settings } = useSettingsAuthContext()
|
const { settings } = useSettingsAuthContext()
|
||||||
const { state, send } = useModelingContext()
|
const { state, send } = useModelingContext()
|
||||||
@ -258,7 +260,7 @@ export const Stream = () => {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}, [mediaStream])
|
}, [mediaStream])
|
||||||
|
|
||||||
const handleMouseUp: MouseEventHandler<HTMLDivElement> = (e) => {
|
const handleClick: MouseEventHandler<HTMLDivElement> = (e) => {
|
||||||
// If we've got no stream or connection, don't do anything
|
// If we've got no stream or connection, don't do anything
|
||||||
if (!isNetworkOkay) return
|
if (!isNetworkOkay) return
|
||||||
if (!videoRef.current) return
|
if (!videoRef.current) return
|
||||||
@ -320,10 +322,11 @@ export const Stream = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={videoWrapperRef}
|
||||||
className="absolute inset-0 z-0"
|
className="absolute inset-0 z-0"
|
||||||
id="stream"
|
id="stream"
|
||||||
data-testid="stream"
|
data-testid="stream"
|
||||||
onClick={handleMouseUp}
|
onClick={handleClick}
|
||||||
onDoubleClick={enterSketchModeIfSelectingSketch}
|
onDoubleClick={enterSketchModeIfSelectingSketch}
|
||||||
onContextMenu={(e) => e.preventDefault()}
|
onContextMenu={(e) => e.preventDefault()}
|
||||||
onContextMenuCapture={(e) => e.preventDefault()}
|
onContextMenuCapture={(e) => e.preventDefault()}
|
||||||
@ -384,6 +387,14 @@ export const Stream = () => {
|
|||||||
</Loading>
|
</Loading>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<ViewControlContextMenu
|
||||||
|
event="mouseup"
|
||||||
|
guard={(e) =>
|
||||||
|
sceneInfra.camControls.wasDragging === false &&
|
||||||
|
btnName(e).right === true
|
||||||
|
}
|
||||||
|
menuTargetElement={videoWrapperRef}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
66
src/components/ViewControlMenu.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
import {
|
||||||
|
ContextMenu,
|
||||||
|
ContextMenuDivider,
|
||||||
|
ContextMenuItem,
|
||||||
|
ContextMenuItemRefresh,
|
||||||
|
ContextMenuProps,
|
||||||
|
} from './ContextMenu'
|
||||||
|
import { AxisNames, VIEW_NAMES_SEMANTIC } from 'lib/constants'
|
||||||
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import { sceneInfra } from 'lib/singletons'
|
||||||
|
|
||||||
|
export function useViewControlMenuItems() {
|
||||||
|
const { send: modelingSend } = useModelingContext()
|
||||||
|
const menuItems = useMemo(
|
||||||
|
() => [
|
||||||
|
...Object.entries(VIEW_NAMES_SEMANTIC).map(([axisName, axisSemantic]) => (
|
||||||
|
<ContextMenuItem
|
||||||
|
key={axisName}
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls
|
||||||
|
.updateCameraToAxis(axisName as AxisNames)
|
||||||
|
.catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{axisSemantic} view
|
||||||
|
</ContextMenuItem>
|
||||||
|
)),
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
sceneInfra.camControls.resetCameraPosition().catch(reportRejection)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reset view
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
modelingSend({ type: 'Center camera on selection' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Center view on selection
|
||||||
|
</ContextMenuItem>,
|
||||||
|
<ContextMenuDivider />,
|
||||||
|
<ContextMenuItemRefresh />,
|
||||||
|
],
|
||||||
|
[VIEW_NAMES_SEMANTIC]
|
||||||
|
)
|
||||||
|
return menuItems
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ViewControlContextMenu({
|
||||||
|
menuTargetElement: wrapperRef,
|
||||||
|
...props
|
||||||
|
}: ContextMenuProps) {
|
||||||
|
const menuItems = useViewControlMenuItems()
|
||||||
|
return (
|
||||||
|
<ContextMenu
|
||||||
|
data-testid="view-controls-menu"
|
||||||
|
menuTargetElement={wrapperRef}
|
||||||
|
items={menuItems}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
@ -15,6 +15,18 @@ import {
|
|||||||
import { StateFrom } from 'xstate'
|
import { StateFrom } from 'xstate'
|
||||||
import { markOnce } from 'lib/performance'
|
import { markOnce } from 'lib/performance'
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
EditorSelection: typeof EditorSelection
|
||||||
|
EditorView: typeof EditorView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to be able to create these during tests dynamically (via
|
||||||
|
// page.evaluate) So that's why this exists.
|
||||||
|
window.EditorSelection = EditorSelection
|
||||||
|
window.EditorView = EditorView
|
||||||
|
|
||||||
const updateOutsideEditorAnnotation = Annotation.define<boolean>()
|
const updateOutsideEditorAnnotation = Annotation.define<boolean>()
|
||||||
export const updateOutsideEditorEvent = updateOutsideEditorAnnotation.of(true)
|
export const updateOutsideEditorEvent = updateOutsideEditorAnnotation.of(true)
|
||||||
|
|
||||||
@ -25,7 +37,6 @@ const setDiagnosticsAnnotation = Annotation.define<boolean>()
|
|||||||
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
||||||
|
|
||||||
export default class EditorManager {
|
export default class EditorManager {
|
||||||
private _editorView: EditorView | null = null
|
|
||||||
private _copilotEnabled: boolean = true
|
private _copilotEnabled: boolean = true
|
||||||
|
|
||||||
private _isShiftDown: boolean = false
|
private _isShiftDown: boolean = false
|
||||||
@ -47,6 +58,8 @@ export default class EditorManager {
|
|||||||
|
|
||||||
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
private _highlightRange: Array<[number, number]> = [[0, 0]]
|
||||||
|
|
||||||
|
public _editorView: EditorView | null = null
|
||||||
|
|
||||||
setCopilotEnabled(enabled: boolean) {
|
setCopilotEnabled(enabled: boolean) {
|
||||||
this._copilotEnabled = enabled
|
this._copilotEnabled = enabled
|
||||||
}
|
}
|
||||||
|
327
src/editor/plugins/lsp/kcl/colors.ts
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import {
|
||||||
|
EditorView,
|
||||||
|
WidgetType,
|
||||||
|
ViewUpdate,
|
||||||
|
ViewPlugin,
|
||||||
|
DecorationSet,
|
||||||
|
Decoration,
|
||||||
|
} from '@codemirror/view'
|
||||||
|
import { Range, Extension, Text } from '@codemirror/state'
|
||||||
|
import { NodeProp, Tree } from '@lezer/common'
|
||||||
|
import { language, syntaxTree } from '@codemirror/language'
|
||||||
|
|
||||||
|
interface PickerState {
|
||||||
|
from: number
|
||||||
|
to: number
|
||||||
|
alpha: string
|
||||||
|
colorType: ColorType
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WidgetOptions extends PickerState {
|
||||||
|
color: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ColorData = Omit<WidgetOptions, 'from' | 'to'>
|
||||||
|
|
||||||
|
const pickerState = new WeakMap<HTMLInputElement, PickerState>()
|
||||||
|
|
||||||
|
export enum ColorType {
|
||||||
|
hex = 'HEX',
|
||||||
|
}
|
||||||
|
|
||||||
|
const hexRegex = /(^|\b)(#[0-9a-f]{3,9})(\b|$)/i
|
||||||
|
|
||||||
|
function discoverColorsInKCL(
|
||||||
|
syntaxTree: Tree,
|
||||||
|
from: number,
|
||||||
|
to: number,
|
||||||
|
typeName: string,
|
||||||
|
doc: Text,
|
||||||
|
language?: string
|
||||||
|
): WidgetOptions | Array<WidgetOptions> | null {
|
||||||
|
switch (typeName) {
|
||||||
|
case 'Program':
|
||||||
|
case 'VariableDeclaration':
|
||||||
|
case 'CallExpression':
|
||||||
|
case 'ObjectExpression':
|
||||||
|
case 'ObjectProperty':
|
||||||
|
case 'ArgumentList':
|
||||||
|
case 'PipeExpression': {
|
||||||
|
let innerTree = syntaxTree.resolveInner(from, 0).tree
|
||||||
|
|
||||||
|
if (!innerTree) {
|
||||||
|
innerTree = syntaxTree.resolveInner(from, 1).tree
|
||||||
|
if (!innerTree) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const overlayTree = innerTree.prop(NodeProp.mounted)?.tree
|
||||||
|
|
||||||
|
if (overlayTree?.type.name !== 'Styles') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const ret: Array<WidgetOptions> = []
|
||||||
|
overlayTree.iterate({
|
||||||
|
from: 0,
|
||||||
|
to: overlayTree.length,
|
||||||
|
enter: ({ type, from: overlayFrom, to: overlayTo }) => {
|
||||||
|
const maybeWidgetOptions = discoverColorsInKCL(
|
||||||
|
syntaxTree,
|
||||||
|
// We add one because the tree doesn't include the
|
||||||
|
// quotation mark from the style tag
|
||||||
|
from + 1 + overlayFrom,
|
||||||
|
from + 1 + overlayTo,
|
||||||
|
type.name,
|
||||||
|
doc,
|
||||||
|
language
|
||||||
|
)
|
||||||
|
|
||||||
|
if (maybeWidgetOptions) {
|
||||||
|
if (Array.isArray(maybeWidgetOptions)) {
|
||||||
|
console.error('Unexpected nested overlays')
|
||||||
|
ret.push(...maybeWidgetOptions)
|
||||||
|
} else {
|
||||||
|
ret.push(maybeWidgetOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'String': {
|
||||||
|
const result = parseColorLiteral(doc.sliceString(from, to))
|
||||||
|
if (!result) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...result,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseColorLiteral(colorLiteral: string): ColorData | null {
|
||||||
|
const literal = colorLiteral.replace(/"/g, '')
|
||||||
|
const match = hexRegex.exec(literal)
|
||||||
|
if (!match) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const [color, alpha] = toFullHex(literal)
|
||||||
|
|
||||||
|
return {
|
||||||
|
colorType: ColorType.hex,
|
||||||
|
color,
|
||||||
|
alpha,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorPickersDecorations(
|
||||||
|
view: EditorView,
|
||||||
|
discoverColors: typeof discoverColorsInKCL
|
||||||
|
) {
|
||||||
|
const widgets: Array<Range<Decoration>> = []
|
||||||
|
|
||||||
|
const st = syntaxTree(view.state)
|
||||||
|
|
||||||
|
for (const range of view.visibleRanges) {
|
||||||
|
st.iterate({
|
||||||
|
from: range.from,
|
||||||
|
to: range.to,
|
||||||
|
enter: ({ type, from, to }) => {
|
||||||
|
const maybeWidgetOptions = discoverColors(
|
||||||
|
st,
|
||||||
|
from,
|
||||||
|
to,
|
||||||
|
type.name,
|
||||||
|
view.state.doc,
|
||||||
|
view.state.facet(language)?.name
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!maybeWidgetOptions) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(maybeWidgetOptions)) {
|
||||||
|
widgets.push(
|
||||||
|
Decoration.widget({
|
||||||
|
widget: new ColorPickerWidget(maybeWidgetOptions),
|
||||||
|
side: 1,
|
||||||
|
}).range(maybeWidgetOptions.from)
|
||||||
|
)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const wo of maybeWidgetOptions) {
|
||||||
|
widgets.push(
|
||||||
|
Decoration.widget({
|
||||||
|
widget: new ColorPickerWidget(wo),
|
||||||
|
side: 1,
|
||||||
|
}).range(wo.from)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return Decoration.set(widgets)
|
||||||
|
}
|
||||||
|
|
||||||
|
function toFullHex(color: string): string[] {
|
||||||
|
if (color.length === 4) {
|
||||||
|
// 3-char hex
|
||||||
|
return [
|
||||||
|
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
||||||
|
'',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.length === 5) {
|
||||||
|
// 4-char hex (alpha)
|
||||||
|
return [
|
||||||
|
`#${color[1].repeat(2)}${color[2].repeat(2)}${color[3].repeat(2)}`,
|
||||||
|
color[4].repeat(2),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (color.length === 9) {
|
||||||
|
// 8-char hex (alpha)
|
||||||
|
return [`#${color.slice(1, -2)}`, color.slice(-2)]
|
||||||
|
}
|
||||||
|
|
||||||
|
return [color, '']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const wrapperClassName = 'cm-css-color-picker-wrapper'
|
||||||
|
|
||||||
|
class ColorPickerWidget extends WidgetType {
|
||||||
|
private readonly state: PickerState
|
||||||
|
private readonly color: string
|
||||||
|
|
||||||
|
constructor({ color, ...state }: WidgetOptions) {
|
||||||
|
super()
|
||||||
|
this.state = state
|
||||||
|
this.color = color
|
||||||
|
}
|
||||||
|
|
||||||
|
eq(other: ColorPickerWidget) {
|
||||||
|
return (
|
||||||
|
other.state.colorType === this.state.colorType &&
|
||||||
|
other.color === this.color &&
|
||||||
|
other.state.from === this.state.from &&
|
||||||
|
other.state.to === this.state.to &&
|
||||||
|
other.state.alpha === this.state.alpha
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
toDOM() {
|
||||||
|
const picker = document.createElement('input')
|
||||||
|
pickerState.set(picker, this.state)
|
||||||
|
picker.type = 'color'
|
||||||
|
picker.value = this.color
|
||||||
|
|
||||||
|
const wrapper = document.createElement('span')
|
||||||
|
wrapper.appendChild(picker)
|
||||||
|
wrapper.className = wrapperClassName
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
ignoreEvent() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const colorPickerTheme = EditorView.baseTheme({
|
||||||
|
[`.${wrapperClassName}`]: {
|
||||||
|
display: 'inline-block',
|
||||||
|
outline: '1px solid #eee',
|
||||||
|
marginRight: '0.6ch',
|
||||||
|
height: '1em',
|
||||||
|
width: '1em',
|
||||||
|
transform: 'translateY(1px)',
|
||||||
|
},
|
||||||
|
[`.${wrapperClassName} input[type="color"]`]: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
padding: 0,
|
||||||
|
border: 'none',
|
||||||
|
'&::-webkit-color-swatch-wrapper': {
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
'&::-webkit-color-swatch': {
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
'&::-moz-color-swatch': {
|
||||||
|
border: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
interface IFactoryOptions {
|
||||||
|
discoverColors: typeof discoverColorsInKCL
|
||||||
|
}
|
||||||
|
|
||||||
|
export const makeColorPicker = (options: IFactoryOptions) =>
|
||||||
|
ViewPlugin.fromClass(
|
||||||
|
class ColorPickerViewPlugin {
|
||||||
|
decorations: DecorationSet
|
||||||
|
|
||||||
|
constructor(view: EditorView) {
|
||||||
|
this.decorations = colorPickersDecorations(view, options.discoverColors)
|
||||||
|
}
|
||||||
|
|
||||||
|
update(update: ViewUpdate) {
|
||||||
|
if (update.docChanged || update.viewportChanged) {
|
||||||
|
this.decorations = colorPickersDecorations(
|
||||||
|
update.view,
|
||||||
|
options.discoverColors
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
decorations: (v) => v.decorations,
|
||||||
|
eventHandlers: {
|
||||||
|
change: (e, view) => {
|
||||||
|
const target = e.target as HTMLInputElement
|
||||||
|
if (
|
||||||
|
target.nodeName !== 'INPUT' ||
|
||||||
|
!target.parentElement ||
|
||||||
|
!target.parentElement.classList.contains(wrapperClassName)
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = pickerState.get(target)!
|
||||||
|
|
||||||
|
let converted = '"' + target.value + data.alpha + '"'
|
||||||
|
|
||||||
|
view.dispatch({
|
||||||
|
changes: {
|
||||||
|
from: data.from,
|
||||||
|
to: data.to,
|
||||||
|
insert: converted,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export const colorPicker: Extension = [
|
||||||
|
makeColorPicker({ discoverColors: discoverColorsInKCL }),
|
||||||
|
colorPickerTheme,
|
||||||
|
]
|
@ -12,6 +12,7 @@ export const kclHighlight = styleTags({
|
|||||||
'AddOp MultOp ExpOp': t.arithmeticOperator,
|
'AddOp MultOp ExpOp': t.arithmeticOperator,
|
||||||
BangOp: t.logicOperator,
|
BangOp: t.logicOperator,
|
||||||
CompOp: t.compareOperator,
|
CompOp: t.compareOperator,
|
||||||
|
LogicOp: t.logicOperator,
|
||||||
'Equals Arrow': t.definitionOperator,
|
'Equals Arrow': t.definitionOperator,
|
||||||
PipeOperator: t.controlOperator,
|
PipeOperator: t.controlOperator,
|
||||||
String: t.string,
|
String: t.string,
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
mult @left
|
mult @left
|
||||||
add @left
|
add @left
|
||||||
comp @left
|
comp @left
|
||||||
|
logic @left
|
||||||
pipe @left
|
pipe @left
|
||||||
range
|
range
|
||||||
}
|
}
|
||||||
@ -40,7 +41,8 @@ expression[@isGroup=Expression] {
|
|||||||
expression !add AddOp expression |
|
expression !add AddOp expression |
|
||||||
expression !mult MultOp expression |
|
expression !mult MultOp expression |
|
||||||
expression !exp ExpOp expression |
|
expression !exp ExpOp expression |
|
||||||
expression !comp CompOp expression
|
expression !comp CompOp expression |
|
||||||
|
expression !logic LogicOp expression
|
||||||
} |
|
} |
|
||||||
UnaryExpression { UnaryOp expression } |
|
UnaryExpression { UnaryOp expression } |
|
||||||
ParenthesizedExpression { "(" expression ")" } |
|
ParenthesizedExpression { "(" expression ")" } |
|
||||||
@ -89,6 +91,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
|||||||
AddOp { "+" | "-" }
|
AddOp { "+" | "-" }
|
||||||
MultOp { "/" | "*" | "\\" }
|
MultOp { "/" | "*" | "\\" }
|
||||||
ExpOp { "^" }
|
ExpOp { "^" }
|
||||||
|
LogicOp { "|" | "&" }
|
||||||
BangOp { "!" }
|
BangOp { "!" }
|
||||||
CompOp { "==" | "!=" | "<=" | ">=" | "<" | ">" }
|
CompOp { "==" | "!=" | "<=" | ">=" | "<" | ">" }
|
||||||
Equals { "=" }
|
Equals { "=" }
|
||||||
|
@ -17,6 +17,7 @@ import { kclPlugin } from '.'
|
|||||||
import type * as LSP from 'vscode-languageserver-protocol'
|
import type * as LSP from 'vscode-languageserver-protocol'
|
||||||
// @ts-ignore: No types available
|
// @ts-ignore: No types available
|
||||||
import { parser } from './kcl.grammar'
|
import { parser } from './kcl.grammar'
|
||||||
|
import { colorPicker } from './colors'
|
||||||
|
|
||||||
export interface LanguageOptions {
|
export interface LanguageOptions {
|
||||||
workspaceFolders: LSP.WorkspaceFolder[]
|
workspaceFolders: LSP.WorkspaceFolder[]
|
||||||
@ -54,14 +55,14 @@ export const KclLanguage = LRLanguage.define({
|
|||||||
})
|
})
|
||||||
|
|
||||||
export function kcl(options: LanguageOptions) {
|
export function kcl(options: LanguageOptions) {
|
||||||
return new LanguageSupport(
|
return new LanguageSupport(KclLanguage, [
|
||||||
KclLanguage,
|
colorPicker,
|
||||||
kclPlugin({
|
kclPlugin({
|
||||||
documentUri: options.documentUri,
|
documentUri: options.documentUri,
|
||||||
workspaceFolders: options.workspaceFolders,
|
workspaceFolders: options.workspaceFolders,
|
||||||
allowHTMLContent: true,
|
allowHTMLContent: true,
|
||||||
client: options.client,
|
client: options.client,
|
||||||
processLspNotification: options.processLspNotification,
|
processLspNotification: options.processLspNotification,
|
||||||
})
|
}),
|
||||||
)
|
])
|
||||||
}
|
}
|
||||||
|
@ -311,8 +311,6 @@ export class KclManager {
|
|||||||
// Do not send send scene commands if the program was interrupted, go to clean up
|
// Do not send send scene commands if the program was interrupted, go to clean up
|
||||||
if (!isInterrupted) {
|
if (!isInterrupted) {
|
||||||
this.addDiagnostics(await lintAst({ ast: ast }))
|
this.addDiagnostics(await lintAst({ ast: ast }))
|
||||||
|
|
||||||
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
|
||||||
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
setSelectionFilterToDefault(execState.memory, this.engineCommandManager)
|
||||||
|
|
||||||
if (args.zoomToFit) {
|
if (args.zoomToFit) {
|
||||||
@ -358,7 +356,13 @@ export class KclManager {
|
|||||||
this.lastSuccessfulProgramMemory = execState.memory
|
this.lastSuccessfulProgramMemory = execState.memory
|
||||||
}
|
}
|
||||||
this.ast = { ...ast }
|
this.ast = { ...ast }
|
||||||
|
// updateArtifactGraph relies on updated executeState/programMemory
|
||||||
|
await this.engineCommandManager.updateArtifactGraph(this.ast)
|
||||||
this._executeCallback()
|
this._executeCallback()
|
||||||
|
if (!isInterrupted) {
|
||||||
|
sceneInfra.modelingSend({ type: 'code edit during sketch' })
|
||||||
|
}
|
||||||
|
|
||||||
this.engineCommandManager.addCommandLog({
|
this.engineCommandManager.addCommandLog({
|
||||||
type: 'execution-done',
|
type: 'execution-done',
|
||||||
data: null,
|
data: null,
|
||||||
|