Compare commits
67 Commits
jtran/json
...
ben/involu
Author | SHA1 | Date | |
---|---|---|---|
3fcdf845c1 | |||
0ef1483e11 | |||
bbaaf86e4d | |||
6a0e10f8ab | |||
5c0ca52291 | |||
bff13f6bfe | |||
6001b71f06 | |||
bd1e68a4c8 | |||
8589f8fc5f | |||
6641e1178b | |||
6e0f1e71b2 | |||
0e945b6457 | |||
19155a9132 | |||
305d613d40 | |||
95f2caacab | |||
8f61ee1d2f | |||
b0594712f8 | |||
a3821f6229 | |||
b02dbd4fe6 | |||
668f6671a9 | |||
f6387eb7e9 | |||
83a87b046f | |||
20c2ce3bac | |||
2956f9ed55 | |||
510d74f2c7 | |||
457ab28f74 | |||
1502f923ee | |||
f03a684eec | |||
45e17c50e7 | |||
6bf74379a7 | |||
01c6fd53fa | |||
42082f72cd | |||
55baf58682 | |||
f8306c0275 | |||
900ef9e18d | |||
02bd211b70 | |||
37d34d45ff | |||
ed774e67b2 | |||
a46186573c | |||
90f6c1bb04 | |||
41d946b339 | |||
b7385d5f25 | |||
3d22f6cd66 | |||
9730e3f5b3 | |||
29d6b22d63 | |||
f99e44e371 | |||
8be36d3d16 | |||
bc1742af48 | |||
e4080cc184 | |||
18de6ccb59 | |||
f00ea4cf5e | |||
c5539be814 | |||
f5d6a12d8c | |||
e4e18dfd4b | |||
30ee547ce4 | |||
f8ca6ad746 | |||
7a90d029e1 | |||
2900858171 | |||
0dee219e46 | |||
790613e708 | |||
0a35722595 | |||
f30fc376ee | |||
8c1a95833d | |||
4f1ba1be01 | |||
90acc00369 | |||
e4fe8a4440 | |||
fbc3251c3f |
@ -19,6 +19,11 @@
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
@ -61,6 +66,7 @@
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
"react/no-unknown-property": "error",
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
|
11
.github/ci-cd-scripts/playwright-electron.sh
vendored
11
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -26,8 +26,8 @@ max_retries=1
|
||||
# Retry failed tests, doing our own retries because using inbuilt Playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retries ]]; do
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
status=$(jq -r '.status' test-results/.last-run.json)
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == *ubuntu* ]]; then
|
||||
@ -56,10 +56,11 @@ done
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
# If it still fails after 3 retries, then fail the job
|
||||
status=$(jq -r '.status' test-results/.last-run.json)
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
# If it still fails after retries, then fail the job
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
13
.github/workflows/e2e-tests.yml
vendored
13
.github/workflows/e2e-tests.yml
vendored
@ -285,21 +285,10 @@ jobs:
|
||||
# TODO: enable namespace-profile-windows-latest once available
|
||||
os:
|
||||
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
|
||||
- namespace-profile-macos-6-cores
|
||||
- namespace-profile-macos-8-cores
|
||||
- windows-latest-8-cores
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
# Disable macos and windows tests on hourly e2e tests since we only care
|
||||
# about server side changes.
|
||||
# Technique from https://github.com/joaomcteixeira/python-project-skeleton/pull/31/files
|
||||
isScheduled:
|
||||
- ${{ github.event_name == 'schedule' }}
|
||||
exclude:
|
||||
- os: namespace-profile-macos-6-cores
|
||||
isScheduled: true
|
||||
- os: windows-latest-8-cores
|
||||
isScheduled: true
|
||||
# TODO: add ref here for main and latest release tag
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
@ -5,6 +5,6 @@ command = "vscode-eslint-language-server"
|
||||
[[language]]
|
||||
name = "typescript"
|
||||
auto-format = true
|
||||
formatter = { command = "node_modules/.bin/prettier", args = ["--parser", "typescript"] }
|
||||
language-servers = [ { name = "eslint", only-features = [ "diagnostics" ] }, "typescript-language-server" ]
|
||||
formatter = { command = "./node_modules/@biomejs/biome/bin/biome", args = ["format", "--write", "--stdin-file-path=foo.ts"] }
|
||||
# language-servers = [ { name = "eslint", only-features = [ "diagnostics" ] }, "typescript-language-server" ]
|
||||
|
||||
|
1
.husky/pre-commit
Executable file
1
.husky/pre-commit
Executable file
@ -0,0 +1 @@
|
||||
npm run fmt
|
@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
npm run fmt-check
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
40
docs/kcl/assertIs.md
Normal file
40
docs/kcl/assertIs.md
Normal file
File diff suppressed because one or more lines are too long
@ -10,8 +10,10 @@ Draw a smooth, continuous, curved line segment from the current origin to the de
|
||||
|
||||
```js
|
||||
bezierCurve(
|
||||
data: BezierData,
|
||||
sketch: Sketch,
|
||||
control1: [number],
|
||||
control2: [number],
|
||||
end: [number],
|
||||
tag?: TagDeclarator,
|
||||
): Sketch
|
||||
```
|
||||
@ -21,9 +23,11 @@ bezierCurve(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`BezierData`](/docs/kcl/types/BezierData) | Data to draw a bezier curve. | Yes |
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
|
||||
| `control1` | [`[number]`](/docs/kcl/types/number) | First control point for the cubic | Yes |
|
||||
| `control2` | [`[number]`](/docs/kcl/types/number) | Second control point for the cubic | Yes |
|
||||
| `end` | [`[number]`](/docs/kcl/types/number) | How far away (along the X and Y axes) should this line go? | Yes |
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | Create a new tag which refers to this line | No |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -36,11 +40,7 @@ bezierCurve(
|
||||
exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [0, 10])
|
||||
|> bezierCurve({
|
||||
to = [10, 10],
|
||||
control1 = [5, 0],
|
||||
control2 = [5, 10]
|
||||
}, %)
|
||||
|> bezierCurve(control1 = [5, 0], control2 = [5, 10], end = [10, 10])
|
||||
|> line(endAbsolute = [10, 0])
|
||||
|> close()
|
||||
|
||||
|
258
docs/kcl/clone.md
Normal file
258
docs/kcl/clone.md
Normal file
File diff suppressed because one or more lines are too long
@ -42,18 +42,10 @@ extrude(
|
||||
example = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [10, 0])
|
||||
|> arc({
|
||||
angleStart = 120,
|
||||
angleEnd = 0,
|
||||
radius = 5
|
||||
}, %)
|
||||
|> arc(angleStart = 120, angleEnd = 0, radius = 5)
|
||||
|> line(end = [5, 0])
|
||||
|> line(end = [0, 10])
|
||||
|> bezierCurve({
|
||||
control1 = [-10, 0],
|
||||
control2 = [2, 10],
|
||||
to = [-5, 10]
|
||||
}, %)
|
||||
|> bezierCurve(control1 = [-10, 0], control2 = [2, 10], end = [-5, 10])
|
||||
|> line(end = [-5, -2])
|
||||
|> close()
|
||||
|> extrude(length = 10)
|
||||
@ -64,18 +56,10 @@ example = startSketchOn(XZ)
|
||||
```js
|
||||
exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([-10, 0], %)
|
||||
|> arc({
|
||||
angleStart = 120,
|
||||
angleEnd = -60,
|
||||
radius = 5
|
||||
}, %)
|
||||
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [5, 0])
|
||||
|> bezierCurve({
|
||||
control1 = [-3, 0],
|
||||
control2 = [2, 10],
|
||||
to = [-5, 10]
|
||||
}, %)
|
||||
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||
|> line(end = [-4, 10])
|
||||
|> line(end = [-5, -2])
|
||||
|> close()
|
||||
@ -88,18 +72,10 @@ example = extrude(exampleSketch, length = 10)
|
||||
```js
|
||||
exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([-10, 0], %)
|
||||
|> arc({
|
||||
angleStart = 120,
|
||||
angleEnd = -60,
|
||||
radius = 5
|
||||
}, %)
|
||||
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [5, 0])
|
||||
|> bezierCurve({
|
||||
control1 = [-3, 0],
|
||||
control2 = [2, 10],
|
||||
to = [-5, 10]
|
||||
}, %)
|
||||
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||
|> line(end = [-4, 10])
|
||||
|> line(end = [-5, -2])
|
||||
|> close()
|
||||
@ -112,18 +88,10 @@ example = extrude(exampleSketch, length = 20, symmetric = true)
|
||||
```js
|
||||
exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([-10, 0], %)
|
||||
|> arc({
|
||||
angleStart = 120,
|
||||
angleEnd = -60,
|
||||
radius = 5
|
||||
}, %)
|
||||
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [5, 0])
|
||||
|> bezierCurve({
|
||||
control1 = [-3, 0],
|
||||
control2 = [2, 10],
|
||||
to = [-5, 10]
|
||||
}, %)
|
||||
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||
|> line(end = [-4, 10])
|
||||
|> line(end = [-5, -2])
|
||||
|> close()
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from centimeters to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from centimeters to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42cm`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in centimeters.
|
||||
|
||||
For example, if the current file uses inches, `fromCm(1)` will return `0.393701`. If the current file uses millimeters, `fromCm(1)` will return `10`. If the current file uses centimeters, `fromCm(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from feet to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from feet to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42ft`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in feet.
|
||||
|
||||
For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from inches to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from inches to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42inch`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in inches.
|
||||
|
||||
For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from meters to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from meters to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42m`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in meters.
|
||||
|
||||
For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from mm to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from mm to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42mm`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters.
|
||||
|
||||
For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from yards to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from yards to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42yd`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in yards.
|
||||
|
||||
For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`.
|
||||
|
@ -9,7 +9,7 @@ Get the next adjacent edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
getNextAdjacentEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the next adjacent edge of. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getOppositeEdge(tag: TagIdentifier): Uuid
|
||||
getOppositeEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getOppositeEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the opposite edge of. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
|
||||
|
||||
|
||||
```js
|
||||
getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
|
||||
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the previous adjacent edge of. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -22,18 +22,8 @@ layout: manual
|
||||
* [`string`](kcl/types/string)
|
||||
* [`tag`](kcl/types/tag)
|
||||
* **std**
|
||||
* [`Axis2d`](kcl/types/Axis2d)
|
||||
* [`Axis3d`](kcl/types/Axis3d)
|
||||
* [`END`](kcl/consts/std-END)
|
||||
* [`Edge`](kcl/types/Edge)
|
||||
* [`Face`](kcl/types/Face)
|
||||
* [`Helix`](kcl/types/Helix)
|
||||
* [`Plane`](kcl/types/Plane)
|
||||
* [`Point2d`](kcl/types/Point2d)
|
||||
* [`Point3d`](kcl/types/Point3d)
|
||||
* [`START`](kcl/consts/std-START)
|
||||
* [`Sketch`](kcl/types/Sketch)
|
||||
* [`Solid`](kcl/types/Solid)
|
||||
* [`X`](kcl/consts/std-X)
|
||||
* [`XY`](kcl/consts/std-XY)
|
||||
* [`XZ`](kcl/consts/std-XZ)
|
||||
@ -42,36 +32,24 @@ layout: manual
|
||||
* [`Z`](kcl/consts/std-Z)
|
||||
* [`abs`](kcl/abs)
|
||||
* [`acos`](kcl/acos)
|
||||
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
|
||||
* [`angleToMatchLengthY`](kcl/angleToMatchLengthY)
|
||||
* [`angledLine`](kcl/angledLine)
|
||||
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
||||
* [`appearance`](kcl/appearance)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arcTo`](kcl/arcTo)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
||||
* [`assertLessThan`](kcl/assertLessThan)
|
||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||
* [`assertIs`](kcl/assertIs)
|
||||
* [`atan`](kcl/atan)
|
||||
* [`atan2`](kcl/atan2)
|
||||
* [`bezierCurve`](kcl/bezierCurve)
|
||||
* [`ceil`](kcl/ceil)
|
||||
* [`chamfer`](kcl/chamfer)
|
||||
* [`circleThreePoint`](kcl/circleThreePoint)
|
||||
* [`clone`](kcl/clone)
|
||||
* [`close`](kcl/close)
|
||||
* [`extrude`](kcl/extrude)
|
||||
* [`fillet`](kcl/fillet)
|
||||
* [`floor`](kcl/floor)
|
||||
* [`fromCm`](kcl/fromCm)
|
||||
* [`fromFt`](kcl/fromFt)
|
||||
* [`fromInches`](kcl/fromInches)
|
||||
* [`fromM`](kcl/fromM)
|
||||
* [`fromMm`](kcl/fromMm)
|
||||
* [`fromYd`](kcl/fromYd)
|
||||
* [`getCommonEdge`](kcl/getCommonEdge)
|
||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
@ -95,7 +73,7 @@ layout: manual
|
||||
* [`map`](kcl/map)
|
||||
* [`max`](kcl/max)
|
||||
* [`min`](kcl/min)
|
||||
* [`offsetPlane`](kcl/offsetPlane)
|
||||
* [`offsetPlane`](kcl/std-offsetPlane)
|
||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||
* [`patternCircular3d`](kcl/patternCircular3d)
|
||||
* [`patternLinear2d`](kcl/patternLinear2d)
|
||||
@ -131,8 +109,14 @@ layout: manual
|
||||
* [`sweep`](kcl/sweep)
|
||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
* [`toDegrees`](kcl/toDegrees)
|
||||
* [`toRadians`](kcl/toRadians)
|
||||
* [`toCentimeters`](kcl/std-toCentimeters)
|
||||
* [`toDegrees`](kcl/std-toDegrees)
|
||||
* [`toFeet`](kcl/std-toFeet)
|
||||
* [`toInches`](kcl/std-toInches)
|
||||
* [`toMeters`](kcl/std-toMeters)
|
||||
* [`toMillimeters`](kcl/std-toMillimeters)
|
||||
* [`toRadians`](kcl/std-toRadians)
|
||||
* [`toYards`](kcl/std-toYards)
|
||||
* [`translate`](kcl/translate)
|
||||
* [`union`](kcl/union)
|
||||
* [`xLine`](kcl/xLine)
|
||||
@ -153,3 +137,14 @@ layout: manual
|
||||
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
|
||||
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
|
||||
* [`turns::ZERO`](kcl/consts/std-turns-ZERO)
|
||||
* **std::types**
|
||||
* [`Axis2d`](kcl/types/Axis2d)
|
||||
* [`Axis3d`](kcl/types/Axis3d)
|
||||
* [`Edge`](kcl/types/Edge)
|
||||
* [`Face`](kcl/types/Face)
|
||||
* [`Helix`](kcl/types/Helix)
|
||||
* [`Plane`](kcl/types/Plane)
|
||||
* [`Point2d`](kcl/types/Point2d)
|
||||
* [`Point3d`](kcl/types/Point3d)
|
||||
* [`Sketch`](kcl/types/Sketch)
|
||||
* [`Solid`](kcl/types/Solid)
|
||||
|
@ -34,7 +34,7 @@ int(num: number): number
|
||||
|
||||
```js
|
||||
n = int(ceil(5 / 2))
|
||||
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||
assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3")
|
||||
// Draw n cylinders.
|
||||
startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 2)
|
||||
|
@ -21,4 +21,6 @@ once fixed in engine will just start working here with no language changes.
|
||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
|
||||
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.
|
||||
|
@ -24,8 +24,8 @@ legAngX(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -35,7 +35,7 @@ legAngX(
|
||||
### Examples
|
||||
|
||||
```js
|
||||
legAngX(5, 3)
|
||||
legAngX(hypotenuse = 5, leg = 3)
|
||||
```
|
||||
|
||||
|
||||
|
@ -24,8 +24,8 @@ legAngY(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -35,7 +35,7 @@ legAngY(
|
||||
### Examples
|
||||
|
||||
```js
|
||||
legAngY(5, 3)
|
||||
legAngY(hypotenuse = 5, leg = 3)
|
||||
```
|
||||
|
||||
|
||||
|
@ -24,8 +24,8 @@ legLen(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | | Yes |
|
||||
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes |
|
||||
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -35,7 +35,7 @@ legLen(
|
||||
### Examples
|
||||
|
||||
```js
|
||||
legLen(5, 3)
|
||||
legLen(hypotenuse = 5, leg = 3)
|
||||
```
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ isolated from other files as a separate module.
|
||||
When you define a function, you can use `export` before it to make it available
|
||||
to other modules.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
return x + 1
|
||||
@ -31,11 +31,11 @@ Imported files _must_ be in the same project so that units are uniform across
|
||||
modules. This means that it must be in the same directory.
|
||||
|
||||
Import statements must be at the top-level of a file. It is not allowed to have
|
||||
an `import` statement inside a function or in the body of an if-else.
|
||||
an `import` statement inside a function or in the body of an if‑else.
|
||||
|
||||
Multiple functions can be exported in a file.
|
||||
|
||||
```
|
||||
```kcl
|
||||
// util.kcl
|
||||
export fn increment(x) {
|
||||
return x + 1
|
||||
@ -58,6 +58,211 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||
import increment as inc, decrement as dec from "util.kcl"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Functions vs `clone`
|
||||
|
||||
There are two common patterns for re‑using geometry:
|
||||
|
||||
1. **Wrap the construction in a function** – flexible and fully parametric.
|
||||
2. **Duplicate an existing object with `clone`** – lightning‑fast, but an exact
|
||||
duplicate.
|
||||
|
||||
### Parametric function example
|
||||
|
||||
```kcl
|
||||
fn cube(center) {
|
||||
return startSketchOn(XY)
|
||||
|> startProfileAt([center[0] - 10, center[1] - 10], %)
|
||||
|> line(endAbsolute = [center[0] + 10, center[1] - 10])
|
||||
|> line(endAbsolute = [center[0] + 10, center[1] + 10])
|
||||
|> line(endAbsolute = [center[0] - 10, center[1] + 10])
|
||||
|> close()
|
||||
|> extrude(length = 10)
|
||||
}
|
||||
|
||||
myCube = cube([0, 0])
|
||||
```
|
||||
|
||||
*Pros*
|
||||
- Any argument can be a parameter – size, position, appearance, etc.
|
||||
- Works great inside loops, arrays, or optimisation sweeps.
|
||||
|
||||
*Cons*
|
||||
- Every invocation rebuilds the entire feature tree.
|
||||
- **Slower** than a straight duplicate – each call is its own render job.
|
||||
|
||||
### `clone` example
|
||||
|
||||
```kcl
|
||||
sketch001 = startSketchOn(-XZ)
|
||||
|> circle(center = [0, 0], radius = 10)
|
||||
|> extrude(length = 5)
|
||||
|> appearance(color = "#ff0000", metalness = 90, roughness = 90)
|
||||
|
||||
sketch002 = clone(sketch001) // ✓ instant copy
|
||||
```
|
||||
|
||||
*Pros*
|
||||
- Roughly an O(1) operation – we just duplicate the underlying engine handle.
|
||||
- Perfect when you need ten identical bolts or two copies of the same imported STEP file.
|
||||
|
||||
*Cons*
|
||||
- **Not parametric** – the clone is exactly the same shape as the source.
|
||||
- If you need to tweak dimensions per‑instance, you’re back to a function.
|
||||
|
||||
> **Rule of thumb** – Reach for `clone` when the geometry is already what you want. Reach for a function when you need customisation.
|
||||
|
||||
---
|
||||
|
||||
## Module‑level parallelism
|
||||
|
||||
Under the hood, the Design Studio runs **every module in parallel** where it can. This means:
|
||||
|
||||
- The top‑level code of `foo.kcl`, `bar.kcl`, and `baz.kcl` all start executing immediately and concurrently.
|
||||
- Imports that read foreign files (STEP/OBJ/…) overlap their I/O and background render.
|
||||
- CPU‑bound calculations in separate modules get their own worker threads.
|
||||
|
||||
### Why modules beat one‑big‑file
|
||||
|
||||
If you shoe‑horn everything into `main.kcl`, each statement runs sequentially:
|
||||
|
||||
```norun
|
||||
import "big.step" as gizmo // blocks main while reading
|
||||
|
||||
gizmo |> translate(x=50) // blocks again while waiting for render
|
||||
```
|
||||
|
||||
Split `gizmo` into its own file and the read/render can overlap whatever else `main.kcl` is doing.
|
||||
|
||||
```norun
|
||||
// gizmo.kcl (worker A)
|
||||
import "big.step"
|
||||
|
||||
// main.kcl (worker B)
|
||||
import "gizmo.kcl" as gizmo // non‑blocking
|
||||
|
||||
// ... other setup ...
|
||||
|
||||
gizmo |> translate(x=50) // only blocks here
|
||||
```
|
||||
|
||||
### Gotcha: defining but **not** calling functions
|
||||
|
||||
Defining a function inside a module is instantaneous – we just record the byte‑code. The heavy lifting happens when the function is **called**. So:
|
||||
|
||||
```norun
|
||||
// util.kcl
|
||||
export fn makeBolt(size) { /* … expensive CAD … */ }
|
||||
```
|
||||
|
||||
If `main.kcl` waits until the very end to call `makeBolt`, *none* of that work was parallelised – you’ve pushed the cost back onto the serial tail of your script.
|
||||
|
||||
**Better:** call it early or move the invocation into another module.
|
||||
|
||||
```norun
|
||||
// bolt_instance.kcl
|
||||
import makeBolt from "util.kcl"
|
||||
bolt = makeBolt(5) // executed in parallel
|
||||
bolt
|
||||
```
|
||||
|
||||
Now `main.kcl` can `import "bolt_instance.kcl" as bolt` and get the result that was rendered while it was busy doing other things.
|
||||
|
||||
---
|
||||
|
||||
## Whole module import
|
||||
|
||||
You can also import the whole module. This is useful if you want to use the
|
||||
result of a module as a variable, like a part.
|
||||
|
||||
```norun
|
||||
import "tests/inputs/cube.kcl" as cube
|
||||
cube
|
||||
|> translate(x=10)
|
||||
```
|
||||
|
||||
This imports the whole module and makes it available as `cube`. You can then
|
||||
use it like any other object. The `cube` variable is now a reference to the
|
||||
result of the module. This means that if you change the module, the `cube`
|
||||
variable will change as well.
|
||||
|
||||
In `cube.kcl`, you cannot have multiple objects. It has to be a single part. If
|
||||
you have multiple objects, you will get an error. This is because the module is
|
||||
expected to return a single object that can be used as a variable.
|
||||
|
||||
You also cannot assign that object to a variable. This is because the module is
|
||||
expected to return a single object that can be used as a variable.
|
||||
|
||||
So for example, this is not allowed:
|
||||
|
||||
```norun
|
||||
... a bunch of code to create cube and cube2 ...
|
||||
|
||||
myUnion = union([cube, cube2])
|
||||
```
|
||||
|
||||
What you need to do instead is:
|
||||
|
||||
```norun
|
||||
... a bunch of code to create cube and cube2 ...
|
||||
|
||||
union([cube, cube2])
|
||||
```
|
||||
|
||||
That way the last line will return the union of the two objects.
|
||||
|
||||
Or what you could do instead is:
|
||||
|
||||
```norun
|
||||
... a bunch of code to create cube and cube2 ...
|
||||
|
||||
myUnion = union([cube, cube2])
|
||||
myUnion
|
||||
```
|
||||
|
||||
This will return the union of the two objects, but it will not be assigned to a
|
||||
variable. This is because the module is expected to return a single object that
|
||||
can be used as a variable.
|
||||
|
||||
---
|
||||
|
||||
## Multiple instances of the same import
|
||||
|
||||
Whether you are importing a file from another CAD system or a KCL file, that
|
||||
file represents object(s) in memory. If you import the same file multiple times,
|
||||
it will only be rendered once.
|
||||
|
||||
If you want to have multiple instances of the same object, you can use the
|
||||
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
||||
|
||||
```norun
|
||||
import cube from "tests/inputs/cube.kcl"
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
clone(cube)
|
||||
|> translate(x=20)
|
||||
```
|
||||
|
||||
In the sample above, the `cube` object is imported from a KCL file. The first
|
||||
instance is translated 10 units in the x direction. The second instance is
|
||||
cloned and translated 20 units in the x direction. The two instances are now
|
||||
separate objects in memory, and can be manipulated independently.
|
||||
|
||||
Here is an example with a file from another CAD system:
|
||||
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
cube
|
||||
|> translate(x=10)
|
||||
clone(cube)
|
||||
|> translate(x=20)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Importing files from other CAD systems
|
||||
|
||||
`import` can also be used to import files from other CAD systems. The format of the statement is the
|
||||
@ -69,25 +274,17 @@ import "tests/inputs/cube.obj"
|
||||
// Use `cube` just like a KCL object.
|
||||
```
|
||||
|
||||
```norun
|
||||
import "tests/inputs/cube-2.sldprt" as cube
|
||||
```kcl
|
||||
import "tests/inputs/cube.sldprt" as cube
|
||||
|
||||
// Use `cube` just like a KCL object.
|
||||
```
|
||||
|
||||
You can make the file format explicit using a format attribute (useful if using a different
|
||||
extension), e.g.,
|
||||
|
||||
```norun
|
||||
@(format = obj)
|
||||
import "tests/inputs/cube"
|
||||
```
|
||||
|
||||
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
|
||||
unit of measurement is millimeters. Alternatively you may specify the unit
|
||||
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
|
||||
by using an attribute. Likewise, you can also specify a coordinate system. E.g.,
|
||||
|
||||
```norun
|
||||
```kcl
|
||||
@(unitLength = ft, coords = opengl)
|
||||
import "tests/inputs/cube.obj"
|
||||
```
|
||||
@ -110,97 +307,55 @@ Coordinate systems:
|
||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
||||
|
||||
### Performance
|
||||
---
|
||||
|
||||
Parallelized foreign-file imports now let you overlap file reads, initialization,
|
||||
## Performance deep‑dive for foreign‑file imports
|
||||
|
||||
Parallelized foreign‑file imports now let you overlap file reads, initialization,
|
||||
and rendering. To maximize throughput, you need to understand the three distinct
|
||||
stages—reading, initializing (background render start), and invocation (blocking)
|
||||
—and structure your code to defer blocking operations until the end.
|
||||
|
||||
#### Foreign Import Execution Stages
|
||||
### Foreign import execution stages
|
||||
|
||||
1. **Import (Read) Stage**
|
||||
```norun
|
||||
1. **Import (Read / Initialization) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
```
|
||||
- Reads the file from disk and makes its API available.
|
||||
- **Does _not_** start Engine rendering or block your script.
|
||||
```
|
||||
- Reads the file from disk and makes its API available.
|
||||
- Starts engine rendering but **does not block** your script.
|
||||
- This kick‑starts the render pipeline while you keep executing other code.
|
||||
|
||||
2. **Initialization (Background Render) Stage**
|
||||
```norun
|
||||
2. **Invocation (Blocking) Stage**
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
myCube = cube // <- This line starts background rendering
|
||||
```
|
||||
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
|
||||
- This kick‑starts the render pipeline but doesn’t block—you can continue other work while the Engine processes the model.
|
||||
cube
|
||||
|> translate(z=10) // ← blocks here only
|
||||
```
|
||||
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
|
||||
|
||||
3. **Invocation (Blocking) Stage**
|
||||
```norun
|
||||
import "tests/inputs/cube.step" as cube
|
||||
### Best practices
|
||||
|
||||
myCube = cube
|
||||
#### 1. Defer blocking calls
|
||||
|
||||
myCube
|
||||
|> translate(z=10) // <- This line blocks
|
||||
```
|
||||
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
|
||||
- This is the only point where your script will block.
|
||||
|
||||
> **Nuance:** Foreign imports differ from pure KCL modules—calling the same import symbol multiple times (e.g., `screw` twice) starts background rendering twice.
|
||||
|
||||
#### Best Practices
|
||||
|
||||
##### 1. Defer Blocking Calls
|
||||
Initialize early but delay all transformations until after your heavy computation:
|
||||
```norun
|
||||
import "tests/inputs/cube.step" as cube // 1) Read
|
||||
|
||||
myCube = cube // 2) Background render starts
|
||||
```kcl
|
||||
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
|
||||
|
||||
|
||||
// --- perform other operations and calculations or setup here ---
|
||||
// --- perform other operations and calculations here ---
|
||||
|
||||
|
||||
myCube
|
||||
|> translate(z=10) // 3) Blocks only here
|
||||
```
|
||||
|
||||
##### 2. Encapsulate Imports in Modules
|
||||
Keep `main.kcl` free of reads and initialization; wrap them:
|
||||
|
||||
```norun
|
||||
// imports.kcl
|
||||
import "tests/inputs/cube.step" as cube // Read only
|
||||
|
||||
|
||||
export myCube = cube // Kick off rendering
|
||||
```
|
||||
|
||||
```norun
|
||||
// main.kcl
|
||||
import myCube from "imports.kcl" // Import the initialized object
|
||||
|
||||
|
||||
// ... computations ...
|
||||
|
||||
|
||||
myCube
|
||||
|> translate(z=10) // Blocking call at the end
|
||||
```
|
||||
|
||||
##### 3. Avoid Immediate Method Calls
|
||||
|
||||
```norun
|
||||
import "tests/inputs/cube.step" as cube
|
||||
|
||||
cube
|
||||
|> translate(z=10) // Blocks immediately, negating parallelism
|
||||
|> translate(z=10) // 2) Blocks only here
|
||||
```
|
||||
|
||||
Both calling methods right on `cube` immediately or leaving an implicit import without assignment introduce blocking.
|
||||
#### 2. Split heavy work into separate modules
|
||||
|
||||
#### Future Improvements
|
||||
Place computationally expensive or IO‑heavy work into its own module so it can render in parallel while `main.kcl` continues.
|
||||
|
||||
#### Future improvements
|
||||
|
||||
Upcoming releases will auto‑analyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
|
||||
|
||||
Upcoming releases will auto‑analyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -174,7 +174,7 @@ t = 0.005 // taper factor [0-1)
|
||||
// Defines how to modify each layer of the vase.
|
||||
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||
fn transform(replicaId) {
|
||||
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8: number(rad)))
|
||||
return {
|
||||
translate = [0, 0, replicaId * 10],
|
||||
scale = [scale, scale, 0]
|
||||
@ -205,12 +205,12 @@ fn transform(i) {
|
||||
}
|
||||
startSketchOn(XY)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> polygon({
|
||||
|> polygon(
|
||||
radius = 10,
|
||||
numSides = 4,
|
||||
center = [0, 0],
|
||||
inscribed = false
|
||||
}, %)
|
||||
inscribed = false,
|
||||
)
|
||||
|> extrude(length = 4)
|
||||
|> patternTransform(instances = 3, transform = transform)
|
||||
```
|
||||
|
@ -10,9 +10,11 @@ Create a regular polygon with the specified number of sides that is either inscr
|
||||
|
||||
```js
|
||||
polygon(
|
||||
data: PolygonData,
|
||||
sketchSurfaceOrGroup: SketchOrSurface,
|
||||
tag?: TagDeclarator,
|
||||
radius: number,
|
||||
numSides: u64,
|
||||
center: [number],
|
||||
inscribed?: bool,
|
||||
): Sketch
|
||||
```
|
||||
|
||||
@ -21,9 +23,11 @@ polygon(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
|
||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
||||
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on | Yes |
|
||||
| `radius` | [`number`](/docs/kcl/types/number) | The radius of the polygon | Yes |
|
||||
| `numSides` | `u64` | The number of sides in the polygon | Yes |
|
||||
| `center` | [`[number]`](/docs/kcl/types/number) | The center point of the polygon | Yes |
|
||||
| `inscribed` | [`bool`](/docs/kcl/types/bool) | Whether the polygon is inscribed (true, the default) or circumscribed (false) about a circle with the specified radius | No |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -35,12 +39,12 @@ polygon(
|
||||
```js
|
||||
// Create a regular hexagon inscribed in a circle of radius 10
|
||||
hex = startSketchOn(XY)
|
||||
|> polygon({
|
||||
|> polygon(
|
||||
radius = 10,
|
||||
numSides = 6,
|
||||
center = [0, 0],
|
||||
inscribed = true
|
||||
}, %)
|
||||
inscribed = true,
|
||||
)
|
||||
|
||||
example = extrude(hex, length = 5)
|
||||
```
|
||||
@ -50,12 +54,12 @@ example = extrude(hex, length = 5)
|
||||
```js
|
||||
// Create a square circumscribed around a circle of radius 5
|
||||
square = startSketchOn(XY)
|
||||
|> polygon({
|
||||
|> polygon(
|
||||
radius = 5.0,
|
||||
numSides = 4,
|
||||
center = [10, 10],
|
||||
inscribed = false
|
||||
}, %)
|
||||
inscribed = false,
|
||||
)
|
||||
example = extrude(square, length = 5)
|
||||
```
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin value.
|
||||
|
||||
|
||||
```js
|
||||
profileStart(sketch: Sketch): [number]
|
||||
profileStart(profile: Sketch): [number]
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStart(sketch: Sketch): [number]
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'x' value.
|
||||
|
||||
|
||||
```js
|
||||
profileStartX(sketch: Sketch): number
|
||||
profileStartX(profile: Sketch): number
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStartX(sketch: Sketch): number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'y' value.
|
||||
|
||||
|
||||
```js
|
||||
profileStartY(sketch: Sketch): number
|
||||
profileStartY(profile: Sketch): number
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStartY(sketch: Sketch): number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -26,7 +26,7 @@ helix(
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `revolutions` | `number(_)` | Number of revolutions. | Yes |
|
||||
| `angleStart` | `number(Angle)` | Start angle (in degrees). | Yes |
|
||||
| `angleStart` | `number(Angle)` | Start angle. | Yes |
|
||||
| `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `radius` | `number(Length)` | Radius of the helix. | No |
|
||||
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 30,
|
||||
length = 3 / cos(toRadians(30)),
|
||||
length = 3 / cos(30deg),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
@ -11,7 +11,7 @@ cartesian (x/y/z grid) coordinates.
|
||||
|
||||
```js
|
||||
polar(
|
||||
angle: number(Angle),
|
||||
angle: number(rad),
|
||||
length: number(Length),
|
||||
): Point2d
|
||||
```
|
||||
@ -21,7 +21,7 @@ polar(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `angle` | `number(Angle)` | | Yes |
|
||||
| `angle` | `number(rad)` | | Yes |
|
||||
| `length` | `number(Length)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 50,
|
||||
length = 15 / sin(toRadians(135)),
|
||||
length = 15 / sin(135deg),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 50,
|
||||
length = 50 * tan(1/2),
|
||||
length = 50 * tan((1/2): number(rad)),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
122
docs/kcl/std-offsetPlane.md
Normal file
122
docs/kcl/std-offsetPlane.md
Normal file
File diff suppressed because one or more lines are too long
27
docs/kcl/std-toCentimeters.md
Normal file
27
docs/kcl/std-toCentimeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toCentimeters"
|
||||
excerpt: "Convert a number to centimeters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to centimeters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toCentimeters(@num: number(cm)): number(cm)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(cm)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(cm)`
|
||||
|
||||
|
||||
|
44
docs/kcl/std-toDegrees.md
Normal file
44
docs/kcl/std-toDegrees.md
Normal file
File diff suppressed because one or more lines are too long
27
docs/kcl/std-toFeet.md
Normal file
27
docs/kcl/std-toFeet.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toFeet"
|
||||
excerpt: "Convert a number to feet from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to feet from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toFeet(@num: number(ft)): number(ft)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(ft)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(ft)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toInches.md
Normal file
27
docs/kcl/std-toInches.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toInches"
|
||||
excerpt: "Convert a number to inches from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to inches from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toInches(@num: number(in)): number(in)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(in)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(in)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toMeters.md
Normal file
27
docs/kcl/std-toMeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toMeters"
|
||||
excerpt: "Convert a number to meters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to meters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toMeters(@num: number(m)): number(m)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(m)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(m)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toMillimeters.md
Normal file
27
docs/kcl/std-toMillimeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toMillimeters"
|
||||
excerpt: "Convert a number to millimeters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to millimeters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toMillimeters(@num: number(mm)): number(mm)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(mm)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(mm)`
|
||||
|
||||
|
||||
|
44
docs/kcl/std-toRadians.md
Normal file
44
docs/kcl/std-toRadians.md
Normal file
File diff suppressed because one or more lines are too long
27
docs/kcl/std-toYards.md
Normal file
27
docs/kcl/std-toYards.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toYards"
|
||||
excerpt: "Converts a number to yards from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Converts a number to yards from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toYards(@num: number(yd)): number(yd)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(yd)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(yd)`
|
||||
|
||||
|
||||
|
69248
docs/kcl/std.json
69248
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
@ -73,7 +73,7 @@ rectangleExtrude = extrude(rectangleSketch, length = 10)
|
||||
```js
|
||||
bottom = startSketchOn(XY)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> arcTo({ end = [10, 10], interior = [5, 1] }, %, $arc1)
|
||||
|> arc(endAbsolute = [10, 10], interiorAbsolute = [5, 1], tag = $arc1)
|
||||
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|
||||
|> close()
|
||||
```
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Axis2d"
|
||||
title: "std::types::Axis2d"
|
||||
excerpt: "An infinite line in 2d space."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Axis3d"
|
||||
title: "std::types::Axis3d"
|
||||
excerpt: "An infinite line in 3d space."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Edge"
|
||||
title: "std::types::Edge"
|
||||
excerpt: "The edge of a solid."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -51,6 +51,26 @@ An extruded arc.
|
||||
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
|
||||
|
||||
|
||||
----
|
||||
An extruded involute.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `extrudeInvolute`| | No |
|
||||
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude surface. | No |
|
||||
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
|
||||
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
|
||||
|
||||
|
||||
----
|
||||
Geometry metadata.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Face"
|
||||
title: "std::types::Face"
|
||||
excerpt: "A face."
|
||||
layout: manual
|
||||
---
|
||||
|
84
docs/kcl/types/GeometryWithImportedGeometry.md
Normal file
84
docs/kcl/types/GeometryWithImportedGeometry.md
Normal file
@ -0,0 +1,84 @@
|
||||
---
|
||||
title: "GeometryWithImportedGeometry"
|
||||
excerpt: "A geometry including an imported geometry."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
A geometry including an imported geometry.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The id of the sketch (this will change when the engine's reference to it changes). | No |
|
||||
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
|
||||
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
|
||||
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
|
||||
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |
|
||||
| `artifactId` |[`string`](/docs/kcl/types/string)| The original id of the sketch. This stays the same even if the sketch is is sketched on face etc. | No |
|
||||
| `originalId` |[`string`](/docs/kcl/types/string)| | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: [`Solid`](/docs/kcl/types/Solid)| | No |
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The id of the solid. | No |
|
||||
| `artifactId` |[`string`](/docs/kcl/types/string)| The artifact ID of the solid. Unlike `id`, this doesn't change. | No |
|
||||
| `value` |`[` [`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface) `]`| The extrude surfaces. | No |
|
||||
| `sketch` |[`Sketch`](/docs/kcl/types/Sketch)| The sketch. | No |
|
||||
| `height` |[`number`](/docs/kcl/types/number)| The height of the solid. | No |
|
||||
| `startCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion start cap | No |
|
||||
| `endCapId` |[`string`](/docs/kcl/types/string)| The id of the extrusion end cap | No |
|
||||
| `edgeCuts` |`[` [`EdgeCut`](/docs/kcl/types/EdgeCut) `]`| Chamfers or fillets on this solid. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| The units of the solid. | No |
|
||||
| `sectional` |`boolean`| Is this a sectional solid? | No |
|
||||
|
||||
|
||||
----
|
||||
Data for an imported geometry.
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ImportedGeometry`| | No |
|
||||
| `id` |[`string`](/docs/kcl/types/string)| The ID of the imported geometry. | No |
|
||||
| `value` |`[` [`string`](/docs/kcl/types/string) `]`| The original file paths. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Helix"
|
||||
title: "std::types::Helix"
|
||||
excerpt: "A helix."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -141,8 +141,8 @@ A base path.
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArcThreePoint`| | No |
|
||||
| `p1` |`[number, number]`| Point 1 of the arc (base on the end of previous segment) | No |
|
||||
| `p2` |`[number, number]`| Point 2 of the arc (interior kwarg) | No |
|
||||
| `p3` |`[number, number]`| Point 3 of the arc (end kwarg) | No |
|
||||
| `p2` |`[number, number]`| Point 2 of the arc (interiorAbsolute kwarg) | No |
|
||||
| `p3` |`[number, number]`| Point 3 of the arc (endAbsolute kwarg) | No |
|
||||
| `from` |`[number, number]`| The from point. | No |
|
||||
| `to` |`[number, number]`| The to point. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
@ -241,6 +241,31 @@ A circular arc, not necessarily tangential to the current point.
|
||||
|
||||
|
||||
----
|
||||
An involute of a circle of start_radius ending at end_radius
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `CircularInvolute`| | No |
|
||||
| `start_radius` |[`number`](/docs/kcl/types/number)| The radius of the base circle of the involute | No |
|
||||
| `end_radius` |[`number`](/docs/kcl/types/number)| The radius that the involute ends at | No |
|
||||
| `angle` |[`number`](/docs/kcl/types/number)| Angle about which the whole involute is rotated | No |
|
||||
| `reverse` |`boolean`| If true, the path segment starts at the end radius and goes towards the start radius | No |
|
||||
| `from` |`[number, number]`| The from point. | No |
|
||||
| `to` |`[number, number]`| The to point. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
|
||||
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Plane"
|
||||
title: "std::types::Plane"
|
||||
excerpt: "A plane."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Point2d"
|
||||
title: "std::types::Point2d"
|
||||
excerpt: "A point in two dimensional space."
|
||||
layout: manual
|
||||
---
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "std::Point3d"
|
||||
title: "std::types::Point3d"
|
||||
excerpt: "A point in three dimensional space."
|
||||
layout: manual
|
||||
---
|
||||
|
File diff suppressed because one or more lines are too long
@ -30,8 +30,6 @@ A sketch type.
|
||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
|
||||
|
||||
----
|
||||
@ -53,7 +51,6 @@ A face.
|
||||
| `value` |[`string`](/docs/kcl/types/string)| The tag of the face. | No |
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
@ -12,7 +12,7 @@ The syntax for declaring a tag is `$myTag` you would use it in the following
|
||||
way:
|
||||
|
||||
```js
|
||||
startSketchOn('XZ')
|
||||
startSketchOn(XZ)
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||
|> angledLine(
|
||||
@ -46,7 +46,7 @@ However if the code was written like this:
|
||||
|
||||
```js
|
||||
fn rect(origin) {
|
||||
return startSketchOn('XZ')
|
||||
return startSketchOn(XZ)
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||
|> angledLine(
|
||||
@ -75,7 +75,7 @@ For example the following code works.
|
||||
|
||||
```js
|
||||
fn rect(origin) {
|
||||
return startSketchOn('XZ')
|
||||
return startSketchOn(XZ)
|
||||
|> startProfileAt(origin, %)
|
||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||
|> angledLine(
|
||||
|
@ -29,11 +29,11 @@ test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
// Open the user sidebar menu.
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
|
||||
// No space after "User settings" since it's textContent.
|
||||
const text =
|
||||
|
70
e2e/playwright/auth.spec.ts
Normal file
70
e2e/playwright/auth.spec.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
// test file is for testing auth functionality
|
||||
test.describe('Authentication tests', () => {
|
||||
test(
|
||||
`The user can sign out and back in`,
|
||||
{ tag: ['@electron'] },
|
||||
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.projectSection.waitFor()
|
||||
|
||||
// This is only needed as an override to test-utils' setup() for this test
|
||||
await page.addInitScript(() => {
|
||||
localStorage.setItem('TOKEN_PERSIST_KEY', '')
|
||||
})
|
||||
|
||||
await test.step('Click on sign out and expect sign in page', async () => {
|
||||
await toolbar.userSidebarButton.click()
|
||||
await toolbar.signOutButton.click()
|
||||
await expect(signInPage.signInButton).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step("Refresh doesn't log the user back in", async () => {
|
||||
await page.reload()
|
||||
await expect(signInPage.signInButton).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Click on sign in and cancel, click again and expect different code', async () => {
|
||||
await signInPage.signInButton.click()
|
||||
await expect(signInPage.userCode).toBeVisible()
|
||||
const firstUserCode = await signInPage.userCode.textContent()
|
||||
await signInPage.cancelSignInButton.click()
|
||||
await expect(signInPage.signInButton).toBeVisible()
|
||||
|
||||
await signInPage.signInButton.click()
|
||||
await expect(signInPage.userCode).toBeVisible()
|
||||
const secondUserCode = await signInPage.userCode.textContent()
|
||||
expect(secondUserCode).not.toEqual(firstUserCode)
|
||||
await signInPage.cancelSignInButton.click()
|
||||
})
|
||||
|
||||
await test.step('Press back button and remain on home page', async () => {
|
||||
await page.goBack()
|
||||
await expect(homePage.projectSection).not.toBeVisible()
|
||||
await expect(signInPage.signInButton).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Sign in, activate, and expect home page', async () => {
|
||||
await signInPage.signInButton.click()
|
||||
await expect(signInPage.userCode).toBeVisible()
|
||||
const userCode = await signInPage.userCode.textContent()
|
||||
expect(userCode).not.toBeNull()
|
||||
await signInPage.verifyAndConfirmAuth(userCode!)
|
||||
|
||||
// Longer timeout than usual here for the wait on home page
|
||||
await expect(homePage.projectSection).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
await test.step('Click on sign out and expect sign in page', async () => {
|
||||
await toolbar.userSidebarButton.click()
|
||||
await toolbar.signOutButton.click()
|
||||
await expect(signInPage.signInButton).toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
@ -155,7 +155,7 @@ async function doBasicSketch(
|
||||
|> xLine(length = -segLen(seg01))`)
|
||||
}
|
||||
|
||||
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Basic sketch', () => {
|
||||
test('code pane open at start', async ({ page, homePage }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await doBasicSketch(page, homePage, ['code'])
|
||||
|
@ -8,130 +8,126 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
import { getUtils } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe(
|
||||
'Can create sketches on all planes and their back sides',
|
||||
{ tag: ['@skipWin'] },
|
||||
() => {
|
||||
const sketchOnPlaneAndBackSideTest = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
scene: SceneFixture,
|
||||
toolbar: ToolbarFixture,
|
||||
plane: string,
|
||||
clickCoords: { x: number; y: number }
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||
const sketchOnPlaneAndBackSideTest = async (
|
||||
page: Page,
|
||||
homePage: HomePageFixture,
|
||||
scene: SceneFixture,
|
||||
toolbar: ToolbarFixture,
|
||||
plane: string,
|
||||
clickCoords: { x: number; y: number }
|
||||
) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
const XYPlanRed: [number, number, number] = [98, 50, 51]
|
||||
await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
|
||||
await homePage.goToModelingScene()
|
||||
const XYPlanRed: [number, number, number] = [98, 50, 51]
|
||||
await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.openDebugPanel()
|
||||
|
||||
const coord =
|
||||
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: coord, y: coord, z: coord },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
|
||||
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
|
||||
|
||||
await u.openDebugPanel()
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||
await page.waitForTimeout(600) // wait for animation
|
||||
|
||||
await toolbar.waitUntilSketchingReady()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).toBeVisible()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(707, 393)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'line Line', exact: true })
|
||||
.first()
|
||||
.click()
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
const coord =
|
||||
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||
const camCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
vantage: { x: coord, y: coord, z: coord },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
}
|
||||
const updateCamCommand: EngineCommand = {
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
}
|
||||
|
||||
const planeConfigs = [
|
||||
{
|
||||
plane: 'XY',
|
||||
coords: { x: 600, y: 388 },
|
||||
description: 'red plane',
|
||||
},
|
||||
{
|
||||
plane: 'YZ',
|
||||
coords: { x: 700, y: 250 },
|
||||
description: 'green plane',
|
||||
},
|
||||
{
|
||||
plane: 'XZ',
|
||||
coords: { x: 684, y: 427 },
|
||||
description: 'blue plane',
|
||||
},
|
||||
{
|
||||
plane: '-XY',
|
||||
coords: { x: 600, y: 118 },
|
||||
description: 'back of red plane',
|
||||
},
|
||||
{
|
||||
plane: '-YZ',
|
||||
coords: { x: 700, y: 219 },
|
||||
description: 'back of green plane',
|
||||
},
|
||||
{
|
||||
plane: '-XZ',
|
||||
coords: { x: 700, y: 80 },
|
||||
description: 'back of blue plane',
|
||||
},
|
||||
]
|
||||
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
|
||||
|
||||
for (const config of planeConfigs) {
|
||||
test(config.plane, async ({ page, homePage, scene, toolbar }) => {
|
||||
await sketchOnPlaneAndBackSideTest(
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
config.plane,
|
||||
config.coords
|
||||
)
|
||||
})
|
||||
}
|
||||
await u.openDebugPanel()
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
|
||||
await u.sendCustomCmd(camCommand)
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd(updateCamCommand)
|
||||
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.mouse.click(clickCoords.x, clickCoords.y)
|
||||
await page.waitForTimeout(600) // wait for animation
|
||||
|
||||
await toolbar.waitUntilSketchingReady()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).toBeVisible()
|
||||
|
||||
await u.closeDebugPanel()
|
||||
await page.mouse.click(707, 393)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'line Line', exact: true })
|
||||
.first()
|
||||
.click()
|
||||
await u.openAndClearDebugPanel()
|
||||
await page.getByRole('button', { name: 'Exit Sketch' }).click()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
|
||||
await u.clearCommandLogs()
|
||||
await u.removeCurrentCode()
|
||||
}
|
||||
)
|
||||
|
||||
const planeConfigs = [
|
||||
{
|
||||
plane: 'XY',
|
||||
coords: { x: 600, y: 388 },
|
||||
description: 'red plane',
|
||||
},
|
||||
{
|
||||
plane: 'YZ',
|
||||
coords: { x: 700, y: 250 },
|
||||
description: 'green plane',
|
||||
},
|
||||
{
|
||||
plane: 'XZ',
|
||||
coords: { x: 684, y: 427 },
|
||||
description: 'blue plane',
|
||||
},
|
||||
{
|
||||
plane: '-XY',
|
||||
coords: { x: 600, y: 118 },
|
||||
description: 'back of red plane',
|
||||
},
|
||||
{
|
||||
plane: '-YZ',
|
||||
coords: { x: 700, y: 219 },
|
||||
description: 'back of green plane',
|
||||
},
|
||||
{
|
||||
plane: '-XZ',
|
||||
coords: { x: 700, y: 80 },
|
||||
description: 'back of blue plane',
|
||||
},
|
||||
]
|
||||
|
||||
for (const config of planeConfigs) {
|
||||
test(config.plane, async ({ page, homePage, scene, toolbar }) => {
|
||||
await sketchOnPlaneAndBackSideTest(
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
toolbar,
|
||||
config.plane,
|
||||
config.coords
|
||||
)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Code pane and errors', () => {
|
||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||
page,
|
||||
homePage,
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Command bar tests', () => {
|
||||
test('Extrude from command bar selects extrude line after', async ({
|
||||
page,
|
||||
homePage,
|
||||
@ -179,57 +179,57 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||
})
|
||||
|
||||
test(
|
||||
'Command bar keybinding works from code editor and can change a setting',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// FIXME: No KCL code, unable to wait for engine execution
|
||||
await page.waitForTimeout(10000)
|
||||
// FIXME: No KCL code, unable to wait for engine execution
|
||||
await page.waitForTimeout(10000)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
// Put the cursor in the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
// Put the cursor in the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
// Now try the same, but with the keyboard shortcut, check focus
|
||||
await page.keyboard.press('ControlOrMeta+K')
|
||||
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||
await expect(cmdSearchBar).toBeVisible()
|
||||
await expect(cmdSearchBar).toBeFocused()
|
||||
|
||||
// Try typing in the command bar
|
||||
await cmdSearchBar.fill('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('dark')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(
|
||||
page.getByRole('option', { name: 'system' })
|
||||
).toHaveAttribute('data-headlessui-state', 'active')
|
||||
await page.keyboard.press('Enter')
|
||||
// Try typing in the command bar
|
||||
await cmdSearchBar.fill('theme')
|
||||
const themeOption = page.getByRole('option', {
|
||||
name: 'Settings · app · theme',
|
||||
})
|
||||
await expect(themeOption).toBeVisible()
|
||||
await themeOption.click()
|
||||
const themeInput = page.getByPlaceholder('dark')
|
||||
await expect(themeInput).toBeVisible()
|
||||
await expect(themeInput).toBeFocused()
|
||||
// Select dark theme
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await page.keyboard.press('ArrowDown')
|
||||
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||
'data-headlessui-state',
|
||||
'active'
|
||||
)
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" as a user default`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
}
|
||||
)
|
||||
// Check the toast appeared
|
||||
await expect(
|
||||
page.getByText(`Set theme to "system" as a user default`)
|
||||
).toBeVisible()
|
||||
// Check that the theme changed
|
||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||
})
|
||||
|
||||
test('Can extrude from the command bar', async ({
|
||||
page,
|
||||
|
@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||
{ tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
|
||||
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Editor tests', () => {
|
||||
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
@ -989,162 +989,162 @@ sketch001 = startSketchOn(XZ)
|
||||
|> close()`)
|
||||
})
|
||||
|
||||
test(
|
||||
'Can undo a sketch modification with ctrl+z',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ page, homePage, editor }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`@settings(defaultLengthUnit=in)
|
||||
test('Can undo a sketch modification with ctrl+z', async ({
|
||||
page,
|
||||
homePage,
|
||||
editor,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`@settings(defaultLengthUnit=in)
|
||||
sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||
|> close()
|
||||
|> extrude(length = 5)`
|
||||
)
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
await homePage.goToModelingScene()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(100)
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 0, y: -1250, z: 580 },
|
||||
center: { x: 0, y: 0, z: 0 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
const startPX = [1200 / 2, 500 / 2]
|
||||
const startPX = [1200 / 2, 500 / 2]
|
||||
|
||||
const dragPX = 40
|
||||
const dragPX = 40
|
||||
|
||||
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
await page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Edit Sketch' })
|
||||
).toBeVisible()
|
||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||
await page.waitForTimeout(400)
|
||||
let prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
await expect(page.getByTestId('segment-overlay')).toHaveCount(2)
|
||||
|
||||
// drag startProfileAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
// drag startProfileAt handle
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// drag line handle
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
// drag line handle
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.waitForTimeout(100)
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||
await page.waitForTimeout(100)
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||
})
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
prevContent = await page.locator('.cm-content').innerText()
|
||||
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
// we wait so it saves the code
|
||||
await page.waitForTimeout(800)
|
||||
|
||||
// drag tangentialArc handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
// drag tangentialArc handle
|
||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||
await page.dragAndDrop('#stream', '#stream', {
|
||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||
targetPosition: {
|
||||
x: tangentEnd.x + dragPX,
|
||||
y: tangentEnd.y + dragPX,
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||
|
||||
// expect the code to have changed
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
// expect the code to have changed
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line(end = [15.4, -2.78])
|
||||
|> tangentialArc(endAbsolute = [27.6, -3.05])
|
||||
|> close()
|
||||
|> extrude(length = 5)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
// Hit undo
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line(end = [15.4, -2.78])
|
||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||
|> close()
|
||||
|> extrude(length = 5)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([2.71, -2.71], %)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||
|> close()
|
||||
|> extrude(length = 5)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
// Hit undo again.
|
||||
await page.keyboard.down('Control')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('Control')
|
||||
|
||||
await page.waitForTimeout(100)
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
await page.waitForTimeout(100)
|
||||
await editor.expectEditor.toContain(
|
||||
`sketch001 = startSketchOn(XZ)
|
||||
|> startProfileAt([4.61, -10.01], %)
|
||||
|> line(end = [12.73, -0.09])
|
||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||
|> close()
|
||||
|> extrude(length = 5)`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
}
|
||||
)
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
})
|
||||
|
||||
test(
|
||||
`Can import a local OBJ file`,
|
||||
|
@ -430,5 +430,17 @@ profile003 = startProfileAt([0, -4.93], sketch001)
|
||||
await editor.expectEditor.not.toContain('sketch001 =')
|
||||
await editor.expectEditor.not.toContain('profile002 = ')
|
||||
})
|
||||
|
||||
await test.step(`Delete the remaining plane via feature tree`, async () => {
|
||||
const operationButton = await toolbar.getFeatureTreeOperation(
|
||||
'Offset Plane',
|
||||
0
|
||||
)
|
||||
await operationButton.click({ button: 'left' })
|
||||
await page.keyboard.press('Delete')
|
||||
|
||||
// Verify the plane code is gone, and https://github.com/KittyCAD/modeling-app/issues/5988 is fixed.
|
||||
await editor.expectEditor.not.toContain('plane001 =')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -29,7 +29,7 @@ test.describe('integrations tests', () => {
|
||||
)
|
||||
})
|
||||
|
||||
const [clickObj] = await scene.makeMouseHelpers(726, 272)
|
||||
const [clickObj] = scene.makeMouseHelpers(726, 272)
|
||||
|
||||
await test.step('setup test', async () => {
|
||||
await homePage.expectState({
|
||||
@ -73,7 +73,7 @@ test.describe('integrations tests', () => {
|
||||
})
|
||||
await test.step('setup for next assertion', async () => {
|
||||
await toolbar.openFile('main.kcl')
|
||||
await page.waitForTimeout(1000)
|
||||
await page.waitForTimeout(2000)
|
||||
await clickObj()
|
||||
await page.waitForTimeout(1000)
|
||||
await scene.moveNoWhere()
|
||||
|
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
|
||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
|
||||
@ -66,6 +67,7 @@ export interface Fixtures {
|
||||
toolbar: ToolbarFixture
|
||||
scene: SceneFixture
|
||||
homePage: HomePageFixture
|
||||
signInPage: SignInPageFixture
|
||||
}
|
||||
|
||||
export class ElectronZoo {
|
||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
|
||||
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||
await use(new SignInPageFixture(page))
|
||||
},
|
||||
}
|
||||
|
||||
if (process.env.PLATFORM === 'web') {
|
||||
|
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
import { secrets } from '@e2e/playwright/secrets'
|
||||
|
||||
export class SignInPageFixture {
|
||||
public page: Page
|
||||
|
||||
signInButton!: Locator
|
||||
cancelSignInButton!: Locator
|
||||
userCode!: Locator
|
||||
|
||||
apiBaseUrl!: string
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
|
||||
this.signInButton = this.page.getByTestId('sign-in-button')
|
||||
this.cancelSignInButton = this.page.getByTestId('cancel-sign-in-button')
|
||||
this.userCode = this.page.getByTestId('sign-in-user-code')
|
||||
|
||||
// TODO: set this thru env var
|
||||
this.apiBaseUrl = 'https://api.dev.zoo.dev'
|
||||
}
|
||||
|
||||
async verifyAndConfirmAuth(userCode: string) {
|
||||
// Device flow: stolen from the tauri days
|
||||
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
|
||||
const headers = {
|
||||
Authorization: `Bearer ${secrets.token}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const verifyUrl = `${this.apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
|
||||
console.log(`GET ${verifyUrl}`)
|
||||
const vr = await fetch(verifyUrl, { headers })
|
||||
console.log(vr.status)
|
||||
|
||||
// Device flow: confirm
|
||||
const confirmUrl = `${this.apiBaseUrl}/oauth2/device/confirm`
|
||||
const data = JSON.stringify({ user_code: userCode })
|
||||
console.log(`POST ${confirmUrl} ${data}`)
|
||||
const cr = await fetch(confirmUrl, {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
console.log(cr.status)
|
||||
}
|
||||
}
|
@ -46,6 +46,9 @@ export class ToolbarFixture {
|
||||
gizmo!: Locator
|
||||
gizmoDisabled!: Locator
|
||||
loadButton!: Locator
|
||||
/** User button for the user sidebar menu */
|
||||
userSidebarButton!: Locator
|
||||
signOutButton!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
|
||||
// element or two different elements can represent these states.
|
||||
this.gizmo = page.getByTestId('gizmo')
|
||||
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||
|
||||
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
|
||||
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
|
||||
}
|
||||
|
||||
get logoLink() {
|
||||
@ -168,6 +174,13 @@ export class ToolbarFixture {
|
||||
openFile = async (fileName: string) => {
|
||||
await this.filePane.getByText(fileName).click()
|
||||
}
|
||||
selectTangentialArc = async () => {
|
||||
await this.page.getByRole('button', { name: 'caret down arcs:' }).click()
|
||||
await expect(
|
||||
this.page.getByTestId('dropdown-three-point-arc')
|
||||
).toBeVisible()
|
||||
await this.page.getByTestId('dropdown-tangential-arc').click()
|
||||
}
|
||||
selectCenterRectangle = async () => {
|
||||
await this.page
|
||||
.getByRole('button', { name: 'caret down rectangles:' })
|
||||
|
@ -1,6 +1,25 @@
|
||||
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'
|
||||
import type {
|
||||
Reporter,
|
||||
TestCase,
|
||||
TestResult,
|
||||
FullResult,
|
||||
} from '@playwright/test/reporter'
|
||||
|
||||
class MyAPIReporter implements Reporter {
|
||||
private pendingRequests: Promise<void>[] = []
|
||||
private allResults: Record<string, any>[] = []
|
||||
private blockingResults: Record<string, any>[] = []
|
||||
|
||||
async onEnd(result: FullResult): Promise<void> {
|
||||
await Promise.all(this.pendingRequests)
|
||||
if (this.allResults.length > 0 && this.blockingResults.length === 0) {
|
||||
result.status = 'passed'
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Marked failures as non-blocking')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTestEnd(test: TestCase, result: TestResult): void {
|
||||
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
|
||||
return
|
||||
@ -20,8 +39,9 @@ class MyAPIReporter implements Reporter {
|
||||
platform: process.env.RUNNER_OS || process.platform,
|
||||
// Extra test and result data
|
||||
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
|
||||
id: test.id, // computed file/test/project ID used for reruns
|
||||
retry: result.retry,
|
||||
tags: test.tags, // e.g. '@snapshot' or '@skipWin'
|
||||
tags: test.tags, // e.g. '@snapshot' or '@skipLocalEngine'
|
||||
// Extra environment variables
|
||||
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
|
||||
CI_PR_NUMBER: process.env.CI_PR_NUMBER || null,
|
||||
@ -35,7 +55,7 @@ class MyAPIReporter implements Reporter {
|
||||
RUNNER_ARCH: process.env.RUNNER_ARCH || null,
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
const request = (async () => {
|
||||
try {
|
||||
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
|
||||
method: 'POST',
|
||||
@ -46,18 +66,27 @@ class MyAPIReporter implements Reporter {
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
if (!response.ok && !process.env.CI) {
|
||||
console.error(
|
||||
'TAB API - Failed to send test result:',
|
||||
await response.text()
|
||||
)
|
||||
if (response.ok) {
|
||||
const result = await response.json()
|
||||
this.allResults.push(result)
|
||||
if (result.block) {
|
||||
this.blockingResults.push(result)
|
||||
}
|
||||
} else {
|
||||
const error = await response.json()
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Failed to send test result:', error)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Unable to send test result')
|
||||
console.error('TAB API - Unable to send test result:', message)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
this.pendingRequests.push(request)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,18 @@ export const isErrorWhitelisted = (exception: Error) => {
|
||||
'e2e/playwright/can-create-sketches-on-all-planes-and-their-back-sides.spec.ts XY',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'fE',
|
||||
message:
|
||||
'engine: Failed to wait for promise from send modeling command: JsValue("no connection to send on")',
|
||||
stack: `fE: engine: Failed to wait for promise from send modeling command: JsValue("no connection to send on")
|
||||
at ET (file:///home/runner/_work/modeling-app/modeling-app/.vite/renderer/main_window/assets/index-BvtRFNLF.js:49875:12)
|
||||
at WGe.clearSceneAndBustCache (file:///home/runner/_work/modeling-app/modeling-app/.vite/renderer/main_window/assets/index-BvtRFNLF.js:91886:19)
|
||||
at async a.onEngineConnectionOpened (file:///home/runner/_work/modeling-app/modeling-app/.vite/renderer/main_window/assets/index-BvtRFNLF.js:91483:9)`,
|
||||
foundInSpec:
|
||||
'e2e/playwright/testing-constraints.spec.ts is frequent but could be any spec',
|
||||
project: 'Google Chrome',
|
||||
},
|
||||
{
|
||||
name: 'RangeError',
|
||||
message: 'Position 160 is out of range for changeset of length 0',
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
|
||||
{
|
||||
tag: '@electron',
|
||||
},
|
||||
async ({ page, tronApp }) => {
|
||||
async ({ page, tronApp, scene }) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
@ -72,7 +72,6 @@ test.describe('Onboarding tests', () => {
|
||||
onboarding_status: '',
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
@ -80,7 +79,7 @@ test.describe('Onboarding tests', () => {
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await createProject({ name: 'project-link', page })
|
||||
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
|
||||
await u.waitForPageLoad()
|
||||
await scene.connectionEstablished()
|
||||
})
|
||||
})
|
||||
|
||||
@ -107,12 +106,10 @@ test.describe('Onboarding tests', () => {
|
||||
)
|
||||
|
||||
test('Code resets after confirmation', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
tronApp,
|
||||
scene,
|
||||
cmdBar,
|
||||
}) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
@ -276,6 +273,8 @@ test.describe('Onboarding tests', () => {
|
||||
page,
|
||||
homePage,
|
||||
tronApp,
|
||||
editor,
|
||||
toolbar,
|
||||
}) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
@ -289,7 +288,6 @@ test.describe('Onboarding tests', () => {
|
||||
},
|
||||
})
|
||||
|
||||
const u = await getUtils(page)
|
||||
const badCode = `// This is bad code we shouldn't see`
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||
@ -299,18 +297,19 @@ test.describe('Onboarding tests', () => {
|
||||
.poll(() => page.url())
|
||||
.toContain(onboardingPaths.PARAMETRIC_MODELING)
|
||||
|
||||
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
||||
|
||||
// Check the code got reset on load
|
||||
await expect(page.locator('#code-pane')).toBeVisible()
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
||||
await toolbar.openPane('code')
|
||||
await editor.expectEditor.toContain(bracket, {
|
||||
shouldNormalise: true,
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
// Mess with the code again
|
||||
await u.codeLocator.selectText()
|
||||
await u.codeLocator.fill(badCode)
|
||||
await expect(u.codeLocator).toHaveText(badCode)
|
||||
await editor.replaceCode('', badCode)
|
||||
await editor.expectEditor.toContain(badCode, {
|
||||
shouldNormalise: true,
|
||||
timeout: 10_000,
|
||||
})
|
||||
|
||||
// Click to the next step
|
||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||
@ -320,7 +319,10 @@ test.describe('Onboarding tests', () => {
|
||||
})
|
||||
|
||||
// Check that the code has been reset
|
||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines)
|
||||
await editor.expectEditor.toContain(bracket, {
|
||||
shouldNormalise: true,
|
||||
timeout: 10_000,
|
||||
})
|
||||
})
|
||||
|
||||
// (lee) The two avatar tests are weird because even on main, we don't have
|
||||
@ -329,6 +331,7 @@ test.describe('Onboarding tests', () => {
|
||||
test('Avatar text updates depending on image load success', async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
homePage,
|
||||
tronApp,
|
||||
}) => {
|
||||
@ -360,7 +363,7 @@ test.describe('Onboarding tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img')
|
||||
const avatarLocator = toolbar.userSidebarButton.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
@ -402,6 +405,7 @@ test.describe('Onboarding tests', () => {
|
||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
homePage,
|
||||
tronApp,
|
||||
}) => {
|
||||
@ -433,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||
const sidebar = toolbar.userSidebarButton
|
||||
const avatar = sidebar.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
@ -462,6 +466,7 @@ test.describe('Onboarding tests', () => {
|
||||
test('Restarting onboarding on desktop takes one attempt', async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
tronApp,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
@ -500,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
|
||||
.filter({ hasText: 'Tutorial Project 00' })
|
||||
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
||||
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
const userMenuButton = page.getByTestId('user-sidebar-toggle')
|
||||
const userMenuButton = toolbar.userSidebarButton
|
||||
const userMenuSettingsButton = page.getByRole('button', {
|
||||
name: 'User settings',
|
||||
})
|
||||
|
@ -6,6 +6,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
import {
|
||||
executorInputPath,
|
||||
getUtils,
|
||||
kclSamplesPath,
|
||||
testsInputPath,
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
@ -143,28 +144,45 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await scene.settled(cmdBar)
|
||||
})
|
||||
|
||||
await test.step('Insert a second time and expect error', async () => {
|
||||
// TODO: revisit once we have clone with #6209
|
||||
await insertPartIntoAssembly(
|
||||
'bracket.kcl',
|
||||
'bracket',
|
||||
toolbar,
|
||||
cmdBar,
|
||||
page
|
||||
)
|
||||
await test.step('Insert a second time with the same name and expect error', async () => {
|
||||
await toolbar.insertButton.click()
|
||||
await cmdBar.selectOption({ name: 'bracket.kcl' }).click()
|
||||
await cmdBar.expectState({
|
||||
stage: 'arguments',
|
||||
currentArgKey: 'localName',
|
||||
currentArgValue: '',
|
||||
headerArguments: { Path: 'bracket.kcl', LocalName: '' },
|
||||
highlightedHeaderArg: 'localName',
|
||||
commandName: 'Insert',
|
||||
})
|
||||
await page.keyboard.insertText('bracket')
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(
|
||||
page.getByText('This variable name is already in use')
|
||||
).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('Insert a second time with a different name and expect error', async () => {
|
||||
await page.keyboard.insertText('2')
|
||||
await cmdBar.progressCmdBar()
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: { Path: 'bracket.kcl', LocalName: 'bracket2' },
|
||||
commandName: 'Insert',
|
||||
})
|
||||
await cmdBar.progressCmdBar()
|
||||
await editor.expectEditor.toContain(
|
||||
`
|
||||
import "cylinder.kcl" as cylinder
|
||||
import "bracket.kcl" as bracket
|
||||
import "bracket.kcl" as bracket
|
||||
import "bracket.kcl" as bracket2
|
||||
cylinder
|
||||
bracket
|
||||
bracket
|
||||
bracket2
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
await scene.settled(cmdBar)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
// TODO: update once we have clone() with #6209
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -261,7 +279,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
highlightedHeaderArg: 'x',
|
||||
commandName: 'Translate',
|
||||
})
|
||||
await page.keyboard.insertText('5')
|
||||
await page.keyboard.insertText('100')
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('0.1')
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -270,7 +288,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
X: '5',
|
||||
X: '100',
|
||||
Y: '0.1',
|
||||
Z: '0.2',
|
||||
},
|
||||
@ -282,7 +300,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`
|
||||
bracket
|
||||
|> translate(x = 5, y = 0.1, z = 0.2)
|
||||
|> translate(x = 100, y = 0.1, z = 0.2)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
@ -331,7 +349,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`
|
||||
bracket
|
||||
|> translate(x = 5, y = 0.1, z = 0.2)
|
||||
|> translate(x = 100, y = 0.1, z = 0.2)
|
||||
|> rotate(roll = 0.1, pitch = 0.2, yaw = 0.3)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
@ -455,4 +473,94 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test(
|
||||
'Assembly gets reexecuted when imported models are updated externally',
|
||||
{ tag: ['@electron'] },
|
||||
async ({ context, page, homePage, scene, toolbar, cmdBar, tronApp }) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
|
||||
const midPoint = { x: 500, y: 250 }
|
||||
const washerPoint = { x: 645, y: 250 }
|
||||
const partColor: [number, number, number] = [120, 120, 120]
|
||||
const redPartColor: [number, number, number] = [200, 0, 0]
|
||||
const bgColor: [number, number, number] = [30, 30, 30]
|
||||
const tolerance = 50
|
||||
const projectName = 'assembly'
|
||||
|
||||
await test.step('Setup parts and expect imported model', async () => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const projectDir = path.join(dir, projectName)
|
||||
await fsp.mkdir(projectDir, { recursive: true })
|
||||
await Promise.all([
|
||||
fsp.copyFile(
|
||||
executorInputPath('cube.kcl'),
|
||||
path.join(projectDir, 'cube.kcl')
|
||||
),
|
||||
fsp.copyFile(
|
||||
kclSamplesPath(
|
||||
path.join(
|
||||
'pipe-flange-assembly',
|
||||
'mcmaster-parts',
|
||||
'98017a257-washer.step'
|
||||
)
|
||||
),
|
||||
path.join(projectDir, 'foreign.step')
|
||||
),
|
||||
fsp.writeFile(
|
||||
path.join(projectDir, 'main.kcl'),
|
||||
`
|
||||
import "cube.kcl" as cube
|
||||
import "foreign.step" as foreign
|
||||
cube
|
||||
foreign
|
||||
|> translate(x = 40, z = 10)`
|
||||
),
|
||||
])
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.openProject(projectName)
|
||||
await scene.settled(cmdBar)
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor(partColor, midPoint, tolerance)
|
||||
})
|
||||
|
||||
await test.step('Change imported kcl file and expect change', async () => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
// Append appearance to the cube.kcl file
|
||||
await fsp.appendFile(
|
||||
path.join(dir, projectName, 'cube.kcl'),
|
||||
`\n |> appearance(color = "#ff0000")`
|
||||
)
|
||||
})
|
||||
await scene.settled(cmdBar)
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor(redPartColor, midPoint, tolerance)
|
||||
await scene.expectPixelColor(partColor, washerPoint, tolerance)
|
||||
})
|
||||
|
||||
await test.step('Change imported step file and expect change', async () => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
// Replace the washer with a pipe
|
||||
await fsp.copyFile(
|
||||
kclSamplesPath(
|
||||
path.join(
|
||||
'pipe-flange-assembly',
|
||||
'mcmaster-parts',
|
||||
'1120t74-pipe.step'
|
||||
)
|
||||
),
|
||||
path.join(dir, projectName, 'foreign.step')
|
||||
)
|
||||
})
|
||||
await scene.settled(cmdBar)
|
||||
await toolbar.closePane('code')
|
||||
// Expect pipe to take over the red cube but leave some space where the washer was
|
||||
await scene.expectPixelColor(partColor, midPoint, tolerance)
|
||||
await scene.expectPixelColor(bgColor, washerPoint, tolerance)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -18,7 +18,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test(
|
||||
'projects reload if a new one is created, deleted, or renamed externally',
|
||||
{ tag: '@electron' },
|
||||
{ tag: ['@electron', '@macos', '@windows'] },
|
||||
async ({ context, page }, testInfo) => {
|
||||
let externalCreatedProjectName = 'external-created-project'
|
||||
|
||||
@ -400,11 +400,6 @@ test(
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
await page.getByText('broken-code').click()
|
||||
|
||||
// Gotcha: You can not use scene.settled() since the KCL code is going to fail
|
||||
await expect(
|
||||
page.getByTestId('model-state-indicator-playing')
|
||||
).toBeAttached()
|
||||
|
||||
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
|
||||
await editor.scrollToText(
|
||||
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
|
||||
@ -779,7 +774,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'rename project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `untitled`
|
||||
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
@ -839,7 +836,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'delete project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
@ -891,7 +890,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'rename project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `untitled`
|
||||
const commandContinueButton = page.getByRole('button', {
|
||||
@ -947,7 +948,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'delete project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
@ -1812,8 +1815,8 @@ test(
|
||||
'basic_fillet_cube_next_adjacent.kcl',
|
||||
'basic_fillet_cube_previous_adjacent.kcl',
|
||||
'basic_fillet_cube_start.kcl',
|
||||
'big_number_angle_to_match_length_x.kcl',
|
||||
'big_number_angle_to_match_length_y.kcl',
|
||||
'broken-code-test.kcl',
|
||||
'circular_pattern3d_a_pattern.kcl',
|
||||
'close_arc.kcl',
|
||||
'computed_var.kcl',
|
||||
'cube-embedded.gltf',
|
||||
@ -1962,13 +1965,13 @@ test(
|
||||
test(
|
||||
'Settings persist across restarts',
|
||||
{ tag: '@electron' },
|
||||
async ({ page, scene, cmdBar }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
await test.step('We can change a user setting like theme', async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
|
||||
await page.getByTestId('user-settings').click()
|
||||
|
||||
@ -1995,7 +1998,7 @@ test(
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
@ -2007,7 +2010,7 @@ test(
|
||||
})
|
||||
|
||||
await test.step('Should go through onboarding', async () => {
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
await page.getByTestId('user-settings').click()
|
||||
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
@ -30,7 +29,7 @@ sketch003 = startSketchOn(XY)
|
||||
extrude003 = extrude(sketch003, length = 20)
|
||||
`
|
||||
|
||||
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
test.describe('Prompt-to-edit tests', () => {
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
@ -51,7 +50,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
@ -200,7 +198,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const body1CapCoords = { x: 571, y: 311 }
|
||||
|
||||
await context.addInitScript((file) => {
|
||||
@ -260,7 +257,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const body1CapCoords = { x: 571, y: 311 }
|
||||
const body2WallCoords = { x: 620, y: 152 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
} from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
test.describe('Regression tests', { tag: ['@skipWin'] }, () => {
|
||||
test.describe('Regression tests', () => {
|
||||
// bugs we found that don't fit neatly into other categories
|
||||
test('bad model has inline error #3251', async ({
|
||||
context,
|
||||
@ -239,17 +239,18 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
await expect(zooLogo).not.toHaveAttribute('href')
|
||||
})
|
||||
|
||||
test(
|
||||
'Position _ Is Out Of Range... regression test',
|
||||
{ tag: ['@skipWin'] },
|
||||
async ({ context, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`exampleSketch = startSketchOn("XZ")
|
||||
test('Position _ Is Out Of Range... regression test', async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(angle = 50, length = 45 )
|
||||
|> yLine(endAbsolute = 0)
|
||||
@ -258,55 +259,55 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|
||||
example = extrude(exampleSketch, length = 5)
|
||||
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
await expect(async () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
// expect it still to be there (sometimes it just clears for a bit?)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
|
||||
await expect(async () => {
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
await page.waitForTimeout(200)
|
||||
// expect it still to be there (sometimes it just clears for a bit?)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
||||
timeout: 1_000,
|
||||
})
|
||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||
// Okay execution finished, let's start editing text below the error.
|
||||
await u.codeLocator.click()
|
||||
// Go to the end of the editor
|
||||
// This bug happens when there is a diagnostic in the editor and you try to
|
||||
// edit text below it.
|
||||
// Or delete a huge chunk of text and then try to edit below it.
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// error text on hover
|
||||
await page.hover('.cm-lint-marker-error')
|
||||
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
|
||||
// Okay execution finished, let's start editing text below the error.
|
||||
await u.codeLocator.click()
|
||||
// Go to the end of the editor
|
||||
// This bug happens when there is a diagnostic in the editor and you try to
|
||||
// edit text below it.
|
||||
// Or delete a huge chunk of text and then try to edit below it.
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.down('Shift')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.up('Shift')
|
||||
await page.keyboard.press('Backspace')
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.type('thing: "blah"', { delay: 100 })
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
|
||||
await expect(
|
||||
page.locator('.cm-content')
|
||||
).toContainText(`exampleSketch = startSketchOn("XZ")
|
||||
await expect(
|
||||
page.locator('.cm-content')
|
||||
).toContainText(`exampleSketch = startSketchOn("XZ")
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(angle = 50, length = 45 )
|
||||
|> yLine(endAbsolute = 0)
|
||||
@ -314,9 +315,8 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
)
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
})
|
||||
|
||||
test(
|
||||
'window resize updates should reconfigure the stream',
|
||||
@ -486,82 +486,81 @@ extrude002 = extrude(profile002, length = 150)
|
||||
}
|
||||
)
|
||||
// We updated this test such that you can have multiple exports going at once.
|
||||
test(
|
||||
'ensure you CAN export while an export is already going',
|
||||
{ tag: ['@skipLinux', '@skipWin'] },
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step('Set up the code and durations', async () => {
|
||||
await page.addInitScript(
|
||||
async ({ code }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
},
|
||||
{
|
||||
code: bracket,
|
||||
}
|
||||
)
|
||||
test('ensure you CAN export while an export is already going', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await test.step('Set up the code and durations', async () => {
|
||||
await page.addInitScript(
|
||||
async ({ code }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
},
|
||||
{
|
||||
code: bracket,
|
||||
}
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
})
|
||||
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
|
||||
await test.step('second export', async () => {
|
||||
await clickExportButton(page)
|
||||
await test.step('second export', async () => {
|
||||
await clickExportButton(page)
|
||||
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
await clickExportButton(page)
|
||||
await clickExportButton(page)
|
||||
|
||||
await test.step('The first export still succeeds', async () => {
|
||||
await Promise.all([
|
||||
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
||||
expect(errorToastMessage).not.toBeVisible(),
|
||||
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||
expect(successToastMessage).toBeVisible({ timeout: 15_000 }),
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible({
|
||||
timeout: 15_000,
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Successful, unblocked export', async () => {
|
||||
// Try exporting again.
|
||||
await clickExportButton(page)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await test.step('The first export still succeeds', async () => {
|
||||
await Promise.all([
|
||||
expect(exportingToastMessage).not.toBeVisible(),
|
||||
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
||||
expect(errorToastMessage).not.toBeVisible(),
|
||||
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||
expect(successToastMessage).toBeVisible({ timeout: 15_000 }),
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible({
|
||||
timeout: 15_000,
|
||||
}),
|
||||
])
|
||||
|
||||
await expect(successToastMessage).toHaveCount(2)
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('Successful, unblocked export', async () => {
|
||||
// Try exporting again.
|
||||
await clickExportButton(page)
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await Promise.all([
|
||||
expect(exportingToastMessage).not.toBeVisible(),
|
||||
expect(errorToastMessage).not.toBeVisible(),
|
||||
expect(engineErrorToastMessage).not.toBeVisible(),
|
||||
expect(alreadyExportingToastMessage).not.toBeVisible(),
|
||||
])
|
||||
|
||||
await expect(successToastMessage).toHaveCount(2)
|
||||
})
|
||||
})
|
||||
|
||||
test(
|
||||
`Network health indicator only appears in modeling view`,
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -47,9 +47,8 @@ test.setTimeout(60_000)
|
||||
// up with another PR if we want this back.
|
||||
test(
|
||||
'exports of each format should work',
|
||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
||||
{ tag: ['@snapshot'] },
|
||||
async ({ page, context, scene, cmdBar, tronApp }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
@ -377,8 +376,6 @@ test.describe(
|
||||
'extrude on default planes should be stable',
|
||||
{ tag: '@snapshot' },
|
||||
() => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
test('XY', async ({ page, context, cmdBar, scene }) => {
|
||||
await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
|
||||
})
|
||||
@ -409,7 +406,6 @@ test(
|
||||
'Draft segments should look right',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, scene, toolbar }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -468,9 +464,7 @@ test(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
|
||||
// click on the end of the profile to continue it
|
||||
await page.waitForTimeout(500)
|
||||
@ -534,8 +528,6 @@ test(
|
||||
'Draft rectangles should look right',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -581,7 +573,6 @@ test(
|
||||
'Draft circle should look right',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -628,9 +619,7 @@ test.describe(
|
||||
'Client side scene scale should match engine scale',
|
||||
{ tag: '@snapshot' },
|
||||
() => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
test('Inch scale', async ({ page, cmdBar, scene }) => {
|
||||
test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setViewportSize({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
@ -664,9 +653,7 @@ test.describe(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
@ -680,9 +667,8 @@ test.describe(
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
// click tangential arc tool again to unequip it
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
// it will be available directly in the toolbar since it was last equipped
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
@ -705,7 +691,13 @@ test.describe(
|
||||
})
|
||||
})
|
||||
|
||||
test('Millimeter scale', async ({ page, context, cmdBar, scene }) => {
|
||||
test('Millimeter scale', async ({
|
||||
page,
|
||||
context,
|
||||
cmdBar,
|
||||
scene,
|
||||
toolbar,
|
||||
}) => {
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
@ -758,9 +750,7 @@ test.describe(
|
||||
|> xLine(length = 184.3)`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.selectTangentialArc()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// click to continue profile
|
||||
@ -773,9 +763,7 @@ test.describe(
|
||||
|> tangentialArc(endAbsolute = [551.2, -62.01])`
|
||||
await expect(u.codeLocator).toHaveText(code)
|
||||
|
||||
await page
|
||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
||||
.click()
|
||||
await toolbar.tangentialArcBtn.click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// screen shot should show the sketch
|
||||
@ -805,8 +793,6 @@ test(
|
||||
'Sketch on face with none z-up',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||
localStorage.setItem(
|
||||
@ -865,8 +851,6 @@ test(
|
||||
'Zoom to fit on load - solid 2d',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -903,8 +887,6 @@ test(
|
||||
'Zoom to fit on load - solid 3d',
|
||||
{ tag: '@snapshot' },
|
||||
async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -944,7 +926,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
cmdBar,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
const stream = page.getByTestId('stream')
|
||||
|
||||
@ -1004,7 +985,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
})
|
||||
|
||||
test('Grid turned off', async ({ page, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
const stream = page.getByTestId('stream')
|
||||
|
||||
@ -1026,7 +1006,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
||||
})
|
||||
|
||||
test('Grid turned on', async ({ page, context, cmdBar, scene }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await context.addInitScript(
|
||||
async ({ settingsKey, settings }) => {
|
||||
localStorage.setItem(settingsKey, settings)
|
||||
@ -1136,7 +1115,6 @@ test('theme persists', async ({ page, context }) => {
|
||||
|
||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||
test('code color goober', async ({ page, context, scene, cmdBar }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
await context.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -1154,11 +1132,7 @@ sweepPath = startSketchOn(XZ)
|
||||
|
||||
sweepSketch = startSketchOn(XY)
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|
||||
|> sweep(path = sweepPath)
|
||||
|> appearance(
|
||||
color = "#bb00ff",
|
||||
@ -1203,11 +1177,7 @@ sweepPath = startSketchOn(XZ)
|
||||
|
||||
sweepSketch = startSketchOn(XY)
|
||||
|> startProfileAt([2, 0], %)
|
||||
|> arc({
|
||||
angleEnd = 360,
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|
||||
|> sweep(path = sweepPath)
|
||||
|> appearance(
|
||||
color = "#bb00ff",
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
Binary file not shown.
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Binary file not shown.
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 58 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user