Compare commits

...

11 Commits

Author SHA1 Message Date
ec36814626 Remove unused snapshots diff steps 2025-05-19 18:53:53 -04:00
553e650fbe Add brake disc to samples. (#7059)
* Add brake disc.

* Update kcl-samples simulation test output

* Update public/kcl-samples/brake-rotor/main.kcl

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>

* Update public/kcl-samples/brake-rotor/main.kcl

Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: Jess Frazelle <jessfraz@users.noreply.github.com>
Co-authored-by: Jess Frazelle <github@jessfraz.com>
2025-05-19 15:24:37 -07:00
9690a24c68 Avoid a spin-lock when auth is never resolved (#7084) 2025-05-19 18:16:07 -04:00
978d5d44a2 rotate a named axis (#7087)
updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-19 22:11:35 +00:00
9df476543a turn on the revolve test (#7075)
* turn on the revolve test

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

* updates

Signed-off-by: Jess Frazelle <github@jessfraz.com>

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-05-19 14:51:44 -07:00
cf303ebe97 Declare pattern transform functions in KCL (#7057)
* Declare pattern transform using KCL

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

* Boolean function param defaults

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

* Parse empty record types in fn types

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-20 08:25:29 +12:00
b1d1d89ca5 Include link to the new book (#7056)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-05-19 14:49:23 -05:00
3a599d0a0a Release KCL 75 (#7077) 2025-05-19 19:02:38 +00:00
8340f6b906 Give a default value to projectName in Text-to-CAD Create command to allow invocation through command palette (#7061)
Give a default value to projectName

This argument is hidden since it's marked as skip, so browser users who
must now invoke the command through the command palette have no way of
setting the value. Since it is also required, the command palette fails
silently on submission (which we need to fix more broadly). This gives
it a default value so that users can submit properly.
2025-05-19 18:13:47 +00:00
ddb034b14d Show KCL backtraces (#7033)
* Add backtrace to errors

* Add display of backtraces with hints

* Change pane badge to only show count of errors

* Fix property name to not collide with Error superclass

* Increase min stack again

* Add e2e test that checks that the diagnostics are created in CodeMirror

* Remove unneeded code

* Change to the new hotness
2025-05-19 18:13:10 +00:00
bfa2f67393 Add a 1s client side rate limit / cache to billing requests (#7067)
* Add a 1s client side rate limit / cache to billing requests

* Stop using err; use isErr instead
2025-05-19 14:11:33 -04:00
153 changed files with 34023 additions and 18589 deletions

View File

@ -88,6 +88,7 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev
RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Commit differences
if: steps.path-changes.outputs.outside-kcl-samples == 'false' && steps.cargo-test-kcl-samples.outcome == 'failure'
shell: bash
@ -119,6 +120,7 @@ jobs:
# Configure nextest when it's run by insta (via just).
NEXTEST_PROFILE: ci
RUST_BACKTRACE: full
RUST_MIN_STACK: 10485760000
- name: Build and archive tests
run: |
cd rust
@ -182,6 +184,7 @@ jobs:
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev
RUST_MIN_STACK: 10485760000
- name: Upload results
if: always()
run: .github/ci-cd-scripts/upload-results.sh
@ -238,6 +241,7 @@ jobs:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN_DEV}}
ZOO_HOST: https://api.dev.zoo.dev
MODELING_APP_INTERNAL_SAMPLES_SECRET: ${{secrets.MODELING_APP_INTERNAL_SAMPLES_SECRET}}
RUST_MIN_STACK: 10485760000
run-wasm-tests:
name: Run wasm tests
strategy:

View File

@ -167,32 +167,6 @@ jobs:
retention-days: 30
overwrite: true
- name: Check diff
if: ${{ github.ref != 'refs/heads/main' }}
shell: bash
id: git-check
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
if git status | grep -q "Changes to be committed"
then echo "modified=true" >> $GITHUB_OUTPUT
else echo "modified=false" >> $GITHUB_OUTPUT
fi
- name: Commit changes
if: ${{ steps.git-check.outputs.modified == 'true' }}
shell: bash
run: |
git add e2e/playwright/snapshot-tests.spec.ts-snapshots e2e/playwright/snapshots
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git remote set-url origin https://${{ github.actor }}:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git
git fetch origin
echo ${{ github.head_ref }}
git checkout ${{ github.head_ref }}
git commit --message "Update snapshots" || true
git push
git push origin ${{ github.head_ref }}
electron:
needs: [prepare-wasm]
timeout-minutes: 60

View File

@ -4,7 +4,7 @@ excerpt: "Documentation of the KCL language for the Zoo Design Studio."
layout: manual
---
This is a reference for KCL. If you are learning KCL, you may prefer the [guide]() which explains
This is a reference for KCL. If you are learning KCL, you may prefer the [guide](https://zoo.dev/docs/kcl-book/intro.html) which explains
things in a more tutorial fashion. See also our documentation of the [standard library](/docs/kcl-std).
## Topics

View File

@ -1,19 +1,19 @@
---
title: "patternTransform2d"
subtitle: "Function in std::sketch"
excerpt: "Just like patternTransform, but works on 2D sketches not 3D solids."
excerpt: "Just like `patternTransform`, but works on 2D sketches not 3D solids."
layout: manual
---
Just like patternTransform, but works on 2D sketches not 3D solids.
Just like `patternTransform`, but works on 2D sketches not 3D solids.
```kcl
patternTransform2d(
@sketches: [Sketch],
instances: number,
transform: FunctionSource,
useOriginal?: bool,
): [Sketch]
@sketches: [Sketch; 1+],
instances: number(_),
transform: fn(number(_)): { },
useOriginal?: boolean,
): [Sketch; 1+]
```
@ -22,14 +22,14 @@ patternTransform2d(
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketches` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate | Yes |
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | `FunctionSource` | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
| `sketches` | [`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch) | The sketch(es) to duplicate. | Yes |
| `instances` | [`number(_)`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `transform` | [`fn(number(_)): { }`](/docs/kcl-std/types/std-types-fn) | How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples. | Yes |
| `useOriginal` | `boolean` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. | No |
### Returns
[`[Sketch]`](/docs/kcl-std/types/std-types-Sketch)
[`[Sketch; 1+]`](/docs/kcl-std/types/std-types-Sketch)
### Examples

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -65,7 +65,7 @@ layout: manual
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX)
@ -94,7 +94,7 @@ layout: manual
* [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/patternTransform)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union)

View File

@ -30,7 +30,7 @@ This module contains functions for creating and manipulating sketches, and makin
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/loft)
* [`patternCircular2d`](/docs/kcl-std/patternCircular2d)
* [`patternTransform2d`](/docs/kcl-std/patternTransform2d)
* [`patternTransform2d`](/docs/kcl-std/functions/std-sketch-patternTransform2d)
* [`polygon`](/docs/kcl-std/polygon)
* [`profileStart`](/docs/kcl-std/profileStart)
* [`profileStartX`](/docs/kcl-std/profileStartX)

View File

@ -18,7 +18,7 @@ This module contains functions for modifying solids, e.g., by adding a fillet or
* [`intersect`](/docs/kcl-std/intersect)
* [`patternCircular3d`](/docs/kcl-std/patternCircular3d)
* [`patternLinear3d`](/docs/kcl-std/patternLinear3d)
* [`patternTransform`](/docs/kcl-std/patternTransform)
* [`patternTransform`](/docs/kcl-std/functions/std-solid-patternTransform)
* [`shell`](/docs/kcl-std/functions/std-solid-shell)
* [`subtract`](/docs/kcl-std/subtract)
* [`union`](/docs/kcl-std/union)

View File

@ -11,7 +11,7 @@ Contains frequently used constants, functions for interacting with the KittyCAD
The standard library is organised into modules (listed below), but most things are always available in KCL programs.
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html).
## Modules

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -235,6 +235,48 @@ extrude001 = extrude(sketch001, length = 5)`
.first()
).toBeVisible()
})
test('KCL errors with functions show hints for the entire backtrace', async ({
page,
homePage,
scene,
cmdBar,
editor,
toolbar,
}) => {
await homePage.goToModelingScene()
await scene.settled(cmdBar)
const code = `fn check(@x) {
return assert(x, isGreaterThan = 0)
}
fn middle(@x) {
return check(x)
}
middle(1)
middle(0)
`
await test.step('Set the code with a KCL error', async () => {
await toolbar.openPane('code')
await editor.replaceCode('', code)
})
// This shows all the diagnostics in a way that doesn't require the mouse
// pointer hovering over a coordinate, which would be brittle.
await test.step('Open CodeMirror diagnostics list', async () => {
// Ensure keyboard focus is in the editor.
await page.getByText('fn check(').click()
await page.keyboard.press('ControlOrMeta+Shift+M')
})
await expect(
page.getByText(`assert failed: Expected 0 to be greater than 0 but it wasn't
check()
middle()`)
).toBeVisible()
// There should be one hint inside middle() and one at the top level.
await expect(page.getByText('Part of the error backtrace')).toHaveCount(2)
})
})
test(

View File

@ -37,6 +37,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
[![bottle](screenshots/bottle.png)](bottle/main.kcl)
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
[![bracket](screenshots/bracket.png)](bracket/main.kcl)
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](screenshots/brake-rotor.png))
[![brake-rotor](screenshots/brake-rotor.png)](brake-rotor/main.kcl)
#### [car-wheel-assembly](car-wheel-assembly/main.kcl) ([screenshot](screenshots/car-wheel-assembly.png))
[![car-wheel-assembly](screenshots/car-wheel-assembly.png)](car-wheel-assembly/main.kcl)
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))

View File

@ -0,0 +1,179 @@
// A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.
@settings(defaultLengthUnit = mm)
// Define parameters.
dDisc = 320
dPitchCircle = 114.3
dBore = 64
nStuds = 5
dStudDrilling = 12.5 // M12
hFrictionSurface = 60
tDiscHalf = 10
// Vent parameters.
tVent = 10
wVent = 6
rVentFillet = 2
nVentBosses = 36
// Drilling parameters.
dDrillDia = 6
aBase = 90
aSweep = 30
nArcs = 12
// Bell parameters.
aDraftBell = 5
tBell = 5 // Wall thickness.
hBellAboveDiscFace = 40
hBellSubflush = 4
wUndercut = 8
fn drillHole(activeSketch, t) {
// Sketch a vent hole at line parameter value t on an arc drawn across the disc surface.
rInner = dDisc / 2 - hFrictionSurface
rOuter = dDisc / 2
aStart = aBase
aEnd = aBase - aSweep
// Linear interpolation of radius.
rCurrent = rInner + t * (rOuter - rInner)
// Linear interpolation of angle.
aCurrent = aStart + t * (aEnd - aStart)
// Calculate position.
xCenter = rCurrent * cos(aCurrent)
yCenter = rCurrent * sin(aCurrent)
// Draw.
drillCircle = circle(activeSketch, center = [xCenter, yCenter], radius = dDrillDia / 2)
return drillCircle
}
fn createDiscHalf(plane, dDiscParam, hFrictionSurfaceParam, tDiscHalfParam) {
// Create a disc half with a vent hole pattern.
sketchFace = startSketchOn(plane)
profileFace = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2)
|> subtract2d(tool = circle(sketchFace, center = [0, 0], radius = dDiscParam / 2 - hFrictionSurfaceParam))
// Create three circles at t = 0, 0.5, and 1
hole1 = drillHole(activeSketch = sketchFace, t = 0.2)
hole2 = drillHole(activeSketch = sketchFace, t = 0.5)
hole3 = drillHole(activeSketch = sketchFace, t = 0.8)
// Pattern and cut.
holes = patternCircular2d(
[hole1, hole2, hole3],
instances = nArcs,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
profileDrilled = subtract2d(profileFace, tool = holes)
// Extrude.
discHalf = extrude(profileFace, length = tDiscHalfParam)
return discHalf
}
// ---------------------------------------------------------------------------------------------------------------------
// Create inboard half.
discInboard = createDiscHalf(
plane = XY,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Create vents.
planeVent = offsetPlane(XY, offset = tDiscHalf)
sketchVent = startSketchOn(planeVent)
profileVent = startProfile(sketchVent, at = [-wVent, dDisc / 2])
|> angledLine(angle = 0, length = wVent, tag = $rectangleSegmentA001)
|> angledLine(angle = segAng(rectangleSegmentA001) - 90, length = hFrictionSurface, tag = $seg02)
|> angledLine(angle = segAng(rectangleSegmentA001), length = -segLen(rectangleSegmentA001), tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|> close()
ventPad = extrude(profileVent, length = tVent)
|> fillet(
radius = rVentFillet,
tags = [
getCommonEdge(faces = [seg01, rectangleSegmentA001]),
getCommonEdge(faces = [seg02, rectangleSegmentA001]),
getCommonEdge(faces = [seg01, seg03]),
getCommonEdge(faces = [seg03, seg02])
],
)
ventSet = patternCircular3d(
ventPad,
instances = nVentBosses,
axis = [0, 0, 1],
center = [0, 0, tDiscHalf],
arcDegrees = 360,
rotateDuplicates = true,
)
// Create outboard half.
planeOutboard = offsetPlane(XY, offset = tDiscHalf + tVent)
discOutboard = createDiscHalf(
plane = planeOutboard,
dDiscParam = dDisc,
hFrictionSurfaceParam = hFrictionSurface,
tDiscHalfParam = tDiscHalf,
)
// Now create bell.
rCenter = dDisc / 2 - hFrictionSurface - wUndercut
rBore = dBore / 2
lDraftExterior = hBellAboveDiscFace / tan(90 - aDraftBell)
lDraftInterior = (hBellAboveDiscFace - tBell) / tan(90 - aDraftBell)
// Inner and outer radius of outboard face of disc bell.
rOuter = rCenter - lDraftExterior - rBore
rInner = rOuter + lDraftExterior - (tBell + lDraftInterior)
sketchDiscBell = startSketchOn(-YZ)
bodyDiscBell = startProfile(
sketchDiscBell,
at = [
-dDisc / 2 + hFrictionSurface,
tDiscHalf * 2 + tVent
],
)
|> arc(
%,
angleStart = -180,
angleEnd = 0,
radius = wUndercut / 2,
)
|> line(end = [lDraftExterior, hBellAboveDiscFace])
|> xLine(length = rOuter, tag = $seg04)
|> yLine(length = -tBell)
|> xLine(length = -rInner)
|> line(end = [-lDraftInterior, -hBellAboveDiscFace])
|> line(end = [0, -2]) // Wall thickness.
|> xLine(length = -1 * (tBell + wUndercut))
|> close(%)
|> revolve(axis = Y)
// Drill lug holes.
sketchLugs = startSketchOn(bodyDiscBell, face = seg04)
profileStud = circle(sketchLugs, center = [0, dPitchCircle / 2], radius = dStudDrilling / 2)
|> patternCircular2d(
%,
instances = nStuds,
center = [0, 0],
arcDegrees = 360,
rotateDuplicates = true,
)
clearance = 2 // Some margin on negative extrude.
lugs = extrude(profileStud, length = -1 * (tBell + clearance))

View File

@ -74,6 +74,16 @@
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "brake-rotor/main.kcl",
"multipleFiles": false,
"title": "A 320mm vented brake disc (rotor), with straight vanes, 30mm thick. The disc bell should accommodate 5 M12 wheel studs on a 114.3mm pitch circle diameter.",
"description": "",
"files": [
"main.kcl"
]
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "car-wheel-assembly/main.kcl",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

20
rust/Cargo.lock generated
View File

@ -1815,7 +1815,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"anyhow",
"clap",
@ -1826,7 +1826,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"Inflector",
"anyhow",
@ -1845,7 +1845,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"convert_case",
"proc-macro2",
@ -1855,7 +1855,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.74"
version = "0.2.75"
dependencies = [
"anyhow",
"clap",
@ -1876,7 +1876,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"anyhow",
"clap",
@ -1896,7 +1896,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.74"
version = "0.2.75"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1973,7 +1973,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.74"
version = "0.3.75"
dependencies = [
"anyhow",
"kcl-lib",
@ -1988,7 +1988,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -2001,7 +2001,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"anyhow",
"async-trait",
@ -2015,7 +2015,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.74"
version = "0.1.75"
dependencies = [
"anyhow",
"bson",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ mod cache;
use kcl_lib::{
test_server::{execute_and_export_step, execute_and_snapshot, execute_and_snapshot_no_auth},
ExecError,
BacktraceItem, ExecError, ModuleId, SourceRange,
};
/// The minimum permissible difference between asserted twenty-twenty images.
@ -441,10 +441,15 @@ async fn kcl_test_import_file_doesnt_exist() {
model = cube"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "File `thing.obj` does not exist.");
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([0, 18, 0])], message: "File `thing.obj` does not exist." }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(0, 18, ModuleId::default()),
fn_name: None,
}]
);
}
@ -519,10 +524,18 @@ import 'e2e/executor/inputs/cube.gltf'
model = cube"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([32, 70, 0])], message: "The given format does not match the file extension. Expected: `gltf`, Given: `obj`" }"#
err.message(),
"The given format does not match the file extension. Expected: `gltf`, Given: `obj`"
);
assert_eq!(
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(32, 70, ModuleId::default()),
fn_name: None,
}]
);
}
@ -1666,10 +1679,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 111, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(70, 111, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1686,10 +1704,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have an x constrained angle of 270 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(70, 112, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1706,10 +1729,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 110, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(70, 110, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1726,10 +1754,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([70, 112, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(70, 112, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1746,10 +1779,15 @@ extrusion = extrude(sketch001, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 90 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(66, 116, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1757,7 +1795,7 @@ extrusion = extrude(sketch001, length = 10)
async fn kcl_test_angled_line_of_x_length_270() {
let code = r#"sketch001 = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 90, lengthX = 90, tag = $edge1)
|> angledLine(angle = 270, lengthX = 90, tag = $edge1)
|> angledLine(angle = -15, lengthX = -15, tag = $edge2)
|> line(end = [0, -5])
|> close(tag = $edge3)
@ -1766,10 +1804,15 @@ extrusion = extrude(sketch001, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have an x constrained angle of 270 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([66, 116, 0])], message: "Cannot have an x constrained angle of 90 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(66, 117, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1788,10 +1831,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 0 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 130, 0])], message: "Cannot have a y constrained angle of 0 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(95, 130, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1810,10 +1858,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 132, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(95, 132, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1832,10 +1885,15 @@ example = extrude(exampleSketch, length = 10)
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(err.message(), "Cannot have a y constrained angle of 180 degrees");
assert_eq!(
result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([95, 133, 0])], message: "Cannot have a y constrained angle of 180 degrees" }"#
err.backtrace(),
vec![BacktraceItem {
source_range: SourceRange::new(95, 133, ModuleId::default()),
fn_name: Some("angledLine".to_owned())
}]
);
}
@ -1849,10 +1907,31 @@ someFunction('INVALID')
"#;
let result = execute_and_snapshot(code, None).await;
assert!(result.is_err());
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert_eq!(
result.err().unwrap().to_string(),
r#"semantic: KclErrorDetails { source_ranges: [SourceRange([46, 55, 0]), SourceRange([60, 83, 0])], message: "This function expected the input argument to be Solid or Plane but it's actually of type string" }"#
err.message(),
"This function expected the input argument to be Solid or Plane but it's actually of type string"
);
assert_eq!(
err.source_ranges(),
vec![
SourceRange::new(46, 55, ModuleId::default()),
SourceRange::new(60, 83, ModuleId::default()),
]
);
assert_eq!(
err.backtrace(),
vec![
BacktraceItem {
source_range: SourceRange::new(46, 55, ModuleId::default()),
fn_name: Some("someFunction".to_owned()),
},
BacktraceItem {
source_range: SourceRange::new(60, 83, ModuleId::default()),
fn_name: None,
},
]
);
}
@ -1873,12 +1952,14 @@ async fn kcl_test_error_no_auth_websocket() {
"#;
let result = execute_and_snapshot_no_auth(code, None).await;
assert!(result.is_err());
assert!(result
.err()
.unwrap()
.to_string()
.contains("Please send the following object over this websocket"));
let err = result.unwrap_err();
let err = err.as_kcl_error().unwrap();
assert!(
err.message()
.contains("Please send the following object over this websocket"),
"actual: {}",
err.message()
);
}
#[tokio::test(flavor = "multi_thread")]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -439,12 +439,7 @@ impl EngineManager for EngineConnection {
request_sent: tx,
})
.await
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to send debug: {}", e),
source_ranges: vec![],
})
})?;
.map_err(|e| KclError::Engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?;
let _ = rx.await;
Ok(())
@ -479,25 +474,25 @@ impl EngineManager for EngineConnection {
})
.await
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to send modeling command: {}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to send modeling command: {}", e),
vec![source_range],
))
})?;
// Wait for the request to be sent.
rx.await
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("could not send request to the engine actor: {e}"),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("could not send request to the engine actor: {e}"),
vec![source_range],
))
})?
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("could not send request to the engine: {e}"),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("could not send request to the engine: {e}"),
vec![source_range],
))
})?;
Ok(())
@ -521,15 +516,15 @@ impl EngineManager for EngineConnection {
// Check if we have any pending errors.
let pe = self.pending_errors.read().await;
if !pe.is_empty() {
return Err(KclError::Engine(KclErrorDetails {
message: pe.join(", ").to_string(),
source_ranges: vec![source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
pe.join(", ").to_string(),
vec![source_range],
)));
} else {
return Err(KclError::Engine(KclErrorDetails {
message: "Modeling command failed: websocket closed early".to_string(),
source_ranges: vec![source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
"Modeling command failed: websocket closed early".to_string(),
vec![source_range],
)));
}
}
@ -548,10 +543,10 @@ impl EngineManager for EngineConnection {
}
}
Err(KclError::Engine(KclErrorDetails {
message: format!("Modeling command timed out `{}`", id),
source_ranges: vec![source_range],
}))
Err(KclError::Engine(KclErrorDetails::new(
format!("Modeling command timed out `{}`", id),
vec![source_range],
)))
}
async fn get_session_data(&self) -> Option<ModelingSessionData> {

View File

@ -147,32 +147,27 @@ impl EngineConnection {
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<(), KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize source range: {:?}", e),
vec![source_range],
))
})?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize modeling command: {:?}", e),
vec![source_range],
))
})?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize id to source range: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize id to source range: {:?}", e),
vec![source_range],
))
})?;
self.manager
.fire_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
Ok(())
}
@ -185,33 +180,28 @@ impl EngineConnection {
id_to_source_range: HashMap<uuid::Uuid, SourceRange>,
) -> Result<WebSocketResponse, KclError> {
let source_range_str = serde_json::to_string(&source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize source range: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize source range: {:?}", e),
vec![source_range],
))
})?;
let cmd_str = serde_json::to_string(&cmd).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize modeling command: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize modeling command: {:?}", e),
vec![source_range],
))
})?;
let id_to_source_range_str = serde_json::to_string(&id_to_source_range).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to serialize id to source range: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to serialize id to source range: {:?}", e),
vec![source_range],
))
})?;
let promise = self
.manager
.send_modeling_cmd_from_wasm(id.to_string(), source_range_str, cmd_str, id_to_source_range_str)
.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = crate::wasm::JsFuture::from(promise).await.map_err(|e| {
// Try to parse the error as an engine error.
@ -219,53 +209,52 @@ impl EngineConnection {
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
serde_json::from_str(&err_str)
{
KclError::Engine(KclErrorDetails {
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range],
))
} else if let Ok(data) =
serde_json::from_str::<Vec<kittycad_modeling_cmds::websocket::FailureWebSocketResponse>>(&err_str)
{
if let Some(data) = data.first() {
// It could also be an array of responses.
KclError::Engine(KclErrorDetails {
message: data
.errors
KclError::Engine(KclErrorDetails::new(
data.errors
.iter()
.map(|e| e.message.clone())
.collect::<Vec<_>>()
.join("\n"),
source_ranges: vec![source_range],
})
vec![source_range],
))
} else {
KclError::Engine(KclErrorDetails {
message: "Received empty response from engine".into(),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
"Received empty response from engine".into(),
vec![source_range],
))
}
} else {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from send modeling command: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to wait for promise from send modeling command: {:?}", e),
vec![source_range],
))
}
})?;
if value.is_null() || value.is_undefined() {
return Err(KclError::Engine(KclErrorDetails {
message: "Received null or undefined response from engine".into(),
source_ranges: vec![source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
"Received null or undefined response from engine".into(),
vec![source_range],
)));
}
// Convert JsValue to a Uint8Array
let data = js_sys::Uint8Array::from(value);
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to deserialize bson response from engine: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to deserialize bson response from engine: {:?}", e),
vec![source_range],
))
})?;
Ok(ws_result)
@ -316,18 +305,16 @@ impl crate::engine::EngineManager for EngineConnection {
*self.default_planes.write().await = Some(new_planes);
// Start a new session.
let promise = self.manager.start_new_session().map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.start_new_session()
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from start new session: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to wait for promise from start new session: {:?}", e),
vec![source_range],
))
})?;
Ok(())

View File

@ -276,10 +276,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
{
let duration = instant::Duration::from_millis(1);
wasm_timer::Delay::new(duration).await.map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to sleep: {:?}", err),
source_ranges: vec![source_range],
})
KclError::Internal(KclErrorDetails::new(
format!("Failed to sleep: {:?}", err),
vec![source_range],
))
})?;
}
#[cfg(not(target_arch = "wasm32"))]
@ -293,10 +293,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
return Ok(response);
}
Err(KclError::Engine(KclErrorDetails {
message: "async command timed out".to_string(),
source_ranges: vec![source_range],
}))
Err(KclError::Engine(KclErrorDetails::new(
"async command timed out".to_string(),
vec![source_range],
)))
}
/// Ensure ALL async commands have been completed.
@ -547,10 +547,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
id_to_source_range.insert(Uuid::from(*cmd_id), *range);
}
_ => {
return Err(KclError::Engine(KclErrorDetails {
message: format!("The request is not a modeling command: {:?}", req),
source_ranges: vec![*range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
format!("The request is not a modeling command: {:?}", req),
vec![*range],
)));
}
}
}
@ -595,10 +595,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
self.parse_batch_responses(last_id.into(), id_to_source_range, responses)
} else {
// We should never get here.
Err(KclError::Engine(KclErrorDetails {
message: format!("Failed to get batch response: {:?}", response),
source_ranges: vec![source_range],
}))
Err(KclError::Engine(KclErrorDetails::new(
format!("Failed to get batch response: {:?}", response),
vec![source_range],
)))
}
}
WebSocketRequest::ModelingCmdReq(ModelingCmdReq { cmd: _, cmd_id }) => {
@ -610,20 +610,20 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// request so we need the original request source range in case the engine returns
// an error.
let source_range = id_to_source_range.get(cmd_id.as_ref()).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
source_ranges: vec![],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![],
))
})?;
let ws_resp = self
.inner_send_modeling_cmd(cmd_id.into(), source_range, final_req, id_to_source_range)
.await?;
self.parse_websocket_response(ws_resp, source_range)
}
_ => Err(KclError::Engine(KclErrorDetails {
message: format!("The final request is not a modeling command: {:?}", final_req),
source_ranges: vec![source_range],
})),
_ => Err(KclError::Engine(KclErrorDetails::new(
format!("The final request is not a modeling command: {:?}", final_req),
vec![source_range],
))),
}
}
@ -729,10 +729,10 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
for (name, plane_id, color) in plane_settings {
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
// We should never get here.
KclError::Engine(KclErrorDetails {
message: format!("Failed to get default plane info for: {:?}", name),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to get default plane info for: {:?}", name),
vec![source_range],
))
})?;
planes.insert(
name,
@ -763,15 +763,14 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
WebSocketResponse::Success(success) => Ok(success.resp),
WebSocketResponse::Failure(fail) => {
let _request_id = fail.request_id;
Err(KclError::Engine(KclErrorDetails {
message: fail
.errors
Err(KclError::Engine(KclErrorDetails::new(
fail.errors
.iter()
.map(|e| e.message.clone())
.collect::<Vec<_>>()
.join("\n"),
source_ranges: vec![source_range],
}))
vec![source_range],
)))
}
}
}
@ -806,25 +805,25 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
BatchResponse::Failure { errors } => {
// Get the source range for the command.
let source_range = id_to_source_range.get(cmd_id).cloned().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get source range for command ID: {:?}", cmd_id),
source_ranges: vec![],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![],
))
})?;
return Err(KclError::Engine(KclErrorDetails {
message: errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
source_ranges: vec![source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range],
)));
}
}
}
// Return an error that we did not get an error or the response we wanted.
// This should never happen but who knows.
Err(KclError::Engine(KclErrorDetails {
message: format!("Failed to find response for command ID: {:?}", id),
source_ranges: vec![],
}))
Err(KclError::Engine(KclErrorDetails::new(
format!("Failed to find response for command ID: {:?}", id),
vec![],
)))
}
async fn modify_grid(

View File

@ -380,20 +380,39 @@ impl miette::Diagnostic for Report {
}
#[derive(Debug, Serialize, Deserialize, ts_rs::TS, Clone, PartialEq, Eq, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[error("{message}")]
#[ts(export)]
pub struct KclErrorDetails {
#[serde(rename = "sourceRanges")]
#[label(collection, "Errors")]
pub source_ranges: Vec<SourceRange>,
pub backtrace: Vec<BacktraceItem>,
#[serde(rename = "msg")]
pub message: String,
}
impl KclErrorDetails {
pub fn new(message: String, source_ranges: Vec<SourceRange>) -> KclErrorDetails {
let backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
KclErrorDetails {
source_ranges,
backtrace,
message,
}
}
}
impl KclError {
pub fn internal(message: String) -> KclError {
KclError::Internal(KclErrorDetails {
source_ranges: Default::default(),
backtrace: Default::default(),
message,
})
}
@ -455,45 +474,122 @@ impl KclError {
}
}
pub fn backtrace(&self) -> Vec<BacktraceItem> {
match self {
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => e.backtrace.clone(),
}
}
pub(crate) fn override_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical(e) => e.source_ranges = source_ranges,
KclError::Syntax(e) => e.source_ranges = source_ranges,
KclError::Semantic(e) => e.source_ranges = source_ranges,
KclError::ImportCycle(e) => e.source_ranges = source_ranges,
KclError::Type(e) => e.source_ranges = source_ranges,
KclError::Io(e) => e.source_ranges = source_ranges,
KclError::Unexpected(e) => e.source_ranges = source_ranges,
KclError::ValueAlreadyDefined(e) => e.source_ranges = source_ranges,
KclError::UndefinedValue(e) => e.source_ranges = source_ranges,
KclError::InvalidExpression(e) => e.source_ranges = source_ranges,
KclError::Engine(e) => e.source_ranges = source_ranges,
KclError::Internal(e) => e.source_ranges = source_ranges,
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => {
e.backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
source_range: *s,
fn_name: None,
})
.collect();
e.source_ranges = source_ranges;
}
}
new
}
pub(crate) fn add_source_ranges(&self, source_ranges: Vec<SourceRange>) -> Self {
pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical(e) => e.source_ranges.extend(source_ranges),
KclError::Syntax(e) => e.source_ranges.extend(source_ranges),
KclError::Semantic(e) => e.source_ranges.extend(source_ranges),
KclError::ImportCycle(e) => e.source_ranges.extend(source_ranges),
KclError::Type(e) => e.source_ranges.extend(source_ranges),
KclError::Io(e) => e.source_ranges.extend(source_ranges),
KclError::Unexpected(e) => e.source_ranges.extend(source_ranges),
KclError::ValueAlreadyDefined(e) => e.source_ranges.extend(source_ranges),
KclError::UndefinedValue(e) => e.source_ranges.extend(source_ranges),
KclError::InvalidExpression(e) => e.source_ranges.extend(source_ranges),
KclError::Engine(e) => e.source_ranges.extend(source_ranges),
KclError::Internal(e) => e.source_ranges.extend(source_ranges),
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
}
}
new
}
pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
let mut new = self.clone();
match &mut new {
KclError::Lexical(e)
| KclError::Syntax(e)
| KclError::Semantic(e)
| KclError::ImportCycle(e)
| KclError::Type(e)
| KclError::Io(e)
| KclError::Unexpected(e)
| KclError::ValueAlreadyDefined(e)
| KclError::UndefinedValue(e)
| KclError::InvalidExpression(e)
| KclError::Engine(e)
| KclError::Internal(e) => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
e.backtrace.push(BacktraceItem {
source_range,
fn_name: None,
});
e.source_ranges.push(source_range);
}
}
new
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, ts_rs::TS, thiserror::Error, miette::Diagnostic)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct BacktraceItem {
pub source_range: SourceRange,
pub fn_name: Option<String>,
}
impl std::fmt::Display for BacktraceItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(fn_name) = &self.fn_name {
write!(f, "{fn_name}: {:?}", self.source_range)
} else {
write!(f, "(fn): {:?}", self.source_range)
}
}
}
impl IntoDiagnostic for KclError {
@ -551,6 +647,7 @@ impl From<pyo3::PyErr> for KclError {
fn from(error: pyo3::PyErr) -> Self {
KclError::Internal(KclErrorDetails {
source_ranges: vec![],
backtrace: Default::default(),
message: error.to_string(),
})
}
@ -629,8 +726,13 @@ impl CompilationError {
impl From<CompilationError> for KclErrorDetails {
fn from(err: CompilationError) -> Self {
let backtrace = vec![BacktraceItem {
source_range: err.source_range,
fn_name: None,
}];
KclErrorDetails {
source_ranges: vec![err.source_range],
backtrace,
message: err.message,
}
}

View File

@ -70,10 +70,10 @@ pub(super) fn expect_properties<'a>(
) -> Result<&'a [Node<ObjectProperty>], KclError> {
assert_eq!(annotation.name().unwrap(), for_key);
Ok(&**annotation.properties.as_ref().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Empty `{for_key}` annotation"),
source_ranges: vec![annotation.as_source_range()],
})
KclError::Semantic(KclErrorDetails::new(
format!("Empty `{for_key}` annotation"),
vec![annotation.as_source_range()],
))
})?)
}
@ -84,10 +84,10 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
}
}
Err(KclError::Semantic(KclErrorDetails {
message: "Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
source_ranges: vec![expr.into()],
}))
Err(KclError::Semantic(KclErrorDetails::new(
"Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
vec![expr.into()],
)))
}
// Returns the unparsed number literal.
@ -98,10 +98,10 @@ pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
}
}
Err(KclError::Semantic(KclErrorDetails {
message: "Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
source_ranges: vec![expr.into()],
}))
Err(KclError::Semantic(KclErrorDetails::new(
"Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
vec![expr.into()],
)))
}
pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRange) -> Result<Option<Impl>, KclError> {
@ -113,14 +113,14 @@ pub(super) fn get_impl(annotations: &[Node<Annotation>], source_range: SourceRan
if &*p.key.name == IMPL {
if let Some(s) = p.value.ident_name() {
return Impl::from_str(s).map(Some).map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Invalid value for {} attribute, expected one of: {}",
IMPL,
IMPL_VALUES.join(", ")
),
source_ranges: vec![source_range],
})
vec![source_range],
))
});
}
}
@ -139,12 +139,12 @@ impl UnitLen {
"inch" | "in" => Ok(UnitLen::Inches),
"ft" => Ok(UnitLen::Feet),
"yd" => Ok(UnitLen::Yards),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!(
value => Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"
),
source_ranges: vec![source_range],
})),
vec![source_range],
))),
}
}
}
@ -154,10 +154,10 @@ impl UnitAngle {
match s {
"deg" => Ok(UnitAngle::Degrees),
"rad" => Ok(UnitAngle::Radians),
value => Err(KclError::Semantic(KclErrorDetails {
message: format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
source_ranges: vec![source_range],
})),
value => Err(KclError::Semantic(KclErrorDetails::new(
format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
vec![source_range],
))),
}
}
}

View File

@ -941,12 +941,10 @@ fn artifacts_to_update(
ModelingCmd::StartPath(_) => {
let mut return_arr = Vec::new();
let current_plane_id = path_to_plane_id_map.get(&artifact_command.cmd_id).ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message: format!(
"Expected a current plane ID when processing StartPath command, but we have none: {id:?}"
),
source_ranges: vec![range],
})
KclError::Internal(KclErrorDetails::new(
format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
vec![range],
))
})?;
return_arr.push(Artifact::Path(Path {
id,
@ -1065,10 +1063,10 @@ fn artifacts_to_update(
// TODO: Using the first one. Make sure to revisit this
// choice, don't think it matters for now.
path_id: ArtifactId::new(*loft_cmd.section_ids.first().ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message: format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
source_ranges: vec![range],
})
KclError::Internal(KclErrorDetails::new(
format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
vec![range],
))
})?),
surface_ids: Vec::new(),
edge_ids: Vec::new(),
@ -1108,12 +1106,12 @@ fn artifacts_to_update(
};
last_path = Some(path);
let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message:format!(
KclError::Internal(KclErrorDetails::new(
format!(
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
),
source_ranges: vec![range],
})
vec![range],
))
})?;
let extra_artifact = exec_artifacts.values().find(|a| {
if let Artifact::StartSketchOnFace(s) = a {
@ -1162,12 +1160,12 @@ fn artifacts_to_update(
continue;
};
let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message:format!(
KclError::Internal(KclErrorDetails::new(
format!(
"Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
),
source_ranges: vec![range],
})
vec![range],
))
})?;
let extra_artifact = exec_artifacts.values().find(|a| {
if let Artifact::StartSketchOnFace(s) = a {

View File

@ -131,10 +131,10 @@ impl ExecutorContext {
match statement {
BodyItem::ImportStatement(import_stmt) => {
if !matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails {
message: "Imports are only supported at the top-level of a file.".to_owned(),
source_ranges: vec![import_stmt.into()],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Imports are only supported at the top-level of a file.".to_owned(),
vec![import_stmt.into()],
)));
}
let source_range = SourceRange::from(import_stmt);
@ -157,28 +157,25 @@ impl ExecutorContext {
let mut ty = mem.get_from(&ty_name, env_ref, import_item.into(), 0).cloned();
if value.is_err() && ty.is_err() {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("{} is not defined in module", import_item.name.name),
source_ranges: vec![SourceRange::from(&import_item.name)],
}));
return Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("{} is not defined in module", import_item.name.name),
vec![SourceRange::from(&import_item.name)],
)));
}
// Check that the item is allowed to be imported (in at least one namespace).
if value.is_ok() && !module_exports.contains(&import_item.name.name) {
value = Err(KclError::Semantic(KclErrorDetails {
message: format!(
value = Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
import_item.name.name
),
source_ranges: vec![SourceRange::from(&import_item.name)],
}));
vec![SourceRange::from(&import_item.name)],
)));
}
if ty.is_ok() && !module_exports.contains(&ty_name) {
ty = Err(KclError::Semantic(KclErrorDetails {
message: String::new(),
source_ranges: vec![],
}));
ty = Err(KclError::Semantic(KclErrorDetails::new(String::new(), vec![])));
}
if value.is_err() && ty.is_err() {
@ -225,10 +222,10 @@ impl ExecutorContext {
.memory
.get_from(name, env_ref, source_range, 0)
.map_err(|_err| {
KclError::Internal(KclErrorDetails {
message: format!("{} is not defined in module (but was exported?)", name),
source_ranges: vec![source_range],
})
KclError::Internal(KclErrorDetails::new(
format!("{} is not defined in module (but was exported?)", name),
vec![source_range],
))
})?
.clone();
exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
@ -297,10 +294,10 @@ impl ExecutorContext {
let std_path = match &exec_state.mod_local.std_path {
Some(p) => p,
None => {
return Err(KclError::Semantic(KclErrorDetails {
message: "User-defined types are not yet supported.".to_owned(),
source_ranges: vec![metadata.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range],
)));
}
};
let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
@ -313,10 +310,10 @@ impl ExecutorContext {
.mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!("Redefinition of type {}.", ty.name.name),
source_ranges: vec![metadata.source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range],
))
})?;
if let ItemVisibility::Export = ty.visibility {
@ -343,10 +340,10 @@ impl ExecutorContext {
.mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!("Redefinition of type {}.", ty.name.name),
source_ranges: vec![metadata.source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range],
))
})?;
if let ItemVisibility::Export = ty.visibility {
@ -354,10 +351,10 @@ impl ExecutorContext {
}
}
None => {
return Err(KclError::Semantic(KclErrorDetails {
message: "User-defined types are not yet supported.".to_owned(),
source_ranges: vec![metadata.source_range],
}))
return Err(KclError::Semantic(KclErrorDetails::new(
"User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range],
)))
}
},
}
@ -368,10 +365,10 @@ impl ExecutorContext {
let metadata = Metadata::from(return_statement);
if matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails {
message: "Cannot return from outside a function.".to_owned(),
source_ranges: vec![metadata.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Cannot return from outside a function.".to_owned(),
vec![metadata.source_range],
)));
}
let value = self
@ -387,10 +384,10 @@ impl ExecutorContext {
.mut_stack()
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: "Multiple returns from a single function.".to_owned(),
source_ranges: vec![metadata.source_range],
})
KclError::Semantic(KclErrorDetails::new(
"Multiple returns from a single function.".to_owned(),
vec![metadata.source_range],
))
})?;
last_expr = None;
}
@ -493,10 +490,10 @@ impl ExecutorContext {
*cache = Some((val, er, items.clone()));
(er, items)
}),
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails {
message: "Cannot import items from foreign modules".to_owned(),
source_ranges: vec![geom.source_range],
})),
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new(
"Cannot import items from foreign modules".to_owned(),
vec![geom.source_range],
))),
ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
};
@ -572,13 +569,13 @@ impl ExecutorContext {
err.override_source_ranges(vec![source_range])
} else {
// TODO would be great to have line/column for the underlying error here
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Error loading imported file ({path}). Open it to view more details.\n {}",
err.message()
),
source_ranges: vec![source_range],
})
vec![source_range],
))
}
})
}
@ -639,11 +636,10 @@ impl ExecutorContext {
meta: vec![metadata.to_owned()],
}
} else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Rust implementation of functions is restricted to the standard library"
.to_owned(),
source_ranges: vec![metadata.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Rust implementation of functions is restricted to the standard library".to_owned(),
vec![metadata.source_range],
)));
}
} else {
// Snapshotting memory here is crucial for semantics so that we close
@ -667,18 +663,18 @@ impl ExecutorContext {
"you cannot declare variable {name} as %, because % can only be used in function calls"
);
return Err(KclError::Semantic(KclErrorDetails {
return Err(KclError::Semantic(KclErrorDetails::new(
message,
source_ranges: vec![pipe_substitution.into()],
}));
vec![pipe_substitution.into()],
)));
}
StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
Some(x) => x,
None => {
return Err(KclError::Semantic(KclErrorDetails {
message: "cannot use % outside a pipe expression".to_owned(),
source_ranges: vec![pipe_substitution.into()],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"cannot use % outside a pipe expression".to_owned(),
vec![pipe_substitution.into()],
)));
}
},
},
@ -750,13 +746,13 @@ fn apply_ascription(
} else {
""
};
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"could not coerce {} value to type {ty}{suggestion}",
value.human_friendly_type()
),
source_ranges: vec![source_range],
})
vec![source_range],
))
})
}
@ -783,10 +779,10 @@ impl Node<Name> {
ctx: &ExecutorContext,
) -> Result<&'a KclValue, KclError> {
if self.abs_path {
return Err(KclError::Semantic(KclErrorDetails {
message: "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
source_ranges: self.as_source_ranges(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
self.as_source_ranges(),
)));
}
if self.path.is_empty() {
@ -798,10 +794,10 @@ impl Node<Name> {
let value = match mem_spec {
Some((env, exports)) => {
if !exports.contains(&p.name) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Item {} not found in module's exported items", p.name),
source_ranges: p.as_source_ranges(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Item {} not found in module's exported items", p.name),
p.as_source_ranges(),
)));
}
exec_state
@ -813,13 +809,13 @@ impl Node<Name> {
};
let KclValue::Module { value: module_id, .. } = value else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Identifier in path must refer to a module, found {}",
value.human_friendly_type()
),
source_ranges: p.as_source_ranges(),
}));
p.as_source_ranges(),
)));
};
mem_spec = Some(
@ -830,10 +826,10 @@ impl Node<Name> {
let (env, exports) = mem_spec.unwrap();
if !exports.contains(&self.name.name) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Item {} not found in module's exported items", self.name.name),
source_ranges: self.name.as_source_ranges(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Item {} not found in module's exported items", self.name.name),
self.name.as_source_ranges(),
)));
}
exec_state
@ -861,46 +857,44 @@ impl Node<MemberExpression> {
if let Some(value) = map.get(&property) {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("Property '{property}' not found in object"),
source_ranges: vec![self.clone().into()],
}))
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("Property '{property}' not found in object"),
vec![self.clone().into()],
)))
}
}
(KclValue::Object { .. }, Property::String(property), true) => Err(KclError::Semantic(KclErrorDetails {
message: format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
source_ranges: vec![self.clone().into()],
})),
(KclValue::Object { .. }, Property::String(property), true) => {
Err(KclError::Semantic(KclErrorDetails::new(
format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
vec![self.clone().into()],
)))
}
(KclValue::Object { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only strings can be used as the property of an object, but you're using {article} {t}",
),
source_ranges: vec![self.clone().into()],
}))
Err(KclError::Semantic(KclErrorDetails::new(
format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
vec![self.clone().into()],
)))
}
(KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("The array doesn't have any item at index {index}"),
source_ranges: vec![self.clone().into()],
}))
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("The array doesn't have any item at index {index}"),
vec![self.clone().into()],
)))
}
}
(KclValue::HomArray { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
message: format!(
"Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",
),
source_ranges: vec![self.clone().into()],
}))
Err(KclError::Semantic(KclErrorDetails::new(
format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
vec![self.clone().into()],
)))
}
(KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => Ok(KclValue::Sketch {
value: Box::new(value.sketch),
@ -918,10 +912,10 @@ impl Node<MemberExpression> {
(being_indexed, _, _) => {
let t = being_indexed.human_friendly_type();
let article = article_for(&t);
Err(KclError::Semantic(KclErrorDetails {
message: format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
source_ranges: vec![self.clone().into()],
}))
Err(KclError::Semantic(KclErrorDetails::new(
format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
vec![self.clone().into()],
)))
}
}
}
@ -996,26 +990,26 @@ impl Node<BinaryExpression> {
meta: _,
} = left_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type()
),
source_ranges: vec![self.left.clone().into()],
}));
vec![self.left.clone().into()],
)));
};
let KclValue::Bool {
value: right_value,
meta: _,
} = right_value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type()
),
source_ranges: vec![self.right.clone().into()],
}));
vec![self.right.clone().into()],
)));
};
let raw_value = match self.operator {
BinaryOperator::Or => left_value || right_value,
@ -1115,13 +1109,13 @@ impl Node<UnaryExpression> {
meta: _,
} = value
else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Cannot apply unary operator ! to non-boolean value: {}",
value.human_friendly_type()
),
source_ranges: vec![self.into()],
}));
vec![self.into()],
)));
};
let meta = vec![Metadata {
source_range: self.into(),
@ -1136,13 +1130,13 @@ impl Node<UnaryExpression> {
let value = &self.argument.get_result(exec_state, ctx).await?;
let err = || {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"You can only negate numbers, planes, or lines, but this is a {}",
value.human_friendly_type()
),
source_ranges: vec![self.into()],
})
vec![self.into()],
))
};
match value {
KclValue::Number { value, ty, .. } => {
@ -1239,10 +1233,10 @@ pub(crate) async fn execute_pipe_body(
ctx: &ExecutorContext,
) -> Result<KclValue, KclError> {
let Some((first, body)) = body.split_first() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Pipe expressions cannot be empty".to_owned(),
source_ranges: vec![source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Pipe expressions cannot be empty".to_owned(),
vec![source_range],
)));
};
// Evaluate the first element in the pipeline.
// They use the pipe_value from some AST node above this, so that if pipe expression is nested in a larger pipe expression,
@ -1277,10 +1271,10 @@ async fn inner_execute_pipe_body(
) -> Result<KclValue, KclError> {
for expression in body {
if let Expr::TagDeclarator(_) = expression {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("This cannot be in a PipeExpression: {:?}", expression),
source_ranges: vec![expression.into()],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("This cannot be in a PipeExpression: {:?}", expression),
vec![expression.into()],
)));
}
let metadata = Metadata {
source_range: SourceRange::from(expression),
@ -1349,35 +1343,37 @@ impl Node<ArrayRangeExpression> {
StatementKind::Expression,
)
.await?;
let (start, start_ty) = start_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Expected int but found {}", start_val.human_friendly_type()),
}))?;
let (start, start_ty) = start_val
.as_int_with_ty()
.ok_or(KclError::Semantic(KclErrorDetails::new(
format!("Expected int but found {}", start_val.human_friendly_type()),
vec![self.into()],
)))?;
let metadata = Metadata::from(&self.end_element);
let end_val = ctx
.execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
.await?;
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Expected int but found {}", end_val.human_friendly_type()),
}))?;
let (end, end_ty) = end_val.as_int_with_ty().ok_or(KclError::Semantic(KclErrorDetails::new(
format!("Expected int but found {}", end_val.human_friendly_type()),
vec![self.into()],
)))?;
if start_ty != end_ty {
let start = start_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: start_ty });
let start = fmt::human_display_number(start.n, start.ty);
let end = end_val.as_ty_f64().unwrap_or(TyF64 { n: 0.0, ty: end_ty });
let end = fmt::human_display_number(end.n, end.ty);
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Range start and end must be of the same type, but found {start} and {end}"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Range start and end must be of the same type, but found {start} and {end}"),
vec![self.into()],
)));
}
if end < start {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.into()],
message: format!("Range start is greater than range end: {start} .. {end}"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Range start is greater than range end: {start} .. {end}"),
vec![self.into()],
)));
}
let range: Vec<_> = if self.end_inclusive {
@ -1438,10 +1434,10 @@ fn article_for<S: AsRef<str>>(s: S) -> &'static str {
fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
v.as_ty_f64().ok_or_else(|| {
let actual_type = v.human_friendly_type();
KclError::Semantic(KclErrorDetails {
source_ranges: vec![source_range],
message: format!("Expected a number, but found {actual_type}",),
})
KclError::Semantic(KclErrorDetails::new(
format!("Expected a number, but found {actual_type}",),
vec![source_range],
))
})
}
@ -1530,16 +1526,16 @@ impl Property {
if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x))
} else {
Err(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message: format!("{value} is not a valid index, indices must be whole numbers >= 0"),
}))
Err(KclError::Semantic(KclErrorDetails::new(
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
property_sr,
)))
}
}
_ => Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![sr],
message: "Only numbers (>= 0) can be indexes".to_owned(),
})),
_ => Err(KclError::Semantic(KclErrorDetails::new(
"Only numbers (>= 0) can be indexes".to_owned(),
vec![sr],
))),
}
}
}
@ -1547,12 +1543,7 @@ impl Property {
}
fn jvalue_to_prop(value: &KclValue, property_sr: Vec<SourceRange>, name: &str) -> Result<Property, KclError> {
let make_err = |message: String| {
Err::<Property, _>(KclError::Semantic(KclErrorDetails {
source_ranges: property_sr,
message,
}))
};
let make_err = |message: String| Err::<Property, _>(KclError::Semantic(KclErrorDetails::new(message, property_sr)));
match value {
KclValue::Number{value: num, .. } => {
let num = *num;
@ -1795,10 +1786,10 @@ d = b + c
crate::engine::conn_mock::EngineConnection::new()
.await
.map_err(|err| {
KclError::Internal(crate::errors::KclErrorDetails {
message: format!("Failed to create mock engine connection: {}", err),
source_ranges: vec![SourceRange::default()],
})
KclError::Internal(KclErrorDetails::new(
format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()],
))
})
.unwrap(),
)),

View File

@ -1,13 +1,16 @@
use async_recursion::async_recursion;
use indexmap::IndexMap;
use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation};
use super::{types::ArrayLen, EnvironmentRef};
use crate::{
docs::StdLibFn,
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::FunctionSource, memory, types::RuntimeType, BodyType, ExecState, ExecutorContext, KclValue,
Metadata, StatementKind, TagEngineInfo, TagIdentifier,
cad_op::{Group, OpArg, OpKclValue, Operation},
kcl_value::FunctionSource,
memory,
types::RuntimeType,
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
},
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
source_range::SourceRange,
@ -15,9 +18,6 @@ use crate::{
CompilationError,
};
use super::types::ArrayLen;
use super::EnvironmentRef;
#[derive(Debug, Clone)]
pub struct Args {
/// Positional args.
@ -281,6 +281,13 @@ impl Node<CallExpressionKw> {
def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
.await
.map(Option::unwrap)
.map_err(|e| {
// This is used for the backtrace display. We don't add
// another location the way we do for user-defined
// functions because the error uses the Args, which
// already points here.
e.set_last_backtrace_fn_name(Some(func.name()))
})
}
None => {
// Clone the function so that we can use a mutable reference to
@ -288,10 +295,10 @@ impl Node<CallExpressionKw> {
let func = fn_name.get_result(exec_state, ctx).await?.clone();
let Some(fn_src) = func.as_fn() else {
return Err(KclError::Semantic(KclErrorDetails {
message: "cannot call this because it isn't a function".to_string(),
source_ranges: vec![callsite],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"cannot call this because it isn't a function".to_string(),
vec![callsite],
)));
};
let return_value = fn_src
@ -299,7 +306,10 @@ impl Node<CallExpressionKw> {
.await
.map_err(|e| {
// Add the call expression to the source ranges.
e.add_source_ranges(vec![callsite])
//
// TODO: Use the name that the function was defined
// with, not the identifier it was used with.
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
})?;
let result = return_value.ok_or_else(move || {
@ -308,10 +318,10 @@ impl Node<CallExpressionKw> {
if let KclValue::Function { meta, .. } = func {
source_ranges = meta.iter().map(|m| m.source_range).collect();
};
KclError::UndefinedValue(KclErrorDetails {
message: format!("Result of user-defined function {} is undefined", fn_name),
KclError::UndefinedValue(KclErrorDetails::new(
format!("Result of user-defined function {} is undefined", fn_name),
source_ranges,
})
))
})?;
Ok(result)
@ -490,10 +500,10 @@ fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut Ex
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
let mut t = t.clone();
let Some(info) = t.get_cur_info() else {
return Err(KclError::Internal(KclErrorDetails {
message: format!("Tag {} does not have path info", tag.name),
source_ranges: vec![tag.into()],
}));
return Err(KclError::Internal(KclErrorDetails::new(
format!("Tag {} does not have path info", tag.name),
vec![tag.into()],
)));
};
let mut info = info.clone();
@ -608,10 +618,10 @@ fn type_check_params_kw(
// 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 {
KclError::Semantic(KclErrorDetails::new(
message,
source_ranges: vec![arg.source_range],
})
vec![arg.source_range],
))
})?;
}
}
@ -673,8 +683,8 @@ fn type_check_params_kw(
exec_state,
)
.map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"The input argument of {} requires a value with type `{}`, but found {}",
fn_name
.map(|n| format!("`{}`", n))
@ -682,8 +692,8 @@ fn type_check_params_kw(
ty,
arg.1.value.human_friendly_type()
),
source_ranges: vec![arg.1.source_range],
})
vec![arg.1.source_range],
))
})?;
}
} else if let Some((name, _)) = &fn_def.input_arg {
@ -730,13 +740,13 @@ fn assign_args_to_params_kw(
.add(name.clone(), value, default_val.source_range())?;
}
None => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges,
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"This function requires a parameter {}, but you haven't passed it one.",
name
),
}));
source_ranges,
)));
}
},
}
@ -747,16 +757,15 @@ fn assign_args_to_params_kw(
let Some(unlabeled) = unlabelled else {
return Err(if args.kw_args.labeled.contains_key(param_name) {
KclError::Semantic(KclErrorDetails {
KclError::Semantic(KclErrorDetails::new(
format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
source_ranges,
message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
})
))
} else {
KclError::Semantic(KclErrorDetails {
KclError::Semantic(KclErrorDetails::new(
"This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
source_ranges,
message: "This function expects an unlabeled first parameter, but you haven't passed it one."
.to_owned(),
})
))
});
};
exec_state.mut_stack().add(
@ -789,14 +798,14 @@ fn coerce_result_type(
ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
}
let val = val.coerce(&ty, exec_state).map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"This function requires its result to be of type `{}`, but found {}",
ty.human_friendly_type(),
val.human_friendly_type(),
),
source_ranges: ret_ty.as_source_ranges(),
})
ret_ty.as_source_ranges(),
))
})?;
Ok(Some(val))
} else {
@ -873,10 +882,10 @@ mod test {
"all params required, none given, should error",
vec![req_param("x")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange::default()],
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
})),
Err(KclError::Semantic(KclErrorDetails::new(
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
vec![SourceRange::default()],
))),
),
(
"all params optional, none given, should be OK",
@ -888,10 +897,10 @@ mod test {
"mixed params, too few given",
vec![req_param("x"), opt_param("y")],
vec![],
Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![SourceRange::default()],
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
})),
Err(KclError::Semantic(KclErrorDetails::new(
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
vec![SourceRange::default()],
))),
),
(
"mixed params, minimum given, should be OK",

View File

@ -469,18 +469,18 @@ impl TryFrom<PlaneData> for PlaneInfo {
PlaneData::NegYZ => PlaneName::NegYz,
PlaneData::Plane(_) => {
// We will never get here since we already checked for PlaneData::Plane.
return Err(KclError::Internal(KclErrorDetails {
message: format!("PlaneData {:?} not found", value),
source_ranges: Default::default(),
}));
return Err(KclError::Internal(KclErrorDetails::new(
format!("PlaneData {:?} not found", value),
Default::default(),
)));
}
};
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
KclError::Internal(KclErrorDetails {
message: format!("Plane {} not found", name),
source_ranges: Default::default(),
})
KclError::Internal(KclErrorDetails::new(
format!("Plane {} not found", name),
Default::default(),
))
})?;
Ok(info.clone())

View File

@ -37,53 +37,43 @@ pub async fn import_foreign(
) -> Result<PreImportedGeometry, KclError> {
// Make sure the file exists.
if !ctxt.fs.exists(file_path, source_range).await? {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("File `{}` does not exist.", file_path.display()),
source_ranges: vec![source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("File `{}` does not exist.", file_path.display()),
vec![source_range],
)));
}
let ext_format = get_import_format_from_extension(file_path.extension().ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("No file extension found for `{}`", file_path.display()),
source_ranges: vec![source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("No file extension found for `{}`", file_path.display()),
vec![source_range],
))
})?)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
// Get the format type from the extension of the file.
let format = if let Some(format) = format {
// Validate the given format with the extension format.
validate_extension_format(ext_format, format.clone()).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
validate_extension_format(ext_format, format.clone())
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
format
} else {
ext_format
};
// Get the file contents for each file path.
let file_contents = ctxt.fs.read(file_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
let file_contents = ctxt
.fs
.read(file_path, source_range)
.await
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
// We want the file_path to be without the parent.
let file_name = file_path.file_name().map(|p| p.to_string()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the file name from the path `{}`", file_path.display()),
source_ranges: vec![source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Could not get the file name from the path `{}`", file_path.display()),
vec![source_range],
))
})?;
let mut import_files = vec![kcmc::ImportFile {
path: file_name.to_string(),
@ -96,12 +86,8 @@ pub async fn import_foreign(
// Check if the file is a binary gltf file, in that case we don't need to import the bin
// file.
if !file_contents.starts_with(b"glTF") {
let json = gltf_json::Root::from_slice(&file_contents).map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
let json = gltf_json::Root::from_slice(&file_contents)
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
// Read the gltf file and check if there is a bin file.
for buffer in json.buffers.iter() {
@ -109,18 +95,16 @@ pub async fn import_foreign(
if !uri.starts_with("data:") {
// We want this path relative to the file_path given.
let bin_path = file_path.parent().map(|p| p.join(uri)).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Could not get the parent path of the file `{}`", file_path.display()),
source_ranges: vec![source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Could not get the parent path of the file `{}`", file_path.display()),
vec![source_range],
))
})?;
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: e.to_string(),
source_ranges: vec![source_range],
})
})?;
let bin_contents =
ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range]))
})?;
import_files.push(ImportFile {
path: uri.to_string(),
@ -157,13 +141,13 @@ pub(super) fn format_from_annotations(
if p.key.name == annotations::IMPORT_FORMAT {
result = Some(
get_import_format_from_extension(annotations::expect_ident(&p.value)?).map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Unknown format for import, expected one of: {}",
crate::IMPORT_FILE_EXTENSIONS.join(", ")
),
source_ranges: vec![p.as_source_range()],
})
vec![p.as_source_range()],
))
})?,
);
break;
@ -175,10 +159,10 @@ pub(super) fn format_from_annotations(
path.extension()
.and_then(|ext| get_import_format_from_extension(ext).ok())
})
.ok_or(KclError::Semantic(KclErrorDetails {
message: "Unknown or missing extension, and no specified format for imported file".to_owned(),
source_ranges: vec![import_source_range],
}))?;
.ok_or(KclError::Semantic(KclErrorDetails::new(
"Unknown or missing extension, and no specified format for imported file".to_owned(),
vec![import_source_range],
)))?;
for p in props {
match p.key.name.as_str() {
@ -190,15 +174,15 @@ pub(super) fn format_from_annotations(
}
annotations::IMPORT_FORMAT => {}
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Unexpected annotation for import, expected one of: {}, {}, {}",
annotations::IMPORT_FORMAT,
annotations::IMPORT_COORDS,
annotations::IMPORT_LENGTH_UNIT
),
source_ranges: vec![p.as_source_range()],
}))
vec![p.as_source_range()],
)))
}
}
}
@ -215,8 +199,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
}
let Some(coords) = coords else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Unknown coordinate system: {coords_str}, expected one of: {}",
annotations::IMPORT_COORDS_VALUES
.iter()
@ -224,8 +208,8 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
.collect::<Vec<_>>()
.join(", ")
),
source_ranges: vec![source_range],
}));
vec![source_range],
)));
};
match fmt {
@ -233,13 +217,13 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
InputFormat3d::Ply(opts) => opts.coords = coords,
InputFormat3d::Stl(opts) => opts.coords = coords,
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"`{}` option cannot be applied to the specified format",
annotations::IMPORT_COORDS
),
source_ranges: vec![source_range],
}))
vec![source_range],
)))
}
}
@ -254,13 +238,13 @@ fn set_length_unit(fmt: &mut InputFormat3d, units_str: &str, source_range: Sourc
InputFormat3d::Ply(opts) => opts.units = units.into(),
InputFormat3d::Stl(opts) => opts.units = units.into(),
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"`{}` option cannot be applied to the specified format",
annotations::IMPORT_LENGTH_UNIT
),
source_ranges: vec![source_range],
}))
vec![source_range],
)))
}
}

View File

@ -543,17 +543,13 @@ impl KclValue {
/// If this value fits in a u32, return it.
pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Expected an integer >= 0".to_owned(),
source_ranges: source_ranges.clone(),
})
KclError::Semantic(KclErrorDetails::new(
"Expected an integer >= 0".to_owned(),
source_ranges.clone(),
))
})?;
u32::try_from(u).map_err(|_| {
KclError::Semantic(KclErrorDetails {
message: "Number was too big".to_owned(),
source_ranges,
})
})
u32::try_from(u)
.map_err(|_| KclError::Semantic(KclErrorDetails::new("Number was too big".to_owned(), source_ranges)))
}
/// If this value is of type function, return it.
@ -568,10 +564,10 @@ impl KclValue {
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
match self {
KclValue::TagIdentifier(t) => Ok(*t.clone()),
_ => Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a tag identifier: {:?}", self),
source_ranges: self.clone().into(),
})),
_ => Err(KclError::Semantic(KclErrorDetails::new(
format!("Not a tag identifier: {:?}", self),
self.clone().into(),
))),
}
}
@ -579,20 +575,20 @@ impl KclValue {
pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
match self {
KclValue::TagDeclarator(t) => Ok((**t).clone()),
_ => Err(KclError::Semantic(KclErrorDetails {
message: format!("Not a tag declarator: {:?}", self),
source_ranges: self.clone().into(),
})),
_ => Err(KclError::Semantic(KclErrorDetails::new(
format!("Not a tag declarator: {:?}", self),
self.clone().into(),
))),
}
}
/// If this KCL value is a bool, retrieve it.
pub fn get_bool(&self) -> Result<bool, KclError> {
let Self::Bool { value: b, .. } = self else {
return Err(KclError::Type(KclErrorDetails {
source_ranges: self.into(),
message: format!("Expected bool, found {}", self.human_friendly_type()),
}));
return Err(KclError::Type(KclErrorDetails::new(
format!("Expected bool, found {}", self.human_friendly_type()),
self.into(),
)));
};
Ok(*b)
}

View File

@ -364,10 +364,10 @@ impl ProgramMemory {
};
}
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("`{}` is not defined", var),
source_ranges: vec![source_range],
}))
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("`{}` is not defined", var),
vec![source_range],
)))
}
/// Iterate over all key/value pairs in the specified environment which satisfy the provided
@ -485,10 +485,10 @@ impl ProgramMemory {
};
}
Err(KclError::UndefinedValue(KclErrorDetails {
message: format!("`{}` is not defined", var),
source_ranges: vec![],
}))
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("`{}` is not defined", var),
vec![],
)))
}
}
@ -643,10 +643,10 @@ impl Stack {
pub fn add(&mut self, key: String, value: KclValue, source_range: SourceRange) -> Result<(), KclError> {
let env = self.memory.get_env(self.current_env.index());
if env.contains_key(&key) {
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
message: format!("Cannot redefine `{}`", key),
source_ranges: vec![source_range],
}));
return Err(KclError::ValueAlreadyDefined(KclErrorDetails::new(
format!("Cannot redefine `{}`", key),
vec![source_range],
)));
}
self.memory.stats.mutation_count.fetch_add(1, Ordering::Relaxed);

View File

@ -858,10 +858,9 @@ impl ExecutorContext {
for module in modules {
let Some((import_stmt, module_id, module_path, repr)) = universe.get(&module) else {
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(KclErrorDetails {
message: format!("Module {module} not found in universe"),
source_ranges: Default::default(),
})));
return Err(KclErrorWithOutputs::no_outputs(KclError::Internal(
KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()),
)));
};
let module_id = *module_id;
let module_path = module_path.clone();
@ -921,10 +920,10 @@ impl ExecutorContext {
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
}
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails {
message: format!("Module {module_path} not found in universe"),
source_ranges: vec![source_range],
})),
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails::new(
format!("Module {module_path} not found in universe"),
vec![source_range],
))),
}
};
@ -1288,10 +1287,10 @@ impl ExecutorContext {
.await?;
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
return Err(KclError::Internal(crate::errors::KclErrorDetails {
message: format!("Expected Export response, got {resp:?}",),
source_ranges: vec![SourceRange::default()],
}));
return Err(KclError::Internal(crate::errors::KclErrorDetails::new(
format!("Expected Export response, got {resp:?}",),
vec![SourceRange::default()],
)));
};
Ok(files)
@ -1308,10 +1307,10 @@ impl ExecutorContext {
coords: *kittycad_modeling_cmds::coord::KITTYCAD,
created: if deterministic_time {
Some("2021-01-01T00:00:00Z".parse().map_err(|e| {
KclError::Internal(crate::errors::KclErrorDetails {
message: format!("Failed to parse date: {}", e),
source_ranges: vec![SourceRange::default()],
})
KclError::Internal(crate::errors::KclErrorDetails::new(
format!("Failed to parse date: {}", e),
vec![SourceRange::default()],
))
})?)
} else {
None
@ -1388,10 +1387,10 @@ pub(crate) async fn parse_execute_with_project_dir(
let exec_ctxt = ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new().await.map_err(|err| {
KclError::Internal(crate::errors::KclErrorDetails {
message: format!("Failed to create mock engine connection: {}", err),
source_ranges: vec![SourceRange::default()],
})
KclError::Internal(crate::errors::KclErrorDetails::new(
format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()],
))
})?,
)),
fs: Arc::new(crate::fs::FileManager::new()),
@ -1804,10 +1803,10 @@ foo
let err = result.unwrap_err();
assert_eq!(
err,
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: #".to_owned(),
source_ranges: vec![SourceRange::new(14, 15, ModuleId::default())],
}),
KclError::Syntax(KclErrorDetails::new(
"Unexpected token: #".to_owned(),
vec![SourceRange::new(14, 15, ModuleId::default())],
)),
);
}
@ -2063,10 +2062,10 @@ notTagIdentifier = !myTag";
// TODO: We don't currently parse this, but we should. It should be
// a runtime error instead.
parse_execute(code10).await.unwrap_err(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: !".to_owned(),
source_ranges: vec![SourceRange::new(10, 11, ModuleId::default())],
})
KclError::Syntax(KclErrorDetails::new(
"Unexpected token: !".to_owned(),
vec![SourceRange::new(10, 11, ModuleId::default())],
))
);
let code11 = "
@ -2076,10 +2075,10 @@ notPipeSub = 1 |> identity(!%))";
// TODO: We don't currently parse this, but we should. It should be
// a runtime error instead.
parse_execute(code11).await.unwrap_err(),
KclError::Syntax(KclErrorDetails {
message: "Unexpected token: |>".to_owned(),
source_ranges: vec![SourceRange::new(44, 46, ModuleId::default())],
})
KclError::Syntax(KclErrorDetails::new(
"Unexpected token: |>".to_owned(),
vec![SourceRange::new(44, 46, ModuleId::default())],
))
);
// TODO: Add these tests when we support these types.

View File

@ -276,8 +276,8 @@ impl ExecState {
}
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails {
message: format!(
KclError::ImportCycle(KclErrorDetails::new(
format!(
"circular import of modules is not allowed: {} -> {}",
self.global
.mod_loader
@ -288,8 +288,8 @@ impl ExecState {
.join(" -> "),
path,
),
source_ranges: vec![source_range],
})
vec![source_range],
))
}
pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
@ -389,14 +389,14 @@ impl MetaSettings {
self.kcl_version = value;
}
name => {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
annotations::SETTINGS_UNIT_LENGTH,
annotations::SETTINGS_UNIT_ANGLE
),
source_ranges: vec![annotation.as_source_range()],
}))
vec![annotation.as_source_range()],
)))
}
}
}

View File

@ -155,9 +155,8 @@ impl RuntimeType {
.map(RuntimeType::Union),
Type::Object { properties } => properties
.into_iter()
.map(|p| {
RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
.map(|ty| (p.identifier.inner.name, ty))
.map(|(id, ty)| {
RuntimeType::from_parsed(ty.inner, exec_state, source_range).map(|ty| (id.name.clone(), ty))
})
.collect::<Result<Vec<_>, CompilationError>>()
.map(RuntimeType::Object),

View File

@ -28,19 +28,19 @@ impl Default for FileManager {
impl FileSystem for FileManager {
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
tokio::fs::read(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails {
message: format!("Failed to read file `{}`: {}", path.display(), e),
source_ranges: vec![source_range],
})
KclError::Io(KclErrorDetails::new(
format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range],
))
})
}
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
tokio::fs::read_to_string(&path.0).await.map_err(|e| {
KclError::Io(KclErrorDetails {
message: format!("Failed to read file `{}`: {}", path.display(), e),
source_ranges: vec![source_range],
})
KclError::Io(KclErrorDetails::new(
format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range],
))
})
}
@ -49,10 +49,10 @@ impl FileSystem for FileManager {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(KclError::Io(KclErrorDetails {
message: format!("Failed to check if file `{}` exists: {}", path.display(), e),
source_ranges: vec![source_range],
}))
Err(KclError::Io(KclErrorDetails::new(
format!("Failed to check if file `{}` exists: {}", path.display(), e),
vec![source_range],
)))
}
})
}
@ -71,10 +71,10 @@ impl FileSystem for FileManager {
}
let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
KclError::Io(KclErrorDetails {
message: format!("Failed to read directory `{}`: {}", path.display(), e),
source_ranges: vec![source_range],
})
KclError::Io(KclErrorDetails::new(
format!("Failed to read directory `{}`: {}", path.display(), e),
vec![source_range],
))
})?;
while let Ok(Some(entry)) = read_dir.next_entry().await {

View File

@ -46,18 +46,16 @@ unsafe impl Sync for FileManager {}
#[async_trait::async_trait]
impl FileSystem for FileManager {
async fn read(&self, path: &TypedPath, source_range: SourceRange) -> Result<Vec<u8>, KclError> {
let promise = self.manager.read_file(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.read_file(path.to_string_lossy())
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from engine: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to wait for promise from engine: {:?}", e),
vec![source_range],
))
})?;
let array = js_sys::Uint8Array::new(&value);
@ -69,35 +67,33 @@ impl FileSystem for FileManager {
async fn read_to_string(&self, path: &TypedPath, source_range: SourceRange) -> Result<String, KclError> {
let bytes = self.read(path, source_range).await?;
let string = String::from_utf8(bytes).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to convert bytes to string: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to convert bytes to string: {:?}", e),
vec![source_range],
))
})?;
Ok(string)
}
async fn exists(&self, path: &TypedPath, source_range: SourceRange) -> Result<bool, crate::errors::KclError> {
let promise = self.manager.exists(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.exists(path.to_string_lossy())
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from engine: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to wait for promise from engine: {:?}", e),
vec![source_range],
))
})?;
let it_exists = value.as_bool().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: "Failed to convert value to bool".to_string(),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
"Failed to convert value to bool".to_string(),
vec![source_range],
))
})?;
Ok(it_exists)
@ -108,32 +104,30 @@ impl FileSystem for FileManager {
path: &TypedPath,
source_range: SourceRange,
) -> Result<Vec<TypedPath>, crate::errors::KclError> {
let promise = self.manager.get_all_files(path.to_string_lossy()).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: e.to_string().into(),
source_ranges: vec![source_range],
})
})?;
let promise = self
.manager
.get_all_files(path.to_string_lossy())
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to wait for promise from javascript: {:?}", e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to wait for promise from javascript: {:?}", e),
vec![source_range],
))
})?;
let s = value.as_string().ok_or_else(|| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to get string from response from javascript: `{:?}`", value),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to get string from response from javascript: `{:?}`", value),
vec![source_range],
))
})?;
let files: Vec<String> = serde_json::from_str(&s).map_err(|e| {
KclError::Engine(KclErrorDetails {
message: format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
source_ranges: vec![source_range],
})
KclError::Engine(KclErrorDetails::new(
format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
vec![source_range],
))
})?;
Ok(files.into_iter().map(|s| TypedPath::from(&s)).collect())

View File

@ -86,7 +86,8 @@ mod wasm;
pub use coredump::CoreDump;
pub use engine::{AsyncTasks, EngineManager, EngineStats};
pub use errors::{
CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report, ReportWithOutputs,
BacktraceItem, CompilationError, ConnectionError, ExecError, KclError, KclErrorWithOutputs, Report,
ReportWithOutputs,
};
pub use execution::{
bust_cache, clear_mem_cache,

View File

@ -58,8 +58,8 @@ impl ModuleLoader {
}
pub(crate) fn import_cycle_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails {
message: format!(
KclError::ImportCycle(KclErrorDetails::new(
format!(
"circular import of modules is not allowed: {} -> {}",
self.import_stack
.iter()
@ -68,8 +68,8 @@ impl ModuleLoader {
.join(" -> "),
path,
),
source_ranges: vec![source_range],
})
vec![source_range],
))
}
pub(crate) fn enter_module(&mut self, path: &ModulePath) {
@ -169,10 +169,10 @@ impl ModulePath {
ModulePath::Std { value: name } => Ok(ModuleSource {
source: read_std(name)
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: format!("Cannot find standard library module to import: std::{name}."),
source_ranges: vec![source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Cannot find standard library module to import: std::{name}."),
vec![source_range],
))
})
.map(str::to_owned)?,
path: self.clone(),

View File

@ -212,8 +212,9 @@ impl Type {
Type::Object { properties } => {
hasher.update(b"FnArgType::Object");
hasher.update(properties.len().to_ne_bytes());
for prop in properties.iter_mut() {
hasher.update(prop.compute_digest());
for (id, ty) in properties.iter_mut() {
hasher.update(id.compute_digest());
hasher.update(ty.compute_digest());
}
}
}

View File

@ -3315,7 +3315,7 @@ pub enum Type {
},
// An object type.
Object {
properties: Vec<Parameter>,
properties: Vec<(Node<Identifier>, Node<Type>)>,
},
}
@ -3348,10 +3348,8 @@ impl fmt::Display for Type {
} else {
write!(f, ",")?;
}
write!(f, " {}:", p.identifier.name)?;
if let Some(ty) = &p.type_ {
write!(f, " {}", ty.inner)?;
}
write!(f, " {}:", p.0.name)?;
write!(f, " {}", p.1)?;
}
write!(f, " }}")
}
@ -3988,7 +3986,7 @@ cylinder = startSketchOn(-XZ)
#[tokio::test(flavor = "multi_thread")]
async fn test_parse_type_args_object_on_functions() {
let some_program_string = r#"fn thing(arg0: [number], arg1: {thing: number, things: [string], more?: string}, tag?: string) {
let some_program_string = r#"fn thing(arg0: [number], arg1: {thing: number, things: [string], more: string}, tag?: string) {
return arg0
}"#;
let module_id = ModuleId::default();
@ -4015,8 +4013,8 @@ cylinder = startSketchOn(-XZ)
params[1].type_.as_ref().unwrap().inner,
Type::Object {
properties: vec![
Parameter {
identifier: Node::new(
(
Node::new(
Identifier {
name: "thing".to_owned(),
digest: None,
@ -4025,18 +4023,15 @@ cylinder = startSketchOn(-XZ)
37,
module_id,
),
type_: Some(Node::new(
Node::new(
Type::Primitive(PrimitiveType::Number(NumericSuffix::None)),
39,
45,
module_id
)),
default_value: None,
labeled: true,
digest: None,
},
Parameter {
identifier: Node::new(
),
),
(
Node::new(
Identifier {
name: "things".to_owned(),
digest: None,
@ -4045,7 +4040,7 @@ cylinder = startSketchOn(-XZ)
53,
module_id,
),
type_: Some(Node::new(
Node::new(
Type::Array {
ty: Box::new(Type::Primitive(PrimitiveType::String)),
len: ArrayLen::None
@ -4053,13 +4048,10 @@ cylinder = startSketchOn(-XZ)
56,
62,
module_id
)),
default_value: None,
labeled: true,
digest: None
},
Parameter {
identifier: Node::new(
)
),
(
Node::new(
Identifier {
name: "more".to_owned(),
digest: None
@ -4068,11 +4060,8 @@ cylinder = startSketchOn(-XZ)
69,
module_id,
),
type_: Some(Node::new(Type::Primitive(PrimitiveType::String), 72, 78, module_id)),
labeled: true,
default_value: Some(DefaultParamVal::none()),
digest: None
}
Node::new(Type::Primitive(PrimitiveType::String), 71, 77, module_id),
)
]
}
);

View File

@ -51,7 +51,7 @@ pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
} else {
format!("found unknown tokens [{}]", token_list.join(", "))
};
return KclError::Lexical(KclErrorDetails { source_ranges, message }).into();
return KclError::Lexical(KclErrorDetails::new(message, source_ranges)).into();
}
// Important, to not call this before the unknown tokens check.

View File

@ -436,7 +436,7 @@ fn pipe_expression(i: &mut TokenSlice) -> PResult<Node<PipeExpression>> {
))
}
fn bool_value(i: &mut TokenSlice) -> PResult<BoxNode<Literal>> {
fn bool_value(i: &mut TokenSlice) -> PResult<Node<Literal>> {
let (value, token) = any
.try_map(|token: Token| match token.token_type {
TokenType::Keyword if token.value == "true" => Ok((true, token)),
@ -448,7 +448,7 @@ fn bool_value(i: &mut TokenSlice) -> PResult<BoxNode<Literal>> {
})
.context(expected("a boolean literal (either true or false)"))
.parse_next(i)?;
Ok(Box::new(Node::new(
Ok(Node::new(
Literal {
value: LiteralValue::Bool(value),
raw: value.to_string(),
@ -457,11 +457,11 @@ fn bool_value(i: &mut TokenSlice) -> PResult<BoxNode<Literal>> {
token.start,
token.end,
token.module_id,
)))
))
}
fn literal(i: &mut TokenSlice) -> PResult<BoxNode<Literal>> {
alt((string_literal, unsigned_number_literal))
alt((string_literal, unsigned_number_literal, bool_value))
.map(Box::new)
.context(expected("a KCL literal, like 'myPart' or 3"))
.parse_next(i)
@ -2051,7 +2051,7 @@ fn unnecessarily_bracketed(i: &mut TokenSlice) -> PResult<Expr> {
fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
alt((
member_expression.map(Box::new).map(Expr::MemberExpression),
bool_value.map(Expr::Literal),
bool_value.map(Box::new).map(Expr::Literal),
tag.map(Box::new).map(Expr::TagDeclarator),
literal.map(Expr::Literal),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
@ -2070,7 +2070,7 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> PResult<Expr> {
fn possible_operands(i: &mut TokenSlice) -> PResult<Expr> {
let mut expr = alt((
unary_expression.map(Box::new).map(Expr::UnaryExpression),
bool_value.map(Expr::Literal),
bool_value.map(Box::new).map(Expr::Literal),
member_expression.map(Box::new).map(Expr::MemberExpression),
literal.map(Expr::Literal),
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
@ -2780,27 +2780,31 @@ fn labeled_argument(i: &mut TokenSlice) -> PResult<LabeledArg> {
.parse_next(i)
}
fn record_ty_field(i: &mut TokenSlice) -> PResult<(Node<Identifier>, Node<Type>)> {
(identifier, colon, opt(whitespace), type_)
.map(|(id, _, _, ty)| (id, ty))
.parse_next(i)
}
/// Parse a type in various positions.
fn type_(i: &mut TokenSlice) -> PResult<Node<Type>> {
let type_ = alt((
// Object types
// TODO it is buggy to treat object fields like parameters since the parameters parser assumes a terminating `)`.
(open_brace, parameters, close_brace).try_map(|(open, params, close)| {
for p in &params {
if p.type_.is_none() {
return Err(CompilationError::fatal(
p.identifier.as_source_range(),
"Missing type for field in record type",
));
}
}
Ok(Node::new(
Type::Object { properties: params },
open.start,
close.end,
open.module_id,
))
}),
(
open_brace,
opt(whitespace),
separated(0.., record_ty_field, comma_sep),
opt(whitespace),
close_brace,
)
.try_map(|(open, _, params, _, close)| {
Ok(Node::new(
Type::Object { properties: params },
open.start,
close.end,
open.module_id,
))
}),
// Array types
array_type,
// Primitive or union types
@ -4866,6 +4870,15 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
|> line(%, tag = $var01)"#;
assert_no_err(some_program_string);
}
#[test]
fn test_parse_param_bool_default() {
let some_program_string = r#"fn patternTransform(
use_original?: boolean = false,
) {}"#;
assert_no_err(some_program_string);
}
#[test]
fn parse_function_types() {
let code = r#"foo = x: fn
@ -4875,6 +4888,8 @@ fn foo(x: fn(a, b: number(mm), c: d): number(Angle)): fn { return 0 }
type fn
type foo = fn
type foo = fn(a: string, b: { f: fn(): any })
type foo = fn(a: string, b: {})
type foo = fn(a: string, b: { })
type foo = fn([fn])
type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
"#;

View File

@ -578,10 +578,10 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
// This is an offset, not an index, and may point to
// the end of input (input.len()) on eof errors.
return KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange::new(offset, offset, module_id)],
message: "unexpected EOF while parsing".to_string(),
});
return KclError::Lexical(crate::errors::KclErrorDetails::new(
"unexpected EOF while parsing".to_owned(),
vec![SourceRange::new(offset, offset, module_id)],
));
}
// TODO: Add the Winnow tokenizer context to the error.
@ -589,9 +589,9 @@ impl From<ParseError<Input<'_>, winnow::error::ContextError>> for KclError {
let bad_token = &input[offset];
// TODO: Add the Winnow parser context to the error.
// See https://github.com/KittyCAD/modeling-app/issues/784
KclError::Lexical(crate::errors::KclErrorDetails {
source_ranges: vec![SourceRange::new(offset, offset + 1, module_id)],
message: format!("found unknown token '{}'", bad_token),
})
KclError::Lexical(crate::errors::KclErrorDetails::new(
format!("found unknown token '{}'", bad_token),
vec![SourceRange::new(offset, offset + 1, module_id)],
))
}
}

View File

@ -3188,7 +3188,6 @@ mod revolve_colinear {
/// Test that KCL is executed correctly.
#[tokio::test(flavor = "multi_thread")]
#[ignore] // until https://github.com/KittyCAD/engine/pull/3417 lands
async fn kcl_test_execute() {
super::execute(TEST_NAME, true).await
}

View File

@ -32,10 +32,10 @@ pub async fn appearance(exec_state: &mut ExecState, args: Args) -> Result<KclVal
// Make sure the color if set is valid.
if !HEX_REGEX.is_match(&color) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Invalid hex color (`{}`), try something like `#fff000`", color),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Invalid hex color (`{}`), try something like `#fff000`", color),
vec![args.source_range],
)));
}
let result = inner_appearance(
@ -282,10 +282,10 @@ async fn inner_appearance(
for solid_id in solids.ids(&args.ctx).await? {
// Set the material properties.
let rgb = rgba_simple::RGB::<f32>::from_hex(&color).map_err(|err| {
KclError::Semantic(KclErrorDetails {
message: format!("Invalid hex color (`{color}`): {err}"),
source_ranges: vec![args.source_range],
})
KclError::Semantic(KclErrorDetails::new(
format!("Invalid hex color (`{color}`): {err}"),
vec![args.source_range],
))
})?;
let color = Color {

View File

@ -9,6 +9,7 @@ use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema;
use serde::Serialize;
pub use crate::execution::fn_call::Args;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
@ -27,8 +28,6 @@ use crate::{
ModuleId,
};
pub use crate::execution::fn_call::Args;
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
@ -122,14 +121,14 @@ impl Args {
}
T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
KclError::Type(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!(
KclError::Type(KclErrorDetails::new(
format!(
"The arg {label} was given, but it was the wrong type. It should be type {} but it was {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type(),
),
})
vec![self.source_range],
))
})
}
@ -155,10 +154,10 @@ impl Args {
T: FromKclValue<'a>,
{
self.get_kw_arg_opt(label)?.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
})
KclError::Semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
))
})
}
@ -172,10 +171,10 @@ impl Args {
T: for<'a> FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
)));
};
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
@ -206,10 +205,7 @@ impl Args {
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
}
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges()))
})?;
// TODO unnecessary cloning
@ -223,21 +219,21 @@ impl Args {
T: FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a keyword argument '{label}'"),
});
let err = KclError::Semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
));
return Err(err);
};
let Some(array) = arg.value.as_array() else {
let err = KclError::Semantic(KclErrorDetails {
source_ranges: vec![arg.source_range],
message: format!(
let err = KclError::Semantic(KclErrorDetails::new(
format!(
"Expected an array of {} but found {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type()
),
});
vec![arg.source_range],
));
return Err(err);
};
array
@ -245,14 +241,14 @@ impl Args {
.map(|item| {
let source = SourceRange::from(item);
let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Expected a {} but found {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type()
),
})
arg.source_ranges(),
))
})?;
Ok((val, source))
})
@ -267,19 +263,19 @@ impl Args {
{
let arg = self
.unlabeled_kw_arg_unconverted()
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
}))?;
.ok_or(KclError::Semantic(KclErrorDetails::new(
format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
vec![self.source_range],
)))?;
T::from_kcl_val(&arg.value).ok_or_else(|| {
let expected_type_name = tynm::type_name::<T>();
let actual_type_name = arg.value.human_friendly_type();
let message = format!("This function expected the input argument to be of type {expected_type_name} but it's actually of type {actual_type_name}");
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
KclError::Semantic(KclErrorDetails::new(
message,
})
arg.source_ranges(),
))
})
}
@ -296,10 +292,10 @@ impl Args {
{
let arg = self
.unlabeled_kw_arg_unconverted()
.ok_or(KclError::Semantic(KclErrorDetails {
source_ranges: vec![self.source_range],
message: format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
}))?;
.ok_or(KclError::Semantic(KclErrorDetails::new(
format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
vec![self.source_range],
)))?;
let arg = arg.value.coerce(ty, exec_state).map_err(|_| {
let actual_type = arg.value.principal_type();
@ -330,17 +326,14 @@ impl Args {
if message.contains("one or more Solids or imported geometry but it's actually of type Sketch") {
message = format!("{message}. {ERROR_STRING_SKETCH_TO_SOLID_HELPER}");
}
KclError::Semantic(KclErrorDetails {
source_ranges: arg.source_ranges(),
message,
})
KclError::Semantic(KclErrorDetails::new(message, arg.source_ranges()))
})?;
T::from_kcl_val(&arg).ok_or_else(|| {
KclError::Internal(KclErrorDetails {
source_ranges: vec![self.source_range],
message: "Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(),
})
KclError::Internal(KclErrorDetails::new(
"Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(),
vec![self.source_range],
))
})
}
@ -383,17 +376,17 @@ impl Args {
exec_state.stack().get_from_call_stack(&tag.value, self.source_range)?
{
let info = t.get_info(epoch).ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have engine info", tag.value),
source_ranges: vec![self.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Tag `{}` does not have engine info", tag.value),
vec![self.source_range],
))
})?;
Ok(info)
} else {
Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not exist", tag.value),
source_ranges: vec![self.source_range],
}))
Err(KclError::Type(KclErrorDetails::new(
format!("Tag `{}` does not exist", tag.value),
vec![self.source_range],
)))
}
}
@ -522,19 +515,19 @@ impl Args {
must_be_planar: bool,
) -> Result<uuid::Uuid, KclError> {
if tag.value.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty tag for the face".to_string(),
source_ranges: vec![self.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Expected a non-empty tag for the face".to_string(),
vec![self.source_range],
)));
}
let engine_info = self.get_tag_engine_info_check_surface(exec_state, tag)?;
let surface = engine_info.surface.as_ref().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Tag `{}` does not have a surface", tag.value),
source_ranges: vec![self.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Tag `{}` does not have a surface", tag.value),
vec![self.source_range],
))
})?;
if let Some(face_from_surface) = match surface {
@ -550,10 +543,10 @@ impl Args {
}
}
// The must be planar check must be called before the arc check.
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` is a non-planar surface", tag.value),
source_ranges: vec![self.source_range],
}))),
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new(
format!("Tag `{}` is a non-planar surface", tag.value),
vec![self.source_range],
)))),
ExtrudeSurface::ExtrudeArc(extrude_arc) => {
if let Some(arc_tag) = &extrude_arc.tag {
if arc_tag.name == tag.value {
@ -577,10 +570,10 @@ impl Args {
}
}
// The must be planar check must be called before the fillet check.
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails {
message: format!("Tag `{}` is a non-planar surface", tag.value),
source_ranges: vec![self.source_range],
}))),
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::Type(KclErrorDetails::new(
format!("Tag `{}` is a non-planar surface", tag.value),
vec![self.source_range],
)))),
ExtrudeSurface::Fillet(fillet) => {
if let Some(fillet_tag) = &fillet.tag {
if fillet_tag.name == tag.value {
@ -597,10 +590,10 @@ impl Args {
}
// If we still haven't found the face, return an error.
Err(KclError::Type(KclErrorDetails {
message: format!("Expected a face with the tag `{}`", tag.value),
source_ranges: vec![self.source_range],
}))
Err(KclError::Type(KclErrorDetails::new(
format!("Expected a face with the tag `{}`", tag.value),
vec![self.source_range],
)))
}
}
@ -622,20 +615,20 @@ where
{
fn from_args(args: &'a Args, i: usize) -> Result<Self, KclError> {
let Some(arg) = args.args.get(i) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected an argument at index {i}"),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected an argument at index {i}"),
vec![args.source_range],
)));
};
let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Argument at index {i} was supposed to be type {} but found {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type(),
),
source_ranges: arg.source_ranges(),
}));
arg.source_ranges(),
)));
};
Ok(val)
}
@ -651,14 +644,14 @@ where
return Ok(None);
}
let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Argument at index {i} was supposed to be type Option<{}> but found {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type()
),
source_ranges: arg.source_ranges(),
}));
arg.source_ranges(),
)));
};
Ok(Some(val))
}

View File

@ -58,10 +58,10 @@ async fn call_map_closure(
let output = map_fn.call_kw(None, exec_state, ctxt, args, source_range).await?;
let source_ranges = vec![source_range];
let output = output.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Map function must return a value".to_string(),
KclError::Semantic(KclErrorDetails::new(
"Map function must return a value".to_owned(),
source_ranges,
})
))
})?;
Ok(output)
}
@ -118,10 +118,10 @@ async fn call_reduce_closure(
// Unpack the returned transform object.
let source_ranges = vec![source_range];
let out = transform_fn_return.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Reducer function must return a value".to_string(),
source_ranges: source_ranges.clone(),
})
KclError::Semantic(KclErrorDetails::new(
"Reducer function must return a value".to_string(),
source_ranges.clone(),
))
})?;
Ok(out)
}
@ -133,10 +133,10 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let KclValue::HomArray { value: values, ty } = array else {
let meta = vec![args.source_range];
let actual_type = array.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't push to a value of type {actual_type}, only an array"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("You can't push to a value of type {actual_type}, only an array"),
meta,
)));
};
let ty = if item.has_type(&ty) {
ty
@ -161,10 +161,10 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let KclValue::HomArray { value: values, ty } = array else {
let meta = vec![args.source_range];
let actual_type = array.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't pop from a value of type {actual_type}, only an array"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("You can't pop from a value of type {actual_type}, only an array"),
meta,
)));
};
let new_array = inner_pop(values, &args)?;
@ -173,10 +173,10 @@ pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
if array.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Cannot pop from an empty array".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Cannot pop from an empty array".to_string(),
vec![args.source_range],
)));
}
// Create a new array with all elements except the last one

View File

@ -12,10 +12,10 @@ use crate::{
async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> {
if !value {
return Err(KclError::Type(KclErrorDetails {
message: format!("assert failed: {}", message),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
format!("assert failed: {}", message),
vec![args.source_range],
)));
}
Ok(())
}
@ -111,19 +111,18 @@ async fn inner_assert(
.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],
}));
return Err(KclError::Type(KclErrorDetails::new(
"You must provide at least one condition in this assert (for example, isEqualTo)".to_owned(),
vec![args.source_range],
)));
}
if tolerance.is_some() && is_equal_to.is_none() {
return Err(KclError::Type(KclErrorDetails {
message:
"The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
.to_owned(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"The `tolerance` arg is only used with `isEqualTo`. Either remove `tolerance` or add an `isEqualTo` arg."
.to_owned(),
vec![args.source_range],
)));
}
let suffix = if let Some(err_string) = error {

View File

@ -41,10 +41,10 @@ async fn inner_chamfer(
// If you try and tag multiple edges with a tagged chamfer, we want to return an
// error to the user that they can only tag one edge at a time.
if tag.is_some() && tags.len() > 1 {
return Err(KclError::Type(KclErrorDetails {
message: "You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"You can only tag one edge at a time with a tagged chamfer. Either delete the tag for the chamfer fn if you don't need it OR separate into individual chamfer functions for each tag.".to_string(),
vec![args.source_range],
)));
}
let mut solid = solid.clone();

View File

@ -84,10 +84,10 @@ async fn inner_clone(
fix_tags_and_references(&mut new_geometry, old_id, exec_state, &args)
.await
.map_err(|e| {
KclError::Internal(KclErrorDetails {
message: format!("failed to fix tags and references: {:?}", e),
source_ranges: vec![args.source_range],
})
KclError::Internal(KclErrorDetails::new(
format!("failed to fix tags and references: {:?}", e),
vec![args.source_range],
))
})?;
Ok(new_geometry)

View File

@ -24,10 +24,10 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "At least two solids are required for a union operation.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::UndefinedValue(KclErrorDetails::new(
"At least two solids are required for a union operation.".to_string(),
vec![args.source_range],
)));
}
let solids = inner_union(solids, tolerance, exec_state, args).await?;
@ -147,10 +147,10 @@ pub(crate) async fn inner_union(
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the union operation.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Internal(KclErrorDetails::new(
"Failed to get the result of the union operation.".to_string(),
vec![args.source_range],
)));
};
// If we have more solids, set those as well.
@ -169,10 +169,10 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
if solids.len() < 2 {
return Err(KclError::UndefinedValue(KclErrorDetails {
message: "At least two solids are required for an intersect operation.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::UndefinedValue(KclErrorDetails::new(
"At least two solids are required for an intersect operation.".to_string(),
vec![args.source_range],
)));
}
let solids = inner_intersect(solids, tolerance, exec_state, args).await?;
@ -273,10 +273,10 @@ pub(crate) async fn inner_intersect(
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the intersection operation.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Internal(KclErrorDetails::new(
"Failed to get the result of the intersection operation.".to_string(),
vec![args.source_range],
)));
};
// If we have more solids, set those as well.
@ -397,10 +397,10 @@ pub(crate) async fn inner_subtract(
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Failed to get the result of the subtract operation.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Internal(KclErrorDetails::new(
"Failed to get the result of the subtract operation.".to_string(),
vec![args.source_range],
)));
};
// If we have more solids, set those as well.

View File

@ -87,10 +87,10 @@ async fn inner_get_opposite_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
vec![args.source_range],
)));
};
Ok(opposite_edge.edge)
@ -172,20 +172,20 @@ async fn inner_get_next_adjacent_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!(
return Err(KclError::Engine(KclErrorDetails::new(
format!(
"mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
resp
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
};
adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("No edge found next adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("No edge found next adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
})
}
@ -264,20 +264,20 @@ async fn inner_get_previous_adjacent_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!(
return Err(KclError::Engine(KclErrorDetails::new(
format!(
"mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
resp
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
};
adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("No edge found previous adjacent to tag: `{}`", edge.value),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("No edge found previous adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
})
}
@ -336,10 +336,10 @@ async fn inner_get_common_edge(
}
if faces.len() != 2 {
return Err(KclError::Type(KclErrorDetails {
message: "getCommonEdge requires exactly two tags for faces".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"getCommonEdge requires exactly two tags for faces".to_string(),
vec![args.source_range],
)));
}
let first_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[0], false).await?;
let second_face_id = args.get_adjacent_face_to_tag(exec_state, &faces[1], false).await?;
@ -348,10 +348,10 @@ async fn inner_get_common_edge(
let second_tagged_path = args.get_tag_engine_info(exec_state, &faces[1])?;
if first_tagged_path.sketch != second_tagged_path.sketch {
return Err(KclError::Type(KclErrorDetails {
message: "getCommonEdge requires the faces to be in the same original sketch".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"getCommonEdge requires the faces to be in the same original sketch".to_string(),
vec![args.source_range],
)));
}
// Flush the batch for our fillets/chamfers if there are any.
@ -377,19 +377,19 @@ async fn inner_get_common_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetCommonEdge response was not as expected: {:?}", resp),
vec![args.source_range],
)));
};
common_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!(
KclError::Type(KclErrorDetails::new(
format!(
"No common edge was found between `{}` and `{}`",
faces[0].value, faces[1].value
),
source_ranges: vec![args.source_range],
})
vec![args.source_range],
))
})
}

View File

@ -175,11 +175,11 @@ async fn inner_extrude(
let mut solids = Vec::new();
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
return Err(KclError::Semantic(KclErrorDetails::new(
"You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
.to_owned(),
}));
vec![args.source_range],
)));
}
let bidirection = bidirectional_length.map(|l| LengthUnit(l.to_mm()));
@ -262,10 +262,10 @@ pub(crate) async fn do_post_extrude<'a>(
// The "get extrusion face info" API call requires *any* edge on the sketch being extruded.
// So, let's just use the first one.
let Some(any_edge_id) = sketch.paths.first().map(|edge| edge.get_base().geo_meta.id) else {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a non-empty sketch".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Expected a non-empty sketch".to_owned(),
vec![args.source_range],
)));
};
any_edge_id
};
@ -387,13 +387,13 @@ pub(crate) async fn do_post_extrude<'a>(
// Add the tags for the start or end caps.
if let Some(tag_start) = named_cap_tags.start {
let Some(start_cap_id) = start_cap_id else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
return Err(KclError::Type(KclErrorDetails::new(
format!(
"Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
tag_start.name, sketch.id
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
};
new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {
@ -407,13 +407,13 @@ pub(crate) async fn do_post_extrude<'a>(
}
if let Some(tag_end) = named_cap_tags.end {
let Some(end_cap_id) = end_cap_id else {
return Err(KclError::Type(KclErrorDetails {
message: format!(
return Err(KclError::Type(KclErrorDetails::new(
format!(
"Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
tag_end.name, sketch.id
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
};
new_value.push(ExtrudeSurface::ExtrudePlane(crate::execution::ExtrudePlane {

View File

@ -49,10 +49,11 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]
}
}
if !duplicate_tags_source.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge".to_string(),
source_ranges: duplicate_tags_source,
}));
return Err(KclError::Type(KclErrorDetails::new(
"The same edge ID is being referenced multiple times, which is not allowed. Please select a different edge"
.to_string(),
duplicate_tags_source,
)));
}
Ok(())
}

View File

@ -6,7 +6,7 @@ use kittycad_modeling_cmds::{self as kcmc, shared::Point3d};
use super::args::TyF64;
use crate::{
errors::KclError,
errors::{KclError, KclErrorDetails},
execution::{
types::{PrimitiveType, RuntimeType},
ExecState, Helix as HelixValue, KclValue, Solid,
@ -33,50 +33,50 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// Make sure we have a radius if we don't have a cylinder.
if radius.is_none() && cylinder.is_none() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is required when creating a helix without a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
}
// Make sure we don't have a radius if we have a cylinder.
if radius.is_some() && cylinder.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is not allowed when creating a helix with a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Radius is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
}
// Make sure we have an axis if we don't have a cylinder.
if axis.is_none() && cylinder.is_none() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is required when creating a helix without a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
}
// Make sure we don't have an axis if we have a cylinder.
if axis.is_some() && cylinder.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is not allowed when creating a helix with a cylinder.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Axis is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
}
// Make sure we have a radius if we have an axis.
if radius.is_none() && axis.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Radius is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
}
// Make sure we have an axis if we have a radius.
if axis.is_none() && radius.is_some() {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Axis is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
}
let value = inner_helix(
@ -140,10 +140,10 @@ async fn inner_helix(
Axis3dOrEdgeReference::Axis { direction, origin } => {
// Make sure they gave us a length.
let Some(length) = length else {
return Err(KclError::Semantic(crate::errors::KclErrorDetails {
message: "Length is required when creating a helix around an axis.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Length is required when creating a helix around an axis.".to_owned(),
vec![args.source_range],
)));
};
args.batch_modeling_cmd(

View File

@ -148,13 +148,13 @@ async fn inner_loft(
) -> Result<Box<Solid>, KclError> {
// Make sure we have at least two sketches.
if sketches.len() < 2 {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Loft requires at least two sketches, but only {} were provided.",
sketches.len()
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
}
let id = exec_state.next_uuid();

View File

@ -56,13 +56,13 @@ pub async fn sqrt(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
let input: TyF64 = args.get_unlabeled_kw_arg_typed("input", &RuntimeType::num_any(), exec_state)?;
if input.n < 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Attempt to take square root (`sqrt`) of a number less than zero ({})",
input.n
),
}));
vec![args.source_range],
)));
}
let result = input.n.sqrt();

View File

@ -101,10 +101,10 @@ async fn inner_mirror_2d(
OkModelingCmdResponse::EntityGetAllChildUuids(EntityGetAllChildUuids { entity_ids: child_ids }),
} = response
else {
return Err(KclError::Internal(KclErrorDetails {
message: "Expected a successful response from EntityGetAllChildUuids".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Internal(KclErrorDetails::new(
"Expected a successful response from EntityGetAllChildUuids".to_string(),
vec![args.source_range],
)));
};
if child_ids.len() >= 2 {
@ -112,10 +112,10 @@ async fn inner_mirror_2d(
let child_id = child_ids[1];
sketch.mirror = Some(child_id);
} else {
return Err(KclError::Type(KclErrorDetails {
message: "Expected child uuids to be >= 2".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Expected child uuids to be >= 2".to_string(),
vec![args.source_range],
)));
}
}

View File

@ -80,8 +80,6 @@ lazy_static! {
Box::new(crate::std::patterns::PatternLinear3D),
Box::new(crate::std::patterns::PatternCircular2D),
Box::new(crate::std::patterns::PatternCircular3D),
Box::new(crate::std::patterns::PatternTransform),
Box::new(crate::std::patterns::PatternTransform2D),
Box::new(crate::std::edge::GetOppositeEdge),
Box::new(crate::std::edge::GetNextAdjacentEdge),
Box::new(crate::std::edge::GetPreviousAdjacentEdge),
@ -280,6 +278,14 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|e, a| Box::pin(crate::std::clone::clone(e, a)),
StdFnProps::default("std::clone").include_in_feature_tree(),
),
("solid", "patternTransform") => (
|e, a| Box::pin(crate::std::patterns::pattern_transform(e, a)),
StdFnProps::default("std::solid::patternTransform").include_in_feature_tree(),
),
("sketch", "patternTransform2d") => (
|e, a| Box::pin(crate::std::patterns::pattern_transform_2d(e, a)),
StdFnProps::default("std::sketch::patternTransform2d"),
),
_ => unreachable!(),
}
}

View File

@ -15,6 +15,7 @@ use kittycad_modeling_cmds::{
use serde::Serialize;
use uuid::Uuid;
use super::axis_or_reference::Axis3dOrPoint3d;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
@ -31,8 +32,6 @@ use crate::{
ExecutorContext, SourceRange,
};
use super::axis_or_reference::Axis3dOrPoint3d;
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
/// Repeat some 3D solid, changing each repetition slightly.
@ -57,202 +56,6 @@ pub async fn pattern_transform_2d(exec_state: &mut ExecState, args: Args) -> Res
Ok(sketches.into())
}
/// Repeat a 3-dimensional solid, changing it each time.
///
/// Replicates the 3D solid, applying a transformation function to each replica.
/// Transformation function could alter rotation, scale, visibility, position, etc.
///
/// The `patternTransform` call itself takes a number for how many total instances of
/// the shape should be. For example, if you use a circle with `patternTransform(instances = 4, transform = f)`
/// then there will be 4 circles: the original, and 3 created by replicating the original and
/// calling the transform function on each.
///
/// The transform function takes a single parameter: an integer representing which
/// number replication the transform is for. E.g. the first replica to be transformed
/// will be passed the argument `1`. This simplifies your math: the transform function can
/// rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
///
/// The transform function returns a transform object. All properties of the object are optional,
/// they each default to "no change". So the overall transform object defaults to "no change" too.
/// Its properties are:
///
/// - `translate` (3D point)
///
/// Translates the replica, moving its position in space.
///
/// - `replicate` (bool)
///
/// If false, this ID will not actually copy the object. It'll be skipped.
///
/// - `scale` (3D point)
///
/// Stretches the object, multiplying its width in the given dimension by the point's component in
/// that direction.
///
/// - `rotation` (object, with the following properties)
///
/// - `rotation.axis` (a 3D point, defaults to the Z axis)
///
/// - `rotation.angle` (number of degrees)
///
/// - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
///
/// ```no_run
/// // Each instance will be shifted along the X axis.
/// fn transform(@id) {
/// return { translate = [4 * id, 0, 0] }
/// }
///
/// // Sketch 4 cylinders.
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 5)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// // Each instance will be shifted along the X axis,
/// // with a gap between the original (at x = 0) and the first replica
/// // (at x = 8). This is because `id` starts at 1.
/// fn transform(@id) {
/// return { translate = [4 * (1+id), 0, 0] }
/// }
///
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 5)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// fn cube(length, center) {
/// l = length/2
/// x = center[0]
/// y = center[1]
/// p0 = [-l + x, -l + y]
/// p1 = [-l + x, l + y]
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchOn(XY)
/// |> startProfile(at = p0)
/// |> line(endAbsolute = p1)
/// |> line(endAbsolute = p2)
/// |> line(endAbsolute = p3)
/// |> line(endAbsolute = p0)
/// |> close()
/// |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
/// return {
/// // Move down each time.
/// translate = [0, 0, -i * width],
/// // Make the cube longer, wider and flatter each time.
/// scale = [pow(1.1, exp = i), pow(1.1, exp = i), pow(0.9, exp = i)],
/// // Turn by 15 degrees each time.
/// rotation = {
/// angle = 15 * i,
/// origin = "local",
/// }
/// }
/// }
///
/// myCubes =
/// cube(length = width, center = [100,0])
/// |> patternTransform(instances = 25, transform = transform)
/// ```
///
/// ```no_run
/// fn cube(length, center) {
/// l = length/2
/// x = center[0]
/// y = center[1]
/// p0 = [-l + x, -l + y]
/// p1 = [-l + x, l + y]
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchOn(XY)
/// |> startProfile(at = p0)
/// |> line(endAbsolute = p1)
/// |> line(endAbsolute = p2)
/// |> line(endAbsolute = p3)
/// |> line(endAbsolute = p0)
/// |> close()
/// |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
/// return {
/// translate = [0, 0, -i * width],
/// rotation = {
/// angle = 90 * i,
/// // Rotate around the overall scene's origin.
/// origin = "global",
/// }
/// }
/// }
/// myCubes =
/// cube(length = width, center = [100,100])
/// |> patternTransform(instances = 4, transform = transform)
/// ```
/// ```no_run
/// // Parameters
/// r = 50 // base radius
/// h = 10 // layer height
/// t = 0.005 // taper factor [0-1)
/// // Defines how to modify each layer of the vase.
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
/// fn transform(@replicaId) {
/// scale = r * abs(1 - (t * replicaId)) * (5 + cos((replicaId / 8): number(rad)))
/// return {
/// translate = [0, 0, replicaId * 10],
/// scale = [scale, scale, 0],
/// }
/// }
/// // Each layer is just a pretty thin cylinder.
/// fn layer() {
/// return startSketchOn(XY) // or some other plane idk
/// |> circle(center = [0, 0], radius = 1, tag = $tag1)
/// |> extrude(length = h)
/// }
/// // The vase is 100 layers tall.
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
/// vase = layer() |> patternTransform(instances = 100, transform = transform)
/// ```
/// ```
/// fn transform(@i) {
/// // Transform functions can return multiple transforms. They'll be applied in order.
/// return [
/// { translate = [30 * i, 0, 0] },
/// { rotation = { angle = 45 * i } },
/// ]
/// }
/// startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> polygon(
/// radius = 10,
/// numSides = 4,
/// center = [0, 0],
/// inscribed = false,
/// )
/// |> extrude(length = 4)
/// |> patternTransform(instances = 3, transform = transform)
/// ```
#[stdlib {
name = "patternTransform",
feature_tree_operation = true,
keywords = true,
unlabeled_first = true,
args = {
solids = { docs = "The solid(s) to duplicate" },
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." },
transform = { docs = "How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples." },
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." },
},
tags = ["solid"]
}]
async fn inner_pattern_transform<'a>(
solids: Vec<Solid>,
instances: u32,
@ -264,10 +67,10 @@ async fn inner_pattern_transform<'a>(
// Build the vec of transforms, one for each repetition.
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
if instances < 1 {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: MUST_HAVE_ONE_INSTANCE.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
}
for i in 1..instances {
let t = make_transform::<Solid>(i, transform, args.source_range, exec_state, &args.ctx).await?;
@ -283,30 +86,6 @@ async fn inner_pattern_transform<'a>(
.await
}
/// Just like patternTransform, but works on 2D sketches not 3D solids.
/// ```no_run
/// // Each instance will be shifted along the X axis.
/// fn transform(@id) {
/// return { translate = [4 * id, 0] }
/// }
///
/// // Sketch 4 circles.
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius= 2)
/// |> patternTransform2d(instances = 4, transform = transform)
/// ```
#[stdlib {
name = "patternTransform2d",
keywords = true,
unlabeled_first = true,
args = {
sketches = { docs = "The sketch(es) to duplicate" },
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect." },
transform = { docs = "How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples." },
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false." },
},
tags = ["sketch"]
}]
async fn inner_pattern_transform_2d<'a>(
sketches: Vec<Sketch>,
instances: u32,
@ -318,10 +97,10 @@ async fn inner_pattern_transform_2d<'a>(
// Build the vec of transforms, one for each repetition.
let mut transform_vec = Vec::with_capacity(usize::try_from(instances).unwrap());
if instances < 1 {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: MUST_HAVE_ONE_INSTANCE.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
}
for i in 1..instances {
let t = make_transform::<Sketch>(i, transform, args.source_range, exec_state, &args.ctx).await?;
@ -398,10 +177,10 @@ async fn send_pattern_transform<T: GeometryTrait>(
}
&mock_ids
} else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("EntityLinearPattern response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
format!("EntityLinearPattern response was not as expected: {:?}", resp),
vec![args.source_range],
)));
};
let mut geometries = vec![solid.clone()];
@ -444,10 +223,10 @@ async fn make_transform<T: GeometryTrait>(
// Unpack the returned transform object.
let source_ranges = vec![source_range];
let transform_fn_return = transform_fn_return.ok_or_else(|| {
KclError::Semantic(KclErrorDetails {
message: "Transform function must return a value".to_string(),
source_ranges: source_ranges.clone(),
})
KclError::Semantic(KclErrorDetails::new(
"Transform function must return a value".to_string(),
source_ranges.clone(),
))
})?;
let transforms = match transform_fn_return {
KclValue::Object { value, meta: _ } => vec![value],
@ -455,19 +234,19 @@ async fn make_transform<T: GeometryTrait>(
let transforms: Vec<_> = value
.into_iter()
.map(|val| {
val.into_object().ok_or(KclError::Semantic(KclErrorDetails {
message: "Transform function must return a transform object".to_string(),
source_ranges: source_ranges.clone(),
}))
val.into_object().ok_or(KclError::Semantic(KclErrorDetails::new(
"Transform function must return a transform object".to_string(),
source_ranges.clone(),
)))
})
.collect::<Result<_, _>>()?;
transforms
}
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: "Transform function must return a transform object".to_string(),
source_ranges: source_ranges.clone(),
}))
return Err(KclError::Semantic(KclErrorDetails::new(
"Transform function must return a transform object".to_string(),
source_ranges.clone(),
)))
}
};
@ -487,10 +266,10 @@ fn transform_from_obj_fields<T: GeometryTrait>(
Some(KclValue::Bool { value: true, .. }) => true,
Some(KclValue::Bool { value: false, .. }) => false,
Some(_) => {
return Err(KclError::Semantic(KclErrorDetails {
message: "The 'replicate' key must be a bool".to_string(),
source_ranges: source_ranges.clone(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"The 'replicate' key must be a bool".to_string(),
source_ranges.clone(),
)));
}
None => true,
};
@ -519,11 +298,10 @@ fn transform_from_obj_fields<T: GeometryTrait>(
let mut rotation = Rotation::default();
if let Some(rot) = transform.get("rotation") {
let KclValue::Object { value: rot, meta: _ } = rot else {
return Err(KclError::Semantic(KclErrorDetails {
message: "The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')"
.to_string(),
source_ranges: source_ranges.clone(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')".to_owned(),
source_ranges.clone(),
)));
};
if let Some(axis) = rot.get("axis") {
rotation.axis = point_3d_to_mm(T::array_to_point3d(axis, source_ranges.clone(), exec_state)?).into();
@ -534,10 +312,10 @@ fn transform_from_obj_fields<T: GeometryTrait>(
rotation.angle = Angle::from_degrees(*number);
}
_ => {
return Err(KclError::Semantic(KclErrorDetails {
message: "The 'rotation.angle' key must be a number (of degrees)".to_string(),
source_ranges: source_ranges.clone(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"The 'rotation.angle' key must be a number (of degrees)".to_owned(),
source_ranges.clone(),
)));
}
}
}
@ -568,15 +346,15 @@ fn array_to_point3d(
) -> Result<[TyF64; 3], KclError> {
val.coerce(&RuntimeType::point3d(), exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Expected an array of 3 numbers (i.e., a 3D point), found {}",
e.found
.map(|t| t.human_friendly_type())
.unwrap_or_else(|| val.human_friendly_type().to_owned())
),
source_ranges,
})
))
})
.map(|val| val.as_point3d().unwrap())
}
@ -588,15 +366,15 @@ fn array_to_point2d(
) -> Result<[TyF64; 2], KclError> {
val.coerce(&RuntimeType::point2d(), exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails {
message: format!(
KclError::Semantic(KclErrorDetails::new(
format!(
"Expected an array of 2 numbers (i.e., a 2D point), found {}",
e.found
.map(|t| t.human_friendly_type())
.unwrap_or_else(|| val.human_friendly_type().to_owned())
),
source_ranges,
})
))
})
.map(|val| val.as_point2d().unwrap())
}
@ -757,12 +535,11 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
let axis = axis.to_point2d();
if axis[0].n == 0.0 && axis[1].n == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
.to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
.to_owned(),
vec![args.source_range],
)));
}
let sketches = inner_pattern_linear_2d(sketches, instances, distance, axis, use_original, exec_state, args).await?;
@ -860,12 +637,11 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
let axis = axis.to_point3d();
if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message:
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
.to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"The axis of the linear pattern cannot be the zero vector. Otherwise they will just duplicate in place."
.to_owned(),
vec![args.source_range],
)));
}
let solids = inner_pattern_linear_3d(solids, instances, distance, axis, use_original, exec_state, args).await?;
@ -1210,10 +986,10 @@ async fn inner_pattern_circular_2d(
.await?;
let Geometries::Sketches(new_sketches) = geometries else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a vec of sketches".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected a vec of sketches".to_string(),
vec![args.source_range],
)));
};
sketches.extend(new_sketches);
@ -1231,7 +1007,16 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
// If instances is 1, this has no effect.
let instances: u32 = args.get_kw_arg_typed("instances", &RuntimeType::count(), exec_state)?;
// The axis around which to make the pattern. This is a 3D vector.
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
let axis: Axis3dOrPoint3d = args.get_kw_arg_typed(
"axis",
&RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::Axis3d),
RuntimeType::point3d(),
]),
exec_state,
)?;
let axis = axis.to_point3d();
// The center about which to make the pattern. This is a 3D vector.
let center: [TyF64; 3] = args.get_kw_arg_typed("center", &RuntimeType::point3d(), exec_state)?;
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
@ -1263,6 +1048,24 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
/// solid with respect to the center of the circle is maintained.
///
/// ```no_run
/// /// Pattern using a named axis.
///
/// exampleSketch = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 1)
///
/// example = extrude(exampleSketch, length = -5)
/// |> patternCircular3d(
/// axis = X,
/// center = [10, -20, 0],
/// instances = 11,
/// arcDegrees = 360,
/// rotateDuplicates = true
/// )
/// ```
///
/// ```no_run
/// /// Pattern using a raw axis.
///
/// exampleSketch = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 1)
///
@ -1333,10 +1136,10 @@ async fn inner_pattern_circular_3d(
.await?;
let Geometries::Solids(new_solids) = geometries else {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected a vec of solids".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected a vec of solids".to_string(),
vec![args.source_range],
)));
};
solids.extend(new_solids);
@ -1358,10 +1161,10 @@ async fn pattern_circular(
return Ok(Geometries::from(geometry));
}
RepetitionsNeeded::Invalid => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: MUST_HAVE_ONE_INSTANCE.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
}
};
@ -1403,10 +1206,10 @@ async fn pattern_circular(
}
&mock_ids
} else {
return Err(KclError::Engine(KclErrorDetails {
message: format!("EntityCircularPattern response was not as expected: {:?}", resp),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Engine(KclErrorDetails::new(
format!("EntityCircularPattern response was not as expected: {:?}", resp),
vec![args.source_range],
)));
};
let geometries = match geometry {

View File

@ -75,10 +75,10 @@ async fn inner_revolve(
// We don't use validate() here because we want to return a specific error message that is
// nice and we use the other data in the docs, so we still need use the derive above for the json schema.
if !(-360.0..=360.0).contains(&angle) || angle == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle),
vec![args.source_range],
)));
}
}
@ -87,35 +87,35 @@ async fn inner_revolve(
// We don't use validate() here because we want to return a specific error message that is
// nice and we use the other data in the docs, so we still need use the derive above for the json schema.
if !(-360.0..=360.0).contains(&bidirectional_angle) || bidirectional_angle == 0.0 {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Expected bidirectional angle to be between -360 and 360 and not 0, found `{}`",
bidirectional_angle
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
}
if let Some(angle) = angle {
let ang = angle.signum() * bidirectional_angle + angle;
if !(-360.0..=360.0).contains(&ang) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!(
return Err(KclError::Semantic(KclErrorDetails::new(
format!(
"Combined angle and bidirectional must be between -360 and 360, found '{}'",
ang
),
source_ranges: vec![args.source_range],
}));
vec![args.source_range],
)));
}
}
}
if symmetric.unwrap_or(false) && bidirectional_angle.is_some() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
return Err(KclError::Semantic(KclErrorDetails::new(
"You cannot give both `symmetric` and `bidirectional` params, you have to choose one or the other"
.to_owned(),
}));
vec![args.source_range],
)));
}
let angle = Angle::from_degrees(angle.unwrap_or(360.0));

