Compare commits

...

16 Commits

Author SHA1 Message Date
01c6fd53fa Fix Cargo.lock (#6452) 2025-04-23 17:39:35 +00:00
f8306c0275 Try running with 8 macOS cores again (#6420)
* Revert "Disable all macOS testing for now (#6395)"

This reverts commit fbc3251c3f.

* Try running with 8 macOS cores again
2025-04-23 13:10:09 -04:00
900ef9e18d Release KCL 63 (#6446) 2025-04-23 12:35:16 -04:00
a46186573c Kwargs: profileStart/x/y (#6439) 2025-04-23 15:21:58 +00:00
90f6c1bb04 Fix Back button on SignIn page & Add Cancel button (#6415)
* Back button on SignIn page leads to Home page
Fixes #6413

* Add regression test for the bug, and for new cancel button

* Full sign in e2e test

* Good bot

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-04-23 15:12:33 +00:00
41d946b339 Remove Spline and Text tools from sketch mode toolbar (#6427)
* Remove Spline and Text tool from sketch toolbar

* Update sketch-related snapshots

* Update snapshots
2025-04-23 13:06:51 +00:00
b7385d5f25 Turn on units of measure (BREAKING CHANGE) (#6343)
* Turn on uom checks

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Convert all lengths to mm for engine calls

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-22 22:58:35 +00:00
3d22f6cd66 Only save one version of tag info per epoch (#6429)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-04-23 09:22:52 +12:00
9730e3f5b3 KCL: Rephrase confusing error msg (#6436)
Before:

  × undefined value: memory item key `foo` is not defined

Now:

  × undefined value: `foo` is not defined
2025-04-22 20:24:53 +00:00
29d6b22d63 Restore tests that use Text-to-CAD (#6284) 2025-04-22 14:55:10 -04:00
f99e44e371 Kwargs: assert functions (#6406)
Closes https://github.com/KittyCAD/modeling-app/issues/6408
2025-04-22 17:44:52 +00:00
8be36d3d16 Update known-issues.md (#6428)
* Update known-issues.md

* Update known-issues.md

Little more description.

---------

Co-authored-by: Josh Gomez <114548659+jgomez720@users.noreply.github.com>
2025-04-22 17:18:35 +00:00
bc1742af48 Remove bold mono font (#6414)
* Remove all use of bold mono font
A few mono font points are left, mostly for numbers which look better in
monospace.

* Update snapshots
2025-04-22 15:17:47 +00:00
e4080cc184 Allow snapshot tests to report status (#6430) 2025-04-22 10:39:21 -04:00
18de6ccb59 #6417 Fix svg attribute names, add new eslint rule (#6418)
* fix svg attribute names, add new eslint rule

* tabs -> spaces
2025-04-22 09:15:38 -04:00
f00ea4cf5e Use TAB API to determine when merges are blocked (#6405)
* Use TAB API to determine when merges are blocked

* Enable a test that is known to fail
2025-04-22 09:02:02 -04:00
478 changed files with 52041 additions and 21390 deletions

View File

@ -19,6 +19,11 @@
"plugin:jsx-a11y/recommended", "plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended" "plugin:react-hooks/recommended"
], ],
"settings": {
"react": {
"version": "detect"
}
},
"rules": { "rules": {
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead. "no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
"@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-array-constructor": "error",
@ -61,6 +66,7 @@
"jsx-a11y/click-events-have-key-events": "off", "jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-autofocus": "off", "jsx-a11y/no-autofocus": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off", "jsx-a11y/no-noninteractive-element-interactions": "off",
"react/no-unknown-property": "error",
"no-restricted-globals": [ "no-restricted-globals": [
"error", "error",
{ {

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

View File

@ -225,7 +225,7 @@ jobs:
uses: nick-fields/retry@v3.0.2 uses: nick-fields/retry@v3.0.2
with: with:
shell: bash shell: bash
command: npm run test:snapshots || true command: npm run test:snapshots
timeout_minutes: 5 timeout_minutes: 5
max_attempts: 5 max_attempts: 5
env: env:
@ -285,8 +285,7 @@ jobs:
# TODO: enable namespace-profile-windows-latest once available # TODO: enable namespace-profile-windows-latest once available
os: os:
- "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64" - "runs-on=${{ github.run_id }}/family=i7ie.2xlarge/image=ubuntu22-full-x64"
# TODO: renable this when macoOS runner seem more stable - 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]
@ -296,7 +295,7 @@ jobs:
isScheduled: isScheduled:
- ${{ github.event_name == 'schedule' }} - ${{ github.event_name == 'schedule' }}
exclude: exclude:
- os: namespace-profile-macos-6-cores - os: namespace-profile-macos-8-cores
isScheduled: true isScheduled: true
- os: windows-latest-8-cores - os: windows-latest-8-cores
isScheduled: true isScheduled: true

File diff suppressed because one or more lines are too long

40
docs/kcl/assertIs.md Normal file

File diff suppressed because one or more lines are too long

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from centimeters to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from centimeters to the current default unit. Converts a number from centimeters to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42cm`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in centimeters. No matter what units the current file uses, this function will always return a number equivalent to the input in centimeters.
For example, if the current file uses inches, `fromCm(1)` will return `0.393701`. If the current file uses millimeters, `fromCm(1)` will return `10`. If the current file uses centimeters, `fromCm(1)` will return `1`. For example, if the current file uses inches, `fromCm(1)` will return `0.393701`. If the current file uses millimeters, `fromCm(1)` will return `10`. If the current file uses centimeters, `fromCm(1)` will return `1`.

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from feet to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from feet to the current default unit. Converts a number from feet to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42ft`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in feet. No matter what units the current file uses, this function will always return a number equivalent to the input in feet.
For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`. For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`.

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from inches to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from inches to the current default unit. Converts a number from inches to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42inch`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in inches. No matter what units the current file uses, this function will always return a number equivalent to the input in inches.
For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`. For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`.

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from meters to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from meters to the current default unit. Converts a number from meters to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42m`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in meters. No matter what units the current file uses, this function will always return a number equivalent to the input in meters.
For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`. For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`.

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from mm to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from mm to the current default unit. Converts a number from mm to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42mm`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters. No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters.
For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`. For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`.

View File

@ -4,8 +4,12 @@ excerpt: "Converts a number from yards to the current default unit."
layout: manual layout: manual
--- ---
**WARNING:** This function is deprecated.
Converts a number from yards to the current default unit. Converts a number from yards to the current default unit.
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42yd`) or the `to...` conversion functions.
No matter what units the current file uses, this function will always return a number equivalent to the input in yards. No matter what units the current file uses, this function will always return a number equivalent to the input in yards.
For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`. For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`.

View File

@ -40,11 +40,7 @@ layout: manual
* [`arc`](kcl/arc) * [`arc`](kcl/arc)
* [`asin`](kcl/asin) * [`asin`](kcl/asin)
* [`assert`](kcl/assert) * [`assert`](kcl/assert)
* [`assertEqual`](kcl/assertEqual) * [`assertIs`](kcl/assertIs)
* [`assertGreaterThan`](kcl/assertGreaterThan)
* [`assertGreaterThanOrEq`](kcl/assertGreaterThanOrEq)
* [`assertLessThan`](kcl/assertLessThan)
* [`assertLessThanOrEq`](kcl/assertLessThanOrEq)
* [`atan`](kcl/atan) * [`atan`](kcl/atan)
* [`atan2`](kcl/atan2) * [`atan2`](kcl/atan2)
* [`bezierCurve`](kcl/bezierCurve) * [`bezierCurve`](kcl/bezierCurve)
@ -55,12 +51,6 @@ layout: manual
* [`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)
@ -120,8 +110,14 @@ layout: manual
* [`sweep`](kcl/sweep) * [`sweep`](kcl/sweep)
* [`tangentToEnd`](kcl/tangentToEnd) * [`tangentToEnd`](kcl/tangentToEnd)
* [`tangentialArc`](kcl/tangentialArc) * [`tangentialArc`](kcl/tangentialArc)
* [`toDegrees`](kcl/toDegrees) * [`toCentimeters`](kcl/std-toCentimeters)
* [`toRadians`](kcl/toRadians) * [`toDegrees`](kcl/std-toDegrees)
* [`toFeet`](kcl/std-toFeet)
* [`toInches`](kcl/std-toInches)
* [`toMeters`](kcl/std-toMeters)
* [`toMillimeters`](kcl/std-toMillimeters)
* [`toRadians`](kcl/std-toRadians)
* [`toYards`](kcl/std-toYards)
* [`translate`](kcl/translate) * [`translate`](kcl/translate)
* [`union`](kcl/union) * [`union`](kcl/union)
* [`xLine`](kcl/xLine) * [`xLine`](kcl/xLine)

View File

@ -34,7 +34,7 @@ int(num: number): number
```js ```js
n = int(ceil(5 / 2)) n = int(ceil(5 / 2))
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3") assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3")
// Draw n cylinders. // Draw n cylinders.
startSketchOn(XZ) startSketchOn(XZ)
|> circle(center = [0, 0], radius = 2) |> circle(center = [0, 0], radius = 2)

View File

@ -22,3 +22,6 @@ 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.
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.

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)) scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8: number(rad)))
return { return {
translate = [0, 0, replicaId * 10], translate = [0, 0, replicaId * 10],
scale = [scale, scale, 0] scale = [scale, scale, 0]

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(sketch: Sketch): [number] profileStart(profile: Sketch): [number]
``` ```
@ -17,7 +17,7 @@ profileStart(sketch: Sketch): [number]
| Name | Type | Description | Required | | Name | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | | Yes | | `profile` | [`Sketch`](/docs/kcl/types/Sketch) | Profile whose start is being used | Yes |
### Returns ### Returns

View File

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

View File

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

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 (in degrees). | Yes | | `angleStart` | `number(Angle)` | Start angle. | Yes |
| `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No | | `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No |
| `radius` | `number(Length)` | Radius of the helix. | No | | `radius` | `number(Length)` | Radius of the helix. | No |
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No | | `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |

View File

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

View File

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

View File

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

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), length = 50 * tan((1/2): number(rad)),
) )
|> yLine(endAbsolute = 0) |> yLine(endAbsolute = 0)
|> close() |> close()

View 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

File diff suppressed because one or more lines are too long

27
docs/kcl/std-toFeet.md Normal file
View 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
View 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
View 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)`

View 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

File diff suppressed because one or more lines are too long

27
docs/kcl/std-toYards.md Normal file
View 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)`

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,6 @@ A sketch type.
| `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 | | `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A unit of length. | No |
---- ----

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

View 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 })
})
}
)
})

View File

@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture' import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture' import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture' import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture' import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture' import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
@ -66,6 +67,7 @@ export interface Fixtures {
toolbar: ToolbarFixture toolbar: ToolbarFixture
scene: SceneFixture scene: SceneFixture
homePage: HomePageFixture homePage: HomePageFixture
signInPage: SignInPageFixture
} }
export class ElectronZoo { export class ElectronZoo {
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
homePage: async ({ page }: { page: Page }, use: FnUse) => { homePage: async ({ page }: { page: Page }, use: FnUse) => {
await use(new HomePageFixture(page)) await use(new HomePageFixture(page))
}, },
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
await use(new SignInPageFixture(page))
},
} }
if (process.env.PLATFORM === 'web') { if (process.env.PLATFORM === 'web') {

View 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)
}
}

View File

@ -46,6 +46,9 @@ export class ToolbarFixture {
gizmo!: Locator gizmo!: Locator
gizmoDisabled!: Locator gizmoDisabled!: Locator
loadButton!: Locator loadButton!: Locator
/** User button for the user sidebar menu */
userSidebarButton!: Locator
signOutButton!: Locator
constructor(page: Page) { constructor(page: Page) {
this.page = page this.page = page
@ -82,6 +85,9 @@ export class ToolbarFixture {
// element or two different elements can represent these states. // element or two different elements can represent these states.
this.gizmo = page.getByTestId('gizmo') this.gizmo = page.getByTestId('gizmo')
this.gizmoDisabled = page.getByTestId('gizmo-disabled') this.gizmoDisabled = page.getByTestId('gizmo-disabled')
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
} }
get logoLink() { get logoLink() {

View File

@ -1,6 +1,25 @@
import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter' import type {
Reporter,
TestCase,
TestResult,
FullResult,
} from '@playwright/test/reporter'
class MyAPIReporter implements Reporter { class MyAPIReporter implements Reporter {
private pendingRequests: Promise<void>[] = []
private allResults: Record<string, any>[] = []
private blockingResults: Record<string, any>[] = []
async onEnd(result: FullResult): Promise<void> {
await Promise.all(this.pendingRequests)
if (this.allResults.length > 0 && this.blockingResults.length === 0) {
result.status = 'passed'
if (!process.env.CI) {
console.error('TAB API - Marked failures as non-blocking')
}
}
}
onTestEnd(test: TestCase, result: TestResult): void { onTestEnd(test: TestCase, result: TestResult): void {
if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) { if (!process.env.TAB_API_URL || !process.env.TAB_API_KEY) {
return return
@ -20,6 +39,7 @@ class MyAPIReporter implements Reporter {
platform: process.env.RUNNER_OS || process.platform, platform: process.env.RUNNER_OS || process.platform,
// Extra test and result data // Extra test and result data
annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme' annotations: test.annotations.map((a) => a.type), // e.g. 'fail' or 'fixme'
id: test.id, // computed file/test/project ID used for reruns
retry: result.retry, retry: result.retry,
tags: test.tags, // e.g. '@snapshot' or '@skipWin' tags: test.tags, // e.g. '@snapshot' or '@skipWin'
// Extra environment variables // Extra environment variables
@ -35,7 +55,7 @@ class MyAPIReporter implements Reporter {
RUNNER_ARCH: process.env.RUNNER_ARCH || null, RUNNER_ARCH: process.env.RUNNER_ARCH || null,
} }
void (async () => { const request = (async () => {
try { try {
const response = await fetch(`${process.env.TAB_API_URL}/api/results`, { const response = await fetch(`${process.env.TAB_API_URL}/api/results`, {
method: 'POST', method: 'POST',
@ -46,18 +66,27 @@ class MyAPIReporter implements Reporter {
body: JSON.stringify(payload), body: JSON.stringify(payload),
}) })
if (!response.ok && !process.env.CI) { if (response.ok) {
console.error( const result = await response.json()
'TAB API - Failed to send test result:', this.allResults.push(result)
await response.text() if (result.block) {
) this.blockingResults.push(result)
} }
} catch { } else {
const error = await response.json()
if (!process.env.CI) { if (!process.env.CI) {
console.error('TAB API - Unable to send test result') console.error('TAB API - Failed to send test result:', error)
}
}
} catch (error) {
const message = error instanceof Error ? error.message : String(error)
if (!process.env.CI) {
console.error('TAB API - Unable to send test result:', message)
} }
} }
})() })()
this.pendingRequests.push(request)
} }
} }

View File

@ -63,7 +63,7 @@ test.describe('Onboarding tests', () => {
{ {
tag: '@electron', tag: '@electron',
}, },
async ({ page, tronApp }) => { async ({ page, tronApp, scene }) => {
if (!tronApp) { if (!tronApp) {
fail() fail()
} }
@ -72,7 +72,6 @@ test.describe('Onboarding tests', () => {
onboarding_status: '', onboarding_status: '',
}, },
}) })
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 } const viewportSize = { width: 1200, height: 500 }
await page.setBodyDimensions(viewportSize) await page.setBodyDimensions(viewportSize)
@ -80,7 +79,7 @@ test.describe('Onboarding tests', () => {
await test.step(`Create a project and open to the onboarding`, async () => { await test.step(`Create a project and open to the onboarding`, async () => {
await createProject({ name: 'project-link', page }) await createProject({ name: 'project-link', page })
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => { await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
await u.waitForPageLoad() await scene.connectionEstablished()
}) })
}) })
@ -332,6 +331,7 @@ test.describe('Onboarding tests', () => {
test('Avatar text updates depending on image load success', async ({ test('Avatar text updates depending on image load success', async ({
context, context,
page, page,
toolbar,
homePage, homePage,
tronApp, tronApp,
}) => { }) => {
@ -363,7 +363,7 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Test that the text in this step is correct // Test that the text in this step is correct
const avatarLocator = page.getByTestId('user-sidebar-toggle').locator('img') const avatarLocator = toolbar.userSidebarButton.locator('img')
const onboardingOverlayLocator = page const onboardingOverlayLocator = page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
.locator('div') .locator('div')
@ -405,6 +405,7 @@ test.describe('Onboarding tests', () => {
test("Avatar text doesn't mention avatar when no avatar", async ({ test("Avatar text doesn't mention avatar when no avatar", async ({
context, context,
page, page,
toolbar,
homePage, homePage,
tronApp, tronApp,
}) => { }) => {
@ -436,7 +437,7 @@ test.describe('Onboarding tests', () => {
await homePage.goToModelingScene() await homePage.goToModelingScene()
// Test that the text in this step is correct // Test that the text in this step is correct
const sidebar = page.getByTestId('user-sidebar-toggle') const sidebar = toolbar.userSidebarButton
const avatar = sidebar.locator('img') const avatar = sidebar.locator('img')
const onboardingOverlayLocator = page const onboardingOverlayLocator = page
.getByTestId('onboarding-content') .getByTestId('onboarding-content')
@ -465,6 +466,7 @@ test.describe('Onboarding tests', () => {
test('Restarting onboarding on desktop takes one attempt', async ({ test('Restarting onboarding on desktop takes one attempt', async ({
context, context,
page, page,
toolbar,
tronApp, tronApp,
}) => { }) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
@ -503,7 +505,7 @@ test('Restarting onboarding on desktop takes one attempt', async ({
.filter({ hasText: 'Tutorial Project 00' }) .filter({ hasText: 'Tutorial Project 00' })
const tutorialModalText = page.getByText('Welcome to Design Studio!') const tutorialModalText = page.getByText('Welcome to Design Studio!')
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
const userMenuButton = page.getByTestId('user-sidebar-toggle') const userMenuButton = toolbar.userSidebarButton
const userMenuSettingsButton = page.getByRole('button', { const userMenuSettingsButton = page.getByRole('button', {
name: 'User settings', name: 'User settings',
}) })

View File

@ -278,7 +278,7 @@ test.describe('Point-and-click assemblies tests', () => {
highlightedHeaderArg: 'x', highlightedHeaderArg: 'x',
commandName: 'Translate', commandName: 'Translate',
}) })
await page.keyboard.insertText('5') await page.keyboard.insertText('100')
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
await page.keyboard.insertText('0.1') await page.keyboard.insertText('0.1')
await cmdBar.progressCmdBar() await cmdBar.progressCmdBar()
@ -287,7 +287,7 @@ test.describe('Point-and-click assemblies tests', () => {
await cmdBar.expectState({ await cmdBar.expectState({
stage: 'review', stage: 'review',
headerArguments: { headerArguments: {
X: '5', X: '100',
Y: '0.1', Y: '0.1',
Z: '0.2', Z: '0.2',
}, },
@ -299,7 +299,7 @@ test.describe('Point-and-click assemblies tests', () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
` `
bracket bracket
|> translate(x = 5, y = 0.1, z = 0.2) |> translate(x = 100, y = 0.1, z = 0.2)
`, `,
{ shouldNormalise: true } { shouldNormalise: true }
) )
@ -348,7 +348,7 @@ test.describe('Point-and-click assemblies tests', () => {
await editor.expectEditor.toContain( await editor.expectEditor.toContain(
` `
bracket bracket
|> translate(x = 5, y = 0.1, z = 0.2) |> translate(x = 100, y = 0.1, z = 0.2)
|> rotate(roll = 0.1, pitch = 0.2, yaw = 0.3) |> rotate(roll = 0.1, pitch = 0.2, yaw = 0.3)
`, `,
{ shouldNormalise: true } { shouldNormalise: true }

View File

@ -1962,13 +1962,13 @@ test(
test( test(
'Settings persist across restarts', 'Settings persist across restarts',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page, scene, cmdBar }, testInfo) => { async ({ page, toolbar }, testInfo) => {
await test.step('We can change a user setting like theme', async () => { await test.step('We can change a user setting like theme', async () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
page.on('console', console.log) page.on('console', console.log)
await page.getByTestId('user-sidebar-toggle').click() await toolbar.userSidebarButton.click()
await page.getByTestId('user-settings').click() await page.getByTestId('user-settings').click()
@ -1995,7 +1995,7 @@ test(
test( test(
'Original project name persist after onboarding', 'Original project name persist after onboarding',
{ tag: '@electron' }, { tag: '@electron' },
async ({ page }, testInfo) => { async ({ page, toolbar }, testInfo) => {
test.fixme(orRunWhenFullSuiteEnabled()) test.fixme(orRunWhenFullSuiteEnabled())
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })
@ -2007,7 +2007,7 @@ test(
}) })
await test.step('Should go through onboarding', async () => { await test.step('Should go through onboarding', async () => {
await page.getByTestId('user-sidebar-toggle').click() await toolbar.userSidebarButton.click()
await page.getByTestId('user-settings').click() await page.getByTestId('user-settings').click()
await page.getByRole('button', { name: 'Replay Onboarding' }).click() await page.getByRole('button', { name: 'Replay Onboarding' }).click()

View File

@ -1,4 +1,3 @@
import { orRunWhenFullSuiteEnabled } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test' import { expect, test } from '@e2e/playwright/zoo-test'
/* eslint-disable jest/no-conditional-expect */ /* eslint-disable jest/no-conditional-expect */
@ -51,7 +50,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
page, page,
scene, scene,
}) => { }) => {
test.fixme(orRunWhenFullSuiteEnabled())
await context.addInitScript((file) => { await context.addInitScript((file) => {
localStorage.setItem('persistCode', file) localStorage.setItem('persistCode', file)
}, file) }, file)
@ -200,7 +198,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
page, page,
scene, scene,
}) => { }) => {
test.fixme(orRunWhenFullSuiteEnabled())
const body1CapCoords = { x: 571, y: 311 } const body1CapCoords = { x: 571, y: 311 }
await context.addInitScript((file) => { await context.addInitScript((file) => {
@ -260,7 +257,6 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
page, page,
scene, scene,
}) => { }) => {
test.fixme(orRunWhenFullSuiteEnabled())
const body1CapCoords = { x: 571, y: 311 } const body1CapCoords = { x: 571, y: 311 }
const body2WallCoords = { x: 620, y: 152 } const body2WallCoords = { x: 620, y: 152 }
const [clickBody1Cap] = scene.makeMouseHelpers( const [clickBody1Cap] = scene.makeMouseHelpers(

View File

@ -3295,7 +3295,7 @@ profile003 = startProfileAt([-201.08, 254.17], sketch002)
) )
await editor.expectState({ await editor.expectState({
activeLines: [], activeLines: [],
diagnostics: ['memoryitemkey`badBadBadFn`isnotdefined'], diagnostics: ['`badBadBadFn`isnotdefined'],
highlightedCode: '', highlightedCode: '',
}) })
await expect( await expect(

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 75 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -181,7 +181,6 @@ test.describe('Testing settings', () => {
}) })
test('Project and user settings can be reset', async ({ page, homePage }) => { test('Project and user settings can be reset', async ({ page, homePage }) => {
test.fixme(orRunWhenFullSuiteEnabled())
const u = await getUtils(page) const u = await getUtils(page)
await test.step(`Setup`, async () => { await test.step(`Setup`, async () => {
await page.setBodyDimensions({ width: 1200, height: 500 }) await page.setBodyDimensions({ width: 1200, height: 500 })

View File

@ -25,10 +25,10 @@ shelfMountingHolePlacementOffset = shelfMountingHoleDiameter * 1.5
wallMountingHolePlacementOffset = wallMountingHoleDiameter * 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. // 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") assert(wallMountLength, isGreaterThanOrEqual = wallMountingHoleDiameter * 3, error = "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") assert(shelfMountLength, isGreaterThanOrEqual = shelfMountingHoleDiameter * 5.5, error = "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") assert(width, isGreaterThanOrEqual = shelfMountingHoleDiameter * 5.5, error = "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(width, isGreaterThanOrEqual = wallMountingHoleDiameter * 5.5, error = "Holes not possible. Either decrease hole diameter or increase width")
// Create the body of the bracket // Create the body of the bracket
bracketBody = startSketchOn(XZ) bracketBody = startSketchOn(XZ)

View File

@ -18,7 +18,7 @@ topTotalThickness = totalThickness - (bottomThickness + baseThickness)
nHoles = 4 nHoles = 4
// Add assertion so nHoles are always greater than 1 // 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 // Create the circular pattern for the mounting holes
circles = startSketchOn(XY) circles = startSketchOn(XY)

View File

@ -33,11 +33,11 @@ invas = map(angles, fn(a) {
// Map the involute curve // Map the involute curve
xs = map([0..cmo], fn(i) { 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) { ys = map([0..cmo], fn(i) {
return rs[i] * sin(invas[i]) return rs[i] * sin(invas[i]: number(rad))
}) })
// Extrude the gear body // Extrude the gear body

View File

@ -22,8 +22,8 @@ lSegments = totalLength / lbumps
wSegments = totalWidth / wbumps wSegments = totalWidth / wbumps
// Add assertions to ensure that the number of bumps are greater than 1 // Add assertions to ensure that the number of bumps are greater than 1
assertGreaterThan(lbumps, 1, "lbumps must be greater than 1") assert(lbumps, isGreaterThan = 1, error = "lbumps must be greater than 1")
assertGreaterThan(wbumps, 1, "wbumps must be greater than 1") assert(wbumps, isGreaterThan = 1, error = "wbumps must be greater than 1")
// Make the base // Make the base
base = startSketchOn(XY) base = startSketchOn(XY)

View File

@ -29,7 +29,7 @@ sketch001 = startSketchOn(XZ)
) )
|> yLine(endAbsolute = -templateThickness, tag = $seg03) |> yLine(endAbsolute = -templateThickness, tag = $seg03)
|> xLine(length = templateThickness, tag = $seg07) |> 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) |> xLine(endAbsolute = segEndX(seg03) + minClampingDistance, tag = $seg06)
|> yLine(length = templateThickness * 2, tag = $seg08) |> yLine(length = templateThickness * 2, tag = $seg08)
|> xLine(endAbsolute = segEndX(seg02) + 0, tag = $seg05) |> xLine(endAbsolute = segEndX(seg02) + 0, tag = $seg05)

20
rust/Cargo.lock generated
View File

@ -1792,7 +1792,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1803,7 +1803,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-derive-docs" name = "kcl-derive-docs"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"Inflector", "Inflector",
"anyhow", "anyhow",
@ -1822,7 +1822,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1831,7 +1831,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server" name = "kcl-language-server"
version = "0.2.62" version = "0.2.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1852,7 +1852,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@ -1872,7 +1872,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-lib" name = "kcl-lib"
version = "0.2.62" version = "0.2.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"approx 0.5.1", "approx 0.5.1",
@ -1943,7 +1943,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-python-bindings" name = "kcl-python-bindings"
version = "0.3.62" version = "0.3.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kcl-lib", "kcl-lib",
@ -1958,7 +1958,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hyper 0.14.32", "hyper 0.14.32",
@ -1971,7 +1971,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-to-core" name = "kcl-to-core"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1985,7 +1985,7 @@ dependencies = [
[[package]] [[package]]
name = "kcl-wasm-lib" name = "kcl-wasm-lib"
version = "0.1.62" version = "0.1.63"
dependencies = [ dependencies = [
"bson", "bson",
"console_error_panic_hook", "console_error_panic_hook",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-bumper" name = "kcl-bumper"
version = "0.1.62" version = "0.1.63"
edition = "2021" edition = "2021"
repository = "https://github.com/KittyCAD/modeling-api" repository = "https://github.com/KittyCAD/modeling-api"
rust-version = "1.76" rust-version = "1.76"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-derive-docs" name = "kcl-derive-docs"
description = "A tool for generating documentation from Rust derive macros" description = "A tool for generating documentation from Rust derive macros"
version = "0.1.62" version = "0.1.63"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-directory-test-macro" name = "kcl-directory-test-macro"
description = "A tool for generating tests from a directory of kcl files" description = "A tool for generating tests from a directory of kcl files"
version = "0.1.62" version = "0.1.63"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "kcl-language-server-release" name = "kcl-language-server-release"
version = "0.1.62" version = "0.1.63"
edition = "2021" edition = "2021"
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
publish = false publish = false

View File

@ -2,7 +2,7 @@
name = "kcl-language-server" name = "kcl-language-server"
description = "A language server for KCL." description = "A language server for KCL."
authors = ["KittyCAD Inc <kcl@kittycad.io>"] authors = ["KittyCAD Inc <kcl@kittycad.io>"]
version = "0.2.62" version = "0.2.63"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,7 +1,7 @@
[package] [package]
name = "kcl-lib" name = "kcl-lib"
description = "KittyCAD Language implementation and tools" description = "KittyCAD Language implementation and tools"
version = "0.2.62" version = "0.2.63"
edition = "2021" edition = "2021"
license = "MIT" license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app" repository = "https://github.com/KittyCAD/modeling-app"

View File

@ -4,7 +4,7 @@ const arr = [0, 0, 0, 10]
const i = 3 const i = 3
const ten = arr[i] const ten = arr[i]
assertEqual(ten, 10, 0.000001, "oops") assert(ten, isEqualTo = 10, error = "oops")
const p = "foo" const p = "foo"
const obj = { const obj = {
@ -13,4 +13,4 @@ const obj = {
} }
const one = obj[p] const one = obj[p]
assertEqual(one, 1, 0.0000001, "oops") assert(one, isEqualTo = 1, error = "oops")

View File

@ -1,25 +1,25 @@
import identity from "identity.kcl" import identity from "identity.kcl"
answer = identity(42) answer = identity(42)
assertEqual(answer, 42, 0.0001, "identity") assert(answer, isEqualTo = 42, error = "identity")
import identity as id from "identity.kcl" import identity as id from "identity.kcl"
answer43 = id(43) answer43 = id(43)
assertEqual(answer43, 43, 0.0001, "identity") assert(answer43, isEqualTo = 43, error = "identity")
import increment, decrement from "numbers.kcl" import increment, decrement from "numbers.kcl"
answer3 = increment(2) answer3 = increment(2)
assertEqual(answer3, 3, 0.0001, "increment") assert(answer3, isEqualTo = 3, error = "increment")
answer5 = decrement(6) 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" import increment as inc, decrement as dec from "numbers.kcl"
answer4 = inc(3) answer4 = inc(3)
assertEqual(answer4, 4, 0.0001, "inc") assert(answer4, isEqualTo = 4, error = "inc")
answer6 = dec(7) answer6 = dec(7)
assertEqual(answer6, 6, 0.0001, "dec") assert(answer6, isEqualTo = 6, error = "dec")

View File

@ -5,7 +5,7 @@ const 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) => {
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 { return {
translate: [0, 0, replicaId * 10], translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0], scale: [scale, scale, 0],

View File

@ -180,10 +180,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
) )
.await?; .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. // Flush the batch queue, so clear is run right away.
// Otherwise the hooks below won't work. // Otherwise the hooks below won't work.
self.flush_batch(false, source_range).await?; self.flush_batch(false, source_range).await?;
@ -298,23 +294,6 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
Ok(()) 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. /// Re-run the command to apply the settings.
async fn reapply_settings( async fn reapply_settings(
&self, &self,

View File

@ -3,11 +3,7 @@ use std::collections::HashMap;
use async_recursion::async_recursion; use async_recursion::async_recursion;
use indexmap::IndexMap; use indexmap::IndexMap;
use super::{ use super::{cad_op::Group, kcl_value::TypeDef, types::PrimitiveType};
cad_op::Group,
kcl_value::TypeDef,
types::{PrimitiveType, CHECK_NUMERIC_TYPES},
};
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{
@ -64,14 +60,6 @@ impl ExecutorContext {
if exec_state.mod_local.settings.update_from_annotation(annotation)? { if exec_state.mod_local.settings.update_from_annotation(annotation)? {
exec_state.mod_local.explicit_length_units = true; 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 { } else {
exec_state.err(CompilationError::err( exec_state.err(CompilationError::err(
annotation.as_source_range(), annotation.as_source_range(),
@ -873,7 +861,10 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()], 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); let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr { if let Some(value) = value_of_arr {
Ok(value.to_owned()) 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 t = p.type_name();
let article = article_for(t); let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails { Err(KclError::Semantic(KclErrorDetails {
@ -1051,7 +1042,7 @@ impl Node<BinaryExpression> {
BinaryOperator::Pow => KclValue::Number { BinaryOperator::Pow => KclValue::Number {
value: left.n.powf(right.n), value: left.n.powf(right.n),
meta, meta,
ty: NumericType::Unknown, ty: exec_state.current_default_units(),
}, },
BinaryOperator::Neq => { BinaryOperator::Neq => {
let (l, r, ty) = NumericType::combine_eq(left, right); 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) { 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 // TODO suggest how to fix this
exec_state.warn(CompilationError::err( exec_state.warn(CompilationError::err(
self.as_source_range(), self.as_source_range(),
@ -1999,11 +1990,39 @@ fn assign_args_to_params(
for (index, param) in function_expression.params.iter().enumerate() { for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) { if let Some(arg) = args.get(index) {
// Argument was provided. // Argument was provided.
if let Some(ty) = &param.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, (&param.identifier).into())?;
} else {
exec_state.mut_stack().add( exec_state.mut_stack().add(
param.identifier.name.clone(), param.identifier.name.clone(),
arg.value.clone(), arg.value.clone(),
(&param.identifier).into(), (&param.identifier).into(),
)?; )?;
}
} else { } else {
// Argument was not provided. // Argument was not provided.
if let Some(ref default_val) = param.default_value { if let Some(ref default_val) = param.default_value {

View File

@ -10,14 +10,13 @@ use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclError, 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}, parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
std::{args::TyF64, sketch::PlaneData}, std::{args::TyF64, sketch::PlaneData},
}; };
use super::ExecutorContext;
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>; type Point3D = kcmc::shared::Point3d<f64>;
/// A geometry. /// A geometry.
@ -265,7 +264,6 @@ pub struct Plane {
pub y_axis: Point3d, pub y_axis: Point3d,
/// The z-axis (normal). /// The z-axis (normal).
pub z_axis: Point3d, pub z_axis: Point3d,
pub units: UnitLen,
#[serde(skip)] #[serde(skip)]
pub meta: Vec<Metadata>, pub meta: Vec<Metadata>,
} }
@ -287,6 +285,8 @@ impl Plane {
x: 1.0, x: 1.0,
y: 0.0, y: 0.0,
z: 0.0, z: 0.0,
// TODO axes must be normalized, so maybe these should all be count
// rather than mm?
units: UnitLen::Mm, units: UnitLen::Mm,
}, },
y_axis: y_axis:
@ -483,7 +483,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::NegXY => Plane { PlaneData::NegXY => Plane {
@ -494,7 +493,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm), z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm),
value: PlaneType::XY, value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::XZ => Plane { PlaneData::XZ => Plane {
@ -505,7 +503,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm), z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::NegXZ => Plane { PlaneData::NegXZ => Plane {
@ -516,7 +513,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm), z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ, value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::YZ => Plane { PlaneData::YZ => Plane {
@ -527,7 +523,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm), z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::NegYZ => Plane { PlaneData::NegYZ => Plane {
@ -538,7 +533,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm), y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm), z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ, value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
}, },
PlaneData::Plane { PlaneData::Plane {
@ -556,7 +550,6 @@ impl Plane {
y_axis, y_axis,
z_axis, z_axis,
value: PlaneType::Custom, value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![], meta: vec![],
} }
} }
@ -713,12 +706,6 @@ impl SketchSurface {
SketchSurface::Face(face) => face.z_axis, 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)] #[derive(Debug, Clone)]
@ -787,7 +774,8 @@ impl Sketch {
return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units)); 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 { 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> + '_ { pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
self.edge_cuts.iter().map(|foc| foc.id()) 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. /// A fillet or a chamfer.
@ -889,28 +881,6 @@ pub struct Point2d {
pub units: UnitLen, 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 { impl Point2d {
pub const ZERO: Self = Self { pub const ZERO: Self = Self {
x: 0.0, x: 0.0,
@ -921,6 +891,18 @@ impl Point2d {
pub fn new(x: f64, y: f64, units: UnitLen) -> Self { pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
Self { x, y, units } 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)] #[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> { impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
fn from(p: Point3d) -> Self { fn from(p: Point3d) -> Self {
Self { Self {
x: LengthUnit(p.x), x: LengthUnit(p.units.adjust_to(p.x, UnitLen::Mm).0),
y: LengthUnit(p.y), y: LengthUnit(p.units.adjust_to(p.y, UnitLen::Mm).0),
z: LengthUnit(p.z), z: LengthUnit(p.units.adjust_to(p.z, UnitLen::Mm).0),
} }
} }
} }
@ -1318,9 +1300,9 @@ impl Path {
ccw: *ccw, ccw: *ccw,
}, },
Path::ArcThreePoint { p1, p2, p3, .. } => { 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 { GetTangentialInfoFromPathsResult::Arc {
center: circle_center.center, center: circle.center,
ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0, ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
} }
} }
@ -1332,14 +1314,13 @@ impl Path {
radius: *radius, radius: *radius,
}, },
Path::CircleThreePoint { p1, p2, p3, .. } => { Path::CircleThreePoint { 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]);
let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1); let center_point = [circle.center[0], circle.center[1]];
let center_point = [circle_center.center[0], circle_center.center[1]];
GetTangentialInfoFromPathsResult::Circle { GetTangentialInfoFromPathsResult::Circle {
center: center_point, center: center_point,
// Note: a circle is always ccw regardless of the order of points // Note: a circle is always ccw regardless of the order of points
ccw: true, ccw: true,
radius, radius: circle.radius,
} }
} }
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => { Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {

View File

@ -365,7 +365,7 @@ impl ProgramMemory {
} }
Err(KclError::UndefinedValue(KclErrorDetails { Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("memory item key `{}` is not defined", var), message: format!("`{}` is not defined", var),
source_ranges: vec![source_range], source_ranges: vec![source_range],
})) }))
} }
@ -486,7 +486,7 @@ impl ProgramMemory {
} }
Err(KclError::UndefinedValue(KclErrorDetails { Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("memory item key `{}` is not defined", var), message: format!("`{}` is not defined", var),
source_ranges: vec![], source_ranges: vec![],
})) }))
} }

View File

@ -95,8 +95,7 @@ pub struct DefaultPlanes {
pub struct TagIdentifier { pub struct TagIdentifier {
pub value: String, pub value: String,
// Multi-version representation of info about the tag. Kept ordered. The usize is the epoch at which the info // 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 // was written.
// the higher index will be the most recent.
#[serde(skip)] #[serde(skip)]
pub info: Vec<(usize, TagEngineInfo)>, pub info: Vec<(usize, TagEngineInfo)>,
#[serde(skip)] #[serde(skip)]
@ -123,10 +122,16 @@ impl TagIdentifier {
/// Add info from a different instance of this tag. /// Add info from a different instance of this tag.
pub fn merge_info(&mut self, other: &TagIdentifier) { pub fn merge_info(&mut self, other: &TagIdentifier) {
assert_eq!(&self.value, &other.value); assert_eq!(&self.value, &other.value);
'new_info: for (oe, ot) in &other.info { for (oe, ot) in &other.info {
for (e, _) in &self.info { if let Some((e, t)) = self.info.last_mut() {
if e > oe { // If there is newer info, then skip this iteration.
continue 'new_info; 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())); self.info.push((*oe, ot.clone()));
@ -1585,7 +1590,7 @@ const answer = returnX()"#;
assert_eq!( assert_eq!(
err, err,
KclError::UndefinedValue(KclErrorDetails { KclError::UndefinedValue(KclErrorDetails {
message: "memory item key `x` is not defined".to_owned(), message: "`x` is not defined".to_owned(),
source_ranges: vec![ source_ranges: vec![
SourceRange::new(64, 65, ModuleId::default()), SourceRange::new(64, 65, ModuleId::default()),
SourceRange::new(97, 106, ModuleId::default()) SourceRange::new(97, 106, ModuleId::default())
@ -1669,7 +1674,7 @@ let shape = layer() |> patternTransform(instances = 10, transform = transform)
assert_eq!( assert_eq!(
err, err,
KclError::UndefinedValue(KclErrorDetails { 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())], source_ranges: vec![SourceRange::new(80, 81, ModuleId::default())],
}), }),
); );
@ -1815,33 +1820,21 @@ const bracket = startSketchOn(XY)
parse_execute(ast).await.unwrap(); 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")] #[tokio::test(flavor = "multi_thread")]
async fn test_unary_operator_not_succeeds() { async fn test_unary_operator_not_succeeds() {
let ast = r#" let ast = r#"
fn returnTrue = () => { return !false } fn returnTrue() { return !false }
const t = true t = true
const f = false f = false
let notTrue = !t notTrue = !t
let notFalse = !f notFalse = !f
let c = !!true c = !!true
let d = !returnTrue() d = !returnTrue()
assert(!false, "expected to pass") assertIs(!false, error = "expected to pass")
fn check = (x) => { fn check = (x) => {
assert(!x, "expected argument to be false") assertIs(!x, error = "expected argument to be false")
return true return true
} }
check(false) check(false)

View File

@ -18,16 +18,6 @@ use crate::{
CompilationError, SourceRange, CompilationError, SourceRange,
}; };
lazy_static::lazy_static! {
pub(crate) static ref CHECK_NUMERIC_TYPES: bool = {
let env_var = std::env::var("ZOO_NUM_TYS");
let Ok(env_var) = env_var else {
return false;
};
!env_var.is_empty()
};
}
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType { pub enum RuntimeType {
Primitive(PrimitiveType), Primitive(PrimitiveType),
@ -62,6 +52,10 @@ impl RuntimeType {
RuntimeType::Primitive(PrimitiveType::Solid) RuntimeType::Primitive(PrimitiveType::Solid)
} }
pub fn helix() -> Self {
RuntimeType::Primitive(PrimitiveType::Helix)
}
pub fn plane() -> Self { pub fn plane() -> Self {
RuntimeType::Primitive(PrimitiveType::Plane) RuntimeType::Primitive(PrimitiveType::Plane)
} }
@ -94,6 +88,10 @@ impl RuntimeType {
)))) ))))
} }
pub fn known_length(len: UnitLen) -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
}
pub fn angle() -> Self { pub fn angle() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle( RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
UnitAngle::Unknown, UnitAngle::Unknown,
@ -370,6 +368,7 @@ impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"), PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
PrimitiveType::Number(_) => write!(f, "number"), PrimitiveType::Number(_) => write!(f, "number"),
PrimitiveType::String => write!(f, "string"), PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"), PrimitiveType::Boolean => write!(f, "bool"),
@ -427,13 +426,61 @@ impl NumericType {
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
} }
/// Combine two types when we expect them to be equal. pub fn expect_default_length(&self) -> Self {
match self {
NumericType::Default { len, .. } => NumericType::Known(UnitType::Length(*len)),
_ => unreachable!(),
}
}
pub fn expect_default_angle(&self) -> Self {
match self {
NumericType::Default { angle, .. } => NumericType::Known(UnitType::Angle(*angle)),
_ => unreachable!(),
}
}
/// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
/// precise, only adjusting one number or the other when they are of known types.
///
/// This combinator function is suitable for comparisons or arithmetic where uncertainty should
/// be handled by the user.
pub fn combine_eq(a: TyF64, b: TyF64) -> (f64, f64, NumericType) { pub fn combine_eq(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*; use NumericType::*;
match (a.ty, b.ty) { match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at), (at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at), (at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt), (Any, bt) => (a.n, b.n, bt),
(t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
(t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
(a.n, b.n, Known(UnitType::Count))
}
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => (a.n, b.n, t),
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => (a.n, b.n, t),
_ => (a.n, b.n, Unknown),
}
}
/// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
/// we are certain about only one type.
///
/// This combinator function is suitable for situations where the user would almost certainly want the types to be
/// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
///
/// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
pub fn combine_eq_coerce(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt),
(Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => (a.n, b.n, Unknown), (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => (a.n, b.n, Unknown),
// Known types and compatible, but needs adjustment. // Known types and compatible, but needs adjustment.
@ -458,12 +505,9 @@ impl NumericType {
pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) { pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
use NumericType::*; use NumericType::*;
let result = input.iter().map(|t| t.n).collect();
let mut result = input.iter().map(|t| t.n).collect();
let mut ty = Any; let mut ty = Any;
// Invariant mismatch is true => ty is fully known
let mut mismatch = false;
for i in input { for i in input {
if i.ty == Any || ty == i.ty { if i.ty == Any || ty == i.ty {
continue; continue;
@ -475,58 +519,24 @@ impl NumericType {
} }
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown), (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
// Known types and compatible, but needs adjustment.
(Known(UnitType::Length(_)), Known(UnitType::Length(_)))
| (Known(UnitType::Angle(_)), Known(UnitType::Angle(_))) => {
mismatch = true;
}
// Known but incompatible.
(Known(_), Known(_)) => return (result, Unknown),
// Known and unknown, no adjustment for counting numbers.
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => { (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
ty = Known(UnitType::Count); ty = Known(UnitType::Count);
} }
(Known(UnitType::Length(l1)), Default { len: l2, .. }) => { (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => {}
mismatch |= l1 != l2; (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {}
}
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
mismatch |= a1 != a2;
}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => { (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
mismatch |= l1 != l2;
ty = Known(UnitType::Length(*l2)); ty = Known(UnitType::Length(*l2));
} }
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => { (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
mismatch |= a1 != a2;
ty = Known(UnitType::Angle(*a2)); ty = Known(UnitType::Angle(*a2));
} }
(Unknown, _) | (_, Any) => unreachable!(), _ => return (result, Unknown),
} }
} }
if !mismatch {
return (result, ty);
}
result = result
.into_iter()
.zip(input)
.map(|(n, i)| match (&ty, &i.ty) {
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2)) | Default { len: l2, .. }) => {
l2.adjust_to(n, *l1).0
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2)) | Default { angle: a2, .. }) => {
a2.adjust_to(n, *a1).0
}
_ => unreachable!(),
})
.collect();
(result, ty) (result, ty)
} }
@ -534,11 +544,11 @@ impl NumericType {
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) { pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*; use NumericType::*;
match (a.ty, b.ty) { match (a.ty, b.ty) {
(at @ Default { .. }, bt @ Default { .. }) if at != bt => (a.n, b.n, Unknown), (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(Known(UnitType::Count), bt) => (a.n, b.n, bt), (Known(UnitType::Count), bt) => (a.n, b.n, bt),
(at, Known(UnitType::Count)) => (a.n, b.n, at), (at, Known(UnitType::Count)) => (a.n, b.n, at),
(Default { .. }, bt) => (a.n, b.n, bt), (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
(at, Default { .. }) => (a.n, b.n, at),
(Any, Any) => (a.n, b.n, Any), (Any, Any) => (a.n, b.n, Any),
_ => (a.n, b.n, Unknown), _ => (a.n, b.n, Unknown),
} }
@ -552,18 +562,7 @@ impl NumericType {
(at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)), (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown), (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(at, Known(UnitType::Count) | Any) => (a.n, b.n, at), (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => { (at @ Known(_), Default { .. }) => (a.n, b.n, at),
(a.n, l2.adjust_to(b.n, l1).0, Known(UnitType::Count))
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
(a.n, a2.adjust_to(b.n, a1).0, Known(UnitType::Count))
}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
(l1.adjust_to(a.n, l2).0, b.n, Known(UnitType::Count))
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
(a1.adjust_to(a.n, a2).0, b.n, Known(UnitType::Count))
}
(Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)), (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
_ => (a.n, b.n, Unknown), _ => (a.n, b.n, Unknown),
} }
@ -608,9 +607,18 @@ impl NumericType {
} }
} }
fn is_unknown(&self) -> bool {
matches!(
self,
NumericType::Unknown
| NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
| NumericType::Known(UnitType::Length(UnitLen::Unknown))
)
}
fn example_ty(&self) -> Option<String> { fn example_ty(&self) -> Option<String> {
match self { match self {
Self::Known(t) => Some(t.to_string()), Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
Self::Default { len, .. } => Some(len.to_string()), Self::Default { len, .. } => Some(len.to_string()),
_ => None, _ => None,
} }
@ -621,10 +629,6 @@ impl NumericType {
return Err(val.into()); return Err(val.into());
}; };
if !*CHECK_NUMERIC_TYPES {
return Ok(val.clone());
}
if ty.subtype(self) { if ty.subtype(self) {
return Ok(KclValue::Number { return Ok(KclValue::Number {
value: *value, value: *value,
@ -775,10 +779,10 @@ pub enum UnitLen {
} }
impl UnitLen { impl UnitLen {
fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) { pub fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
use UnitLen::*; use UnitLen::*;
if !*CHECK_NUMERIC_TYPES || self == to { if self == to {
return (value, to); return (value, to);
} }
@ -898,15 +902,11 @@ pub enum UnitAngle {
} }
impl UnitAngle { impl UnitAngle {
fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) { pub fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
use std::f64::consts::PI; use std::f64::consts::PI;
use UnitAngle::*; use UnitAngle::*;
if !*CHECK_NUMERIC_TYPES {
return (value, to);
}
if to == Unknown { if to == Unknown {
return (value, self); return (value, self);
} }
@ -1057,8 +1057,6 @@ impl KclValue {
y_axis, y_axis,
z_axis, z_axis,
value: super::PlaneType::Uninit, value: super::PlaneType::Uninit,
// TODO use length unit from origin
units: exec_state.length_unit(),
meta: meta.clone(), meta: meta.clone(),
}; };
@ -1952,10 +1950,6 @@ mod test {
assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state); assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state); assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
if !*CHECK_NUMERIC_TYPES {
return;
}
assert_eq!( assert_eq!(
default default
.coerce( .coerce(
@ -2068,20 +2062,14 @@ u = min(3rad, 4in)
"#; "#;
let result = parse_execute(program).await.unwrap(); let result = parse_execute(program).await.unwrap();
if *CHECK_NUMERIC_TYPES { assert_eq!(result.exec_state.errors().len(), 5);
assert_eq!(result.exec_state.errors().len(), 3);
} else {
assert!(result.exec_state.errors().is_empty());
}
assert_value_and_type("a", &result, 9.0, NumericType::default()); assert_value_and_type("a", &result, 9.0, NumericType::default());
assert_value_and_type("b", &result, 3.0, NumericType::default()); assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 13.0, NumericType::mm()); assert_value_and_type("c", &result, 13.0, NumericType::mm());
assert_value_and_type("d", &result, 13.0, NumericType::mm()); assert_value_and_type("d", &result, 13.0, NumericType::mm());
assert_value_and_type("e", &result, 13.0, NumericType::mm()); assert_value_and_type("e", &result, 13.0, NumericType::mm());
if *CHECK_NUMERIC_TYPES {
assert_value_and_type("f", &result, 5.0, NumericType::mm()); assert_value_and_type("f", &result, 5.0, NumericType::mm());
}
assert_value_and_type("g", &result, 20.0, NumericType::default()); assert_value_and_type("g", &result, 20.0, NumericType::default());
assert_value_and_type("h", &result, 20.0, NumericType::mm()); assert_value_and_type("h", &result, 20.0, NumericType::mm());
@ -2091,16 +2079,14 @@ u = min(3rad, 4in)
assert_value_and_type("l", &result, 0.0, NumericType::default()); assert_value_and_type("l", &result, 0.0, NumericType::default());
assert_value_and_type("m", &result, 2.0, NumericType::count()); assert_value_and_type("m", &result, 2.0, NumericType::count());
if *CHECK_NUMERIC_TYPES { assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
assert_value_and_type("n", &result, 127.0, NumericType::count()); assert_value_and_type("o", &result, 1.0, NumericType::mm());
}
assert_value_and_type("o", &result, 1.0, NumericType::Unknown);
assert_value_and_type("p", &result, 1.0, NumericType::count()); assert_value_and_type("p", &result, 1.0, NumericType::count());
assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches))); assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
assert_value_and_type("r", &result, 0.0, NumericType::default()); assert_value_and_type("r", &result, 0.0, NumericType::default());
assert_value_and_type("s", &result, -42.0, NumericType::mm()); assert_value_and_type("s", &result, -42.0, NumericType::mm());
assert_value_and_type("t", &result, 3.0, NumericType::Known(UnitType::Length(UnitLen::Inches))); assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
assert_value_and_type("u", &result, 3.0, NumericType::Unknown); assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
} }
@ -2114,7 +2100,24 @@ b = 180 / PI * a + 360
let result = parse_execute(program).await.unwrap(); let result = parse_execute(program).await.unwrap();
assert_value_and_type("a", &result, 1.0, NumericType::radians()); assert_value_and_type("a", &result, 1.0, NumericType::radians());
// TODO type is not ideal assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
assert_value_and_type("b", &result, 417.0, NumericType::radians()); }
#[tokio::test(flavor = "multi_thread")]
async fn cos_coercions() {
let program = r#"
a = cos(toRadians(30))
b = 3 / a
c = cos(30deg)
d = cos(30)
"#;
let result = parse_execute(program).await.unwrap();
assert_eq!(result.exec_state.errors().len(), 1);
assert_value_and_type("a", &result, 1.0, NumericType::count());
assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 1.0, NumericType::count());
assert_value_and_type("d", &result, 0.0, NumericType::count());
} }
} }

View File

@ -1,7 +1,7 @@
use sha2::{Digest as DigestTrait, Sha256}; use sha2::{Digest as DigestTrait, Sha256};
use crate::parsing::ast::types::{ use crate::parsing::ast::types::{
Annotation, ArrayExpression, ArrayRangeExpression, Ascription, BinaryExpression, BinaryPart, BodyItem, Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem,
CallExpression, CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, CallExpression, CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression,
Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression,
Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty,
@ -464,7 +464,7 @@ impl LabelledExpression {
}); });
} }
impl Ascription { impl AscribedExpression {
compute_digest!(|slf, hasher| { compute_digest!(|slf, hasher| {
hasher.update(slf.expr.compute_digest()); hasher.update(slf.expr.compute_digest());
hasher.update(slf.ty.compute_digest()); hasher.update(slf.ty.compute_digest());

View File

@ -819,7 +819,7 @@ pub enum Expr {
UnaryExpression(BoxNode<UnaryExpression>), UnaryExpression(BoxNode<UnaryExpression>),
IfExpression(BoxNode<IfExpression>), IfExpression(BoxNode<IfExpression>),
LabelledExpression(BoxNode<LabelledExpression>), LabelledExpression(BoxNode<LabelledExpression>),
AscribedExpression(BoxNode<Ascription>), AscribedExpression(BoxNode<AscribedExpression>),
None(Node<KclNone>), None(Node<KclNone>),
} }
@ -1092,7 +1092,7 @@ impl LabelledExpression {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub struct Ascription { pub struct AscribedExpression {
pub expr: Expr, pub expr: Expr,
pub ty: Node<Type>, pub ty: Node<Type>,
@ -1101,12 +1101,12 @@ pub struct Ascription {
pub digest: Option<Digest>, pub digest: Option<Digest>,
} }
impl Ascription { impl AscribedExpression {
pub(crate) fn new(expr: Expr, ty: Node<Type>) -> Node<Ascription> { pub(crate) fn new(expr: Expr, ty: Node<Type>) -> Node<AscribedExpression> {
let start = expr.start(); let start = expr.start();
let end = ty.end; let end = ty.end;
let module_id = expr.module_id(); let module_id = expr.module_id();
Node::new(Ascription { expr, ty, digest: None }, start, end, module_id) Node::new(AscribedExpression { expr, ty, digest: None }, start, end, module_id)
} }
} }
@ -3080,7 +3080,7 @@ impl PipeExpression {
#[allow(clippy::large_enum_variant)] #[allow(clippy::large_enum_variant)]
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "p_type")]
pub enum PrimitiveType { pub enum PrimitiveType {
/// A string type. /// A string type.
String, String,

View File

@ -14,7 +14,7 @@ use winnow::{
}; };
use super::{ use super::{
ast::types::{Ascription, ImportPath, LabelledExpression}, ast::types::{AscribedExpression, ImportPath, LabelledExpression},
token::{NumericSuffix, RESERVED_WORDS}, token::{NumericSuffix, RESERVED_WORDS},
DeprecationKind, DeprecationKind,
}; };
@ -530,13 +530,6 @@ pub(crate) fn unsigned_number_literal(i: &mut TokenSlice) -> PResult<Node<Litera
CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value)) CompilationError::fatal(token.as_source_range(), format!("Invalid float: {}", token.value))
})?; })?;
if token.numeric_suffix().is_some() {
ParseContext::warn(CompilationError::err(
(&token).into(),
"Unit of Measure suffixes are experimental and currently do nothing.",
));
}
Ok(( Ok((
LiteralValue::Number { LiteralValue::Number {
value, value,
@ -2021,9 +2014,7 @@ fn expression_but_not_pipe(i: &mut TokenSlice) -> PResult<Expr> {
let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?; let ty = opt((colon, opt(whitespace), argument_type)).parse_next(i)?;
if let Some((_, _, ty)) = ty { if let Some((_, _, ty)) = ty {
ParseContext::warn(CompilationError::err((&ty).into(), "Type ascription is experimental.")); expr = Expr::AscribedExpression(Box::new(AscribedExpression::new(expr, ty)))
expr = Expr::AscribedExpression(Box::new(Ascription::new(expr, ty)))
} }
let label = opt(label).parse_next(i)?; let label = opt(label).parse_next(i)?;
match label { match label {
@ -2817,13 +2808,6 @@ fn primitive_type(i: &mut TokenSlice) -> PResult<Node<PrimitiveType>> {
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id); let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
result.inner = PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident)); result.inner = PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
if suffix.is_some() {
ParseContext::warn(CompilationError::err(
result.as_source_range(),
"Unit of Measure types are experimental and currently do nothing.",
));
}
Ok(result) Ok(result)
} }
@ -4547,19 +4531,19 @@ export fn cos(num: number(rad)): number(_) {}"#;
fn fn_decl_uom_ty() { fn fn_decl_uom_ty() {
let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#; let some_program_string = r#"fn foo(x: number(mm)): number(_) { return 1 }"#;
let (_, errs) = assert_no_fatal(some_program_string); let (_, errs) = assert_no_fatal(some_program_string);
assert_eq!(errs.len(), 2); assert!(errs.is_empty(), "Expected no errors, found: {errs:?}");
} }
#[test] #[test]
fn error_underscore() { fn error_underscore() {
let (_, errs) = assert_no_fatal("_foo(_blah, _)"); let (_, errs) = assert_no_fatal("_foo(_blah, _)");
assert_eq!(errs.len(), 3, "found: {:#?}", errs); assert_eq!(errs.len(), 3, "found: {errs:#?}");
} }
#[test] #[test]
fn error_type_ascription() { fn error_type_ascription() {
let (_, errs) = assert_no_fatal("a + b: number"); let (_, errs) = assert_no_fatal("a + b: number");
assert_eq!(errs.len(), 1, "found: {:#?}", errs); assert!(errs.is_empty());
} }
#[test] #[test]

View File

@ -2600,3 +2600,24 @@ mod import_async {
super::execute(TEST_NAME, true).await super::execute(TEST_NAME, true).await
} }
} }
mod loop_tag {
const TEST_NAME: &str = "loop_tag";
/// Test parsing KCL.
#[test]
fn parse() {
super::parse(TEST_NAME)
}
/// Test that parsing and unparsing KCL produces the original KCL input.
#[tokio::test(flavor = "multi_thread")]
async fn unparse() {
super::unparse(TEST_NAME).await
}
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}
}

View File

@ -11,10 +11,7 @@ use serde::Serialize;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{ execution::{types::RuntimeType, ExecState, KclValue, SolidOrImportedGeometry},
types::{NumericType, PrimitiveType, RuntimeType},
ExecState, KclValue, SolidOrImportedGeometry,
},
std::Args, std::Args,
}; };
@ -50,9 +47,8 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
)?; )?;
let color: String = args.get_kw_arg("color")?; let color: String = args.get_kw_arg("color")?;
let count_ty = RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())); let metalness: Option<TyF64> = args.get_kw_arg_opt_typed("metalness", &RuntimeType::count(), exec_state)?;
let metalness: Option<TyF64> = args.get_kw_arg_opt_typed("metalness", &count_ty, exec_state)?; let roughness: Option<TyF64> = args.get_kw_arg_opt_typed("roughness", &RuntimeType::count(), exec_state)?;
let roughness: Option<TyF64> = args.get_kw_arg_opt_typed("roughness", &count_ty, exec_state)?;
let data = AppearanceData { let data = AppearanceData {
color, color,
metalness, metalness,

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