Compare commits
56 Commits
jtran/json
...
nightly-v2
Author | SHA1 | Date | |
---|---|---|---|
5c0ca52291 | |||
bff13f6bfe | |||
6001b71f06 | |||
bd1e68a4c8 | |||
8589f8fc5f | |||
6641e1178b | |||
6e0f1e71b2 | |||
0e945b6457 | |||
19155a9132 | |||
305d613d40 | |||
95f2caacab | |||
8f61ee1d2f | |||
b02dbd4fe6 | |||
668f6671a9 | |||
f6387eb7e9 | |||
83a87b046f | |||
20c2ce3bac | |||
2956f9ed55 | |||
510d74f2c7 | |||
457ab28f74 | |||
1502f923ee | |||
f03a684eec | |||
45e17c50e7 | |||
6bf74379a7 | |||
01c6fd53fa | |||
f8306c0275 | |||
900ef9e18d | |||
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:jsx-a11y/recommended",
|
||||||
"plugin:react-hooks/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
@ -61,6 +66,7 @@
|
|||||||
"jsx-a11y/click-events-have-key-events": "off",
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
"jsx-a11y/no-autofocus": "off",
|
"jsx-a11y/no-autofocus": "off",
|
||||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||||
|
"react/no-unknown-property": "error",
|
||||||
"no-restricted-globals": [
|
"no-restricted-globals": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
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
|
# Retry failed tests, doing our own retries because using inbuilt Playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retries ]]; do
|
while [[ $retry -le $max_retries ]]; do
|
||||||
if [[ -f "test-results/.last-run.json" ]]; then
|
if [[ -f "test-results/.last-run.json" ]]; then
|
||||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
status=$(jq -r '.status' test-results/.last-run.json)
|
||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ "$status" == "failed" ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
if [[ "$3" == *ubuntu* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
@ -56,10 +56,11 @@ done
|
|||||||
echo "retried=false" >>$GITHUB_OUTPUT
|
echo "retried=false" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
if [[ -f "test-results/.last-run.json" ]]; then
|
if [[ -f "test-results/.last-run.json" ]]; then
|
||||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
status=$(jq -r '.status' test-results/.last-run.json)
|
||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ "$status" == "failed" ]]; then
|
||||||
# If it still fails after 3 retries, then fail the job
|
# If it still fails after retries, then fail the job
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
4
.github/workflows/e2e-tests.yml
vendored
@ -285,7 +285,7 @@ jobs:
|
|||||||
# TODO: enable namespace-profile-windows-latest once available
|
# TODO: enable namespace-profile-windows-latest once available
|
||||||
os:
|
os:
|
||||||
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
|
- "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
|
- windows-latest-8-cores
|
||||||
shardIndex: [1, 2, 3, 4]
|
shardIndex: [1, 2, 3, 4]
|
||||||
shardTotal: [4]
|
shardTotal: [4]
|
||||||
@ -295,7 +295,7 @@ jobs:
|
|||||||
isScheduled:
|
isScheduled:
|
||||||
- ${{ github.event_name == 'schedule' }}
|
- ${{ github.event_name == 'schedule' }}
|
||||||
exclude:
|
exclude:
|
||||||
- os: namespace-profile-macos-6-cores
|
- os: namespace-profile-macos-8-cores
|
||||||
isScheduled: true
|
isScheduled: true
|
||||||
- os: windows-latest-8-cores
|
- os: windows-latest-8-cores
|
||||||
isScheduled: true
|
isScheduled: true
|
||||||
|
@ -5,6 +5,6 @@ command = "vscode-eslint-language-server"
|
|||||||
[[language]]
|
[[language]]
|
||||||
name = "typescript"
|
name = "typescript"
|
||||||
auto-format = true
|
auto-format = true
|
||||||
formatter = { command = "node_modules/.bin/prettier", args = ["--parser", "typescript"] }
|
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" ]
|
# language-servers = [ { name = "eslint", only-features = [ "diagnostics" ] }, "typescript-language-server" ]
|
||||||
|
|
||||||
|
40
docs/kcl/assertIs.md
Normal file
@ -10,8 +10,10 @@ Draw a smooth, continuous, curved line segment from the current origin to the de
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
bezierCurve(
|
bezierCurve(
|
||||||
data: BezierData,
|
|
||||||
sketch: Sketch,
|
sketch: Sketch,
|
||||||
|
control1: [number],
|
||||||
|
control2: [number],
|
||||||
|
end: [number],
|
||||||
tag?: TagDeclarator,
|
tag?: TagDeclarator,
|
||||||
): Sketch
|
): Sketch
|
||||||
```
|
```
|
||||||
@ -21,9 +23,11 @@ bezierCurve(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`BezierData`](/docs/kcl/types/BezierData) | Data to draw a bezier curve. | Yes |
|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes |
|
||||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
| `control1` | [`[number]`](/docs/kcl/types/number) | First control point for the cubic | Yes |
|
||||||
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
| `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
|
### Returns
|
||||||
|
|
||||||
@ -36,11 +40,7 @@ bezierCurve(
|
|||||||
exampleSketch = startSketchOn(XZ)
|
exampleSketch = startSketchOn(XZ)
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line(end = [0, 10])
|
|> line(end = [0, 10])
|
||||||
|> bezierCurve({
|
|> bezierCurve(control1 = [5, 0], control2 = [5, 10], end = [10, 10])
|
||||||
to = [10, 10],
|
|
||||||
control1 = [5, 0],
|
|
||||||
control2 = [5, 10]
|
|
||||||
}, %)
|
|
||||||
|> line(endAbsolute = [10, 0])
|
|> line(endAbsolute = [10, 0])
|
||||||
|> close()
|
|> close()
|
||||||
|
|
||||||
|
258
docs/kcl/clone.md
Normal file
@ -42,18 +42,10 @@ extrude(
|
|||||||
example = startSketchOn(XZ)
|
example = startSketchOn(XZ)
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> line(end = [10, 0])
|
|> line(end = [10, 0])
|
||||||
|> arc({
|
|> arc(angleStart = 120, angleEnd = 0, radius = 5)
|
||||||
angleStart = 120,
|
|
||||||
angleEnd = 0,
|
|
||||||
radius = 5
|
|
||||||
}, %)
|
|
||||||
|> line(end = [5, 0])
|
|> line(end = [5, 0])
|
||||||
|> line(end = [0, 10])
|
|> line(end = [0, 10])
|
||||||
|> bezierCurve({
|
|> bezierCurve(control1 = [-10, 0], control2 = [2, 10], end = [-5, 10])
|
||||||
control1 = [-10, 0],
|
|
||||||
control2 = [2, 10],
|
|
||||||
to = [-5, 10]
|
|
||||||
}, %)
|
|
||||||
|> line(end = [-5, -2])
|
|> line(end = [-5, -2])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 10)
|
|> extrude(length = 10)
|
||||||
@ -64,18 +56,10 @@ example = startSketchOn(XZ)
|
|||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn(XZ)
|
exampleSketch = startSketchOn(XZ)
|
||||||
|> startProfileAt([-10, 0], %)
|
|> startProfileAt([-10, 0], %)
|
||||||
|> arc({
|
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||||
angleStart = 120,
|
|
||||||
angleEnd = -60,
|
|
||||||
radius = 5
|
|
||||||
}, %)
|
|
||||||
|> line(end = [10, 0])
|
|> line(end = [10, 0])
|
||||||
|> line(end = [5, 0])
|
|> line(end = [5, 0])
|
||||||
|> bezierCurve({
|
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||||
control1 = [-3, 0],
|
|
||||||
control2 = [2, 10],
|
|
||||||
to = [-5, 10]
|
|
||||||
}, %)
|
|
||||||
|> line(end = [-4, 10])
|
|> line(end = [-4, 10])
|
||||||
|> line(end = [-5, -2])
|
|> line(end = [-5, -2])
|
||||||
|> close()
|
|> close()
|
||||||
@ -88,18 +72,10 @@ example = extrude(exampleSketch, length = 10)
|
|||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn(XZ)
|
exampleSketch = startSketchOn(XZ)
|
||||||
|> startProfileAt([-10, 0], %)
|
|> startProfileAt([-10, 0], %)
|
||||||
|> arc({
|
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||||
angleStart = 120,
|
|
||||||
angleEnd = -60,
|
|
||||||
radius = 5
|
|
||||||
}, %)
|
|
||||||
|> line(end = [10, 0])
|
|> line(end = [10, 0])
|
||||||
|> line(end = [5, 0])
|
|> line(end = [5, 0])
|
||||||
|> bezierCurve({
|
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||||
control1 = [-3, 0],
|
|
||||||
control2 = [2, 10],
|
|
||||||
to = [-5, 10]
|
|
||||||
}, %)
|
|
||||||
|> line(end = [-4, 10])
|
|> line(end = [-4, 10])
|
||||||
|> line(end = [-5, -2])
|
|> line(end = [-5, -2])
|
||||||
|> close()
|
|> close()
|
||||||
@ -112,18 +88,10 @@ example = extrude(exampleSketch, length = 20, symmetric = true)
|
|||||||
```js
|
```js
|
||||||
exampleSketch = startSketchOn(XZ)
|
exampleSketch = startSketchOn(XZ)
|
||||||
|> startProfileAt([-10, 0], %)
|
|> startProfileAt([-10, 0], %)
|
||||||
|> arc({
|
|> arc(angleStart = 120, angleEnd = -60, radius = 5)
|
||||||
angleStart = 120,
|
|
||||||
angleEnd = -60,
|
|
||||||
radius = 5
|
|
||||||
}, %)
|
|
||||||
|> line(end = [10, 0])
|
|> line(end = [10, 0])
|
||||||
|> line(end = [5, 0])
|
|> line(end = [5, 0])
|
||||||
|> bezierCurve({
|
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10])
|
||||||
control1 = [-3, 0],
|
|
||||||
control2 = [2, 10],
|
|
||||||
to = [-5, 10]
|
|
||||||
}, %)
|
|
||||||
|> line(end = [-4, 10])
|
|> line(end = [-4, 10])
|
||||||
|> line(end = [-5, -2])
|
|> line(end = [-5, -2])
|
||||||
|> close()
|
|> close()
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from centimeters to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from centimeters to the current default unit.
|
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.
|
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`.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from feet to the current default unit.
|
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.
|
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`.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from inches to the current default unit.
|
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.
|
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`.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from meters to the current default unit.
|
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.
|
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`.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from mm to the current default unit.
|
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.
|
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`.
|
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
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from yards to the current default unit.
|
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.
|
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`.
|
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
|
```js
|
||||||
getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
getNextAdjacentEdge(edge: TagIdentifier): Uuid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ getNextAdjacentEdge(tag: TagIdentifier): Uuid
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
getOppositeEdge(tag: TagIdentifier): Uuid
|
getOppositeEdge(edge: TagIdentifier): Uuid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ getOppositeEdge(tag: TagIdentifier): Uuid
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -22,18 +22,8 @@ layout: manual
|
|||||||
* [`string`](kcl/types/string)
|
* [`string`](kcl/types/string)
|
||||||
* [`tag`](kcl/types/tag)
|
* [`tag`](kcl/types/tag)
|
||||||
* **std**
|
* **std**
|
||||||
* [`Axis2d`](kcl/types/Axis2d)
|
|
||||||
* [`Axis3d`](kcl/types/Axis3d)
|
|
||||||
* [`END`](kcl/consts/std-END)
|
* [`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)
|
* [`START`](kcl/consts/std-START)
|
||||||
* [`Sketch`](kcl/types/Sketch)
|
|
||||||
* [`Solid`](kcl/types/Solid)
|
|
||||||
* [`X`](kcl/consts/std-X)
|
* [`X`](kcl/consts/std-X)
|
||||||
* [`XY`](kcl/consts/std-XY)
|
* [`XY`](kcl/consts/std-XY)
|
||||||
* [`XZ`](kcl/consts/std-XZ)
|
* [`XZ`](kcl/consts/std-XZ)
|
||||||
@ -42,36 +32,24 @@ layout: manual
|
|||||||
* [`Z`](kcl/consts/std-Z)
|
* [`Z`](kcl/consts/std-Z)
|
||||||
* [`abs`](kcl/abs)
|
* [`abs`](kcl/abs)
|
||||||
* [`acos`](kcl/acos)
|
* [`acos`](kcl/acos)
|
||||||
* [`angleToMatchLengthX`](kcl/angleToMatchLengthX)
|
|
||||||
* [`angleToMatchLengthY`](kcl/angleToMatchLengthY)
|
|
||||||
* [`angledLine`](kcl/angledLine)
|
* [`angledLine`](kcl/angledLine)
|
||||||
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
* [`angledLineThatIntersects`](kcl/angledLineThatIntersects)
|
||||||
* [`appearance`](kcl/appearance)
|
* [`appearance`](kcl/appearance)
|
||||||
* [`arc`](kcl/arc)
|
* [`arc`](kcl/arc)
|
||||||
* [`arcTo`](kcl/arcTo)
|
|
||||||
* [`asin`](kcl/asin)
|
* [`asin`](kcl/asin)
|
||||||
* [`assert`](kcl/assert)
|
* [`assert`](kcl/assert)
|
||||||
* [`assertEqual`](kcl/assertEqual)
|
* [`assertIs`](kcl/assertIs)
|
||||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
|
||||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
|
||||||
* [`assertLessThan`](kcl/assertLessThan)
|
|
||||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
|
||||||
* [`atan`](kcl/atan)
|
* [`atan`](kcl/atan)
|
||||||
* [`atan2`](kcl/atan2)
|
* [`atan2`](kcl/atan2)
|
||||||
* [`bezierCurve`](kcl/bezierCurve)
|
* [`bezierCurve`](kcl/bezierCurve)
|
||||||
* [`ceil`](kcl/ceil)
|
* [`ceil`](kcl/ceil)
|
||||||
* [`chamfer`](kcl/chamfer)
|
* [`chamfer`](kcl/chamfer)
|
||||||
* [`circleThreePoint`](kcl/circleThreePoint)
|
* [`circleThreePoint`](kcl/circleThreePoint)
|
||||||
|
* [`clone`](kcl/clone)
|
||||||
* [`close`](kcl/close)
|
* [`close`](kcl/close)
|
||||||
* [`extrude`](kcl/extrude)
|
* [`extrude`](kcl/extrude)
|
||||||
* [`fillet`](kcl/fillet)
|
* [`fillet`](kcl/fillet)
|
||||||
* [`floor`](kcl/floor)
|
* [`floor`](kcl/floor)
|
||||||
* [`fromCm`](kcl/fromCm)
|
|
||||||
* [`fromFt`](kcl/fromFt)
|
|
||||||
* [`fromInches`](kcl/fromInches)
|
|
||||||
* [`fromM`](kcl/fromM)
|
|
||||||
* [`fromMm`](kcl/fromMm)
|
|
||||||
* [`fromYd`](kcl/fromYd)
|
|
||||||
* [`getCommonEdge`](kcl/getCommonEdge)
|
* [`getCommonEdge`](kcl/getCommonEdge)
|
||||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||||
@ -95,7 +73,7 @@ layout: manual
|
|||||||
* [`map`](kcl/map)
|
* [`map`](kcl/map)
|
||||||
* [`max`](kcl/max)
|
* [`max`](kcl/max)
|
||||||
* [`min`](kcl/min)
|
* [`min`](kcl/min)
|
||||||
* [`offsetPlane`](kcl/offsetPlane)
|
* [`offsetPlane`](kcl/std-offsetPlane)
|
||||||
* [`patternCircular2d`](kcl/patternCircular2d)
|
* [`patternCircular2d`](kcl/patternCircular2d)
|
||||||
* [`patternCircular3d`](kcl/patternCircular3d)
|
* [`patternCircular3d`](kcl/patternCircular3d)
|
||||||
* [`patternLinear2d`](kcl/patternLinear2d)
|
* [`patternLinear2d`](kcl/patternLinear2d)
|
||||||
@ -131,8 +109,14 @@ layout: manual
|
|||||||
* [`sweep`](kcl/sweep)
|
* [`sweep`](kcl/sweep)
|
||||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||||
* [`tangentialArc`](kcl/tangentialArc)
|
* [`tangentialArc`](kcl/tangentialArc)
|
||||||
* [`toDegrees`](kcl/toDegrees)
|
* [`toCentimeters`](kcl/std-toCentimeters)
|
||||||
* [`toRadians`](kcl/toRadians)
|
* [`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)
|
* [`translate`](kcl/translate)
|
||||||
* [`union`](kcl/union)
|
* [`union`](kcl/union)
|
||||||
* [`xLine`](kcl/xLine)
|
* [`xLine`](kcl/xLine)
|
||||||
@ -153,3 +137,14 @@ layout: manual
|
|||||||
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
|
* [`turns::QUARTER_TURN`](kcl/consts/std-turns-QUARTER_TURN)
|
||||||
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
|
* [`turns::THREE_QUARTER_TURN`](kcl/consts/std-turns-THREE_QUARTER_TURN)
|
||||||
* [`turns::ZERO`](kcl/consts/std-turns-ZERO)
|
* [`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
|
```js
|
||||||
n = int(ceil(5 / 2))
|
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.
|
// Draw n cylinders.
|
||||||
startSketchOn(XZ)
|
startSketchOn(XZ)
|
||||||
|> circle(center = [0, 0], radius = 2)
|
|> 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
|
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||||
chamfer cases work currently.
|
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 |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `hypotenuse` | [`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) | | Yes |
|
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ legAngX(
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
legAngX(5, 3)
|
legAngX(hypotenuse = 5, leg = 3)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ legAngY(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `hypotenuse` | [`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) | | Yes |
|
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ legAngY(
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```js
|
```js
|
||||||
legAngY(5, 3)
|
legAngY(hypotenuse = 5, leg = 3)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,8 +24,8 @@ legLen(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `hypotenuse` | [`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) | | Yes |
|
| `leg` | [`number`](/docs/kcl/types/number) | The length of one of the triangle's legs (i.e. non-hypotenuse side) | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ legLen(
|
|||||||
### Examples
|
### Examples
|
||||||
|
|
||||||
```js
|
```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
|
When you define a function, you can use `export` before it to make it available
|
||||||
to other modules.
|
to other modules.
|
||||||
|
|
||||||
```
|
```kcl
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment(x) {
|
export fn increment(x) {
|
||||||
return x + 1
|
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.
|
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
|
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.
|
Multiple functions can be exported in a file.
|
||||||
|
|
||||||
```
|
```kcl
|
||||||
// util.kcl
|
// util.kcl
|
||||||
export fn increment(x) {
|
export fn increment(x) {
|
||||||
return x + 1
|
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"
|
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
|
## 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
|
`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.
|
// Use `cube` just like a KCL object.
|
||||||
```
|
```
|
||||||
|
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube-2.sldprt" as cube
|
import "tests/inputs/cube.sldprt" as cube
|
||||||
|
|
||||||
// Use `cube` just like a KCL object.
|
// 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
|
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
|
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)
|
@(unitLength = ft, coords = opengl)
|
||||||
import "tests/inputs/cube.obj"
|
import "tests/inputs/cube.obj"
|
||||||
```
|
```
|
||||||
@ -110,97 +307,55 @@ Coordinate systems:
|
|||||||
- `opengl`, forward: +Z, up: +Y, handedness: right
|
- `opengl`, forward: +Z, up: +Y, handedness: right
|
||||||
- `vulkan`, forward: +Z, up: -Y, handedness: left
|
- `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
|
and rendering. To maximize throughput, you need to understand the three distinct
|
||||||
stages—reading, initializing (background render start), and invocation (blocking)
|
stages—reading, initializing (background render start), and invocation (blocking)
|
||||||
—and structure your code to defer blocking operations until the end.
|
—and structure your code to defer blocking operations until the end.
|
||||||
|
|
||||||
#### Foreign Import Execution Stages
|
### Foreign import execution stages
|
||||||
|
|
||||||
1. **Import (Read) Stage**
|
1. **Import (Read / Initialization) Stage**
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step" as cube
|
||||||
```
|
```
|
||||||
- Reads the file from disk and makes its API available.
|
- Reads the file from disk and makes its API available.
|
||||||
- **Does _not_** start Engine rendering or block your script.
|
- 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**
|
2. **Invocation (Blocking) Stage**
|
||||||
```norun
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step" as cube
|
||||||
|
|
||||||
myCube = cube // <- This line starts background rendering
|
cube
|
||||||
```
|
|> translate(z=10) // ← blocks here only
|
||||||
- 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.
|
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations.
|
||||||
|
|
||||||
3. **Invocation (Blocking) Stage**
|
### Best practices
|
||||||
```norun
|
|
||||||
import "tests/inputs/cube.step" as cube
|
|
||||||
|
|
||||||
myCube = cube
|
#### 1. Defer blocking calls
|
||||||
|
|
||||||
myCube
|
```kcl
|
||||||
|> translate(z=10) // <- This line blocks
|
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts
|
||||||
```
|
|
||||||
- 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
|
|
||||||
|
|
||||||
|
|
||||||
// --- 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
|
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.
|
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ t = 0.005 // taper factor [0-1)
|
|||||||
// Defines how to modify each layer of the vase.
|
// Defines how to modify each layer of the vase.
|
||||||
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||||
fn transform(replicaId) {
|
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 {
|
return {
|
||||||
translate = [0, 0, replicaId * 10],
|
translate = [0, 0, replicaId * 10],
|
||||||
scale = [scale, scale, 0]
|
scale = [scale, scale, 0]
|
||||||
@ -205,12 +205,12 @@ fn transform(i) {
|
|||||||
}
|
}
|
||||||
startSketchOn(XY)
|
startSketchOn(XY)
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> polygon({
|
|> polygon(
|
||||||
radius = 10,
|
radius = 10,
|
||||||
numSides = 4,
|
numSides = 4,
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
inscribed = false
|
inscribed = false,
|
||||||
}, %)
|
)
|
||||||
|> extrude(length = 4)
|
|> extrude(length = 4)
|
||||||
|> patternTransform(instances = 3, transform = transform)
|
|> patternTransform(instances = 3, transform = transform)
|
||||||
```
|
```
|
||||||
|
@ -10,9 +10,11 @@ Create a regular polygon with the specified number of sides that is either inscr
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
polygon(
|
polygon(
|
||||||
data: PolygonData,
|
|
||||||
sketchSurfaceOrGroup: SketchOrSurface,
|
sketchSurfaceOrGroup: SketchOrSurface,
|
||||||
tag?: TagDeclarator,
|
radius: number,
|
||||||
|
numSides: u64,
|
||||||
|
center: [number],
|
||||||
|
inscribed?: bool,
|
||||||
): Sketch
|
): Sketch
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -21,9 +23,11 @@ polygon(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
|
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on | Yes |
|
||||||
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
|
| `radius` | [`number`](/docs/kcl/types/number) | The radius of the polygon | Yes |
|
||||||
| [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
|
| `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
|
### Returns
|
||||||
|
|
||||||
@ -35,12 +39,12 @@ polygon(
|
|||||||
```js
|
```js
|
||||||
// Create a regular hexagon inscribed in a circle of radius 10
|
// Create a regular hexagon inscribed in a circle of radius 10
|
||||||
hex = startSketchOn(XY)
|
hex = startSketchOn(XY)
|
||||||
|> polygon({
|
|> polygon(
|
||||||
radius = 10,
|
radius = 10,
|
||||||
numSides = 6,
|
numSides = 6,
|
||||||
center = [0, 0],
|
center = [0, 0],
|
||||||
inscribed = true
|
inscribed = true,
|
||||||
}, %)
|
)
|
||||||
|
|
||||||
example = extrude(hex, length = 5)
|
example = extrude(hex, length = 5)
|
||||||
```
|
```
|
||||||
@ -50,12 +54,12 @@ example = extrude(hex, length = 5)
|
|||||||
```js
|
```js
|
||||||
// Create a square circumscribed around a circle of radius 5
|
// Create a square circumscribed around a circle of radius 5
|
||||||
square = startSketchOn(XY)
|
square = startSketchOn(XY)
|
||||||
|> polygon({
|
|> polygon(
|
||||||
radius = 5.0,
|
radius = 5.0,
|
||||||
numSides = 4,
|
numSides = 4,
|
||||||
center = [10, 10],
|
center = [10, 10],
|
||||||
inscribed = false
|
inscribed = false,
|
||||||
}, %)
|
)
|
||||||
example = extrude(square, length = 5)
|
example = extrude(square, length = 5)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin value.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
profileStart(sketch: Sketch): [number]
|
profileStart(profile: Sketch): [number]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ profileStart(sketch: Sketch): [number]
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'x' value.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
profileStartX(sketch: Sketch): number
|
profileStartX(profile: Sketch): number
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ profileStartX(sketch: Sketch): number
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'y' value.
|
|||||||
|
|
||||||
|
|
||||||
```js
|
```js
|
||||||
profileStartY(sketch: Sketch): number
|
profileStartY(profile: Sketch): number
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -17,7 +17,7 @@ profileStartY(sketch: Sketch): number
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| 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
|
### Returns
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ helix(
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `revolutions` | `number(_)` | Number of revolutions. | Yes |
|
| `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 |
|
| `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 |
|
| `radius` | `number(Length)` | Radius of the helix. | No |
|
||||||
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 30,
|
angle = 30,
|
||||||
length = 3 / cos(toRadians(30)),
|
length = 3 / cos(30deg),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
@ -11,7 +11,7 @@ cartesian (x/y/z grid) coordinates.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
polar(
|
polar(
|
||||||
angle: number(Angle),
|
angle: number(rad),
|
||||||
length: number(Length),
|
length: number(Length),
|
||||||
): Point2d
|
): Point2d
|
||||||
```
|
```
|
||||||
@ -21,7 +21,7 @@ polar(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `angle` | `number(Angle)` | | Yes |
|
| `angle` | `number(rad)` | | Yes |
|
||||||
| `length` | `number(Length)` | | Yes |
|
| `length` | `number(Length)` | | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 50,
|
angle = 50,
|
||||||
length = 15 / sin(toRadians(135)),
|
length = 15 / sin(135deg),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 50,
|
angle = 50,
|
||||||
length = 50 * tan(1/2),
|
length = 50 * tan((1/2): number(rad)),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
122
docs/kcl/std-offsetPlane.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
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
@ -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
@ -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
@ -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
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)`
|
||||||
|
|
||||||
|
|
||||||
|
|
42319
docs/kcl/std.json
@ -73,7 +73,7 @@ rectangleExtrude = extrude(rectangleSketch, length = 10)
|
|||||||
```js
|
```js
|
||||||
bottom = startSketchOn(XY)
|
bottom = startSketchOn(XY)
|
||||||
|> startProfileAt([0, 0], %)
|
|> 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)
|
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|
||||||
|> close()
|
|> close()
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Axis2d"
|
title: "std::types::Axis2d"
|
||||||
excerpt: "An infinite line in 2d space."
|
excerpt: "An infinite line in 2d space."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Axis3d"
|
title: "std::types::Axis3d"
|
||||||
excerpt: "An infinite line in 3d space."
|
excerpt: "An infinite line in 3d space."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Edge"
|
title: "std::types::Edge"
|
||||||
excerpt: "The edge of a solid."
|
excerpt: "The edge of a solid."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Face"
|
title: "std::types::Face"
|
||||||
excerpt: "A face."
|
excerpt: "A face."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
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."
|
excerpt: "A helix."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -141,8 +141,8 @@ A base path.
|
|||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `ArcThreePoint`| | No |
|
| `type` |enum: `ArcThreePoint`| | No |
|
||||||
| `p1` |`[number, number]`| Point 1 of the arc (base on the end of previous segment) | 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 |
|
| `p2` |`[number, number]`| Point 2 of the arc (interiorAbsolute kwarg) | No |
|
||||||
| `p3` |`[number, number]`| Point 3 of the arc (end kwarg) | No |
|
| `p3` |`[number, number]`| Point 3 of the arc (endAbsolute kwarg) | No |
|
||||||
| `from` |`[number, number]`| The from point. | No |
|
| `from` |`[number, number]`| The from point. | No |
|
||||||
| `to` |`[number, number]`| The to point. | No |
|
| `to` |`[number, number]`| The to point. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Plane"
|
title: "std::types::Plane"
|
||||||
excerpt: "A plane."
|
excerpt: "A plane."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Point2d"
|
title: "std::types::Point2d"
|
||||||
excerpt: "A point in two dimensional space."
|
excerpt: "A point in two dimensional space."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: "std::Point3d"
|
title: "std::types::Point3d"
|
||||||
excerpt: "A point in three dimensional space."
|
excerpt: "A point in three dimensional space."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
@ -30,8 +30,6 @@ A sketch type.
|
|||||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `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 |
|
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
|
||||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ The syntax for declaring a tag is `$myTag` you would use it in the following
|
|||||||
way:
|
way:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
startSketchOn('XZ')
|
startSketchOn(XZ)
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
@ -46,7 +46,7 @@ However if the code was written like this:
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
fn rect(origin) {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn(XZ)
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
@ -75,7 +75,7 @@ For example the following code works.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
fn rect(origin) {
|
fn rect(origin) {
|
||||||
return startSketchOn('XZ')
|
return startSketchOn(XZ)
|
||||||
|> startProfileAt(origin, %)
|
|> startProfileAt(origin, %)
|
||||||
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
|> angledLine(angle = 0, length = 191.26, tag = $rectangleSegmentA001)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
|
@ -29,11 +29,11 @@ test.describe('Electron app header tests', () => {
|
|||||||
test(
|
test(
|
||||||
'User settings has correct shortcut',
|
'User settings has correct shortcut',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
// Open the user sidebar menu.
|
// 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.
|
// No space after "User settings" since it's textContent.
|
||||||
const text =
|
const text =
|
||||||
|
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))`)
|
|> xLine(length = -segLen(seg01))`)
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {
|
test.describe('Basic sketch', () => {
|
||||||
test('code pane open at start', async ({ page, homePage }) => {
|
test('code pane open at start', async ({ page, homePage }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
await doBasicSketch(page, homePage, ['code'])
|
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 { getUtils } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
test.describe(
|
test.describe('Can create sketches on all planes and their back sides', () => {
|
||||||
'Can create sketches on all planes and their back sides',
|
const sketchOnPlaneAndBackSideTest = async (
|
||||||
{ tag: ['@skipWin'] },
|
page: Page,
|
||||||
() => {
|
homePage: HomePageFixture,
|
||||||
const sketchOnPlaneAndBackSideTest = async (
|
scene: SceneFixture,
|
||||||
page: Page,
|
toolbar: ToolbarFixture,
|
||||||
homePage: HomePageFixture,
|
plane: string,
|
||||||
scene: SceneFixture,
|
clickCoords: { x: number; y: number }
|
||||||
toolbar: ToolbarFixture,
|
) => {
|
||||||
plane: string,
|
const u = await getUtils(page)
|
||||||
clickCoords: { x: number; y: number }
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
) => {
|
|
||||||
const u = await getUtils(page)
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
const XYPlanRed: [number, number, number] = [98, 50, 51]
|
const XYPlanRed: [number, number, number] = [98, 50, 51]
|
||||||
await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
|
await scene.expectPixelColor(XYPlanRed, { x: 700, y: 300 }, 15)
|
||||||
|
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const coord =
|
const coord =
|
||||||
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
plane === '-XY' || plane === '-YZ' || plane === 'XZ' ? -100 : 100
|
||||||
const camCommand: EngineCommand = {
|
const camCommand: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_look_at',
|
type: 'default_camera_look_at',
|
||||||
center: { x: 0, y: 0, z: 0 },
|
center: { x: 0, y: 0, z: 0 },
|
||||||
vantage: { x: coord, y: coord, z: coord },
|
vantage: { x: coord, y: coord, z: coord },
|
||||||
up: { x: 0, y: 0, z: 1 },
|
up: { x: 0, y: 0, z: 1 },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
const updateCamCommand: EngineCommand = {
|
const updateCamCommand: EngineCommand = {
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_get_settings',
|
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 planeConfigs = [
|
const code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)`
|
||||||
{
|
|
||||||
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) {
|
await u.openDebugPanel()
|
||||||
test(config.plane, async ({ page, homePage, scene, toolbar }) => {
|
|
||||||
await sketchOnPlaneAndBackSideTest(
|
await u.clearCommandLogs()
|
||||||
page,
|
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||||
homePage,
|
|
||||||
scene,
|
await u.sendCustomCmd(camCommand)
|
||||||
toolbar,
|
await page.waitForTimeout(100)
|
||||||
config.plane,
|
await u.sendCustomCmd(updateCamCommand)
|
||||||
config.coords
|
|
||||||
)
|
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'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
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 ({
|
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
@ -9,7 +9,7 @@ import {
|
|||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
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 ({
|
test('Extrude from command bar selects extrude line after', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
@ -179,57 +179,57 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
|||||||
await expect(commandLevelArgButton).toHaveText('level: project')
|
await expect(commandLevelArgButton).toHaveText('level: project')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test('Command bar keybinding works from code editor and can change a setting', async ({
|
||||||
'Command bar keybinding works from code editor and can change a setting',
|
page,
|
||||||
{ tag: ['@skipWin'] },
|
homePage,
|
||||||
async ({ page, homePage }) => {
|
}) => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// FIXME: No KCL code, unable to wait for engine execution
|
// FIXME: No KCL code, unable to wait for engine execution
|
||||||
await page.waitForTimeout(10000)
|
await page.waitForTimeout(10000)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
// Put the cursor in the code editor
|
// Put the cursor in the code editor
|
||||||
await page.locator('.cm-content').click()
|
await page.locator('.cm-content').click()
|
||||||
|
|
||||||
// Now try the same, but with the keyboard shortcut, check focus
|
// Now try the same, but with the keyboard shortcut, check focus
|
||||||
await page.keyboard.press('ControlOrMeta+K')
|
await page.keyboard.press('ControlOrMeta+K')
|
||||||
|
|
||||||
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
let cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
await expect(cmdSearchBar).toBeFocused()
|
await expect(cmdSearchBar).toBeFocused()
|
||||||
|
|
||||||
// Try typing in the command bar
|
// Try typing in the command bar
|
||||||
await cmdSearchBar.fill('theme')
|
await cmdSearchBar.fill('theme')
|
||||||
const themeOption = page.getByRole('option', {
|
const themeOption = page.getByRole('option', {
|
||||||
name: 'Settings · app · theme',
|
name: 'Settings · app · theme',
|
||||||
})
|
})
|
||||||
await expect(themeOption).toBeVisible()
|
await expect(themeOption).toBeVisible()
|
||||||
await themeOption.click()
|
await themeOption.click()
|
||||||
const themeInput = page.getByPlaceholder('dark')
|
const themeInput = page.getByPlaceholder('dark')
|
||||||
await expect(themeInput).toBeVisible()
|
await expect(themeInput).toBeVisible()
|
||||||
await expect(themeInput).toBeFocused()
|
await expect(themeInput).toBeFocused()
|
||||||
// Select dark theme
|
// Select dark theme
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await page.keyboard.press('ArrowDown')
|
await page.keyboard.press('ArrowDown')
|
||||||
await expect(
|
await expect(page.getByRole('option', { name: 'system' })).toHaveAttribute(
|
||||||
page.getByRole('option', { name: 'system' })
|
'data-headlessui-state',
|
||||||
).toHaveAttribute('data-headlessui-state', 'active')
|
'active'
|
||||||
await page.keyboard.press('Enter')
|
)
|
||||||
|
await page.keyboard.press('Enter')
|
||||||
|
|
||||||
// Check the toast appeared
|
// Check the toast appeared
|
||||||
await expect(
|
await expect(
|
||||||
page.getByText(`Set theme to "system" as a user default`)
|
page.getByText(`Set theme to "system" as a user default`)
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
// Check that the theme changed
|
// Check that the theme changed
|
||||||
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
await expect(page.locator('body')).not.toHaveClass(`body-bg dark`)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test('Can extrude from the command bar', async ({
|
test('Can extrude from the command bar', async ({
|
||||||
page,
|
page,
|
||||||
|
@ -10,7 +10,7 @@ import { expect, test } from '@e2e/playwright/zoo-test'
|
|||||||
|
|
||||||
test(
|
test(
|
||||||
'export works on the first try',
|
'export works on the first try',
|
||||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
{ tag: ['@electron', '@macos', '@windows', '@skipLocalEngine'] },
|
||||||
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
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 }) => {
|
test('can comment out code with ctrl+/', async ({ page, homePage }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
@ -989,162 +989,162 @@ sketch001 = startSketchOn(XZ)
|
|||||||
|> close()`)
|
|> close()`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test('Can undo a sketch modification with ctrl+z', async ({
|
||||||
'Can undo a sketch modification with ctrl+z',
|
page,
|
||||||
{ tag: ['@skipWin'] },
|
homePage,
|
||||||
async ({ page, homePage, editor }) => {
|
editor,
|
||||||
const u = await getUtils(page)
|
}) => {
|
||||||
await page.addInitScript(async () => {
|
const u = await getUtils(page)
|
||||||
localStorage.setItem(
|
await page.addInitScript(async () => {
|
||||||
'persistCode',
|
localStorage.setItem(
|
||||||
`@settings(defaultLengthUnit=in)
|
'persistCode',
|
||||||
|
`@settings(defaultLengthUnit=in)
|
||||||
sketch001 = startSketchOn(XZ)
|
sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`
|
|> extrude(length = 5)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
).not.toBeDisabled()
|
).not.toBeDisabled()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await u.openAndClearDebugPanel()
|
await u.openAndClearDebugPanel()
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_look_at',
|
type: 'default_camera_look_at',
|
||||||
vantage: { x: 0, y: -1250, z: 580 },
|
vantage: { x: 0, y: -1250, z: 580 },
|
||||||
center: { x: 0, y: 0, z: 0 },
|
center: { x: 0, y: 0, z: 0 },
|
||||||
up: { x: 0, y: 0, z: 1 },
|
up: { x: 0, y: 0, z: 1 },
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await u.sendCustomCmd({
|
await u.sendCustomCmd({
|
||||||
type: 'modeling_cmd_req',
|
type: 'modeling_cmd_req',
|
||||||
cmd_id: uuidv4(),
|
cmd_id: uuidv4(),
|
||||||
cmd: {
|
cmd: {
|
||||||
type: 'default_camera_get_settings',
|
type: 'default_camera_get_settings',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
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 page.getByText('startProfileAt([4.61, -10.01], %)').click()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Edit Sketch' })
|
page.getByRole('button', { name: 'Edit Sketch' })
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
await page.getByRole('button', { name: 'Edit Sketch' }).click()
|
||||||
await page.waitForTimeout(400)
|
await page.waitForTimeout(400)
|
||||||
let prevContent = await page.locator('.cm-content').innerText()
|
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
|
// drag startProfileAt handle
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
sourcePosition: { x: startPX[0] + 68, y: startPX[1] + 147 },
|
||||||
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
targetPosition: { x: startPX[0] + dragPX, y: startPX[1] + dragPX },
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
// drag line handle
|
// drag line handle
|
||||||
// we wait so it saves the code
|
// we wait so it saves the code
|
||||||
await page.waitForTimeout(800)
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
const lineEnd = await u.getBoundingBox('[data-overlay-index="0"]')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
sourcePosition: { x: lineEnd.x - 5, y: lineEnd.y },
|
||||||
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
targetPosition: { x: lineEnd.x + dragPX, y: lineEnd.y + dragPX },
|
||||||
})
|
})
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
prevContent = await page.locator('.cm-content').innerText()
|
prevContent = await page.locator('.cm-content').innerText()
|
||||||
|
|
||||||
// we wait so it saves the code
|
// we wait so it saves the code
|
||||||
await page.waitForTimeout(800)
|
await page.waitForTimeout(800)
|
||||||
|
|
||||||
// drag tangentialArc handle
|
// drag tangentialArc handle
|
||||||
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
const tangentEnd = await u.getBoundingBox('[data-overlay-index="1"]')
|
||||||
await page.dragAndDrop('#stream', '#stream', {
|
await page.dragAndDrop('#stream', '#stream', {
|
||||||
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
sourcePosition: { x: tangentEnd.x + 10, y: tangentEnd.y - 5 },
|
||||||
targetPosition: {
|
targetPosition: {
|
||||||
x: tangentEnd.x + dragPX,
|
x: tangentEnd.x + dragPX,
|
||||||
y: tangentEnd.y + dragPX,
|
y: tangentEnd.y + dragPX,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
await expect(page.locator('.cm-content')).not.toHaveText(prevContent)
|
||||||
|
|
||||||
// expect the code to have changed
|
// expect the code to have changed
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn(XZ)
|
`sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [15.4, -2.78])
|
|> line(end = [15.4, -2.78])
|
||||||
|> tangentialArc(endAbsolute = [27.6, -3.05])
|
|> tangentialArc(endAbsolute = [27.6, -3.05])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`,
|
|> extrude(length = 5)`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hit undo
|
// Hit undo
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn(XZ)
|
`sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [15.4, -2.78])
|
|> line(end = [15.4, -2.78])
|
||||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`,
|
|> extrude(length = 5)`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn(XZ)
|
`sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([2.71, -2.71], %)
|
|> startProfileAt([2.71, -2.71], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`,
|
|> extrude(length = 5)`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Hit undo again.
|
// Hit undo again.
|
||||||
await page.keyboard.down('Control')
|
await page.keyboard.down('Control')
|
||||||
await page.keyboard.press('KeyZ')
|
await page.keyboard.press('KeyZ')
|
||||||
await page.keyboard.up('Control')
|
await page.keyboard.up('Control')
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`sketch001 = startSketchOn(XZ)
|
`sketch001 = startSketchOn(XZ)
|
||||||
|> startProfileAt([4.61, -10.01], %)
|
|> startProfileAt([4.61, -10.01], %)
|
||||||
|> line(end = [12.73, -0.09])
|
|> line(end = [12.73, -0.09])
|
||||||
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
|> tangentialArc(endAbsolute = [24.95, -0.38])
|
||||||
|> close()
|
|> close()
|
||||||
|> extrude(length = 5)`,
|
|> extrude(length = 5)`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
`Can import a local OBJ file`,
|
`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('sketch001 =')
|
||||||
await editor.expectEditor.not.toContain('profile002 = ')
|
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 =')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
|
|||||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||||
|
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
|
||||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ export interface Fixtures {
|
|||||||
toolbar: ToolbarFixture
|
toolbar: ToolbarFixture
|
||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
homePage: HomePageFixture
|
homePage: HomePageFixture
|
||||||
|
signInPage: SignInPageFixture
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronZoo {
|
export class ElectronZoo {
|
||||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
|
|||||||
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
|
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
|
await use(new SignInPageFixture(page))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
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
|
gizmo!: Locator
|
||||||
gizmoDisabled!: Locator
|
gizmoDisabled!: Locator
|
||||||
loadButton!: Locator
|
loadButton!: Locator
|
||||||
|
/** User button for the user sidebar menu */
|
||||||
|
userSidebarButton!: Locator
|
||||||
|
signOutButton!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
|
|||||||
// element or two different elements can represent these states.
|
// element or two different elements can represent these states.
|
||||||
this.gizmo = page.getByTestId('gizmo')
|
this.gizmo = page.getByTestId('gizmo')
|
||||||
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||||
|
|
||||||
|
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
|
||||||
|
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoLink() {
|
get logoLink() {
|
||||||
@ -168,6 +174,13 @@ export class ToolbarFixture {
|
|||||||
openFile = async (fileName: string) => {
|
openFile = async (fileName: string) => {
|
||||||
await this.filePane.getByText(fileName).click()
|
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 () => {
|
selectCenterRectangle = async () => {
|
||||||
await this.page
|
await this.page
|
||||||
.getByRole('button', { name: 'caret down rectangles:' })
|
.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 {
|
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 {
|
onTestEnd(test: TestCase, result: TestResult): void {
|
||||||
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
|
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
|
||||||
return
|
return
|
||||||
@ -20,8 +39,9 @@ class MyAPIReporter implements Reporter {
|
|||||||
platform: process.env.RUNNER_OS || process.platform,
|
platform: process.env.RUNNER_OS || process.platform,
|
||||||
// Extra test and result data
|
// Extra test and result data
|
||||||
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
|
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,
|
retry: result.retry,
|
||||||
tags: test.tags, // e.g. '@snapshot' or '@skipWin'
|
tags: test.tags, // e.g. '@snapshot' or '@skipLocalEngine'
|
||||||
// Extra environment variables
|
// Extra environment variables
|
||||||
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
|
CI_COMMIT_SHA: process.env.CI_COMMIT_SHA || null,
|
||||||
CI_PR_NUMBER: process.env.CI_PR_NUMBER || 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,
|
RUNNER_ARCH: process.env.RUNNER_ARCH || null,
|
||||||
}
|
}
|
||||||
|
|
||||||
void (async () => {
|
const request = (async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
|
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@ -46,18 +66,27 @@ class MyAPIReporter implements Reporter {
|
|||||||
body: JSON.stringify(payload),
|
body: JSON.stringify(payload),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!response.ok && !process.env.CI) {
|
if (response.ok) {
|
||||||
console.error(
|
const result = await response.json()
|
||||||
'TAB API - Failed to send test result:',
|
this.allResults.push(result)
|
||||||
await response.text()
|
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) {
|
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',
|
'e2e/playwright/can-create-sketches-on-all-planes-and-their-back-sides.spec.ts XY',
|
||||||
project: 'Google Chrome',
|
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',
|
name: 'RangeError',
|
||||||
message: 'Position 160 is out of range for changeset of length 0',
|
message: 'Position 160 is out of range for changeset of length 0',
|
||||||
|
@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
{
|
{
|
||||||
tag: '@electron',
|
tag: '@electron',
|
||||||
},
|
},
|
||||||
async ({ page, tronApp }) => {
|
async ({ page, tronApp, scene }) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
@ -72,7 +72,6 @@ test.describe('Onboarding tests', () => {
|
|||||||
onboarding_status: '',
|
onboarding_status: '',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const u = await getUtils(page)
|
|
||||||
|
|
||||||
const viewportSize = { width: 1200, height: 500 }
|
const viewportSize = { width: 1200, height: 500 }
|
||||||
await page.setBodyDimensions(viewportSize)
|
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 test.step(`Create a project and open to the onboarding`, async () => {
|
||||||
await createProject({ name: 'project-link', page })
|
await createProject({ name: 'project-link', page })
|
||||||
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
|
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 ({
|
test('Code resets after confirmation', async ({
|
||||||
context,
|
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
scene,
|
scene,
|
||||||
cmdBar,
|
|
||||||
}) => {
|
}) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -276,6 +273,8 @@ test.describe('Onboarding tests', () => {
|
|||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
|
editor,
|
||||||
|
toolbar,
|
||||||
}) => {
|
}) => {
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
@ -289,7 +288,6 @@ test.describe('Onboarding tests', () => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const u = await getUtils(page)
|
|
||||||
const badCode = `// This is bad code we shouldn't see`
|
const badCode = `// This is bad code we shouldn't see`
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
await page.setBodyDimensions({ width: 1200, height: 1080 })
|
||||||
@ -299,18 +297,19 @@ test.describe('Onboarding tests', () => {
|
|||||||
.poll(() => page.url())
|
.poll(() => page.url())
|
||||||
.toContain(onboardingPaths.PARAMETRIC_MODELING)
|
.toContain(onboardingPaths.PARAMETRIC_MODELING)
|
||||||
|
|
||||||
const bracketNoNewLines = bracket.replace(/\n/g, '')
|
|
||||||
|
|
||||||
// Check the code got reset on load
|
// Check the code got reset on load
|
||||||
await expect(page.locator('#code-pane')).toBeVisible()
|
await toolbar.openPane('code')
|
||||||
await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
|
await editor.expectEditor.toContain(bracket, {
|
||||||
|
shouldNormalise: true,
|
||||||
timeout: 10_000,
|
timeout: 10_000,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Mess with the code again
|
// Mess with the code again
|
||||||
await u.codeLocator.selectText()
|
await editor.replaceCode('', badCode)
|
||||||
await u.codeLocator.fill(badCode)
|
await editor.expectEditor.toContain(badCode, {
|
||||||
await expect(u.codeLocator).toHaveText(badCode)
|
shouldNormalise: true,
|
||||||
|
timeout: 10_000,
|
||||||
|
})
|
||||||
|
|
||||||
// Click to the next step
|
// Click to the next step
|
||||||
await page.locator('[data-testid="onboarding-next"]').hover()
|
await page.locator('[data-testid="onboarding-next"]').hover()
|
||||||
@ -320,7 +319,10 @@ test.describe('Onboarding tests', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Check that the code has been reset
|
// 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
|
// (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 ({
|
test('Avatar text updates depending on image load success', async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
@ -360,7 +363,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// 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
|
const onboardingOverlayLocator = page
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
.locator('div')
|
.locator('div')
|
||||||
@ -402,6 +405,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
homePage,
|
homePage,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
@ -433,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
|
|
||||||
// Test that the text in this step is correct
|
// 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 avatar = sidebar.locator('img')
|
||||||
const onboardingOverlayLocator = page
|
const onboardingOverlayLocator = page
|
||||||
.getByTestId('onboarding-content')
|
.getByTestId('onboarding-content')
|
||||||
@ -462,6 +466,7 @@ test.describe('Onboarding tests', () => {
|
|||||||
test('Restarting onboarding on desktop takes one attempt', async ({
|
test('Restarting onboarding on desktop takes one attempt', async ({
|
||||||
context,
|
context,
|
||||||
page,
|
page,
|
||||||
|
toolbar,
|
||||||
tronApp,
|
tronApp,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
@ -500,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
|
|||||||
.filter({ hasText: 'Tutorial Project 00' })
|
.filter({ hasText: 'Tutorial Project 00' })
|
||||||
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
||||||
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||||
const userMenuButton = page.getByTestId('user-sidebar-toggle')
|
const userMenuButton = toolbar.userSidebarButton
|
||||||
const userMenuSettingsButton = page.getByRole('button', {
|
const userMenuSettingsButton = page.getByRole('button', {
|
||||||
name: 'User settings',
|
name: 'User settings',
|
||||||
})
|
})
|
||||||
|
@ -6,6 +6,7 @@ import type { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
|||||||
import {
|
import {
|
||||||
executorInputPath,
|
executorInputPath,
|
||||||
getUtils,
|
getUtils,
|
||||||
|
kclSamplesPath,
|
||||||
testsInputPath,
|
testsInputPath,
|
||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
@ -143,28 +144,45 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Insert a second time and expect error', async () => {
|
await test.step('Insert a second time with the same name and expect error', async () => {
|
||||||
// TODO: revisit once we have clone with #6209
|
await toolbar.insertButton.click()
|
||||||
await insertPartIntoAssembly(
|
await cmdBar.selectOption({ name: 'bracket.kcl' }).click()
|
||||||
'bracket.kcl',
|
await cmdBar.expectState({
|
||||||
'bracket',
|
stage: 'arguments',
|
||||||
toolbar,
|
currentArgKey: 'localName',
|
||||||
cmdBar,
|
currentArgValue: '',
|
||||||
page
|
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(
|
await editor.expectEditor.toContain(
|
||||||
`
|
`
|
||||||
import "cylinder.kcl" as cylinder
|
import "cylinder.kcl" as cylinder
|
||||||
import "bracket.kcl" as bracket
|
import "bracket.kcl" as bracket
|
||||||
import "bracket.kcl" as bracket
|
import "bracket.kcl" as bracket2
|
||||||
cylinder
|
cylinder
|
||||||
bracket
|
bracket
|
||||||
bracket
|
bracket2
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
await scene.settled(cmdBar)
|
// TODO: update once we have clone() with #6209
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -261,7 +279,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
highlightedHeaderArg: 'x',
|
highlightedHeaderArg: 'x',
|
||||||
commandName: 'Translate',
|
commandName: 'Translate',
|
||||||
})
|
})
|
||||||
await page.keyboard.insertText('5')
|
await page.keyboard.insertText('100')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await page.keyboard.insertText('0.1')
|
await page.keyboard.insertText('0.1')
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -270,7 +288,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
X: '5',
|
X: '100',
|
||||||
Y: '0.1',
|
Y: '0.1',
|
||||||
Z: '0.2',
|
Z: '0.2',
|
||||||
},
|
},
|
||||||
@ -282,7 +300,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`
|
`
|
||||||
bracket
|
bracket
|
||||||
|> translate(x = 5, y = 0.1, z = 0.2)
|
|> translate(x = 100, y = 0.1, z = 0.2)
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ shouldNormalise: true }
|
||||||
)
|
)
|
||||||
@ -331,7 +349,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
|||||||
await editor.expectEditor.toContain(
|
await editor.expectEditor.toContain(
|
||||||
`
|
`
|
||||||
bracket
|
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)
|
|> rotate(roll = 0.1, pitch = 0.2, yaw = 0.3)
|
||||||
`,
|
`,
|
||||||
{ shouldNormalise: true }
|
{ 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(
|
test(
|
||||||
'projects reload if a new one is created, deleted, or renamed externally',
|
'projects reload if a new one is created, deleted, or renamed externally',
|
||||||
{ tag: '@electron' },
|
{ tag: ['@electron', '@macos', '@windows'] },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page }, testInfo) => {
|
||||||
let externalCreatedProjectName = 'external-created-project'
|
let externalCreatedProjectName = 'external-created-project'
|
||||||
|
|
||||||
@ -400,11 +400,6 @@ test(
|
|||||||
await expect(page.getByText('broken-code')).toBeVisible()
|
await expect(page.getByText('broken-code')).toBeVisible()
|
||||||
await page.getByText('broken-code').click()
|
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
|
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
|
||||||
await editor.scrollToText(
|
await editor.scrollToText(
|
||||||
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
|
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
|
||||||
@ -779,7 +774,9 @@ test.describe(`Project management commands`, () => {
|
|||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
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 projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const projectRenamedName = `untitled`
|
const projectRenamedName = `untitled`
|
||||||
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||||
@ -839,7 +836,9 @@ test.describe(`Project management commands`, () => {
|
|||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
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 projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
const commandSubmitButton = page.getByRole('button', {
|
||||||
@ -891,7 +890,9 @@ test.describe(`Project management commands`, () => {
|
|||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
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 projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const projectRenamedName = `untitled`
|
const projectRenamedName = `untitled`
|
||||||
const commandContinueButton = page.getByRole('button', {
|
const commandContinueButton = page.getByRole('button', {
|
||||||
@ -947,7 +948,9 @@ test.describe(`Project management commands`, () => {
|
|||||||
// Constants and locators
|
// Constants and locators
|
||||||
const projectHomeLink = page.getByTestId('project-link')
|
const projectHomeLink = page.getByTestId('project-link')
|
||||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
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 projectNameOption = page.getByRole('option', { name: projectName })
|
||||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||||
const commandSubmitButton = page.getByRole('button', {
|
const commandSubmitButton = page.getByRole('button', {
|
||||||
@ -1812,8 +1815,8 @@ test(
|
|||||||
'basic_fillet_cube_next_adjacent.kcl',
|
'basic_fillet_cube_next_adjacent.kcl',
|
||||||
'basic_fillet_cube_previous_adjacent.kcl',
|
'basic_fillet_cube_previous_adjacent.kcl',
|
||||||
'basic_fillet_cube_start.kcl',
|
'basic_fillet_cube_start.kcl',
|
||||||
'big_number_angle_to_match_length_x.kcl',
|
'broken-code-test.kcl',
|
||||||
'big_number_angle_to_match_length_y.kcl',
|
'circular_pattern3d_a_pattern.kcl',
|
||||||
'close_arc.kcl',
|
'close_arc.kcl',
|
||||||
'computed_var.kcl',
|
'computed_var.kcl',
|
||||||
'cube-embedded.gltf',
|
'cube-embedded.gltf',
|
||||||
@ -1962,13 +1965,13 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Settings persist across restarts',
|
'Settings persist across restarts',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page, scene, cmdBar }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
await test.step('We can change a user setting like theme', async () => {
|
await test.step('We can change a user setting like theme', async () => {
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
page.on('console', console.log)
|
page.on('console', console.log)
|
||||||
|
|
||||||
await page.getByTestId('user-sidebar-toggle').click()
|
await toolbar.userSidebarButton.click()
|
||||||
|
|
||||||
await page.getByTestId('user-settings').click()
|
await page.getByTestId('user-settings').click()
|
||||||
|
|
||||||
@ -1995,7 +1998,7 @@ test(
|
|||||||
test(
|
test(
|
||||||
'Original project name persist after onboarding',
|
'Original project name persist after onboarding',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ page }, testInfo) => {
|
async ({ page, toolbar }, testInfo) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test.fixme(orRunWhenFullSuiteEnabled())
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
|
|
||||||
@ -2007,7 +2010,7 @@ test(
|
|||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Should go through onboarding', async () => {
|
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.getByTestId('user-settings').click()
|
||||||
await page.getByRole('button', { name: 'Replay Onboarding' }).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'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
/* eslint-disable jest/no-conditional-expect */
|
/* eslint-disable jest/no-conditional-expect */
|
||||||
@ -30,7 +29,7 @@ sketch003 = startSketchOn(XY)
|
|||||||
extrude003 = extrude(sketch003, length = 20)
|
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', () => {
|
test.describe('Check the happy path, for basic changing color', () => {
|
||||||
const cases = [
|
const cases = [
|
||||||
{
|
{
|
||||||
@ -51,7 +50,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
await context.addInitScript((file) => {
|
await context.addInitScript((file) => {
|
||||||
localStorage.setItem('persistCode', file)
|
localStorage.setItem('persistCode', file)
|
||||||
}, file)
|
}, file)
|
||||||
@ -200,7 +198,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const body1CapCoords = { x: 571, y: 311 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
|
|
||||||
await context.addInitScript((file) => {
|
await context.addInitScript((file) => {
|
||||||
@ -260,7 +257,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
|||||||
page,
|
page,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const body1CapCoords = { x: 571, y: 311 }
|
const body1CapCoords = { x: 571, y: 311 }
|
||||||
const body2WallCoords = { x: 620, y: 152 }
|
const body2WallCoords = { x: 620, y: 152 }
|
||||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||||
|
@ -14,7 +14,7 @@ import {
|
|||||||
} from '@e2e/playwright/test-utils'
|
} from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
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
|
// bugs we found that don't fit neatly into other categories
|
||||||
test('bad model has inline error #3251', async ({
|
test('bad model has inline error #3251', async ({
|
||||||
context,
|
context,
|
||||||
@ -239,17 +239,18 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
await expect(zooLogo).not.toHaveAttribute('href')
|
await expect(zooLogo).not.toHaveAttribute('href')
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test('Position _ Is Out Of Range... regression test', async ({
|
||||||
'Position _ Is Out Of Range... regression test',
|
context,
|
||||||
{ tag: ['@skipWin'] },
|
page,
|
||||||
async ({ context, page, homePage }) => {
|
homePage,
|
||||||
const u = await getUtils(page)
|
}) => {
|
||||||
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const u = await getUtils(page)
|
||||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
// const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
await context.addInitScript(async () => {
|
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||||
localStorage.setItem(
|
await context.addInitScript(async () => {
|
||||||
'persistCode',
|
localStorage.setItem(
|
||||||
`exampleSketch = startSketchOn("XZ")
|
'persistCode',
|
||||||
|
`exampleSketch = startSketchOn("XZ")
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(angle = 50, length = 45 )
|
|> angledLine(angle = 50, length = 45 )
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
@ -258,55 +259,55 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
|
|
||||||
example = extrude(exampleSketch, length = 5)
|
example = extrude(exampleSketch, length = 5)
|
||||||
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
|
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 () => {
|
// error text on hover
|
||||||
await homePage.goToModelingScene()
|
await page.hover('.cm-lint-marker-error')
|
||||||
await u.waitForPageLoad()
|
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
|
||||||
|
|
||||||
// error in guter
|
// Okay execution finished, let's start editing text below the error.
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
await u.codeLocator.click()
|
||||||
timeout: 1_000,
|
// Go to the end of the editor
|
||||||
})
|
// This bug happens when there is a diagnostic in the editor and you try to
|
||||||
await page.waitForTimeout(200)
|
// edit text below it.
|
||||||
// expect it still to be there (sometimes it just clears for a bit?)
|
// Or delete a huge chunk of text and then try to edit below it.
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
|
await page.keyboard.press('End')
|
||||||
timeout: 1_000,
|
await page.keyboard.down('Shift')
|
||||||
})
|
await page.keyboard.press('ArrowUp')
|
||||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
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.keyboard.press('Enter')
|
||||||
await page.hover('.cm-lint-marker-error')
|
await page.keyboard.press('Enter')
|
||||||
await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
|
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 expect(
|
||||||
await u.codeLocator.click()
|
page.locator('.cm-content')
|
||||||
// Go to the end of the editor
|
).toContainText(`exampleSketch = startSketchOn("XZ")
|
||||||
// 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")
|
|
||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(angle = 50, length = 45 )
|
|> angledLine(angle = 50, length = 45 )
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
@ -314,9 +315,8 @@ extrude001 = extrude(sketch001, length = 50)
|
|||||||
|
|
||||||
thing: "blah"`)
|
thing: "blah"`)
|
||||||
|
|
||||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||||
}
|
})
|
||||||
)
|
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'window resize updates should reconfigure the stream',
|
'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.
|
// We updated this test such that you can have multiple exports going at once.
|
||||||
test(
|
test('ensure you CAN export while an export is already going', async ({
|
||||||
'ensure you CAN export while an export is already going',
|
page,
|
||||||
{ tag: ['@skipLinux', '@skipWin'] },
|
homePage,
|
||||||
async ({ page, homePage }) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await test.step('Set up the code and durations', async () => {
|
await test.step('Set up the code and durations', async () => {
|
||||||
await page.addInitScript(
|
await page.addInitScript(
|
||||||
async ({ code }) => {
|
async ({ code }) => {
|
||||||
localStorage.setItem('persistCode', code)
|
localStorage.setItem('persistCode', code)
|
||||||
;(window as any).playwrightSkipFilePicker = true
|
;(window as any).playwrightSkipFilePicker = true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: bracket,
|
code: bracket,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
// wait for execution done
|
// wait for execution done
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// expect zero errors in guter
|
// expect zero errors in guter
|
||||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||||
})
|
})
|
||||||
|
|
||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||||
const successToastMessage = page.getByText(`Exported successfully`)
|
const successToastMessage = page.getByText(`Exported successfully`)
|
||||||
|
|
||||||
await test.step('second export', async () => {
|
await test.step('second export', async () => {
|
||||||
await clickExportButton(page)
|
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 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 Promise.all([
|
await Promise.all([
|
||||||
expect(exportingToastMessage).not.toBeVisible(),
|
expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 }),
|
||||||
expect(errorToastMessage).not.toBeVisible(),
|
expect(errorToastMessage).not.toBeVisible(),
|
||||||
expect(engineErrorToastMessage).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(
|
test(
|
||||||
`Network health indicator only appears in modeling view`,
|
`Network health indicator only appears in modeling view`,
|
||||||
|
@ -47,9 +47,8 @@ test.setTimeout(60_000)
|
|||||||
// up with another PR if we want this back.
|
// up with another PR if we want this back.
|
||||||
test(
|
test(
|
||||||
'exports of each format should work',
|
'exports of each format should work',
|
||||||
{ tag: ['@snapshot', '@skipWin', '@skipMacos'] },
|
{ tag: ['@snapshot'] },
|
||||||
async ({ page, context, scene, cmdBar, tronApp }) => {
|
async ({ page, context, scene, cmdBar, tronApp }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
if (!tronApp) {
|
if (!tronApp) {
|
||||||
fail()
|
fail()
|
||||||
}
|
}
|
||||||
@ -377,8 +376,6 @@ test.describe(
|
|||||||
'extrude on default planes should be stable',
|
'extrude on default planes should be stable',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
() => {
|
() => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
|
|
||||||
test('XY', async ({ page, context, cmdBar, scene }) => {
|
test('XY', async ({ page, context, cmdBar, scene }) => {
|
||||||
await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
|
await extrudeDefaultPlane(context, page, cmdBar, scene, 'XY')
|
||||||
})
|
})
|
||||||
@ -409,7 +406,6 @@ test(
|
|||||||
'Draft segments should look right',
|
'Draft segments should look right',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, scene, toolbar }) => {
|
async ({ page, scene, toolbar }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -468,9 +464,7 @@ test(
|
|||||||
|> xLine(length = 184.3)`
|
|> xLine(length = 184.3)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await toolbar.selectTangentialArc()
|
||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
|
||||||
.click()
|
|
||||||
|
|
||||||
// click on the end of the profile to continue it
|
// click on the end of the profile to continue it
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
@ -534,8 +528,6 @@ test(
|
|||||||
'Draft rectangles should look right',
|
'Draft rectangles should look right',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -581,7 +573,6 @@ test(
|
|||||||
'Draft circle should look right',
|
'Draft circle should look right',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -628,9 +619,7 @@ test.describe(
|
|||||||
'Client side scene scale should match engine scale',
|
'Client side scene scale should match engine scale',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
() => {
|
() => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => {
|
||||||
|
|
||||||
test('Inch scale', async ({ page, cmdBar, scene }) => {
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await page.setViewportSize({ width: 1200, height: 500 })
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||||
@ -664,9 +653,7 @@ test.describe(
|
|||||||
|> xLine(length = 184.3)`
|
|> xLine(length = 184.3)`
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await toolbar.selectTangentialArc()
|
||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// click to continue profile
|
// click to continue profile
|
||||||
@ -680,9 +667,8 @@ test.describe(
|
|||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
// click tangential arc tool again to unequip it
|
// click tangential arc tool again to unequip it
|
||||||
await page
|
// it will be available directly in the toolbar since it was last equipped
|
||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
await toolbar.tangentialArcBtn.click()
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// screen shot should show the sketch
|
// 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(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
@ -758,9 +750,7 @@ test.describe(
|
|||||||
|> xLine(length = 184.3)`
|
|> xLine(length = 184.3)`
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await toolbar.selectTangentialArc()
|
||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// click to continue profile
|
// click to continue profile
|
||||||
@ -773,9 +763,7 @@ test.describe(
|
|||||||
|> tangentialArc(endAbsolute = [551.2, -62.01])`
|
|> tangentialArc(endAbsolute = [551.2, -62.01])`
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await toolbar.tangentialArcBtn.click()
|
||||||
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
|
|
||||||
.click()
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
// screen shot should show the sketch
|
// screen shot should show the sketch
|
||||||
@ -805,8 +793,6 @@ test(
|
|||||||
'Sketch on face with none z-up',
|
'Sketch on face with none z-up',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
await context.addInitScript(async (KCL_DEFAULT_LENGTH) => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -865,8 +851,6 @@ test(
|
|||||||
'Zoom to fit on load - solid 2d',
|
'Zoom to fit on load - solid 2d',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -903,8 +887,6 @@ test(
|
|||||||
'Zoom to fit on load - solid 3d',
|
'Zoom to fit on load - solid 3d',
|
||||||
{ tag: '@snapshot' },
|
{ tag: '@snapshot' },
|
||||||
async ({ page, context, cmdBar, scene }) => {
|
async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -944,7 +926,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
cmdBar,
|
cmdBar,
|
||||||
scene,
|
scene,
|
||||||
}) => {
|
}) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const stream = page.getByTestId('stream')
|
const stream = page.getByTestId('stream')
|
||||||
|
|
||||||
@ -1004,7 +985,6 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
test('Grid turned off', async ({ page, cmdBar, scene }) => {
|
test('Grid turned off', async ({ page, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
const stream = page.getByTestId('stream')
|
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('Grid turned on', async ({ page, context, cmdBar, scene }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
await context.addInitScript(
|
await context.addInitScript(
|
||||||
async ({ settingsKey, settings }) => {
|
async ({ settingsKey, settings }) => {
|
||||||
localStorage.setItem(settingsKey, settings)
|
localStorage.setItem(settingsKey, settings)
|
||||||
@ -1136,7 +1115,6 @@ test('theme persists', async ({ page, context }) => {
|
|||||||
|
|
||||||
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
test.describe('code color goober', { tag: '@snapshot' }, () => {
|
||||||
test('code color goober', async ({ page, context, scene, cmdBar }) => {
|
test('code color goober', async ({ page, context, scene, cmdBar }) => {
|
||||||
test.fixme(orRunWhenFullSuiteEnabled())
|
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
await context.addInitScript(async () => {
|
await context.addInitScript(async () => {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
@ -1154,11 +1132,7 @@ sweepPath = startSketchOn(XZ)
|
|||||||
|
|
||||||
sweepSketch = startSketchOn(XY)
|
sweepSketch = startSketchOn(XY)
|
||||||
|> startProfileAt([2, 0], %)
|
|> startProfileAt([2, 0], %)
|
||||||
|> arc({
|
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|
||||||
angleEnd = 360,
|
|
||||||
angleStart = 0,
|
|
||||||
radius = 2
|
|
||||||
}, %)
|
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> appearance(
|
|> appearance(
|
||||||
color = "#bb00ff",
|
color = "#bb00ff",
|
||||||
@ -1203,11 +1177,7 @@ sweepPath = startSketchOn(XZ)
|
|||||||
|
|
||||||
sweepSketch = startSketchOn(XY)
|
sweepSketch = startSketchOn(XY)
|
||||||
|> startProfileAt([2, 0], %)
|
|> startProfileAt([2, 0], %)
|
||||||
|> arc({
|
|> arc(angleStart = 0, angleEnd = 360, radius = 2)
|
||||||
angleEnd = 360,
|
|
||||||
angleStart = 0,
|
|
||||||
radius = 2
|
|
||||||
}, %)
|
|
||||||
|> sweep(path = sweepPath)
|
|> sweep(path = sweepPath)
|
||||||
|> appearance(
|
|> appearance(
|
||||||
color = "#bb00ff",
|
color = "#bb00ff",
|
||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |