Compare commits
11 Commits
revert-706
...
remove-unu
Author | SHA1 | Date | |
---|---|---|---|
ec36814626 | |||
553e650fbe | |||
9690a24c68 | |||
978d5d44a2 | |||
9df476543a | |||
cf303ebe97 | |||
b1d1d89ca5 | |||
3a599d0a0a | |||
8340f6b906 | |||
ddb034b14d | |||
bfa2f67393 |
4
.github/workflows/cargo-test.yml
vendored
@ -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:
|
||||
|
26
.github/workflows/e2e-tests.yml
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
16124
docs/kcl-std/std.json
@ -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(
|
||||
|
@ -37,6 +37,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
||||
[](bottle/main.kcl)
|
||||
#### [bracket](bracket/main.kcl) ([screenshot](screenshots/bracket.png))
|
||||
[](bracket/main.kcl)
|
||||
#### [brake-rotor](brake-rotor/main.kcl) ([screenshot](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/main.kcl)
|
||||
#### [cold-plate](cold-plate/main.kcl) ([screenshot](screenshots/cold-plate.png))
|
||||
|
179
public/kcl-samples/brake-rotor/main.kcl
Normal 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))
|
@ -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",
|
||||
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
BIN
public/kcl-samples/screenshots/brake-rotor.png
Normal file
After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 216 KiB After Width: | Height: | Size: 216 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
20
rust/Cargo.lock
generated
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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")]
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
@ -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> {
|
||||
|
@ -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(())
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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],
|
||||
))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(),
|
||||
)),
|
||||
|
@ -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",
|
||||
|
@ -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())
|
||||
|
@ -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],
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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()],
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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())
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
]
|
||||
}
|
||||
);
|
||||
|
@ -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.
|
||||
|
@ -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 ¶ms {
|
||||
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]
|
||||
"#;
|
||||
|
@ -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)],
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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();
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
|
@ -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],
|
||||
))
|
||||
})
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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(())
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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],
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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!(),
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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],
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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.
|
||||
///
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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+] {}
|
||||
|
@ -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+] {}
|
||||
|
@ -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
|
||||
|
@ -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": [],
|
||||
|
@ -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
|
||||
```
|
||||
|
@ -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
|
||||
╰────
|
||||
|
@ -110,7 +110,6 @@ description: Operations executed error_revolve_on_edge_get_edge.kcl
|
||||
"sourceRange": []
|
||||
}
|
||||
},
|
||||
"sourceRange": [],
|
||||
"isError": true
|
||||
"sourceRange": []
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
@ -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
|
||||
|
@ -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
|
||||
```
|
||||
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
@ -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
|
||||
```
|
||||
|
3487
rust/kcl-lib/tests/kcl_samples/brake-rotor/artifact_commands.snap
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
source: kcl-lib/src/simulation_tests.rs
|
||||
description: Artifact graph flowchart brake-rotor.kcl
|
||||
extension: md
|
||||
snapshot_kind: binary
|
||||
---
|
@ -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
|
||||
```
|