View File

@ -59,10 +59,10 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
let (p, ty) = path.end_point_components();
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
@ -104,10 +104,10 @@ pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<Kcl
fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
Ok(TyF64::new(path.get_base().to[0], path.get_base().units.into()))
@ -147,10 +147,10 @@ pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<Kcl
fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
Ok(path.get_to()[1].clone())
@ -201,10 +201,10 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
let (p, ty) = path.start_point_components();
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
@ -246,10 +246,10 @@ pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<K
fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
Ok(path.get_from()[0].clone())
@ -289,10 +289,10 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<K
fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
Ok(path.get_from()[1].clone())
@ -334,10 +334,10 @@ fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
vec![args.source_range],
))
})?
.get_base();
@ -381,10 +381,10 @@ fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
vec![args.source_range],
))
})?
.get_base();
@ -429,10 +429,10 @@ pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<Kc
fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
Ok(path.length())
@ -473,10 +473,10 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
let result = between(path.get_base().from, path.get_base().to);
@ -575,10 +575,10 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<Kc
async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
let line = args.get_tag_engine_info(exec_state, tag)?;
let path = line.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected a line segment with a path, found `{:?}`", line),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
})?;
let from = untype_point(path.get_to()).0;

View File

@ -325,17 +325,17 @@ async fn inner_polygon(
args: Args,
) -> Result<Sketch, KclError> {
if num_sides < 3 {
return Err(KclError::Type(KclErrorDetails {
message: "Polygon must have at least 3 sides".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Polygon must have at least 3 sides".to_string(),
vec![args.source_range],
)));
}
if radius.n <= 0.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Radius must be greater than 0".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Radius must be greater than 0".to_string(),
vec![args.source_range],
)));
}
let (sketch_surface, units) = match sketch_surface_or_group {

View File

@ -36,17 +36,17 @@ async fn inner_shell(
args: Args,
) -> Result<Vec<Solid>, KclError> {
if faces.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "You must shell at least one face".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"You must shell at least one face".to_owned(),
vec![args.source_range],
)));
}
if solids.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "You must shell at least one solid".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"You must shell at least one solid".to_owned(),
vec![args.source_range],
)));
}
let mut face_ids = Vec::new();
@ -63,20 +63,19 @@ async fn inner_shell(
}
if face_ids.is_empty() {
return Err(KclError::Type(KclErrorDetails {
message: "Expected at least one valid face".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Expected at least one valid face".to_owned(),
vec![args.source_range],
)));
}
// Make sure all the solids have the same id, as we are going to shell them all at
// once.
if !solids.iter().all(|eg| eg.id == solids[0].id) {
return Err(KclError::Type(KclErrorDetails {
message: "All solids stem from the same root object, like multiple sketch on face extrusions, etc."
.to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(),
vec![args.source_range],
)));
}
args.batch_modeling_cmd(

View File

@ -64,16 +64,16 @@ impl FaceTag {
match self {
FaceTag::Tag(ref t) => args.get_adjacent_face_to_tag(exec_state, t, must_be_planar).await,
FaceTag::StartOrEnd(StartOrEnd::Start) => solid.start_cap_id.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: "Expected a start face".to_string(),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
"Expected a start face".to_string(),
vec![args.source_range],
))
}),
FaceTag::StartOrEnd(StartOrEnd::End) => solid.end_cap_id.ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: "Expected an end face".to_string(),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
"Expected an end face".to_string(),
vec![args.source_range],
))
}),
}
}
@ -329,19 +329,18 @@ async fn straight_line(
let from = sketch.current_pen_position()?;
let (point, is_absolute) = match (end_absolute, end) {
(Some(_), Some(_)) => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other"
.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"You cannot give both `end` and `endAbsolute` params, you have to choose one or the other".to_owned(),
vec![args.source_range],
)));
}
(Some(end_absolute), None) => (end_absolute, true),
(None, Some(end)) => (end, false),
(None, None) => {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: format!("You must supply either `{relative_name}` or `endAbsolute` arguments"),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("You must supply either `{relative_name}` or `endAbsolute` arguments"),
vec![args.source_range],
)));
}
};
@ -608,10 +607,10 @@ async fn inner_angled_line(
.filter(|x| x.is_some())
.count();
if options_given > 1 {
return Err(KclError::Type(KclErrorDetails {
message: " one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
" one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_string(),
vec![args.source_range],
)));
}
if let Some(length_x) = length_x {
return inner_angled_line_of_x_length(angle, length_x, sketch, tag, exec_state, args).await;
@ -636,15 +635,14 @@ async fn inner_angled_line(
(None, None, None, None, Some(end_absolute_y)) => {
inner_angled_line_to_y(angle_degrees, end_absolute_y, sketch, tag, exec_state, args).await
}
(None, None, None, None, None) => Err(KclError::Type(KclErrorDetails {
message: "One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` must be given".to_string(),
source_ranges: vec![args.source_range],
})),
_ => Err(KclError::Type(KclErrorDetails {
message: "Only One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given"
.to_string(),
source_ranges: vec![args.source_range],
})),
(None, None, None, None, None) => Err(KclError::Type(KclErrorDetails::new(
"One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` must be given".to_string(),
vec![args.source_range],
))),
_ => Err(KclError::Type(KclErrorDetails::new(
"Only One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_owned(),
vec![args.source_range],
))),
}
}
@ -715,17 +713,17 @@ async fn inner_angled_line_of_x_length(
args: Args,
) -> Result<Sketch, KclError> {
if angle_degrees.abs() == 270.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have an x constrained angle of 270 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have an x constrained angle of 270 degrees".to_string(),
vec![args.source_range],
)));
}
if angle_degrees.abs() == 90.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have an x constrained angle of 90 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have an x constrained angle of 90 degrees".to_string(),
vec![args.source_range],
)));
}
let to = get_y_component(Angle::from_degrees(angle_degrees), length.n);
@ -747,17 +745,17 @@ async fn inner_angled_line_to_x(
let from = sketch.current_pen_position()?;
if angle_degrees.abs() == 270.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have an x constrained angle of 270 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have an x constrained angle of 270 degrees".to_string(),
vec![args.source_range],
)));
}
if angle_degrees.abs() == 90.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have an x constrained angle of 90 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have an x constrained angle of 90 degrees".to_string(),
vec![args.source_range],
)));
}
let x_component = x_to.to_length_units(from.units) - from.x;
@ -782,17 +780,17 @@ async fn inner_angled_line_of_y_length(
args: Args,
) -> Result<Sketch, KclError> {
if angle_degrees.abs() == 0.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have a y constrained angle of 0 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have a y constrained angle of 0 degrees".to_string(),
vec![args.source_range],
)));
}
if angle_degrees.abs() == 180.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have a y constrained angle of 180 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have a y constrained angle of 180 degrees".to_string(),
vec![args.source_range],
)));
}
let to = get_x_component(Angle::from_degrees(angle_degrees), length.n);
@ -814,17 +812,17 @@ async fn inner_angled_line_to_y(
let from = sketch.current_pen_position()?;
if angle_degrees.abs() == 0.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have a y constrained angle of 0 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have a y constrained angle of 0 degrees".to_string(),
vec![args.source_range],
)));
}
if angle_degrees.abs() == 180.0 {
return Err(KclError::Type(KclErrorDetails {
message: "Cannot have a y constrained angle of 180 degrees".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Cannot have a y constrained angle of 180 degrees".to_string(),
vec![args.source_range],
)));
}
let y_component = y_to.to_length_units(from.units) - from.y;
@ -898,10 +896,10 @@ pub async fn inner_angled_line_that_intersects(
) -> Result<Sketch, KclError> {
let intersect_path = args.get_tag_engine_info(exec_state, &intersect_tag)?;
let path = intersect_path.path.clone().ok_or_else(|| {
KclError::Type(KclErrorDetails {
message: format!("Expected an intersect path with a path, found `{:?}`", intersect_path),
source_ranges: vec![args.source_range],
})
KclError::Type(KclErrorDetails::new(
format!("Expected an intersect path with a path, found `{:?}`", intersect_path),
vec![args.source_range],
))
})?;
let from = sketch.current_pen_position()?;
@ -1176,10 +1174,10 @@ async fn inner_start_sketch_on(
SketchData::Plane(plane) => {
if plane.value == crate::exec::PlaneType::Uninit {
if plane.info.origin.units == UnitLen::Unknown {
return Err(KclError::Semantic(KclErrorDetails {
message: "Origin of plane has unknown units".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Origin of plane has unknown units".to_string(),
vec![args.source_range],
)));
}
let plane = make_sketch_plane_from_orientation(plane.info.into_plane_data(), exec_state, args).await?;
Ok(SketchSurface::Plane(plane))
@ -1200,10 +1198,10 @@ async fn inner_start_sketch_on(
}
SketchData::Solid(solid) => {
let Some(tag) = face else {
return Err(KclError::Type(KclErrorDetails {
message: "Expected a tag for the face to sketch on".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Expected a tag for the face to sketch on".to_string(),
vec![args.source_range],
)));
};
let face = start_sketch_on_face(solid, tag, exec_state, args).await?;
@ -1715,12 +1713,10 @@ pub(crate) async fn inner_arc(
absolute_arc(&args, id, exec_state, sketch, from, interior_absolute, end_absolute, tag).await
}
_ => {
Err(KclError::Type(KclErrorDetails {
message:
"Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)"
.to_string(),
source_ranges: vec![args.source_range],
}))
Err(KclError::Type(KclErrorDetails::new(
"Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)".to_owned(),
vec![args.source_range],
)))
}
}
}
@ -1804,10 +1800,10 @@ pub async fn relative_arc(
let radius = radius.to_length_units(from.units);
let (center, end) = arc_center_and_end(from.ignore_units(), a_start, a_end, radius);
if a_start == a_end {
return Err(KclError::Type(KclErrorDetails {
message: "Arc start and end angles must be different".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Type(KclErrorDetails::new(
"Arc start and end angles must be different".to_string(),
vec![args.source_range],
)));
}
let ccw = a_start < a_end;
@ -1958,19 +1954,18 @@ async fn inner_tangential_arc(
let data = TangentialArcData::RadiusAndOffset { radius, offset: angle };
inner_tangential_arc_radius_angle(data, sketch, tag, exec_state, args).await
}
(Some(_), Some(_), None, None) => Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You cannot give both `end` and `endAbsolute` params, you have to choose one or the other"
.to_owned(),
})),
(None, None, Some(_), None) | (None, None, None, Some(_)) => Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You must supply both `radius` and `angle` arguments".to_owned(),
})),
(_, _, _, _) => Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You must supply `end`, `endAbsolute`, or both `radius` and `angle` arguments".to_owned(),
})),
(Some(_), Some(_), None, None) => Err(KclError::Semantic(KclErrorDetails::new(
"You cannot give both `end` and `endAbsolute` params, you have to choose one or the other".to_owned(),
vec![args.source_range],
))),
(None, None, Some(_), None) | (None, None, None, Some(_)) => Err(KclError::Semantic(KclErrorDetails::new(
"You must supply both `radius` and `angle` arguments".to_owned(),
vec![args.source_range],
))),
(_, _, _, _) => Err(KclError::Semantic(KclErrorDetails::new(
"You must supply `end`, `endAbsolute`, or both `radius` and `angle` arguments".to_owned(),
vec![args.source_range],
))),
}
}
@ -2121,19 +2116,17 @@ async fn inner_tangential_arc_to_point(
});
if result.center[0].is_infinite() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message:
"could not sketch tangential arc, because its center would be infinitely far away in the X direction"
.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"could not sketch tangential arc, because its center would be infinitely far away in the X direction"
.to_owned(),
vec![args.source_range],
)));
} else if result.center[1].is_infinite() {
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message:
"could not sketch tangential arc, because its center would be infinitely far away in the Y direction"
.to_owned(),
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"could not sketch tangential arc, because its center would be infinitely far away in the Y direction"
.to_owned(),
vec![args.source_range],
)));
}
let delta = if is_absolute {

View File

@ -193,10 +193,10 @@ async fn inner_sweep(
Some("sketchPlane") => RelativeTo::SketchPlane,
Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
Some(_) => {
return Err(KclError::Syntax(crate::errors::KclErrorDetails {
source_ranges: vec![args.source_range],
message: "If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
}))
return Err(KclError::Syntax(crate::errors::KclErrorDetails::new(
"If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
vec![args.source_range],
)))
}
};

View File

@ -11,11 +11,13 @@ use kcmc::{
};
use kittycad_modeling_cmds as kcmc;
use super::args::TyF64;
use crate::{
errors::{KclError, KclErrorDetails},
execution::{types::RuntimeType, ExecState, KclValue, SolidOrSketchOrImportedGeometry},
std::Args,
execution::{
types::{PrimitiveType, RuntimeType},
ExecState, KclValue, SolidOrSketchOrImportedGeometry,
},
std::{args::TyF64, axis_or_reference::Axis3dOrPoint3d, Args},
};
/// Scale a solid or a sketch.
@ -36,10 +38,10 @@ pub async fn scale(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// Ensure at least one scale value is provided.
if scale_x.is_none() && scale_y.is_none() && scale_z.is_none() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `x`, `y`, or `z` to be provided.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `x`, `y`, or `z` to be provided.".to_string(),
vec![args.source_range],
)));
}
let objects = inner_scale(
@ -219,10 +221,10 @@ pub async fn translate(exec_state: &mut ExecState, args: Args) -> Result<KclValu
// Ensure at least one translation value is provided.
if translate_x.is_none() && translate_y.is_none() && translate_z.is_none() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `x`, `y`, or `z` to be provided.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `x`, `y`, or `z` to be provided.".to_string(),
vec![args.source_range],
)));
}
let objects = inner_translate(objects, translate_x, translate_y, translate_z, global, exec_state, args).await?;
@ -446,88 +448,96 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
let roll: Option<TyF64> = args.get_kw_arg_opt_typed("roll", &RuntimeType::degrees(), exec_state)?;
let pitch: Option<TyF64> = args.get_kw_arg_opt_typed("pitch", &RuntimeType::degrees(), exec_state)?;
let yaw: Option<TyF64> = args.get_kw_arg_opt_typed("yaw", &RuntimeType::degrees(), exec_state)?;
let axis: Option<[TyF64; 3]> = args.get_kw_arg_opt_typed("axis", &RuntimeType::point3d(), exec_state)?;
let axis: Option<Axis3dOrPoint3d> = args.get_kw_arg_opt_typed(
"axis",
&RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::Axis3d),
RuntimeType::point3d(),
]),
exec_state,
)?;
let axis = axis.map(|a| a.to_point3d());
let angle: Option<TyF64> = args.get_kw_arg_opt_typed("angle", &RuntimeType::degrees(), exec_state)?;
let global = args.get_kw_arg_opt("global")?;
// Check if no rotation values are provided.
if roll.is_none() && pitch.is_none() && yaw.is_none() && axis.is_none() && angle.is_none() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
vec![args.source_range],
)));
}
// If they give us a roll, pitch, or yaw, they must give us at least one of them.
if roll.is_some() || pitch.is_some() || yaw.is_some() {
// Ensure they didn't also provide an axis or angle.
if axis.is_some() || angle.is_some() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
.to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
.to_owned(),
vec![args.source_range],
)));
}
}
// If they give us an axis or angle, they must give us both.
if axis.is_some() || angle.is_some() {
if axis.is_none() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `axis` to be provided when `angle` is provided.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `axis` to be provided when `angle` is provided.".to_string(),
vec![args.source_range],
)));
}
if angle.is_none() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `angle` to be provided when `axis` is provided.".to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `angle` to be provided when `axis` is provided.".to_string(),
vec![args.source_range],
)));
}
// Ensure they didn't also provide a roll, pitch, or yaw.
if roll.is_some() || pitch.is_some() || yaw.is_some() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
.to_string(),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
"Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
.to_owned(),
vec![args.source_range],
)));
}
}
// Validate the roll, pitch, and yaw values.
if let Some(roll) = &roll {
if !(-360.0..=360.0).contains(&roll.n) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
vec![args.source_range],
)));
}
}
if let Some(pitch) = &pitch {
if !(-360.0..=360.0).contains(&pitch.n) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
vec![args.source_range],
)));
}
}
if let Some(yaw) = &yaw {
if !(-360.0..=360.0).contains(&yaw.n) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
vec![args.source_range],
)));
}
}
// Validate the axis and angle values.
if let Some(angle) = &angle {
if !(-360.0..=360.0).contains(&angle.n) {
return Err(KclError::Semantic(KclErrorDetails {
message: format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
source_ranges: vec![args.source_range],
}));
return Err(KclError::Semantic(KclErrorDetails::new(
format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
vec![args.source_range],
)));
}
}
@ -642,7 +652,51 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ```
///
/// ```no_run
/// // Rotate a pipe about an axis with an angle.
/// // Rotate a pipe about a named axis with an angle.
///
/// // Create a path for the sweep.
/// sweepPath = startSketchOn(XZ)
/// |> startProfile(at = [0.05, 0.05])
/// |> line(end = [0, 7])
/// |> tangentialArc(angle = 90, radius = 5)
/// |> line(end = [-3, 0])
/// |> tangentialArc(angle = -90, radius = 5)
/// |> line(end = [0, 7])
///
/// // Create a hole for the pipe.
/// pipeHole = startSketchOn(XY)
/// |> circle(
/// center = [0, 0],
/// radius = 1.5,
/// )
///
/// sweepSketch = startSketchOn(XY)
/// |> circle(
/// center = [0, 0],
/// radius = 2,
/// )
/// |> subtract2d(tool = pipeHole)
/// |> sweep(path = sweepPath)
/// |> rotate(
/// axis = Z,
/// angle = 90,
/// )
/// ```
///
/// ```no_run
/// // Rotate an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// cube
/// |> rotate(
/// axis = [0, 0, 1.0],
/// angle = 9,
/// )
/// ```
///
/// ```no_run
/// // Rotate a pipe about a raw axis with an angle.
///
/// // Create a path for the sweep.
/// sweepPath = startSketchOn(XZ)
@ -673,18 +727,6 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// )
/// ```
///
/// ```no_run
/// // Rotate an imported model.
///
/// import "tests/inputs/cube.sldprt" as cube
///
/// cube
/// |> rotate(
/// axis = [0, 0, 1.0],
/// angle = 9,
/// )
/// ```
///
/// ```
/// // Sweep two sketches along the same path.
///

