Compare commits
20 Commits
david/fix-
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
445e18642f | |||
cc2ae13241 | |||
980495c94b | |||
41d946b339 | |||
cdcee56ae2 | |||
4067ff4be8 | |||
b7385d5f25 | |||
3d22f6cd66 | |||
9730e3f5b3 | |||
2570713f6d | |||
29d6b22d63 | |||
f99e44e371 | |||
8be36d3d16 | |||
bc1742af48 | |||
e4080cc184 | |||
18de6ccb59 | |||
f00ea4cf5e | |||
42cd72c9bb | |||
e53de2952a | |||
2a07f5addc |
@ -19,6 +19,11 @@
|
|||||||
"plugin:jsx-a11y/recommended",
|
"plugin:jsx-a11y/recommended",
|
||||||
"plugin:react-hooks/recommended"
|
"plugin:react-hooks/recommended"
|
||||||
],
|
],
|
||||||
|
"settings": {
|
||||||
|
"react": {
|
||||||
|
"version": "detect"
|
||||||
|
}
|
||||||
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
"no-array-constructor": "off", // This is wrong; use the @typescript-eslint one instead.
|
||||||
"@typescript-eslint/no-array-constructor": "error",
|
"@typescript-eslint/no-array-constructor": "error",
|
||||||
@ -61,6 +66,7 @@
|
|||||||
"jsx-a11y/click-events-have-key-events": "off",
|
"jsx-a11y/click-events-have-key-events": "off",
|
||||||
"jsx-a11y/no-autofocus": "off",
|
"jsx-a11y/no-autofocus": "off",
|
||||||
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
"jsx-a11y/no-noninteractive-element-interactions": "off",
|
||||||
|
"react/no-unknown-property": "error",
|
||||||
"no-restricted-globals": [
|
"no-restricted-globals": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
11
.github/ci-cd-scripts/playwright-electron.sh
vendored
@ -26,8 +26,8 @@ max_retries=1
|
|||||||
# Retry failed tests, doing our own retries because using inbuilt Playwright retries causes connection issues
|
# Retry failed tests, doing our own retries because using inbuilt Playwright retries causes connection issues
|
||||||
while [[ $retry -le $max_retries ]]; do
|
while [[ $retry -le $max_retries ]]; do
|
||||||
if [[ -f "test-results/.last-run.json" ]]; then
|
if [[ -f "test-results/.last-run.json" ]]; then
|
||||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
status=$(jq -r '.status' test-results/.last-run.json)
|
||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ "$status" == "failed" ]]; then
|
||||||
echo "retried=true" >>$GITHUB_OUTPUT
|
echo "retried=true" >>$GITHUB_OUTPUT
|
||||||
echo "run playwright with last failed tests and retry $retry"
|
echo "run playwright with last failed tests and retry $retry"
|
||||||
if [[ "$3" == *ubuntu* ]]; then
|
if [[ "$3" == *ubuntu* ]]; then
|
||||||
@ -56,10 +56,11 @@ done
|
|||||||
echo "retried=false" >>$GITHUB_OUTPUT
|
echo "retried=false" >>$GITHUB_OUTPUT
|
||||||
|
|
||||||
if [[ -f "test-results/.last-run.json" ]]; then
|
if [[ -f "test-results/.last-run.json" ]]; then
|
||||||
failed_tests=$(jq '.failedTests | length' test-results/.last-run.json)
|
status=$(jq -r '.status' test-results/.last-run.json)
|
||||||
if [[ $failed_tests -gt 0 ]]; then
|
if [[ "$status" == "failed" ]]; then
|
||||||
# If it still fails after 3 retries, then fail the job
|
# If it still fails after retries, then fail the job
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
2
.github/workflows/e2e-tests.yml
vendored
@ -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:
|
||||||
|
40
docs/kcl/assertIs.md
Normal 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`.
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from feet to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from feet to the current default unit.
|
Converts a number from feet to the current default unit.
|
||||||
|
|
||||||
|
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42ft`) or the `to...` conversion functions.
|
||||||
|
|
||||||
No matter what units the current file uses, this function will always return a number equivalent to the input in feet.
|
No matter what units the current file uses, this function will always return a number equivalent to the input in feet.
|
||||||
|
|
||||||
For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`.
|
For example, if the current file uses inches, `fromFt(1)` will return `12`. If the current file uses millimeters, `fromFt(1)` will return `304.8`. If the current file uses feet, `fromFt(1)` will return `1`.
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from inches to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from inches to the current default unit.
|
Converts a number from inches to the current default unit.
|
||||||
|
|
||||||
|
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42inch`) or the `to...` conversion functions.
|
||||||
|
|
||||||
No matter what units the current file uses, this function will always return a number equivalent to the input in inches.
|
No matter what units the current file uses, this function will always return a number equivalent to the input in inches.
|
||||||
|
|
||||||
For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`.
|
For example, if the current file uses inches, `fromInches(1)` will return `1`. If the current file uses millimeters, `fromInches(1)` will return `25.4`.
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from meters to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from meters to the current default unit.
|
Converts a number from meters to the current default unit.
|
||||||
|
|
||||||
|
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42m`) or the `to...` conversion functions.
|
||||||
|
|
||||||
No matter what units the current file uses, this function will always return a number equivalent to the input in meters.
|
No matter what units the current file uses, this function will always return a number equivalent to the input in meters.
|
||||||
|
|
||||||
For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`.
|
For example, if the current file uses inches, `fromM(1)` will return `39.3701`. If the current file uses millimeters, `fromM(1)` will return `1000`. If the current file uses meters, `fromM(1)` will return `1`.
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from mm to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from mm to the current default unit.
|
Converts a number from mm to the current default unit.
|
||||||
|
|
||||||
|
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42mm`) or the `to...` conversion functions.
|
||||||
|
|
||||||
No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters.
|
No matter what units the current file uses, this function will always return a number equivalent to the input in millimeters.
|
||||||
|
|
||||||
For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`.
|
For example, if the current file uses inches, `fromMm(1)` will return `1/25.4`. If the current file uses millimeters, `fromMm(1)` will return `1`.
|
||||||
|
@ -4,8 +4,12 @@ excerpt: "Converts a number from yards to the current default unit."
|
|||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
|
**WARNING:** This function is deprecated.
|
||||||
|
|
||||||
Converts a number from yards to the current default unit.
|
Converts a number from yards to the current default unit.
|
||||||
|
|
||||||
|
*DEPRECATED* prefer using explicit numeric suffixes (e.g., `42yd`) or the `to...` conversion functions.
|
||||||
|
|
||||||
No matter what units the current file uses, this function will always return a number equivalent to the input in yards.
|
No matter what units the current file uses, this function will always return a number equivalent to the input in yards.
|
||||||
|
|
||||||
For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`.
|
For example, if the current file uses inches, `fromYd(1)` will return `36`. If the current file uses millimeters, `fromYd(1)` will return `914.4`. If the current file uses yards, `fromYd(1)` will return `1`.
|
||||||
|
@ -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)
|
||||||
|
@ -34,7 +34,7 @@ int(num: number): number
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
n = int(ceil(5 / 2))
|
n = int(ceil(5 / 2))
|
||||||
assertEqual(n, 3, 0.0001, "5/2 = 2.5, rounded up makes 3")
|
assert(n, isEqualTo = 3, error = "5/2 = 2.5, rounded up makes 3")
|
||||||
// Draw n cylinders.
|
// Draw n cylinders.
|
||||||
startSketchOn(XZ)
|
startSketchOn(XZ)
|
||||||
|> circle(center = [0, 0], radius = 2)
|
|> circle(center = [0, 0], radius = 2)
|
||||||
|
@ -21,4 +21,7 @@ once fixed in engine will just start working here with no language changes.
|
|||||||
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
- **Chamfers**: Chamfers cannot intersect, you will get an error. Only simple
|
||||||
chamfer cases work currently.
|
chamfer cases work currently.
|
||||||
|
|
||||||
- **Appearance**: Changing the appearance on a loft does not work.
|
- **Appearance**: Changing the appearance on a loft does not work.
|
||||||
|
Changing the appearance on an imported model does not work.
|
||||||
|
|
||||||
|
- **CSG Booleans**: Coplanar (bodies that share a plane) unions, subtractions, and intersections are not currently supported.
|
||||||
|
@ -174,7 +174,7 @@ t = 0.005 // taper factor [0-1)
|
|||||||
// Defines how to modify each layer of the vase.
|
// 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]
|
||||||
|
@ -26,7 +26,7 @@ helix(
|
|||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `revolutions` | `number(_)` | Number of revolutions. | Yes |
|
| `revolutions` | `number(_)` | Number of revolutions. | Yes |
|
||||||
| `angleStart` | `number(Angle)` | Start angle (in degrees). | Yes |
|
| `angleStart` | `number(Angle)` | Start angle. | Yes |
|
||||||
| `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No |
|
| `ccw` | [`bool`](/docs/kcl/types/bool) | Is the helix rotation counter clockwise? The default is `false`. | No |
|
||||||
| `radius` | `number(Length)` | Radius of the helix. | No |
|
| `radius` | `number(Length)` | Radius of the helix. | No |
|
||||||
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
| `axis` | `Axis3d | Edge` | Axis to use for the helix. | No |
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 30,
|
angle = 30,
|
||||||
length = 3 / cos(toRadians(30)),
|
length = 3 / cos(30deg),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
@ -11,7 +11,7 @@ cartesian (x/y/z grid) coordinates.
|
|||||||
|
|
||||||
```js
|
```js
|
||||||
polar(
|
polar(
|
||||||
angle: number(Angle),
|
angle: number(rad),
|
||||||
length: number(Length),
|
length: number(Length),
|
||||||
): Point2d
|
): Point2d
|
||||||
```
|
```
|
||||||
@ -21,7 +21,7 @@ polar(
|
|||||||
|
|
||||||
| Name | Type | Description | Required |
|
| Name | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `angle` | `number(Angle)` | | Yes |
|
| `angle` | `number(rad)` | | Yes |
|
||||||
| `length` | `number(Length)` | | Yes |
|
| `length` | `number(Length)` | | Yes |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 50,
|
angle = 50,
|
||||||
length = 15 / sin(toRadians(135)),
|
length = 15 / sin(135deg),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
@ -31,7 +31,7 @@ exampleSketch = startSketchOn(XZ)
|
|||||||
|> startProfileAt([0, 0], %)
|
|> startProfileAt([0, 0], %)
|
||||||
|> angledLine(
|
|> angledLine(
|
||||||
angle = 50,
|
angle = 50,
|
||||||
length = 50 * tan(1/2),
|
length = 50 * tan((1/2): number(rad)),
|
||||||
)
|
)
|
||||||
|> yLine(endAbsolute = 0)
|
|> yLine(endAbsolute = 0)
|
||||||
|> close()
|
|> close()
|
||||||
|
27
docs/kcl/std-toCentimeters.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toCentimeters"
|
||||||
|
excerpt: "Convert a number to centimeters from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Convert a number to centimeters from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toCentimeters(@num: number(cm)): number(cm)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(cm)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(cm)`
|
||||||
|
|
||||||
|
|
||||||
|
|
44
docs/kcl/std-toDegrees.md
Normal file
27
docs/kcl/std-toFeet.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toFeet"
|
||||||
|
excerpt: "Convert a number to feet from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Convert a number to feet from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toFeet(@num: number(ft)): number(ft)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(ft)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(ft)`
|
||||||
|
|
||||||
|
|
||||||
|
|
27
docs/kcl/std-toInches.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toInches"
|
||||||
|
excerpt: "Convert a number to inches from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Convert a number to inches from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toInches(@num: number(in)): number(in)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(in)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(in)`
|
||||||
|
|
||||||
|
|
||||||
|
|
27
docs/kcl/std-toMeters.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toMeters"
|
||||||
|
excerpt: "Convert a number to meters from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Convert a number to meters from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toMeters(@num: number(m)): number(m)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(m)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(m)`
|
||||||
|
|
||||||
|
|
||||||
|
|
27
docs/kcl/std-toMillimeters.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toMillimeters"
|
||||||
|
excerpt: "Convert a number to millimeters from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Convert a number to millimeters from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toMillimeters(@num: number(mm)): number(mm)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(mm)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(mm)`
|
||||||
|
|
||||||
|
|
||||||
|
|
44
docs/kcl/std-toRadians.md
Normal file
27
docs/kcl/std-toYards.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "std::toYards"
|
||||||
|
excerpt: "Converts a number to yards from its current units."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Converts a number to yards from its current units.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
toYards(@num: number(yd)): number(yd)
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
|
||||||
|
| Name | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `num` | `number(yd)` | | Yes |
|
||||||
|
|
||||||
|
### Returns
|
||||||
|
|
||||||
|
`number(yd)`
|
||||||
|
|
||||||
|
|
||||||
|
|
1842
docs/kcl/std.json
@ -31,7 +31,6 @@ A sketch type.
|
|||||||
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane's X axis be? | No |
|
| `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 |
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
|
@ -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 =
|
||||||
|
53
e2e/playwright/auth.spec.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
|
||||||
|
// test file is for testing auth functionality
|
||||||
|
test.describe('Authentication tests', () => {
|
||||||
|
test(
|
||||||
|
`The user can sign out and back in`,
|
||||||
|
{ tag: ['@electron'] },
|
||||||
|
async ({ page, homePage, signInPage, toolbar, tronApp }) => {
|
||||||
|
if (!tronApp) {
|
||||||
|
fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
await homePage.projectSection.waitFor()
|
||||||
|
|
||||||
|
await test.step('Click on sign out and expect sign in page', async () => {
|
||||||
|
await toolbar.userSidebarButton.click()
|
||||||
|
await toolbar.signOutButton.click()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Click on sign in and cancel, click again and expect different code', async () => {
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const firstUserCode = await signInPage.userCode.textContent()
|
||||||
|
await signInPage.cancelSignInButton.click()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const secondUserCode = await signInPage.userCode.textContent()
|
||||||
|
expect(secondUserCode).not.toEqual(firstUserCode)
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Press back button and remain on home page', async () => {
|
||||||
|
await page.goBack()
|
||||||
|
await expect(homePage.projectSection).not.toBeVisible()
|
||||||
|
await expect(signInPage.signInButton).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
await test.step('Sign in, activate, and expect home page', async () => {
|
||||||
|
await signInPage.signInButton.click()
|
||||||
|
await expect(signInPage.userCode).toBeVisible()
|
||||||
|
const userCode = await signInPage.userCode.textContent()
|
||||||
|
expect(userCode).not.toBeNull()
|
||||||
|
await signInPage.verifyAndConfirmAuth(userCode!)
|
||||||
|
|
||||||
|
// Longer timeout than usual here for the wait on home page
|
||||||
|
await expect(homePage.projectSection).toBeVisible({ timeout: 10000 })
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
@ -18,6 +18,7 @@ import type { Settings } from '@rust/kcl-lib/bindings/Settings'
|
|||||||
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
import { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
import { EditorFixture } from '@e2e/playwright/fixtures/editorFixture'
|
||||||
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
import { HomePageFixture } from '@e2e/playwright/fixtures/homePageFixture'
|
||||||
|
import { SignInPageFixture } from '@e2e/playwright/fixtures/signInPageFixture'
|
||||||
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
import { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
|
||||||
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
import { ToolbarFixture } from '@e2e/playwright/fixtures/toolbarFixture'
|
||||||
|
|
||||||
@ -66,6 +67,7 @@ export interface Fixtures {
|
|||||||
toolbar: ToolbarFixture
|
toolbar: ToolbarFixture
|
||||||
scene: SceneFixture
|
scene: SceneFixture
|
||||||
homePage: HomePageFixture
|
homePage: HomePageFixture
|
||||||
|
signInPage: SignInPageFixture
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ElectronZoo {
|
export class ElectronZoo {
|
||||||
@ -387,6 +389,9 @@ const fixturesBasedOnProcessEnvPlatform = {
|
|||||||
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
homePage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
await use(new HomePageFixture(page))
|
await use(new HomePageFixture(page))
|
||||||
},
|
},
|
||||||
|
signInPage: async ({ page }: { page: Page }, use: FnUse) => {
|
||||||
|
await use(new SignInPageFixture(page))
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process.env.PLATFORM === 'web') {
|
if (process.env.PLATFORM === 'web') {
|
||||||
|
48
e2e/playwright/fixtures/signInPageFixture.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
import { secrets } from '@e2e/playwright/secrets'
|
||||||
|
|
||||||
|
export class SignInPageFixture {
|
||||||
|
public page: Page
|
||||||
|
|
||||||
|
signInButton!: Locator
|
||||||
|
cancelSignInButton!: Locator
|
||||||
|
userCode!: Locator
|
||||||
|
|
||||||
|
apiBaseUrl!: string
|
||||||
|
|
||||||
|
constructor(page: Page) {
|
||||||
|
this.page = page
|
||||||
|
|
||||||
|
this.signInButton = this.page.getByTestId('sign-in-button')
|
||||||
|
this.cancelSignInButton = this.page.getByTestId('cancel-sign-in-button')
|
||||||
|
this.userCode = this.page.getByTestId('sign-in-user-code')
|
||||||
|
|
||||||
|
// TODO: set this thru env var
|
||||||
|
this.apiBaseUrl = 'https://api.dev.zoo.dev'
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyAndConfirmAuth(userCode: string) {
|
||||||
|
// Device flow: stolen from the tauri days
|
||||||
|
// https://github.com/KittyCAD/modeling-app/blob/d916c7987452e480719004e6d11fd2e595c7d0eb/e2e/tauri/specs/app.spec.ts#L19
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${secrets.token}`,
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
const verifyUrl = `${this.apiBaseUrl}/oauth2/device/verify?user_code=${userCode}`
|
||||||
|
console.log(`GET ${verifyUrl}`)
|
||||||
|
const vr = await fetch(verifyUrl, { headers })
|
||||||
|
console.log(vr.status)
|
||||||
|
|
||||||
|
// Device flow: confirm
|
||||||
|
const confirmUrl = `${this.apiBaseUrl}/oauth2/device/confirm`
|
||||||
|
const data = JSON.stringify({ user_code: userCode })
|
||||||
|
console.log(`POST ${confirmUrl} ${data}`)
|
||||||
|
const cr = await fetch(confirmUrl, {
|
||||||
|
headers,
|
||||||
|
method: 'POST',
|
||||||
|
body: data,
|
||||||
|
})
|
||||||
|
console.log(cr.status)
|
||||||
|
}
|
||||||
|
}
|
@ -46,6 +46,9 @@ export class ToolbarFixture {
|
|||||||
gizmo!: Locator
|
gizmo!: Locator
|
||||||
gizmoDisabled!: Locator
|
gizmoDisabled!: Locator
|
||||||
loadButton!: Locator
|
loadButton!: Locator
|
||||||
|
/** User button for the user sidebar menu */
|
||||||
|
userSidebarButton!: Locator
|
||||||
|
signOutButton!: Locator
|
||||||
|
|
||||||
constructor(page: Page) {
|
constructor(page: Page) {
|
||||||
this.page = page
|
this.page = page
|
||||||
@ -82,6 +85,9 @@ export class ToolbarFixture {
|
|||||||
// element or two different elements can represent these states.
|
// element or two different elements can represent these states.
|
||||||
this.gizmo = page.getByTestId('gizmo')
|
this.gizmo = page.getByTestId('gizmo')
|
||||||
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
this.gizmoDisabled = page.getByTestId('gizmo-disabled')
|
||||||
|
|
||||||
|
this.userSidebarButton = page.getByTestId('user-sidebar-toggle')
|
||||||
|
this.signOutButton = page.getByTestId('user-sidebar-sign-out')
|
||||||
}
|
}
|
||||||
|
|
||||||
get logoLink() {
|
get logoLink() {
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const error = await response.json()
|
||||||
|
if (!process.env.CI) {
|
||||||
|
console.error('TAB API - Failed to send test result:', error)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error)
|
||||||
if (!process.env.CI) {
|
if (!process.env.CI) {
|
||||||
console.error('TAB API - Unable to send test result')
|
console.error('TAB API - Unable to send test result:', message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
this.pendingRequests.push(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,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',
|
||||||
})
|
})
|
||||||
|
@ -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 }
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 64 KiB |
@ -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 })
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
|
@ -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")
|
||||||
|
@ -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],
|
||||||
|
@ -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,
|
||||||
|
@ -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.
|
||||||
exec_state.mut_stack().add(
|
|
||||||
param.identifier.name.clone(),
|
if let Some(ty) = ¶m.type_ {
|
||||||
arg.value.clone(),
|
let value = arg
|
||||||
(¶m.identifier).into(),
|
.value
|
||||||
)?;
|
.coerce(
|
||||||
|
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
|
||||||
|
exec_state,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
let mut message = format!(
|
||||||
|
"Argument requires a value with type `{}`, but found {}",
|
||||||
|
ty.inner,
|
||||||
|
arg.value.human_friendly_type(),
|
||||||
|
);
|
||||||
|
if let Some(ty) = e.explicit_coercion {
|
||||||
|
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||||
|
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||||
|
}
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message,
|
||||||
|
source_ranges: vec![arg.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
exec_state
|
||||||
|
.mut_stack()
|
||||||
|
.add(param.identifier.name.clone(), value, (¶m.identifier).into())?;
|
||||||
|
} else {
|
||||||
|
exec_state.mut_stack().add(
|
||||||
|
param.identifier.name.clone(),
|
||||||
|
arg.value.clone(),
|
||||||
|
(¶m.identifier).into(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
} else {
|
} 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 {
|
||||||
|
@ -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 { .. } => {
|
||||||
|
@ -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![],
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
@ -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())],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -1830,18 +1835,18 @@ const bracket = startSketchOn(XY)
|
|||||||
#[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)
|
||||||
|
@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
@ -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,
|
||||||
|
@ -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]
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -14,7 +14,7 @@ use crate::{
|
|||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
kcl_value::FunctionSource,
|
kcl_value::FunctionSource,
|
||||||
types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
|
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitLen, UnitType},
|
||||||
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface,
|
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, Sketch, SketchSurface,
|
||||||
Solid, TagIdentifier,
|
Solid, TagIdentifier,
|
||||||
},
|
},
|
||||||
@ -88,6 +88,34 @@ impl TyF64 {
|
|||||||
Self { n, ty }
|
Self { n, ty }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_mm(&self) -> f64 {
|
||||||
|
self.to_length_units(UnitLen::Mm)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_length_units(&self, units: UnitLen) -> f64 {
|
||||||
|
let len = match &self.ty {
|
||||||
|
NumericType::Default { len, .. } => *len,
|
||||||
|
NumericType::Known(UnitType::Length(len)) => *len,
|
||||||
|
t => unreachable!("expected length, found {t:?}"),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ne!(len, UnitLen::Unknown);
|
||||||
|
|
||||||
|
len.adjust_to(self.n, units).0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_degrees(&self) -> f64 {
|
||||||
|
let angle = match self.ty {
|
||||||
|
NumericType::Default { angle, .. } => angle,
|
||||||
|
NumericType::Known(UnitType::Angle(angle)) => angle,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_ne!(angle, UnitAngle::Unknown);
|
||||||
|
|
||||||
|
angle.adjust_to(self.n, UnitAngle::Degrees).0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn count(n: f64) -> Self {
|
pub fn count(n: f64) -> Self {
|
||||||
Self {
|
Self {
|
||||||
n,
|
n,
|
||||||
@ -547,6 +575,29 @@ impl Args {
|
|||||||
FromArgs::from_args(self, 0)
|
FromArgs::from_args(self, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn get_number_typed(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<f64, KclError> {
|
||||||
|
let Some(arg) = self.args.first() else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Expected an argument".to_owned(),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
arg.value.coerce(ty, exec_state).map_err(|_| {
|
||||||
|
let actual_type_name = arg.value.human_friendly_type();
|
||||||
|
let message = format!(
|
||||||
|
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
||||||
|
ty.human_friendly_type(),
|
||||||
|
);
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: arg.source_ranges(),
|
||||||
|
message,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(TyF64::from_kcl_val(&arg.value).unwrap().n)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
|
pub(crate) fn get_number_array_with_types(&self) -> Result<Vec<TyF64>, KclError> {
|
||||||
let numbers = self
|
let numbers = self
|
||||||
.args
|
.args
|
||||||
@ -577,7 +628,7 @@ impl Args {
|
|||||||
let mut numbers = numbers.into_iter();
|
let mut numbers = numbers.into_iter();
|
||||||
let a = numbers.next().unwrap();
|
let a = numbers.next().unwrap();
|
||||||
let b = numbers.next().unwrap();
|
let b = numbers.next().unwrap();
|
||||||
Ok(NumericType::combine_eq(a, b))
|
Ok(NumericType::combine_eq_coerce(a, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
|
pub(crate) fn get_sketches(&self, exec_state: &mut ExecState) -> Result<(Vec<Sketch>, Sketch), KclError> {
|
||||||
@ -657,8 +708,24 @@ impl Args {
|
|||||||
FromArgs::from_args(self, 0)
|
FromArgs::from_args(self, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn get_data_and_solid(&self, exec_state: &mut ExecState) -> Result<(TyF64, Box<Solid>), KclError> {
|
pub(crate) fn get_length_and_solid(&self, exec_state: &mut ExecState) -> Result<(TyF64, Box<Solid>), KclError> {
|
||||||
let data = FromArgs::from_args(self, 0)?;
|
let Some(arg0) = self.args.first() else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "Expected a `number(Length)` for first argument".to_owned(),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let val0 = arg0.value.coerce(&RuntimeType::length(), exec_state).map_err(|_| {
|
||||||
|
KclError::Type(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"Expected a `number(Length)` for first argument, found {}",
|
||||||
|
arg0.value.human_friendly_type()
|
||||||
|
),
|
||||||
|
source_ranges: vec![self.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
let data = TyF64::from_kcl_val(&val0).unwrap();
|
||||||
|
|
||||||
let Some(arg1) = self.args.get(1) else {
|
let Some(arg1) = self.args.get(1) else {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message: "Expected a solid for second argument".to_owned(),
|
message: "Expected a solid for second argument".to_owned(),
|
||||||
|
@ -115,9 +115,9 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// return sumSoFar
|
/// return sumSoFar
|
||||||
/// */
|
/// */
|
||||||
///
|
///
|
||||||
/// // We use `assertEqual` to check that our `sum` function gives the
|
/// // We use `assert` to check that our `sum` function gives the
|
||||||
/// // expected result. It's good to check your work!
|
/// // expected result. It's good to check your work!
|
||||||
/// assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
|
/// assert(sum([1, 2, 3]), isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // This example works just like the previous example above, but it uses
|
/// // This example works just like the previous example above, but it uses
|
||||||
@ -126,9 +126,9 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
/// arr = [1, 2, 3]
|
/// arr = [1, 2, 3]
|
||||||
/// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
|
/// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
|
||||||
///
|
///
|
||||||
/// // We use `assertEqual` to check that our `sum` function gives the
|
/// // We use `assert` to check that our `sum` function gives the
|
||||||
/// // expected result. It's good to check your work!
|
/// // expected result. It's good to check your work!
|
||||||
/// assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
|
/// assert(sum, isEqualTo = 6, tolerance = 0.1, error = "1 + 2 + 3 summed is 6")
|
||||||
/// ```
|
/// ```
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// // Declare a function that sketches a decagon.
|
/// // Declare a function that sketches a decagon.
|
||||||
@ -224,7 +224,7 @@ async fn call_reduce_closure(
|
|||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// arr = [1, 2, 3]
|
/// arr = [1, 2, 3]
|
||||||
/// new_arr = push(arr, 4)
|
/// new_arr = push(arr, 4)
|
||||||
/// assertEqual(new_arr[3], 4, 0.00001, "4 was added to the end of the array")
|
/// assert(new_arr[3], isEqualTo = 4, tolerance = 0.1, error = "4 was added to the end of the array")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "push",
|
name = "push",
|
||||||
@ -260,9 +260,9 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// arr = [1, 2, 3, 4]
|
/// arr = [1, 2, 3, 4]
|
||||||
/// new_arr = pop(arr)
|
/// new_arr = pop(arr)
|
||||||
/// assertEqual(new_arr[0], 1, 0.00001, "1 is the first element of the array")
|
/// assert(new_arr[0], isEqualTo = 1, tolerance = 0.00001, error = "1 is the first element of the array")
|
||||||
/// assertEqual(new_arr[1], 2, 0.00001, "2 is the second element of the array")
|
/// assert(new_arr[1], isEqualTo = 2, tolerance = 0.00001, error = "2 is the second element of the array")
|
||||||
/// assertEqual(new_arr[2], 3, 0.00001, "3 is the third element of the array")
|
/// assert(new_arr[2], isEqualTo = 3, tolerance = 0.00001, error = "3 is the third element of the array")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "pop",
|
name = "pop",
|
||||||
|
@ -21,135 +21,165 @@ async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn assert_is(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
|
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||||
|
let error = args.get_kw_arg_opt("error")?;
|
||||||
|
inner_assert_is(actual, error, &args).await?;
|
||||||
|
Ok(KclValue::none())
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that the provided value is true, or raise a [KclError]
|
/// Check that the provided value is true, or raise a [KclError]
|
||||||
/// with the provided description.
|
/// with the provided description.
|
||||||
pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn assert(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let (data, description): (bool, String) = args.get_data()?;
|
let actual = args.get_unlabeled_kw_arg("actual")?;
|
||||||
inner_assert(data, &description, &args).await?;
|
let gt = args.get_kw_arg_opt("isGreaterThan")?;
|
||||||
|
let lt = args.get_kw_arg_opt("isLessThan")?;
|
||||||
|
let gte = args.get_kw_arg_opt("isGreaterThanOrEqual")?;
|
||||||
|
let lte = args.get_kw_arg_opt("isLessThanOrEqual")?;
|
||||||
|
let eq = args.get_kw_arg_opt("isEqualTo")?;
|
||||||
|
let tolerance = args.get_kw_arg_opt("tolerance")?;
|
||||||
|
let error = args.get_kw_arg_opt("error")?;
|
||||||
|
inner_assert(actual, gt, lt, gte, lte, eq, tolerance, error, &args).await?;
|
||||||
Ok(KclValue::none())
|
Ok(KclValue::none())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check a value at runtime, and raise an error if the argument provided
|
/// Asserts that a value is the boolean value true.
|
||||||
/// is false.
|
/// ```no_run
|
||||||
|
/// kclIsFun = true
|
||||||
|
/// assertIs(kclIsFun)
|
||||||
|
/// ```
|
||||||
|
#[stdlib{
|
||||||
|
name = "assertIs",
|
||||||
|
keywords = true,
|
||||||
|
unlabeled_first = true,
|
||||||
|
args = {
|
||||||
|
actual = { docs = "Value to check. If this is the boolean value true, assert passes. Otherwise it fails." },
|
||||||
|
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
async fn inner_assert_is(actual: bool, error: Option<String>, args: &Args) -> Result<(), KclError> {
|
||||||
|
let error_msg = match &error {
|
||||||
|
Some(x) => x,
|
||||||
|
None => "should have been true, but it was not",
|
||||||
|
};
|
||||||
|
_assert(actual, error_msg, args).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check a value meets some expected conditions at runtime. Program terminates with an error if conditions aren't met.
|
||||||
|
/// If you provide multiple conditions, they will all be checked and all must be met.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// myVar = true
|
/// n = 10
|
||||||
/// assert(myVar, "should always be true")
|
/// assert(n, isEqualTo = 10)
|
||||||
|
/// assert(n, isGreaterThanOrEqual = 0, isLessThan = 100, error = "number should be between 0 and 100")
|
||||||
|
/// assert(1.0000000000012, isEqualTo = 1, tolerance = 0.0001, error = "number should be almost exactly 1")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "assert",
|
name = "assert",
|
||||||
}]
|
keywords = true,
|
||||||
async fn inner_assert(data: bool, message: &str, args: &Args) -> Result<(), KclError> {
|
unlabeled_first = true,
|
||||||
_assert(data, message, args).await
|
args = {
|
||||||
}
|
actual = { docs = "Value to check. It will be compared with one of the comparison arguments." },
|
||||||
|
is_greater_than = { docs = "Comparison argument. If given, checks the `actual` value is greater than this." },
|
||||||
pub async fn assert_lt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
is_less_than = { docs = "Comparison argument. If given, checks the `actual` value is less than this." },
|
||||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
is_greater_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is greater than or equal to this." },
|
||||||
inner_assert_lt(left.n, right.n, &description, &args).await?;
|
is_less_than_or_equal = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this." },
|
||||||
Ok(KclValue::none())
|
is_equal_to = { docs = "Comparison argument. If given, checks the `actual` value is less than or equal to this.", include_in_snippet = true },
|
||||||
}
|
tolerance = { docs = "If `isEqualTo` is used, this is the tolerance to allow for the comparison. This tolerance is used because KCL's number system has some floating-point imprecision when used with very large decimal places." },
|
||||||
|
error = { docs = "If the value was false, the program will terminate with this error message" },
|
||||||
/// Check that a numerical value is less than to another at runtime,
|
|
||||||
/// otherwise raise an error.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// assertLessThan(1, 2, "1 is less than 2")
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "assertLessThan",
|
|
||||||
}]
|
|
||||||
async fn inner_assert_lt(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
|
||||||
_assert(left < right, message, args).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn assert_gt(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
||||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
|
||||||
inner_assert_gt(left.n, right.n, &description, &args).await?;
|
|
||||||
Ok(KclValue::none())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that a numerical value equals another at runtime,
|
|
||||||
/// otherwise raise an error.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// n = 1.0285
|
|
||||||
/// o = 1.0286
|
|
||||||
/// assertEqual(n, o, 0.01, "n is within the given tolerance for o")
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "assertEqual",
|
|
||||||
}]
|
|
||||||
async fn inner_assert_equal(left: f64, right: f64, epsilon: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
|
||||||
if epsilon <= 0.0 {
|
|
||||||
Err(KclError::Type(KclErrorDetails {
|
|
||||||
message: "assertEqual epsilon must be greater than zero".to_owned(),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
}))
|
|
||||||
} else if (right - left).abs() < epsilon {
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(KclError::Type(KclErrorDetails {
|
|
||||||
message: format!("assert failed because {left} != {right}: {message}"),
|
|
||||||
source_ranges: vec![args.source_range],
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn assert_equal(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
||||||
let (left, right, epsilon, description): (TyF64, TyF64, TyF64, String) = args.get_data()?;
|
|
||||||
inner_assert_equal(left.n, right.n, epsilon.n, &description, &args).await?;
|
|
||||||
Ok(KclValue::none())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check that a numerical value is greater than another at runtime,
|
|
||||||
/// otherwise raise an error.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// assertGreaterThan(2, 1, "2 is greater than 1")
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "assertGreaterThan",
|
|
||||||
}]
|
}]
|
||||||
async fn inner_assert_gt(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
#[allow(clippy::too_many_arguments)]
|
||||||
_assert(left > right, message, args).await
|
async fn inner_assert(
|
||||||
}
|
actual: TyF64,
|
||||||
|
is_greater_than: Option<TyF64>,
|
||||||
|
is_less_than: Option<TyF64>,
|
||||||
|
is_greater_than_or_equal: Option<TyF64>,
|
||||||
|
is_less_than_or_equal: Option<TyF64>,
|
||||||
|
is_equal_to: Option<TyF64>,
|
||||||
|
tolerance: Option<TyF64>,
|
||||||
|
error: Option<String>,
|
||||||
|
args: &Args,
|
||||||
|
) -> Result<(), KclError> {
|
||||||
|
// Validate the args
|
||||||
|
let no_condition_given = [
|
||||||
|
&is_greater_than,
|
||||||
|
&is_less_than,
|
||||||
|
&is_greater_than_or_equal,
|
||||||
|
&is_less_than_or_equal,
|
||||||
|
&is_equal_to,
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.all(|cond| cond.is_none());
|
||||||
|
if no_condition_given {
|
||||||
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
|
message: "You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn assert_lte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
if tolerance.is_some() && is_equal_to.is_none() {
|
||||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
return Err(KclError::Type(KclErrorDetails {
|
||||||
inner_assert_lte(left.n, right.n, &description, &args).await?;
|
message:
|
||||||
Ok(KclValue::none())
|
"The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
|
||||||
}
|
.to_owned(),
|
||||||
|
source_ranges: vec![args.source_range],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/// Check that a numerical value is less than or equal to another at runtime,
|
let suffix = if let Some(err_string) = error {
|
||||||
/// otherwise raise an error.
|
format!(": {err_string}")
|
||||||
///
|
} else {
|
||||||
/// ```no_run
|
Default::default()
|
||||||
/// assertLessThanOrEq(1, 2, "1 is less than 2")
|
};
|
||||||
/// assertLessThanOrEq(1, 1, "1 is equal to 1")
|
let actual = actual.n;
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "assertLessThanOrEq",
|
|
||||||
}]
|
|
||||||
async fn inner_assert_lte(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
|
||||||
_assert(left <= right, message, args).await
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn assert_gte(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
// Run the checks.
|
||||||
let (left, right, description): (TyF64, TyF64, String) = args.get_data()?;
|
if let Some(exp) = is_greater_than {
|
||||||
inner_assert_gte(left.n, right.n, &description, &args).await?;
|
let exp = exp.n;
|
||||||
Ok(KclValue::none())
|
_assert(
|
||||||
}
|
actual > exp,
|
||||||
|
&format!("Expected {actual} to be greater than {exp} but it wasn't{suffix}"),
|
||||||
/// Check that a numerical value is greater than or equal to another at runtime,
|
args,
|
||||||
/// otherwise raise an error.
|
)
|
||||||
///
|
.await?;
|
||||||
/// ```no_run
|
}
|
||||||
/// assertGreaterThanOrEq(2, 1, "2 is greater than 1")
|
if let Some(exp) = is_less_than {
|
||||||
/// assertGreaterThanOrEq(1, 1, "1 is equal to 1")
|
let exp = exp.n;
|
||||||
/// ```
|
_assert(
|
||||||
#[stdlib {
|
actual < exp,
|
||||||
name = "assertGreaterThanOrEq",
|
&format!("Expected {actual} to be less than {exp} but it wasn't{suffix}"),
|
||||||
}]
|
args,
|
||||||
async fn inner_assert_gte(left: f64, right: f64, message: &str, args: &Args) -> Result<(), KclError> {
|
)
|
||||||
_assert(left >= right, message, args).await
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(exp) = is_greater_than_or_equal {
|
||||||
|
let exp = exp.n;
|
||||||
|
_assert(
|
||||||
|
actual >= exp,
|
||||||
|
&format!("Expected {actual} to be greater than or equal to {exp} but it wasn't{suffix}"),
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(exp) = is_less_than_or_equal {
|
||||||
|
let exp = exp.n;
|
||||||
|
_assert(
|
||||||
|
actual <= exp,
|
||||||
|
&format!("Expected {actual} to be less than or equal to {exp} but it wasn't{suffix}"),
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
if let Some(exp) = is_equal_to {
|
||||||
|
let exp = exp.n;
|
||||||
|
let tolerance = tolerance.map(|e| e.n).unwrap_or(0.0000000001);
|
||||||
|
_assert(
|
||||||
|
(actual - exp).abs() < tolerance,
|
||||||
|
&format!("Expected {actual} to be equal to {exp} but it wasn't{suffix}"),
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -137,7 +137,7 @@ async fn inner_chamfer(
|
|||||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||||
edge_id,
|
edge_id,
|
||||||
object_id: solid.id,
|
object_id: solid.id,
|
||||||
radius: LengthUnit(length.n),
|
radius: LengthUnit(length.to_mm()),
|
||||||
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
|
tolerance: LengthUnit(DEFAULT_TOLERANCE), // We can let the user set this in the future.
|
||||||
cut_type: CutType::Chamfer,
|
cut_type: CutType::Chamfer,
|
||||||
}),
|
}),
|
||||||
|
@ -22,7 +22,7 @@ pub async fn int(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// 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 )
|
||||||
|
@ -40,9 +40,9 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
|
|
||||||
let result = inner_extrude(
|
let result = inner_extrude(
|
||||||
sketches,
|
sketches,
|
||||||
length.n,
|
length,
|
||||||
symmetric,
|
symmetric,
|
||||||
bidirectional_length.map(|t| t.n),
|
bidirectional_length,
|
||||||
tag_start,
|
tag_start,
|
||||||
tag_end,
|
tag_end,
|
||||||
exec_state,
|
exec_state,
|
||||||
@ -164,9 +164,9 @@ pub async fn extrude(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
async fn inner_extrude(
|
async fn inner_extrude(
|
||||||
sketches: Vec<Sketch>,
|
sketches: Vec<Sketch>,
|
||||||
length: f64,
|
length: TyF64,
|
||||||
symmetric: Option<bool>,
|
symmetric: Option<bool>,
|
||||||
bidirectional_length: Option<f64>,
|
bidirectional_length: Option<TyF64>,
|
||||||
tag_start: Option<TagNode>,
|
tag_start: Option<TagNode>,
|
||||||
tag_end: Option<TagNode>,
|
tag_end: Option<TagNode>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
@ -183,7 +183,7 @@ async fn inner_extrude(
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bidirection = bidirectional_length.map(LengthUnit);
|
let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
|
||||||
|
|
||||||
let opposite = match (symmetric, bidirection) {
|
let opposite = match (symmetric, bidirection) {
|
||||||
(Some(true), _) => Opposite::Symmetric,
|
(Some(true), _) => Opposite::Symmetric,
|
||||||
@ -201,7 +201,7 @@ async fn inner_extrude(
|
|||||||
cmd_id: id.into(),
|
cmd_id: id.into(),
|
||||||
cmd: ModelingCmd::from(mcmd::Extrude {
|
cmd: ModelingCmd::from(mcmd::Extrude {
|
||||||
target: sketch.id.into(),
|
target: sketch.id.into(),
|
||||||
distance: LengthUnit(length),
|
distance: LengthUnit(length.to_mm()),
|
||||||
faces: Default::default(),
|
faces: Default::default(),
|
||||||
opposite: opposite.clone(),
|
opposite: opposite.clone(),
|
||||||
}),
|
}),
|
||||||
@ -213,7 +213,7 @@ async fn inner_extrude(
|
|||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
sketch,
|
sketch,
|
||||||
id.into(),
|
id.into(),
|
||||||
length,
|
length.clone(),
|
||||||
false,
|
false,
|
||||||
&NamedCapTags {
|
&NamedCapTags {
|
||||||
start: tag_start.as_ref(),
|
start: tag_start.as_ref(),
|
||||||
@ -238,7 +238,7 @@ pub(crate) struct NamedCapTags<'a> {
|
|||||||
pub(crate) async fn do_post_extrude<'a>(
|
pub(crate) async fn do_post_extrude<'a>(
|
||||||
sketch: &Sketch,
|
sketch: &Sketch,
|
||||||
solid_id: ArtifactId,
|
solid_id: ArtifactId,
|
||||||
length: f64,
|
length: TyF64,
|
||||||
sectional: bool,
|
sectional: bool,
|
||||||
named_cap_tags: &'a NamedCapTags<'a>,
|
named_cap_tags: &'a NamedCapTags<'a>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
@ -470,8 +470,8 @@ pub(crate) async fn do_post_extrude<'a>(
|
|||||||
value: new_value,
|
value: new_value,
|
||||||
meta: sketch.meta.clone(),
|
meta: sketch.meta.clone(),
|
||||||
units: sketch.units,
|
units: sketch.units,
|
||||||
|
height: length.to_length_units(sketch.units),
|
||||||
sketch,
|
sketch,
|
||||||
height: length,
|
|
||||||
start_cap_id,
|
start_cap_id,
|
||||||
end_cap_id,
|
end_cap_id,
|
||||||
edge_cuts: vec![],
|
edge_cuts: vec![],
|
||||||
|
@ -64,14 +64,14 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]
|
|||||||
pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn fillet(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
|
let solid = args.get_unlabeled_kw_arg_typed("solid", &RuntimeType::solid(), exec_state)?;
|
||||||
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
|
let radius: TyF64 = args.get_kw_arg_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
|
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||||
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
|
let tags = args.kw_arg_array_and_source::<EdgeReference>("tags")?;
|
||||||
let tag = args.get_kw_arg_opt("tag")?;
|
let tag = args.get_kw_arg_opt("tag")?;
|
||||||
|
|
||||||
// Run the function.
|
// Run the function.
|
||||||
validate_unique(&tags)?;
|
validate_unique(&tags)?;
|
||||||
let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
|
let tags: Vec<EdgeReference> = tags.into_iter().map(|item| item.0).collect();
|
||||||
let value = inner_fillet(solid, radius, tags, tolerance.map(|t| t.n), tag, exec_state, args).await?;
|
let value = inner_fillet(solid, radius, tags, tolerance, tag, exec_state, args).await?;
|
||||||
Ok(KclValue::Solid { value })
|
Ok(KclValue::Solid { value })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +148,7 @@ async fn inner_fillet(
|
|||||||
solid: Box<Solid>,
|
solid: Box<Solid>,
|
||||||
radius: TyF64,
|
radius: TyF64,
|
||||||
tags: Vec<EdgeReference>,
|
tags: Vec<EdgeReference>,
|
||||||
tolerance: Option<f64>,
|
tolerance: Option<TyF64>,
|
||||||
tag: Option<TagNode>,
|
tag: Option<TagNode>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
args: Args,
|
args: Args,
|
||||||
@ -163,8 +163,8 @@ async fn inner_fillet(
|
|||||||
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
ModelingCmd::from(mcmd::Solid3dFilletEdge {
|
||||||
edge_id,
|
edge_id,
|
||||||
object_id: solid.id,
|
object_id: solid.id,
|
||||||
radius: LengthUnit(radius.n),
|
radius: LengthUnit(radius.to_mm()),
|
||||||
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||||
cut_type: CutType::Fillet,
|
cut_type: CutType::Fillet,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -17,7 +17,7 @@ use super::args::TyF64;
|
|||||||
|
|
||||||
/// Create a helix.
|
/// Create a helix.
|
||||||
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::angle(), exec_state)?;
|
let angle_start: TyF64 = args.get_kw_arg_typed("angleStart", &RuntimeType::degrees(), exec_state)?;
|
||||||
let revolutions: TyF64 = args.get_kw_arg_typed("revolutions", &RuntimeType::count(), exec_state)?;
|
let revolutions: TyF64 = args.get_kw_arg_typed("revolutions", &RuntimeType::count(), exec_state)?;
|
||||||
let ccw = args.get_kw_arg_opt("ccw")?;
|
let ccw = args.get_kw_arg_opt("ccw")?;
|
||||||
let radius: Option<TyF64> = args.get_kw_arg_opt_typed("radius", &RuntimeType::length(), exec_state)?;
|
let radius: Option<TyF64> = args.get_kw_arg_opt_typed("radius", &RuntimeType::length(), exec_state)?;
|
||||||
@ -84,9 +84,9 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
|||||||
revolutions.n,
|
revolutions.n,
|
||||||
angle_start.n,
|
angle_start.n,
|
||||||
ccw,
|
ccw,
|
||||||
radius.map(|t| t.n),
|
radius,
|
||||||
axis,
|
axis,
|
||||||
length.map(|t| t.n),
|
length,
|
||||||
cylinder,
|
cylinder,
|
||||||
exec_state,
|
exec_state,
|
||||||
args,
|
args,
|
||||||
@ -100,9 +100,9 @@ async fn inner_helix(
|
|||||||
revolutions: f64,
|
revolutions: f64,
|
||||||
angle_start: f64,
|
angle_start: f64,
|
||||||
ccw: Option<bool>,
|
ccw: Option<bool>,
|
||||||
radius: Option<f64>,
|
radius: Option<TyF64>,
|
||||||
axis: Option<Axis3dOrEdgeReference>,
|
axis: Option<Axis3dOrEdgeReference>,
|
||||||
length: Option<f64>,
|
length: Option<TyF64>,
|
||||||
cylinder: Option<Solid>,
|
cylinder: Option<Solid>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
args: Args,
|
args: Args,
|
||||||
@ -130,7 +130,7 @@ async fn inner_helix(
|
|||||||
ModelingCmd::from(mcmd::EntityMakeHelix {
|
ModelingCmd::from(mcmd::EntityMakeHelix {
|
||||||
cylinder_id: cylinder.id,
|
cylinder_id: cylinder.id,
|
||||||
is_clockwise: !helix_result.ccw,
|
is_clockwise: !helix_result.ccw,
|
||||||
length: LengthUnit(length.unwrap_or(cylinder.height)),
|
length: LengthUnit(length.as_ref().map(|t| t.to_mm()).unwrap_or(cylinder.height_in_mm())),
|
||||||
revolutions,
|
revolutions,
|
||||||
start_angle: Angle::from_degrees(angle_start),
|
start_angle: Angle::from_degrees(angle_start),
|
||||||
}),
|
}),
|
||||||
@ -150,20 +150,20 @@ async fn inner_helix(
|
|||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromParams {
|
||||||
radius: LengthUnit(radius),
|
radius: LengthUnit(radius.to_mm()),
|
||||||
is_clockwise: !helix_result.ccw,
|
is_clockwise: !helix_result.ccw,
|
||||||
length: LengthUnit(length),
|
length: LengthUnit(length.to_mm()),
|
||||||
revolutions,
|
revolutions,
|
||||||
start_angle: Angle::from_degrees(angle_start),
|
start_angle: Angle::from_degrees(angle_start),
|
||||||
axis: Point3d {
|
axis: Point3d {
|
||||||
x: direction[0].n,
|
x: direction[0].to_mm(),
|
||||||
y: direction[1].n,
|
y: direction[1].to_mm(),
|
||||||
z: direction[2].n,
|
z: direction[2].to_mm(),
|
||||||
},
|
},
|
||||||
center: Point3d {
|
center: Point3d {
|
||||||
x: LengthUnit(origin[0].n),
|
x: LengthUnit(origin[0].to_mm()),
|
||||||
y: LengthUnit(origin[1].n),
|
y: LengthUnit(origin[1].to_mm()),
|
||||||
z: LengthUnit(origin[2].n),
|
z: LengthUnit(origin[2].to_mm()),
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -175,9 +175,9 @@ async fn inner_helix(
|
|||||||
args.batch_modeling_cmd(
|
args.batch_modeling_cmd(
|
||||||
id,
|
id,
|
||||||
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
ModelingCmd::from(mcmd::EntityMakeHelixFromEdge {
|
||||||
radius: LengthUnit(radius),
|
radius: LengthUnit(radius.to_mm()),
|
||||||
is_clockwise: !helix_result.ccw,
|
is_clockwise: !helix_result.ccw,
|
||||||
length: length.map(LengthUnit),
|
length: length.map(|t| LengthUnit(t.to_mm())),
|
||||||
revolutions,
|
revolutions,
|
||||||
start_angle: Angle::from_degrees(angle_start),
|
start_angle: Angle::from_degrees(angle_start),
|
||||||
edge_id,
|
edge_id,
|
||||||
|
@ -10,7 +10,10 @@ use kittycad_modeling_cmds as kcmc;
|
|||||||
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
use super::{args::TyF64, DEFAULT_TOLERANCE};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{types::RuntimeType, ExecState, KclValue, Sketch, Solid},
|
execution::{
|
||||||
|
types::{NumericType, RuntimeType},
|
||||||
|
ExecState, KclValue, Sketch, Solid,
|
||||||
|
},
|
||||||
parsing::ast::types::TagNode,
|
parsing::ast::types::TagNode,
|
||||||
std::{extrude::do_post_extrude, Args},
|
std::{extrude::do_post_extrude, Args},
|
||||||
};
|
};
|
||||||
@ -30,7 +33,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
// This can be set to override the automatically determined topological base curve, which is usually the first section encountered.
|
||||||
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
|
let base_curve_index: Option<u32> = args.get_kw_arg_opt("baseCurveIndex")?;
|
||||||
// Tolerance for the loft operation.
|
// Tolerance for the loft operation.
|
||||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::count(), exec_state)?;
|
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||||
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
let tag_start = args.get_kw_arg_opt("tagStart")?;
|
||||||
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
let tag_end = args.get_kw_arg_opt("tagEnd")?;
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ pub async fn loft(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
|||||||
v_degree,
|
v_degree,
|
||||||
bez_approximate_rational,
|
bez_approximate_rational,
|
||||||
base_curve_index,
|
base_curve_index,
|
||||||
tolerance.map(|t| t.n),
|
tolerance,
|
||||||
tag_start,
|
tag_start,
|
||||||
tag_end,
|
tag_end,
|
||||||
exec_state,
|
exec_state,
|
||||||
@ -136,7 +139,7 @@ async fn inner_loft(
|
|||||||
v_degree: NonZeroU32,
|
v_degree: NonZeroU32,
|
||||||
bez_approximate_rational: bool,
|
bez_approximate_rational: bool,
|
||||||
base_curve_index: Option<u32>,
|
base_curve_index: Option<u32>,
|
||||||
tolerance: Option<f64>,
|
tolerance: Option<TyF64>,
|
||||||
tag_start: Option<TagNode>,
|
tag_start: Option<TagNode>,
|
||||||
tag_end: Option<TagNode>,
|
tag_end: Option<TagNode>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
@ -160,7 +163,7 @@ async fn inner_loft(
|
|||||||
section_ids: sketches.iter().map(|group| group.id).collect(),
|
section_ids: sketches.iter().map(|group| group.id).collect(),
|
||||||
base_curve_index,
|
base_curve_index,
|
||||||
bez_approximate_rational,
|
bez_approximate_rational,
|
||||||
tolerance: LengthUnit(tolerance.unwrap_or(DEFAULT_TOLERANCE)),
|
tolerance: LengthUnit(tolerance.as_ref().map(|t| t.to_mm()).unwrap_or(DEFAULT_TOLERANCE)),
|
||||||
v_degree,
|
v_degree,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -174,7 +177,7 @@ async fn inner_loft(
|
|||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
&sketch,
|
&sketch,
|
||||||
id.into(),
|
id.into(),
|
||||||
0.0,
|
TyF64::new(0.0, NumericType::mm()),
|
||||||
false,
|
false,
|
||||||
&super::extrude::NamedCapTags {
|
&super::extrude::NamedCapTags {
|
||||||
start: tag_start.as_ref(),
|
start: tag_start.as_ref(),
|
||||||
|
@ -6,7 +6,7 @@ use kcl_derive_docs::stdlib;
|
|||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
types::{self, NumericType, RuntimeType},
|
types::{NumericType, RuntimeType, UnitAngle, UnitType},
|
||||||
ExecState, KclValue,
|
ExecState, KclValue,
|
||||||
},
|
},
|
||||||
std::args::{Args, TyF64},
|
std::args::{Args, TyF64},
|
||||||
@ -20,11 +20,10 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
let d: TyF64 = args.get_kw_arg_typed("divisor", &RuntimeType::num_any(), exec_state)?;
|
let d: TyF64 = args.get_kw_arg_typed("divisor", &RuntimeType::num_any(), exec_state)?;
|
||||||
|
|
||||||
let (n, d, ty) = NumericType::combine_div(n, d);
|
let (n, d, ty) = NumericType::combine_div(n, d);
|
||||||
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
if ty == NumericType::Unknown {
|
||||||
// TODO suggest how to fix this
|
|
||||||
exec_state.warn(CompilationError::err(
|
exec_state.warn(CompilationError::err(
|
||||||
args.source_range,
|
args.source_range,
|
||||||
"Remainder of numbers which have unknown or incompatible units.",
|
"Calling `rem` on numbers which have unknown or incompatible units.\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})`"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let remainder = inner_rem(n, d);
|
let remainder = inner_rem(n, d);
|
||||||
@ -36,12 +35,12 @@ pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
|||||||
/// If `num` is negative, the result will be too.
|
/// If `num` is negative, the result will be too.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
/// assertEqual(rem( 7, divisor = 4), 3, 0.01, "remainder is 3" )
|
/// assert(rem( 7, divisor = 4), isEqualTo = 3, error = "remainder is 3")
|
||||||
/// assertEqual(rem(-7, divisor = 4), -3, 0.01, "remainder is -3")
|
/// assert(rem(-7, divisor = 4), isEqualTo = -3, error = "remainder is -3")
|
||||||
/// assertEqual(rem( 7, divisor = -4), 3, 0.01, "remainder is 3" )
|
/// assert(rem( 7, divisor = -4), isEqualTo = 3, error = "remainder is 3")
|
||||||
/// assertEqual(rem( 6, divisor = 2.5), 1, 0.01, "remainder is 1" )
|
/// assert(rem( 6, divisor = 2.5), isEqualTo = 1, error = "remainder is 1")
|
||||||
/// assertEqual(rem( 6.5, divisor = 2.5), 1.5, 0.01, "remainder is 1.5" )
|
/// assert(rem( 6.5, divisor = 2.5), isEqualTo = 1.5, error = "remainder is 1.5")
|
||||||
/// assertEqual(rem( 6.5, divisor = 2), 0.5, 0.01, "remainder is 0.5" )
|
/// assert(rem( 6.5, divisor = 2), isEqualTo = 0.5, error = "remainder is 0.5")
|
||||||
/// ```
|
/// ```
|
||||||
#[stdlib {
|
#[stdlib {
|
||||||
name = "rem",
|
name = "rem",
|
||||||
@ -59,20 +58,63 @@ fn inner_rem(num: f64, divisor: f64) -> f64 {
|
|||||||
|
|
||||||
/// Compute the cosine of a number (in radians).
|
/// Compute the cosine of a number (in radians).
|
||||||
pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn cos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
|
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.cos())))
|
let num = match num.ty {
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`cos` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
num.n
|
||||||
|
}
|
||||||
|
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
|
||||||
|
_ => num.n,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.cos())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the sine of a number (in radians).
|
/// Compute the sine of a number (in radians).
|
||||||
pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn sin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
|
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.sin())))
|
let num = match num.ty {
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`sin` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
num.n
|
||||||
|
}
|
||||||
|
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
|
||||||
|
_ => num.n,
|
||||||
|
};
|
||||||
|
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.sin())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the tangent of a number (in radians).
|
/// Compute the tangent of a number (in radians).
|
||||||
pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn tan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::radians(), exec_state)?;
|
let num: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::angle(), exec_state)?;
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.n.tan())))
|
let num = match num.ty {
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`tan` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
num.n
|
||||||
|
}
|
||||||
|
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => num.n.to_radians(),
|
||||||
|
_ => num.n,
|
||||||
|
};
|
||||||
|
Ok(args.make_user_val_from_f64_with_type(TyF64::count(num.tan())))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return the value of `pi`. Archimedes’ constant (π).
|
/// Return the value of `pi`. Archimedes’ constant (π).
|
||||||
@ -258,11 +300,10 @@ fn inner_ceil(num: f64) -> Result<f64, KclError> {
|
|||||||
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let nums = args.get_number_array_with_types()?;
|
let nums = args.get_number_array_with_types()?;
|
||||||
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
if ty == NumericType::Unknown {
|
||||||
// TODO suggest how to fix this
|
|
||||||
exec_state.warn(CompilationError::err(
|
exec_state.warn(CompilationError::err(
|
||||||
args.source_range,
|
args.source_range,
|
||||||
"Calling `min` on numbers which have unknown or incompatible units.",
|
"Calling `min` on numbers which have unknown or incompatible units.\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})`",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let result = inner_min(nums);
|
let result = inner_min(nums);
|
||||||
@ -303,11 +344,10 @@ fn inner_min(args: Vec<f64>) -> f64 {
|
|||||||
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let nums = args.get_number_array_with_types()?;
|
let nums = args.get_number_array_with_types()?;
|
||||||
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
let (nums, ty) = NumericType::combine_eq_array(&nums);
|
||||||
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
|
if ty == NumericType::Unknown {
|
||||||
// TODO suggest how to fix this
|
|
||||||
exec_state.warn(CompilationError::err(
|
exec_state.warn(CompilationError::err(
|
||||||
args.source_range,
|
args.source_range,
|
||||||
"Calling `max` on numbers which have unknown or incompatible units.",
|
"Calling `max` on numbers which have unknown or incompatible units.\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})`",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let result = inner_max(nums);
|
let result = inner_max(nums);
|
||||||
@ -389,8 +429,20 @@ fn inner_pow(num: f64, pow: f64) -> Result<f64, KclError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the arccosine of a number (in radians).
|
/// Compute the arccosine of a number (in radians).
|
||||||
pub async fn acos(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn acos(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num = args.get_number_with_type()?;
|
let num = args.get_number_with_type()?;
|
||||||
|
if matches!(
|
||||||
|
num.ty,
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`acos` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
}
|
||||||
let result = inner_acos(num.n)?;
|
let result = inner_acos(num.n)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||||
@ -420,8 +472,20 @@ fn inner_acos(num: f64) -> Result<f64, KclError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the arcsine of a number (in radians).
|
/// Compute the arcsine of a number (in radians).
|
||||||
pub async fn asin(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn asin(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num = args.get_number_with_type()?;
|
let num = args.get_number_with_type()?;
|
||||||
|
if matches!(
|
||||||
|
num.ty,
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`asin` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
}
|
||||||
let result = inner_asin(num.n)?;
|
let result = inner_asin(num.n)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||||
@ -450,8 +514,20 @@ fn inner_asin(num: f64) -> Result<f64, KclError> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the arctangent of a number (in radians).
|
/// Compute the arctangent of a number (in radians).
|
||||||
pub async fn atan(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn atan(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let num = args.get_number_with_type()?;
|
let num = args.get_number_with_type()?;
|
||||||
|
if matches!(
|
||||||
|
num.ty,
|
||||||
|
NumericType::Default {
|
||||||
|
angle: UnitAngle::Degrees,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
args.source_range,
|
||||||
|
"`atan` requires its input in radians, but the input is assumed to be in degrees. You can use a numeric suffix (e.g., `0rad`) or type ascription (e.g., `(1/2): number(rad)`) to show the number is in radians, or `toRadians` to convert from degrees to radians",
|
||||||
|
));
|
||||||
|
}
|
||||||
let result = inner_atan(num.n)?;
|
let result = inner_atan(num.n)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||||
@ -483,7 +559,7 @@ fn inner_atan(num: f64) -> Result<f64, KclError> {
|
|||||||
pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn atan2(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let y = args.get_kw_arg_typed("y", &RuntimeType::length(), exec_state)?;
|
let y = args.get_kw_arg_typed("y", &RuntimeType::length(), exec_state)?;
|
||||||
let x = args.get_kw_arg_typed("x", &RuntimeType::length(), exec_state)?;
|
let x = args.get_kw_arg_typed("x", &RuntimeType::length(), exec_state)?;
|
||||||
let (y, x, _) = NumericType::combine_eq(y, x);
|
let (y, x, _) = NumericType::combine_eq_coerce(y, x);
|
||||||
let result = inner_atan2(y, x)?;
|
let result = inner_atan2(y, x)?;
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
||||||
@ -714,66 +790,6 @@ fn inner_tau() -> Result<f64, KclError> {
|
|||||||
Ok(std::f64::consts::TAU)
|
Ok(std::f64::consts::TAU)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a number from degrees to radians.
|
|
||||||
pub async fn to_radians(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
||||||
let num = args.get_number_with_type()?;
|
|
||||||
let result = inner_to_radians(num.n)?;
|
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::radians())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a number from degrees to radians.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// exampleSketch = startSketchOn("XZ")
|
|
||||||
/// |> startProfileAt([0, 0], %)
|
|
||||||
/// |> angledLine(
|
|
||||||
/// angle = 50,
|
|
||||||
/// length = 70 * cos(toRadians(45)),
|
|
||||||
/// )
|
|
||||||
/// |> yLine(endAbsolute = 0)
|
|
||||||
/// |> close()
|
|
||||||
///
|
|
||||||
/// example = extrude(exampleSketch, length = 5)
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "toRadians",
|
|
||||||
tags = ["math"],
|
|
||||||
}]
|
|
||||||
fn inner_to_radians(num: f64) -> Result<f64, KclError> {
|
|
||||||
Ok(num.to_radians())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a number from radians to degrees.
|
|
||||||
pub async fn to_degrees(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
|
||||||
let num = args.get_number_with_type()?;
|
|
||||||
let result = inner_to_degrees(num.n)?;
|
|
||||||
|
|
||||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts a number from radians to degrees.
|
|
||||||
///
|
|
||||||
/// ```no_run
|
|
||||||
/// exampleSketch = startSketchOn("XZ")
|
|
||||||
/// |> startProfileAt([0, 0], %)
|
|
||||||
/// |> angledLine(
|
|
||||||
/// angle = 50,
|
|
||||||
/// length = 70 * cos(toDegrees(pi()/4)),
|
|
||||||
/// )
|
|
||||||
/// |> yLine(endAbsolute = 0)
|
|
||||||
/// |> close()
|
|
||||||
///
|
|
||||||
/// example = extrude(exampleSketch, length = 5)
|
|
||||||
/// ```
|
|
||||||
#[stdlib {
|
|
||||||
name = "toDegrees",
|
|
||||||
tags = ["math"],
|
|
||||||
}]
|
|
||||||
fn inner_to_degrees(num: f64) -> Result<f64, KclError> {
|
|
||||||
Ok(num.to_degrees())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|