Compare commits
21 Commits
david/fix-
...
jtran/upda
Author | SHA1 | Date | |
---|---|---|---|
441e18e916 | |||
1502f923ee | |||
f03a684eec | |||
45e17c50e7 | |||
6bf74379a7 | |||
01c6fd53fa | |||
f8306c0275 | |||
900ef9e18d | |||
a46186573c | |||
90f6c1bb04 | |||
41d946b339 | |||
b7385d5f25 | |||
3d22f6cd66 | |||
9730e3f5b3 | |||
29d6b22d63 | |||
f99e44e371 | |||
8be36d3d16 | |||
bc1742af48 | |||
e4080cc184 | |||
18de6ccb59 | |||
f00ea4cf5e |
@ -19,6 +19,11 @@
|
||||
"plugin:jsx-a11y/recommended",
|
||||
"plugin:react-hooks/recommended"
|
||||
],
|
||||
"settings": {
|
||||
"react": {
|
||||
"version": "detect"
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||
"@typescript-eslint/no-array-constructor": "error",
|
||||
@ -61,6 +66,7 @@
|
||||
"jsx-a11y/click-events-have-key-events": "off",
|
||||
"jsx-a11y/no-autofocus": "off",
|
||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||
"react/no-unknown-property": "error",
|
||||
"no-restricted-globals": [
|
||||
"error",
|
||||
{
|
||||
|
11
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -26,8 +26,8 @@ max_retries=1
|
||||
# Retry failed tests, doing our own retries because using inbuilt Playwright retries causes connection issues
|
||||
while [[ $retry -le $max_retries ]]; do
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
status=$(jq -r '.status' test-results/.last-run.json)
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
echo "retried=true" >>$GITHUB_OUTPUT
|
||||
echo "run playwright with last failed tests and retry $retry"
|
||||
if [[ "$3" == *ubuntu* ]]; then
|
||||
@ -56,10 +56,11 @@ done
|
||||
echo "retried=false" >>$GITHUB_OUTPUT
|
||||
|
||||
if [[ -f "test-results/.last-run.json" ]]; then
|
||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
||||
if [[ $failed_tests -gt 0 ]]; then
|
||||
# If it still fails after 3 retries, then fail the job
|
||||
status=$(jq -r '.status' test-results/.last-run.json)
|
||||
if [[ "$status" == "failed" ]]; then
|
||||
# If it still fails after retries, then fail the job
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
7
.github/workflows/e2e-tests.yml
vendored
@ -225,7 +225,7 @@ jobs:
|
||||
uses: nick-fields/retry@v3.0.2
|
||||
with:
|
||||
shell: bash
|
||||
command: npm run test:snapshots || true
|
||||
command: npm run test:snapshots
|
||||
timeout_minutes: 5
|
||||
max_attempts: 5
|
||||
env:
|
||||
@ -285,8 +285,7 @@ jobs:
|
||||
# TODO: enable namespace-profile-windows-latest once available
|
||||
os:
|
||||
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
|
||||
# TODO: renable this when macoOS runner seem more stable
|
||||
# - namespace-profile-macos-6-cores
|
||||
- namespace-profile-macos-8-cores
|
||||
- windows-latest-8-cores
|
||||
shardIndex: [1, 2, 3, 4]
|
||||
shardTotal: [4]
|
||||
@ -296,7 +295,7 @@ jobs:
|
||||
isScheduled:
|
||||
- ${{ github.event_name == 'schedule' }}
|
||||
exclude:
|
||||
- os: namespace-profile-macos-6-cores
|
||||
- os: namespace-profile-macos-8-cores
|
||||
isScheduled: true
|
||||
- os: windows-latest-8-cores
|
||||
isScheduled: true
|
||||
|
40
docs/kcl/assertIs.md
Normal file
@ -4,8 +4,12 @@ excerpt: "Converts a number from centimeters to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from centimeters to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42cm`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in centimeters.
|
||||
|
||||
For example, if the current file uses inches, `fromCm(1)` will return `0.393701`. If the current file uses millimeters, `fromCm(1)` will return `10`. If the current file uses centimeters, `fromCm(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from feet to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from feet to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42ft`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in feet.
|
||||
|
||||
For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from inches to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from inches to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42inch`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in inches.
|
||||
|
||||
For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from meters to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from meters to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42m`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in meters.
|
||||
|
||||
For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from mm to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from mm to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42mm`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters.
|
||||
|
||||
For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`.
|
||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from yards to the current default unit."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
**WARNING:** This function is deprecated.
|
||||
|
||||
Converts a number from yards to the current default unit.
|
||||
|
||||
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42yd`) or the `to...` conversion functions.
|
||||
|
||||
No matter what units the current file uses, this function will always return a number equivalent to the input in yards.
|
||||
|
||||
For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`.
|
||||
|
@ -40,11 +40,7 @@ layout: manual
|
||||
* [`arc`](kcl/arc)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`assertEqual`](kcl/assertEqual)
|
||||
* [`assertGreaterThan`](kcl/assertGreaterThan)
|
||||
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
|
||||
* [`assertLessThan`](kcl/assertLessThan)
|
||||
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
|
||||
* [`assertIs`](kcl/assertIs)
|
||||
* [`atan`](kcl/atan)
|
||||
* [`atan2`](kcl/atan2)
|
||||
* [`bezierCurve`](kcl/bezierCurve)
|
||||
@ -55,12 +51,6 @@ layout: manual
|
||||
* [`extrude`](kcl/extrude)
|
||||
* [`fillet`](kcl/fillet)
|
||||
* [`floor`](kcl/floor)
|
||||
* [`fromCm`](kcl/fromCm)
|
||||
* [`fromFt`](kcl/fromFt)
|
||||
* [`fromInches`](kcl/fromInches)
|
||||
* [`fromM`](kcl/fromM)
|
||||
* [`fromMm`](kcl/fromMm)
|
||||
* [`fromYd`](kcl/fromYd)
|
||||
* [`getCommonEdge`](kcl/getCommonEdge)
|
||||
* [`getNextAdjacentEdge`](kcl/getNextAdjacentEdge)
|
||||
* [`getOppositeEdge`](kcl/getOppositeEdge)
|
||||
@ -120,8 +110,14 @@ layout: manual
|
||||
* [`sweep`](kcl/sweep)
|
||||
* [`tangentToEnd`](kcl/tangentToEnd)
|
||||
* [`tangentialArc`](kcl/tangentialArc)
|
||||
* [`toDegrees`](kcl/toDegrees)
|
||||
* [`toRadians`](kcl/toRadians)
|
||||
* [`toCentimeters`](kcl/std-toCentimeters)
|
||||
* [`toDegrees`](kcl/std-toDegrees)
|
||||
* [`toFeet`](kcl/std-toFeet)
|
||||
* [`toInches`](kcl/std-toInches)
|
||||
* [`toMeters`](kcl/std-toMeters)
|
||||
* [`toMillimeters`](kcl/std-toMillimeters)
|
||||
* [`toRadians`](kcl/std-toRadians)
|
||||
* [`toYards`](kcl/std-toYards)
|
||||
* [`translate`](kcl/translate)
|
||||
* [`union`](kcl/union)
|
||||
* [`xLine`](kcl/xLine)
|
||||
|
@ -34,7 +34,7 @@ int(num: number): number
|
||||
|
||||
```js
|
||||
n = int(ceil(5 / 2))
|
||||
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
||||
assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3")
|
||||
// Draw n cylinders.
|
||||
startSketchOn(XZ)
|
||||
|> circle(center = [0, 0], radius = 2)
|
||||
|
@ -22,3 +22,6 @@ once fixed in engine will just start working here with no language changes.
|
||||
chamfer cases work currently.
|
||||
|
||||
- **Appearance**: Changing the appearance on a loft does not work.
|
||||
Changing the appearance on an imported model does not work.
|
||||
|
||||
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.
|
||||
|
@ -174,7 +174,7 @@ t = 0.005 // taper factor [0-1)
|
||||
// Defines how to modify each layer of the vase.
|
||||
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||
fn transform(replicaId) {
|
||||
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8: number(rad)))
|
||||
return {
|
||||
translate = [0, 0, replicaId * 10],
|
||||
scale = [scale, scale, 0]
|
||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin value.
|
||||
|
||||
|
||||
```js
|
||||
profileStart(sketch: Sketch): [number]
|
||||
profileStart(profile: Sketch): [number]
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStart(sketch: Sketch): [number]
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'x' value.
|
||||
|
||||
|
||||
```js
|
||||
profileStartX(sketch: Sketch): number
|
||||
profileStartX(profile: Sketch): number
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStartX(sketch: Sketch): number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -9,7 +9,7 @@ Extract the provided 2-dimensional sketch's profile's origin's 'y' value.
|
||||
|
||||
|
||||
```js
|
||||
profileStartY(sketch: Sketch): number
|
||||
profileStartY(profile: Sketch): number
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ profileStartY(sketch: Sketch): number
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes |
|
||||
| `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
|
@ -26,7 +26,7 @@ helix(
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `revolutions` | `number(_)` | Number of revolutions. | Yes |
|
||||
| `angleStart` | `number(Angle)` | Start angle (in degrees). | Yes |
|
||||
| `angleStart` | `number(Angle)` | Start angle. | Yes |
|
||||
| `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||
| `radius` | `number(Length)` | Radius of the helix. | No |
|
||||
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 30,
|
||||
length = 3 / cos(toRadians(30)),
|
||||
length = 3 / cos(30deg),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
@ -11,7 +11,7 @@ cartesian (x/y/z grid) coordinates.
|
||||
|
||||
```js
|
||||
polar(
|
||||
angle: number(Angle),
|
||||
angle: number(rad),
|
||||
length: number(Length),
|
||||
): Point2d
|
||||
```
|
||||
@ -21,7 +21,7 @@ polar(
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `angle` | `number(Angle)` | | Yes |
|
||||
| `angle` | `number(rad)` | | Yes |
|
||||
| `length` | `number(Length)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 50,
|
||||
length = 15 / sin(toRadians(135)),
|
||||
length = 15 / sin(135deg),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine(
|
||||
angle = 50,
|
||||
length = 50 * tan(1/2),
|
||||
length = 50 * tan((1/2): number(rad)),
|
||||
)
|
||||
|> yLine(endAbsolute = 0)
|
||||
|> close()
|
||||
|
27
docs/kcl/std-toCentimeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toCentimeters"
|
||||
excerpt: "Convert a number to centimeters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to centimeters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toCentimeters(@num: number(cm)): number(cm)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(cm)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(cm)`
|
||||
|
||||
|
||||
|
44
docs/kcl/std-toDegrees.md
Normal file
27
docs/kcl/std-toFeet.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toFeet"
|
||||
excerpt: "Convert a number to feet from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to feet from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toFeet(@num: number(ft)): number(ft)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(ft)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(ft)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toInches.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toInches"
|
||||
excerpt: "Convert a number to inches from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to inches from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toInches(@num: number(in)): number(in)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(in)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(in)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toMeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toMeters"
|
||||
excerpt: "Convert a number to meters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to meters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toMeters(@num: number(m)): number(m)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(m)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(m)`
|
||||
|
||||
|
||||
|
27
docs/kcl/std-toMillimeters.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toMillimeters"
|
||||
excerpt: "Convert a number to millimeters from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Convert a number to millimeters from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toMillimeters(@num: number(mm)): number(mm)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(mm)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(mm)`
|
||||
|
||||
|
||||
|
44
docs/kcl/std-toRadians.md
Normal file
27
docs/kcl/std-toYards.md
Normal file
@ -0,0 +1,27 @@
|
||||
---
|
||||
title: "std::toYards"
|
||||
excerpt: "Converts a number to yards from its current units."
|
||||
layout: manual
|
||||
---
|
||||
|
||||
Converts a number to yards from its current units.
|
||||
|
||||
|
||||
|
||||
```js
|
||||
toYards(@num: number(yd)): number(yd)
|
||||
```
|
||||
|
||||
|
||||
### Arguments
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `num` | `number(yd)` | | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
`number(yd)`
|
||||
|
||||
|
||||
|
3496
docs/kcl/std.json
@ -31,7 +31,6 @@ A sketch type.
|
||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's Y axis be? | No |
|
||||
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
|
||||
|
||||
|
||||
----
|
||||
|
@ -29,11 +29,11 @@ test.describe('Electron app header tests', () => {
|
||||
test(
|
||||
'User settings has correct shortcut',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
// Open the user sidebar menu.
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
|
||||
// No space after "User settings" since it's textContent.
|
||||
const text =
|
||||
|
53
e2e/playwright/auth.spec.ts
Normal file
@ -0,0 +1,53 @@
|
||||
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()
|
||||
|
||||
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('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 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 })
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
|
||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
|
||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||
|
||||
@ -66,6 +67,7 @@ export interface Fixtures {
|
||||
toolbar: ToolbarFixture
|
||||
scene: SceneFixture
|
||||
homePage: HomePageFixture
|
||||
signInPage: SignInPageFixture
|
||||
}
|
||||
|
||||
export class ElectronZoo {
|
||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
|
||||
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||
await use(new HomePageFixture(page))
|
||||
},
|
||||
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||
await use(new SignInPageFixture(page))
|
||||
},
|
||||
}
|
||||
|
||||
if (process.env.PLATFORM === 'web') {
|
||||
|
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
@ -0,0 +1,48 @@
|
||||
import type { Locator, Page } from '@playwright/test'
|
||||
import { secrets } from '@e2e/playwright/secrets'
|
||||
|
||||
export class SignInPageFixture {
|
||||
public page: Page
|
||||
|
||||
signInButton!: Locator
|
||||
cancelSignInButton!: Locator
|
||||
userCode!: Locator
|
||||
|
||||
apiBaseUrl!: string
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
|
||||
this.signInButton = this.page.getByTestId('sign-in-button')
|
||||
this.cancelSignInButton = this.page.getByTestId('cancel-sign-in-button')
|
||||
this.userCode = this.page.getByTestId('sign-in-user-code')
|
||||
|
||||
// TODO: set this thru env var
|
||||
this.apiBaseUrl = 'https://api.dev.zoo.dev'
|
||||
}
|
||||
|
||||
async verifyAndConfirmAuth(userCode: string) {
|
||||
// Device flow: stolen from the tauri days
|
||||
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
|
||||
const headers = {
|
||||
Authorization: `Bearer ${secrets.token}`,
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
const verifyUrl = `${this.apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
|
||||
console.log(`GET ${verifyUrl}`)
|
||||
const vr = await fetch(verifyUrl, { headers })
|
||||
console.log(vr.status)
|
||||
|
||||
// Device flow: confirm
|
||||
const confirmUrl = `${this.apiBaseUrl}/oauth2/device/confirm`
|
||||
const data = JSON.stringify({ user_code: userCode })
|
||||
console.log(`POST ${confirmUrl} ${data}`)
|
||||
const cr = await fetch(confirmUrl, {
|
||||
headers,
|
||||
method: 'POST',
|
||||
body: data,
|
||||
})
|
||||
console.log(cr.status)
|
||||
}
|
||||
}
|
@ -46,6 +46,9 @@ export class ToolbarFixture {
|
||||
gizmo!: Locator
|
||||
gizmoDisabled!: Locator
|
||||
loadButton!: Locator
|
||||
/** User button for the user sidebar menu */
|
||||
userSidebarButton!: Locator
|
||||
signOutButton!: Locator
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page
|
||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
|
||||
// element or two different elements can represent these states.
|
||||
this.gizmo = page.getByTestId('gizmo')
|
||||
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||
|
||||
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
|
||||
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
|
||||
}
|
||||
|
||||
get logoLink() {
|
||||
|
@ -1,6 +1,25 @@
|
||||
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'
|
||||
import type {
|
||||
Reporter,
|
||||
TestCase,
|
||||
TestResult,
|
||||
FullResult,
|
||||
} from '@playwright/test/reporter'
|
||||
|
||||
class MyAPIReporter implements Reporter {
|
||||
private pendingRequests: Promise<void>[] = []
|
||||
private allResults: Record<string, any>[] = []
|
||||
private blockingResults: Record<string, any>[] = []
|
||||
|
||||
async onEnd(result: FullResult): Promise<void> {
|
||||
await Promise.all(this.pendingRequests)
|
||||
if (this.allResults.length > 0 && this.blockingResults.length === 0) {
|
||||
result.status = 'passed'
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Marked failures as non-blocking')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onTestEnd(test: TestCase, result: TestResult): void {
|
||||
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
|
||||
return
|
||||
@ -20,6 +39,7 @@ class MyAPIReporter implements Reporter {
|
||||
platform: process.env.RUNNER_OS || process.platform,
|
||||
// Extra test and result data
|
||||
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
|
||||
id: test.id, // computed file/test/project ID used for reruns
|
||||
retry: result.retry,
|
||||
tags: test.tags, // e.g. '@snapshot' or '@skipWin'
|
||||
// Extra environment variables
|
||||
@ -35,7 +55,7 @@ class MyAPIReporter implements Reporter {
|
||||
RUNNER_ARCH: process.env.RUNNER_ARCH || null,
|
||||
}
|
||||
|
||||
void (async () => {
|
||||
const request = (async () => {
|
||||
try {
|
||||
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
|
||||
method: 'POST',
|
||||
@ -46,18 +66,27 @@ class MyAPIReporter implements Reporter {
|
||||
body: JSON.stringify(payload),
|
||||
})
|
||||
|
||||
if (!response.ok && !process.env.CI) {
|
||||
console.error(
|
||||
'TAB API - Failed to send test result:',
|
||||
await response.text()
|
||||
)
|
||||
if (response.ok) {
|
||||
const result = await response.json()
|
||||
this.allResults.push(result)
|
||||
if (result.block) {
|
||||
this.blockingResults.push(result)
|
||||
}
|
||||
} else {
|
||||
const error = await response.json()
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Failed to send test result:', error)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
if (!process.env.CI) {
|
||||
console.error('TAB API - Unable to send test result')
|
||||
console.error('TAB API - Unable to send test result:', message)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
this.pendingRequests.push(request)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -409,11 +409,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
)
|
||||
.toBe(true)
|
||||
})
|
||||
test('Home.Help.Refresh and report a bug', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
}) => {
|
||||
test('Home.Help.Report a bug', async ({ tronApp, cmdBar, page }) => {
|
||||
if (!tronApp) fail()
|
||||
// Run electron snippet to find the Menu!
|
||||
await page.waitForTimeout(100) // wait for createModelingPageMenu() to run
|
||||
@ -424,9 +420,8 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
if (!app || !app.applicationMenu) {
|
||||
return false
|
||||
}
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.Refresh and report a bug'
|
||||
)
|
||||
const menu =
|
||||
app.applicationMenu.getMenuItemById('Help.Report a bug')
|
||||
if (!menu) return false
|
||||
menu.click()
|
||||
return true
|
||||
@ -2291,7 +2286,7 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
if (!menu) fail()
|
||||
})
|
||||
})
|
||||
test('Modeling.Help.Refresh and report a bug', async ({
|
||||
test('Modeling.Help.Report a bug', async ({
|
||||
tronApp,
|
||||
cmdBar,
|
||||
page,
|
||||
@ -2315,9 +2310,8 @@ test.describe('Native file menu', { tag: ['@electron'] }, () => {
|
||||
async () =>
|
||||
await tronApp.electron.evaluate(async ({ app }) => {
|
||||
if (!app || !app.applicationMenu) return false
|
||||
const menu = app.applicationMenu.getMenuItemById(
|
||||
'Help.Refresh and report a bug'
|
||||
)
|
||||
const menu =
|
||||
app.applicationMenu.getMenuItemById('Help.Report a bug')
|
||||
if (!menu) return false
|
||||
menu.click()
|
||||
return true
|
||||
|
@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
|
||||
{
|
||||
tag: '@electron',
|
||||
},
|
||||
async ({ page, tronApp }) => {
|
||||
async ({ page, tronApp, scene }) => {
|
||||
if (!tronApp) {
|
||||
fail()
|
||||
}
|
||||
@ -72,7 +72,6 @@ test.describe('Onboarding tests', () => {
|
||||
onboarding_status: '',
|
||||
},
|
||||
})
|
||||
const u = await getUtils(page)
|
||||
|
||||
const viewportSize = { width: 1200, height: 500 }
|
||||
await page.setBodyDimensions(viewportSize)
|
||||
@ -80,7 +79,7 @@ test.describe('Onboarding tests', () => {
|
||||
await test.step(`Create a project and open to the onboarding`, async () => {
|
||||
await createProject({ name: 'project-link', page })
|
||||
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
|
||||
await u.waitForPageLoad()
|
||||
await scene.connectionEstablished()
|
||||
})
|
||||
})
|
||||
|
||||
@ -332,6 +331,7 @@ test.describe('Onboarding tests', () => {
|
||||
test('Avatar text updates depending on image load success', async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
homePage,
|
||||
tronApp,
|
||||
}) => {
|
||||
@ -363,7 +363,7 @@ test.describe('Onboarding tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img')
|
||||
const avatarLocator = toolbar.userSidebarButton.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
.locator('div')
|
||||
@ -405,6 +405,7 @@ test.describe('Onboarding tests', () => {
|
||||
test("Avatar text doesn't mention avatar when no avatar", async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
homePage,
|
||||
tronApp,
|
||||
}) => {
|
||||
@ -436,7 +437,7 @@ test.describe('Onboarding tests', () => {
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Test that the text in this step is correct
|
||||
const sidebar = page.getByTestId('user-sidebar-toggle')
|
||||
const sidebar = toolbar.userSidebarButton
|
||||
const avatar = sidebar.locator('img')
|
||||
const onboardingOverlayLocator = page
|
||||
.getByTestId('onboarding-content')
|
||||
@ -465,6 +466,7 @@ test.describe('Onboarding tests', () => {
|
||||
test('Restarting onboarding on desktop takes one attempt', async ({
|
||||
context,
|
||||
page,
|
||||
toolbar,
|
||||
tronApp,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
@ -503,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
|
||||
.filter({ hasText: 'Tutorial Project 00' })
|
||||
const tutorialModalText = page.getByText('Welcome to Design Studio!')
|
||||
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
|
||||
const userMenuButton = page.getByTestId('user-sidebar-toggle')
|
||||
const userMenuButton = toolbar.userSidebarButton
|
||||
const userMenuSettingsButton = page.getByRole('button', {
|
||||
name: 'User settings',
|
||||
})
|
||||
|
@ -278,7 +278,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
highlightedHeaderArg: 'x',
|
||||
commandName: 'Translate',
|
||||
})
|
||||
await page.keyboard.insertText('5')
|
||||
await page.keyboard.insertText('100')
|
||||
await cmdBar.progressCmdBar()
|
||||
await page.keyboard.insertText('0.1')
|
||||
await cmdBar.progressCmdBar()
|
||||
@ -287,7 +287,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await cmdBar.expectState({
|
||||
stage: 'review',
|
||||
headerArguments: {
|
||||
X: '5',
|
||||
X: '100',
|
||||
Y: '0.1',
|
||||
Z: '0.2',
|
||||
},
|
||||
@ -299,7 +299,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`
|
||||
bracket
|
||||
|> translate(x = 5, y = 0.1, z = 0.2)
|
||||
|> translate(x = 100, y = 0.1, z = 0.2)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
)
|
||||
@ -348,7 +348,7 @@ test.describe('Point-and-click assemblies tests', () => {
|
||||
await editor.expectEditor.toContain(
|
||||
`
|
||||
bracket
|
||||
|> translate(x = 5, y = 0.1, z = 0.2)
|
||||
|> translate(x = 100, y = 0.1, z = 0.2)
|
||||
|> rotate(roll = 0.1, pitch = 0.2, yaw = 0.3)
|
||||
`,
|
||||
{ shouldNormalise: true }
|
||||
|
@ -400,11 +400,6 @@ test(
|
||||
await expect(page.getByText('broken-code')).toBeVisible()
|
||||
await page.getByText('broken-code').click()
|
||||
|
||||
// Gotcha: You can not use scene.settled() since the KCL code is going to fail
|
||||
await expect(
|
||||
page.getByTestId('model-state-indicator-playing')
|
||||
).toBeAttached()
|
||||
|
||||
// Gotcha: Scroll to the text content in code mirror because CodeMirror lazy loads DOM content
|
||||
await editor.scrollToText(
|
||||
"|> line(end = [0, wallMountL], tag = 'outerEdge')"
|
||||
@ -779,7 +774,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'rename project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `untitled`
|
||||
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
|
||||
@ -839,7 +836,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'delete project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
@ -891,7 +890,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'rename project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'rename project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const projectRenamedName = `untitled`
|
||||
const commandContinueButton = page.getByRole('button', {
|
||||
@ -947,7 +948,9 @@ test.describe(`Project management commands`, () => {
|
||||
// Constants and locators
|
||||
const projectHomeLink = page.getByTestId('project-link')
|
||||
const commandButton = page.getByRole('button', { name: 'Commands' })
|
||||
const commandOption = page.getByRole('option', { name: 'delete project' })
|
||||
const commandOption = page.getByRole('option', {
|
||||
name: 'delete project',
|
||||
})
|
||||
const projectNameOption = page.getByRole('option', { name: projectName })
|
||||
const commandWarning = page.getByText('Are you sure you want to delete?')
|
||||
const commandSubmitButton = page.getByRole('button', {
|
||||
@ -1962,13 +1965,13 @@ test(
|
||||
test(
|
||||
'Settings persist across restarts',
|
||||
{ tag: '@electron' },
|
||||
async ({ page, scene, cmdBar }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
await test.step('We can change a user setting like theme', async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
page.on('console', console.log)
|
||||
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
|
||||
await page.getByTestId('user-settings').click()
|
||||
|
||||
@ -1995,7 +1998,7 @@ test(
|
||||
test(
|
||||
'Original project name persist after onboarding',
|
||||
{ tag: '@electron' },
|
||||
async ({ page }, testInfo) => {
|
||||
async ({ page, toolbar }, testInfo) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
@ -2007,7 +2010,7 @@ test(
|
||||
})
|
||||
|
||||
await test.step('Should go through onboarding', async () => {
|
||||
await page.getByTestId('user-sidebar-toggle').click()
|
||||
await toolbar.userSidebarButton.click()
|
||||
await page.getByTestId('user-settings').click()
|
||||
await page.getByRole('button', { name: 'Replay Onboarding' }).click()
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
|
||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||
|
||||
/* eslint-disable jest/no-conditional-expect */
|
||||
@ -51,7 +50,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
await context.addInitScript((file) => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
@ -200,7 +198,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const body1CapCoords = { x: 571, y: 311 }
|
||||
|
||||
await context.addInitScript((file) => {
|
||||
@ -260,7 +257,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
page,
|
||||
scene,
|
||||
}) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const body1CapCoords = { x: 571, y: 311 }
|
||||
const body2WallCoords = { x: 620, y: 152 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
|
@ -3295,7 +3295,7 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
|
||||
)
|
||||
await editor.expectState({
|
||||
activeLines: [],
|
||||
diagnostics: ['memoryitemkey`badBadBadFn`isnotdefined'],
|
||||
diagnostics: ['`badBadBadFn`isnotdefined'],
|
||||
highlightedCode: '',
|
||||
})
|
||||
await expect(
|
||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
@ -36,7 +36,6 @@ export const headerMasks = (page: Page) => [
|
||||
]
|
||||
|
||||
export const networkingMasks = (page: Page) => [
|
||||
page.getByTestId('model-state-indicator'),
|
||||
page.getByTestId('network-toggle'),
|
||||
]
|
||||
|
||||
@ -85,12 +84,6 @@ async function waitForPageLoadWithRetry(page: Page) {
|
||||
await expect(async () => {
|
||||
await page.goto('/')
|
||||
const errorMessage = 'App failed to load - 🔃 Retrying ...'
|
||||
await expect(
|
||||
page.getByTestId('model-state-indicator-playing'),
|
||||
errorMessage
|
||||
).toBeAttached({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'sketch Start Sketch' }),
|
||||
@ -103,11 +96,6 @@ async function waitForPageLoadWithRetry(page: Page) {
|
||||
|
||||
// lee: This needs to be replaced by scene.settled() eventually.
|
||||
async function waitForPageLoad(page: Page) {
|
||||
// wait for all spinners to be gone
|
||||
await expect(page.getByTestId('model-state-indicator-playing')).toBeVisible({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
||||
await expect(page.getByRole('button', { name: 'Start Sketch' })).toBeEnabled({
|
||||
timeout: 20_000,
|
||||
})
|
||||
|
@ -181,7 +181,6 @@ test.describe('Testing settings', () => {
|
||||
})
|
||||
|
||||
test('Project and user settings can be reset', async ({ page, homePage }) => {
|
||||
test.fixme(orRunWhenFullSuiteEnabled())
|
||||
const u = await getUtils(page)
|
||||
await test.step(`Setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
@ -25,10 +25,10 @@ shelfMountingHolePlacementOffset = shelfMountingHoleDiameter * 1.5
|
||||
wallMountingHolePlacementOffset = wallMountingHoleDiameter * 1.5
|
||||
|
||||
// Add checks to ensure bracket is possible. These make sure that there is adequate distance between holes and edges.
|
||||
assertGreaterThanOrEq(wallMountLength, wallMountingHoleDiameter * 3, "Holes not possible. Either decrease hole diameter or increase wallMountLength")
|
||||
assertGreaterThanOrEq(shelfMountLength, shelfMountingHoleDiameter * 5.5, "wallMountLength must be longer for hole sizes to work. Either decrease mounting hole diameters or increase shelfMountLength")
|
||||
assertGreaterThanOrEq(width, shelfMountingHoleDiameter * 5.5, "Holes not possible. Either decrease hole diameter or increase width")
|
||||
assertGreaterThanOrEq(width, wallMountingHoleDiameter * 5.5, "Holes not possible. Either decrease hole diameter or increase width")
|
||||
assert(wallMountLength, isGreaterThanOrEqual = wallMountingHoleDiameter * 3, error = "Holes not possible. Either decrease hole diameter or increase wallMountLength")
|
||||
assert(shelfMountLength, isGreaterThanOrEqual = shelfMountingHoleDiameter * 5.5, error = "wallMountLength must be longer for hole sizes to work. Either decrease mounting hole diameters or increase shelfMountLength")
|
||||
assert(width, isGreaterThanOrEqual = shelfMountingHoleDiameter * 5.5, error = "Holes not possible. Either decrease hole diameter or increase width")
|
||||
assert(width, isGreaterThanOrEqual = wallMountingHoleDiameter * 5.5, error = "Holes not possible. Either decrease hole diameter or increase width")
|
||||
|
||||
// Create the body of the bracket
|
||||
bracketBody = startSketchOn(XZ)
|
||||
|
@ -18,7 +18,7 @@ topTotalThickness = totalThickness - (bottomThickness + baseThickness)
|
||||
nHoles = 4
|
||||
|
||||
// Add assertion so nHoles are always greater than 1
|
||||
assertGreaterThan(nHoles, 1, "nHoles must be greater than 1")
|
||||
assert(nHoles, isGreaterThan = 1, error = "nHoles must be greater than 1")
|
||||
|
||||
// Create the circular pattern for the mounting holes
|
||||
circles = startSketchOn(XY)
|
||||
|
@ -33,11 +33,11 @@ invas = map(angles, fn(a) {
|
||||
|
||||
// Map the involute curve
|
||||
xs = map([0..cmo], fn(i) {
|
||||
return rs[i] * cos(invas[i])
|
||||
return rs[i] * cos(invas[i]: number(rad))
|
||||
})
|
||||
|
||||
ys = map([0..cmo], fn(i) {
|
||||
return rs[i] * sin(invas[i])
|
||||
return rs[i] * sin(invas[i]: number(rad))
|
||||
})
|
||||
|
||||
// Extrude the gear body
|
||||
|
@ -22,8 +22,8 @@ lSegments = totalLength / lbumps
|
||||
wSegments = totalWidth / wbumps
|
||||
|
||||
// Add assertions to ensure that the number of bumps are greater than 1
|
||||
assertGreaterThan(lbumps, 1, "lbumps must be greater than 1")
|
||||
assertGreaterThan(wbumps, 1, "wbumps must be greater than 1")
|
||||
assert(lbumps, isGreaterThan = 1, error = "lbumps must be greater than 1")
|
||||
assert(wbumps, isGreaterThan = 1, error = "wbumps must be greater than 1")
|
||||
|
||||
// Make the base
|
||||
base = startSketchOn(XY)
|
||||
|
@ -29,7 +29,7 @@ sketch001 = startSketchOn(XZ)
|
||||
)
|
||||
|> yLine(endAbsolute = -templateThickness, tag = $seg03)
|
||||
|> xLine(length = templateThickness, tag = $seg07)
|
||||
|> yLine(endAbsolute = (segEndY(seg01) + templateThickness) / 2 - templateThickness, tag = $seg02)
|
||||
|> yLine(endAbsolute = (segEndY(seg01) + templateThickness) / 2_ - templateThickness, tag = $seg02)
|
||||
|> xLine(endAbsolute = segEndX(seg03) + minClampingDistance, tag = $seg06)
|
||||
|> yLine(length = templateThickness * 2, tag = $seg08)
|
||||
|> xLine(endAbsolute = segEndX(seg02) + 0, tag = $seg05)
|
||||
|
20
rust/Cargo.lock
generated
@ -1792,7 +1792,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1803,7 +1803,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-derive-docs"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"anyhow",
|
||||
@ -1822,7 +1822,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-directory-test-macro"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -1831,7 +1831,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1852,7 +1852,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
@ -1872,7 +1872,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-lib"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"approx 0.5.1",
|
||||
@ -1943,7 +1943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-python-bindings"
|
||||
version = "0.3.62"
|
||||
version = "0.3.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"kcl-lib",
|
||||
@ -1958,7 +1958,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"hyper 0.14.32",
|
||||
@ -1971,7 +1971,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-to-core"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1985,7 +1985,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "kcl-wasm-lib"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
dependencies = [
|
||||
"bson",
|
||||
"console_error_panic_hook",
|
||||
|
@ -1,7 +1,7 @@
|
||||
|
||||
[package]
|
||||
name = "kcl-bumper"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/KittyCAD/modeling-api"
|
||||
rust-version = "1.76"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-derive-docs"
|
||||
description = "A tool for generating documentation from Rust derive macros"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-directory-test-macro"
|
||||
description = "A tool for generating tests from a directory of kcl files"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "kcl-language-server-release"
|
||||
version = "0.1.62"
|
||||
version = "0.1.63"
|
||||
edition = "2021"
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
publish = false
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "kcl-language-server"
|
||||
description = "A language server for KCL."
|
||||
authors = ["KittyCAD Inc <kcl@kittycad.io>"]
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "kcl-lib"
|
||||
description = "KittyCAD Language implementation and tools"
|
||||
version = "0.2.62"
|
||||
version = "0.2.63"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/KittyCAD/modeling-app"
|
||||
|
@ -19,6 +19,30 @@ We've built a lot of tooling to make contributing to KCL easier. If you are inte
|
||||
11. Run `just redo-kcl-stdlib-docs` to generate new Markdown documentation for your function that will be used [to generate docs on our website](https://zoo.dev/docs/kcl).
|
||||
12. Create a PR in GitHub.
|
||||
|
||||
## Making a Simulation Test
|
||||
|
||||
If you have KCL code that you want to test, simulation tests are the preferred way to do that.
|
||||
|
||||
Make a new sim test. Replace `foo_bar` with the snake case name of your test. The name needs to be unique.
|
||||
|
||||
```shell
|
||||
just new-sim-test foo_bar
|
||||
```
|
||||
|
||||
It will show the commands it ran, including the path to a new file `foo_bar/input.kcl`. Edit that with your KCL. If you need additional KCL files to import, include them in this directory.
|
||||
|
||||
Then run it.
|
||||
|
||||
```shell
|
||||
just overwrite-sim-test foo_bar
|
||||
```
|
||||
|
||||
The above should create a bunch of output files in the same directory.
|
||||
|
||||
Make sure you actually look at them. Specifically, if there's an `execution_error.snap`, it means the execution failed. Depending on the test, this may be what you expect. But if it's not, delete the snap file and run it again.
|
||||
|
||||
When it looks good, commit all the files, including `input.kcl`, generated output files in the test directory, and changes to `simulation_tests.rs`.
|
||||
|
||||
## Bumping the version
|
||||
|
||||
If you bump the version of kcl-lib and push it to crates, be sure to update the repos we own that use it as well. These are:
|
||||
|
@ -4,7 +4,7 @@ const arr = [0, 0, 0, 10]
|
||||
const i = 3
|
||||
const ten = arr[i]
|
||||
|
||||
assertEqual(ten, 10, 0.000001, "oops")
|
||||
assert(ten, isEqualTo = 10, error = "oops")
|
||||
|
||||
const p = "foo"
|
||||
const obj = {
|
||||
@ -13,4 +13,4 @@ const obj = {
|
||||
}
|
||||
const one = obj[p]
|
||||
|
||||
assertEqual(one, 1, 0.0000001, "oops")
|
||||
assert(one, isEqualTo = 1, error = "oops")
|
||||
|
@ -1,25 +1,25 @@
|
||||
import identity from "identity.kcl"
|
||||
|
||||
answer = identity(42)
|
||||
assertEqual(answer, 42, 0.0001, "identity")
|
||||
assert(answer, isEqualTo = 42, error = "identity")
|
||||
|
||||
import identity as id from "identity.kcl"
|
||||
|
||||
answer43 = id(43)
|
||||
assertEqual(answer43, 43, 0.0001, "identity")
|
||||
assert(answer43, isEqualTo = 43, error = "identity")
|
||||
|
||||
import increment, decrement from "numbers.kcl"
|
||||
|
||||
answer3 = increment(2)
|
||||
assertEqual(answer3, 3, 0.0001, "increment")
|
||||
assert(answer3, isEqualTo = 3, error = "increment")
|
||||
|
||||
answer5 = decrement(6)
|
||||
assertEqual(answer5, 5, 0.0001, "decrement")
|
||||
assert(answer5, isEqualTo = 5, error = "decrement")
|
||||
|
||||
import increment as inc, decrement as dec from "numbers.kcl"
|
||||
|
||||
answer4 = inc(3)
|
||||
assertEqual(answer4, 4, 0.0001, "inc")
|
||||
assert(answer4, isEqualTo = 4, error = "inc")
|
||||
|
||||
answer6 = dec(7)
|
||||
assertEqual(answer6, 6, 0.0001, "dec")
|
||||
assert(answer6, isEqualTo = 6, error = "dec")
|
||||
|
@ -5,7 +5,7 @@ const t = 0.005 // taper factor [0-1)
|
||||
// Defines how to modify each layer of the vase.
|
||||
// Each replica is shifted up the Z axis, and has a smoothly-varying radius
|
||||
fn transform = (replicaId) => {
|
||||
let scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||
let scale = r * abs(1 - (t * replicaId)) * (5 + cos((replicaId / 8): number(rad)))
|
||||
return {
|
||||
translate: [0, 0, replicaId * 10],
|
||||
scale: [scale, scale, 0],
|
||||
|
@ -180,10 +180,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Reset to the default units. Modules assume the engine starts in the
|
||||
// default state.
|
||||
self.set_units(Default::default(), source_range, id_generator).await?;
|
||||
|
||||
// Flush the batch queue, so clear is run right away.
|
||||
// Otherwise the hooks below won't work.
|
||||
self.flush_batch(false, source_range).await?;
|
||||
@ -298,23 +294,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn set_units(
|
||||
&self,
|
||||
units: crate::UnitLength,
|
||||
source_range: SourceRange,
|
||||
id_generator: &mut IdGenerator,
|
||||
) -> Result<(), crate::errors::KclError> {
|
||||
// Before we even start executing the program, set the units.
|
||||
self.batch_modeling_cmd(
|
||||
id_generator.next_uuid(),
|
||||
source_range,
|
||||
&ModelingCmd::from(mcmd::SetSceneUnits { unit: units.into() }),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Re-run the command to apply the settings.
|
||||
async fn reapply_settings(
|
||||
&self,
|
||||
|
@ -3,11 +3,7 @@ use std::collections::HashMap;
|
||||
use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{
|
||||
cad_op::Group,
|
||||
kcl_value::TypeDef,
|
||||
types::{PrimitiveType, CHECK_NUMERIC_TYPES},
|
||||
};
|
||||
use super::{cad_op::Group, kcl_value::TypeDef, types::PrimitiveType};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
@ -64,14 +60,6 @@ impl ExecutorContext {
|
||||
if exec_state.mod_local.settings.update_from_annotation(annotation)? {
|
||||
exec_state.mod_local.explicit_length_units = true;
|
||||
}
|
||||
let new_units = exec_state.length_unit();
|
||||
self.engine
|
||||
.set_units(
|
||||
new_units.into(),
|
||||
annotation.as_source_range(),
|
||||
exec_state.id_generator(),
|
||||
)
|
||||
.await?;
|
||||
} else {
|
||||
exec_state.err(CompilationError::err(
|
||||
annotation.as_source_range(),
|
||||
@ -873,7 +861,10 @@ impl Node<MemberExpression> {
|
||||
source_ranges: vec![self.clone().into()],
|
||||
}))
|
||||
}
|
||||
(KclValue::MixedArray { value: arr, meta: _ }, Property::UInt(index)) => {
|
||||
(
|
||||
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
|
||||
Property::UInt(index),
|
||||
) => {
|
||||
let value_of_arr = arr.get(index);
|
||||
if let Some(value) = value_of_arr {
|
||||
Ok(value.to_owned())
|
||||
@ -884,7 +875,7 @@ impl Node<MemberExpression> {
|
||||
}))
|
||||
}
|
||||
}
|
||||
(KclValue::MixedArray { .. }, p) => {
|
||||
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
Err(KclError::Semantic(KclErrorDetails {
|
||||
@ -1051,7 +1042,7 @@ impl Node<BinaryExpression> {
|
||||
BinaryOperator::Pow => KclValue::Number {
|
||||
value: left.n.powf(right.n),
|
||||
meta,
|
||||
ty: NumericType::Unknown,
|
||||
ty: exec_state.current_default_units(),
|
||||
},
|
||||
BinaryOperator::Neq => {
|
||||
let (l, r, ty) = NumericType::combine_eq(left, right);
|
||||
@ -1090,7 +1081,7 @@ impl Node<BinaryExpression> {
|
||||
}
|
||||
|
||||
fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
|
||||
if *CHECK_NUMERIC_TYPES && ty == &NumericType::Unknown {
|
||||
if ty == &NumericType::Unknown {
|
||||
// TODO suggest how to fix this
|
||||
exec_state.warn(CompilationError::err(
|
||||
self.as_source_range(),
|
||||
@ -1999,11 +1990,39 @@ fn assign_args_to_params(
|
||||
for (index, param) in function_expression.params.iter().enumerate() {
|
||||
if let Some(arg) = args.get(index) {
|
||||
// Argument was provided.
|
||||
exec_state.mut_stack().add(
|
||||
param.identifier.name.clone(),
|
||||
arg.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
|
||||
if let Some(ty) = ¶m.type_ {
|
||||
let value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"Argument requires a value with type `{}`, but found {}",
|
||||
ty.inner,
|
||||
arg.value.human_friendly_type(),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||
}
|
||||
KclError::Semantic(KclErrorDetails {
|
||||
message,
|
||||
source_ranges: vec![arg.source_range],
|
||||
})
|
||||
})?;
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(param.identifier.name.clone(), value, (¶m.identifier).into())?;
|
||||
} else {
|
||||
exec_state.mut_stack().add(
|
||||
param.identifier.name.clone(),
|
||||
arg.value.clone(),
|
||||
(¶m.identifier).into(),
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
// Argument was not provided.
|
||||
if let Some(ref default_val) = param.default_value {
|
||||
|
@ -10,14 +10,13 @@ use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
execution::{types::NumericType, ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
||||
execution::{
|
||||
types::NumericType, ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen,
|
||||
},
|
||||
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
||||
std::{args::TyF64, sketch::PlaneData},
|
||||
};
|
||||
|
||||
use super::ExecutorContext;
|
||||
|
||||
type Point2D = kcmc::shared::Point2d<f64>;
|
||||
type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
/// A geometry.
|
||||
@ -265,7 +264,6 @@ pub struct Plane {
|
||||
pub y_axis: Point3d,
|
||||
/// The z-axis (normal).
|
||||
pub z_axis: Point3d,
|
||||
pub units: UnitLen,
|
||||
#[serde(skip)]
|
||||
pub meta: Vec<Metadata>,
|
||||
}
|
||||
@ -287,6 +285,8 @@ impl Plane {
|
||||
x: 1.0,
|
||||
y: 0.0,
|
||||
z: 0.0,
|
||||
// TODO axes must be normalized, so maybe these should all be count
|
||||
// rather than mm?
|
||||
units: UnitLen::Mm,
|
||||
},
|
||||
y_axis:
|
||||
@ -483,7 +483,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
|
||||
value: PlaneType::XY,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::NegXY => Plane {
|
||||
@ -494,7 +493,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm),
|
||||
value: PlaneType::XY,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::XZ => Plane {
|
||||
@ -505,7 +503,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
|
||||
value: PlaneType::XZ,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::NegXZ => Plane {
|
||||
@ -516,7 +513,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
|
||||
value: PlaneType::XZ,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::YZ => Plane {
|
||||
@ -527,7 +523,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
|
||||
value: PlaneType::YZ,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::NegYZ => Plane {
|
||||
@ -538,7 +533,6 @@ impl Plane {
|
||||
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
|
||||
z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
|
||||
value: PlaneType::YZ,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
},
|
||||
PlaneData::Plane {
|
||||
@ -556,7 +550,6 @@ impl Plane {
|
||||
y_axis,
|
||||
z_axis,
|
||||
value: PlaneType::Custom,
|
||||
units: exec_state.length_unit(),
|
||||
meta: vec![],
|
||||
}
|
||||
}
|
||||
@ -713,12 +706,6 @@ impl SketchSurface {
|
||||
SketchSurface::Face(face) => face.z_axis,
|
||||
}
|
||||
}
|
||||
pub(crate) fn units(&self) -> UnitLen {
|
||||
match self {
|
||||
SketchSurface::Plane(plane) => plane.units,
|
||||
SketchSurface::Face(face) => face.units,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -787,7 +774,8 @@ impl Sketch {
|
||||
return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
|
||||
};
|
||||
|
||||
Ok(path.get_to().into())
|
||||
let to = path.get_base().to;
|
||||
Ok(Point2d::new(to[0], to[1], path.get_base().units))
|
||||
}
|
||||
|
||||
pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
|
||||
@ -829,6 +817,10 @@ impl Solid {
|
||||
pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
|
||||
self.edge_cuts.iter().map(|foc| foc.id())
|
||||
}
|
||||
|
||||
pub(crate) fn height_in_mm(&self) -> f64 {
|
||||
self.units.adjust_to(self.height, UnitLen::Mm).0
|
||||
}
|
||||
}
|
||||
|
||||
/// A fillet or a chamfer.
|
||||
@ -889,28 +881,6 @@ pub struct Point2d {
|
||||
pub units: UnitLen,
|
||||
}
|
||||
|
||||
impl From<[TyF64; 2]> for Point2d {
|
||||
fn from(p: [TyF64; 2]) -> Self {
|
||||
Self {
|
||||
x: p[0].n,
|
||||
y: p[1].n,
|
||||
units: p[0].ty.expect_length(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point2d> for [f64; 2] {
|
||||
fn from(p: Point2d) -> Self {
|
||||
[p.x, p.y]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point2d> for Point2D {
|
||||
fn from(p: Point2d) -> Self {
|
||||
Self { x: p.x, y: p.y }
|
||||
}
|
||||
}
|
||||
|
||||
impl Point2d {
|
||||
pub const ZERO: Self = Self {
|
||||
x: 0.0,
|
||||
@ -921,6 +891,18 @@ impl Point2d {
|
||||
pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
|
||||
Self { x, y, units }
|
||||
}
|
||||
|
||||
pub fn into_x(self) -> TyF64 {
|
||||
TyF64::new(self.x, self.units.into())
|
||||
}
|
||||
|
||||
pub fn into_y(self) -> TyF64 {
|
||||
TyF64::new(self.y, self.units.into())
|
||||
}
|
||||
|
||||
pub fn ignore_units(self) -> [f64; 2] {
|
||||
[self.x, self.y]
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
|
||||
@ -968,9 +950,9 @@ impl From<Point3d> for Point3D {
|
||||
impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
|
||||
fn from(p: Point3d) -> Self {
|
||||
Self {
|
||||
x: LengthUnit(p.x),
|
||||
y: LengthUnit(p.y),
|
||||
z: LengthUnit(p.z),
|
||||
x: LengthUnit(p.units.adjust_to(p.x, UnitLen::Mm).0),
|
||||
y: LengthUnit(p.units.adjust_to(p.y, UnitLen::Mm).0),
|
||||
z: LengthUnit(p.units.adjust_to(p.z, UnitLen::Mm).0),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1318,9 +1300,9 @@ impl Path {
|
||||
ccw: *ccw,
|
||||
},
|
||||
Path::ArcThreePoint { p1, p2, p3, .. } => {
|
||||
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
||||
let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
||||
GetTangentialInfoFromPathsResult::Arc {
|
||||
center: circle_center.center,
|
||||
center: circle.center,
|
||||
ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
|
||||
}
|
||||
}
|
||||
@ -1332,14 +1314,13 @@ impl Path {
|
||||
radius: *radius,
|
||||
},
|
||||
Path::CircleThreePoint { p1, p2, p3, .. } => {
|
||||
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
||||
let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1);
|
||||
let center_point = [circle_center.center[0], circle_center.center[1]];
|
||||
let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
|
||||
let center_point = [circle.center[0], circle.center[1]];
|
||||
GetTangentialInfoFromPathsResult::Circle {
|
||||
center: center_point,
|
||||
// Note: a circle is always ccw regardless of the order of points
|
||||
ccw: true,
|
||||
radius,
|
||||
radius: circle.radius,
|
||||
}
|
||||
}
|
||||
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {
|
||||
|
@ -365,7 +365,7 @@ impl ProgramMemory {
|
||||
}
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("memory item key `{}` is not defined", var),
|
||||
message: format!("`{}` is not defined", var),
|
||||
source_ranges: vec![source_range],
|
||||
}))
|
||||
}
|
||||
@ -486,7 +486,7 @@ impl ProgramMemory {
|
||||
}
|
||||
|
||||
Err(KclError::UndefinedValue(KclErrorDetails {
|
||||
message: format!("memory item key `{}` is not defined", var),
|
||||
message: format!("`{}` is not defined", var),
|
||||
source_ranges: vec![],
|
||||
}))
|
||||
}
|
||||
|
@ -95,8 +95,7 @@ pub struct DefaultPlanes {
|
||||
pub struct TagIdentifier {
|
||||
pub value: String,
|
||||
// Multi-version representation of info about the tag. Kept ordered. The usize is the epoch at which the info
|
||||
// was written. Note that there might be multiple versions of tag info from the same epoch, the version with
|
||||
// the higher index will be the most recent.
|
||||
// was written.
|
||||
#[serde(skip)]
|
||||
pub info: Vec<(usize, TagEngineInfo)>,
|
||||
#[serde(skip)]
|
||||
@ -123,10 +122,16 @@ impl TagIdentifier {
|
||||
/// Add info from a different instance of this tag.
|
||||
pub fn merge_info(&mut self, other: &TagIdentifier) {
|
||||
assert_eq!(&self.value, &other.value);
|
||||
'new_info: for (oe, ot) in &other.info {
|
||||
for (e, _) in &self.info {
|
||||
if e > oe {
|
||||
continue 'new_info;
|
||||
for (oe, ot) in &other.info {
|
||||
if let Some((e, t)) = self.info.last_mut() {
|
||||
// If there is newer info, then skip this iteration.
|
||||
if *e > *oe {
|
||||
continue;
|
||||
}
|
||||
// If we're in the same epoch, then overwrite.
|
||||
if e == oe {
|
||||
*t = ot.clone();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
self.info.push((*oe, ot.clone()));
|
||||
@ -1585,7 +1590,7 @@ const answer = returnX()"#;
|
||||
assert_eq!(
|
||||
err,
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: "memory item key `x` is not defined".to_owned(),
|
||||
message: "`x` is not defined".to_owned(),
|
||||
source_ranges: vec![
|
||||
SourceRange::new(64, 65, ModuleId::default()),
|
||||
SourceRange::new(97, 106, ModuleId::default())
|
||||
@ -1669,7 +1674,7 @@ let shape = layer() |> patternTransform(instances = 10, transform = transform)
|
||||
assert_eq!(
|
||||
err,
|
||||
KclError::UndefinedValue(KclErrorDetails {
|
||||
message: "memory item key `x` is not defined".to_owned(),
|
||||
message: "`x` is not defined".to_owned(),
|
||||
source_ranges: vec![SourceRange::new(80, 81, ModuleId::default())],
|
||||
}),
|
||||
);
|
||||
@ -1815,33 +1820,21 @@ const bracket = startSketchOn(XY)
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_bad_arg_count_std() {
|
||||
let ast = "startSketchOn(XY)
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> profileStartX()";
|
||||
assert!(parse_execute(ast)
|
||||
.await
|
||||
.unwrap_err()
|
||||
.message()
|
||||
.contains("Expected a sketch argument"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_unary_operator_not_succeeds() {
|
||||
let ast = r#"
|
||||
fn returnTrue = () => { return !false }
|
||||
const t = true
|
||||
const f = false
|
||||
let notTrue = !t
|
||||
let notFalse = !f
|
||||
let c = !!true
|
||||
let d = !returnTrue()
|
||||
fn returnTrue() { return !false }
|
||||
t = true
|
||||
f = false
|
||||
notTrue = !t
|
||||
notFalse = !f
|
||||
c = !!true
|
||||
d = !returnTrue()
|
||||
|
||||
assert(!false, "expected to pass")
|
||||
assertIs(!false, error = "expected to pass")
|
||||
|
||||
fn check = (x) => {
|
||||
assert(!x, "expected argument to be false")
|
||||
assertIs(!x, error = "expected argument to be false")
|
||||
return true
|
||||
}
|
||||
check(false)
|
||||
|