Compare commits

..

2 Commits

870 changed files with 77612 additions and 167557 deletions

View File

@ -19,11 +19,6 @@
"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",
@ -66,7 +61,6 @@
"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",
{ {

View File

@ -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
status=$(jq -r '.status' test-results/.last-run.json) failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
if [[ "$status" == "failed" ]]; then if [[ $failed_tests -gt 0 ]]; then
echo "retried=true" >>$GITHUB_OUTPUT echo "retried=true" >>$GITHUB_OUTPUT
echo "run playwright with last failed tests and retry $retry" echo "run playwright with last failed tests and retry $retry"
if [[ "$3" == *ubuntu* ]]; then if [[ "$3" == *ubuntu* ]]; then
@ -56,11 +56,10 @@ 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
status=$(jq -r '.status' test-results/.last-run.json) failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
if [[ "$status" == "failed" ]]; then if [[ $failed_tests -gt 0 ]]; then
# If it still fails after retries, then fail the job # If it still fails after 3 retries, then fail the job
exit 1 exit 1
fi fi
fi fi
exit 0 exit 0

View File

@ -285,10 +285,21 @@ 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-8-cores - namespace-profile-macos-6-cores
- windows-latest-8-cores - windows-latest-8-cores
shardIndex: [1, 2, 3, 4] shardIndex: [1, 2, 3, 4]
shardTotal: [4] shardTotal: [4]
# Disable macos and windows tests on hourly e2e tests since we only care
# about server side changes.
# Technique from https://github.com/joaomcteixeira/python-project-skeleton/pull/31/files
isScheduled:
- ${{ github.event_name == 'schedule' }}
exclude:
- os: namespace-profile-macos-6-cores
isScheduled: true
- os: windows-latest-8-cores
isScheduled: true
# TODO: add ref here for main and latest release tag
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@ -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/@biomejs/biome/bin/biome", args = ["format", "--write", "--stdin-file-path=foo.ts"] } formatter = { command = "node_modules/.bin/prettier", args = ["--parser", "typescript"] }
# language-servers = [ { name = "eslint", only-features = [ "diagnostics" ] }, "typescript-language-server" ] language-servers = [ { name = "eslint", only-features = [ "diagnostics" ] }, "typescript-language-server" ]

View File

@ -1 +0,0 @@
npm run fmt

4
.husky/pre-push Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run fmt-check

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

45
docs/kcl/arcTo.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,8 @@ 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
``` ```
@ -23,11 +21,9 @@ bezierCurve(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | Which sketch should this path be added to? | Yes | | `data` | [`BezierData`](/docs/kcl/types/BezierData) | Data to draw a bezier curve. | Yes |
| `control1` | [`[number]`](/docs/kcl/types/number) | First control point for the cubic | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
| `control2` | [`[number]`](/docs/kcl/types/number) | Second control point for the cubic | Yes | | [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `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
@ -40,7 +36,11 @@ bezierCurve(
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [0, 10]) |> line(end = [0, 10])
|> bezierCurve(control1 = [5, 0], control2 = [5, 10], end = [10, 10]) |> bezierCurve({
to = [10, 10],
control1 = [5, 0],
control2 = [5, 10]
}, %)
|> line(endAbsolute = [10, 0]) |> line(endAbsolute = [10, 0])
|> close() |> close()

File diff suppressed because one or more lines are too long

View File

@ -42,10 +42,18 @@ extrude(
example = startSketchOn(XZ) example = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line(end = [10, 0]) |> line(end = [10, 0])
|> arc(angleStart = 120, angleEnd = 0, radius = 5) |> arc({
angleStart = 120,
angleEnd = 0,
radius = 5
}, %)
|> line(end = [5, 0]) |> line(end = [5, 0])
|> line(end = [0, 10]) |> line(end = [0, 10])
|> bezierCurve(control1 = [-10, 0], control2 = [2, 10], end = [-5, 10]) |> bezierCurve({
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)
@ -56,10 +64,18 @@ example = startSketchOn(XZ)
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([-10, 0], %) |> startProfileAt([-10, 0], %)
|> arc(angleStart = 120, angleEnd = -60, radius = 5) |> arc({
angleStart = 120,
angleEnd = -60,
radius = 5
}, %)
|> line(end = [10, 0]) |> line(end = [10, 0])
|> line(end = [5, 0]) |> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10]) |> bezierCurve({
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()
@ -72,10 +88,18 @@ example = extrude(exampleSketch, length = 10)
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([-10, 0], %) |> startProfileAt([-10, 0], %)
|> arc(angleStart = 120, angleEnd = -60, radius = 5) |> arc({
angleStart = 120,
angleEnd = -60,
radius = 5
}, %)
|> line(end = [10, 0]) |> line(end = [10, 0])
|> line(end = [5, 0]) |> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10]) |> bezierCurve({
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,10 +112,18 @@ example = extrude(exampleSketch, length = 20, symmetric = true)
```js ```js
exampleSketch = startSketchOn(XZ) exampleSketch = startSketchOn(XZ)
|> startProfileAt([-10, 0], %) |> startProfileAt([-10, 0], %)
|> arc(angleStart = 120, angleEnd = -60, radius = 5) |> arc({
angleStart = 120,
angleEnd = -60,
radius = 5
}, %)
|> line(end = [10, 0]) |> line(end = [10, 0])
|> line(end = [5, 0]) |> line(end = [5, 0])
|> bezierCurve(control1 = [-3, 0], control2 = [2, 10], end = [-5, 10]) |> bezierCurve({
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()

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -4,12 +4,8 @@ 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`.

View File

@ -9,7 +9,7 @@ Get the next adjacent edge to the edge given.
```js ```js
getNextAdjacentEdge(edge: TagIdentifier): Uuid getNextAdjacentEdge(tag: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getNextAdjacentEdge(edge: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the next adjacent edge of. | Yes | | [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the opposite edge to the edge given.
```js ```js
getOppositeEdge(edge: TagIdentifier): Uuid getOppositeEdge(tag: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getOppositeEdge(edge: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the opposite edge of. | Yes | | [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Get the previous adjacent edge to the edge given.
```js ```js
getPreviousAdjacentEdge(edge: TagIdentifier): Uuid getPreviousAdjacentEdge(tag: TagIdentifier): Uuid
``` ```
@ -17,7 +17,7 @@ getPreviousAdjacentEdge(edge: TagIdentifier): Uuid
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `edge` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The tag of the edge you want to find the previous adjacent edge of. | Yes | | [`tag`](/docs/kcl/types/tag) | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
### Returns ### Returns

File diff suppressed because one or more lines are too long

View File

@ -22,8 +22,18 @@ 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)
@ -32,24 +42,36 @@ 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)
* [`assertIs`](kcl/assertIs) * [`assertEqual`](kcl/assertEqual)
* [`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)
@ -73,7 +95,7 @@ layout: manual
* [`map`](kcl/map) * [`map`](kcl/map)
* [`max`](kcl/max) * [`max`](kcl/max)
* [`min`](kcl/min) * [`min`](kcl/min)
* [`offsetPlane`](kcl/std-offsetPlane) * [`offsetPlane`](kcl/offsetPlane)
* [`patternCircular2d`](kcl/patternCircular2d) * [`patternCircular2d`](kcl/patternCircular2d)
* [`patternCircular3d`](kcl/patternCircular3d) * [`patternCircular3d`](kcl/patternCircular3d)
* [`patternLinear2d`](kcl/patternLinear2d) * [`patternLinear2d`](kcl/patternLinear2d)
@ -109,14 +131,8 @@ layout: manual
* [`sweep`](kcl/sweep) * [`sweep`](kcl/sweep)
* [`tangentToEnd`](kcl/tangentToEnd) * [`tangentToEnd`](kcl/tangentToEnd)
* [`tangentialArc`](kcl/tangentialArc) * [`tangentialArc`](kcl/tangentialArc)
* [`toCentimeters`](kcl/std-toCentimeters) * [`toDegrees`](kcl/toDegrees)
* [`toDegrees`](kcl/std-toDegrees) * [`toRadians`](kcl/toRadians)
* [`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)
@ -137,14 +153,3 @@ 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)

View File

@ -34,7 +34,7 @@ int(num: number): number
```js ```js
n = int(ceil(5 / 2)) n = int(ceil(5 / 2))
assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3") assertEqual(n, 3, 0.0001, "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)

View File

@ -22,5 +22,3 @@ once fixed in engine will just start working here with no language changes.
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.

View File

@ -24,8 +24,8 @@ legAngX(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes | | `hypotenuse` | [`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 | | `leg` | [`number`](/docs/kcl/types/number) | | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legAngX(
### Examples ### Examples
```js ```js
legAngX(hypotenuse = 5, leg = 3) legAngX(5, 3)
``` ```

View File

@ -24,8 +24,8 @@ legAngY(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes | | `hypotenuse` | [`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 | | `leg` | [`number`](/docs/kcl/types/number) | | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legAngY(
### Examples ### Examples
```js ```js
legAngY(hypotenuse = 5, leg = 3) legAngY(5, 3)
``` ```

View File

@ -24,8 +24,8 @@ legLen(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `hypotenuse` | [`number`](/docs/kcl/types/number) | The length of the triangle's hypotenuse | Yes | | `hypotenuse` | [`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 | | `leg` | [`number`](/docs/kcl/types/number) | | Yes |
### Returns ### Returns
@ -35,7 +35,7 @@ legLen(
### Examples ### Examples
```js ```js
legLen(hypotenuse = 5, leg = 3) legLen(5, 3)
``` ```

View File

@ -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 ifelse. 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,211 +58,6 @@ 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 reusing geometry:
1. **Wrap the construction in a function** flexible and fully parametric.
2. **Duplicate an existing object with `clone`** lightningfast, 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 perinstance, youre 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.
---
## Modulelevel parallelism
Under the hood, the Design Studio runs **every module in parallel** where it can. This means:
- The toplevel 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.
- CPUbound calculations in separate modules get their own worker threads.
### Why modules beat onebigfile
If you shoehorn 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 // nonblocking
// ... 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 bytecode. 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 youve 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
@ -274,17 +69,25 @@ import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object. // Use `cube` just like a KCL object.
``` ```
```kcl ```norun
import "tests/inputs/cube.sldprt" as cube import "tests/inputs/cube-2.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 attribute. Likewise, you can also specify a coordinate system. E.g., by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
```kcl ```norun
@(unitLength = ft, coords = opengl) @(unitLength = ft, coords = opengl)
import "tests/inputs/cube.obj" import "tests/inputs/cube.obj"
``` ```
@ -307,55 +110,97 @@ 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
## Performance deepdive for foreignfile imports Parallelized foreign-file imports now let you overlap file reads, initialization,
Parallelized foreignfile 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 / Initialization) Stage** 1. **Import (Read) Stage**
```kcl ```norun
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.
- Starts engine rendering but **does not block** your script. - **Does _not_** start Engine rendering or block your script.
- This kickstarts the render pipeline while you keep executing other code.
2. **Invocation (Blocking) Stage** 2. **Initialization (Background Render) Stage**
```kcl ```norun
import "tests/inputs/cube.step" as cube import "tests/inputs/cube.step" as cube
cube myCube = cube // <- This line starts background rendering
|> translate(z=10) // ← blocks here only ```
- Invoking the imported symbol (assignment or plain call) triggers Engine rendering _in the background_.
- This kickstarts the render pipeline but doesnt block—you can continue other work while the Engine processes the model.
3. **Invocation (Blocking) Stage**
```norun
import "tests/inputs/cube.step" as cube
myCube = cube
myCube
|> translate(z=10) // <- This line blocks
``` ```
- Any method call (e.g., `translate`, `scale`, `rotate`) waits for the background render to finish before applying transformations. - 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.
### Best practices > **Nuance:** Foreign imports differ from pure KCL modules—calling the same import symbol multiple times (e.g., `screw` twice) starts background rendering twice.
#### 1. Defer blocking calls #### Best Practices
```kcl ##### 1. Defer Blocking Calls
import "tests/inputs/cube.step" as cube // 1) Read / Background render starts 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 here --- // --- perform other operations and calculations or setup here ---
cube myCube
|> translate(z=10) // 2) Blocks only here |> translate(z=10) // 3) Blocks only here
``` ```
#### 2. Split heavy work into separate modules ##### 2. Encapsulate Imports in Modules
Keep `main.kcl` free of reads and initialization; wrap them:
Place computationally expensive or IOheavy work into its own module so it can render in parallel while `main.kcl` continues. ```norun
// imports.kcl
#### Future improvements import "tests/inputs/cube.step" as cube // Read only
Upcoming releases will autoanalyse dependencies and only block when truly necessary. Until then, explicit deferral will give you the best performance.
export myCube = cube // Kick off rendering
```
```norun
// main.kcl
import myCube from "imports.kcl" // Import the initialized object
// ... computations ...
myCube
|> translate(z=10) // Blocking call at the end
```
##### 3. Avoid Immediate Method Calls
```norun
import "tests/inputs/cube.step" as cube
cube
|> translate(z=10) // Blocks immediately, negating parallelism
```
Both calling methods right on `cube` immediately or leaving an implicit import without assignment introduce blocking.
#### Future Improvements
Upcoming releases will autoanalyze dependencies and only block when truly necessary. Until then, explicit deferral and modular wrapping give you the best performance.

121
docs/kcl/offsetPlane.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -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: number(rad))) scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
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)
``` ```

View File

@ -10,11 +10,9 @@ Create a regular polygon with the specified number of sides that is either inscr
```js ```js
polygon( polygon(
data: PolygonData,
sketchSurfaceOrGroup: SketchOrSurface, sketchSurfaceOrGroup: SketchOrSurface,
radius: number, tag?: TagDeclarator,
numSides: u64,
center: [number],
inscribed?: bool,
): Sketch ): Sketch
``` ```
@ -23,11 +21,9 @@ polygon(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | Plane or surface to sketch on | Yes | | `data` | [`PolygonData`](/docs/kcl/types/PolygonData) | Data for drawing a polygon | Yes |
| `radius` | [`number`](/docs/kcl/types/number) | The radius of the polygon | Yes | | `sketchSurfaceOrGroup` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `numSides` | `u64` | The number of sides in the polygon | Yes | | [`tag`](/docs/kcl/types/tag) | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `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
@ -39,12 +35,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)
``` ```
@ -54,12 +50,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)
``` ```

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin value.
```js ```js
profileStart(profile: Sketch): [number] profileStart(sketch: Sketch): [number]
``` ```
@ -17,7 +17,7 @@ profileStart(profile: Sketch): [number]
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'x' value.
```js ```js
profileStartX(profile: Sketch): number profileStartX(sketch: Sketch): number
``` ```
@ -17,7 +17,7 @@ profileStartX(profile: Sketch): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
### Returns ### Returns

View File

@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'y' value.
```js ```js
profileStartY(profile: Sketch): number profileStartY(sketch: Sketch): number
``` ```
@ -17,7 +17,7 @@ profileStartY(profile: Sketch): number
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes | | `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
### Returns ### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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. | Yes | | `angleStart` | `number(Angle)` | Start angle (in degrees). | 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 |

View File

@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine( |> angledLine(
angle = 30, angle = 30,
length = 3 / cos(30deg), length = 3 / cos(toRadians(30)),
) )
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
|> close() |> close()

View File

@ -11,7 +11,7 @@ cartesian (x/y/z grid) coordinates.
```js ```js
polar( polar(
angle: number(rad), angle: number(Angle),
length: number(Length), length: number(Length),
): Point2d ): Point2d
``` ```
@ -21,7 +21,7 @@ polar(
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `angle` | `number(rad)` | | Yes | | `angle` | `number(Angle)` | | Yes |
| `length` | `number(Length)` | | Yes | | `length` | `number(Length)` | | Yes |
### Returns ### Returns

View File

@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine( |> angledLine(
angle = 50, angle = 50,
length = 15 / sin(135deg), length = 15 / sin(toRadians(135)),
) )
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
|> close() |> close()

View File

@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine( |> angledLine(
angle = 50, angle = 50,
length = 50 * tan((1/2): number(rad)), length = 50 * tan(1/2),
) )
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
|> close() |> close()

File diff suppressed because one or more lines are too long

View File

@ -1,27 +0,0 @@
---
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)`

File diff suppressed because one or more lines are too long

View File

@ -1,27 +0,0 @@
---
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)`

View File

@ -1,27 +0,0 @@
---
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)`

View File

@ -1,27 +0,0 @@
---
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)`

View File

@ -1,27 +0,0 @@
---
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)`

File diff suppressed because one or more lines are too long

View File

@ -1,27 +0,0 @@
---
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)`

File diff suppressed because it is too large Load Diff

View File

@ -73,7 +73,7 @@ rectangleExtrude = extrude(rectangleSketch, length = 10)
```js ```js
bottom = startSketchOn(XY) bottom = startSketchOn(XY)
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> arc(endAbsolute = [10, 10], interiorAbsolute = [5, 1], tag = $arc1) |> arcTo({ end = [10, 10], interior = [5, 1] }, %, $arc1)
|> angledLine(angle = tangentToEnd(arc1), length = 20) |> angledLine(angle = tangentToEnd(arc1), length = 20)
|> close() |> close()
``` ```

45
docs/kcl/toDegrees.md Normal file

File diff suppressed because one or more lines are too long

45
docs/kcl/toRadians.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Axis2d" title: "std::Axis2d"
excerpt: "An infinite line in 2d space." excerpt: "An infinite line in 2d space."
layout: manual layout: manual
--- ---

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Axis3d" title: "std::Axis3d"
excerpt: "An infinite line in 3d space." excerpt: "An infinite line in 3d space."
layout: manual layout: manual
--- ---

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Edge" title: "std::Edge"
excerpt: "The edge of a solid." excerpt: "The edge of a solid."
layout: manual layout: manual
--- ---

View File

@ -51,26 +51,6 @@ An extruded arc.
| `sourceRange` |`[integer, integer, integer]`| The source range. | No | | `sourceRange` |`[integer, integer, integer]`| The source range. | No |
----
An extruded involute.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `extrudeInvolute`| | No |
| `faceId` |[`string`](/docs/kcl/types/string)| The face id for the extrude surface. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `id` |[`string`](/docs/kcl/types/string)| The id of the geometry. | No |
| `sourceRange` |`[integer, integer, integer]`| The source range. | No |
---- ----
Geometry metadata. Geometry metadata.

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Face" title: "std::Face"
excerpt: "A face." excerpt: "A face."
layout: manual layout: manual
--- ---

View File

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

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Helix" title: "std::Helix"
excerpt: "A helix." excerpt: "A helix."
layout: manual layout: manual
--- ---

View File

@ -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 (interiorAbsolute kwarg) | No | | `p2` |`[number, number]`| Point 2 of the arc (interior kwarg) | No |
| `p3` |`[number, number]`| Point 3 of the arc (endAbsolute kwarg) | No | | `p3` |`[number, number]`| Point 3 of the arc (end 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 |
@ -241,31 +241,6 @@ A circular arc, not necessarily tangential to the current point.
---- ----
An involute of a circle of start_radius ending at end_radius
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CircularInvolute`| | No |
| `start_radius` |[`number`](/docs/kcl/types/number)| The radius of the base circle of the involute | No |
| `end_radius` |[`number`](/docs/kcl/types/number)| The radius that the involute ends at | No |
| `angle` |[`number`](/docs/kcl/types/number)| Angle about which the whole involute is rotated | No |
| `reverse` |`boolean`| If true, the path segment starts at the end radius and goes towards the start radius | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
| [`tag`](/docs/kcl/types/tag) |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Plane" title: "std::Plane"
excerpt: "A plane." excerpt: "A plane."
layout: manual layout: manual
--- ---

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Point2d" title: "std::Point2d"
excerpt: "A point in two dimensional space." excerpt: "A point in two dimensional space."
layout: manual layout: manual
--- ---

View File

@ -1,5 +1,5 @@
--- ---
title: "std::types::Point3d" title: "std::Point3d"
excerpt: "A point in three dimensional space." excerpt: "A point in three dimensional space."
layout: manual layout: manual
--- ---

File diff suppressed because one or more lines are too long

View File

@ -30,6 +30,8 @@ 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 |
---- ----
@ -51,6 +53,7 @@ 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 |

File diff suppressed because one or more lines are too long

View File

@ -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(

View File

@ -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, toolbar }, testInfo) => { async ({ page }, 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 toolbar.userSidebarButton.click() await page.getByTestId('user-sidebar-toggle').click()
// No space after "User settings" since it's textContent. // No space after "User settings" since it's textContent.
const text = const text =

View File

@ -1,70 +0,0 @@
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()
})
}
)
})

View File

@ -155,7 +155,7 @@ async function doBasicSketch(
|> xLine(length = -segLen(seg01))`) |> xLine(length = -segLen(seg01))`)
} }
test.describe('Basic sketch', () => { test.describe('Basic sketch', { tag: ['@skipWin'] }, () => {
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'])

View File

@ -8,126 +8,130 @@ 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('Can create sketches on all planes and their back sides', () => { test.describe(
const sketchOnPlaneAndBackSideTest = async ( 'Can create sketches on all planes and their back sides',
page: Page, { tag: ['@skipWin'] },
homePage: HomePageFixture, () => {
scene: SceneFixture, const sketchOnPlaneAndBackSideTest = async (
toolbar: ToolbarFixture, page: Page,
plane: string, homePage: HomePageFixture,
clickCoords: { x: number; y: number } scene: SceneFixture,
) => { toolbar: ToolbarFixture,
const u = await getUtils(page) plane: string,
await page.setBodyDimensions({ width: 1200, height: 500 }) clickCoords: { x: number; y: number }
) => {
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 code = `@settings(defaultLengthUnit = in)sketch001 = startSketchOn(${plane})profile001 = startProfileAt([0.91, -1.22], sketch001)` 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',
},
]
await u.openDebugPanel() for (const config of planeConfigs) {
test(config.plane, async ({ page, homePage, scene, toolbar }) => {
await u.clearCommandLogs() await sketchOnPlaneAndBackSideTest(
await page.getByRole('button', { name: 'Start Sketch' }).click() page,
homePage,
await u.sendCustomCmd(camCommand) scene,
await page.waitForTimeout(100) toolbar,
await u.sendCustomCmd(updateCamCommand) config.plane,
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
)
})
}
})

View File

@ -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', () => { test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
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,

View File

@ -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', () => { test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
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', () => {
await expect(commandLevelArgButton).toHaveText('level: project') await expect(commandLevelArgButton).toHaveText('level: project')
}) })
test('Command bar keybinding works from code editor and can change a setting', async ({ test(
page, 'Command bar keybinding works from code editor and can change a setting',
homePage, { tag: ['@skipWin'] },
}) => { 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(page.getByRole('option', { name: 'system' })).toHaveAttribute( await expect(
'data-headlessui-state', page.getByRole('option', { name: 'system' })
'active' ).toHaveAttribute('data-headlessui-state', '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,

View File

@ -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', '@macos', '@windows', '@skipLocalEngine'] }, { tag: ['@electron', '@skipLocalEngine'] },
async ({ page, context, scene, tronApp, cmdBar }, testInfo) => { async ({ page, context, scene, tronApp, cmdBar }, testInfo) => {
if (!tronApp) { if (!tronApp) {
fail() fail()

View File

@ -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', () => { test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
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('Can undo a sketch modification with ctrl+z', async ({ test(
page, 'Can undo a sketch modification with ctrl+z',
homePage, { tag: ['@skipWin'] },
editor, async ({ page, homePage, editor }) => {
}) => { const u = await getUtils(page)
const u = await getUtils(page) await page.addInitScript(async () => {
await page.addInitScript(async () => { localStorage.setItem(
localStorage.setItem( 'persistCode',
'persistCode', `@settings(defaultLengthUnit=in)
`@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`,

View File

@ -430,17 +430,5 @@ 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 =')
})
}) })
}) })

