Compare commits

...

31 Commits

Author SHA1 Message Date
f97bdaf8b7 Release KCL 79 (#7347) 2025-06-03 22:04:56 -04:00
3f3693e12d Type ascription produces two incompatible fields (#7355)
# Symptoms

This code produces a big ugly confusing error in the frontend, see #7340.

# Root cause

I added a new test case, with an unknown type. In `ast.snap` under `body[0].declaration.init.ty` there two different `type` fields in the AST node for the type's name, and they have conflicting values Primitive and Identifier.

<img width="602" alt="Screenshot 2025-06-03 at 4 04 55 PM" src="https://github.com/user-attachments/assets/913a0fa0-3e8d-473f-bb64-003d44915be0" />

# Solution

Change the `enum PrimitiveType` variant from `Named(Node<Identifier>)` to `Named { name: Node<Identifier> }` so that the fields nest differently.

Now the error correctly points out to the user that the type `NotARealType` can't be found. Much better error message that shows the user the problem.

# Alternative solutions

Stop the duplicated JSON fields altogether. I tried this previously in https://github.com/KittyCAD/modeling-app/pull/4369 but it was very involved, and I didn't think it was worth it. Maybe I should reopen that PR and solve this properly.

Closes #7340
2025-06-03 20:05:40 -04:00
73660d1db8 Bump criterion from 0.5.1 to 0.6.0 (#7357) 2025-06-04 09:34:34 +10:00
c373f33507 KCL: Fix format 2025-06-04 09:04:35 +10:00
2dc76a71cc Stabilize test: "Can edit a sketch with multiple profiles, dragging segments to edit them, and adding one new profile" (#7356) 2025-06-03 18:20:06 -04:00
ce42966f2b Upgrade to Rust 1.87 (#7346)
I ignored some new clippy lints about large differences between enum variants.
We can always revisit these later (the compiler suggests boxing them so
that the enum variants are similar size)
2025-06-03 17:32:24 -04:00
b47b9c9613 KCL: Emit proper errors in unexpected edge cases (#7351)
There's some bug in the frontend or KCL somewhere, which results in the TypeScript frontend sending an AST (serialized to JSON) to the KCL executor, but the JSON cannot be deserialized into an AST. If this happens, it's a bug in ZDS, not a user error. 

The problem is that this sort of error will cause the frontend to silently stop rendering KCL, and it won't show the user any errors. They need to open up the console and look at the error there, and even if they do, it's hard to understand.

This PR changes how we report these unexpected errors due to bugs in ZDS. ZDS should not silently stop working, it should at least print a half-decent error like this:

<img width="527" alt="nicer error" src="https://github.com/user-attachments/assets/1bb37a64-0915-4472-849c-d146f397356b" />

## Fix

Right now, the wasm library exports a function `execute`. It previous returned an error as a String if one occurred. The frontend assumed this error string would be JSON that matched the schema `KclErrorWithOutputs`. This was not always true! For example, if something couldn't be serialized to JSON, we'd take the raw Serde error and stringify that. It wouldn't match `KclErrorWithOutputs`.

Now I've changed `execute` so that if it errors, it'll returns a JsValue not a string. So that's one check (can this string be deserialized into a JSON object) that can be removed -- it'll return a JSON object directly now. The next check is "does this JSON object conform to the KclErrorWithOutputs schema". To prove that's correct, I changed `execute` to be a thin wrapper around `fn execute_typed` which returns `Result<ExecOutcome, KclErrorWithOutputs>`. Now we know the error will be the right type.
2025-06-03 15:37:17 -05:00
2af2144f89 Add query parameter to skip the embedded "sign in" view (#7352)
Add query parameter to skip sign-in view if not necessary
2025-06-03 16:23:23 -04:00
bd37c488ee Fix trackball, finally add an E2E test for it (#7341)
* Fix orbit style setting not updating in camControls

* Break apart camera movement tests, add trackball to orbit one

* I don't think zoom was actually testing changes, this fixes that

* test refactor: pass in expected cam pos, not its inverse

* Lints

* Lint fix broke the test, fix fix

* Gah biome whyyy did you format other test names like that?
2025-06-03 14:56:19 -04:00
a3551e4b2f Bump csscolorparser from 0.7.0 to 0.7.2 (#7344) 2025-06-03 11:50:09 -05:00
d3979edb41 KCL: Better error message when using var in its own definition (#7339)
I thought I did this in https://github.com/KittyCAD/modeling-app/pull/7325, but I forgot to actually set the better message.

Actually fixes, for real this time, https://github.com/KittyCAD/modeling-app/issues/6072 this time.
2025-06-03 16:46:28 +00:00
095a7a575b Bump tokio-tungstenite from 0.24.0 to 0.26.2 (#7329) 2025-06-03 10:16:22 -05:00
b5c8ca05a5 Fix whitespace in updater toast (#7331)
pierremtb/adhoc/whitespace-fix-updater-toast
2025-06-03 10:15:42 -04:00
33f7badf41 point and click-ify mounting plate (#7287)
* point and click-ify mounting plate

* Update kcl-samples simulation test output

* Update public/kcl-samples/mounting-plate/main.kcl

* Update public/kcl-samples/mounting-plate/main.kcl

* fix

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2025-06-03 08:10:37 -04:00
569935c21f Move segment functions to KCL (#7333)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-06-03 15:15:51 +12:00
7680605085 KCL: Fix 'cryptic' error when referencing a variable in its own declaration (#7325)
Previously, `x = cos(x)` would just say "`x` is undefined". Now it says that `x` cannot be referenced in its own definition, try using a different variable instead.

To do this, I've added a new `Option<String>` field to the mod-local executor context, tracking the current variable declaration. This means cloning some strings, implying a small performance hit. I think it's fine, for the better diagnostics.

In the future we could refactor this to use a &str or store variable labels in stack-allocated strings like docs.rs/compact_str or something.

Closes https://github.com/KittyCAD/modeling-app/issues/6072
2025-06-02 18:25:55 -04:00
2bb6c74f42 Update error message after engine change (#7330) 2025-06-02 18:05:46 -04:00
faf4d42b6a Update getSketchExprsFromSelection to use codeRef.pathToNode (#6737) 2025-06-02 16:49:41 -04:00
8dd2a86191 Make sweep tests more point-and-click like and clean up pathToNode retrieval (#6963)
WIP: udpate sweep point-and-click tests and mess with pathToNode
Fixes #6952
2025-06-02 16:49:20 -04:00
13c4de77c3 Fix broken WASM test (#7324)
Previous PR (#7321) was set to automerge, but apparently the npm-unit-test
CI target doesn't block merges if it fails. Fixed now.
2025-06-02 15:56:49 -04:00
e29ee9d1ca KCL: Use named fields for KclError (#7321)
We've changed the unnamed field of `KclError` variants to a named called `details`.

To clarify: previously KCL errors looked like this:

```rust
pub enum KclError {
    Lexical(KclErrorDetails),
    Syntax(KclErrorDetails),
```

Now they look like this:

```rust
pub enum KclError {
    Lexical { details: KclErrorDetails },
    Syntax { details: KclErrorDetails },
}
```

This lets us more easily add fields to the errors. For example, in the UndefinedValue case, adding a field for what the undefined name was. This PR refactors the code to make my PR in https://github.com/KittyCAD/modeling-app/pull/7309 much easier.

Pure refactor, should not change any behaviour.
2025-06-02 14:30:57 -04:00
b7437e949a Update generated output to try to fix flakiness (#7323) 2025-06-02 13:36:41 -04:00
08781ff010 Add transform operations to the Feature Tree (#7307)
* Add transform operations

* Update output

* Add scale icon
2025-06-02 10:32:36 -04:00
eb79b1f746 #7227 Fix project not saving when dragging a segment (#7276)
* fix bug of not saving project when dragging a segment, add a test

* Update e2e/playwright/projects.spec.ts

Co-authored-by: Jace Browning <jacebrowning@gmail.com>

---------

Co-authored-by: Jace Browning <jacebrowning@gmail.com>
2025-06-02 09:28:23 +02:00
8df81b2753 Update URL in Discord Bot (#7306) 2025-05-30 14:36:45 -07:00
e75a604c64 Fix the link for plan upgrades (#7305) 2025-05-30 16:54:07 -04:00
0624e42822 Add more detail to close() docs (#7300)
* Add more detail to close() docs

* Run gen
2025-05-30 16:05:32 -04:00
1c07e8af5b Add hysteresis and EMA to ping to avoid flickering network badge (#7197)
* Don't use WEAK and yellow

* fmt && lint && tsc

* Fix up the rebase & dark mode colors

* Update src/hooks/useNetworkStatus.tsx

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

* Change Weak to Ok

* Change Connected to Strong

* fmt

* Sync selectors for start sketch

* Remove unused test-util brought back in a rebase

* Align the other OKs

* Add an else statement to overallState

---------

Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
2025-05-30 15:50:05 -04:00
5fccaad0e7 KCL: Fix cryptic error on unexpected tokens in fn call (#7295)
This program:
```kcl
1
|> extrude(
  length=depth,
})
```

was giving this bad error:

```
unexpected token |>
```

Now it gives

```kcl
There was an unexpected }. Try removing it.
```

and it correctly puts the diagnostic on the extra }.

Fixes https://github.com/KittyCAD/modeling-app/issues/6126
2025-05-30 14:57:05 -04:00
a506f7f698 KCL: Fix cryptic error when missing commas between arguments (#7297)
Previously, this KCL

```
arc(
    endAbsolute = [0, 50]
    interiorAbsolute = [-50, 0]
)
```

gave the error `This argument has a label, but no value. Put some value after the equals sign`.

Now it gives this much better error `Missing comma between arguments, try adding a comma in`, and its source range (red underline) is on the whitespace which was missing a comma:

<img width="666" src="https://github.com/user-attachments/assets/aa5035f5-f748-4dab-b918-b81b05733323" />

Thanks for reporting this @benjamaan476
2025-05-30 13:20:06 -05:00
1bb96cd878 Release KCL 78 (#7298) 2025-05-30 13:48:17 -04:00
209 changed files with 12780 additions and 9234 deletions

View File

@ -14,7 +14,7 @@ close(
): Sketch
```
If you want to perform some 3-dimensional operation on a sketch, like extrude or sweep, you must `close` it first. `close` must be called even if the end point of the last segment is coincident with the sketch starting point.
### Arguments

View File

@ -8,7 +8,7 @@ layout: manual
Extract the 'x' axis value of the last line segment in the provided 2-d sketch.
```kcl
lastSegX(@sketch: Sketch): number
lastSegX(@sketch: Sketch): number(Length)
```
@ -17,11 +17,11 @@ lastSegX(@sketch: Sketch): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | The sketch whose line segment is being queried | Yes |
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | The sketch whose line segment is being queried. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Extract the 'y' axis value of the last line segment in the provided 2-d sketch.
```kcl
lastSegY(@sketch: Sketch): number
lastSegY(@sketch: Sketch): number(Length)
```
@ -17,11 +17,11 @@ lastSegY(@sketch: Sketch): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | The sketch whose line segment is being queried | Yes |
| `sketch` | [`Sketch`](/docs/kcl-std/types/std-types-Sketch) | The sketch whose line segment is being queried. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Compute the angle (in degrees) of the provided line segment.
```kcl
segAng(@tag: TagIdentifier): number
segAng(@tag: tag): number(Angle)
```
@ -17,11 +17,11 @@ segAng(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Angle)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Compute the ending point of the provided line segment.
```kcl
segEnd(@tag: TagIdentifier): Point2d
segEnd(@tag: tag): Point2d
```
@ -17,7 +17,7 @@ segEnd(@tag: TagIdentifier): Point2d
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
@ -39,9 +39,9 @@ cube = startSketchOn(XY)
fn cylinder(radius, tag) {
return startSketchOn(XY)
|> startProfile(at = [0, 0])
|> circle(radius = radius, center = segEnd(tag))
|> extrude(length = radius)
|> startProfile(at = [0, 0])
|> circle(radius = radius, center = segEnd(tag) )
|> extrude(length = radius)
}
cylinder(radius = 1, tag = line1)

View File

@ -8,7 +8,7 @@ layout: manual
Compute the ending point of the provided line segment along the 'x' axis.
```kcl
segEndX(@tag: TagIdentifier): number
segEndX(@tag: tag): number(Length)
```
@ -17,11 +17,11 @@ segEndX(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Compute the ending point of the provided line segment along the 'y' axis.
```kcl
segEndY(@tag: TagIdentifier): number
segEndY(@tag: tag): number(Length)
```
@ -17,11 +17,11 @@ segEndY(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Compute the length of the provided line segment.
```kcl
segLen(@tag: TagIdentifier): number
segLen(@tag: tag): number(Length)
```
@ -17,11 +17,11 @@ segLen(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -29,9 +29,16 @@ segLen(@tag: TagIdentifier): number
```kcl
exampleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> angledLine(angle = 60, length = 10, tag = $thing)
|> angledLine(
angle = 60,
length = 10,
tag = $thing,
)
|> tangentialArc(angle = -120, radius = 5)
|> angledLine(angle = -60, length = segLen(thing))
|> angledLine(
angle = -60,
length = segLen(thing),
)
|> close()
example = extrude(exampleSketch, length = 5)

View File

@ -8,7 +8,7 @@ layout: manual
Compute the starting point of the provided line segment.
```kcl
segStart(@tag: TagIdentifier): Point2d
segStart(@tag: tag): Point2d
```
@ -17,7 +17,7 @@ segStart(@tag: TagIdentifier): Point2d
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
@ -39,9 +39,9 @@ cube = startSketchOn(XY)
fn cylinder(radius, tag) {
return startSketchOn(XY)
|> startProfile(at = [0, 0])
|> circle(radius = radius, center = segStart(tag))
|> extrude(length = radius)
|> startProfile(at = [0, 0])
|> circle( radius = radius, center = segStart(tag) )
|> extrude(length = radius)
}
cylinder(radius = 1, tag = line1)

View File

@ -8,7 +8,7 @@ layout: manual
Compute the starting point of the provided line segment along the 'x' axis.
```kcl
segStartX(@tag: TagIdentifier): number
segStartX(@tag: tag): number(Length)
```
@ -17,11 +17,11 @@ segStartX(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples

View File

@ -8,7 +8,7 @@ layout: manual
Compute the starting point of the provided line segment along the 'y' axis.
```kcl
segStartY(@tag: TagIdentifier): number
segStartY(@tag: tag): number(Length)
```
@ -17,11 +17,11 @@ segStartY(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -32,7 +32,7 @@ exampleSketch = startSketchOn(XZ)
|> line(end = [20, 0])
|> line(end = [0, 3], tag = $thing)
|> line(end = [-10, 0])
|> line(end = [0, 20 - segStartY(thing)])
|> line(end = [0, 20-segStartY(thing)])
|> line(end = [-10, 0])
|> close()

View File

@ -8,7 +8,7 @@ layout: manual
Returns the angle coming out of the end of the segment in degrees.
```kcl
tangentToEnd(@tag: TagIdentifier): number
tangentToEnd(@tag: tag): number(Angle)
```
@ -17,11 +17,11 @@ tangentToEnd(@tag: TagIdentifier): number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`TagIdentifier`](/docs/kcl-lang/types#TagIdentifier) | The line segment being queried by its tag | Yes |
| [`tag`](/docs/kcl-std/types/std-types-tag) | [`tag`](/docs/kcl-std/types/std-types-tag) | The line segment being queried by its tag. | Yes |
### Returns
[`number`](/docs/kcl-std/types/std-types-number) - A number.
[`number(Angle)`](/docs/kcl-std/types/std-types-number) - A number.
### Examples
@ -32,7 +32,10 @@ pillSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [20, 0])
|> tangentialArc(end = [0, 10], tag = $arc1)
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|> angledLine(
angle = tangentToEnd(arc1),
length = 20,
)
|> tangentialArc(end = [0, -10])
|> close()
@ -47,7 +50,10 @@ pillSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [0, 20])
|> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|> angledLine(
angle = tangentToEnd(arc1),
length = 20,
)
|> tangentialArc(end = [-10, 0])
|> close()
@ -60,7 +66,10 @@ pillExtrude = extrude(pillSketch, length = 10)
rectangleSketch = startSketchOn(XZ)
|> startProfile(at = [0, 0])
|> line(end = [10, 0], tag = $seg1)
|> angledLine(angle = tangentToEnd(seg1), length = 10)
|> angledLine(
angle = tangentToEnd(seg1),
length = 10,
)
|> line(end = [0, 10])
|> line(end = [-20, 0])
|> close()
@ -73,7 +82,11 @@ rectangleExtrude = extrude(rectangleSketch, length = 10)
```kcl
bottom = startSketchOn(XY)
|> startProfile(at = [0, 0])
|> arc(endAbsolute = [10, 10], interiorAbsolute = [5, 1], tag = $arc1)
|> arc(
endAbsolute = [10, 10],
interiorAbsolute = [5, 1],
tag = $arc1,
)
|> angledLine(angle = tangentToEnd(arc1), length = 20)
|> close()
```
@ -82,7 +95,7 @@ bottom = startSketchOn(XY)
```kcl
circSketch = startSketchOn(XY)
|> circle(center = [0, 0], radius = 3, tag = $circ)
|> circle(center = [0, 0], radius= 3, tag = $circ)
triangleSketch = startSketchOn(XY)
|> startProfile(at = [-5, 0])

View File

@ -60,8 +60,8 @@ layout: manual
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
* [`lastSegX`](/docs/kcl-std/lastSegX)
* [`lastSegY`](/docs/kcl-std/lastSegY)
* [`lastSegX`](/docs/kcl-std/functions/std-sketch-lastSegX)
* [`lastSegY`](/docs/kcl-std/functions/std-sketch-lastSegY)
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/functions/std-sketch-loft)
* [`patternCircular2d`](/docs/kcl-std/functions/std-sketch-patternCircular2d)
@ -72,19 +72,19 @@ layout: manual
* [`profileStartX`](/docs/kcl-std/profileStartX)
* [`profileStartY`](/docs/kcl-std/profileStartY)
* [`revolve`](/docs/kcl-std/functions/std-sketch-revolve)
* [`segAng`](/docs/kcl-std/segAng)
* [`segEnd`](/docs/kcl-std/segEnd)
* [`segEndX`](/docs/kcl-std/segEndX)
* [`segEndY`](/docs/kcl-std/segEndY)
* [`segLen`](/docs/kcl-std/segLen)
* [`segStart`](/docs/kcl-std/segStart)
* [`segStartX`](/docs/kcl-std/segStartX)
* [`segStartY`](/docs/kcl-std/segStartY)
* [`segAng`](/docs/kcl-std/functions/std-sketch-segAng)
* [`segEnd`](/docs/kcl-std/functions/std-sketch-segEnd)
* [`segEndX`](/docs/kcl-std/functions/std-sketch-segEndX)
* [`segEndY`](/docs/kcl-std/functions/std-sketch-segEndY)
* [`segLen`](/docs/kcl-std/functions/std-sketch-segLen)
* [`segStart`](/docs/kcl-std/functions/std-sketch-segStart)
* [`segStartX`](/docs/kcl-std/functions/std-sketch-segStartX)
* [`segStartY`](/docs/kcl-std/functions/std-sketch-segStartY)
* [`startProfile`](/docs/kcl-std/startProfile)
* [`startSketchOn`](/docs/kcl-std/startSketchOn)
* [`subtract2d`](/docs/kcl-std/subtract2d)
* [`sweep`](/docs/kcl-std/functions/std-sketch-sweep)
* [`tangentToEnd`](/docs/kcl-std/tangentToEnd)
* [`tangentToEnd`](/docs/kcl-std/functions/std-sketch-tangentToEnd)
* [`tangentialArc`](/docs/kcl-std/tangentialArc)
* [`xLine`](/docs/kcl-std/xLine)
* [`yLine`](/docs/kcl-std/yLine)

View File

@ -25,8 +25,8 @@ This module contains functions for creating and manipulating sketches, and makin
* [`getOppositeEdge`](/docs/kcl-std/functions/std-sketch-getOppositeEdge)
* [`getPreviousAdjacentEdge`](/docs/kcl-std/functions/std-sketch-getPreviousAdjacentEdge)
* [`involuteCircular`](/docs/kcl-std/involuteCircular)
* [`lastSegX`](/docs/kcl-std/lastSegX)
* [`lastSegY`](/docs/kcl-std/lastSegY)
* [`lastSegX`](/docs/kcl-std/functions/std-sketch-lastSegX)
* [`lastSegY`](/docs/kcl-std/functions/std-sketch-lastSegY)
* [`line`](/docs/kcl-std/line)
* [`loft`](/docs/kcl-std/functions/std-sketch-loft)
* [`patternCircular2d`](/docs/kcl-std/functions/std-sketch-patternCircular2d)
@ -37,19 +37,19 @@ This module contains functions for creating and manipulating sketches, and makin
* [`profileStartX`](/docs/kcl-std/profileStartX)
* [`profileStartY`](/docs/kcl-std/profileStartY)
* [`revolve`](/docs/kcl-std/functions/std-sketch-revolve)
* [`segAng`](/docs/kcl-std/segAng)
* [`segEnd`](/docs/kcl-std/segEnd)
* [`segEndX`](/docs/kcl-std/segEndX)
* [`segEndY`](/docs/kcl-std/segEndY)
* [`segLen`](/docs/kcl-std/segLen)
* [`segStart`](/docs/kcl-std/segStart)
* [`segStartX`](/docs/kcl-std/segStartX)
* [`segStartY`](/docs/kcl-std/segStartY)
* [`segAng`](/docs/kcl-std/functions/std-sketch-segAng)
* [`segEnd`](/docs/kcl-std/functions/std-sketch-segEnd)
* [`segEndX`](/docs/kcl-std/functions/std-sketch-segEndX)
* [`segEndY`](/docs/kcl-std/functions/std-sketch-segEndY)
* [`segLen`](/docs/kcl-std/functions/std-sketch-segLen)
* [`segStart`](/docs/kcl-std/functions/std-sketch-segStart)
* [`segStartX`](/docs/kcl-std/functions/std-sketch-segStartX)
* [`segStartY`](/docs/kcl-std/functions/std-sketch-segStartY)
* [`startProfile`](/docs/kcl-std/startProfile)
* [`startSketchOn`](/docs/kcl-std/startSketchOn)
* [`subtract2d`](/docs/kcl-std/subtract2d)
* [`sweep`](/docs/kcl-std/functions/std-sketch-sweep)
* [`tangentToEnd`](/docs/kcl-std/tangentToEnd)
* [`tangentToEnd`](/docs/kcl-std/functions/std-sketch-tangentToEnd)
* [`tangentialArc`](/docs/kcl-std/tangentialArc)
* [`xLine`](/docs/kcl-std/xLine)
* [`yLine`](/docs/kcl-std/yLine)

File diff suppressed because it is too large Load Diff

View File

@ -1766,7 +1766,7 @@ loft001 = loft([sketch001, sketch002])
sketch001 = startSketchOn(YZ)
profile001 = circle(sketch001, center = [0, 0], radius = 500)
sketch002 = startSketchOn(XZ)
|> startProfile(at = [0, 0])
profile002 = startProfile(sketch002, at = [0, 0])
|> xLine(length = -500)
|> tangentialArc(endAbsolute = [-2000, 500])`,
},
@ -1782,7 +1782,7 @@ profile001 = startProfile(sketch001, at = [-400, -400])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(XZ)
|> startProfile(at = [0, 0])
profile002 = startProfile(sketch002, at = [0, 0])
|> xLine(length = -500)
|> tangentialArc(endAbsolute = [-2000, 500])`,
},
@ -1810,9 +1810,9 @@ sketch002 = startSketchOn(XZ)
testPoint.x - 50,
testPoint.y
)
const sweepDeclaration = 'sweep001 = sweep(profile001, path = sketch002)'
const sweepDeclaration = 'sweep001 = sweep(profile001, path = profile002)'
const editedSweepDeclaration =
'sweep001 = sweep(profile001, path = sketch002, sectional = true)'
'sweep001 = sweep(profile001, path = profile002, sectional = true)'
await test.step(`Look for sketch001`, async () => {
await toolbar.closePane('code')

View File

@ -2116,3 +2116,74 @@ test(
})
}
)
test(
'segment position changes persist after dragging and reopening project',
{ tag: '@desktop' },
async ({ scene, cmdBar, context, page, editor, toolbar }, testInfo) => {
const projectName = 'segment-drag-test'
await context.folderSetupFn(async (dir) => {
const projectDir = path.join(dir, projectName)
await fsp.mkdir(projectDir, { recursive: true })
await fsp.writeFile(
path.join(projectDir, 'main.kcl'),
`sketch001 = startSketchOn(XZ)
profile001 = startProfile(sketch001, at = [0, 0])
|> line(end = [0, 6])
|> line(end = [10, 0])
|> line(end = [-8, -5])
`
)
})
await page.setBodyDimensions({ width: 1200, height: 600 })
const u = await getUtils(page)
await test.step('Opening the project and entering sketch mode', async () => {
await expect(page.getByText(projectName)).toBeVisible()
await page.getByText(projectName).click()
await scene.settled(cmdBar)
// go to sketch mode
await (await toolbar.getFeatureTreeOperation('Sketch', 0)).dblclick()
// Without this, "add axis n grid" action runs after editing the sketch and invokes codeManager.writeToFile()
// so we wait for that action to run first before we start editing the sketch and making sure it's saving
// because of those edits.
await page.waitForTimeout(2000)
})
const changedLine = 'line(end = [-6.54, -4.99])'
await test.step('Dragging the line endpoint to modify it', async () => {
// Get the last line's endpoint position
const lineEnd = await u.getBoundingBox('[data-overlay-index="3"]')
await page.mouse.move(lineEnd.x, lineEnd.y - 5)
await page.mouse.down()
await page.mouse.move(lineEnd.x + 80, lineEnd.y)
await page.mouse.up()
await editor.expectEditor.toContain(changedLine)
// Exit sketch mode
await page.keyboard.press('Escape')
await page.waitForTimeout(100)
})
await test.step('Going back to dashboard', async () => {
await page.getByTestId('app-logo').click()
await page.waitForTimeout(1000)
})
await test.step('Reopening the project and verifying changes are saved', async () => {
await page.getByText(projectName).click()
await scene.settled(cmdBar)
// Check if new line coordinates were saved
await editor.expectEditor.toContain(changedLine)
})
}
)

View File

@ -2362,7 +2362,7 @@ profile004 = circleThreePoint(sketch001, p1 = [13.44, -6.8], p2 = [13.39, -2.07]
await test.step('add new profile', async () => {
await toolbar.rectangleBtn.click()
await page.waitForTimeout(100)
await page.waitForTimeout(200)
await rectStart()
await editor.expectEditor.toContain(
`profile005 = startProfile(sketch001, at = [15.68, -3.84])`

View File

@ -823,7 +823,7 @@ test('theme persists', async ({ page, context, homePage }) => {
uploadThroughput: -1,
})
await expect(networkToggle).toContainText('Connected')
await expect(networkToggle).toContainText('Network health (Strong)')
await expect(page.getByText('building scene')).not.toBeVisible()

View File

@ -14,8 +14,10 @@ test.describe('Test network related behaviors', () => {
'simulate network down and network little widget',
{ tag: '@skipLocalEngine' },
async ({ page, homePage }) => {
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const networkToggleConnectedText = page.getByText(
'Network health (Strong)'
)
const networkToggleWeakText = page.getByText('Network health (Ok)')
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -98,8 +100,10 @@ test.describe('Test network related behaviors', () => {
{ tag: '@skipLocalEngine' },
async ({ page, homePage, toolbar, scene, cmdBar }) => {
const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const networkToggleConnectedText = page.getByText(
'Network health (Strong)'
)
const networkToggleWeakText = page.getByText('Network health (Ok)')
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
@ -282,8 +286,10 @@ profile001 = startProfile(sketch001, at = [12.34, -12.34])
{ tag: ['@desktop', '@skipLocalEngine'] },
async ({ page, homePage, scene, cmdBar, toolbar, tronApp }) => {
const networkToggle = page.getByTestId('network-toggle')
const networkToggleConnectedText = page.getByText('Connected')
const networkToggleWeakText = page.getByText('Network health (Weak)')
const networkToggleConnectedText = page.getByText(
'Network health (Strong)'
)
const networkToggleWeakText = page.getByText('Network health (Ok)')
if (!tronApp) {
fail()

View File

@ -3,187 +3,250 @@ import { uuidv4 } from '@src/lib/utils'
import { getUtils } from '@e2e/playwright/test-utils'
import { expect, test } from '@e2e/playwright/zoo-test'
import type { Page } from '@playwright/test'
import type { SceneFixture } from '@e2e/playwright/fixtures/sceneFixture'
test.describe('Testing Camera Movement', () => {
test('Can move camera reliably', async ({
/**
* hack that we're implemented our own retry instead of using retries built into playwright.
* however each of these camera drags can be flaky, because of udp
* and so putting them together means only one needs to fail to make this test extra flaky.
* this way we can retry within the test
* We could break them out into separate tests, but the longest past of the test is waiting
* for the stream to start, so it can be good to bundle related things together.
*/
const bakeInRetries = async ({
mouseActions,
afterPosition,
beforePosition,
retryCount = 0,
page,
context,
homePage,
scene,
}: {
mouseActions: () => Promise<void>
beforePosition: [number, number, number]
afterPosition: [number, number, number]
retryCount?: number
page: Page
scene: SceneFixture
}) => {
const acceptableCamError = 5
const u = await getUtils(page)
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await scene.connectionEstablished()
await test.step('Set up initial camera position', async () =>
await scene.moveCameraTo({
x: beforePosition[0],
y: beforePosition[1],
z: beforePosition[2],
}))
await u.openAndClearDebugPanel()
await u.closeKclCodePanel()
const camPos: [number, number, number] = [0, 85, 85]
const bakeInRetries = async (
mouseActions: any,
xyz: [number, number, number],
cnt = 0
) => {
// hack that we're implemented our own retry instead of using retries built into playwright.
// however each of these camera drags can be flaky, because of udp
// and so putting them together means only one needs to fail to make this test extra flaky.
// this way we can retry within the test
// We could break them out into separate tests, but the longest past of the test is waiting
// for the stream to start, so it can be good to bundle related things together.
const camCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
up: { x: 0, y: 0, z: 1 },
},
}
const updateCamCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
await u.sendCustomCmd(camCommand)
await page.waitForTimeout(100)
await u.sendCustomCmd(updateCamCommand)
await page.waitForTimeout(100)
// rotate
await u.closeDebugPanel()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(100)
// const yo = page.getByTestId('cam-x-position').inputValue()
await u.doAndWaitForImageDiff(async () => {
await test.step('Do actions and watch for changes', async () =>
u.doAndWaitForImageDiff(async () => {
await mouseActions()
await u.openAndClearDebugPanel()
await u.closeDebugPanel()
await page.waitForTimeout(100)
}, 300)
}, 300))
await u.openAndClearDebugPanel()
await expect(page.getByTestId('cam-x-position')).toBeAttached()
const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(),
page.getByTestId('cam-z-position').inputValue(),
])
const errors = vals.map((v, i) => Math.abs(Number(v) - afterPosition[i]))
let shouldRetry = false
if (errors.some((e) => e > acceptableCamError)) {
if (retryCount > 2) {
console.log('xVal', vals[0], 'xError', errors[0])
console.log('yVal', vals[1], 'yError', errors[1])
console.log('zVal', vals[2], 'zError', errors[2])
throw new Error('Camera position not as expected', {
cause: {
vals,
errors,
},
})
}
shouldRetry = true
}
if (shouldRetry) {
await bakeInRetries({
mouseActions,
afterPosition: afterPosition,
beforePosition: beforePosition,
retryCount: retryCount + 1,
page,
scene,
})
}
}
test(
'Can pan and zoom camera reliably',
{
tag: '@web',
},
async ({ page, homePage, scene, cmdBar }) => {
const u = await getUtils(page)
const camInitialPosition: [number, number, number] = [0, 85, 85]
await homePage.goToModelingScene()
await scene.settled(cmdBar)
await u.openAndClearDebugPanel()
await page.getByTestId('cam-x-position').isVisible()
await u.closeKclCodePanel()
const vals = await Promise.all([
page.getByTestId('cam-x-position').inputValue(),
page.getByTestId('cam-y-position').inputValue(),
page.getByTestId('cam-z-position').inputValue(),
])
const xError = Math.abs(Number(vals[0]) + xyz[0])
const yError = Math.abs(Number(vals[1]) + xyz[1])
const zError = Math.abs(Number(vals[2]) + xyz[2])
await test.step('Pan', async () => {
await bakeInRetries({
mouseActions: async () => {
await page.keyboard.down('Shift')
await page.mouse.move(600, 200)
await page.mouse.down({ button: 'right' })
// Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
await page.mouse.move(700, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
await page.waitForTimeout(200)
},
afterPosition: [19, 85, 85],
beforePosition: camInitialPosition,
page,
scene,
})
})
let shouldRetry = false
await test.step('Zoom with click and drag', async () => {
await bakeInRetries({
mouseActions: async () => {
await page.keyboard.down('Control')
await page.mouse.move(700, 400)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 300)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
},
afterPosition: [0, 118, 118],
beforePosition: camInitialPosition,
page,
scene,
})
})
if (xError > 5 || yError > 5 || zError > 5) {
if (cnt > 2) {
console.log('xVal', vals[0], 'xError', xError)
console.log('yVal', vals[1], 'yError', yError)
console.log('zVal', vals[2], 'zError', zError)
throw new Error('Camera position not as expected')
await test.step('Zoom with scrollwheel', async () => {
const refreshCamValuesCmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
shouldRetry = true
}
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await page.waitForTimeout(100)
if (shouldRetry) await bakeInRetries(mouseActions, xyz, cnt + 1)
await bakeInRetries({
mouseActions: async () => {
await page.mouse.move(700, 400)
await page.mouse.wheel(0, -150)
// Scroll zooming doesn't update the debug pane's cam position values,
// so we have to force a refresh.
await u.openAndClearDebugPanel()
await u.sendCustomCmd(refreshCamValuesCmd)
await u.waitForCmdReceive('default_camera_get_settings')
await u.closeDebugPanel()
},
afterPosition: [0, 42.5, 42.5],
beforePosition: camInitialPosition,
page,
scene,
})
})
}
await bakeInRetries(async () => {
await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' })
await page.waitForTimeout(100)
)
const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
expect(appLogoBBox).not.toBeNull()
if (!appLogoBBox) throw new Error('app logo not found')
await page.mouse.move(
appLogoBBox.x + appLogoBBox.width / 2,
appLogoBBox.y + appLogoBBox.height / 2
)
await page.waitForTimeout(100)
await page.mouse.move(600, 303)
await page.waitForTimeout(100)
await page.mouse.up({ button: 'right' })
}, [4, -10.5, -120])
test(
'Can orbit camera reliably',
{
tag: '@web',
},
async ({ page, homePage, scene, cmdBar }) => {
const u = await getUtils(page)
const initialCamPosition: [number, number, number] = [0, 85, 85]
await bakeInRetries(async () => {
await page.keyboard.down('Shift')
await page.mouse.move(600, 200)
await page.mouse.down({ button: 'right' })
// Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
await page.mouse.move(700, 200)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Shift')
}, [-19, -85, -85])
await homePage.goToModelingScene()
// this turns on the debug pane setting as well
await scene.settled(cmdBar)
const camCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 0, y: 0, z: 0 },
vantage: { x: camPos[0], y: camPos[1], z: camPos[2] },
up: { x: 0, y: 0, z: 1 },
},
await u.openAndClearDebugPanel()
await u.closeKclCodePanel()
await test.step('Test orbit with spherical mode', async () => {
await bakeInRetries({
mouseActions: async () => {
await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' })
await page.waitForTimeout(100)
const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
expect(appLogoBBox).not.toBeNull()
if (!appLogoBBox) throw new Error('app logo not found')
await page.mouse.move(
appLogoBBox.x + appLogoBBox.width / 2,
appLogoBBox.y + appLogoBBox.height / 2
)
await page.waitForTimeout(100)
await page.mouse.move(600, 303)
await page.waitForTimeout(100)
await page.mouse.up({ button: 'right' })
},
afterPosition: [-4, 10.5, 120],
beforePosition: initialCamPosition,
page,
scene,
})
})
await test.step('Test orbit with trackball mode', async () => {
await test.step('Set orbitMode to trackball', async () => {
await cmdBar.openCmdBar()
await cmdBar.selectOption({ name: 'camera orbit' }).click()
await cmdBar.selectOption({ name: 'trackball' }).click()
await expect(
page.getByText(`camera orbit to "trackball"`)
).toBeVisible()
})
await bakeInRetries({
mouseActions: async () => {
await page.mouse.move(700, 200)
await page.mouse.down({ button: 'right' })
await page.waitForTimeout(100)
const appLogoBBox = await page.getByTestId('app-logo').boundingBox()
expect(appLogoBBox).not.toBeNull()
if (!appLogoBBox) {
throw new Error('app logo not found')
}
await page.mouse.move(
appLogoBBox.x + appLogoBBox.width / 2,
appLogoBBox.y + appLogoBBox.height / 2
)
await page.waitForTimeout(100)
await page.mouse.move(600, 303)
await page.waitForTimeout(100)
await page.mouse.up({ button: 'right' })
},
afterPosition: [18.06, -42.79, 110.87],
beforePosition: initialCamPosition,
page,
scene,
})
})
}
const updateCamCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
await u.sendCustomCmd(camCommand)
await page.waitForTimeout(100)
await u.sendCustomCmd(updateCamCommand)
await page.waitForTimeout(100)
await u.clearCommandLogs()
await u.closeDebugPanel()
await page.getByRole('button', { name: 'Start Sketch' }).click()
await page.waitForTimeout(200)
// zoom
await u.doAndWaitForImageDiff(async () => {
await page.keyboard.down('Control')
await page.mouse.move(700, 400)
await page.mouse.down({ button: 'right' })
await page.mouse.move(700, 300)
await page.mouse.up({ button: 'right' })
await page.keyboard.up('Control')
await u.openDebugPanel()
await page.waitForTimeout(300)
await u.clearCommandLogs()
await u.closeDebugPanel()
}, 300)
// zoom with scroll
await u.openAndClearDebugPanel()
// TODO, it appears we don't get the cam setting back from the engine when the interaction is zoom into `backInRetries` once the information is sent back on zoom
// await expect(Math.abs(Number(await page.getByTestId('cam-x-position').inputValue()) + 12)).toBeLessThan(1.5)
// await expect(Math.abs(Number(await page.getByTestId('cam-y-position').inputValue()) - 85)).toBeLessThan(1.5)
// await expect(Math.abs(Number(await page.getByTestId('cam-z-position').inputValue()) - 85)).toBeLessThan(1.5)
await page.getByRole('button', { name: 'Exit Sketch' }).click()
await bakeInRetries(async () => {
await page.mouse.move(700, 400)
await page.mouse.wheel(0, -100)
}, [0, -85, -85])
})
)
// TODO: fix after electron migration is merged
test('Zoom should be consistent when exiting or entering sketches', async ({

View File

@ -27,7 +27,7 @@ if len(modified_release_body) > max_length:
# Message to send to Discord
data = {
"content": textwrap.dedent(f'''
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/design-studio>
**{release_version}** is now available! Check out the latest features and improvements here: <https://zoo.dev/design-studio/download#added>
{modified_release_body}
'''),

View File

@ -11,24 +11,20 @@ filletRadius = 0.5
plateThickness = .5
centerHoleDiameter = 2
// Create a function that defines the body width and length of the mounting plate. Tag the corners so they can be passed through the fillet function.
fn rectShape(pos, w, l) {
rr = startSketchOn(XY)
|> startProfile(at = [pos[0] - (w / 2), pos[1] - (l / 2)])
|> line(endAbsolute = [pos[0] + w / 2, pos[1] - (l / 2)], tag = $edge1)
|> line(endAbsolute = [pos[0] + w / 2, pos[1] + l / 2], tag = $edge2)
|> line(endAbsolute = [pos[0] - (w / 2), pos[1] + l / 2], tag = $edge3)
|> close(tag = $edge4)
return rr
}
// Define the hole radius and x, y location constants
holeRadius = .25
holeIndex = .75
sketch001 = startSketchOn(XY)
rectShape = startProfile(sketch001, at = [-plateWidth / 2, plateLength / 2])
|> angledLine(angle = 0, length = plateWidth, tag = $basePlateEdge1)
|> angledLine(angle = segAng(basePlateEdge1) - 90, length = plateLength, tag = $basePlateEdge2)
|> angledLine(angle = segAng(basePlateEdge1), length = -segLen(basePlateEdge1), tag = $basePlateEdge3)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $basePlateEdge4)
|> close()
// Create the mounting plate extrusion, holes, and fillets
rs = rectShape(pos = [0, 0], w = plateWidth, l = plateLength)
part = rs
part = rectShape
|> subtract2d(tool = circle(
center = [
-plateWidth / 2 + holeIndex,
@ -62,9 +58,9 @@ part = rs
|> fillet(
radius = filletRadius,
tags = [
getPreviousAdjacentEdge(rs.tags.edge1),
getPreviousAdjacentEdge(rs.tags.edge2),
getPreviousAdjacentEdge(rs.tags.edge3),
getPreviousAdjacentEdge(rs.tags.edge4)
getCommonEdge(faces = [basePlateEdge3, basePlateEdge2]),
getCommonEdge(faces = [basePlateEdge4, basePlateEdge3]),
getCommonEdge(faces = [basePlateEdge4, basePlateEdge1]),
getCommonEdge(faces = [basePlateEdge2, basePlateEdge1])
],
)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

47
rust/Cargo.lock generated
View File

@ -627,26 +627,22 @@ dependencies = [
[[package]]
name = "criterion"
version = "0.5.1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f"
checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
dependencies = [
"anes",
"cast",
"ciborium",
"clap",
"criterion-plot",
"futures",
"is-terminal",
"itertools 0.10.5",
"itertools 0.13.0",
"num-traits 0.2.19",
"once_cell",
"oorandom",
"plotters",
"rayon",
"regex",
"serde",
"serde_derive",
"serde_json",
"tinytemplate",
"tokio",
@ -715,9 +711,9 @@ dependencies = [
[[package]]
name = "csscolorparser"
version = "0.7.0"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f9a16a848a7fb95dd47ce387ac1ee9a6df879ba784b815537fcd388a1a8288"
checksum = "5fda6aace1fbef3aa217b27f4c8d7d071ef2a70a5ca51050b1f17d40299d3f16"
dependencies = [
"phf",
]
@ -1815,7 +1811,7 @@ dependencies = [
[[package]]
name = "kcl-bumper"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"anyhow",
"clap",
@ -1826,7 +1822,7 @@ dependencies = [
[[package]]
name = "kcl-derive-docs"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"Inflector",
"anyhow",
@ -1845,7 +1841,7 @@ dependencies = [
[[package]]
name = "kcl-directory-test-macro"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"convert_case",
"proc-macro2",
@ -1855,7 +1851,7 @@ dependencies = [
[[package]]
name = "kcl-language-server"
version = "0.2.77"
version = "0.2.79"
dependencies = [
"anyhow",
"clap",
@ -1876,7 +1872,7 @@ dependencies = [
[[package]]
name = "kcl-language-server-release"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"anyhow",
"clap",
@ -1896,7 +1892,7 @@ dependencies = [
[[package]]
name = "kcl-lib"
version = "0.2.77"
version = "0.2.79"
dependencies = [
"anyhow",
"approx 0.5.1",
@ -1973,7 +1969,7 @@ dependencies = [
[[package]]
name = "kcl-python-bindings"
version = "0.3.77"
version = "0.3.79"
dependencies = [
"anyhow",
"kcl-lib",
@ -1988,7 +1984,7 @@ dependencies = [
[[package]]
name = "kcl-test-server"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"anyhow",
"hyper 0.14.32",
@ -2001,7 +1997,7 @@ dependencies = [
[[package]]
name = "kcl-to-core"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"anyhow",
"async-trait",
@ -2015,7 +2011,7 @@ dependencies = [
[[package]]
name = "kcl-wasm-lib"
version = "0.1.77"
version = "0.1.79"
dependencies = [
"anyhow",
"bson",
@ -4200,9 +4196,9 @@ dependencies = [
[[package]]
name = "tokio-tungstenite"
version = "0.24.0"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
checksum = "7a9daff607c6d2bf6c16fd681ccb7eecc83e4e2cdc1ca067ffaadfca5de7f084"
dependencies = [
"futures-util",
"log",
@ -4456,21 +4452,20 @@ dependencies = [
[[package]]
name = "tungstenite"
version = "0.24.0"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
checksum = "4793cb5e56680ecbb1d843515b23b6de9a75eb04b66643e256a396d43be33c13"
dependencies = [
"byteorder",
"bytes",
"data-encoding",
"http 1.3.1",
"httparse",
"log",
"rand 0.8.5",
"rand 0.9.0",
"rustls",
"rustls-pki-types",
"sha1",
"thiserror 1.0.69",
"thiserror 2.0.12",
"utf-8",
]

View File

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

View File

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

View File

@ -134,6 +134,21 @@ pub const TEST_NAMES: &[&str] = &[
"std-sketch-patternLinear2d-1",
"std-sketch-patternCircular2d-0",
"std-sketch-circleThreePoint-0",
"std-sketch-segStart-0",
"std-sketch-segStartX-0",
"std-sketch-segStartY-0",
"std-sketch-segEnd-0",
"std-sketch-segEndX-0",
"std-sketch-segEndY-0",
"std-sketch-lastSegX-0",
"std-sketch-lastSegY-0",
"std-sketch-segAng-0",
"std-sketch-segLen-0",
"std-sketch-tangentToEnd-0",
"std-sketch-tangentToEnd-1",
"std-sketch-tangentToEnd-2",
"std-sketch-tangentToEnd-3",
"std-sketch-tangentToEnd-4",
"std-solid-appearance-0",
"std-solid-appearance-1",
"std-solid-appearance-2",

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
[package]
name = "kcl-lib"
description = "KittyCAD Language implementation and tools"
version = "0.2.77"
version = "0.2.79"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
@ -31,7 +31,7 @@ clap = { version = "4.5.36", default-features = false, optional = true, features
"derive",
] }
convert_case = "0.8.0"
csscolorparser = "0.7.0"
csscolorparser = "0.7.2"
dashmap = { workspace = true }
dhat = { version = "0.3", optional = true }
fnv = "1.0.7"
@ -107,7 +107,7 @@ web-sys = { version = "0.3.76", features = ["console"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
instant = "0.1.13"
tokio = { workspace = true, features = ["full"] }
tokio-tungstenite = { version = "0.24.0", features = [
tokio-tungstenite = { version = "0.26.2", features = [
"rustls-tls-native-roots",
] }
tower-lsp = { workspace = true, features = ["proposed", "default"] }
@ -130,7 +130,7 @@ tabled = ["dep:tabled"]
[dev-dependencies]
approx = "0.5"
base64 = "0.22.1"
criterion = { version = "0.5.1", features = ["async_tokio"] }
criterion = { version = "0.6.0", features = ["async_tokio"] }
expectorate = "1.1.0"
handlebars = "6.3.2"
image = { version = "0.25.6", default-features = false, features = ["png"] }

View File

@ -1,9 +1,10 @@
use std::{
fs,
hint::black_box,
path::{Path, PathBuf},
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use criterion::{criterion_group, criterion_main, Criterion};
const IGNORE_DIRS: [&str; 2] = ["step", "screenshots"];

View File

@ -1,4 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, Criterion};
pub fn bench_parse(c: &mut Criterion) {
for (name, file) in [

View File

@ -1,4 +1,6 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use std::hint::black_box;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use kcl_lib::kcl_lsp_server;
use tokio::runtime::Runtime;
use tower_lsp::LanguageServer;

View File

@ -67,6 +67,7 @@ pub struct TcpRead {
/// Occurs when client couldn't read from the WebSocket to the engine.
// #[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum WebSocketReadError {
/// Could not read a message due to WebSocket errors.
Read(tokio_tungstenite::tungstenite::Error),
@ -206,7 +207,7 @@ impl EngineConnection {
async fn inner_send_to_engine(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
let msg = serde_json::to_string(&request).map_err(|e| anyhow!("could not serialize json: {e}"))?;
tcp_write
.send(WsMsg::Text(msg))
.send(WsMsg::Text(msg.into()))
.await
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
Ok(())
@ -216,19 +217,17 @@ impl EngineConnection {
async fn inner_send_to_engine_binary(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
let msg = bson::to_vec(&request).map_err(|e| anyhow!("could not serialize bson: {e}"))?;
tcp_write
.send(WsMsg::Binary(msg))
.send(WsMsg::Binary(msg.into()))
.await
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
Ok(())
}
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig {
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
// 4294967296 bytes, which is around 4.2 GB.
max_message_size: Some(usize::MAX),
max_frame_size: Some(usize::MAX),
..Default::default()
};
.max_message_size(Some(usize::MAX))
.max_frame_size(Some(usize::MAX));
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
ws,
@ -439,7 +438,7 @@ impl EngineManager for EngineConnection {
request_sent: tx,
})
.await
.map_err(|e| KclError::Engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(format!("Failed to send debug: {}", e), vec![])))?;
let _ = rx.await;
Ok(())
@ -474,7 +473,7 @@ impl EngineManager for EngineConnection {
})
.await
.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to send modeling command: {}", e),
vec![source_range],
))
@ -483,13 +482,13 @@ impl EngineManager for EngineConnection {
// Wait for the request to be sent.
rx.await
.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("could not send request to the engine actor: {e}"),
vec![source_range],
))
})?
.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("could not send request to the engine: {e}"),
vec![source_range],
))
@ -516,12 +515,12 @@ 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::new(
return Err(KclError::new_engine(KclErrorDetails::new(
pe.join(", ").to_string(),
vec![source_range],
)));
} else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
"Modeling command failed: websocket closed early".to_string(),
vec![source_range],
)));
@ -543,7 +542,7 @@ impl EngineManager for EngineConnection {
}
}
Err(KclError::Engine(KclErrorDetails::new(
Err(KclError::new_engine(KclErrorDetails::new(
format!("Modeling command timed out `{}`", id),
vec![source_range],
)))

View File

@ -147,19 +147,19 @@ 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::new(
KclError::new_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::new(
KclError::new_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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to serialize id to source range: {:?}", e),
vec![source_range],
))
@ -167,7 +167,7 @@ impl EngineConnection {
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::new(e.to_string().into(), vec![source_range])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
Ok(())
}
@ -180,19 +180,19 @@ 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::new(
KclError::new_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::new(
KclError::new_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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to serialize id to source range: {:?}", e),
vec![source_range],
))
@ -201,7 +201,7 @@ impl EngineConnection {
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::new(e.to_string().into(), vec![source_range])))?;
.map_err(|e| KclError::new_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.
@ -209,7 +209,7 @@ impl EngineConnection {
if let Ok(kittycad_modeling_cmds::websocket::FailureWebSocketResponse { errors, .. }) =
serde_json::from_str(&err_str)
{
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range],
))
@ -218,7 +218,7 @@ impl EngineConnection {
{
if let Some(data) = data.first() {
// It could also be an array of responses.
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
data.errors
.iter()
.map(|e| e.message.clone())
@ -227,13 +227,13 @@ impl EngineConnection {
vec![source_range],
))
} else {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
"Received empty response from engine".into(),
vec![source_range],
))
}
} else {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to wait for promise from send modeling command: {:?}", e),
vec![source_range],
))
@ -241,7 +241,7 @@ impl EngineConnection {
})?;
if value.is_null() || value.is_undefined() {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
"Received null or undefined response from engine".into(),
vec![source_range],
)));
@ -251,7 +251,7 @@ impl EngineConnection {
let data = js_sys::Uint8Array::from(value);
let ws_result: WebSocketResponse = bson::from_slice(&data.to_vec()).map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to deserialize bson response from engine: {:?}", e),
vec![source_range],
))
@ -308,10 +308,10 @@ impl crate::engine::EngineManager for EngineConnection {
let promise = self
.manager
.start_new_session()
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
crate::wasm::JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to wait for promise from start new session: {:?}", e),
vec![source_range],
))

View File

@ -276,7 +276,7 @@ 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::new(
KclError::new_internal(KclErrorDetails::new(
format!("Failed to sleep: {:?}", err),
vec![source_range],
))
@ -293,7 +293,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
return Ok(response);
}
Err(KclError::Engine(KclErrorDetails::new(
Err(KclError::new_engine(KclErrorDetails::new(
"async command timed out".to_string(),
vec![source_range],
)))
@ -547,7 +547,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
id_to_source_range.insert(Uuid::from(*cmd_id), *range);
}
_ => {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!("The request is not a modeling command: {:?}", req),
vec![*range],
)));
@ -595,7 +595,7 @@ 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::new(
Err(KclError::new_engine(KclErrorDetails::new(
format!("Failed to get batch response: {:?}", response),
vec![source_range],
)))
@ -610,7 +610,7 @@ 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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![],
))
@ -620,7 +620,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
.await?;
self.parse_websocket_response(ws_resp, source_range)
}
_ => Err(KclError::Engine(KclErrorDetails::new(
_ => Err(KclError::new_engine(KclErrorDetails::new(
format!("The final request is not a modeling command: {:?}", final_req),
vec![source_range],
))),
@ -729,7 +729,7 @@ 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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to get default plane info for: {:?}", name),
vec![source_range],
))
@ -763,7 +763,7 @@ 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::new(
Err(KclError::new_engine(KclErrorDetails::new(
fail.errors
.iter()
.map(|e| e.message.clone())
@ -805,12 +805,12 @@ 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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to get source range for command ID: {:?}", cmd_id),
vec![],
))
})?;
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
errors.iter().map(|e| e.message.clone()).collect::<Vec<_>>().join("\n"),
vec![source_range],
)));
@ -820,7 +820,7 @@ pub trait EngineManager: std::fmt::Debug + Send + Sync + 'static {
// 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::new(
Err(KclError::new_engine(KclErrorDetails::new(
format!("Failed to find response for command ID: {:?}", id),
vec![],
)))

View File

@ -91,30 +91,33 @@ pub enum ConnectionError {
#[ts(export)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum KclError {
#[error("lexical: {0:?}")]
Lexical(KclErrorDetails),
#[error("syntax: {0:?}")]
Syntax(KclErrorDetails),
#[error("semantic: {0:?}")]
Semantic(KclErrorDetails),
#[error("import cycle: {0:?}")]
ImportCycle(KclErrorDetails),
#[error("type: {0:?}")]
Type(KclErrorDetails),
#[error("i/o: {0:?}")]
Io(KclErrorDetails),
#[error("unexpected: {0:?}")]
Unexpected(KclErrorDetails),
#[error("value already defined: {0:?}")]
ValueAlreadyDefined(KclErrorDetails),
#[error("undefined value: {0:?}")]
UndefinedValue(KclErrorDetails),
#[error("invalid expression: {0:?}")]
InvalidExpression(KclErrorDetails),
#[error("engine: {0:?}")]
Engine(KclErrorDetails),
#[error("internal error, please report to KittyCAD team: {0:?}")]
Internal(KclErrorDetails),
#[error("lexical: {details:?}")]
Lexical { details: KclErrorDetails },
#[error("syntax: {details:?}")]
Syntax { details: KclErrorDetails },
#[error("semantic: {details:?}")]
Semantic { details: KclErrorDetails },
#[error("import cycle: {details:?}")]
ImportCycle { details: KclErrorDetails },
#[error("type: {details:?}")]
Type { details: KclErrorDetails },
#[error("i/o: {details:?}")]
Io { details: KclErrorDetails },
#[error("unexpected: {details:?}")]
Unexpected { details: KclErrorDetails },
#[error("value already defined: {details:?}")]
ValueAlreadyDefined { details: KclErrorDetails },
#[error("undefined value: {details:?}")]
UndefinedValue {
details: KclErrorDetails,
name: Option<String>,
},
#[error("invalid expression: {details:?}")]
InvalidExpression { details: KclErrorDetails },
#[error("engine: {details:?}")]
Engine { details: KclErrorDetails },
#[error("internal error, please report to KittyCAD team: {details:?}")]
Internal { details: KclErrorDetails },
}
impl From<KclErrorWithOutputs> for KclError {
@ -296,18 +299,18 @@ pub struct ReportWithOutputs {
impl miette::Diagnostic for ReportWithOutputs {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
let family = match self.error.error {
KclError::Lexical(_) => "Lexical",
KclError::Syntax(_) => "Syntax",
KclError::Semantic(_) => "Semantic",
KclError::ImportCycle(_) => "ImportCycle",
KclError::Type(_) => "Type",
KclError::Io(_) => "I/O",
KclError::Unexpected(_) => "Unexpected",
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
KclError::UndefinedValue(_) => "UndefinedValue",
KclError::InvalidExpression(_) => "InvalidExpression",
KclError::Engine(_) => "Engine",
KclError::Internal(_) => "Internal",
KclError::Lexical { .. } => "Lexical",
KclError::Syntax { .. } => "Syntax",
KclError::Semantic { .. } => "Semantic",
KclError::ImportCycle { .. } => "ImportCycle",
KclError::Type { .. } => "Type",
KclError::Io { .. } => "I/O",
KclError::Unexpected { .. } => "Unexpected",
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
KclError::UndefinedValue { .. } => "UndefinedValue",
KclError::InvalidExpression { .. } => "InvalidExpression",
KclError::Engine { .. } => "Engine",
KclError::Internal { .. } => "Internal",
};
let error_string = format!("KCL {family} error");
Some(Box::new(error_string))
@ -346,18 +349,18 @@ pub struct Report {
impl miette::Diagnostic for Report {
fn code<'a>(&'a self) -> Option<Box<dyn std::fmt::Display + 'a>> {
let family = match self.error {
KclError::Lexical(_) => "Lexical",
KclError::Syntax(_) => "Syntax",
KclError::Semantic(_) => "Semantic",
KclError::ImportCycle(_) => "ImportCycle",
KclError::Type(_) => "Type",
KclError::Io(_) => "I/O",
KclError::Unexpected(_) => "Unexpected",
KclError::ValueAlreadyDefined(_) => "ValueAlreadyDefined",
KclError::UndefinedValue(_) => "UndefinedValue",
KclError::InvalidExpression(_) => "InvalidExpression",
KclError::Engine(_) => "Engine",
KclError::Internal(_) => "Internal",
KclError::Lexical { .. } => "Lexical",
KclError::Syntax { .. } => "Syntax",
KclError::Semantic { .. } => "Semantic",
KclError::ImportCycle { .. } => "ImportCycle",
KclError::Type { .. } => "Type",
KclError::Io { .. } => "I/O",
KclError::Unexpected { .. } => "Unexpected",
KclError::ValueAlreadyDefined { .. } => "ValueAlreadyDefined",
KclError::UndefinedValue { .. } => "UndefinedValue",
KclError::InvalidExpression { .. } => "InvalidExpression",
KclError::Engine { .. } => "Engine",
KclError::Internal { .. } => "Internal",
};
let error_string = format!("KCL {family} error");
Some(Box::new(error_string))
@ -410,11 +413,53 @@ impl KclErrorDetails {
impl KclError {
pub fn internal(message: String) -> KclError {
KclError::Internal(KclErrorDetails {
source_ranges: Default::default(),
backtrace: Default::default(),
message,
})
KclError::Internal {
details: KclErrorDetails {
source_ranges: Default::default(),
backtrace: Default::default(),
message,
},
}
}
pub fn new_internal(details: KclErrorDetails) -> KclError {
KclError::Internal { details }
}
pub fn new_import_cycle(details: KclErrorDetails) -> KclError {
KclError::ImportCycle { details }
}
pub fn new_semantic(details: KclErrorDetails) -> KclError {
KclError::Semantic { details }
}
pub fn new_value_already_defined(details: KclErrorDetails) -> KclError {
KclError::ValueAlreadyDefined { details }
}
pub fn new_syntax(details: KclErrorDetails) -> KclError {
KclError::Syntax { details }
}
pub fn new_io(details: KclErrorDetails) -> KclError {
KclError::Io { details }
}
pub fn new_engine(details: KclErrorDetails) -> KclError {
KclError::Engine { details }
}
pub fn new_lexical(details: KclErrorDetails) -> KclError {
KclError::Lexical { details }
}
pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
KclError::UndefinedValue { details, name }
}
pub fn new_type(details: KclErrorDetails) -> KclError {
KclError::Type { details }
}
/// Get the error message.
@ -424,88 +469,88 @@ impl KclError {
pub fn error_type(&self) -> &'static str {
match self {
KclError::Lexical(_) => "lexical",
KclError::Syntax(_) => "syntax",
KclError::Semantic(_) => "semantic",
KclError::ImportCycle(_) => "import cycle",
KclError::Type(_) => "type",
KclError::Io(_) => "i/o",
KclError::Unexpected(_) => "unexpected",
KclError::ValueAlreadyDefined(_) => "value already defined",
KclError::UndefinedValue(_) => "undefined value",
KclError::InvalidExpression(_) => "invalid expression",
KclError::Engine(_) => "engine",
KclError::Internal(_) => "internal",
KclError::Lexical { .. } => "lexical",
KclError::Syntax { .. } => "syntax",
KclError::Semantic { .. } => "semantic",
KclError::ImportCycle { .. } => "import cycle",
KclError::Type { .. } => "type",
KclError::Io { .. } => "i/o",
KclError::Unexpected { .. } => "unexpected",
KclError::ValueAlreadyDefined { .. } => "value already defined",
KclError::UndefinedValue { .. } => "undefined value",
KclError::InvalidExpression { .. } => "invalid expression",
KclError::Engine { .. } => "engine",
KclError::Internal { .. } => "internal",
}
}
pub fn source_ranges(&self) -> Vec<SourceRange> {
match &self {
KclError::Lexical(e) => e.source_ranges.clone(),
KclError::Syntax(e) => e.source_ranges.clone(),
KclError::Semantic(e) => e.source_ranges.clone(),
KclError::ImportCycle(e) => e.source_ranges.clone(),
KclError::Type(e) => e.source_ranges.clone(),
KclError::Io(e) => e.source_ranges.clone(),
KclError::Unexpected(e) => e.source_ranges.clone(),
KclError::ValueAlreadyDefined(e) => e.source_ranges.clone(),
KclError::UndefinedValue(e) => e.source_ranges.clone(),
KclError::InvalidExpression(e) => e.source_ranges.clone(),
KclError::Engine(e) => e.source_ranges.clone(),
KclError::Internal(e) => e.source_ranges.clone(),
KclError::Lexical { details: e } => e.source_ranges.clone(),
KclError::Syntax { details: e } => e.source_ranges.clone(),
KclError::Semantic { details: e } => e.source_ranges.clone(),
KclError::ImportCycle { details: e } => e.source_ranges.clone(),
KclError::Type { details: e } => e.source_ranges.clone(),
KclError::Io { details: e } => e.source_ranges.clone(),
KclError::Unexpected { details: e } => e.source_ranges.clone(),
KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
KclError::Engine { details: e } => e.source_ranges.clone(),
KclError::Internal { details: e } => e.source_ranges.clone(),
}
}
/// Get the inner error message.
pub fn message(&self) -> &str {
match &self {
KclError::Lexical(e) => &e.message,
KclError::Syntax(e) => &e.message,
KclError::Semantic(e) => &e.message,
KclError::ImportCycle(e) => &e.message,
KclError::Type(e) => &e.message,
KclError::Io(e) => &e.message,
KclError::Unexpected(e) => &e.message,
KclError::ValueAlreadyDefined(e) => &e.message,
KclError::UndefinedValue(e) => &e.message,
KclError::InvalidExpression(e) => &e.message,
KclError::Engine(e) => &e.message,
KclError::Internal(e) => &e.message,
KclError::Lexical { details: e } => &e.message,
KclError::Syntax { details: e } => &e.message,
KclError::Semantic { details: e } => &e.message,
KclError::ImportCycle { details: e } => &e.message,
KclError::Type { details: e } => &e.message,
KclError::Io { details: e } => &e.message,
KclError::Unexpected { details: e } => &e.message,
KclError::ValueAlreadyDefined { details: e } => &e.message,
KclError::UndefinedValue { details: e, .. } => &e.message,
KclError::InvalidExpression { details: e } => &e.message,
KclError::Engine { details: e } => &e.message,
KclError::Internal { details: e } => &e.message,
}
}
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(),
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::Engine { details: e }
| KclError::Internal { details: 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)
| 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) => {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::Engine { details: e }
| KclError::Internal { details: e } => {
e.backtrace = source_ranges
.iter()
.map(|s| BacktraceItem {
@ -523,18 +568,18 @@ impl KclError {
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)
| 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) => {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::Engine { details: e }
| KclError::Internal { details: e } => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
@ -547,18 +592,18 @@ impl KclError {
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) => {
KclError::Lexical { details: e }
| KclError::Syntax { details: e }
| KclError::Semantic { details: e }
| KclError::ImportCycle { details: e }
| KclError::Type { details: e }
| KclError::Io { details: e }
| KclError::Unexpected { details: e }
| KclError::ValueAlreadyDefined { details: e }
| KclError::UndefinedValue { details: e, .. }
| KclError::InvalidExpression { details: e }
| KclError::Engine { details: e }
| KclError::Internal { details: e } => {
if let Some(item) = e.backtrace.last_mut() {
item.fn_name = last_fn_name;
}
@ -645,7 +690,7 @@ impl From<String> for KclError {
#[cfg(feature = "pyo3")]
impl From<pyo3::PyErr> for KclError {
fn from(error: pyo3::PyErr) -> Self {
KclError::Internal(KclErrorDetails {
KclError::new_internal(KclErrorDetails {
source_ranges: vec![],
backtrace: Default::default(),
message: error.to_string(),

View File

@ -70,7 +70,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Empty `{for_key}` annotation"),
vec![annotation.as_source_range()],
))
@ -84,7 +84,7 @@ pub(super) fn expect_ident(expr: &Expr) -> Result<&str, KclError> {
}
}
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
"Unexpected settings value, expected a simple name, e.g., `mm`".to_owned(),
vec![expr.into()],
)))
@ -98,7 +98,7 @@ pub(super) fn expect_number(expr: &Expr) -> Result<String, KclError> {
}
}
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
"Unexpected settings value, expected a number, e.g., `1.0`".to_owned(),
vec![expr.into()],
)))
@ -113,7 +113,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"Invalid value for {} attribute, expected one of: {}",
IMPL,
@ -139,7 +139,7 @@ impl UnitLen {
"inch" | "in" => Ok(UnitLen::Inches),
"ft" => Ok(UnitLen::Feet),
"yd" => Ok(UnitLen::Yards),
value => Err(KclError::Semantic(KclErrorDetails::new(
value => Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"
),
@ -154,7 +154,7 @@ impl UnitAngle {
match s {
"deg" => Ok(UnitAngle::Degrees),
"rad" => Ok(UnitAngle::Radians),
value => Err(KclError::Semantic(KclErrorDetails::new(
value => Err(KclError::new_semantic(KclErrorDetails::new(
format!("Unexpected value for angle units: `{value}`; expected one of `deg`, `rad`"),
vec![source_range],
))),

View File

@ -24,7 +24,7 @@ macro_rules! internal_error {
($range:expr, $($rest:tt)*) => {{
let message = format!($($rest)*);
debug_assert!(false, "{}", &message);
return Err(KclError::Internal(KclErrorDetails::new(message, vec![$range])));
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![$range])));
}};
}
@ -949,7 +949,7 @@ 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::new(
KclError::new_internal(KclErrorDetails::new(
format!("Expected a current plane ID when processing StartPath command, but we have none: {id:?}"),
vec![range],
))
@ -1137,7 +1137,7 @@ 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::new(
KclError::new_internal(KclErrorDetails::new(
format!("Expected at least one section ID in Loft command: {id:?}; cmd={cmd:?}"),
vec![range],
))
@ -1180,7 +1180,7 @@ fn artifacts_to_update(
};
last_path = Some(path);
let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!(
"Expected a sweep ID on the path when processing Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
),
@ -1234,7 +1234,7 @@ fn artifacts_to_update(
continue;
};
let path_sweep_id = path.sweep_id.ok_or_else(|| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!(
"Expected a sweep ID on the path when processing last path's Solid3dGetExtrusionFaceInfo command, but we have none: {id:?}, {path:?}"
),

View File

@ -131,7 +131,7 @@ impl ExecutorContext {
match statement {
BodyItem::ImportStatement(import_stmt) => {
if !matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Imports are only supported at the top-level of a file.".to_owned(),
vec![import_stmt.into()],
)));
@ -164,15 +164,18 @@ impl ExecutorContext {
let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
if value.is_err() && ty.is_err() && mod_value.is_err() {
return Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("{} is not defined in module", import_item.name.name),
vec![SourceRange::from(&import_item.name)],
)));
return Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("{} is not defined in module", import_item.name.name),
vec![SourceRange::from(&import_item.name)],
),
None,
));
}
// 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::new(
value = Err(KclError::new_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
@ -182,7 +185,7 @@ impl ExecutorContext {
}
if ty.is_ok() && !module_exports.contains(&ty_name) {
ty = Err(KclError::Semantic(KclErrorDetails::new(format!(
ty = Err(KclError::new_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
),
@ -190,7 +193,7 @@ impl ExecutorContext {
}
if mod_value.is_ok() && !module_exports.contains(&mod_name) {
mod_value = Err(KclError::Semantic(KclErrorDetails::new(format!(
mod_value = Err(KclError::new_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
),
@ -253,7 +256,7 @@ impl ExecutorContext {
.memory
.get_from(name, env_ref, source_range, 0)
.map_err(|_err| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("{} is not defined in module (but was exported?)", name),
vec![source_range],
))
@ -301,7 +304,11 @@ impl ExecutorContext {
let annotations = &variable_declaration.outer_attrs;
let value = self
// During the evaluation of the variable's RHS, set context that this is all happening inside a variable
// declaration, for the given name. This helps improve user-facing error messages.
let lhs = variable_declaration.inner.name().to_owned();
exec_state.mod_local.being_declared = Some(lhs);
let rhs_result = self
.execute_expr(
&variable_declaration.declaration.init,
exec_state,
@ -309,10 +316,14 @@ impl ExecutorContext {
annotations,
StatementKind::Declaration { name: &var_name },
)
.await?;
.await;
// Declaration over, so unset this context.
exec_state.mod_local.being_declared = None;
let rhs = rhs_result?;
exec_state
.mut_stack()
.add(var_name.clone(), value.clone(), source_range)?;
.add(var_name.clone(), rhs.clone(), source_range)?;
// Track exports.
if let ItemVisibility::Export = variable_declaration.visibility {
@ -326,7 +337,7 @@ impl ExecutorContext {
}
}
// Variable declaration can be the return value of a module.
last_expr = matches!(body_type, BodyType::Root).then_some(value);
last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
}
BodyItem::TypeDeclaration(ty) => {
let metadata = Metadata::from(&**ty);
@ -336,7 +347,7 @@ impl ExecutorContext {
let std_path = match &exec_state.mod_local.path {
ModulePath::Std { value } => value,
ModulePath::Local { .. } | ModulePath::Main => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range],
)));
@ -352,7 +363,7 @@ impl ExecutorContext {
.mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range],
))
@ -373,7 +384,7 @@ impl ExecutorContext {
exec_state,
metadata.source_range,
)
.map_err(|e| KclError::Semantic(e.into()))?,
.map_err(|e| KclError::new_semantic(e.into()))?,
),
meta: vec![metadata],
};
@ -382,7 +393,7 @@ impl ExecutorContext {
.mut_stack()
.add(name_in_mem.clone(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Redefinition of type {}.", ty.name.name),
vec![metadata.source_range],
))
@ -393,7 +404,7 @@ impl ExecutorContext {
}
}
None => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"User-defined types are not yet supported.".to_owned(),
vec![metadata.source_range],
)))
@ -407,7 +418,7 @@ impl ExecutorContext {
let metadata = Metadata::from(return_statement);
if matches!(body_type, BodyType::Root) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Cannot return from outside a function.".to_owned(),
vec![metadata.source_range],
)));
@ -426,7 +437,7 @@ impl ExecutorContext {
.mut_stack()
.add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
.map_err(|_| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
"Multiple returns from a single function.".to_owned(),
vec![metadata.source_range],
))
@ -531,7 +542,7 @@ impl ExecutorContext {
*cache = Some((val, er, items.clone()));
(er, items)
}),
ModuleRepr::Foreign(geom, _) => Err(KclError::Semantic(KclErrorDetails::new(
ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
"Cannot import items from foreign modules".to_owned(),
vec![geom.source_range],
))),
@ -605,12 +616,12 @@ impl ExecutorContext {
exec_state.global.mod_loader.leave_module(path);
result.map_err(|err| {
if let KclError::ImportCycle(_) = err {
if let KclError::ImportCycle { .. } = err {
// It was an import cycle. Keep the original message.
err.override_source_ranges(vec![source_range])
} else {
// TODO would be great to have line/column for the underlying error here
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"Error loading imported file ({path}). Open it to view more details.\n {}",
err.message()
@ -635,7 +646,12 @@ impl ExecutorContext {
Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
Expr::Name(name) => {
let value = name.get_result(exec_state, self).await?.clone();
let being_declared = exec_state.mod_local.being_declared.clone();
let value = name
.get_result(exec_state, self)
.await
.map_err(|e| var_in_own_ref_err(e, &being_declared))?
.clone();
if let KclValue::Module { value: module_id, meta } = value {
self.exec_module_for_result(
module_id,
@ -677,7 +693,7 @@ impl ExecutorContext {
meta: vec![metadata.to_owned()],
}
} else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Rust implementation of functions is restricted to the standard library".to_owned(),
vec![metadata.source_range],
)));
@ -704,7 +720,7 @@ impl ExecutorContext {
"you cannot declare variable {name} as %, because % can only be used in function calls"
);
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
message,
vec![pipe_substitution.into()],
)));
@ -712,7 +728,7 @@ impl ExecutorContext {
StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
Some(x) => x,
None => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"cannot use % outside a pipe expression".to_owned(),
vec![pipe_substitution.into()],
)));
@ -741,6 +757,24 @@ impl ExecutorContext {
}
}
/// If the error is about an undefined name, and that name matches the name being defined,
/// make the error message more specific.
fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
let KclError::UndefinedValue { name, mut details } = e else {
return e;
};
// TODO after June 26th: replace this with a let-chain,
// which will be available in Rust 1.88
// https://rust-lang.github.io/rfcs/2497-if-let-chains.html
match (&being_declared, &name) {
(Some(name0), Some(name1)) if name0 == name1 => {
details.message = format!("You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead.");
}
_ => {}
}
KclError::UndefinedValue { details, name }
}
impl Node<AscribedExpression> {
#[async_recursion]
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
@ -761,7 +795,7 @@ fn apply_ascription(
source_range: SourceRange,
) -> Result<KclValue, KclError> {
let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into())
.map_err(|e| KclError::Semantic(e.into()))?;
.map_err(|e| KclError::new_semantic(e.into()))?;
value.coerce(&ty, false, exec_state).map_err(|_| {
let suggestion = if ty == RuntimeType::length() {
@ -771,7 +805,7 @@ fn apply_ascription(
} else {
""
};
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"could not coerce value of type {} to type {ty}{suggestion}",
value.human_friendly_type()
@ -804,7 +838,7 @@ impl Node<Name> {
ctx: &ExecutorContext,
) -> Result<&'a KclValue, KclError> {
if self.abs_path {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
self.as_source_ranges(),
)));
@ -825,7 +859,7 @@ impl Node<Name> {
let value = match mem_spec {
Some((env, exports)) => {
if !exports.contains(&p.name) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Item {} not found in module's exported items", p.name),
p.as_source_ranges(),
)));
@ -842,7 +876,7 @@ impl Node<Name> {
};
let KclValue::Module { value: module_id, .. } = value else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Identifier in path must refer to a module, found {}",
value.human_friendly_type()
@ -888,7 +922,7 @@ impl Node<Name> {
// Either item or module is defined, but not exported.
debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Item {} not found in module's exported items", self.name.name),
self.name.as_source_ranges(),
)))
@ -913,14 +947,17 @@ impl Node<MemberExpression> {
if let Some(value) = map.get(&property) {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("Property '{property}' not found in object"),
vec![self.clone().into()],
)))
Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("Property '{property}' not found in object"),
vec![self.clone().into()],
),
None,
))
}
}
(KclValue::Object { .. }, Property::String(property), true) => {
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
vec![self.clone().into()],
)))
@ -928,7 +965,7 @@ impl Node<MemberExpression> {
(KclValue::Object { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_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()],
)))
@ -938,10 +975,13 @@ impl Node<MemberExpression> {
if let Some(value) = value_of_arr {
Ok(value.to_owned())
} else {
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("The array doesn't have any item at index {index}"),
vec![self.clone().into()],
)))
Err(KclError::new_undefined_value(
KclErrorDetails::new(
format!("The array doesn't have any item at index {index}"),
vec![self.clone().into()],
),
None,
))
}
}
// Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
@ -950,7 +990,7 @@ impl Node<MemberExpression> {
(KclValue::HomArray { .. }, p, _) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_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()],
)))
@ -971,7 +1011,7 @@ impl Node<MemberExpression> {
(being_indexed, _, _) => {
let t = being_indexed.human_friendly_type();
let article = article_for(&t);
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Only arrays can be indexed, but you're trying to index {article} {t}"),
vec![self.clone().into()],
)))
@ -1049,7 +1089,7 @@ impl Node<BinaryExpression> {
meta: _,
} = left_value
else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Cannot apply logical operator to non-boolean value: {}",
left_value.human_friendly_type()
@ -1062,7 +1102,7 @@ impl Node<BinaryExpression> {
meta: _,
} = right_value
else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Cannot apply logical operator to non-boolean value: {}",
right_value.human_friendly_type()
@ -1168,7 +1208,7 @@ impl Node<UnaryExpression> {
meta: _,
} = value
else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Cannot apply unary operator ! to non-boolean value: {}",
value.human_friendly_type()
@ -1189,7 +1229,7 @@ impl Node<UnaryExpression> {
let value = &self.argument.get_result(exec_state, ctx).await?;
let err = || {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"You can only negate numbers, planes, or lines, but this is a {}",
value.human_friendly_type()
@ -1292,7 +1332,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Pipe expressions cannot be empty".to_owned(),
vec![source_range],
)));
@ -1311,7 +1351,7 @@ pub(crate) async fn execute_pipe_body(
// Now that we've evaluated the first child expression in the pipeline, following child expressions
// should use the previous child expression for %.
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
// Evaluate remaining elements.
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
// Restore the previous pipe value.
@ -1330,7 +1370,7 @@ async fn inner_execute_pipe_body(
) -> Result<KclValue, KclError> {
for expression in body {
if let Expr::TagDeclarator(_) = expression {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("This cannot be in a PipeExpression: {:?}", expression),
vec![expression.into()],
)));
@ -1404,7 +1444,7 @@ impl Node<ArrayRangeExpression> {
.await?;
let (start, start_ty) = start_val
.as_int_with_ty()
.ok_or(KclError::Semantic(KclErrorDetails::new(
.ok_or(KclError::new_semantic(KclErrorDetails::new(
format!("Expected int but found {}", start_val.human_friendly_type()),
vec![self.into()],
)))?;
@ -1412,24 +1452,26 @@ impl Node<ArrayRangeExpression> {
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::new(
format!("Expected int but found {}", end_val.human_friendly_type()),
vec![self.into()],
)))?;
let (end, end_ty) = end_val
.as_int_with_ty()
.ok_or(KclError::new_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::new(
return Err(KclError::new_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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Range start is greater than range end: {start} .. {end}"),
vec![self.into()],
)));
@ -1493,7 +1535,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Expected a number, but found {actual_type}",),
vec![source_range],
))
@ -1585,13 +1627,13 @@ impl Property {
if let Some(x) = crate::try_f64_to_usize(value) {
Ok(Property::UInt(x))
} else {
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
format!("{value} is not a valid index, indices must be whole numbers >= 0"),
property_sr,
)))
}
}
_ => Err(KclError::Semantic(KclErrorDetails::new(
_ => Err(KclError::new_semantic(KclErrorDetails::new(
"Only numbers (>= 0) can be indexes".to_owned(),
vec![sr],
))),
@ -1602,7 +1644,8 @@ 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::new(message, property_sr)));
let make_err =
|message: String| Err::<Property, _>(KclError::new_semantic(KclErrorDetails::new(message, property_sr)));
match value {
KclValue::Number{value: num, .. } => {
let num = *num;
@ -1846,7 +1889,7 @@ d = b + c
crate::engine::conn_mock::EngineConnection::new()
.await
.map_err(|err| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()],
))

View File

@ -295,7 +295,7 @@ impl Node<CallExpressionKw> {
let func = fn_name.get_result(exec_state, ctx).await?.clone();
let Some(fn_src) = func.as_function() else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"cannot call this because it isn't a function".to_string(),
vec![callsite],
)));
@ -318,10 +318,13 @@ impl Node<CallExpressionKw> {
if let KclValue::Function { meta, .. } = func {
source_ranges = meta.iter().map(|m| m.source_range).collect();
};
KclError::UndefinedValue(KclErrorDetails::new(
format!("Result of user-defined function {} is undefined", fn_name),
source_ranges,
))
KclError::new_undefined_value(
KclErrorDetails::new(
format!("Result of user-defined function {} is undefined", fn_name),
source_ranges,
),
None,
)
})?;
Ok(result)
@ -500,7 +503,7 @@ 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::new(
return Err(KclError::new_internal(KclErrorDetails::new(
format!("Tag {} does not have path info", tag.name),
vec![tag.into()],
)));
@ -605,7 +608,7 @@ fn type_check_params_kw(
arg.value = arg
.value
.coerce(
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::new_semantic(e.into()))?,
true,
exec_state,
)
@ -619,7 +622,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
message,
vec![arg.source_range],
))
@ -670,7 +673,7 @@ fn type_check_params_kw(
let first = errors.next().unwrap();
errors.for_each(|e| exec_state.err(e));
return Err(KclError::Semantic(first.into()));
return Err(KclError::new_semantic(first.into()));
}
if let Some(arg) = &mut args.unlabeled {
@ -680,12 +683,12 @@ fn type_check_params_kw(
.value
.coerce(
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
.map_err(|e| KclError::Semantic(e.into()))?,
.map_err(|e| KclError::new_semantic(e.into()))?,
true,
exec_state,
)
.map_err(|_| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"The input argument of {} requires a value with type `{}`, but found {}",
fn_name
@ -742,7 +745,7 @@ fn assign_args_to_params_kw(
.add(name.clone(), value, default_val.source_range())?;
}
None => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"This function requires a parameter {}, but you haven't passed it one.",
name
@ -759,12 +762,12 @@ 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::new(
KclError::new_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,
))
} else {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
"This function expects an unlabeled first parameter, but you haven't passed it one.".to_owned(),
source_ranges,
))
@ -788,9 +791,9 @@ fn coerce_result_type(
if let Ok(Some(val)) = result {
if let Some(ret_ty) = &fn_def.return_type {
let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
.map_err(|e| KclError::Semantic(e.into()))?;
.map_err(|e| KclError::new_semantic(e.into()))?;
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"This function requires its result to be of type `{}`, but found {}",
ty.human_friendly_type(),
@ -874,7 +877,7 @@ mod test {
"all params required, none given, should error",
vec![req_param("x")],
vec![],
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
vec![SourceRange::default()],
))),
@ -889,7 +892,7 @@ mod test {
"mixed params, too few given",
vec![req_param("x"), opt_param("y")],
vec![],
Err(KclError::Semantic(KclErrorDetails::new(
Err(KclError::new_semantic(KclErrorDetails::new(
"This function requires a parameter x, but you haven't passed it one.".to_owned(),
vec![SourceRange::default()],
))),

View File

@ -24,6 +24,7 @@ type Point3D = kcmc::shared::Point3d<f64>;
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
#[allow(clippy::large_enum_variant)]
pub enum Geometry {
Sketch(Sketch),
Solid(Solid),
@ -52,6 +53,7 @@ impl Geometry {
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(tag = "type")]
#[allow(clippy::large_enum_variant)]
pub enum GeometryWithImportedGeometry {
Sketch(Sketch),
Solid(Solid),
@ -469,7 +471,7 @@ 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::new(
return Err(KclError::new_internal(KclErrorDetails::new(
format!("PlaneData {:?} not found", value),
Default::default(),
)));
@ -477,7 +479,7 @@ impl TryFrom<PlaneData> for PlaneInfo {
};
let info = DEFAULT_PLANE_INFO.get(&name).ok_or_else(|| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Plane {} not found", name),
Default::default(),
))

View File

@ -37,25 +37,25 @@ 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::new(
return Err(KclError::new_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::new(
KclError::new_semantic(KclErrorDetails::new(
format!("No file extension found for `{}`", file_path.display()),
vec![source_range],
))
})?)
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
.map_err(|e| KclError::new_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::new(e.to_string(), vec![source_range])))?;
.map_err(|e| KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
format
} else {
ext_format
@ -66,11 +66,11 @@ pub async fn import_foreign(
.fs
.read(file_path, source_range)
.await
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
.map_err(|e| KclError::new_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::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Could not get the file name from the path `{}`", file_path.display()),
vec![source_range],
))
@ -87,7 +87,7 @@ pub async fn import_foreign(
// file.
if !file_contents.starts_with(b"glTF") {
let json = gltf_json::Root::from_slice(&file_contents)
.map_err(|e| KclError::Semantic(KclErrorDetails::new(e.to_string(), vec![source_range])))?;
.map_err(|e| KclError::new_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() {
@ -95,16 +95,15 @@ 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::new(
KclError::new_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::new(e.to_string(), vec![source_range]))
})?;
let bin_contents = ctxt.fs.read(&bin_path, source_range).await.map_err(|e| {
KclError::new_semantic(KclErrorDetails::new(e.to_string(), vec![source_range]))
})?;
import_files.push(ImportFile {
path: uri.to_string(),
@ -141,7 +140,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"Unknown format for import, expected one of: {}",
crate::IMPORT_FILE_EXTENSIONS.join(", ")
@ -159,7 +158,7 @@ pub(super) fn format_from_annotations(
path.extension()
.and_then(|ext| get_import_format_from_extension(ext).ok())
})
.ok_or(KclError::Semantic(KclErrorDetails::new(
.ok_or(KclError::new_semantic(KclErrorDetails::new(
"Unknown or missing extension, and no specified format for imported file".to_owned(),
vec![import_source_range],
)))?;
@ -174,7 +173,7 @@ pub(super) fn format_from_annotations(
}
annotations::IMPORT_FORMAT => {}
_ => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Unexpected annotation for import, expected one of: {}, {}, {}",
annotations::IMPORT_FORMAT,
@ -199,7 +198,7 @@ fn set_coords(fmt: &mut InputFormat3d, coords_str: &str, source_range: SourceRan
}
let Some(coords) = coords else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Unknown coordinate system: {coords_str}, expected one of: {}",
annotations::IMPORT_COORDS_VALUES
@ -217,7 +216,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"`{}` option cannot be applied to the specified format",
annotations::IMPORT_COORDS
@ -238,7 +237,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"`{}` option cannot be applied to the specified format",
annotations::IMPORT_LENGTH_UNIT

View File

@ -574,7 +574,7 @@ impl KclValue {
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
match self {
KclValue::TagIdentifier(t) => Ok(*t.clone()),
_ => Err(KclError::Semantic(KclErrorDetails::new(
_ => Err(KclError::new_semantic(KclErrorDetails::new(
format!("Not a tag identifier: {:?}", self),
self.clone().into(),
))),
@ -585,7 +585,7 @@ impl KclValue {
pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
match self {
KclValue::TagDeclarator(t) => Ok((**t).clone()),
_ => Err(KclError::Semantic(KclErrorDetails::new(
_ => Err(KclError::new_semantic(KclErrorDetails::new(
format!("Not a tag declarator: {:?}", self),
self.clone().into(),
))),
@ -595,7 +595,7 @@ impl KclValue {
/// If this KCL value is a bool, retrieve it.
pub fn get_bool(&self) -> Result<bool, KclError> {
self.as_bool().ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected bool, found {}", self.human_friendly_type()),
self.into(),
))

View File

@ -367,10 +367,10 @@ impl ProgramMemory {
let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("`{name}` is not defined"),
vec![source_range],
)))
Err(KclError::new_undefined_value(
KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
Some(name.to_owned()),
))
}
/// Iterate over all key/value pairs in the specified environment which satisfy the provided
@ -488,10 +488,10 @@ impl ProgramMemory {
};
}
Err(KclError::UndefinedValue(KclErrorDetails::new(
format!("`{}` is not defined", var),
vec![],
)))
Err(KclError::new_undefined_value(
KclErrorDetails::new(format!("`{}` is not defined", var), vec![]),
Some(var.to_owned()),
))
}
}
@ -646,7 +646,7 @@ 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::new(
return Err(KclError::new_value_already_defined(KclErrorDetails::new(
format!("Cannot redefine `{}`", key),
vec![source_range],
)));

View File

@ -858,7 +858,7 @@ 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(
return Err(KclErrorWithOutputs::no_outputs(KclError::new_internal(
KclErrorDetails::new(format!("Module {module} not found in universe"), Default::default()),
)));
};
@ -920,7 +920,7 @@ impl ExecutorContext {
result.map(|val| ModuleRepr::Foreign(geom.clone(), val))
}
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::Internal(KclErrorDetails::new(
ModuleRepr::Dummy | ModuleRepr::Root => Err(KclError::new_internal(KclErrorDetails::new(
format!("Module {module_path} not found in universe"),
vec![source_range],
))),
@ -1283,7 +1283,7 @@ impl ExecutorContext {
.await?;
let kittycad_modeling_cmds::websocket::OkWebSocketResponseData::Export { files } = resp else {
return Err(KclError::Internal(crate::errors::KclErrorDetails::new(
return Err(KclError::new_internal(crate::errors::KclErrorDetails::new(
format!("Expected Export response, got {resp:?}",),
vec![SourceRange::default()],
)));
@ -1303,7 +1303,7 @@ 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::new(
KclError::new_internal(crate::errors::KclErrorDetails::new(
format!("Failed to parse date: {}", e),
vec![SourceRange::default()],
))
@ -1383,7 +1383,7 @@ 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::new(
KclError::new_internal(crate::errors::KclErrorDetails::new(
format!("Failed to create mock engine connection: {}", err),
vec![SourceRange::default()],
))
@ -1799,7 +1799,7 @@ foo
let err = result.unwrap_err();
assert_eq!(
err,
KclError::Syntax(KclErrorDetails::new(
KclError::new_syntax(KclErrorDetails::new(
"Unexpected token: #".to_owned(),
vec![SourceRange::new(14, 15, ModuleId::default())],
)),
@ -2058,7 +2058,7 @@ 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::new(
KclError::new_syntax(KclErrorDetails::new(
"Unexpected token: !".to_owned(),
vec![SourceRange::new(10, 11, ModuleId::default())],
))
@ -2071,9 +2071,9 @@ 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::new(
"Unexpected token: |>".to_owned(),
vec![SourceRange::new(44, 46, ModuleId::default())],
KclError::new_syntax(KclErrorDetails::new(
"There was an unexpected !. Try removing it.".to_owned(),
vec![SourceRange::new(56, 57, ModuleId::default())],
))
);

View File

@ -80,6 +80,11 @@ pub(super) struct ModuleState {
/// The current value of the pipe operator returned from the previous
/// expression. If we're not currently in a pipeline, this will be None.
pub pipe_value: Option<KclValue>,
/// The closest variable declaration being executed in any parent node in the AST.
/// This is used to provide better error messages, e.g. noticing when the user is trying
/// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
/// TODO: Make this a reference.
pub being_declared: Option<String>,
/// Identifiers that have been exported from the current module.
pub module_exports: Vec<String>,
/// Settings specified from annotations.
@ -276,7 +281,7 @@ impl ExecState {
}
pub(super) fn circular_import_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails::new(
KclError::new_import_cycle(KclErrorDetails::new(
format!(
"circular import of modules is not allowed: {} -> {}",
self.global
@ -342,6 +347,7 @@ impl ModuleState {
id_generator: IdGenerator::new(module_id),
stack: memory.new_stack(),
pipe_value: Default::default(),
being_declared: Default::default(),
module_exports: Default::default(),
explicit_length_units: false,
path,
@ -389,7 +395,7 @@ impl MetaSettings {
self.kcl_version = value;
}
name => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Unexpected settings key: `{name}`; expected one of `{}`, `{}`",
annotations::SETTINGS_UNIT_LENGTH,

View File

@ -187,7 +187,7 @@ impl RuntimeType {
};
RuntimeType::Primitive(PrimitiveType::Number(ty))
}
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),

View File

@ -28,7 +28,7 @@ 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::new(
KclError::new_io(KclErrorDetails::new(
format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range],
))
@ -37,7 +37,7 @@ impl FileSystem for FileManager {
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::new(
KclError::new_io(KclErrorDetails::new(
format!("Failed to read file `{}`: {}", path.display(), e),
vec![source_range],
))
@ -49,7 +49,7 @@ impl FileSystem for FileManager {
if e.kind() == std::io::ErrorKind::NotFound {
Ok(false)
} else {
Err(KclError::Io(KclErrorDetails::new(
Err(KclError::new_io(KclErrorDetails::new(
format!("Failed to check if file `{}` exists: {}", path.display(), e),
vec![source_range],
)))
@ -71,7 +71,7 @@ impl FileSystem for FileManager {
}
let mut read_dir = tokio::fs::read_dir(&path).await.map_err(|e| {
KclError::Io(KclErrorDetails::new(
KclError::new_io(KclErrorDetails::new(
format!("Failed to read directory `{}`: {}", path.display(), e),
vec![source_range],
))

View File

@ -49,10 +49,10 @@ impl FileSystem for FileManager {
let promise = self
.manager
.read_file(path.to_string_lossy())
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to wait for promise from engine: {:?}", e),
vec![source_range],
))
@ -67,7 +67,7 @@ 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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to convert bytes to string: {:?}", e),
vec![source_range],
))
@ -80,17 +80,17 @@ impl FileSystem for FileManager {
let promise = self
.manager
.exists(path.to_string_lossy())
.map_err(|e| KclError::Engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_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::new(
KclError::new_engine(KclErrorDetails::new(
"Failed to convert value to bool".to_string(),
vec![source_range],
))
@ -107,24 +107,24 @@ impl FileSystem for FileManager {
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])))?;
.map_err(|e| KclError::new_engine(KclErrorDetails::new(e.to_string().into(), vec![source_range])))?;
let value = JsFuture::from(promise).await.map_err(|e| {
KclError::Engine(KclErrorDetails::new(
KclError::new_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::new(
KclError::new_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::new(
KclError::new_engine(KclErrorDetails::new(
format!("Failed to parse json from javascript: `{}` `{:?}`", s, e),
vec![source_range],
))

View File

@ -1189,7 +1189,6 @@ impl LanguageServer for Backend {
}
async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
// ADAM: This is the entrypoint.
let mut completions = vec![CompletionItem {
label: PIPE_OPERATOR.to_string(),
label_details: None,

View File

@ -58,7 +58,7 @@ impl ModuleLoader {
}
pub(crate) fn import_cycle_error(&self, path: &ModulePath, source_range: SourceRange) -> KclError {
KclError::ImportCycle(KclErrorDetails::new(
KclError::new_import_cycle(KclErrorDetails::new(
format!(
"circular import of modules is not allowed: {} -> {}",
self.import_stack
@ -163,7 +163,7 @@ impl ModulePath {
ModulePath::Std { value: name } => Ok(ModuleSource {
source: read_std(name)
.ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Cannot find standard library module to import: std::{name}."),
vec![source_range],
))
@ -202,7 +202,7 @@ impl ModulePath {
ModulePath::Std { .. } => {
let message = format!("Cannot import a non-std KCL file from std: {path}.");
debug_assert!(false, "{}", &message);
return Err(KclError::Internal(KclErrorDetails::new(message, vec![])));
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
}
};
@ -217,7 +217,7 @@ impl ModulePath {
if path.len() != 2 || path[0] != "std" {
let message = format!("Invalid std import path: {path:?}.");
debug_assert!(false, "{}", &message);
return Err(KclError::Internal(KclErrorDetails::new(message, vec![])));
return Err(KclError::new_internal(KclErrorDetails::new(message, vec![])));
}
Ok(ModulePath::Std { value: path[1].clone() })

View File

@ -228,7 +228,7 @@ impl PrimitiveType {
let mut hasher = Sha256::new();
match self {
PrimitiveType::Any => hasher.update(b"any"),
PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
PrimitiveType::Named { id } => hasher.update(id.compute_digest()),
PrimitiveType::String => hasher.update(b"string"),
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
PrimitiveType::Boolean => hasher.update(b"bool"),

View File

@ -454,7 +454,7 @@ impl Node<Program> {
alpha: c.a,
},
};
if colors.borrow().iter().any(|c| *c == color) {
if colors.borrow().contains(&color) {
return;
}
colors.borrow_mut().push(color);
@ -529,7 +529,7 @@ impl Node<Program> {
let new_color = csscolorparser::Color::new(color.red, color.green, color.blue, color.alpha);
Ok(Some(ColorPresentation {
// The label will be what they replace the color with.
label: new_color.to_hex_string(),
label: new_color.to_css_hex(),
text_edit: None,
additional_text_edits: None,
}))
@ -3230,7 +3230,7 @@ pub enum PrimitiveType {
/// `fn`, type of functions.
Function(FunctionType),
/// An identifier used as a type (not really a primitive type, but whatever).
Named(Node<Identifier>),
Named { id: Node<Identifier> },
}
impl PrimitiveType {
@ -3286,7 +3286,7 @@ impl fmt::Display for PrimitiveType {
}
Ok(())
}
PrimitiveType::Named(n) => write!(f, "{}", n.name),
PrimitiveType::Named { id: n } => write!(f, "{}", n.name),
}
}
}

View File

@ -51,7 +51,7 @@ pub fn parse_tokens(mut tokens: TokenStream) -> ParseResult {
} else {
format!("found unknown tokens [{}]", token_list.join(", "))
};
return KclError::Lexical(KclErrorDetails::new(message, source_ranges)).into();
return KclError::new_lexical(KclErrorDetails::new(message, source_ranges)).into();
}
// Important, to not call this before the unknown tokens check.
@ -110,7 +110,7 @@ impl ParseResult {
let (p, errs) = self.0?;
if let Some(err) = errs.iter().find(|e| e.severity.is_err()) {
return Err(KclError::Syntax(err.clone().into()));
return Err(KclError::new_syntax(err.clone().into()));
}
match p {
Some(p) => Ok(p),

View File

@ -979,12 +979,18 @@ fn property_separator(i: &mut TokenSlice) -> ModalResult<()> {
}
/// Match something that separates the labeled arguments of a fn call.
fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<()> {
/// Returns the source range of the erroneous separator, if any was found.
fn labeled_arg_separator(i: &mut TokenSlice) -> ModalResult<Option<SourceRange>> {
alt((
// Normally you need a comma.
comma_sep,
comma_sep.map(|_| None),
// But, if the argument list is ending, no need for a comma.
peek(preceded(opt(whitespace), close_paren)).void(),
peek(preceded(opt(whitespace), close_paren)).void().map(|_| None),
whitespace.map(|mut tokens| {
// Safe to unwrap here because `whitespace` is guaranteed to return at least 1 whitespace.
let first_token = tokens.pop().unwrap();
Some(SourceRange::from(&first_token))
}),
))
.parse_next(i)
}
@ -2932,7 +2938,7 @@ fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
(identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
result.inner =
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident });
result
}),
))
@ -3135,7 +3141,7 @@ fn binding_name(i: &mut TokenSlice) -> ModalResult<Node<Identifier>> {
/// Either a positional or keyword function call.
fn fn_call_pos_or_kw(i: &mut TokenSlice) -> ModalResult<Expr> {
alt((fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),)).parse_next(i)
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw).parse_next(i)
}
fn labelled_fn_call(i: &mut TokenSlice) -> ModalResult<Expr> {
@ -3198,7 +3204,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
#[allow(clippy::large_enum_variant)]
enum ArgPlace {
NonCode(Node<NonCodeNode>),
LabeledArg(LabeledArg),
LabeledArg((LabeledArg, Option<SourceRange>)),
UnlabeledArg(Expr),
Keyword(Token),
}
@ -3208,7 +3214,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
alt((
terminated(non_code_node.map(ArgPlace::NonCode), whitespace),
terminated(any_keyword.map(ArgPlace::Keyword), whitespace),
terminated(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
(labeled_argument, labeled_arg_separator).map(ArgPlace::LabeledArg),
expression.map(ArgPlace::UnlabeledArg),
)),
)
@ -3220,7 +3226,16 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
ArgPlace::NonCode(x) => {
non_code_nodes.insert(index, vec![x]);
}
ArgPlace::LabeledArg(x) => {
ArgPlace::LabeledArg((x, bad_token_source_range)) => {
if let Some(bad_token_source_range) = bad_token_source_range {
return Err(ErrMode::Cut(
CompilationError::fatal(
bad_token_source_range,
"Missing comma between arguments, try adding a comma in",
)
.into(),
));
}
args.push(x);
}
ArgPlace::Keyword(kw) => {
@ -3255,7 +3270,22 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
)?;
ignore_whitespace(i);
opt(comma_sep).parse_next(i)?;
let end = close_paren.parse_next(i)?.end;
let end = match close_paren.parse_next(i) {
Ok(tok) => tok.end,
Err(e) => {
if let Some(tok) = i.next_token() {
return Err(ErrMode::Cut(
CompilationError::fatal(
SourceRange::from(&tok),
format!("There was an unexpected {}. Try removing it.", tok.value),
)
.into(),
));
} else {
return Err(e);
}
}
};
// Validate there aren't any duplicate labels.
let mut counted_labels = IndexMap::with_capacity(args.len());
@ -3376,8 +3406,7 @@ mod tests {
fn kw_call_as_operand() {
let tokens = crate::parsing::token::lex("f(x = 1)", ModuleId::default()).unwrap();
let tokens = tokens.as_slice();
let op = operand.parse(tokens).unwrap();
println!("{op:#?}");
operand.parse(tokens).unwrap();
}
#[test]
@ -4383,7 +4412,7 @@ secondExtrude = startSketchOn(XY)
#[test]
fn test_parse_parens_unicode() {
let result = crate::parsing::top_level_parse("");
let KclError::Lexical(details) = result.0.unwrap_err() else {
let KclError::Lexical { details } = result.0.unwrap_err() else {
panic!();
};
// TODO: Better errors when program cannot tokenize.
@ -4417,8 +4446,8 @@ z(-[["#,
assert_err(
r#"z
(--#"#,
"Unexpected token: (",
[2, 3],
"There was an unexpected -. Try removing it.",
[3, 4],
);
}
@ -5133,6 +5162,27 @@ bar = 1
assert_eq!(actual.operator, UnaryOperator::Not);
crate::parsing::top_level_parse(some_program_string).unwrap(); // Updated import path
}
#[test]
fn test_sensible_error_when_missing_comma_between_fn_args() {
let program_source = "startSketchOn(XY)
|> arc(
endAbsolute = [0, 50]
interiorAbsolute = [-50, 0]
)";
let expected_src_start = program_source.find("]").unwrap();
let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
ParseContext::init();
let err = program
.parse(tokens.as_slice())
.expect_err("Program succeeded, but it should have failed");
let cause = err
.inner()
.cause
.as_ref()
.expect("Found an error, but there was no cause. Add a cause.");
assert_eq!(cause.message, "Missing comma between arguments, try adding a comma in",);
assert_eq!(cause.source_range.start() - 1, expected_src_start);
}
#[test]
fn test_sensible_error_when_missing_rhs_of_kw_arg() {
@ -5152,14 +5202,32 @@ bar = 1
}
}
#[test]
fn test_sensible_error_when_unexpected_token_in_fn_call() {
let program_source = "1
|> extrude(
length=depth,
})";
let expected_src_start = program_source.find("}").expect("Program should have an extraneous }");
let tokens = crate::parsing::token::lex(program_source, ModuleId::default()).unwrap();
ParseContext::init();
let err = program.parse(tokens.as_slice()).unwrap_err();
let cause = err
.inner()
.cause
.as_ref()
.expect("Found an error, but there was no cause. Add a cause.");
assert_eq!(cause.message, "There was an unexpected }. Try removing it.",);
assert_eq!(cause.source_range.start(), expected_src_start);
}
#[test]
fn test_sensible_error_when_using_keyword_as_arg_label() {
for (i, program) in ["pow(2, fn = 8)"].into_iter().enumerate() {
let tokens = crate::parsing::token::lex(program, ModuleId::default()).unwrap();
let err = match fn_call_kw.parse(tokens.as_slice()) {
Err(e) => e,
Ok(ast) => {
eprintln!("{ast:#?}");
Ok(_ast) => {
panic!("Expected this to error but it didn't");
}
};

View File

@ -597,7 +597,7 @@ 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::new(
return KclError::new_lexical(crate::errors::KclErrorDetails::new(
"unexpected EOF while parsing".to_owned(),
vec![SourceRange::new(offset, offset, module_id)],
));
@ -608,7 +608,7 @@ 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::new(
KclError::new_lexical(crate::errors::KclErrorDetails::new(
format!("found unknown token '{}'", bad_token),
vec![SourceRange::new(offset, offset + 1, module_id)],
))

View File

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

View File

@ -30,7 +30,7 @@ pub async fn hex_string(exec_state: &mut ExecState, args: Args) -> Result<KclVal
// Make sure the color if set is valid.
if let Some(component) = rgb.iter().find(|component| component.n < 0.0 || component.n > 255.0) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Colors are given between 0 and 255, so {} is invalid", component.n),
vec![args.source_range],
)));
@ -62,7 +62,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Invalid hex color (`{}`), try something like `#fff000`", color),
vec![args.source_range],
)));
@ -93,7 +93,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Invalid hex color (`{color}`): {err}"),
vec![args.source_range],
))

View File

@ -123,7 +123,7 @@ impl Args {
}
T::from_kcl_val(&arg.value).map(Some).ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_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>(),
@ -156,7 +156,7 @@ impl Args {
T: FromKclValue<'a>,
{
self.get_kw_arg_opt(label)?.ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
))
@ -173,7 +173,7 @@ impl Args {
T: for<'a> FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
)));
@ -207,7 +207,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::new(message, arg.source_ranges()))
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
})?;
// TODO unnecessary cloning
@ -221,7 +221,7 @@ impl Args {
label: &str,
) -> Result<Vec<(EdgeReference, SourceRange)>, KclError> {
let Some(arg) = self.kw_args.labeled.get(label) else {
let err = KclError::Semantic(KclErrorDetails::new(
let err = KclError::new_semantic(KclErrorDetails::new(
format!("This function requires a keyword argument '{label}'"),
vec![self.source_range],
));
@ -234,7 +234,7 @@ impl Args {
.map(|item| {
let source = SourceRange::from(item);
let val = FromKclValue::from_kcl_val(item).ok_or_else(|| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!("Expected an Edge but found {}", arg.value.human_friendly_type()),
arg.source_ranges(),
))
@ -270,7 +270,7 @@ impl Args {
{
let arg = self
.unlabeled_kw_arg_unconverted()
.ok_or(KclError::Semantic(KclErrorDetails::new(
.ok_or(KclError::new_semantic(KclErrorDetails::new(
format!("This function requires a value for the special unlabeled first parameter, '{label}'"),
vec![self.source_range],
)))?;
@ -304,11 +304,11 @@ 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::new(message, arg.source_ranges()))
KclError::new_semantic(KclErrorDetails::new(message, arg.source_ranges()))
})?;
T::from_kcl_val(&arg).ok_or_else(|| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
vec![self.source_range],
))
@ -354,14 +354,14 @@ 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::new(
KclError::new_type(KclErrorDetails::new(
format!("Tag `{}` does not have engine info", tag.value),
vec![self.source_range],
))
})?;
Ok(info)
} else {
Err(KclError::Type(KclErrorDetails::new(
Err(KclError::new_type(KclErrorDetails::new(
format!("Tag `{}` does not exist", tag.value),
vec![self.source_range],
)))
@ -493,7 +493,7 @@ impl Args {
must_be_planar: bool,
) -> Result<uuid::Uuid, KclError> {
if tag.value.is_empty() {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Expected a non-empty tag for the face".to_string(),
vec![self.source_range],
)));
@ -502,7 +502,7 @@ impl Args {
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Tag `{}` does not have a surface", tag.value),
vec![self.source_range],
))
@ -521,7 +521,7 @@ 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::new(
ExtrudeSurface::ExtrudeArc(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
format!("Tag `{}` is a non-planar surface", tag.value),
vec![self.source_range],
)))),
@ -548,7 +548,7 @@ 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::new(
ExtrudeSurface::Fillet(_) if must_be_planar => Some(Err(KclError::new_type(KclErrorDetails::new(
format!("Tag `{}` is a non-planar surface", tag.value),
vec![self.source_range],
)))),
@ -568,7 +568,7 @@ impl Args {
}
// If we still haven't found the face, return an error.
Err(KclError::Type(KclErrorDetails::new(
Err(KclError::new_type(KclErrorDetails::new(
format!("Expected a face with the tag `{}`", tag.value),
vec![self.source_range],
)))
@ -593,13 +593,13 @@ 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::new(
return Err(KclError::new_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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Argument at index {i} was supposed to be type {} but found {}",
tynm::type_name::<T>(),
@ -622,7 +622,7 @@ where
return Ok(None);
}
let Some(val) = T::from_kcl_val(&arg.value) else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Argument at index {i} was supposed to be type Option<{}> but found {}",
tynm::type_name::<T>(),

View File

@ -58,7 +58,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
"Map function must return a value".to_owned(),
source_ranges,
))
@ -118,7 +118,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
"Reducer function must return a value".to_string(),
source_ranges.clone(),
))
@ -138,7 +138,7 @@ pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
if array.is_empty() {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Cannot pop from an empty array".to_string(),
vec![args.source_range],
)));

View File

@ -11,7 +11,7 @@ use crate::{
async fn _assert(value: bool, message: &str, args: &Args) -> Result<(), KclError> {
if !value {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
format!("assert failed: {}", message),
vec![args.source_range],
)));
@ -72,14 +72,14 @@ async fn inner_assert(
.iter()
.all(|cond| cond.is_none());
if no_condition_given {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_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],

View File

@ -41,7 +41,7 @@ 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::new(
return Err(KclError::new_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],
)));

View File

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

View File

@ -23,7 +23,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"At least two solids are required for a union operation.".to_string(),
vec![args.source_range],
)));
@ -66,7 +66,7 @@ pub(crate) async fn inner_union(
modeling_response: OkModelingCmdResponse::BooleanUnion(BooleanUnion { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails::new(
return Err(KclError::new_internal(KclErrorDetails::new(
"Failed to get the result of the union operation.".to_string(),
vec![args.source_range],
)));
@ -88,7 +88,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"At least two solids are required for an intersect operation.".to_string(),
vec![args.source_range],
)));
@ -131,7 +131,7 @@ pub(crate) async fn inner_intersect(
modeling_response: OkModelingCmdResponse::BooleanIntersection(BooleanIntersection { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails::new(
return Err(KclError::new_internal(KclErrorDetails::new(
"Failed to get the result of the intersection operation.".to_string(),
vec![args.source_range],
)));
@ -193,7 +193,7 @@ pub(crate) async fn inner_subtract(
modeling_response: OkModelingCmdResponse::BooleanSubtract(BooleanSubtract { extra_solid_ids }),
} = result
else {
return Err(KclError::Internal(KclErrorDetails::new(
return Err(KclError::new_internal(KclErrorDetails::new(
"Failed to get the result of the subtract operation.".to_string(),
vec![args.source_range],
)));

View File

@ -52,7 +52,7 @@ async fn inner_get_opposite_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetOppositeEdge(opposite_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!("mcmd::Solid3dGetOppositeEdge response was not as expected: {:?}", resp),
vec![args.source_range],
)));
@ -100,7 +100,7 @@ async fn inner_get_next_adjacent_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetNextAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!(
"mcmd::Solid3dGetNextAdjacentEdge response was not as expected: {:?}",
resp
@ -110,7 +110,7 @@ async fn inner_get_next_adjacent_edge(
};
adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_type(KclErrorDetails::new(
format!("No edge found next adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
@ -155,7 +155,7 @@ async fn inner_get_previous_adjacent_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetPrevAdjacentEdge(adjacent_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!(
"mcmd::Solid3dGetPrevAdjacentEdge response was not as expected: {:?}",
resp
@ -165,7 +165,7 @@ async fn inner_get_previous_adjacent_edge(
};
adjacent_edge.edge.ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_type(KclErrorDetails::new(
format!("No edge found previous adjacent to tag: `{}`", edge.value),
vec![args.source_range],
))
@ -198,7 +198,7 @@ async fn inner_get_common_edge(
}
if faces.len() != 2 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
"getCommonEdge requires exactly two tags for faces".to_string(),
vec![args.source_range],
)));
@ -210,7 +210,7 @@ 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"getCommonEdge requires the faces to be in the same original sketch".to_string(),
vec![args.source_range],
)));
@ -239,14 +239,14 @@ async fn inner_get_common_edge(
modeling_response: OkModelingCmdResponse::Solid3dGetCommonEdge(common_edge),
} = &resp
else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_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::new(
KclError::new_type(KclErrorDetails::new(
format!(
"No common edge was found between `{}` and `{}`",
faces[0].value, faces[1].value

View File

@ -66,7 +66,7 @@ async fn inner_extrude(
let mut solids = Vec::new();
if symmetric.unwrap_or(false) && bidirectional_length.is_some() {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_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],
@ -153,7 +153,7 @@ 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Expected a non-empty sketch".to_owned(),
vec![args.source_range],
)));
@ -278,7 +278,7 @@ 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
format!(
"Expected a start cap ID for tag `{}` for extrusion of sketch {:?}",
tag_start.name, sketch.id
@ -298,7 +298,7 @@ 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
format!(
"Expected an end cap ID for tag `{}` for extrusion of sketch {:?}",
tag_end.name, sketch.id

View File

@ -49,7 +49,7 @@ pub(super) fn validate_unique<T: Eq + std::hash::Hash>(tags: &[(T, SourceRange)]
}
}
if !duplicate_tags_source.is_empty() {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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,
@ -85,14 +85,14 @@ async fn inner_fillet(
// If you try and tag multiple edges with a tagged fillet, 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 {
return Err(KclError::new_type(KclErrorDetails {
message: "You can only tag one edge at a time with a tagged fillet. Either delete the tag for the fillet fn if you don't need it OR separate into individual fillet functions for each tag.".to_string(),
source_ranges: vec![args.source_range],
backtrace: Default::default(),
}));
}
if tags.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
return Err(KclError::new_semantic(KclErrorDetails {
source_ranges: vec![args.source_range],
message: "You must fillet at least one tag".to_owned(),
backtrace: Default::default(),

View File

@ -33,7 +33,7 @@ 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
@ -41,7 +41,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
@ -49,7 +49,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix without a cylinder.".to_string(),
vec![args.source_range],
)));
@ -57,7 +57,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is not allowed when creating a helix with a cylinder.".to_string(),
vec![args.source_range],
)));
@ -65,7 +65,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Radius is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
@ -73,7 +73,7 @@ pub async fn helix(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
// 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::new(
return Err(KclError::new_semantic(crate::errors::KclErrorDetails::new(
"Axis is required when creating a helix around an axis.".to_string(),
vec![args.source_range],
)));
@ -140,7 +140,7 @@ async fn inner_helix(
Axis3dOrEdgeReference::Axis { direction, origin } => {
// Make sure they gave us a length.
let Some(length) = length else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Length is required when creating a helix around an axis.".to_owned(),
vec![args.source_range],
)));

View File

@ -68,7 +68,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Loft requires at least two sketches, but only {} were provided.",
sketches.len()

View File

@ -56,7 +56,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Attempt to take square root (`sqrt`) of a number less than zero ({})",
input.n

View File

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

View File

@ -45,17 +45,6 @@ pub type StdFn = fn(
lazy_static! {
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
Box::new(crate::std::segment::SegEnd),
Box::new(crate::std::segment::SegEndX),
Box::new(crate::std::segment::SegEndY),
Box::new(crate::std::segment::SegStart),
Box::new(crate::std::segment::SegStartX),
Box::new(crate::std::segment::SegStartY),
Box::new(crate::std::segment::LastSegX),
Box::new(crate::std::segment::LastSegY),
Box::new(crate::std::segment::SegLen),
Box::new(crate::std::segment::SegAng),
Box::new(crate::std::segment::TangentToEnd),
Box::new(crate::std::sketch::InvoluteCircular),
Box::new(crate::std::sketch::Line),
Box::new(crate::std::sketch::XLine),
@ -210,15 +199,15 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
),
("transform", "translate") => (
|e, a| Box::pin(crate::std::transform::translate(e, a)),
StdFnProps::default("std::transform::translate"),
StdFnProps::default("std::transform::translate").include_in_feature_tree(),
),
("transform", "rotate") => (
|e, a| Box::pin(crate::std::transform::rotate(e, a)),
StdFnProps::default("std::transform::rotate"),
StdFnProps::default("std::transform::rotate").include_in_feature_tree(),
),
("transform", "scale") => (
|e, a| Box::pin(crate::std::transform::scale(e, a)),
StdFnProps::default("std::transform::scale"),
StdFnProps::default("std::transform::scale").include_in_feature_tree(),
),
("prelude", "offsetPlane") => (
|e, a| Box::pin(crate::std::planes::offset_plane(e, a)),
@ -352,6 +341,50 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|e, a| Box::pin(crate::std::patterns::pattern_circular_2d(e, a)),
StdFnProps::default("std::sketch::patternCircular2d"),
),
("sketch", "segEnd") => (
|e, a| Box::pin(crate::std::segment::segment_end(e, a)),
StdFnProps::default("std::sketch::segEnd"),
),
("sketch", "segEndX") => (
|e, a| Box::pin(crate::std::segment::segment_end_x(e, a)),
StdFnProps::default("std::sketch::segEndX"),
),
("sketch", "segEndY") => (
|e, a| Box::pin(crate::std::segment::segment_end_y(e, a)),
StdFnProps::default("std::sketch::segEndY"),
),
("sketch", "segStart") => (
|e, a| Box::pin(crate::std::segment::segment_start(e, a)),
StdFnProps::default("std::sketch::segStart"),
),
("sketch", "segStartX") => (
|e, a| Box::pin(crate::std::segment::segment_start_x(e, a)),
StdFnProps::default("std::sketch::segStartX"),
),
("sketch", "segStartY") => (
|e, a| Box::pin(crate::std::segment::segment_start_y(e, a)),
StdFnProps::default("std::sketch::segStartY"),
),
("sketch", "lastSegX") => (
|e, a| Box::pin(crate::std::segment::last_segment_x(e, a)),
StdFnProps::default("std::sketch::lastSegX"),
),
("sketch", "lastSegY") => (
|e, a| Box::pin(crate::std::segment::last_segment_y(e, a)),
StdFnProps::default("std::sketch::lastSegY"),
),
("sketch", "segLen") => (
|e, a| Box::pin(crate::std::segment::segment_length(e, a)),
StdFnProps::default("std::sketch::segLen"),
),
("sketch", "segAng") => (
|e, a| Box::pin(crate::std::segment::segment_angle(e, a)),
StdFnProps::default("std::sketch::segAng"),
),
("sketch", "tangentToEnd") => (
|e, a| Box::pin(crate::std::segment::tangent_to_end(e, a)),
StdFnProps::default("std::sketch::tangentToEnd"),
),
("appearance", "hexString") => (
|e, a| Box::pin(crate::std::appearance::hex_string(e, a)),
StdFnProps::default("std::appearance::hexString"),

View File

@ -66,7 +66,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
@ -96,7 +96,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
@ -176,7 +176,7 @@ async fn send_pattern_transform<T: GeometryTrait>(
}
&mock_ids
} else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!("EntityLinearPattern response was not as expected: {:?}", resp),
vec![args.source_range],
)));
@ -222,7 +222,7 @@ 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::new(
KclError::new_semantic(KclErrorDetails::new(
"Transform function must return a value".to_string(),
source_ranges.clone(),
))
@ -233,7 +233,7 @@ async fn make_transform<T: GeometryTrait>(
let transforms: Vec<_> = value
.into_iter()
.map(|val| {
val.into_object().ok_or(KclError::Semantic(KclErrorDetails::new(
val.into_object().ok_or(KclError::new_semantic(KclErrorDetails::new(
"Transform function must return a transform object".to_string(),
source_ranges.clone(),
)))
@ -242,7 +242,7 @@ async fn make_transform<T: GeometryTrait>(
transforms
}
_ => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Transform function must return a transform object".to_string(),
source_ranges.clone(),
)))
@ -265,7 +265,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"The 'replicate' key must be a bool".to_string(),
source_ranges.clone(),
)));
@ -297,7 +297,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"The 'rotation' key must be an object (with optional fields 'angle', 'axis' and 'origin')".to_owned(),
source_ranges.clone(),
)));
@ -311,7 +311,7 @@ fn transform_from_obj_fields<T: GeometryTrait>(
rotation.angle = Angle::from_degrees(*number);
}
_ => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"The 'rotation.angle' key must be a number (of degrees)".to_owned(),
source_ranges.clone(),
)));
@ -345,7 +345,7 @@ fn array_to_point3d(
) -> Result<[TyF64; 3], KclError> {
val.coerce(&RuntimeType::point3d(), true, exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"Expected an array of 3 numbers (i.e., a 3D point), found {}",
e.found
@ -365,7 +365,7 @@ fn array_to_point2d(
) -> Result<[TyF64; 2], KclError> {
val.coerce(&RuntimeType::point2d(), true, exec_state)
.map_err(|e| {
KclError::Semantic(KclErrorDetails::new(
KclError::new_semantic(KclErrorDetails::new(
format!(
"Expected an array of 2 numbers (i.e., a 2D point), found {}",
e.found
@ -534,7 +534,7 @@ 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::new(
return Err(KclError::new_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],
@ -594,7 +594,7 @@ 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::new(
return Err(KclError::new_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],
@ -803,7 +803,7 @@ async fn inner_pattern_circular_2d(
.await?;
let Geometries::Sketches(new_sketches) = geometries else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected a vec of sketches".to_string(),
vec![args.source_range],
)));
@ -901,7 +901,7 @@ async fn inner_pattern_circular_3d(
.await?;
let Geometries::Solids(new_solids) = geometries else {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected a vec of solids".to_string(),
vec![args.source_range],
)));
@ -926,7 +926,7 @@ async fn pattern_circular(
return Ok(Geometries::from(geometry));
}
RepetitionsNeeded::Invalid => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
MUST_HAVE_ONE_INSTANCE.to_owned(),
vec![args.source_range],
)));
@ -971,7 +971,7 @@ async fn pattern_circular(
}
&mock_ids
} else {
return Err(KclError::Engine(KclErrorDetails::new(
return Err(KclError::new_engine(KclErrorDetails::new(
format!("EntityCircularPattern response was not as expected: {:?}", resp),
vec![args.source_range],
)));

View File

@ -75,7 +75,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Expected angle to be between -360 and 360 and not 0, found `{}`", angle),
vec![args.source_range],
)));
@ -87,7 +87,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Expected bidirectional angle to be between -360 and 360 and not 0, found `{}`",
bidirectional_angle
@ -99,7 +99,7 @@ async fn inner_revolve(
if let Some(angle) = angle {
let ang = angle.signum() * bidirectional_angle + angle;
if !(-360.0..=360.0).contains(&ang) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Combined angle and bidirectional must be between -360 and 360, found '{}'",
ang
@ -111,7 +111,7 @@ async fn inner_revolve(
}
if symmetric.unwrap_or(false) && bidirectional_angle.is_some() {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_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],

View File

@ -1,7 +1,6 @@
//! Functions related to line segments.
use anyhow::Result;
use kcl_derive_docs::stdlib;
use kittycad_modeling_cmds::shared::Angle;
use super::utils::untype_point;
@ -22,43 +21,10 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
}
/// Compute the ending point of the provided line segment.
///
/// ```no_run
/// w = 15
/// cube = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [w, 0], tag = $line1)
/// |> line(end = [0, w], tag = $line2)
/// |> line(end = [-w, 0], tag = $line3)
/// |> line(end = [0, -w], tag = $line4)
/// |> close()
/// |> extrude(length = 5)
///
/// fn cylinder(radius, tag) {
/// return startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> circle(radius = radius, center = segEnd(tag) )
/// |> extrude(length = radius)
/// }
///
/// cylinder(radius = 1, tag = line1)
/// cylinder(radius = 2, tag = line2)
/// cylinder(radius = 3, tag = line3)
/// cylinder(radius = 4, tag = line4)
/// ```
#[stdlib {
name = "segEnd",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -78,31 +44,10 @@ pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<Kcl
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Compute the ending point of the provided line segment along the 'x' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0], tag = $thing)
/// |> line(end = [0, 5])
/// |> line(end = [segEndX(thing), 0])
/// |> line(end = [-20, 10])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "segEndX",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -119,32 +64,10 @@ pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<Kcl
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Compute the ending point of the provided line segment along the 'y' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> line(end = [0, 3], tag = $thing)
/// |> line(end = [-10, 0])
/// |> line(end = [0, segEndY(thing)])
/// |> line(end = [-10, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "segEndY",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -161,43 +84,10 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
}
/// Compute the starting point of the provided line segment.
///
/// ```no_run
/// w = 15
/// cube = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [w, 0], tag = $line1)
/// |> line(end = [0, w], tag = $line2)
/// |> line(end = [-w, 0], tag = $line3)
/// |> line(end = [0, -w], tag = $line4)
/// |> close()
/// |> extrude(length = 5)
///
/// fn cylinder(radius, tag) {
/// return startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> circle( radius = radius, center = segStart(tag) )
/// |> extrude(length = radius)
/// }
///
/// cylinder(radius = 1, tag = line1)
/// cylinder(radius = 2, tag = line2)
/// cylinder(radius = 3, tag = line3)
/// cylinder(radius = 4, tag = line4)
/// ```
#[stdlib {
name = "segStart",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -217,31 +107,10 @@ pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<K
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Compute the starting point of the provided line segment along the 'x' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0], tag = $thing)
/// |> line(end = [0, 5])
/// |> line(end = [20 - segStartX(thing), 0])
/// |> line(end = [-20, 10])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "segStartX",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -258,32 +127,10 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<K
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Compute the starting point of the provided line segment along the 'y' axis.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> line(end = [0, 3], tag = $thing)
/// |> line(end = [-10, 0])
/// |> line(end = [0, 20-segStartY(thing)])
/// |> line(end = [-10, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "segStartY",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -300,34 +147,12 @@ pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<Kc
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Extract the 'x' axis value of the last line segment in the provided 2-d
/// sketch.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [5, 0])
/// |> line(end = [20, 5])
/// |> line(end = [lastSegX(%), 0])
/// |> line(end = [-15, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "lastSegX",
unlabeled_first = true,
args = {
sketch = { docs = "The sketch whose line segment is being queried"},
},
tags = ["sketch"]
}]
fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
let last_line = sketch
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
vec![args.source_range],
))
@ -346,34 +171,12 @@ pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<Kc
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Extract the 'y' axis value of the last line segment in the provided 2-d
/// sketch.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [5, 0])
/// |> line(end = [20, 5])
/// |> line(end = [0, lastSegY(%)])
/// |> line(end = [-15, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "lastSegY",
unlabeled_first = true,
args = {
sketch = { docs = "The sketch whose line segment is being queried"},
},
tags = ["sketch"]
}]
fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
let last_line = sketch
.paths
.last()
.ok_or_else(|| {
KclError::Type(KclErrorDetails::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a Sketch with at least one segment, found `{:?}`", sketch),
vec![args.source_range],
))
@ -390,37 +193,10 @@ pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<Kc
Ok(args.make_user_val_from_f64_with_type(result))
}
/// Compute the length of the provided line segment.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> angledLine(
/// angle = 60,
/// length = 10,
/// tag = $thing,
/// )
/// |> tangentialArc(angle = -120, radius = 5)
/// |> angledLine(
/// angle = -60,
/// length = segLen(thing),
/// )
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
#[stdlib {
name = "segLen",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -437,33 +213,10 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
/// Compute the angle (in degrees) of the provided line segment.
///
/// ```no_run
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0])
/// |> line(end = [5, 10], tag = $seg01)
/// |> line(end = [-10, 0])
/// |> angledLine(angle = segAng(seg01), length = 10)
/// |> line(end = [-10, 0])
/// |> angledLine(angle = segAng(seg01), length = -15)
/// |> close()
///
/// example = extrude(exampleSketch, length = 4)
/// ```
#[stdlib {
name = "segAng",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))
@ -482,89 +235,10 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<Kc
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
}
/// Returns the angle coming out of the end of the segment in degrees.
///
/// ```no_run
/// // Horizontal pill.
/// pillSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> tangentialArc(end = [0, 10], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArc(end = [0, -10])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)
/// ```
///
/// ```no_run
/// // Vertical pill. Use absolute coordinate for arc.
/// pillSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [0, 20])
/// |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArc(end = [-10, 0])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)
/// ```
///
/// ```no_run
/// rectangleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0], tag = $seg1)
/// |> angledLine(
/// angle = tangentToEnd(seg1),
/// length = 10,
/// )
/// |> line(end = [0, 10])
/// |> line(end = [-20, 0])
/// |> close()
///
/// rectangleExtrude = extrude(rectangleSketch, length = 10)
/// ```
///
/// ```no_run
/// bottom = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> arc(
/// endAbsolute = [10, 10],
/// interiorAbsolute = [5, 1],
/// tag = $arc1,
/// )
/// |> angledLine(angle = tangentToEnd(arc1), length = 20)
/// |> close()
/// ```
///
/// ```no_run
/// circSketch = startSketchOn(XY)
/// |> circle( center= [0, 0], radius= 3 , tag= $circ)
///
/// triangleSketch = startSketchOn(XY)
/// |> startProfile(at = [-5, 0])
/// |> angledLine(angle = tangentToEnd(circ), length = 10)
/// |> line(end = [-15, 0])
/// |> close()
/// ```
#[stdlib {
name = "tangentToEnd",
unlabeled_first = true,
args = {
tag = { docs = "The line segment being queried by its tag"},
},
tags = ["sketch"]
}]
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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected a line segment with a path, found `{:?}`", line),
vec![args.source_range],
))

View File

@ -271,14 +271,14 @@ async fn inner_polygon(
args: Args,
) -> Result<Sketch, KclError> {
if num_sides < 3 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Radius must be greater than 0".to_string(),
vec![args.source_range],
)));
@ -407,11 +407,11 @@ pub(crate) fn get_radius(
match (radius, diameter) {
(Some(radius), None) => Ok(radius),
(None, Some(diameter)) => Ok(TyF64::new(diameter.n / 2.0, diameter.ty)),
(None, None) => Err(KclError::Type(KclErrorDetails::new(
(None, None) => Err(KclError::new_type(KclErrorDetails::new(
"This function needs either `diameter` or `radius`".to_string(),
vec![source_range],
))),
(Some(_), Some(_)) => Err(KclError::Type(KclErrorDetails::new(
(Some(_), Some(_)) => Err(KclError::new_type(KclErrorDetails::new(
"You cannot specify both `diameter` and `radius`, please remove one".to_string(),
vec![source_range],
))),

View File

@ -36,14 +36,14 @@ async fn inner_shell(
args: Args,
) -> Result<Vec<Solid>, KclError> {
if faces.is_empty() {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"You must shell at least one solid".to_owned(),
vec![args.source_range],
)));
@ -63,7 +63,7 @@ async fn inner_shell(
}
if face_ids.is_empty() {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Expected at least one valid face".to_owned(),
vec![args.source_range],
)));
@ -72,7 +72,7 @@ async fn inner_shell(
// 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"All solids stem from the same root object, like multiple sketch on face extrusions, etc.".to_owned(),
vec![args.source_range],
)));

View File

@ -12,6 +12,7 @@ use parse_display::{Display, FromStr};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use super::shapes::get_radius;
#[cfg(feature = "artifact-graph")]
use crate::execution::{Artifact, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane};
use crate::{
@ -32,8 +33,6 @@ use crate::{
},
};
use super::shapes::get_radius;
/// A tag for a face.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
@ -66,13 +65,13 @@ 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::new(
KclError::new_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::new(
KclError::new_type(KclErrorDetails::new(
"Expected an end face".to_string(),
vec![args.source_range],
))
@ -329,7 +328,7 @@ 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::new(
return Err(KclError::new_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],
)));
@ -337,7 +336,7 @@ async fn straight_line(
(Some(end_absolute), None) => (end_absolute, true),
(None, Some(end)) => (end, false),
(None, None) => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("You must supply either `{relative_name}` or `endAbsolute` arguments"),
vec![args.source_range],
)));
@ -604,7 +603,7 @@ async fn inner_angled_line(
.filter(|x| x.is_some())
.count();
if options_given > 1 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
" one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_string(),
vec![args.source_range],
)));
@ -632,11 +631,11 @@ 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::new(
(None, None, None, None, None) => Err(KclError::new_type(KclErrorDetails::new(
"One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` must be given".to_string(),
vec![args.source_range],
))),
_ => Err(KclError::Type(KclErrorDetails::new(
_ => Err(KclError::new_type(KclErrorDetails::new(
"Only One of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given".to_owned(),
vec![args.source_range],
))),
@ -710,14 +709,14 @@ async fn inner_angled_line_of_x_length(
args: Args,
) -> Result<Sketch, KclError> {
if angle_degrees.abs() == 270.0 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Cannot have an x constrained angle of 90 degrees".to_string(),
vec![args.source_range],
)));
@ -742,14 +741,14 @@ async fn inner_angled_line_to_x(
let from = sketch.current_pen_position()?;
if angle_degrees.abs() == 270.0 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Cannot have an x constrained angle of 90 degrees".to_string(),
vec![args.source_range],
)));
@ -777,14 +776,14 @@ async fn inner_angled_line_of_y_length(
args: Args,
) -> Result<Sketch, KclError> {
if angle_degrees.abs() == 0.0 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Cannot have a y constrained angle of 180 degrees".to_string(),
vec![args.source_range],
)));
@ -809,14 +808,14 @@ async fn inner_angled_line_to_y(
let from = sketch.current_pen_position()?;
if angle_degrees.abs() == 0.0 {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Cannot have a y constrained angle of 180 degrees".to_string(),
vec![args.source_range],
)));
@ -893,7 +892,7 @@ 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::new(
KclError::new_type(KclErrorDetails::new(
format!("Expected an intersect path with a path, found `{:?}`", intersect_path),
vec![args.source_range],
))
@ -1170,7 +1169,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Origin of plane has unknown units".to_string(),
vec![args.source_range],
)));
@ -1194,7 +1193,7 @@ async fn inner_start_sketch_on(
}
SketchData::Solid(solid) => {
let Some(tag) = face else {
return Err(KclError::Type(KclErrorDetails::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Expected a tag for the face to sketch on".to_string(),
vec![args.source_range],
)));
@ -1544,6 +1543,11 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
/// Construct a line segment from the current origin back to the profile's
/// origin, ensuring the resulting 2-dimensional sketch is not open-ended.
///
/// If you want to perform some 3-dimensional operation on a sketch, like
/// extrude or sweep, you must `close` it first. `close` must be called even
/// if the end point of the last segment is coincident with the sketch
/// starting point.
///
/// ```no_run
/// startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
@ -1713,7 +1717,7 @@ pub(crate) async fn inner_arc(
absolute_arc(&args, id, exec_state, sketch, from, interior_absolute, end_absolute, tag).await
}
_ => {
Err(KclError::Type(KclErrorDetails::new(
Err(KclError::new_type(KclErrorDetails::new(
"Invalid combination of arguments. Either provide (angleStart, angleEnd, radius) or (endAbsolute, interiorAbsolute)".to_owned(),
vec![args.source_range],
)))
@ -1800,7 +1804,7 @@ 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::new(
return Err(KclError::new_type(KclErrorDetails::new(
"Arc start and end angles must be different".to_string(),
vec![args.source_range],
)));
@ -1968,11 +1972,11 @@ 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, None) => Err(KclError::Semantic(KclErrorDetails::new(
(Some(_), Some(_), None, None, None) => Err(KclError::new_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],
))),
(_, _, _, _, _) => Err(KclError::Semantic(KclErrorDetails::new(
(_, _, _, _, _) => Err(KclError::new_semantic(KclErrorDetails::new(
"You must supply `end`, `endAbsolute`, or both `angle` and `radius`/`diameter` arguments".to_owned(),
vec![args.source_range],
))),
@ -2126,13 +2130,13 @@ async fn inner_tangential_arc_to_point(
});
if result.center[0].is_infinite() {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_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::new(
return Err(KclError::new_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],
@ -2310,7 +2314,7 @@ async fn inner_bezier_curve(
to
}
_ => {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"You must either give `control1`, `control2` and `end`, or `control1Absolute`, `control2Absolute` and `endAbsolute`.".to_owned(),
vec![args.source_range],
)));

View File

@ -21,6 +21,7 @@ use crate::{
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum SweepPath {
Sketch(Sketch),
Helix(Box<Helix>),
@ -75,7 +76,7 @@ async fn inner_sweep(
Some("sketchPlane") => RelativeTo::SketchPlane,
Some("trajectoryCurve") | None => RelativeTo::TrajectoryCurve,
Some(_) => {
return Err(KclError::Syntax(crate::errors::KclErrorDetails::new(
return Err(KclError::new_syntax(crate::errors::KclErrorDetails::new(
"If you provide relativeTo, it must either be 'sketchPlane' or 'trajectoryCurve'".to_owned(),
vec![args.source_range],
)))

View File

@ -37,7 +37,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `x`, `y`, or `z` to be provided.".to_string(),
vec![args.source_range],
)));
@ -119,7 +119,7 @@ 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `x`, `y`, or `z` to be provided.".to_string(),
vec![args.source_range],
)));
@ -202,7 +202,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `roll`, `pitch`, and `yaw` or `axis` and `angle` to be provided.".to_string(),
vec![args.source_range],
)));
@ -212,7 +212,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `axis` and `angle` to not be provided when `roll`, `pitch`, and `yaw` are provided."
.to_owned(),
vec![args.source_range],
@ -223,13 +223,13 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// 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::new(
return Err(KclError::new_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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `angle` to be provided when `axis` is provided.".to_string(),
vec![args.source_range],
)));
@ -237,7 +237,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
"Expected `roll`, `pitch`, and `yaw` to not be provided when `axis` and `angle` are provided."
.to_owned(),
vec![args.source_range],
@ -248,7 +248,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// 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::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Expected roll to be between -360 and 360, found `{}`", roll.n),
vec![args.source_range],
)));
@ -256,7 +256,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}
if let Some(pitch) = &pitch {
if !(-360.0..=360.0).contains(&pitch.n) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Expected pitch to be between -360 and 360, found `{}`", pitch.n),
vec![args.source_range],
)));
@ -264,7 +264,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
}
if let Some(yaw) = &yaw {
if !(-360.0..=360.0).contains(&yaw.n) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Expected yaw to be between -360 and 360, found `{}`", yaw.n),
vec![args.source_range],
)));
@ -274,7 +274,7 @@ pub async fn rotate(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
// Validate the axis and angle values.
if let Some(angle) = &angle {
if !(-360.0..=360.0).contains(&angle.n) {
return Err(KclError::Semantic(KclErrorDetails::new(
return Err(KclError::new_semantic(KclErrorDetails::new(
format!("Expected angle to be between -360 and 360, found `{}`", angle.n),
vec![args.source_range],
)));

View File

@ -93,7 +93,7 @@ async fn do_execute_and_snapshot(
for e in exec_state.errors() {
if e.severity.is_err() {
return Err(ExecErrorWithState::new(
KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
KclErrorWithOutputs::no_outputs(KclError::new_semantic(e.clone().into())).into(),
exec_state.clone(),
));
}
@ -164,7 +164,7 @@ pub async fn execute_and_export_step(
for e in exec_state.errors() {
if e.severity.is_err() {
return Err(ExecErrorWithState::new(
KclErrorWithOutputs::no_outputs(KclError::Semantic(e.clone().into())).into(),
KclErrorWithOutputs::no_outputs(KclError::new_semantic(e.clone().into())).into(),
exec_state.clone(),
));
}

View File

@ -883,7 +883,7 @@ 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::new(
crate::KclError::new_internal(crate::errors::KclErrorDetails::new(
format!("Failed to walk directory `{}`: {:?}", dir.display(), err),
vec![crate::SourceRange::default()],
))
@ -912,7 +912,7 @@ pub async fn recast_dir(dir: &std::path::Path, options: &crate::FormatOptions) -
if ce.severity != crate::errors::Severity::Warning {
let report = crate::Report {
kcl_source: contents.to_string(),
error: crate::KclError::Semantic(ce.clone().into()),
error: crate::KclError::new_semantic(ce.clone().into()),
filename: file.to_string_lossy().to_string(),
};
let report = miette::Report::new(report);

View File

@ -96,7 +96,7 @@ 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::new(
return Err(KclError::new_import_cycle(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
vec![SourceRange::default()],
@ -146,7 +146,7 @@ pub(crate) fn import_dependencies(
// This is a bit of a hack, but it works for now.
ret.lock()
.map_err(|err| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
@ -156,7 +156,7 @@ pub(crate) fn import_dependencies(
ImportPath::Foreign { path } => {
ret.lock()
.map_err(|err| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
@ -178,7 +178,7 @@ pub(crate) fn import_dependencies(
walk(ret.clone(), prog.into(), path, ctx)?;
let ret = ret.lock().map_err(|err| {
KclError::Internal(KclErrorDetails::new(
KclError::new_internal(KclErrorDetails::new(
format!("Failed to lock mutex: {}", err),
Default::default(),
))
@ -223,7 +223,7 @@ pub(crate) async fn import_universe(
let repr = {
let Some(module_info) = exec_state.get_module(module_id) else {
return Err(KclError::Internal(KclErrorDetails::new(
return Err(KclError::new_internal(KclErrorDetails::new(
format!("Module {} not found", module_id),
vec![import_stmt.into()],
)));

View File

@ -895,3 +895,304 @@ export fn patternCircular2d(
/// If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid.
useOriginal?: bool = false,
): [Sketch; 1+] {}
/// Compute the ending point of the provided line segment.
///
/// ```kcl
/// w = 15
/// cube = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [w, 0], tag = $line1)
/// |> line(end = [0, w], tag = $line2)
/// |> line(end = [-w, 0], tag = $line3)
/// |> line(end = [0, -w], tag = $line4)
/// |> close()
/// |> extrude(length = 5)
///
/// fn cylinder(radius, tag) {
/// return startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> circle(radius = radius, center = segEnd(tag) )
/// |> extrude(length = radius)
/// }
///
/// cylinder(radius = 1, tag = line1)
/// cylinder(radius = 2, tag = line2)
/// cylinder(radius = 3, tag = line3)
/// cylinder(radius = 4, tag = line4)
/// ```
@(impl = std_rust)
export fn segEnd(
/// The line segment being queried by its tag.
@tag: tag,
): Point2d {}
/// Compute the ending point of the provided line segment along the 'x' axis.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0], tag = $thing)
/// |> line(end = [0, 5])
/// |> line(end = [segEndX(thing), 0])
/// |> line(end = [-20, 10])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn segEndX(
/// The line segment being queried by its tag.
@tag: tag,
): number(Length) {}
/// Compute the ending point of the provided line segment along the 'y' axis.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> line(end = [0, 3], tag = $thing)
/// |> line(end = [-10, 0])
/// |> line(end = [0, segEndY(thing)])
/// |> line(end = [-10, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn segEndY(
/// The line segment being queried by its tag.
@tag: tag,
): number(Length) {}
/// Compute the starting point of the provided line segment.
///
/// ```kcl
/// w = 15
/// cube = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> line(end = [w, 0], tag = $line1)
/// |> line(end = [0, w], tag = $line2)
/// |> line(end = [-w, 0], tag = $line3)
/// |> line(end = [0, -w], tag = $line4)
/// |> close()
/// |> extrude(length = 5)
///
/// fn cylinder(radius, tag) {
/// return startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> circle( radius = radius, center = segStart(tag) )
/// |> extrude(length = radius)
/// }
///
/// cylinder(radius = 1, tag = line1)
/// cylinder(radius = 2, tag = line2)
/// cylinder(radius = 3, tag = line3)
/// cylinder(radius = 4, tag = line4)
/// ```
@(impl = std_rust)
export fn segStart(
/// The line segment being queried by its tag.
@tag: tag,
): Point2d {}
/// Compute the starting point of the provided line segment along the 'x' axis.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0], tag = $thing)
/// |> line(end = [0, 5])
/// |> line(end = [20 - segStartX(thing), 0])
/// |> line(end = [-20, 10])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn segStartX(
/// The line segment being queried by its tag.
@tag: tag,
): number(Length) {}
/// Compute the starting point of the provided line segment along the 'y' axis.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> line(end = [0, 3], tag = $thing)
/// |> line(end = [-10, 0])
/// |> line(end = [0, 20-segStartY(thing)])
/// |> line(end = [-10, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn segStartY(
/// The line segment being queried by its tag.
@tag: tag,
): number(Length) {}
/// Extract the 'x' axis value of the last line segment in the provided 2-d sketch.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [5, 0])
/// |> line(end = [20, 5])
/// |> line(end = [lastSegX(%), 0])
/// |> line(end = [-15, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn lastSegX(
/// The sketch whose line segment is being queried.
@sketch: Sketch,
): number(Length) {}
/// Extract the 'y' axis value of the last line segment in the provided 2-d sketch.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [5, 0])
/// |> line(end = [20, 5])
/// |> line(end = [0, lastSegY(%)])
/// |> line(end = [-15, 0])
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn lastSegY(
/// The sketch whose line segment is being queried.
@sketch: Sketch,
): number(Length) {}
/// Compute the length of the provided line segment.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> angledLine(
/// angle = 60,
/// length = 10,
/// tag = $thing,
/// )
/// |> tangentialArc(angle = -120, radius = 5)
/// |> angledLine(
/// angle = -60,
/// length = segLen(thing),
/// )
/// |> close()
///
/// example = extrude(exampleSketch, length = 5)
/// ```
@(impl = std_rust)
export fn segLen(
/// The line segment being queried by its tag.
@tag: tag,
): number(Length) {}
/// Compute the angle (in degrees) of the provided line segment.
///
/// ```kcl
/// exampleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0])
/// |> line(end = [5, 10], tag = $seg01)
/// |> line(end = [-10, 0])
/// |> angledLine(angle = segAng(seg01), length = 10)
/// |> line(end = [-10, 0])
/// |> angledLine(angle = segAng(seg01), length = -15)
/// |> close()
///
/// example = extrude(exampleSketch, length = 4)
/// ```
@(impl = std_rust)
export fn segAng(
/// The line segment being queried by its tag.
@tag: tag,
): number(Angle) {}
/// Returns the angle coming out of the end of the segment in degrees.
///
/// ```kcl
/// // Horizontal pill.
/// pillSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [20, 0])
/// |> tangentialArc(end = [0, 10], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArc(end = [0, -10])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)
/// ```
///
/// ```kcl
/// // Vertical pill. Use absolute coordinate for arc.
/// pillSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [0, 20])
/// |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
/// |> angledLine(
/// angle = tangentToEnd(arc1),
/// length = 20,
/// )
/// |> tangentialArc(end = [-10, 0])
/// |> close()
///
/// pillExtrude = extrude(pillSketch, length = 10)
/// ```
///
/// ```kcl
/// rectangleSketch = startSketchOn(XZ)
/// |> startProfile(at = [0, 0])
/// |> line(end = [10, 0], tag = $seg1)
/// |> angledLine(
/// angle = tangentToEnd(seg1),
/// length = 10,
/// )
/// |> line(end = [0, 10])
/// |> line(end = [-20, 0])
/// |> close()
///
/// rectangleExtrude = extrude(rectangleSketch, length = 10)
/// ```
///
/// ```kcl
/// bottom = startSketchOn(XY)
/// |> startProfile(at = [0, 0])
/// |> arc(
/// endAbsolute = [10, 10],
/// interiorAbsolute = [5, 1],
/// tag = $arc1,
/// )
/// |> angledLine(angle = tangentToEnd(arc1), length = 20)
/// |> close()
/// ```
///
/// ```kcl
/// circSketch = startSketchOn(XY)
/// |> circle(center = [0, 0], radius= 3, tag = $circ)
///
/// triangleSketch = startSketchOn(XY)
/// |> startProfile(at = [-5, 0])
/// |> angledLine(angle = tangentToEnd(circ), length = 10)
/// |> line(end = [-15, 0])
/// |> close()
/// ```
@(impl = std_rust)
export fn tangentToEnd(
/// The line segment being queried by its tag.
@tag: tag,
): number(Angle) {}

View File

@ -0,0 +1,32 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Artifact commands ascription_unknown_type.kcl
---
[
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "edge_lines_visible",
"hidden": false
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
},
{
"cmdId": "[uuid]",
"range": [],
"command": {
"type": "object_visible",
"object_id": "[uuid]",
"hidden": true
}
}
]

View File

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

View File

@ -0,0 +1,3 @@
```mermaid
flowchart LR
```

View File

@ -0,0 +1,67 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of parsing ascription_unknown_type.kcl
---
{
"Ok": {
"body": [
{
"commentStart": 0,
"declaration": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"name": "z",
"start": 0,
"type": "Identifier"
},
"init": {
"commentStart": 0,
"end": 0,
"expr": {
"commentStart": 0,
"end": 0,
"raw": "10",
"start": 0,
"type": "Literal",
"type": "Literal",
"value": {
"value": 10.0,
"suffix": "None"
}
},
"start": 0,
"ty": {
"commentStart": 0,
"end": 0,
"id": {
"commentStart": 0,
"end": 0,
"name": "NotARealType",
"start": 0,
"type": "Identifier"
},
"p_type": "Named",
"start": 0,
"type": "Primitive"
},
"type": "AscribedExpression",
"type": "AscribedExpression"
},
"start": 0,
"type": "VariableDeclarator"
},
"end": 0,
"kind": "const",
"start": 0,
"type": "VariableDeclaration",
"type": "VariableDeclaration"
}
],
"commentStart": 0,
"end": 0,
"start": 0
}
}

View File

@ -0,0 +1,12 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Error from executing ascription_unknown_type.kcl
---
KCL Semantic error
× semantic: Unknown type: NotARealType
╭────
1 │ z = 10: NotARealType
· ─┬
· ╰── tests/ascription_unknown_type/input.kcl
╰────

View File

@ -0,0 +1 @@
z = 10: NotARealType

View File

@ -0,0 +1,5 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Operations executed ascription_unknown_type.kcl
---
[]

View File

@ -0,0 +1,5 @@
---
source: kcl-lib/src/simulation_tests.rs
description: Result of unparsing ascription_unknown_type.kcl
---
z = 10: NotARealType

View File

@ -797,6 +797,36 @@ description: Operations executed import_async.kcl
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"y": {
"value": {
"type": "Number",
"value": 10.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "GroupEnd"
},

View File

@ -37,6 +37,7 @@ flowchart LR
25["SweepEdge Adjacent"]
26["SweepEdge Adjacent"]
27["SweepEdge Adjacent"]
28["SweepEdge Adjacent"]
1 --- 2
2 --- 3
2 --- 4
@ -50,28 +51,28 @@ flowchart LR
2 ---- 12
12 <--x 3
3 --- 16
3 x--> 21
3 --- 21
12 <--x 4
4 --- 15
4 --- 21
4 --- 22
12 <--x 5
5 --- 13
5 --- 22
5 --- 23
12 <--x 6
6 --- 20
6 --- 23
6 --- 24
12 <--x 7
7 --- 17
7 --- 24
7 --- 25
12 <--x 8
8 --- 19
8 --- 25
8 --- 26
12 <--x 9
9 --- 14
9 --- 26
9 --- 27
12 <--x 10
10 --- 18
10 --- 27
10 --- 28
12 --- 13
12 --- 14
12 --- 15
@ -87,18 +88,21 @@ flowchart LR
12 --- 25
12 --- 26
12 --- 27
13 --- 22
25 <--x 14
14 --- 26
15 --- 21
12 --- 28
22 <--x 13
13 --- 23
26 <--x 14
14 --- 27
21 <--x 15
15 --- 22
16 --- 21
27 <--x 16
23 <--x 17
17 --- 24
26 <--x 18
18 --- 27
24 <--x 19
19 --- 25
22 <--x 20
20 --- 23
28 <--x 16
24 <--x 17
17 --- 25
27 <--x 18
18 --- 28
25 <--x 19
19 --- 26
23 <--x 20
20 --- 24
```

View File

@ -14,7 +14,7 @@ description: Operations executed import_mesh_clone.kcl
},
{
"type": "StdLibCall",
"name": "clone",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
@ -22,7 +22,40 @@ description: Operations executed import_mesh_clone.kcl
},
"sourceRange": []
},
"labeledArgs": {},
"labeledArgs": {
"x": {
"value": {
"type": "Number",
"value": -2000.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"y": {
"value": {
"type": "Number",
"value": -2000.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
@ -38,6 +71,36 @@ description: Operations executed import_mesh_clone.kcl
"labeledArgs": {},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"x": {
"value": {
"type": "Number",
"value": 4000.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "clone",
@ -51,6 +114,79 @@ description: Operations executed import_mesh_clone.kcl
"labeledArgs": {},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"y": {
"value": {
"type": "Number",
"value": 4000.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "clone",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"y": {
"value": {
"type": "Number",
"value": 4000.0,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "GroupEnd"
}

View File

@ -12,6 +12,177 @@ description: Operations executed import_transform.kcl
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "rotate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"pitch": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"roll": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"yaw": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "translate",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"x": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"y": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
},
"z": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Default",
"len": {
"type": "Mm"
},
"angle": {
"type": "Degrees"
}
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "StdLibCall",
"name": "scale",
"unlabeledArg": {
"value": {
"type": "ImportedGeometry",
"artifact_id": "[uuid]"
},
"sourceRange": []
},
"labeledArgs": {
"x": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"y": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
},
"z": {
"value": {
"type": "Number",
"value": 3.14,
"ty": {
"type": "Known",
"type": "Count"
}
},
"sourceRange": []
}
},
"sourceRange": []
},
{
"type": "GroupEnd"
}

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