View File

@ -881,10 +881,10 @@ pub async fn walk_dir(dir: &std::path::PathBuf) -> Result<Vec<std::path::PathBuf
#[cfg(not(target_arch = "wasm32"))]
pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -> Result<(), anyhow::Error> {
let files = walk_dir(&dir.to_path_buf()).await.map_err(|err| {
crate::KclError::Internal(crate::errors::KclErrorDetails {
message: format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
source_ranges: vec![crate::SourceRange::default()],
})
crate::KclError::Internal(crate::errors::KclErrorDetails::new(
format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
vec![crate::SourceRange::default()],
))
})?;
let futures = files

View File

@ -96,11 +96,11 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
if stage_modules.is_empty() {
waiting_modules.sort();
return Err(KclError::ImportCycle(KclErrorDetails {
message: format!("circular import of modules not allowed: {}", waiting_modules.join(", ")),
return Err(KclError::ImportCycle(KclErrorDetails::new(
format!("circular import of modules not allowed: {}", waiting_modules.join(", ")),
// TODO: we can get the right import lines from the AST, but we don't
source_ranges: vec![SourceRange::default()],
}));
vec![SourceRange::default()],
)));
}
// not strictly needed here, but perhaps helpful to avoid thinking
@ -137,20 +137,20 @@ pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> R
// This is a bit of a hack, but it works for now.
ret.lock()
.map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to lock mutex: {}", err),
source_ranges: Default::default(),
})
KclError::Internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
})?
.push((filename.to_string(), is.clone(), resolved_path));
}
ImportPath::Foreign { path } => {
ret.lock()
.map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to lock mutex: {}", err),
source_ranges: Default::default(),
})
KclError::Internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
})?
.push((path.to_string(), is.clone(), resolved_path));
}
@ -169,10 +169,10 @@ pub(crate) fn import_dependencies(repr: &ModuleRepr, ctx: &ExecutorContext) -> R
walk(ret.clone(), prog.into(), ctx)?;
let ret = ret.lock().map_err(|err| {
KclError::Internal(KclErrorDetails {
message: format!("Failed to lock mutex: {}", err),
source_ranges: Default::default(),
})
KclError::Internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
})?;
Ok(ret.clone())
@ -213,10 +213,10 @@ pub(crate) async fn import_universe(
let repr = {
let Some(module_info) = exec_state.get_module(module_id) else {
return Err(KclError::Internal(KclErrorDetails {
message: format!("Module {} not found", module_id),
source_ranges: vec![import_stmt.into()],
}));
return Err(KclError::Internal(KclErrorDetails::new(
format!("Module {} not found", module_id),
vec![import_stmt.into()],
)));
};
module_info.repr.clone()
};

View File

@ -6,7 +6,7 @@
/// The standard library is organised into modules (listed below), but most things are always available
/// in KCL programs.
///
/// You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide]().
/// You might also want the [KCL language reference](/docs/kcl-lang) or the [KCL guide](https://zoo.dev/docs/kcl-book/intro.html).
@no_std
@settings(defaultLengthUnit = mm, kclVersion = 1.0)

View File

@ -278,3 +278,28 @@ export fn revolve(
/// A named tag for the face at the end of the revolve.
tagEnd?: tag,
): [Solid; 1+] {}
/// Just like `patternTransform`, but works on 2D sketches not 3D solids.
///
/// ```kcl
/// // Each instance will be shifted along the X axis.
/// fn transform(@id) {
/// return { translate = [4 * id, 0] }
/// }
///
/// // Sketch 4 circles.
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 2)
/// |> patternTransform2d(instances = 4, transform = transform)
/// ```
@(impl = std_rust)
export fn patternTransform2d(
/// The sketch(es) to duplicate.
@sketches: [Sketch; 1+],
/// The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect.
instances: number(Count),
/// How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
transform: fn(number(Count)): {},
/// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
useOriginal?: boolean = false,
): [Sketch; 1+] {}