View File

@ -29,7 +29,7 @@ test.describe('integrations tests', () => {
) )
}) })
const [clickObj] = scene.makeMouseHelpers(726, 272) const [clickObj] = await scene.makeMouseHelpers(726, 272)
await test.step('setup test', async () => { await test.step('setup test', async () => {
await homePage.expectState({ await homePage.expectState({
@ -73,7 +73,7 @@ test.describe('integrations tests', () => {
}) })
await test.step('setup for next assertion', async () => { await test.step('setup for next assertion', async () => {
await toolbar.openFile('main.kcl') await toolbar.openFile('main.kcl')
await page.waitForTimeout(2000) await page.waitForTimeout(1000)
await clickObj() await clickObj()
await page.waitForTimeout(1000) await page.waitForTimeout(1000)
await scene.moveNoWhere() await scene.moveNoWhere()

View File

@ -18,7 +18,6 @@ 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'
@ -67,7 +66,6 @@ export interface Fixtures {
toolbar: ToolbarFixture toolbar: ToolbarFixture
scene: SceneFixture scene: SceneFixture
homePage: HomePageFixture homePage: HomePageFixture
signInPage: SignInPageFixture
} }
export class ElectronZoo { export class ElectronZoo {
@ -389,9 +387,6 @@ 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') {

View File

@ -1,48 +0,0 @@
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)
}
}

View File

@ -46,9 +46,6 @@ 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
@ -85,9 +82,6 @@ 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() {
@ -174,13 +168,6 @@ 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:' })

View File

@ -1,25 +1,6 @@
import type { import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'
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
@ -39,9 +20,8 @@ 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 '@skipLocalEngine' tags: test.tags, // e.g. '@snapshot' or '@skipWin'
// 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,
@ -55,7 +35,7 @@ class MyAPIReporter implements Reporter {
RUNNER_ARCH: process.env.RUNNER_ARCH || null, RUNNER_ARCH: process.env.RUNNER_ARCH || null,
} }
const request = (async () => { void (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',
@ -66,27 +46,18 @@ class MyAPIReporter implements Reporter {
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
if (response.ok) { if (!response.ok && !process.env.CI) {
const result = await response.json() console.error(
this.allResults.push(result) 'TAB API - Failed to send test result:',
if (result.block) { await response.text()
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 (error) { } catch {
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:', message) console.error('TAB API - Unable to send test result')
} }
} }
})() })()
this.pendingRequests.push(request)
} }
} }

View File

@ -63,18 +63,6 @@ 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',

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
{ {
tag: '@electron', tag: '@electron',
}, },
async ({ page, tronApp, scene }) => { async ({ page, tronApp }) => {
if (!tronApp) { if (!tronApp) {
fail() fail()
} }
@ -72,6 +72,7 @@ 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)
@ -79,7 +80,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 scene.connectionEstablished() await u.waitForPageLoad()
}) })
}) })
@ -106,10 +107,12 @@ 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()
@ -273,8 +276,6 @@ test.describe('Onboarding tests', () => {
page, page,
homePage, homePage,
tronApp, tronApp,
editor,
toolbar,
}) => { }) => {
if (!tronApp) { if (!tronApp) {
fail() fail()
@ -288,6 +289,7 @@ 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 })
@ -297,19 +299,18 @@ 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 toolbar.openPane('code') await expect(page.locator('#code-pane')).toBeVisible()
await editor.expectEditor.toContain(bracket, { await expect(u.codeLocator).toHaveText(bracketNoNewLines, {
shouldNormalise: true,
timeout: 10_000, timeout: 10_000,
}) })
// Mess with the code again // Mess with the code again
await editor.replaceCode('', badCode) await u.codeLocator.selectText()
await editor.expectEditor.toContain(badCode, { await u.codeLocator.fill(badCode)
shouldNormalise: true, await expect(u.codeLocator).toHaveText(badCode)
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()
@ -319,10 +320,7 @@ test.describe('Onboarding tests', () => {
}) })
// Check that the code has been reset // Check that the code has been reset
await editor.expectEditor.toContain(bracket, { await expect(u.codeLocator).toHaveText(bracketNoNewLines)
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
@ -331,7 +329,6 @@ 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,
}) => { }) => {
@ -363,7 +360,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 = toolbar.userSidebarButton.locator('img') const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img')
const onboardingOverlayLocator = page const onboardingOverlayLocator = page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
.locator('div') .locator('div')
@ -405,7 +402,6 @@ 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,
}) => { }) => {
@ -437,7 +433,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 = toolbar.userSidebarButton const sidebar = page.getByTestId('user-sidebar-toggle')
const avatar = sidebar.locator('img') const avatar = sidebar.locator('img')
const onboardingOverlayLocator = page const onboardingOverlayLocator = page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
@ -466,7 +462,6 @@ 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())
@ -505,7 +500,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 = toolbar.userSidebarButton const userMenuButton = page.getByTestId('user-sidebar-toggle')
const userMenuSettingsButton = page.getByRole('button', { const userMenuSettingsButton = page.getByRole('button', {
name: 'User settings', name: 'User settings',
}) })