View File

@ -365,3 +365,202 @@ export fn hollow(
/// The thickness of the remaining shell
thickness: number(Length),
): Solid {}
/// Repeat a 3-dimensional solid, changing it each time.
///
/// Replicates the 3D solid, applying a transformation function to each replica.
/// Transformation function could alter rotation, scale, visibility, position, etc.
///
/// The `patternTransform` call itself takes a number for how many total instances of
/// the shape should be. For example, if you use a circle with `patternTransform(instances = 4, transform = f)`
/// then there will be 4 circles: the original, and 3 created by replicating the original and
/// calling the transform function on each.
///
/// The transform function takes a single parameter: an integer representing which
/// number replication the transform is for. E.g. the first replica to be transformed
/// will be passed the argument `1`. This simplifies your math: the transform function can
/// rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
///
/// The transform function returns a transform object. All properties of the object are optional,
/// they each default to "no change". So the overall transform object defaults to "no change" too.
/// Its properties are:
///
/// - `translate` (3D point)
///
/// Translates the replica, moving its position in space.
///
/// - `replicate` (bool)
///
/// If false, this ID will not actually copy the object. It'll be skipped.
///
/// - `scale` (3D point)
///
/// Stretches the object, multiplying its width in the given dimension by the point's component in
/// that direction.
///
/// - `rotation` (object, with the following properties)
///
/// - `rotation.axis` (a 3D point, defaults to the Z axis)
///
/// - `rotation.angle` (number of degrees)
///
/// - `rotation.origin` (either "local" i.e. rotate around its own center, "global" i.e. rotate around the scene's center, or a 3D point, defaults to "local")
///
/// ```kcl
/// // Each instance will be shifted along the X axis.
/// fn transform(@id) {
/// return { translate = [4 * id, 0, 0] }
/// }
///
/// // Sketch 4 cylinders.
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 5)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// // Each instance will be shifted along the X axis,
/// // with a gap between the original (at x = 0) and the first replica
/// // (at x = 8). This is because `id` starts at 1.
/// fn transform(@id) {
/// return { translate = [4 * (1+id), 0, 0] }
/// }
///
/// sketch001 = startSketchOn(XZ)
/// |> circle(center = [0, 0], radius = 2)
/// |> extrude(length = 5)
/// |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// fn cube(length, center) {
/// l = length/2
/// x = center[0]
/// y = center[1]
/// p0 = [-l + x, -l + y]
/// p1 = [-l + x, l + y]
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchOn(XY)
/// |> startProfile(at = p0)
/// |> line(endAbsolute = p1)
/// |> line(endAbsolute = p2)
/// |> line(endAbsolute = p3)
/// |> line(endAbsolute = p0)
/// |> close()
/// |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
/// return {
/// // Move down each time.
/// translate = [0, 0, -i * width],
/// // Make the cube longer, wider and flatter each time.
/// scale = [pow(1.1, exp = i), pow(1.1, exp = i), pow(0.9, exp = i)],
/// // Turn by 15 degrees each time.
/// rotation = {
/// angle = 15 * i,
/// origin = "local",
/// }
/// }
/// }
///
/// myCubes =
/// cube(length = width, center = [100,0])
/// |> patternTransform(instances = 25, transform = transform)
/// ```
///
/// ```kcl
/// fn cube(length, center) {
/// l = length/2
/// x = center[0]
/// y = center[1]
/// p0 = [-l + x, -l + y]
/// p1 = [-l + x, l + y]
/// p2 = [ l + x, l + y]
/// p3 = [ l + x, -l + y]
///
/// return startSketchOn(XY)
/// |> startProfile(at = p0)
/// |> line(endAbsolute = p1)
/// |> line(endAbsolute = p2)
/// |> line(endAbsolute = p3)
/// |> line(endAbsolute = p0)
/// |> close()
/// |> extrude(length = length)
/// }
///
/// width = 20
/// fn transform(@i) {
/// return {
/// translate = [0, 0, -i * width],
/// rotation = {
/// angle = 90 * i,
/// // Rotate around the overall scene's origin.
/// origin = "global",
/// }
/// }
/// }
/// myCubes =
/// cube(length = width, center = [100,100])
/// |> patternTransform(instances = 4, transform = transform)
/// ```
///
/// ```kcl
/// // Parameters
/// r = 50 // base radius
/// h = 10 // layer height
/// t = 0.005 // taper factor [0-1)
/// // Defines how to modify each layer of the vase.
/// // Each replica is shifted up the Z axis, and has a smoothly-varying radius
/// fn transform(@replicaId) {
/// scale = r * abs(1 - (t * replicaId)) * (5 + cos((replicaId / 8): number(rad)))
/// return {
/// translate = [0, 0, replicaId * 10],
/// scale = [scale, scale, 0],
/// }
/// }
/// // Each layer is just a pretty thin cylinder.
/// fn layer() {
/// return startSketchOn(XY) // or some other plane idk
/// |> circle(center = [0, 0], radius = 1, tag = $tag1)
/// |> extrude(length = h)
/// }
/// // The vase is 100 layers tall.
/// // The 100 layers are replica of each other, with a slight transformation applied to each.
/// vase = layer() |> patternTransform(instances = 100, transform = transform)
/// ```
///
/// ```kcl
/// fn transform(@i) {
/// // Transform functions can return multiple transforms. They'll be applied in order.
/// return [
/// { translate = [30 * i, 0, 0] },
/// { rotation = { angle = 45 * i } },
/// ]
/// }
/// startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> polygon(
/// radius = 10,
/// numSides = 4,
/// center = [0, 0],
/// inscribed = false,
/// )
/// |> extrude(length = 4)
/// |> patternTransform(instances = 3, transform = transform)
/// ```
@(impl = std_rust)
export fn patternTransform(
/// The solid(s) to duplicate.
@solids: [Solid; 1+],
/// The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect.
instances: number(Count),
/// How each replica should be transformed. The transform function takes a single parameter: an integer representing which number replication the transform is for. E.g. the first replica to be transformed will be passed the argument `1`. This simplifies your math: the transform function can rely on id `0` being the original instance passed into the `patternTransform`. See the examples.
transform: fn(number(Count)): {},
/// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
useOriginal?: boolean = false,
): [Solid; 1+] {}