View File

@ -6,7 +6,6 @@ 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'
@ -144,45 +143,28 @@ test.describe('Point-and-click assemblies tests', () => {
await scene.settled(cmdBar) await scene.settled(cmdBar)
}) })
await test.step('Insert a second time with the same name and expect error', async () => { await test.step('Insert a second time and expect error', async () => {
await toolbar.insertButton.click() // TODO: revisit once we have clone with #6209
await cmdBar.selectOption({ name: 'bracket.kcl' }).click() await insertPartIntoAssembly(
await cmdBar.expectState({ 'bracket.kcl',
stage: 'arguments', 'bracket',
currentArgKey: 'localName', toolbar,
currentArgValue: '', cmdBar,
headerArguments: { Path: 'bracket.kcl', LocalName: '' }, page
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 bracket2 import "bracket.kcl" as bracket
cylinder cylinder
bracket bracket
bracket2 bracket
`, `,
{ shouldNormalise: true } { shouldNormalise: true }
) )
// TODO: update once we have clone() with #6209 await scene.settled(cmdBar)
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
}) })
} }
) )
@ -279,7 +261,7 @@ test.describe('Point-and-click assemblies tests', () => {
highlightedHeaderArg: 'x', highlightedHeaderArg: 'x',
commandName: 'Translate', commandName: 'Translate',
}) })
await page.keyboard.insertText('100') await page.keyboard.insertText('5')
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.keyboard.insertText('0.1') await page.keyboard.insertText('0.1')
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -288,7 +270,7 @@ test.describe('Point-and-click assemblies tests', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
X: '100', X: '5',
Y: '0.1', Y: '0.1',
Z: '0.2', Z: '0.2',
}, },
@ -300,7 +282,7 @@ test.describe('Point-and-click assemblies tests', () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
` `
bracket bracket
|> translate(x = 100, y = 0.1, z = 0.2) |> translate(x = 5, y = 0.1, z = 0.2)
`, `,
{ shouldNormalise: true } { shouldNormalise: true }
) )
@ -349,7 +331,7 @@ test.describe('Point-and-click assemblies tests', () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
` `
bracket bracket
|> translate(x = 100, y = 0.1, z = 0.2) |> translate(x = 5, 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 }
@ -473,94 +455,4 @@ 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)
})
}
)
}) })