View File

@ -296,15 +296,15 @@ flowchart LR
33 --- 92
33 --- 105
37 --- 65
37 x--> 84
37 x--> 80
37 --- 86
37 --- 99
38 --- 66
38 x--> 84
38 x--> 80
38 --- 87
38 --- 100
39 --- 67
39 x--> 84
39 x--> 80
39 --- 88
39 --- 101
46 --- 75
@ -409,9 +409,6 @@ flowchart LR
77 --- 96
77 --- 109
110 <--x 77
86 <--x 80
87 <--x 80
88 <--x 80
92 <--x 82
93 <--x 82
94 <--x 82
@ -419,6 +416,9 @@ flowchart LR
96 <--x 83
97 <--x 83
98 <--x 83
86 <--x 84
87 <--x 84
88 <--x 84
89 <--x 85
90 <--x 85
91 <--x 85

View File

@ -337,6 +337,15 @@ description: Artifact commands error_revolve_on_edge_get_edge.kcl
"opposite": "None"
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "solid3d_get_adjacency_info",
"object_id": "[uuid]",
"edge_id": "[uuid]"
}
},
{
"cmdId": "[uuid]",
"range": [],

View File

@ -32,28 +32,30 @@ flowchart LR
%% [ProgramBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
15["Sweep Extrusion<br>[169, 189, 0]"]
%% [ProgramBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 6 }]
16[Wall]
%% face_code_ref=Missing NodePath
16["Sweep RevolveAboutEdge<br>[367, 406, 0]"]
%% [ProgramBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 6 }]
17[Wall]
%% face_code_ref=Missing NodePath
18[Wall]
%% face_code_ref=Missing NodePath
19[Wall]
%% face_code_ref=Missing NodePath
20[Wall]
%% face_code_ref=[ProgramBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
20["Cap Start"]
21["Cap Start"]
%% face_code_ref=Missing NodePath
21["Cap End"]
22["Cap End"]
%% face_code_ref=Missing NodePath
22["SweepEdge Opposite"]
23["SweepEdge Opposite"]
24["SweepEdge Opposite"]
25["SweepEdge Opposite"]
26["SweepEdge Adjacent"]
26["SweepEdge Opposite"]
27["SweepEdge Adjacent"]
28["SweepEdge Adjacent"]
29["SweepEdge Adjacent"]
30["SweepEdge Adjacent"]
1 --- 3
19 x--> 2
20 x--> 2
3 --- 5
3 --- 6
3 --- 7
@ -65,24 +67,24 @@ flowchart LR
4 --- 11
4 --- 12
4 --- 14
19 --- 4
5 --- 18
5 x--> 20
5 --- 25
5 --- 29
6 --- 16
6 x--> 20
6 --- 24
6 --- 28
7 --- 19
7 x--> 20
7 --- 23
7 --- 27
8 --- 17
8 x--> 20
8 --- 22
8 --- 26
15 --- 16
4 ---- 16
20 --- 4
5 --- 19
5 x--> 21
5 --- 26
5 --- 30
6 --- 17
6 x--> 21
6 --- 25
6 --- 29
7 --- 20
7 x--> 21
7 --- 24
7 --- 28
8 --- 18
8 x--> 21
8 --- 23
8 --- 27
15 --- 17
15 --- 18
15 --- 19
@ -96,20 +98,21 @@ flowchart LR
15 --- 27
15 --- 28
15 --- 29
16 --- 24
16 --- 28
29 <--x 16
17 --- 22
17 --- 26
27 <--x 17
18 --- 25
26 <--x 18
18 --- 29
19 --- 23
19 --- 27
28 <--x 19
22 <--x 21
23 <--x 21
24 <--x 21
25 <--x 21
15 --- 30
17 --- 25
17 --- 29
30 <--x 17
18 --- 23
18 --- 27
28 <--x 18
19 --- 26
27 <--x 19
19 --- 30
20 --- 24
20 --- 28
29 <--x 20
23 <--x 22
24 <--x 22
25 <--x 22
26 <--x 22
```

View File

@ -4,23 +4,11 @@ description: Error from executing error_revolve_on_edge_get_edge.kcl
---
KCL Engine error
× engine: Solid3D revolve failed: sketch profile must lie entirely on one
side of the revolution axis
× engine: Entity found but it was of the wrong type
No such edge exists, cannot retrieve adjacency information.
╭─[15:6]
14 │ |> close()
15 │ |> revolve(axis = revolveAxis, angle = 90)
· ───────────────────┬───────────────────
· ╰── tests/error_revolve_on_edge_get_edge/input.kcl
· ───────────────────┬───────────────────
· ╰── tests/error_revolve_on_edge_get_edge/input.kcl
╰────
╰─▶ KCL Engine error
× engine: Solid3D revolve failed: sketch profile must lie entirely on
│ one side of the revolution axis
╭─[15:6]
14 │ |> close()
15 │ |> revolve(axis = revolveAxis, angle = 90)
· ───────────────────┬───────────────────
· ╰── tests/error_revolve_on_edge_get_edge/
input.kcl
╰────

View File

@ -110,7 +110,6 @@ description: Operations executed error_revolve_on_edge_get_edge.kcl
"sourceRange": []
}
},
"sourceRange": [],
"isError": true
"sourceRange": []
}
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@ -661,19 +661,19 @@ flowchart LR
84 --- 144
84 --- 237
86 --- 168
86 x--> 188
86 x--> 189
86 --- 212
86 --- 256
88 --- 169
88 x--> 188
88 x--> 189
88 --- 213
88 --- 257
90 --- 167
90 x--> 188
90 x--> 189
90 --- 214
90 --- 258
92 --- 170
92 x--> 188
92 x--> 189
92 --- 215
92 --- 259
119 --- 133
@ -955,10 +955,10 @@ flowchart LR
218 <--x 186
219 <--x 186
194 <--x 187
212 <--x 189
213 <--x 189
214 <--x 189
215 <--x 189
212 <--x 188
213 <--x 188
214 <--x 188
215 <--x 188
220 <--x 275
223 <--x 270
224 <--x 274

View File

@ -177,7 +177,7 @@ flowchart LR
22 --- 44
22 --- 62
23 --- 38
23 x--> 52
23 x--> 49
23 --- 53
23 --- 56
24 --- 46
@ -226,7 +226,7 @@ flowchart LR
62 <--x 45
46 --- 55
46 --- 63
53 <--x 49
54 <--x 50
55 <--x 51
53 <--x 52
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

View File

@ -327,67 +327,67 @@ flowchart LR
11 ---- 54
11 --- 62
12 --- 83
12 x--> 100
12 x--> 91
12 --- 113
12 --- 137
13 --- 78
13 x--> 100
13 x--> 91
13 --- 114
13 --- 138
14 --- 77
14 x--> 100
14 x--> 91
14 --- 115
14 --- 139
15 --- 79
15 x--> 100
15 x--> 91
15 --- 116
15 --- 140
16 --- 75
16 x--> 100
16 x--> 91
16 --- 117
16 --- 141
17 --- 74
17 x--> 100
17 x--> 91
17 --- 118
17 --- 142
18 --- 82
18 x--> 100
18 x--> 91
18 --- 119
18 --- 143
19 --- 85
19 x--> 100
19 x--> 91
19 --- 120
19 --- 144
20 --- 84
20 x--> 100
20 x--> 91
20 --- 121
20 --- 145
21 --- 72
21 x--> 100
21 x--> 91
21 --- 122
21 --- 146
22 --- 73
22 x--> 100
22 x--> 91
22 --- 123
22 --- 147
23 --- 86
23 x--> 100
23 x--> 91
23 --- 124
23 --- 148
24 --- 80
24 x--> 100
24 x--> 91
24 --- 125
24 --- 149
25 --- 76
25 x--> 100
25 x--> 91
25 --- 126
25 --- 150
26 --- 71
26 x--> 100
26 x--> 91
26 --- 127
26 --- 151
27 --- 81
27 x--> 100
27 x--> 91
27 --- 128
27 --- 152
29 --- 70
@ -587,24 +587,24 @@ flowchart LR
110 <--x 88
112 <--x 89
108 <--x 90
113 <--x 91
114 <--x 91
115 <--x 91
116 <--x 91
117 <--x 91
118 <--x 91
119 <--x 91
120 <--x 91
121 <--x 91
122 <--x 91
123 <--x 91
124 <--x 91
125 <--x 91
126 <--x 91
127 <--x 91
128 <--x 91
111 <--x 92
106 <--x 93
105 <--x 94
107 <--x 95
113 <--x 100
114 <--x 100
115 <--x 100
116 <--x 100
117 <--x 100
118 <--x 100
119 <--x 100
120 <--x 100
121 <--x 100
122 <--x 100
123 <--x 100
124 <--x 100
125 <--x 100
126 <--x 100
127 <--x 100
128 <--x 100
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact graph flowchart brake-rotor.kcl
extension: md
snapshot_kind: binary
---

View File

@ -0,0 +1,430 @@
```mermaid
flowchart LR
subgraph path8 [Path]
8["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
26["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
48[Solid2d]
end
subgraph path9 [Path]
9["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
24["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
49[Solid2d]
end
subgraph path10 [Path]
10["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
21["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
51[Solid2d]
end
subgraph path11 [Path]
11["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
25["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
52[Solid2d]
end
subgraph path12 [Path]
12["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
23["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
53[Solid2d]
end
subgraph path13 [Path]
13["Path<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
22["Segment<br>[1124, 1197, 0]"]
%% [ProgramBodyItem { index: 20 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 8 }, VariableDeclarationDeclaration, VariableDeclarationInit]
57[Solid2d]
end
subgraph path14 [Path]
14["Path<br>[1402, 1462, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
27["Segment<br>[1402, 1462, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
46[Solid2d]
end
subgraph path15 [Path]
15["Path<br>[1402, 1462, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
28["Segment<br>[1402, 1462, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
54[Solid2d]
end
subgraph path16 [Path]
16["Path<br>[1488, 1572, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }, CallKwArg { index: 0 }]
29["Segment<br>[1488, 1572, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }, CallKwArg { index: 0 }]
55[Solid2d]
end
subgraph path17 [Path]
17["Path<br>[1488, 1572, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }, CallKwArg { index: 0 }]
30["Segment<br>[1488, 1572, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 1 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }, CallKwArg { index: 0 }]
58[Solid2d]
end
subgraph path18 [Path]
18["Path<br>[2526, 2576, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
31["Segment<br>[2582, 2648, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
32["Segment<br>[2654, 2748, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
33["Segment<br>[2754, 2856, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
34["Segment<br>[2862, 2932, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
35["Segment<br>[2938, 2945, 0]"]
%% [ProgramBodyItem { index: 25 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
47[Solid2d]
end
subgraph path19 [Path]
19["Path<br>[4126, 4264, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
36["Segment<br>[4270, 4369, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
37["Segment<br>[4375, 4423, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 2 }]
38["Segment<br>[4429, 4465, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 3 }]
39["Segment<br>[4471, 4493, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 4 }]
40["Segment<br>[4499, 4522, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 5 }]
41["Segment<br>[4528, 4578, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 6 }]
42["Segment<br>[4584, 4603, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 7 }]
43["Segment<br>[4628, 4668, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 8 }]
44["Segment<br>[4674, 4682, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 9 }]
56[Solid2d]
end
subgraph path20 [Path]
20["Path<br>[4796, 4874, 0]"]
%% [ProgramBodyItem { index: 39 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
45["Segment<br>[4796, 4874, 0]"]
%% [ProgramBodyItem { index: 39 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
50[Solid2d]
end
1["Plane<br>[1365, 1385, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit]
2["Plane<br>[2438, 2473, 0]"]
%% [ProgramBodyItem { index: 23 }, VariableDeclarationDeclaration, VariableDeclarationInit]
3["Plane<br>[3498, 3541, 0]"]
%% [ProgramBodyItem { index: 28 }, VariableDeclarationDeclaration, VariableDeclarationInit]
4["Plane<br>[4092, 4110, 0]"]
%% [ProgramBodyItem { index: 36 }, VariableDeclarationDeclaration, VariableDeclarationInit]
5["StartSketchOnPlane<br>[1365, 1385, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 0 }, VariableDeclarationDeclaration, VariableDeclarationInit]
6["StartSketchOnPlane<br>[2487, 2511, 0]"]
%% [ProgramBodyItem { index: 24 }, VariableDeclarationDeclaration, VariableDeclarationInit]
7["StartSketchOnFace<br>[4740, 4781, 0]"]
%% [ProgramBodyItem { index: 38 }, VariableDeclarationDeclaration, VariableDeclarationInit]
59["Sweep Extrusion<br>[2053, 2098, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 7 }, VariableDeclarationDeclaration, VariableDeclarationInit]
60["Sweep Extrusion<br>[2053, 2098, 0]"]
%% [ProgramBodyItem { index: 21 }, VariableDeclarationDeclaration, VariableDeclarationInit, FunctionExpressionBody, FunctionExpressionBodyItem { index: 7 }, VariableDeclarationDeclaration, VariableDeclarationInit]
61["Sweep Extrusion<br>[2957, 2993, 0]"]
%% [ProgramBodyItem { index: 26 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 0 }]
62["Sweep Revolve<br>[4688, 4705, 0]"]
%% [ProgramBodyItem { index: 37 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 10 }]
63["Sweep Extrusion<br>[5082, 5137, 0]"]
%% [ProgramBodyItem { index: 41 }, VariableDeclarationDeclaration, VariableDeclarationInit]
64["Sweep Extrusion<br>[5082, 5137, 0]"]
%% [ProgramBodyItem { index: 41 }, VariableDeclarationDeclaration, VariableDeclarationInit]
65["Sweep Extrusion<br>[5082, 5137, 0]"]
%% [ProgramBodyItem { index: 41 }, VariableDeclarationDeclaration, VariableDeclarationInit]
66["Sweep Extrusion<br>[5082, 5137, 0]"]
%% [ProgramBodyItem { index: 41 }, VariableDeclarationDeclaration, VariableDeclarationInit]
67["Sweep Extrusion<br>[5082, 5137, 0]"]
%% [ProgramBodyItem { index: 41 }, VariableDeclarationDeclaration, VariableDeclarationInit]
68[Wall]
%% face_code_ref=Missing NodePath
69[Wall]
%% face_code_ref=Missing NodePath
70[Wall]
%% face_code_ref=Missing NodePath
71[Wall]
%% face_code_ref=Missing NodePath
72[Wall]
%% face_code_ref=Missing NodePath
73[Wall]
%% face_code_ref=Missing NodePath
74[Wall]
%% face_code_ref=Missing NodePath
75[Wall]
%% face_code_ref=Missing NodePath
76[Wall]
%% face_code_ref=Missing NodePath
77[Wall]
%% face_code_ref=Missing NodePath
78[Wall]
%% face_code_ref=Missing NodePath
79[Wall]
%% face_code_ref=Missing NodePath
80[Wall]
%% face_code_ref=Missing NodePath
81[Wall]
%% face_code_ref=Missing NodePath
82[Wall]
%% face_code_ref=[ProgramBodyItem { index: 38 }, VariableDeclarationDeclaration, VariableDeclarationInit]
83[Wall]
%% face_code_ref=Missing NodePath
84["Cap Start"]
%% face_code_ref=Missing NodePath
85["Cap Start"]
%% face_code_ref=Missing NodePath
86["Cap Start"]
%% face_code_ref=Missing NodePath
87["Cap Start"]
%% face_code_ref=Missing NodePath
88["Cap End"]
%% face_code_ref=Missing NodePath
89["Cap End"]
%% face_code_ref=Missing NodePath
90["Cap End"]
%% face_code_ref=Missing NodePath
91["SweepEdge Opposite"]
92["SweepEdge Opposite"]
93["SweepEdge Opposite"]
94["SweepEdge Opposite"]
95["SweepEdge Opposite"]
96["SweepEdge Opposite"]
97["SweepEdge Opposite"]
98["SweepEdge Adjacent"]
99["SweepEdge Adjacent"]
100["SweepEdge Adjacent"]
101["SweepEdge Adjacent"]
102["SweepEdge Adjacent"]
103["SweepEdge Adjacent"]
104["SweepEdge Adjacent"]
105["SweepEdge Adjacent"]
106["SweepEdge Adjacent"]
107["SweepEdge Adjacent"]
108["SweepEdge Adjacent"]
109["SweepEdge Adjacent"]
110["SweepEdge Adjacent"]
111["SweepEdge Adjacent"]
112["SweepEdge Adjacent"]
113["EdgeCut Fillet<br>[2999, 3289, 0]"]
%% [ProgramBodyItem { index: 26 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
114["EdgeCut Fillet<br>[2999, 3289, 0]"]
%% [ProgramBodyItem { index: 26 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
115["EdgeCut Fillet<br>[2999, 3289, 0]"]
%% [ProgramBodyItem { index: 26 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
116["EdgeCut Fillet<br>[2999, 3289, 0]"]
%% [ProgramBodyItem { index: 26 }, VariableDeclarationDeclaration, VariableDeclarationInit, PipeBodyItem { index: 1 }]
1 --- 8
1 --- 9
1 --- 13
1 --- 15
1 --- 16
2 <--x 6
2 --- 18
3 <--x 5
3 --- 10
3 --- 11
3 --- 12
3 --- 14
3 --- 17
4 --- 19
82 x--> 7
8 --- 26
8 --- 48
9 --- 24
9 --- 49
10 --- 21
10 --- 51
11 --- 25
11 --- 52
12 --- 23
12 --- 53
13 --- 22
13 --- 57
14 --- 27
14 --- 46
14 ---- 60
15 --- 28
15 --- 54
15 ---- 59
16 --- 29
16 --- 55
17 --- 30
17 --- 58
18 --- 31
18 --- 32
18 --- 33
18 --- 34
18 --- 35
18 --- 47
18 ---- 61
19 --- 36
19 --- 37
19 --- 38
19 --- 39
19 --- 40
19 --- 41
19 --- 42
19 --- 43
19 --- 44
19 --- 56
19 ---- 62
20 --- 45
20 --- 50
20 ---- 64
82 --- 20
27 --- 83
27 x--> 87
27 --- 97
27 --- 112
28 --- 68
28 x--> 85
28 --- 91
28 --- 98
31 --- 70
31 x--> 84
31 --- 95
31 --- 102
32 --- 71
32 x--> 84
32 --- 94
32 --- 101
33 --- 69
33 x--> 84
33 --- 93
33 --- 100
34 --- 72
34 x--> 84
34 --- 92
34 --- 99
62 <--x 36
36 --- 78
36 x--> 104
62 <--x 37
37 --- 77
37 --- 104
62 <--x 38
38 --- 82
38 --- 105
62 <--x 39
39 --- 79
39 --- 106
62 <--x 40
40 --- 74
40 --- 107
62 <--x 41
41 --- 81
41 --- 108
62 <--x 42
42 --- 76
42 --- 109
62 <--x 43
43 --- 75
43 --- 110
62 <--x 44
44 --- 80
44 --- 111
45 --- 73
45 x--> 82
45 --- 96
45 --- 103
59 --- 68
59 --- 85
59 --- 89
59 --- 91
59 --- 98
60 --- 83
60 --- 87
60 --- 90
60 --- 97
60 --- 112
61 --- 69
61 --- 70
61 --- 71
61 --- 72
61 --- 84
61 --- 88
61 --- 92
61 --- 93
61 --- 94
61 --- 95
61 --- 99
61 --- 100
61 --- 101
61 --- 102
62 --- 74
62 --- 75
62 --- 76
62 --- 77
62 --- 78
62 --- 79
62 --- 80
62 --- 81
62 --- 82
62 --- 104
62 --- 105
62 --- 106
62 --- 107
62 --- 108
62 --- 109
62 --- 110
62 --- 111
64 --- 73
64 --- 86
64 --- 96
64 --- 103
68 --- 91
68 --- 98
69 --- 93
69 --- 100
101 <--x 69
70 --- 95
99 <--x 70
70 --- 102
71 --- 94
71 --- 101
102 <--x 71
72 --- 92
72 --- 99
100 <--x 72
73 --- 96
73 --- 103
106 <--x 74
74 --- 107
109 <--x 75
75 --- 110
108 <--x 76
76 --- 109
77 --- 104
78 --- 104
111 <--x 78
105 <--x 79
79 --- 106
110 <--x 80
80 --- 111
107 <--x 81
81 --- 108
82 --- 105
83 --- 97
83 --- 112
96 <--x 86
92 <--x 88
93 <--x 88
94 <--x 88
95 <--x 88
91 <--x 89
97 <--x 90
99 <--x 114
100 <--x 113
101 <--x 116
102 <--x 115
```

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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