View File

@ -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', '@macos', '@windows'] }, { tag: '@electron' },
async ({ context, page }, testInfo) => { async ({ context, page }, testInfo) => {
let externalCreatedProjectName = 'external-created-project' let externalCreatedProjectName = 'external-created-project'
@ -400,6 +400,11 @@ 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')"
@ -774,9 +779,7 @@ 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', { const commandOption = page.getByRole('option', { name: 'rename project' })
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')
@ -836,9 +839,7 @@ 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', { const commandOption = page.getByRole('option', { name: 'delete project' })
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', {
@ -890,9 +891,7 @@ 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', { const commandOption = page.getByRole('option', { name: 'rename project' })
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', {
@ -948,9 +947,7 @@ 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', { const commandOption = page.getByRole('option', { name: 'delete project' })
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', {
@ -1815,8 +1812,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',
'broken-code-test.kcl', 'big_number_angle_to_match_length_x.kcl',
'circular_pattern3d_a_pattern.kcl', 'big_number_angle_to_match_length_y.kcl',
'close_arc.kcl', 'close_arc.kcl',
'computed_var.kcl', 'computed_var.kcl',
'cube-embedded.gltf', 'cube-embedded.gltf',
@ -1965,13 +1962,13 @@ test(
test( test(
'Settings persist across restarts', 'Settings persist across restarts',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page, toolbar }, testInfo) => { async ({ page, scene, cmdBar }, 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 toolbar.userSidebarButton.click() await page.getByTestId('user-sidebar-toggle').click()
await page.getByTestId('user-settings').click() await page.getByTestId('user-settings').click()
@ -1998,7 +1995,7 @@ test(
test( test(
'Original project name persist after onboarding', 'Original project name persist after onboarding',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page, toolbar }, testInfo) => { async ({ page }, testInfo) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -2010,7 +2007,7 @@ test(
}) })
await test.step('Should go through onboarding', async () => { await test.step('Should go through onboarding', async () => {
await toolbar.userSidebarButton.click() await page.getByTestId('user-sidebar-toggle').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()

View File

@ -1,3 +1,4 @@
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 */
@ -29,7 +30,7 @@ sketch003 = startSketchOn(XY)
extrude003 = extrude(sketch003, length = 20) extrude003 = extrude(sketch003, length = 20)
` `
test.describe('Prompt-to-edit tests', () => { test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
test.describe('Check the happy path, for basic changing color', () => { test.describe('Check the happy path, for basic changing color', () => {
const cases = [ const cases = [
{ {
@ -50,6 +51,7 @@ test.describe('Prompt-to-edit tests', () => {
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)
@ -198,6 +200,7 @@ test.describe('Prompt-to-edit tests', () => {
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) => {
@ -257,6 +260,7 @@ test.describe('Prompt-to-edit tests', () => {
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(

View File

@ -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', () => { test.describe('Regression tests', { tag: ['@skipWin'] }, () => {
// 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,18 +239,17 @@ extrude001 = extrude(sketch001, length = 50)
await expect(zooLogo).not.toHaveAttribute('href') await expect(zooLogo).not.toHaveAttribute('href')
}) })
test('Position _ Is Out Of Range... regression test', async ({ test(
context, 'Position _ Is Out Of Range... regression test',
page, { tag: ['@skipWin'] },
homePage, async ({ context, page, homePage }) => {
}) => { const u = await getUtils(page)
const u = await getUtils(page) // const PUR = 400 / 37.5 //pixeltoUnitRatio
// const PUR = 400 / 37.5 //pixeltoUnitRatio await page.setBodyDimensions({ width: 1200, height: 500 })
await page.setBodyDimensions({ width: 1200, height: 500 }) await context.addInitScript(async () => {
await context.addInitScript(async () => { localStorage.setItem(
localStorage.setItem( 'persistCode',
'persistCode', `exampleSketch = startSketchOn("XZ")
`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)
@ -259,55 +258,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] })
// error text on hover await expect(async () => {
await page.hover('.cm-lint-marker-error') await homePage.goToModelingScene()
await expect(page.getByText('Unexpected token: |').first()).toBeVisible() await u.waitForPageLoad()
// Okay execution finished, let's start editing text below the error. // error in guter
await u.codeLocator.click() await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
// Go to the end of the editor timeout: 1_000,
// This bug happens when there is a diagnostic in the editor and you try to })
// edit text below it. await page.waitForTimeout(200)
// Or delete a huge chunk of text and then try to edit below it. // expect it still to be there (sometimes it just clears for a bit?)
await page.keyboard.press('End') await expect(page.locator('.cm-lint-marker-error')).toBeVisible({
await page.keyboard.down('Shift') timeout: 1_000,
await page.keyboard.press('ArrowUp') })
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('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') // error text on hover
await page.keyboard.press('Enter') await page.hover('.cm-lint-marker-error')
await page.keyboard.type('thing: "blah"', { delay: 100 }) await expect(page.getByText('Unexpected token: |').first()).toBeVisible()
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowLeft')
await expect( // Okay execution finished, let's start editing text below the error.
page.locator('.cm-content') await u.codeLocator.click()
).toContainText(`exampleSketch = startSketchOn("XZ") // Go to the end of the editor
// This bug happens when there is a diagnostic in the editor and you try to
// edit text below it.
// Or delete a huge chunk of text and then try to edit below it.
await page.keyboard.press('End')
await page.keyboard.down('Shift')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('ArrowUp')
await page.keyboard.press('End')
await page.keyboard.up('Shift')
await page.keyboard.press('Backspace')
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
await page.keyboard.press('Enter')
await page.keyboard.press('Enter')
await page.keyboard.type('thing: "blah"', { delay: 100 })
await page.keyboard.press('Enter')
await page.keyboard.press('ArrowLeft')
await expect(
page.locator('.cm-content')
).toContainText(`exampleSketch = startSketchOn("XZ")
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> angledLine(angle = 50, length = 45 ) |> angledLine(angle = 50, length = 45 )
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
@ -315,8 +314,9 @@ 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,81 +486,82 @@ 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('ensure you CAN export while an export is already going', async ({ test(
page, 'ensure you CAN export while an export is already going',
homePage, { tag: ['@skipLinux', '@skipWin'] },
}) => { 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({ timeout: 15_000 }), expect(exportingToastMessage).not.toBeVisible(),
expect(errorToastMessage).not.toBeVisible(), expect(errorToastMessage).not.toBeVisible(),
expect(engineErrorToastMessage).not.toBeVisible(), expect(engineErrorToastMessage).not.toBeVisible(),
expect(successToastMessage).toBeVisible({ timeout: 15_000 }), expect(alreadyExportingToastMessage).not.toBeVisible(),
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`,

File diff suppressed because it is too large Load Diff

View File

@ -47,8 +47,9 @@ 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'] }, { tag: ['@snapshot', '@skipWin', '@skipMacos'] },
async ({ page, context, scene, cmdBar, tronApp }) => { async ({ page, context, scene, cmdBar, tronApp }) => {
test.fixme(orRunWhenFullSuiteEnabled())
if (!tronApp) { if (!tronApp) {
fail() fail()
} }
@ -376,6 +377,8 @@ 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')
}) })
@ -406,6 +409,7 @@ 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
@ -464,7 +468,9 @@ 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 toolbar.selectTangentialArc() await page
.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)
@ -528,6 +534,8 @@ 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
@ -573,6 +581,7 @@ 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
@ -619,7 +628,9 @@ test.describe(
'Client side scene scale should match engine scale', 'Client side scene scale should match engine scale',
{ tag: '@snapshot' }, { tag: '@snapshot' },
() => { () => {
test('Inch scale', async ({ page, cmdBar, scene, toolbar }) => { test.fixme(orRunWhenFullSuiteEnabled())
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
@ -653,7 +664,9 @@ test.describe(
|> xLine(length = 184.3)` |> xLine(length = 184.3)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await toolbar.selectTangentialArc() await page
.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
@ -667,8 +680,9 @@ 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
// it will be available directly in the toolbar since it was last equipped 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
@ -691,13 +705,7 @@ test.describe(
}) })
}) })
test('Millimeter scale', async ({ test('Millimeter scale', async ({ page, context, cmdBar, scene }) => {
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)
@ -750,7 +758,9 @@ test.describe(
|> xLine(length = 184.3)` |> xLine(length = 184.3)`
await expect(u.codeLocator).toHaveText(code) await expect(u.codeLocator).toHaveText(code)
await toolbar.selectTangentialArc() await page
.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
@ -763,7 +773,9 @@ 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 toolbar.tangentialArcBtn.click() await page
.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
@ -793,6 +805,8 @@ 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(
@ -851,6 +865,8 @@ 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(
@ -887,6 +903,8 @@ 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(
@ -926,6 +944,7 @@ 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')
@ -985,6 +1004,7 @@ 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')
@ -1006,6 +1026,7 @@ 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)
@ -1115,6 +1136,7 @@ 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(
@ -1132,7 +1154,11 @@ sweepPath = startSketchOn(XZ)
sweepSketch = startSketchOn(XY) sweepSketch = startSketchOn(XY)
|> startProfileAt([2, 0], %) |> startProfileAt([2, 0], %)
|> arc(angleStart = 0, angleEnd = 360, radius = 2) |> arc({
angleEnd = 360,
angleStart = 0,
radius = 2
}, %)
|> sweep(path = sweepPath) |> sweep(path = sweepPath)
|> appearance( |> appearance(
color = "#bb00ff", color = "#bb00ff",
@ -1177,7 +1203,11 @@ sweepPath = startSketchOn(XZ)
sweepSketch = startSketchOn(XY) sweepSketch = startSketchOn(XY)
|> startProfileAt([2, 0], %) |> startProfileAt([2, 0], %)
|> arc(angleStart = 0, angleEnd = 360, radius = 2) |> arc({
angleEnd = 360,
angleStart = 0,
radius = 2
}, %)
|> sweep(path = sweepPath) |> sweep(path = sweepPath)
|> appearance( |> appearance(
color = "#bb00ff", color = "#bb00ff",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 55 KiB

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