Compare commits

...

14 Commits

Author SHA1 Message Date
30afa65ccf Cut release v0.27.0 (#4516)
* Cut release v0.26.6

* Cut release v0.27.0
2024-11-20 10:09:53 -06:00
a2f9e70d18 breaking change: Change "type" to be a keyword, import() "type:" parameter to "format:" (#4517) 2024-11-20 14:53:37 +00:00
986675fe89 Fix formatting for nested function returns (#4518)
Previously, this was the output of the formatter:

```
fn f = () => {
  return () => {
  return 1
}
}
```

Now the above will be reformatted as

```
fn f = () => {
  return () => {
    return 1
  }
}
```

Much better!
2024-11-20 09:23:30 -05:00
d8ce5ad8bd Make most top-level modules in KCL private (#4478)
* Make ast module private

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

* Make most other modules private

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

* Expand API to support CLI, Python bindings, and LSP crate

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-11-20 15:19:25 +13:00
1a9926be8a Fix to not have unneeded console errors (#4482)
* Fix to not have unneeded console errors

* Change to use a type that isn't string
2024-11-19 17:34:54 -05:00
max
54b5774f9e Add Support for Fillet with Extrude in the Sketch Pipe (#4168)
* update code mod

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest)

* fmt

* lint

* make yarn-tsc happy

* fmt

* typo

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2024-11-19 21:30:26 +01:00
66bbbf81e2 Pass current file name through to export command (#4503)
* Pass current file name through to export command

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Oops I needed a couple other things, not just that one line change

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* Undo overriding of internal zipped file names
That was liable to cause conflicts and whatnot per @jessfraz feedback

* Update E2E test that was still looking for `output.gltf`

* Missed one other test my bad

* Should've just grepped for output.gltf to begin with

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paul Tagliamonte <paul@zoo.dev>
2024-11-19 11:30:23 -05:00
652519aeae fix: center rectangle code writing bug (#4512)
fix: bug that did not write the code to the editor when the workflow finished
2024-11-19 11:10:08 -05:00
f826afb32d Clear code mirror history on file change (#4510)
* clear history when loading a new file

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

* updates

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

* updates

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

* updates

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-11-19 02:08:22 +00:00
f71fafdece breaking change: Add more KCL reserved words, part 1 (#4502) 2024-11-19 00:54:25 +00:00
16b7544d69 fix missing docs files (#4509)
* fix missing docs files

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

* fix justfile

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

* fix justfile

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

---------

Signed-off-by: Jess Frazelle <github@jessfraz.com>
2024-11-18 22:58:33 +00:00
34f019305b Move tests into simulation (#4487)
Replace wasm-lib integration tests with KCL simulation tests.
2024-11-18 22:20:32 +00:00
79e06b3a00 Nadro/adhoc/stdlib arcTo (three point arc) (#4485)
* feat: implementing arcTo in standard library, first pass

* feat: computing center and radius for arcto

* fix: updating comment for arcTo

* fix: cargo fmt fix

* fix: bug, the x was used twice!

* fix: Cleaning up some code and adding more comments

* fix: this has to be removed

* fix: resolved merge conflicts with main and updated the codebase to remove the JSON stuff

* fix: addressing cargo clippy issues

* fix: typos

* fix: adding generated docs

* Update doc test snapshots

---------

Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-11-18 22:17:16 +00:00
24bc4fcd8c Show offset planes in the scene, let user select them (#4481)
* Update offset_plane to actually create and show the plane in-engine

* Fix broken ability to use offsetPlanes in startSketchOn

* Make the newly-visible offset planes usable for sketching via UI

* Add a playwright test for sketching on an offset plane via point-and-click

* cargo clippy & cargo fmt

* Make `PlaneData` the first item of `SketchData` so autocomplete continues to work well for `startSketchOn`

* @nadr0 feedback re: `offsetIndex`

* From @jtran: "Need to call the ID generator so that IDs are stable."

* More feedback from @jtran and fix incomplete use of `id_generator` in last commit

* Oops I missed saving `isPathToNodeNumber` earlier 🤦🏻

* Make the distinction between `Plane` and `PlaneOrientationData` more clear per @nadr0 and @lf94's feedback

* Make `newPathToNode` less hardcoded, per @lf94's feedback

* Don't need to unbox and rebox `plane`

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Rearranging of enums and structs, but the offsetPlanes are still not used by their sketches

* A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores)

* A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-latest-8-cores)

* Revert all my little newtype fiddling it's a waste of time.

* Update docs

* cargo fmt

* Remove log

* Print the unexpected diagnostics

* Undo renaming of `PlaneData`

* Remove generated PlaneRientationData docs page

* Redo doc generation after undoing `PlaneData` rename

* Impl FromKclValue for the new plane datatypes

* Clippy lint

* When starting a sketch, only hide the plane if it's a custom plane

* Fix FromKclValue and macro use since merge

* Fix to not convert Plane to PlaneData

* Make sure offset planes are `Custom` type

* SketchData actually doesn't need to be in a certain order
This avoids the autocompletion issue I was having.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2024-11-18 16:25:25 -05:00
323 changed files with 369291 additions and 3365 deletions

File diff suppressed because one or more lines are too long

41
docs/kcl/arcTo.md Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -20,6 +20,7 @@ layout: manual
* [`angledLineToX`](kcl/angledLineToX) * [`angledLineToX`](kcl/angledLineToX)
* [`angledLineToY`](kcl/angledLineToY) * [`angledLineToY`](kcl/angledLineToY)
* [`arc`](kcl/arc) * [`arc`](kcl/arc)
* [`arcTo`](kcl/arcTo)
* [`asin`](kcl/asin) * [`asin`](kcl/asin)
* [`assert`](kcl/assert) * [`assert`](kcl/assert)
* [`assertEqual`](kcl/assertEqual) * [`assertEqual`](kcl/assertEqual)

File diff suppressed because one or more lines are too long

View File

@ -31,7 +31,7 @@ map(array: [KclValue], map_fn: FunctionParam) -> [KclValue]
r = 10 // radius r = 10 // radius
fn drawCircle = (id) => { fn drawCircle = (id) => {
return startSketchOn("XY") return startSketchOn("XY")
|> circle({ center: [id * 2 * r, 0], radius: r }, %) |> circle({ center: [id * 2 * r, 0], radius: r }, %)
} }
// Call `drawCircle`, passing in each element of the array. // Call `drawCircle`, passing in each element of the array.
@ -47,7 +47,7 @@ r = 10 // radius
// Call `map`, using an anonymous function instead of a named one. // Call `map`, using an anonymous function instead of a named one.
circles = map([1..3], (id) => { circles = map([1..3], (id) => {
return startSketchOn("XY") return startSketchOn("XY")
|> circle({ center: [id * 2 * r, 0], radius: r }, %) |> circle({ center: [id * 2 * r, 0], radius: r }, %)
}) })
``` ```

View File

@ -9,7 +9,7 @@ Offset a plane by a distance along its normal.
For example, if you offset the 'XZ' plane by 10, the new plane will be parallel to the 'XZ' plane and 10 units away from it. For example, if you offset the 'XZ' plane by 10, the new plane will be parallel to the 'XZ' plane and 10 units away from it.
```js ```js
offsetPlane(std_plane: StandardPlane, offset: number) -> PlaneData offsetPlane(std_plane: StandardPlane, offset: number) -> Plane
``` ```
@ -22,7 +22,7 @@ offsetPlane(std_plane: StandardPlane, offset: number) -> PlaneData
### Returns ### Returns
[`PlaneData`](/docs/kcl/types/PlaneData) - Data for a plane. [`Plane`](/docs/kcl/types/Plane) - A plane.
### Examples ### Examples

View File

@ -96,24 +96,24 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y] p3 = [l + x, -l + y]
return startSketchAt(p0) return startSketchAt(p0)
|> lineTo(p1, %) |> lineTo(p1, %)
|> lineTo(p2, %) |> lineTo(p2, %)
|> lineTo(p3, %) |> lineTo(p3, %)
|> lineTo(p0, %) |> lineTo(p0, %)
|> close(%) |> close(%)
|> extrude(length, %) |> extrude(length, %)
} }
width = 20 width = 20
fn transform = (i) => { fn transform = (i) => {
return { return {
// Move down each time. // Move down each time.
translate: [0, 0, -i * width], translate: [0, 0, -i * width],
// Make the cube longer, wider and flatter each time. // Make the cube longer, wider and flatter each time.
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)], scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
// Turn by 15 degrees each time. // Turn by 15 degrees each time.
rotation: { angle: 15 * i, origin: "local" } rotation: { angle: 15 * i, origin: "local" }
} }
} }
myCubes = cube(width, [100, 0]) myCubes = cube(width, [100, 0])
@ -133,25 +133,25 @@ fn cube = (length, center) => {
p3 = [l + x, -l + y] p3 = [l + x, -l + y]
return startSketchAt(p0) return startSketchAt(p0)
|> lineTo(p1, %) |> lineTo(p1, %)
|> lineTo(p2, %) |> lineTo(p2, %)
|> lineTo(p3, %) |> lineTo(p3, %)
|> lineTo(p0, %) |> lineTo(p0, %)
|> close(%) |> close(%)
|> extrude(length, %) |> extrude(length, %)
} }
width = 20 width = 20
fn transform = (i) => { fn transform = (i) => {
return { return {
translate: [0, 0, -i * width], translate: [0, 0, -i * width],
rotation: { rotation: {
angle: 90 * i, angle: 90 * i,
// Rotate around the overall scene's origin. // Rotate around the overall scene's origin.
origin: "global" origin: "global"
}
} }
} }
}
myCubes = cube(width, [100, 100]) myCubes = cube(width, [100, 100])
|> patternTransform(4, transform, %) |> patternTransform(4, transform, %)
``` ```
@ -168,16 +168,16 @@ t = 0.005 // taper factor [0-1)
fn transform = (replicaId) => { fn transform = (replicaId) => {
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8)) scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
return { return {
translate: [0, 0, replicaId * 10], translate: [0, 0, replicaId * 10],
scale: [scale, scale, 0] scale: [scale, scale, 0]
} }
} }
// Each layer is just a pretty thin cylinder. // Each layer is just a pretty thin cylinder.
fn layer = () => { fn layer = () => {
return startSketchOn("XY") return startSketchOn("XY")
// or some other plane idk // or some other plane idk
|> circle({ center: [0, 0], radius: 1 }, %, $tag1) |> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|> extrude(h, %) |> extrude(h, %)
} }
// The vase is 100 layers tall. // The vase is 100 layers tall.
// The 100 layers are replica of each other, with a slight transformation applied to each. // The 100 layers are replica of each other, with a slight transformation applied to each.

View File

@ -36,15 +36,15 @@ fn add = (a, b) => {
// This function adds an array of numbers. // This function adds an array of numbers.
// It uses the `reduce` function, to call the `add` function on every // It uses the `reduce` function, to call the `add` function on every
// element of the `array` parameter. The starting value is 0. // element of the `arr` parameter. The starting value is 0.
fn sum = (array) => { fn sum = (arr) => {
return reduce(array, 0, add) return reduce(arr, 0, add)
} }
/* The above is basically like this pseudo-code: /* The above is basically like this pseudo-code:
fn sum(array): fn sum(arr):
let sumSoFar = 0 let sumSoFar = 0
for i in array: for i in arr:
sumSoFar = add(sumSoFar, i) sumSoFar = add(sumSoFar, i)
return sumSoFar */ return sumSoFar */
@ -60,8 +60,8 @@ assertEqual(sum([1, 2, 3]), 6, 0.00001, "1 + 2 + 3 summed is 6")
// This example works just like the previous example above, but it uses // This example works just like the previous example above, but it uses
// an anonymous `add` function as its parameter, instead of declaring a // an anonymous `add` function as its parameter, instead of declaring a
// named function outside. // named function outside.
array = [1, 2, 3] arr = [1, 2, 3]
sum = reduce(array, 0, (i, result_so_far) => { sum = reduce(arr, 0, (i, result_so_far) => {
return i + result_so_far return i + result_so_far
}) })
@ -89,7 +89,7 @@ fn decagon = (radius) => {
x = cos(stepAngle * i) * radius x = cos(stepAngle * i) * radius
y = sin(stepAngle * i) * radius y = sin(stepAngle * i) * radius
return lineTo([x, y], partialDecagon) return lineTo([x, y], partialDecagon)
}) })
return fullDecagon return fullDecagon
} }

View File

@ -38,8 +38,8 @@ cube = startSketchAt([0, 0])
fn cylinder = (radius, tag) => { fn cylinder = (radius, tag) => {
return startSketchAt([0, 0]) return startSketchAt([0, 0])
|> circle({ radius: radius, center: segEnd(tag) }, %) |> circle({ radius: radius, center: segEnd(tag) }, %)
|> extrude(radius, %) |> extrude(radius, %)
} }
cylinder(1, line1) cylinder(1, line1)

View File

@ -38,11 +38,11 @@ cube = startSketchAt([0, 0])
fn cylinder = (radius, tag) => { fn cylinder = (radius, tag) => {
return startSketchAt([0, 0]) return startSketchAt([0, 0])
|> circle({ |> circle({
radius: radius, radius: radius,
center: segStart(tag) center: segStart(tag)
}, %) }, %)
|> extrude(radius, %) |> extrude(radius, %)
} }
cylinder(1, line1) cylinder(1, line1)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
---
title: "ArcToData"
excerpt: "Data to draw a three point arc (arcTo)."
layout: manual
---
Data to draw a three point arc (arcTo).
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `end` |`[number, number]`| End point of the arc. A point in 3D space | No |
| `interior` |`[number, number]`| Interior point of the arc. A point in 3D space | No |

View File

@ -24,7 +24,7 @@ Autodesk Filmbox (FBX) format
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `fbx`| | No | | `format` |enum: `fbx`| | No |
---- ----
@ -40,7 +40,7 @@ Binary glTF 2.0. We refer to this as glTF since that is how our customers refer
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `gltf`| | No | | `format` |enum: `gltf`| | No |
---- ----
@ -56,7 +56,7 @@ Wavefront OBJ format.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `obj`| | No | | `format` |enum: `obj`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No | | `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No | | `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -74,7 +74,7 @@ The PLY Polygon File Format.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `ply`| | No | | `format` |enum: `ply`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No | | `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No | | `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
@ -92,7 +92,7 @@ SolidWorks part (SLDPRT) format.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `sldprt`| | No | | `format` |enum: `sldprt`| | No |
---- ----
@ -108,7 +108,7 @@ ISO 10303-21 (STEP) format.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `step`| | No | | `format` |enum: `step`| | No |
---- ----
@ -124,7 +124,7 @@ ST**ereo**L**ithography format.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `stl`| | No | | `format` |enum: `stl`| | No |
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No | | `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No | | `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |

View File

@ -180,7 +180,7 @@ A plane.
| Property | Type | Description | Required | | Property | Type | Description | Required |
|----------|------|-------------|----------| |----------|------|-------------|----------|
| `type` |enum: `Plane`| | No | | `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
| `id` |`string`| The id of the plane. | No | | `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No | | `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No | | `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |

27
docs/kcl/types/Plane.md Normal file
View File

@ -0,0 +1,27 @@
---
title: "Plane"
excerpt: "A plane."
layout: manual
---
A plane.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the plane. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -1,10 +1,10 @@
--- ---
title: "PlaneData" title: "PlaneData"
excerpt: "Data for a plane." excerpt: "Orientation data that can be used to construct a plane, not a plane in itself."
layout: manual layout: manual
--- ---
Data for a plane. Orientation data that can be used to construct a plane, not a plane in itself.

View File

@ -22,6 +22,18 @@ Data for start sketch on. You can start a sketch on a plane or an solid.
----
Data for start sketch on. You can start a sketch on a plane or an solid.
[`Plane`](/docs/kcl/types/Plane)
---- ----
Data for start sketch on. You can start a sketch on a plane or an solid. Data for start sketch on. You can start a sketch on a plane or an solid.

View File

@ -62,6 +62,8 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`) const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`) const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`) const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `main.kcl`, so the export file name should be `main.gltf`
const exportFileName = `main.gltf`
// Click the export button // Click the export button
await exportButton.click() await exportButton.click()
@ -96,7 +98,7 @@ test(
.poll( .poll(
async () => { async () => {
try { try {
const outputGltf = await fsp.readFile('output.gltf') const outputGltf = await fsp.readFile(exportFileName)
return outputGltf.byteLength return outputGltf.byteLength
} catch (e) { } catch (e) {
return 0 return 0
@ -106,8 +108,8 @@ test(
) )
.toBeGreaterThan(300_000) .toBeGreaterThan(300_000)
// clean up output.gltf // clean up exported file
await fsp.rm('output.gltf') await fsp.rm(exportFileName)
}) })
}) })
@ -138,6 +140,8 @@ test(
const errorToastMessage = page.getByText(`Error while exporting`) const errorToastMessage = page.getByText(`Error while exporting`)
const engineErrorToastMessage = page.getByText(`Nothing to export`) const engineErrorToastMessage = page.getByText(`Nothing to export`)
const alreadyExportingToastMessage = page.getByText(`Already exporting`) const alreadyExportingToastMessage = page.getByText(`Already exporting`)
// The open file's name is `other.kcl`, so the export file name should be `other.gltf`
const exportFileName = `other.gltf`
// Click the export button // Click the export button
await exportButton.click() await exportButton.click()
@ -171,7 +175,7 @@ test(
.poll( .poll(
async () => { async () => {
try { try {
const outputGltf = await fsp.readFile('output.gltf') const outputGltf = await fsp.readFile(exportFileName)
return outputGltf.byteLength return outputGltf.byteLength
} catch (e) { } catch (e) {
return 0 return 0
@ -181,8 +185,8 @@ test(
) )
.toBeGreaterThan(100_000) .toBeGreaterThan(100_000)
// clean up output.gltf // clean up exported file
await fsp.rm('output.gltf') await fsp.rm(exportFileName)
}) })
await electronApp.close() await electronApp.close()
}) })

View File

@ -1135,3 +1135,189 @@ _test.describe('Deleting items from the file pane', () => {
} }
) )
}) })
_test.describe(
'Undo and redo do not keep history when navigating between files',
() => {
_test(
`open a file, change something, open a different file, hitting undo should do nothing`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(`sketch001 = startSketchOn('XY')
some other shit`)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
})
await _test.step('hit undo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
})
}
)
_test(
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
{ tag: '@electron' },
// Skip on windows i think the keybindings are different for redo.
async ({ browserName }, testInfo) => {
test.skip(process.platform === 'win32', 'Skip on windows')
const { page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const testDir = join(dir, 'testProject')
await fsp.mkdir(testDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(testDir, 'main.kcl')
)
await fsp.copyFile(
executorInputPath('basic_fillet_cube_end.kcl'),
join(testDir, 'other.kcl')
)
},
})
const u = await getUtils(page)
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
// Constants and locators
const projectCard = page.getByText('testProject')
const otherFile = page
.getByRole('listitem')
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
const badContent = 'this shit'
await _test.step(
'Open project and make a change to the file',
async () => {
await projectCard.click()
await u.waitForPageLoad()
// Get the text in the code locator.
const originalText = await u.codeLocator.innerText()
// Click in the editor and add some new lines.
await u.codeLocator.click()
await page.keyboard.type(badContent)
// Ensure the content in the editor changed.
const newContent = await u.codeLocator.innerText()
expect(originalText !== newContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
// Hit redo.
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).toContainText(badContent)
// Now hit undo
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
}
)
await _test.step('navigate to other.kcl', async () => {
await u.openFilePanel()
await otherFile.click()
await u.waitForPageLoad()
await u.openKclCodePanel()
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
await expect(u.codeLocator).not.toContainText(badContent)
})
await _test.step('hit redo', async () => {
// Get the original content of the file.
const originalText = await u.codeLocator.innerText()
// Now hit redo
await page.keyboard.down('Shift')
await page.keyboard.down('ControlOrMeta')
await page.keyboard.press('KeyZ')
await page.keyboard.up('ControlOrMeta')
await page.keyboard.up('Shift')
await page.waitForTimeout(100)
await expect(u.codeLocator).toContainText(originalText)
await expect(u.codeLocator).not.toContainText(badContent)
})
}
)
}
)

View File

@ -247,7 +247,7 @@ test.describe('Can export from electron app', () => {
.poll( .poll(
async () => { async () => {
try { try {
const outputGltf = await fsp.readFile('output.gltf') const outputGltf = await fsp.readFile('main.gltf')
return outputGltf.byteLength return outputGltf.byteLength
} catch (e) { } catch (e) {
return 0 return 0
@ -257,8 +257,8 @@ test.describe('Can export from electron app', () => {
) )
.toBeGreaterThan(300_000) .toBeGreaterThan(300_000)
// clean up output.gltf // clean up exported file
await fsp.rm('output.gltf') await fsp.rm('main.gltf')
}) })
await electronApp.close() await electronApp.close()

View File

@ -1274,3 +1274,44 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
} }
) )
}) })
test2.describe(`Sketching with offset planes`, () => {
test2(
`Can select an offset plane to sketch on`,
async ({ app, scene, toolbar, editor }) => {
// We seed the scene with a single offset plane
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
await test2.step(`Start sketching on the offset plane`, async () => {
await toolbar.startSketchPlaneSelection()
await test2.step(`Hovering should highlight code`, async () => {
await planeHover()
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: 'offsetPlane("XY", 10)',
})
})
await test2.step(
`Clicking should select the plane and enter sketch mode`,
async () => {
await planeClick()
// Have to wait for engine-side animation to finish
await app.page.waitForTimeout(600)
await expect2(toolbar.lineBtn).toBeEnabled()
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
await editor.expectState({
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
diagnostics: [],
highlightedCode: '',
})
}
)
})
}
)
})

View File

@ -283,7 +283,7 @@ part001 = startSketchOn('-XZ')
const gltfFilename = filenames.filter((t: string) => const gltfFilename = filenames.filter((t: string) =>
t.includes('.gltf') t.includes('.gltf')
)[0] )[0]
if (!gltfFilename) throw new Error('No output.gltf in this archive') if (!gltfFilename) throw new Error('No gLTF in this archive')
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}` cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,6 +1,6 @@
{ {
"name": "zoo-modeling-app", "name": "zoo-modeling-app",
"version": "0.26.5", "version": "0.27.0",
"private": true, "private": true,
"productName": "Zoo Modeling App", "productName": "Zoo Modeling App",
"author": { "author": {

View File

@ -47,6 +47,7 @@ import {
VariableDeclaration, VariableDeclaration,
VariableDeclarator, VariableDeclarator,
sketchFromKclValue, sketchFromKclValue,
sketchFromKclValueOptional,
} from 'lang/wasm' } from 'lang/wasm'
import { import {
engineCommandManager, engineCommandManager,
@ -92,7 +93,7 @@ import {
updateCenterRectangleSketch, updateCenterRectangleSketch,
} from 'lib/rectangleTool' } from 'lib/rectangleTool'
import { getThemeColorForThreeJs, Themes } from 'lib/theme' import { getThemeColorForThreeJs, Themes } from 'lib/theme'
import { err, reportRejection, trap } from 'lib/trap' import { err, Reason, reportRejection, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d' import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes' import { SegmentInputs } from 'lang/std/stdTypes'
@ -1178,6 +1179,11 @@ export class SceneEntities {
await kclManager.executeAstMock(_ast) await kclManager.executeAstMock(_ast)
sceneInfra.modelingSend({ type: 'Finish center rectangle' }) sceneInfra.modelingSend({ type: 'Finish center rectangle' })
// lee: I had this at the bottom of the function, but it's
// possible sketchFromKclValue "fails" when sketching on a face,
// and this couldn't wouldn't run.
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
const { execState } = await executeAst({ const { execState } = await executeAst({
ast: _ast, ast: _ast,
useFakeExecutor: true, useFakeExecutor: true,
@ -1692,10 +1698,13 @@ export class SceneEntities {
this.sceneProgramMemory = programMemory this.sceneProgramMemory = programMemory
const maybeSketch = programMemory.get(variableDeclarationName) const maybeSketch = programMemory.get(variableDeclarationName)
let sketch = undefined let sketch: Sketch | undefined
const sg = sketchFromKclValue(maybeSketch, variableDeclarationName) const sk = sketchFromKclValueOptional(
if (!err(sg)) { maybeSketch,
sketch = sg variableDeclarationName
)
if (!(sk instanceof Reason)) {
sketch = sk
} else if ((maybeSketch as Solid).sketch) { } else if ((maybeSketch as Solid).sketch) {
sketch = (maybeSketch as Solid).sketch sketch = (maybeSketch as Solid).sketch
} }

View File

@ -63,6 +63,7 @@ import {
import { import {
moveValueIntoNewVariablePath, moveValueIntoNewVariablePath,
sketchOnExtrudedFace, sketchOnExtrudedFace,
sketchOnOffsetPlane,
startSketchOnDefault, startSketchOnDefault,
} from 'lang/modifyAst' } from 'lang/modifyAst'
import { Program, parse, recast } from 'lang/wasm' import { Program, parse, recast } from 'lang/wasm'
@ -483,7 +484,7 @@ export const ModelingMachineProvider = ({
engineCommandManager.exportInfo = { engineCommandManager.exportInfo = {
intent: ExportIntent.Save, intent: ExportIntent.Save,
// This never gets used its only for make. // This never gets used its only for make.
name: '', name: file?.name?.replace('.kcl', `.${event.data.type}`) || '',
} }
const format = { const format = {
@ -636,13 +637,16 @@ export const ModelingMachineProvider = ({
), ),
'animate-to-face': fromPromise(async ({ input }) => { 'animate-to-face': fromPromise(async ({ input }) => {
if (!input) return undefined if (!input) return undefined
if (input.type === 'extrudeFace') { if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
const sketched = sketchOnExtrudedFace( const sketched =
kclManager.ast, input.type === 'extrudeFace'
input.sketchPathToNode, ? sketchOnExtrudedFace(
input.extrudePathToNode, kclManager.ast,
input.faceInfo input.sketchPathToNode,
) input.extrudePathToNode,
input.faceInfo
)
: sketchOnOffsetPlane(kclManager.ast, input.pathToNode)
if (err(sketched)) { if (err(sketched)) {
const sketchedError = new Error( const sketchedError = new Error(
'Incompatible face, please try another' 'Incompatible face, please try another'
@ -654,10 +658,9 @@ export const ModelingMachineProvider = ({
await kclManager.executeAstMock(modifiedAst) await kclManager.executeAstMock(modifiedAst)
await letEngineAnimateAndSyncCamAfter( const id =
engineCommandManager, input.type === 'extrudeFace' ? input.faceId : input.planeId
input.faceId await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
)
sceneInfra.camControls.syncDirection = 'clientToEngine' sceneInfra.camControls.syncDirection = 'clientToEngine'
return { return {
sketchPathToNode: pathToNewSketchNode, sketchPathToNode: pathToNewSketchNode,

View File

@ -43,6 +43,7 @@ import {
completionKeymap, completionKeymap,
} from '@codemirror/autocomplete' } from '@codemirror/autocomplete'
import CodeEditor from './CodeEditor' import CodeEditor from './CodeEditor'
import { codeManagerHistoryCompartment } from 'lang/codeManager'
export const editorShortcutMeta = { export const editorShortcutMeta = {
formatCode: { formatCode: {
@ -89,7 +90,7 @@ export const KclEditorPane = () => {
cursorBlinkRate: cursorBlinking.current ? 1200 : 0, cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
}), }),
lineHighlightField, lineHighlightField,
history(), codeManagerHistoryCompartment.of(history()),
closeBrackets(), closeBrackets(),
codeFolding(), codeFolding(),
keymap.of([ keymap.of([
@ -121,7 +122,6 @@ export const KclEditorPane = () => {
lineNumbers(), lineNumbers(),
highlightActiveLineGutter(), highlightActiveLineGutter(),
highlightSpecialChars(), highlightSpecialChars(),
history(),
foldGutter(), foldGutter(),
EditorState.allowMultipleSelections.of(true), EditorState.allowMultipleSelections.of(true),
indentOnInput(), indentOnInput(),

View File

@ -5,12 +5,12 @@ import {
ProgramMemory, ProgramMemory,
Path, Path,
ExtrudeSurface, ExtrudeSurface,
sketchFromKclValue, sketchFromKclValueOptional,
} from 'lang/wasm' } from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider' import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme' import { useResolvedTheme } from 'hooks/useResolvedTheme'
import { ActionButton } from 'components/ActionButton' import { ActionButton } from 'components/ActionButton'
import { err, trap } from 'lib/trap' import { Reason, trap } from 'lib/trap'
import Tooltip from 'components/Tooltip' import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext' import { useModelingContext } from 'hooks/useModelingContext'
@ -93,13 +93,13 @@ export const processMemory = (programMemory: ProgramMemory) => {
// @ts-ignore // @ts-ignore
val.type !== 'Function' val.type !== 'Function'
) { ) {
const sg = sketchFromKclValue(val, key) const sk = sketchFromKclValueOptional(val, key)
if (val.type === 'Solid') { if (val.type === 'Solid') {
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => { processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
return rest return rest
}) })
} else if (!err(sg)) { } else if (!(sk instanceof Reason)) {
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => { processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
return rest return rest
}) })
} else { } else {

View File

@ -88,6 +88,10 @@ export function useEngineConnectionSubscriptions() {
? [codeRef.range] ? [codeRef.range]
: [codeRef.range, consumedCodeRef.range] : [codeRef.range, consumedCodeRef.range]
) )
} else if (artifact?.type === 'plane') {
const codeRef = artifact.codeRef
if (err(codeRef)) return
editorManager.setHighlightRange([codeRef.range])
} else { } else {
editorManager.setHighlightRange([[0, 0]]) editorManager.setHighlightRange([[0, 0]])
} }
@ -186,8 +190,42 @@ export function useEngineConnectionSubscriptions() {
}) })
return return
} }
const artifact =
engineCommandManager.artifactGraph.get(planeOrFaceId)
if (artifact?.type === 'plane') {
const planeInfo = await getFaceDetails(planeOrFaceId)
sceneInfra.modelingSend({
type: 'Select default plane',
data: {
type: 'offsetPlane',
zAxis: [
planeInfo.z_axis.x,
planeInfo.z_axis.y,
planeInfo.z_axis.z,
],
yAxis: [
planeInfo.y_axis.x,
planeInfo.y_axis.y,
planeInfo.y_axis.z,
],
position: [
planeInfo.origin.x,
planeInfo.origin.y,
planeInfo.origin.z,
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
number,
number,
number
],
planeId: planeOrFaceId,
pathToNode: artifact.codeRef.pathToNode,
},
})
}
// Artifact is likely an extrusion face
const faceId = planeOrFaceId const faceId = planeOrFaceId
const artifact = engineCommandManager.artifactGraph.get(faceId)
const extrusion = getSweepFromSuspectedSweepSurface( const extrusion = getSweepFromSuspectedSweepSurface(
faceId, faceId,
engineCommandManager.artifactGraph engineCommandManager.artifactGraph

View File

@ -6,14 +6,17 @@ import { isDesktop } from 'lib/isDesktop'
import toast from 'react-hot-toast' import toast from 'react-hot-toast'
import { editorManager } from 'lib/singletons' import { editorManager } from 'lib/singletons'
import { Annotation, Transaction } from '@codemirror/state' import { Annotation, Transaction } from '@codemirror/state'
import { KeyBinding } from '@codemirror/view' import { EditorView, KeyBinding } from '@codemirror/view'
import { recast, Program } from 'lang/wasm' import { recast, Program } from 'lang/wasm'
import { err } from 'lib/trap' import { err } from 'lib/trap'
import { Compartment } from '@codemirror/state'
import { history } from '@codemirror/commands'
const PERSIST_CODE_KEY = 'persistCode' const PERSIST_CODE_KEY = 'persistCode'
const codeManagerUpdateAnnotation = Annotation.define<boolean>() const codeManagerUpdateAnnotation = Annotation.define<boolean>()
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true) export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
export const codeManagerHistoryCompartment = new Compartment()
export default class CodeManager { export default class CodeManager {
private _code: string = bracket private _code: string = bracket
@ -90,9 +93,12 @@ export default class CodeManager {
/** /**
* Update the code in the editor. * Update the code in the editor.
*/ */
updateCodeEditor(code: string): void { updateCodeEditor(code: string, clearHistory?: boolean): void {
this.code = code this.code = code
if (editorManager.editorView) { if (editorManager.editorView) {
if (clearHistory) {
clearCodeMirrorHistory(editorManager.editorView)
}
editorManager.editorView.dispatch({ editorManager.editorView.dispatch({
changes: { changes: {
from: 0, from: 0,
@ -101,7 +107,7 @@ export default class CodeManager {
}, },
annotations: [ annotations: [
codeManagerUpdateEvent, codeManagerUpdateEvent,
Transaction.addToHistory.of(true), Transaction.addToHistory.of(!clearHistory),
], ],
}) })
} }
@ -110,11 +116,11 @@ export default class CodeManager {
/** /**
* Update the code, state, and the code the code mirror editor sees. * Update the code, state, and the code the code mirror editor sees.
*/ */
updateCodeStateEditor(code: string): void { updateCodeStateEditor(code: string, clearHistory?: boolean): void {
if (this._code !== code) { if (this._code !== code) {
this.code = code this.code = code
this.#updateState(code) this.#updateState(code)
this.updateCodeEditor(code) this.updateCodeEditor(code, clearHistory)
} }
} }
@ -167,3 +173,17 @@ function safeLSSetItem(key: string, value: string) {
if (typeof window === 'undefined') return if (typeof window === 'undefined') return
localStorage?.setItem(key, value) localStorage?.setItem(key, value)
} }
function clearCodeMirrorHistory(view: EditorView) {
// Clear history
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([])],
annotations: [codeManagerUpdateEvent],
})
// Add history back
view.dispatch({
effects: [codeManagerHistoryCompartment.reconfigure([history()])],
annotations: [codeManagerUpdateEvent],
})
}

View File

@ -19,6 +19,7 @@ import {
ProgramMemory, ProgramMemory,
SourceRange, SourceRange,
sketchFromKclValue, sketchFromKclValue,
isPathToNodeNumber,
} from './wasm' } from './wasm'
import { import {
isNodeSafeToReplacePath, isNodeSafeToReplacePath,
@ -526,6 +527,60 @@ export function sketchOnExtrudedFace(
} }
} }
/**
* Modify the AST to create a new sketch using the variable declaration
* of an offset plane. The new sketch just has to come after the offset
* plane declaration.
*/
export function sketchOnOffsetPlane(
node: Node<Program>,
offsetPathToNode: PathToNode
) {
let _node = { ...node }
// Find the offset plane declaration
const offsetPlaneDeclarator = getNodeFromPath<VariableDeclarator>(
_node,
offsetPathToNode,
'VariableDeclarator',
true
)
if (err(offsetPlaneDeclarator)) return offsetPlaneDeclarator
const { node: offsetPlaneNode } = offsetPlaneDeclarator
const offsetPlaneName = offsetPlaneNode.id.name
// Create a new sketch declaration
const newSketchName = findUniqueName(
node,
KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH
)
const newSketch = createVariableDeclaration(
newSketchName,
createCallExpressionStdLib('startSketchOn', [
createIdentifier(offsetPlaneName),
]),
undefined,
'const'
)
// Decide where to insert the new sketch declaration
const offsetIndex = offsetPathToNode[1][0]
if (!isPathToNodeNumber(offsetIndex)) {
return new Error('Expected offsetIndex to be a number')
}
// and insert it
_node.body.splice(offsetIndex + 1, 0, newSketch)
const newPathToNode = structuredClone(offsetPathToNode)
newPathToNode[1][0] = offsetIndex + 1
// Return the modified AST and the path to the new sketch declaration
return {
modifiedAst: _node,
pathToNode: newPathToNode,
}
}
export const getLastIndex = (pathToNode: PathToNode): number => export const getLastIndex = (pathToNode: PathToNode): number =>
splitPathAtLastIndex(pathToNode).index splitPathAtLastIndex(pathToNode).index

View File

@ -77,22 +77,30 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
code.indexOf(expectedExtrudeSnippet), code.indexOf(expectedExtrudeSnippet),
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length, code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
] ]
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange) const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
const expedtedExtrudeNodeResult = getNodeFromPath<VariableDeclarator>( const expectedExtrudeNodeResult = getNodeFromPath<
ast, VariableDeclarator | CallExpression
expedtedExtrudePath >(ast, expectedExtrudePath)
) if (err(expectedExtrudeNodeResult)) {
if (err(expedtedExtrudeNodeResult)) { return expectedExtrudeNodeResult
return expedtedExtrudeNodeResult
} }
const expectedExtrudeNode = expedtedExtrudeNodeResult.node const expectedExtrudeNode = expectedExtrudeNodeResult.node
const init = expectedExtrudeNode.init
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') { // check whether extrude is in the sketch pipe
return new Error( const extrudeInSketchPipe = expectedExtrudeNode.type === 'CallExpression'
'Expected extrude expression is not a CallExpression or PipeExpression' if (extrudeInSketchPipe) {
) return expectedExtrudeNode
} }
return init if (!extrudeInSketchPipe) {
const init = expectedExtrudeNode.init
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
return new Error(
'Expected extrude expression is not a CallExpression or PipeExpression'
)
}
return init
}
return new Error('Expected extrude expression not found')
} }
// ast // ast
@ -160,6 +168,23 @@ extrude001 = extrude(-15, sketch001)`
expectedExtrudeSnippet expectedExtrudeSnippet
) )
}, 5_000) }, 5_000)
it('should return the correct paths when extrusion occurs within the sketch pipe', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(15, %)`
const selectedSegmentSnippet = `line([20, 0], %)`
const expectedExtrudeSnippet = `extrude(15, %)`
await runGetPathToExtrudeForSegmentSelectionTest(
code,
selectedSegmentSnippet,
expectedExtrudeSnippet
)
}, 5_000)
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => { it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
const code = `sketch001 = startSketchOn('XY') const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-30, 30], %) |> startProfileAt([-30, 30], %)
@ -296,6 +321,34 @@ extrude001 = extrude(-15, sketch001)`
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
extrude001 = extrude(-15, sketch001) extrude001 = extrude(-15, sketch001)
|> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag(
code,
segmentSnippets,
radiusValue,
expectedCode
)
})
it('should add a fillet to the sketch pipe', async () => {
const code = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)`
const segmentSnippets = ['line([0, -20], %)']
const radiusValue = 3
const expectedCode = `sketch001 = startSketchOn('XY')
|> startProfileAt([-10, 10], %)
|> line([20, 0], %)
|> line([0, -20], %, $seg01)
|> line([-20, 0], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(-15, %)
|> fillet({ radius: 3, tags: [seg01] }, %)` |> fillet({ radius: 3, tags: [seg01] }, %)`
await runModifyAstCloneWithFilletAndTag( await runModifyAstCloneWithFilletAndTag(

View File

@ -146,7 +146,7 @@ export function modifyAstCloneWithFilletAndTag(
// Modify the extrude expression to include this fillet expression // Modify the extrude expression to include this fillet expression
// CallExpression - no fillet // CallExpression - no fillet
// PipeExpression - fillet exists // PipeExpression - fillet exists or extrude in sketch pipe
let pathToFilletNode: PathToNode = [] let pathToFilletNode: PathToNode = []
@ -167,15 +167,7 @@ export function modifyAstCloneWithFilletAndTag(
) )
pathToFilletNodes.push(pathToFilletNode) pathToFilletNodes.push(pathToFilletNode)
} else if (extrudeDeclarator.init.type === 'PipeExpression') { } else if (extrudeDeclarator.init.type === 'PipeExpression') {
// 2. case when fillet exists // 2. case when fillet exists or extrude in sketch pipe
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
return node.type === 'CallExpression' && node.callee.name === 'fillet'
})
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
return new Error('Fillet CallExpression not found.')
}
// mutate the extrude node with the new fillet call // mutate the extrude node with the new fillet call
extrudeDeclarator.init.body.push(filletCall) extrudeDeclarator.init.body.push(filletCall)
@ -317,14 +309,14 @@ function locateExtrudeDeclarator(
node: Program, node: Program,
pathToExtrudeNode: PathToNode pathToExtrudeNode: PathToNode
): { extrudeDeclarator: VariableDeclarator } | Error { ): { extrudeDeclarator: VariableDeclarator } | Error {
const extrudeChunk = getNodeFromPath<VariableDeclaration>( const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
node, node,
pathToExtrudeNode, pathToExtrudeNode,
'VariableDeclaration' 'VariableDeclaration'
) )
if (err(extrudeChunk)) return extrudeChunk if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
const { node: extrudeVarDecl } = extrudeChunk const { node: extrudeVarDecl } = nodeOfExtrudeCall
const extrudeDeclarator = extrudeVarDecl.declarations[0] const extrudeDeclarator = extrudeVarDecl.declarations[0]
if (!extrudeDeclarator) { if (!extrudeDeclarator) {
return new Error('Extrude Declarator not found.') return new Error('Extrude Declarator not found.')

View File

@ -530,14 +530,25 @@ describe('Testing hasSketchPipeBeenExtruded', () => {
|> line([-17.67, 0.85], %) |> line([-17.67, 0.85], %)
|> close(%) |> close(%)
extrude001 = extrude(10, sketch001) extrude001 = extrude(10, sketch001)
sketch002 = startSketchOn(extrude001, $seg01) sketch002 = startSketchOn(extrude001, seg01)
|> startProfileAt([-12.94, 6.6], %) |> startProfileAt([-12.94, 6.6], %)
|> line([2.45, -0.2], %) |> line([2.45, -0.2], %)
|> line([-2, -1.25], %) |> line([-2, -1.25], %)
|> lineTo([profileStartX(%), profileStartY(%)], %) |> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%) |> close(%)
sketch003 = startSketchOn(extrude001, 'END')
|> startProfileAt([8.14, 2.8], %)
|> line([-1.24, 4.39], %)
|> line([3.79, 1.91], %)
|> line([1.77, -2.95], %)
|> line([3.12, 1.74], %)
|> line([1.91, -4.09], %)
|> line([-5.6, -2.75], %)
|> lineTo([profileStartX(%), profileStartY(%)], %)
|> close(%)
|> extrude(3.14, %)
` `
it('finds sketch001 pipe to be extruded', async () => { it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const lineOfInterest = `line([4.99, -0.46], %, $seg01)` const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
@ -552,7 +563,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
) )
expect(extruded).toBeTruthy() expect(extruded).toBeTruthy()
}) })
it('find sketch002 NOT pipe to be extruded', async () => { it('identifies sketch002 pipe as not extruded', async () => {
const ast = parse(exampleCode) const ast = parse(exampleCode)
if (err(ast)) throw ast if (err(ast)) throw ast
const lineOfInterest = `line([2.45, -0.2], %)` const lineOfInterest = `line([2.45, -0.2], %)`
@ -567,6 +578,21 @@ sketch002 = startSketchOn(extrude001, $seg01)
) )
expect(extruded).toBeFalsy() expect(extruded).toBeFalsy()
}) })
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
const ast = parse(exampleCode)
if (err(ast)) throw ast
const lineOfInterest = `|> line([3.12, 1.74], %)`
const characterIndex =
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
const extruded = hasSketchPipeBeenExtruded(
{
range: [characterIndex, characterIndex],
type: 'default',
},
ast
)
expect(extruded).toBeTruthy()
})
}) })
describe('Testing doesSceneHaveSweepableSketch', () => { describe('Testing doesSceneHaveSweepableSketch', () => {

View File

@ -14,6 +14,7 @@ import {
ProgramMemory, ProgramMemory,
ReturnStatement, ReturnStatement,
sketchFromKclValue, sketchFromKclValue,
sketchFromKclValueOptional,
SourceRange, SourceRange,
SyntaxType, SyntaxType,
VariableDeclaration, VariableDeclaration,
@ -27,7 +28,7 @@ import {
getConstraintLevelFromSourceRange, getConstraintLevelFromSourceRange,
getConstraintType, getConstraintType,
} from './std/sketchcombos' } from './std/sketchcombos'
import { err } from 'lib/trap' import { err, Reason } from 'lib/trap'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement' import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
import { Node } from 'wasm-lib/kcl/bindings/Node' import { Node } from 'wasm-lib/kcl/bindings/Node'
@ -846,7 +847,8 @@ export function hasExtrudeSketch({
const varName = varDec.declarations[0].id.name const varName = varDec.declarations[0].id.name
const varValue = programMemory?.get(varName) const varValue = programMemory?.get(varName)
return ( return (
varValue?.type === 'Solid' || !err(sketchFromKclValue(varValue, varName)) varValue?.type === 'Solid' ||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
) )
} }
@ -927,7 +929,11 @@ export function findUsesOfTagInPipe(
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) { export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const path = getNodePathFromSourceRange(ast, selection.range) const path = getNodePathFromSourceRange(ast, selection.range)
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression') const _node = getNodeFromPath<Node<PipeExpression>>(
ast,
path,
'PipeExpression'
)
if (err(_node)) return false if (err(_node)) return false
const { node: pipeExpression } = _node const { node: pipeExpression } = _node
if (pipeExpression.type !== 'PipeExpression') return false if (pipeExpression.type !== 'PipeExpression') return false
@ -940,19 +946,33 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
const varDec = _varDec.node const varDec = _varDec.node
if (varDec.type !== 'VariableDeclarator') return false if (varDec.type !== 'VariableDeclarator') return false
let extruded = false let extruded = false
traverse(ast as any, { // option 1: extrude or revolve is called in the sketch pipe
traverse(pipeExpression, {
enter(node) { enter(node) {
if ( if (
node.type === 'CallExpression' && node.type === 'CallExpression' &&
node.callee.type === 'Identifier' && (node.callee.name === 'extrude' || node.callee.name === 'revolve')
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
) { ) {
extruded = true extruded = true
} }
}, },
}) })
// option 2: extrude or revolve is called in the separate pipe
if (!extruded) {
traverse(ast as any, {
enter(node) {
if (
node.type === 'CallExpression' &&
node.callee.type === 'Identifier' &&
(node.callee.name === 'extrude' || node.callee.name === 'revolve') &&
node.arguments?.[1]?.type === 'Identifier' &&
node.arguments[1].name === varDec.id.name
) {
extruded = true
}
},
})
}
return extruded return extruded
} }

View File

@ -98,12 +98,22 @@ sketch004 = startSketchOn(extrude003, seg02)
|> close(%) |> close(%)
extrude004 = extrude(3, sketch004) extrude004 = extrude(3, sketch004)
` `
const exampleCodeOffsetPlanes = `
offsetPlane001 = offsetPlane("XY", 20)
offsetPlane002 = offsetPlane("XZ", -50)
offsetPlane003 = offsetPlane("YZ", 10)
sketch002 = startSketchOn(offsetPlane001)
|> startProfileAt([0, 0], %)
|> line([6.78, 15.01], %)
`
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests // add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests
const codeToWriteCacheFor = { const codeToWriteCacheFor = {
exampleCode1, exampleCode1,
sketchOnFaceOnFaceEtc, sketchOnFaceOnFaceEtc,
exampleCodeNo3D, exampleCodeNo3D,
exampleCodeOffsetPlanes,
} as const } as const
type CodeKey = keyof typeof codeToWriteCacheFor type CodeKey = keyof typeof codeToWriteCacheFor
@ -165,6 +175,52 @@ afterAll(() => {
}) })
describe('testing createArtifactGraph', () => { describe('testing createArtifactGraph', () => {
describe('code with offset planes and a sketch:', () => {
let ast: Program
let theMap: ReturnType<typeof createArtifactGraph>
it('setup', () => {
// putting this logic in here because describe blocks runs before beforeAll has finished
const {
orderedCommands,
responseMap,
ast: _ast,
} = getCommands('exampleCodeOffsetPlanes')
ast = _ast
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
})
it(`there should be one sketch`, () => {
const sketches = [...filterArtifacts({ types: ['path'] }, theMap)].map(
(path) => expandPath(path[1], theMap)
)
expect(sketches).toHaveLength(1)
sketches.forEach((path) => {
if (err(path)) throw path
expect(path.type).toBe('path')
})
})
it(`there should be three offsetPlanes`, () => {
const offsetPlanes = [
...filterArtifacts({ types: ['plane'] }, theMap),
].map((plane) => expandPlane(plane[1], theMap))
expect(offsetPlanes).toHaveLength(3)
offsetPlanes.forEach((path) => {
expect(path.type).toBe('plane')
})
})
it(`Only one offset plane should have a path`, () => {
const offsetPlanes = [
...filterArtifacts({ types: ['plane'] }, theMap),
].map((plane) => expandPlane(plane[1], theMap))
const offsetPlaneWithPaths = offsetPlanes.filter(
(plane) => plane.paths.length
)
expect(offsetPlaneWithPaths).toHaveLength(1)
})
})
describe('code with an extrusion, fillet and sketch of face:', () => { describe('code with an extrusion, fillet and sketch of face:', () => {
let ast: Program let ast: Program
let theMap: ReturnType<typeof createArtifactGraph> let theMap: ReturnType<typeof createArtifactGraph>

View File

@ -249,7 +249,20 @@ export function getArtifactsToUpdate({
const cmd = command.cmd const cmd = command.cmd
const returnArr: ReturnType<typeof getArtifactsToUpdate> = [] const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
if (!response) return returnArr if (!response) return returnArr
if (cmd.type === 'enable_sketch_mode') { if (cmd.type === 'make_plane' && range[1] !== 0) {
// If we're calling `make_plane` and the code range doesn't end at `0`
// it's not a default plane, but a custom one from the offsetPlane standard library function
return [
{
id,
artifact: {
type: 'plane',
pathIds: [],
codeRef: { range, pathToNode },
},
},
]
} else if (cmd.type === 'enable_sketch_mode') {
const plane = getArtifact(currentPlaneId) const plane = getArtifact(currentPlaneId)
const pathIds = plane?.type === 'plane' ? plane?.pathIds : [] const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
const codeRef = const codeRef =

View File

@ -1631,7 +1631,11 @@ export class EngineCommandManager extends EventTarget {
switch (this.exportInfo.intent) { switch (this.exportInfo.intent) {
case ExportIntent.Save: { case ExportIntent.Save: {
exportSave(event.data, this.pendingExport.toastId).then(() => { exportSave({
data: event.data,
fileName: this.exportInfo.name,
toastId: this.pendingExport.toastId,
}).then(() => {
this.pendingExport?.resolve(null) this.pendingExport?.resolve(null)
}, this.pendingExport?.reject) }, this.pendingExport?.reject)
break break

View File

@ -32,7 +32,7 @@ import { CoreDumpManager } from 'lib/coredump'
import openWindow from 'lib/openWindow' import openWindow from 'lib/openWindow'
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes' import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
import { TEST } from 'env' import { TEST } from 'env'
import { err } from 'lib/trap' import { err, Reason } from 'lib/trap'
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration' import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
import { DeepPartial } from 'lib/types' import { DeepPartial } from 'lib/types'
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration' import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
@ -144,6 +144,12 @@ export const parse = (code: string | Error): Node<Program> | Error => {
export type PathToNode = [string | number, string][] export type PathToNode = [string | number, string][]
export const isPathToNodeNumber = (
pathToNode: string | number
): pathToNode is number => {
return typeof pathToNode === 'number'
}
export interface ExecState { export interface ExecState {
memory: ProgramMemory memory: ProgramMemory
idGenerator: IdGenerator idGenerator: IdGenerator
@ -359,10 +365,10 @@ export class ProgramMemory {
} }
// TODO: In the future, make the parameter be a KclValue. // TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValue( export function sketchFromKclValueOptional(
obj: any, obj: any,
varName: string | null varName: string | null
): Sketch | Error { ): Sketch | Reason {
if (obj?.value?.type === 'Sketch') return obj.value if (obj?.value?.type === 'Sketch') return obj.value
if (obj?.value?.type === 'Solid') return obj.value.sketch if (obj?.value?.type === 'Solid') return obj.value.sketch
if (obj?.type === 'Solid') return obj.sketch if (obj?.type === 'Solid') return obj.sketch
@ -371,15 +377,26 @@ export function sketchFromKclValue(
} }
const actualType = obj?.value?.type ?? obj?.type const actualType = obj?.value?.type ?? obj?.type
if (actualType) { if (actualType) {
console.log(obj) return new Reason(
return new Error(
`Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.` `Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.`
) )
} else { } else {
return new Error(`Expected ${varName} to be a sketch, but it wasn't.`) return new Reason(`Expected ${varName} to be a sketch, but it wasn't.`)
} }
} }
// TODO: In the future, make the parameter be a KclValue.
export function sketchFromKclValue(
obj: any,
varName: string | null
): Sketch | Error {
const result = sketchFromKclValueOptional(obj, varName)
if (result instanceof Reason) {
return result.toError()
}
return result
}
export const executor = async ( export const executor = async (
node: Node<Program>, node: Node<Program>,
programMemory: ProgramMemory | Error = ProgramMemory.empty(), programMemory: ProgramMemory | Error = ProgramMemory.empty(),

View File

@ -68,7 +68,16 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
} }
// Saves files locally from an export call. // Saves files locally from an export call.
export async function exportSave(data: ArrayBuffer, toastId: string) { // We override the file's name with one passed in from the client side.
export async function exportSave({
data,
fileName,
toastId,
}: {
data: ArrayBuffer
fileName: string
toastId: string
}) {
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>. // This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
let uintArray = new Uint8Array(data) let uintArray = new Uint8Array(data)
@ -80,9 +89,10 @@ export async function exportSave(data: ArrayBuffer, toastId: string) {
zip.file(file.name, new Uint8Array(file.contents), { binary: true }) zip.file(file.name, new Uint8Array(file.contents), { binary: true })
} }
return zip.generateAsync({ type: 'array' }).then((contents) => { return zip.generateAsync({ type: 'array' }).then((contents) => {
return save_({ name: 'output.zip', contents }, toastId) return save_({ name: `${fileName || 'output'}.zip`, contents }, toastId)
}) })
} else { } else {
files[0].name = fileName || files[0].name
return save_(files[0], toastId) return save_(files[0], toastId)
} }
} }

View File

@ -124,7 +124,9 @@ export const fileLoader: LoaderFunction = async (
// We explicitly do not write to the file here since we are loading from // We explicitly do not write to the file here since we are loading from
// the file system and not the editor. // the file system and not the editor.
codeManager.updateCurrentFilePath(currentFilePath) codeManager.updateCurrentFilePath(currentFilePath)
codeManager.updateCodeStateEditor(code) // We pass true on the end here to clear the code editor history.
// This way undo and redo are not super weird when opening new files.
codeManager.updateCodeStateEditor(code, true)
} }
// Set the file system manager to the project path // Set the file system manager to the project path

View File

@ -2,6 +2,23 @@ import toast from 'react-hot-toast'
type ExcludeErr<T> = Exclude<T, Error> type ExcludeErr<T> = Exclude<T, Error>
/**
* Similar to Error, but more lightweight, without the stack trace. It can also
* be used to represent a reason for not being able to provide an alternative,
* which isn't necessarily an error.
*/
export class Reason {
message: string
constructor(message: string) {
this.message = message
}
toError() {
return new Error(this.message)
}
}
/** /**
* This is intentionally *not* exported due to misuse. We'd like to add a lint. * This is intentionally *not* exported due to misuse. We'd like to add a lint.
*/ */

View File

@ -159,6 +159,15 @@ export type DefaultPlane = {
yAxis: [number, number, number] yAxis: [number, number, number]
} }
export type OffsetPlane = {
type: 'offsetPlane'
position: [number, number, number]
planeId: string
pathToNode: PathToNode
zAxis: [number, number, number]
yAxis: [number, number, number]
}
export type SegmentOverlayPayload = export type SegmentOverlayPayload =
| { | {
type: 'set-one' type: 'set-one'
@ -198,7 +207,7 @@ export type ModelingMachineEvent =
| { type: 'Sketch On Face' } | { type: 'Sketch On Face' }
| { | {
type: 'Select default plane' type: 'Select default plane'
data: DefaultPlane | ExtrudeFacePlane data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
} }
| { | {
type: 'Set selection' type: 'Set selection'
@ -1394,7 +1403,7 @@ export const modelingMachine = setup({
} }
), ),
'animate-to-face': fromPromise( 'animate-to-face': fromPromise(
async (_: { input?: ExtrudeFacePlane | DefaultPlane }) => { async (_: { input?: ExtrudeFacePlane | DefaultPlane | OffsetPlane }) => {
return {} as return {} as
| undefined | undefined
| { | {

View File

@ -1589,6 +1589,8 @@ dependencies = [
"console", "console",
"lazy_static", "lazy_static",
"linked-hash-map", "linked-hash-map",
"pest",
"pest_derive",
"regex", "regex",
"serde", "serde",
"similar", "similar",
@ -1689,6 +1691,7 @@ dependencies = [
"databake", "databake",
"derive-docs", "derive-docs",
"expectorate", "expectorate",
"fnv",
"form_urlencoded", "form_urlencoded",
"futures", "futures",
"git_rev", "git_rev",
@ -1734,18 +1737,6 @@ dependencies = [
"zip", "zip",
] ]
[[package]]
name = "kcl-macros"
version = "0.1.0"
dependencies = [
"databake",
"kcl-lib",
"pretty_assertions",
"proc-macro2",
"quote",
"syn 2.0.87",
]
[[package]] [[package]]
name = "kcl-test-server" name = "kcl-test-server"
version = "0.1.16" version = "0.1.16"

View File

@ -68,7 +68,6 @@ debug = "line-tables-only"
members = [ members = [
"derive-docs", "derive-docs",
"kcl", "kcl",
"kcl-macros",
"kcl-test-server", "kcl-test-server",
"kcl-to-core", "kcl-to-core",
] ]

View File

@ -173,11 +173,11 @@ fn do_stdlib_inner(
quote! { quote! {
let code_blocks = vec![#(#cb),*]; let code_blocks = vec![#(#cb),*];
code_blocks.iter().map(|cb| { code_blocks.iter().map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}).collect::<Vec<String>>() }).collect::<Vec<String>>()
} }
} else { } else {
@ -748,8 +748,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
quote! { quote! {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn #test_name_mock() { async fn #test_name_mock() {
let program = crate::parser::top_level_parse(#code_block).unwrap(); let program = crate::Program::parse(#code_block).unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())), engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
fs: std::sync::Arc::new(crate::fs::FileManager::new()), fs: std::sync::Arc::new(crate::fs::FileManager::new()),
@ -758,7 +757,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]

View File

@ -2,8 +2,7 @@
mod test_examples_someFn { mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let program = crate::parser::top_level_parse("someFn()").unwrap(); let program = crate::Program::parse("someFn()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,8 +2,7 @@
mod test_examples_someFn { mod test_examples_someFn {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_someFn0() { async fn test_mock_example_someFn0() {
let program = crate::parser::top_level_parse("someFn()").unwrap(); let program = crate::Program::parse("someFn()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_someFn {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -3,9 +3,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nshow") crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show1() { async fn test_mock_example_show1() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_show { mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -3,9 +3,7 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func0() { async fn test_mock_example_my_func0() {
let program = let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmyFunc") crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_my_func {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_my_func1() { async fn test_mock_example_my_func1() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_my_func {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for MyFunc {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -3,9 +3,7 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to0() { async fn test_mock_example_line_to0() {
let program = let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nlineTo") crate::Program::parse("This is another code block.\nyes sirrr.\nlineTo").unwrap();
.unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -17,7 +15,9 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -36,9 +36,7 @@ mod test_examples_line_to {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_line_to1() { async fn test_mock_example_line_to1() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nlineTo").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nlineTo").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -50,7 +48,9 @@ mod test_examples_line_to {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -157,10 +157,10 @@ impl crate::docs::StdLibFn for LineTo {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -3,8 +3,7 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min0() { async fn test_mock_example_min0() {
let program = let program =
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmin").unwrap(); crate::Program::parse("This is another code block.\nyes sirrr.\nmin").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +15,9 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -35,9 +36,7 @@ mod test_examples_min {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_min1() { async fn test_mock_example_min1() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nmin").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmin").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -49,7 +48,9 @@ mod test_examples_min {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -148,10 +149,10 @@ impl crate::docs::StdLibFn for Min {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_show { mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_import { mod test_examples_import {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_import { mod test_examples_import {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_import { mod test_examples_import {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_import0() { async fn test_mock_example_import0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_import {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,9 +2,7 @@
mod test_examples_show { mod test_examples_show {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_show0() { async fn test_mock_example_show0() {
let program = let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -16,7 +14,9 @@ mod test_examples_show {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -2,8 +2,7 @@
mod test_examples_some_function { mod test_examples_some_function {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_mock_example_some_function0() { async fn test_mock_example_some_function0() {
let program = crate::parser::top_level_parse("someFunction()").unwrap(); let program = crate::Program::parse("someFunction()").unwrap();
let id_generator = crate::executor::IdGenerator::default();
let ctx = crate::executor::ExecutorContext { let ctx = crate::executor::ExecutorContext {
engine: std::sync::Arc::new(Box::new( engine: std::sync::Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new() crate::engine::conn_mock::EngineConnection::new()
@ -15,7 +14,9 @@ mod test_examples_some_function {
settings: Default::default(), settings: Default::default(),
context_type: crate::executor::ContextType::Mock, context_type: crate::executor::ContextType::Mock,
}; };
ctx.run(&program, None, id_generator, None).await.unwrap(); ctx.run(&program, &mut crate::ExecState::default())
.await
.unwrap();
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 5)] #[tokio::test(flavor = "multi_thread", worker_threads = 5)]
@ -106,10 +107,10 @@ impl crate::docs::StdLibFn for SomeFunction {
code_blocks code_blocks
.iter() .iter()
.map(|cb| { .map(|cb| {
let program = crate::parser::top_level_parse(cb).unwrap(); let program = crate::Program::parse(cb).unwrap();
let mut options: crate::ast::types::FormatOptions = Default::default(); let mut options: crate::ast::types::FormatOptions = Default::default();
options.insert_final_newline = false; options.insert_final_newline = false;
program.recast(&options, 0) program.ast.recast(&options, 0)
}) })
.collect::<Vec<String>>() .collect::<Vec<String>>()
} }

View File

@ -1,29 +1,20 @@
cnr := "cargo nextest run" cnr := "cargo nextest run"
cita := "cargo insta test --accept" cita := "cargo insta test --accept"
# Create a new KCL snapshot test from `tests/inputs/my-test.kcl`. # Run the same lint checks we run in CI.
new-test name:
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
TWENTY_TWENTY=overwrite {{cnr}} --test executor -E 'test(=visuals::{{name}})'
lint: lint:
cargo clippy --workspace --all-targets -- -D warnings cargo clippy --workspace --all-targets -- -D warnings
# Generate the stdlib image artifacts
# Then run the stdlib docs generation
redo-kcl-stdlib-docs: redo-kcl-stdlib-docs:
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
# Create a new KCL deterministic simulation test case. # Create a new KCL deterministic simulation test case.
new-sim-test test_name kcl_program render_to_png="true": new-sim-test test_name render_to_png="true":
# Each test file gets its own directory. This will contain the KCL program, and its
# snapshotted artifacts (e.g. serialized tokens, serialized ASTs, program memory,
# PNG snapshots, etc).
mkdir kcl/tests/{{test_name}}
echo "{{kcl_program}}" > kcl/tests/{{test_name}}/input.kcl
# Add the various tests for this new test case.
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" | sed "s/RENDER_TO_PNG/{{render_to_png}}/" >> kcl/src/simulation_tests.rs
# Run all the tests for the first time, in the right order.
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize {{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse {{cita}} -p kcl-lib -- tests::{{test_name}}::parse
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse {{cita}} -p kcl-lib -- tests::{{test_name}}::unparse
TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute TWENTY_TWENTY=overwrite {{cita}} -p kcl-lib -- tests::{{test_name}}::kcl_test_execute

View File

@ -1,21 +0,0 @@
[package]
name = "kcl-macros"
description = "Macro for compiling KCL to its AST during Rust compile-time"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/KittyCAD/modeling-app"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
databake = "0.1.8"
kcl-lib = { path = "../kcl" }
proc-macro2 = "1"
quote = "1"
syn = { version = "2.0.87", features = ["full"] }
[dev-dependencies]
pretty_assertions = "1.4.1"

View File

@ -1,22 +0,0 @@
//! This crate contains macros for parsing KCL at Rust compile-time.
use databake::*;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, LitStr};
/// Parses KCL into its AST at compile-time.
/// This macro takes exactly one argument: A string literal containing KCL.
/// # Examples
/// ```
/// extern crate alloc;
/// use kcl_compile_macro::parse_kcl;
/// let ast: kcl_lib::ast::types::Program = parse_kcl!("const y = 4");
/// ```
#[proc_macro]
pub fn parse(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as LitStr);
let kcl_src = input.value();
let ast = kcl_lib::parser::top_level_parse(&kcl_src).unwrap();
let ast_struct = ast.bake(&Default::default());
quote!(#ast_struct).into()
}

View File

@ -1,60 +0,0 @@
extern crate alloc;
use kcl_lib::ast::types::{
BodyItem, Expr, Identifier, ItemVisibility, Literal, LiteralValue, ModuleId, Node, Program, VariableDeclaration,
VariableDeclarator, VariableKind,
};
use kcl_macros::parse;
use pretty_assertions::assert_eq;
#[test]
fn basic() {
let actual = parse!("const y = 4");
let module_id = ModuleId::default();
let expected = Node {
inner: Program {
body: vec![BodyItem::VariableDeclaration(Box::new(Node::new(
VariableDeclaration {
declarations: vec![Node::new(
VariableDeclarator {
id: Node::new(
Identifier {
name: "y".to_owned(),
digest: None,
},
6,
7,
module_id,
),
init: Expr::Literal(Box::new(Node::new(
Literal {
value: LiteralValue::IInteger(4),
raw: "4".to_owned(),
digest: None,
},
10,
11,
module_id,
))),
digest: None,
},
6,
11,
module_id,
)],
visibility: ItemVisibility::Default,
kind: VariableKind::Const,
digest: None,
},
0,
11,
module_id,
)))],
non_code_meta: Default::default(),
digest: None,
},
start: 0,
end: 11,
module_id,
};
assert_eq!(expected, actual);
}

View File

@ -15,7 +15,7 @@ use hyper::{
service::{make_service_fn, service_fn}, service::{make_service_fn, service_fn},
Body, Error, Response, Server, Body, Error, Response, Server,
}; };
use kcl_lib::{ast::types::ModuleId, executor::ExecutorContext, settings::types::UnitLength, test_server::RequestBody}; use kcl_lib::{test_server::RequestBody, ExecState, ExecutorContext, Program, UnitLength};
use tokio::{ use tokio::{
sync::{mpsc, oneshot}, sync::{mpsc, oneshot},
task::JoinHandle, task::JoinHandle,
@ -157,21 +157,18 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
Err(e) => return bad_request(format!("Invalid request JSON: {e}")), Err(e) => return bad_request(format!("Invalid request JSON: {e}")),
}; };
let RequestBody { kcl_program, test_name } = body; let RequestBody { kcl_program, test_name } = body;
let module_id = ModuleId::default();
let parser = match kcl_lib::token::lexer(&kcl_program, module_id) { let program = match Program::parse(&kcl_program) {
Ok(ts) => kcl_lib::parser::Parser::new(ts),
Err(e) => return bad_request(format!("tokenization error: {e}")),
};
let program = match parser.ast() {
Ok(pr) => pr, Ok(pr) => pr,
Err(e) => return bad_request(format!("Parse error: {e}")), Err(e) => return bad_request(format!("Parse error: {e}")),
}; };
eprintln!("Executing {test_name}"); eprintln!("Executing {test_name}");
let mut id_generator = kcl_lib::executor::IdGenerator::default(); let mut exec_state = ExecState::default();
// This is a shitty source range, I don't know what else to use for it though. // This is a shitty source range, I don't know what else to use for it though.
// There's no actual KCL associated with this reset_scene call. // There's no actual KCL associated with this reset_scene call.
if let Err(e) = state if let Err(e) = state
.reset_scene(&mut id_generator, kcl_lib::executor::SourceRange::default()) .reset_scene(&mut exec_state, kcl_lib::SourceRange::default())
.await .await
{ {
return kcl_err(e); return kcl_err(e);
@ -179,7 +176,7 @@ async fn snapshot_endpoint(body: Bytes, state: ExecutorContext) -> Response<Body
// Let users know if the test is taking a long time. // Let users know if the test is taking a long time.
let (done_tx, done_rx) = oneshot::channel::<()>(); let (done_tx, done_rx) = oneshot::channel::<()>();
let timer = time_until(done_rx); let timer = time_until(done_rx);
let snapshot = match state.execute_and_prepare_snapshot(&program, id_generator, None).await { let snapshot = match state.execute_and_prepare_snapshot(&program, &mut exec_state).await {
Ok(sn) => sn, Ok(sn) => sn,
Err(e) => return kcl_err(e), Err(e) => return kcl_err(e),
}; };

View File

@ -1,9 +1,8 @@
use anyhow::Result; use anyhow::Result;
use indexmap::IndexMap; use indexmap::IndexMap;
use kcl_lib::{ use kcl_lib::{
engine::ExecutionKind, exec::{DefaultPlanes, IdGenerator},
errors::KclError, ExecutionKind, KclError,
executor::{DefaultPlanes, IdGenerator},
}; };
use kittycad_modeling_cmds::{ use kittycad_modeling_cmds::{
self as kcmc, self as kcmc,
@ -23,8 +22,8 @@ const NEED_PLANES: bool = true;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct EngineConnection { pub struct EngineConnection {
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>>, batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>>, batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
core_test: Arc<Mutex<String>>, core_test: Arc<Mutex<String>>,
default_planes: Arc<RwLock<Option<DefaultPlanes>>>, default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
execution_kind: Arc<Mutex<ExecutionKind>>, execution_kind: Arc<Mutex<ExecutionKind>>,
@ -354,12 +353,12 @@ fn codegen_cpp_repl_uuid_setters(reps_id: &str, entity_ids: &[uuid::Uuid]) -> St
} }
#[async_trait::async_trait] #[async_trait::async_trait]
impl kcl_lib::engine::EngineManager for EngineConnection { impl kcl_lib::EngineManager for EngineConnection {
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>> { fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
self.batch.clone() self.batch.clone()
} }
fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>> { fn batch_end(&self) -> Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>> {
self.batch_end.clone() self.batch_end.clone()
} }
@ -378,7 +377,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
async fn default_planes( async fn default_planes(
&self, &self,
id_generator: &mut IdGenerator, id_generator: &mut IdGenerator,
source_range: kcl_lib::executor::SourceRange, source_range: kcl_lib::SourceRange,
) -> Result<DefaultPlanes, KclError> { ) -> Result<DefaultPlanes, KclError> {
if NEED_PLANES { if NEED_PLANES {
{ {
@ -400,7 +399,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
async fn clear_scene_post_hook( async fn clear_scene_post_hook(
&self, &self,
_id_generator: &mut IdGenerator, _id_generator: &mut IdGenerator,
_source_range: kcl_lib::executor::SourceRange, _source_range: kcl_lib::SourceRange,
) -> Result<(), KclError> { ) -> Result<(), KclError> {
Ok(()) Ok(())
} }
@ -408,9 +407,9 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
async fn inner_send_modeling_cmd( async fn inner_send_modeling_cmd(
&self, &self,
id: uuid::Uuid, id: uuid::Uuid,
_source_range: kcl_lib::executor::SourceRange, _source_range: kcl_lib::SourceRange,
cmd: WebSocketRequest, cmd: WebSocketRequest,
_id_to_source_range: std::collections::HashMap<uuid::Uuid, kcl_lib::executor::SourceRange>, _id_to_source_range: std::collections::HashMap<uuid::Uuid, kcl_lib::SourceRange>,
) -> Result<WebSocketResponse, KclError> { ) -> Result<WebSocketResponse, KclError> {
match cmd { match cmd {
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch { WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {

View File

@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use kcl_lib::executor::{ExecutorContext, IdGenerator}; use kcl_lib::{ExecState, ExecutorContext};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
@ -7,21 +7,15 @@ mod conn_mock_core;
///Converts the given kcl code to an engine test ///Converts the given kcl code to an engine test
pub async fn kcl_to_engine_core(code: &str) -> Result<String> { pub async fn kcl_to_engine_core(code: &str) -> Result<String> {
let program = kcl_lib::parser::top_level_parse(code)?; let program = kcl_lib::Program::parse(code)?;
let result = Arc::new(Mutex::new("".into())); let result = Arc::new(Mutex::new("".into()));
let ref_result = Arc::clone(&result); let ref_result = Arc::clone(&result);
let ctx = ExecutorContext { let ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
engine: Arc::new(Box::new( crate::conn_mock_core::EngineConnection::new(ref_result).await?,
crate::conn_mock_core::EngineConnection::new(ref_result).await?, )));
)), ctx.run(&program, &mut ExecState::default()).await?;
fs: Arc::new(kcl_lib::fs::FileManager::new()),
stdlib: Arc::new(kcl_lib::std::StdLib::new()),
settings: Default::default(),
context_type: kcl_lib::executor::ContextType::MockCustomForwarded,
};
let _memory = ctx.run(&program, None, IdGenerator::default(), None).await?;
let result = result.lock().expect("mutex lock").clone(); let result = result.lock().expect("mutex lock").clone();
Ok(result) Ok(result)

View File

@ -21,6 +21,7 @@ convert_case = "0.6.0"
dashmap = "6.1.0" dashmap = "6.1.0"
databake = { version = "0.1.8", features = ["derive"] } databake = { version = "0.1.8", features = ["derive"] }
derive-docs = { version = "0.1.29", path = "../derive-docs" } derive-docs = { version = "0.1.29", path = "../derive-docs" }
fnv = "1.0.7"
form_urlencoded = "1.2.1" form_urlencoded = "1.2.1"
futures = { version = "0.3.31" } futures = { version = "0.3.31" }
git_rev = "0.1.0" git_rev = "0.1.0"
@ -86,7 +87,7 @@ expectorate = "1.1.0"
handlebars = "6.2.0" handlebars = "6.2.0"
iai = "0.1" iai = "0.1"
image = { version = "0.25.5", default-features = false, features = ["png"] } image = { version = "0.25.5", default-features = false, features = ["png"] }
insta = { version = "1.41.1", features = ["json", "filters"] } insta = { version = "1.41.1", features = ["json", "filters", "redactions"] }
itertools = "0.13.0" itertools = "0.13.0"
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] } tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }

View File

@ -1,12 +1,5 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion}; use criterion::{black_box, criterion_group, criterion_main, Criterion};
pub fn bench_lex(c: &mut Criterion) {
let module_id = kcl_lib::ast::types::ModuleId::default();
c.bench_function("lex_cube", |b| b.iter(|| lex(CUBE_PROGRAM, module_id)));
c.bench_function("lex_big_kitt", |b| b.iter(|| lex(KITT_PROGRAM, module_id)));
c.bench_function("lex_pipes_on_pipes", |b| b.iter(|| lex(PIPES_PROGRAM, module_id)));
}
pub fn bench_parse(c: &mut Criterion) { pub fn bench_parse(c: &mut Criterion) {
for (name, file) in [ for (name, file) in [
("pipes_on_pipes", PIPES_PROGRAM), ("pipes_on_pipes", PIPES_PROGRAM),
@ -16,28 +9,20 @@ pub fn bench_parse(c: &mut Criterion) {
("mike_stress_test", MIKE_STRESS_TEST_PROGRAM), ("mike_stress_test", MIKE_STRESS_TEST_PROGRAM),
("koch snowflake", LSYSTEM_KOCH_SNOWFLAKE_PROGRAM), ("koch snowflake", LSYSTEM_KOCH_SNOWFLAKE_PROGRAM),
] { ] {
let module_id = kcl_lib::ast::types::ModuleId::default();
let tokens = kcl_lib::token::lexer(file, module_id).unwrap();
c.bench_function(&format!("parse_{name}"), move |b| { c.bench_function(&format!("parse_{name}"), move |b| {
let tok = tokens.clone();
b.iter(move || { b.iter(move || {
let parser = kcl_lib::parser::Parser::new(tok.clone()); black_box(kcl_lib::Program::parse(file).unwrap());
black_box(parser.ast().unwrap());
}) })
}); });
} }
} }
fn lex(program: &str, module_id: kcl_lib::ast::types::ModuleId) { criterion_group!(benches, bench_parse);
black_box(kcl_lib::token::lexer(program, module_id).unwrap());
}
criterion_group!(benches, bench_lex, bench_parse);
criterion_main!(benches); criterion_main!(benches);
const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl"); const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_svg.kcl");
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl"); const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../tests/mike_stress_test/input.kcl");
const LSYSTEM_KOCH_SNOWFLAKE_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); const LSYSTEM_KOCH_SNOWFLAKE_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");

View File

@ -1,32 +1,7 @@
use iai::black_box; use iai::black_box;
pub fn parse(program: &str) { pub fn parse(program: &str) {
let module_id = kcl_lib::ast::types::ModuleId::default(); black_box(kcl_lib::Program::parse(program).unwrap());
let tokens = kcl_lib::token::lexer(program, module_id).unwrap();
let tok = tokens.clone();
let parser = kcl_lib::parser::Parser::new(tok.clone());
black_box(parser.ast().unwrap());
}
fn lex_kitt() {
let module_id = kcl_lib::ast::types::ModuleId::default();
black_box(kcl_lib::token::lexer(KITT_PROGRAM, module_id).unwrap());
}
fn lex_pipes() {
let module_id = kcl_lib::ast::types::ModuleId::default();
black_box(kcl_lib::token::lexer(PIPES_PROGRAM, module_id).unwrap());
}
fn lex_cube() {
let module_id = kcl_lib::ast::types::ModuleId::default();
black_box(kcl_lib::token::lexer(CUBE_PROGRAM, module_id).unwrap());
}
fn lex_math() {
let module_id = kcl_lib::ast::types::ModuleId::default();
black_box(kcl_lib::token::lexer(MATH_PROGRAM, module_id).unwrap());
}
fn lex_lsystem() {
let module_id = kcl_lib::ast::types::ModuleId::default();
black_box(kcl_lib::token::lexer(LSYSTEM_PROGRAM, module_id).unwrap());
} }
fn parse_kitt() { fn parse_kitt() {
@ -46,11 +21,6 @@ fn parse_lsystem() {
} }
iai::main! { iai::main! {
lex_kitt,
lex_pipes,
lex_cube,
lex_math,
lex_lsystem,
parse_kitt, parse_kitt,
parse_pipes, parse_pipes,
parse_cube, parse_cube,

View File

@ -9,7 +9,7 @@ pub fn bench_digest(c: &mut Criterion) {
("mike_stress_test", MIKE_STRESS_TEST_PROGRAM), ("mike_stress_test", MIKE_STRESS_TEST_PROGRAM),
("lsystem", LSYSTEM_PROGRAM), ("lsystem", LSYSTEM_PROGRAM),
] { ] {
let prog = kcl_lib::parser::top_level_parse(file).unwrap(); let prog = kcl_lib::Program::parse(file).unwrap();
c.bench_function(&format!("digest_{name}"), move |b| { c.bench_function(&format!("digest_{name}"), move |b| {
let prog = prog.clone(); let prog = prog.clone();
@ -28,5 +28,5 @@ const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_sv
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl"); const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../tests/mike_stress_test/input.kcl");
const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");

View File

@ -1,5 +1,5 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use kcl_lib::{settings::types::UnitLength::Mm, test_server}; use kcl_lib::{test_server, UnitLength::Mm};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
pub fn bench_execute(c: &mut Criterion) { pub fn bench_execute(c: &mut Criterion) {

View File

@ -3,7 +3,7 @@ use iai::black_box;
async fn execute_server_rack_heavy() { async fn execute_server_rack_heavy() {
let code = SERVER_RACK_HEAVY_PROGRAM; let code = SERVER_RACK_HEAVY_PROGRAM;
black_box( black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm) kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
.await .await
.unwrap(), .unwrap(),
); );
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
async fn execute_server_rack_lite() { async fn execute_server_rack_lite() {
let code = SERVER_RACK_LITE_PROGRAM; let code = SERVER_RACK_LITE_PROGRAM;
black_box( black_box(
kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::settings::types::UnitLength::Mm) kcl_lib::test_server::execute_and_snapshot(code, kcl_lib::UnitLength::Mm)
.await .await
.unwrap(), .unwrap(),
); );

View File

@ -1,5 +1,5 @@
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion};
use kcl_lib::lsp::test_util::kcl_lsp_server; use kcl_lib::kcl_lsp_server;
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tower_lsp::LanguageServer; use tower_lsp::LanguageServer;
@ -62,6 +62,6 @@ const KITT_PROGRAM: &str = include_str!("../../tests/executor/inputs/kittycad_sv
const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl"); const PIPES_PROGRAM: &str = include_str!("../../tests/executor/inputs/pipes_on_pipes.kcl");
const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl"); const CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.kcl");
const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl"); const MATH_PROGRAM: &str = include_str!("../../tests/executor/inputs/math.kcl");
const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../../tests/executor/inputs/mike_stress_test.kcl"); const MIKE_STRESS_TEST_PROGRAM: &str = include_str!("../tests/mike_stress_test/input.kcl");
const GLOBAL_TAGS_FILE: &str = include_str!("../../tests/executor/inputs/global-tags.kcl"); const GLOBAL_TAGS_FILE: &str = include_str!("../../tests/executor/inputs/global-tags.kcl");
const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl"); const LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");

View File

@ -1,5 +1,5 @@
use iai::black_box; use iai::black_box;
use kcl_lib::lsp::test_util::kcl_lsp_server; use kcl_lib::kcl_lsp_server;
use tower_lsp::LanguageServer; use tower_lsp::LanguageServer;
async fn kcl_lsp_semantic_tokens(code: &str) { async fn kcl_lsp_semantic_tokens(code: &str) {

View File

@ -9,11 +9,12 @@ use kittycad_modeling_cmds as kcmc;
use crate::{ use crate::{
ast::types::{ ast::types::{
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, PipeExpression, PipeSubstitution, ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, PipeExpression, PipeSubstitution,
Program, VariableDeclarator, VariableDeclarator,
}, },
engine::EngineManager, engine::EngineManager,
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
executor::{Point2d, SourceRange}, executor::{Point2d, SourceRange},
Program,
}; };
use super::types::{ModuleId, Node}; use super::types::{ModuleId, Node};
@ -22,13 +23,13 @@ type Point3d = kcmc::shared::Point3d<f64>;
#[derive(Debug)] #[derive(Debug)]
/// The control point data for a curve or line. /// The control point data for a curve or line.
pub struct ControlPointData { struct ControlPointData {
/// The control points for the curve or line. /// The control points for the curve or line.
pub points: Vec<Point3d>, points: Vec<Point3d>,
/// The command that created this curve or line. /// The command that created this curve or line.
pub command: PathCommand, _command: PathCommand,
/// The id of the curve or line. /// The id of the curve or line.
pub id: uuid::Uuid, _id: uuid::Uuid,
} }
const EPSILON: f64 = 0.015625; // or 2^-6 const EPSILON: f64 = 0.015625; // or 2^-6
@ -37,7 +38,7 @@ const EPSILON: f64 = 0.015625; // or 2^-6
/// a move or a new line. /// a move or a new line.
pub async fn modify_ast_for_sketch( pub async fn modify_ast_for_sketch(
engine: &Arc<Box<dyn EngineManager>>, engine: &Arc<Box<dyn EngineManager>>,
program: &mut Node<Program>, program: &mut Program,
module_id: ModuleId, module_id: ModuleId,
// The name of the sketch. // The name of the sketch.
sketch_name: &str, sketch_name: &str,
@ -50,7 +51,7 @@ pub async fn modify_ast_for_sketch(
// If it is, we cannot modify it. // If it is, we cannot modify it.
// Get the information about the sketch. // Get the information about the sketch.
if let Some(ast_sketch) = program.get_variable(sketch_name) { if let Some(ast_sketch) = program.ast.get_variable(sketch_name) {
let constraint_level = match ast_sketch { let constraint_level = match ast_sketch {
super::types::Definition::Variable(var) => var.get_constraint_level(), super::types::Definition::Variable(var) => var.get_constraint_level(),
super::types::Definition::Import(import) => import.get_constraint_level(), super::types::Definition::Import(import) => import.get_constraint_level(),
@ -130,8 +131,8 @@ pub async fn modify_ast_for_sketch(
control_points.push(ControlPointData { control_points.push(ControlPointData {
points: data.control_points.clone(), points: data.control_points.clone(),
command: segment.command, _command: segment.command,
id: (*command_id).into(), _id: (*command_id).into(),
}); });
} }
} }
@ -179,12 +180,12 @@ pub async fn modify_ast_for_sketch(
)?; )?;
// Add the sketch back to the program. // Add the sketch back to the program.
program.replace_variable(sketch_name, sketch); program.ast.replace_variable(sketch_name, sketch);
let recasted = program.recast(&FormatOptions::default(), 0); let recasted = program.ast.recast(&FormatOptions::default(), 0);
// Re-parse the ast so we get the correct source ranges. // Re-parse the ast so we get the correct source ranges.
*program = crate::parser::parse(&recasted, module_id)?; *program = crate::parser::parse(&recasted, module_id)?.into();
Ok(recasted) Ok(recasted)
} }

View File

@ -223,7 +223,7 @@ impl Node<Program> {
/// Check the provided Program for any lint findings. /// Check the provided Program for any lint findings.
pub fn lint<'a, RuleT>(&'a self, rule: RuleT) -> Result<Vec<crate::lint::Discovered>> pub fn lint<'a, RuleT>(&'a self, rule: RuleT) -> Result<Vec<crate::lint::Discovered>>
where where
RuleT: crate::lint::rule::Rule<'a>, RuleT: crate::lint::Rule<'a>,
{ {
let v = Arc::new(Mutex::new(vec![])); let v = Arc::new(Mutex::new(vec![]));
crate::walk::walk(self, &|node: crate::walk::Node<'a>| { crate::walk::walk(self, &|node: crate::walk::Node<'a>| {

View File

@ -1,4 +1,5 @@
//! Core dump related structures and functions. //! Core dump related structures and functions.
#![allow(dead_code)]
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod local; pub mod local;

View File

@ -26,14 +26,14 @@ type Point3D = kcmc::shared::Point3d<f64>;
use crate::{ use crate::{
ast::types::{ ast::types::{
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, Program, TagDeclarator, BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, TagDeclarator, TagNode,
TagNode,
}, },
engine::{EngineManager, ExecutionKind}, engine::{EngineManager, ExecutionKind},
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
fs::{FileManager, FileSystem}, fs::{FileManager, FileSystem},
settings::types::UnitLength, settings::types::UnitLength,
std::{FnAsArg, StdLib}, std::{FnAsArg, StdLib},
Program,
}; };
/// State for executing a program. /// State for executing a program.
@ -152,6 +152,7 @@ impl ProgramMemory {
/// Find all solids in the memory that are on a specific sketch id. /// Find all solids in the memory that are on a specific sketch id.
/// This does not look inside closures. But as long as we do not allow /// This does not look inside closures. But as long as we do not allow
/// mutation of variables in KCL, closure memory should be a subset of this. /// mutation of variables in KCL, closure memory should be a subset of this.
#[allow(clippy::vec_box)]
pub fn find_solids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<Box<Solid>> { pub fn find_solids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<Box<Solid>> {
self.environments self.environments
.iter() .iter()
@ -537,6 +538,7 @@ impl Geometry {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type")] #[serde(tag = "type")]
#[allow(clippy::vec_box)]
pub enum Geometries { pub enum Geometries {
Sketches(Vec<Box<Sketch>>), Sketches(Vec<Box<Sketch>>),
Solids(Vec<Box<Solid>>), Solids(Vec<Box<Solid>>),
@ -555,6 +557,7 @@ impl From<Geometry> for Geometries {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
#[allow(clippy::vec_box)]
pub enum SketchSet { pub enum SketchSet {
Sketch(Box<Sketch>), Sketch(Box<Sketch>),
Sketches(Vec<Box<Sketch>>), Sketches(Vec<Box<Sketch>>),
@ -635,6 +638,7 @@ impl From<Box<Sketch>> for Vec<Box<Sketch>> {
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(tag = "type", rename_all = "camelCase")] #[serde(tag = "type", rename_all = "camelCase")]
#[allow(clippy::vec_box)]
pub enum SolidSet { pub enum SolidSet {
Solid(Box<Solid>), Solid(Box<Solid>),
Solids(Vec<Box<Solid>>), Solids(Vec<Box<Solid>>),
@ -801,6 +805,17 @@ impl Plane {
}, },
} }
} }
/// The standard planes are XY, YZ and XZ (in both positive and negative)
pub fn is_standard(&self) -> bool {
!self.is_custom()
}
/// The standard planes are XY, YZ and XZ (in both positive and negative)
/// Custom planes are any other plane that the user might specify.
pub fn is_custom(&self) -> bool {
matches!(self.value, PlaneType::Custom)
}
} }
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
@ -1049,6 +1064,14 @@ impl KclValue {
} }
} }
pub fn as_plane(&self) -> Option<&Plane> {
if let KclValue::Plane(value) = &self {
Some(value)
} else {
None
}
}
pub fn as_solid(&self) -> Option<&Solid> { pub fn as_solid(&self) -> Option<&Solid> {
if let KclValue::Solid(value) = &self { if let KclValue::Solid(value) = &self {
Some(value) Some(value)
@ -2170,6 +2193,70 @@ impl ExecutorContext {
}) })
} }
#[cfg(not(target_arch = "wasm32"))]
pub async fn new_mock() -> Self {
ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
)),
fs: Arc::new(FileManager::new()),
stdlib: Arc::new(StdLib::new()),
settings: Default::default(),
context_type: ContextType::Mock,
}
}
#[cfg(target_arch = "wasm32")]
pub async fn new(
engine_manager: crate::engine::conn_wasm::EngineCommandManager,
fs_manager: crate::fs::wasm::FileSystemManager,
units: UnitLength,
) -> Result<Self, String> {
Ok(ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_wasm::EngineConnection::new(engine_manager)
.await
.map_err(|e| format!("{:?}", e))?,
)),
fs: Arc::new(FileManager::new(fs_manager)),
stdlib: Arc::new(StdLib::new()),
settings: ExecutorSettings {
units,
..Default::default()
},
context_type: ContextType::Live,
})
}
#[cfg(target_arch = "wasm32")]
pub async fn new_mock(fs_manager: crate::fs::wasm::FileSystemManager, units: UnitLength) -> Result<Self, String> {
Ok(ExecutorContext {
engine: Arc::new(Box::new(
crate::engine::conn_mock::EngineConnection::new()
.await
.map_err(|e| format!("{:?}", e))?,
)),
fs: Arc::new(FileManager::new(fs_manager)),
stdlib: Arc::new(StdLib::new()),
settings: ExecutorSettings {
units,
..Default::default()
},
context_type: ContextType::Mock,
})
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new_forwarded_mock(engine: Arc<Box<dyn EngineManager>>) -> Self {
ExecutorContext {
engine,
fs: Arc::new(FileManager::new()),
stdlib: Arc::new(StdLib::new()),
settings: Default::default(),
context_type: ContextType::MockCustomForwarded,
}
}
/// Create a new default executor context. /// Create a new default executor context.
/// With a kittycad client. /// With a kittycad client.
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment /// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
@ -2193,9 +2280,17 @@ impl ExecutorContext {
/// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment /// This allows for passing in `ZOO_API_TOKEN` and `ZOO_HOST` as environment
/// variables. /// variables.
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub async fn new_with_default_client(settings: ExecutorSettings) -> Result<Self> { pub async fn new_with_default_client(units: UnitLength) -> Result<Self> {
// Create the client. // Create the client.
let ctx = Self::new_with_client(settings, None, None).await?; let ctx = Self::new_with_client(
ExecutorSettings {
units,
..Default::default()
},
None,
None,
)
.await?;
Ok(ctx) Ok(ctx)
} }
@ -2223,48 +2318,31 @@ impl ExecutorContext {
pub async fn reset_scene( pub async fn reset_scene(
&self, &self,
id_generator: &mut IdGenerator, exec_state: &mut ExecState,
source_range: crate::executor::SourceRange, source_range: crate::executor::SourceRange,
) -> Result<()> { ) -> Result<()> {
self.engine.clear_scene(id_generator, source_range).await?; self.engine
.clear_scene(&mut exec_state.id_generator, source_range)
.await?;
Ok(()) Ok(())
} }
/// Perform the execution of a program. /// Perform the execution of a program.
/// You can optionally pass in some initialization memory. /// You can optionally pass in some initialization memory.
/// Kurt uses this for partial execution. /// Kurt uses this for partial execution.
pub async fn run( pub async fn run(&self, program: &Program, exec_state: &mut ExecState) -> Result<(), KclError> {
&self, self.run_with_session_data(program, exec_state).await?;
program: NodeRef<'_, crate::ast::types::Program>, Ok(())
memory: Option<ProgramMemory>,
id_generator: IdGenerator,
project_directory: Option<String>,
) -> Result<ExecState, KclError> {
self.run_with_session_data(program, memory, id_generator, project_directory)
.await
.map(|x| x.0)
} }
/// Perform the execution of a program. /// Perform the execution of a program.
/// You can optionally pass in some initialization memory. /// You can optionally pass in some initialization memory.
/// Kurt uses this for partial execution. /// Kurt uses this for partial execution.
pub async fn run_with_session_data( pub async fn run_with_session_data(
&self, &self,
program: NodeRef<'_, crate::ast::types::Program>, program: &Program,
memory: Option<ProgramMemory>, exec_state: &mut ExecState,
id_generator: IdGenerator, ) -> Result<Option<ModelingSessionData>, KclError> {
project_directory: Option<String>,
) -> Result<(ExecState, Option<ModelingSessionData>), KclError> {
let memory = if let Some(memory) = memory {
memory.clone()
} else {
Default::default()
};
let mut exec_state = ExecState {
memory,
id_generator,
project_directory,
..Default::default()
};
// TODO: Use the top-level file's path. // TODO: Use the top-level file's path.
exec_state.add_module(std::path::PathBuf::from("")); exec_state.add_module(std::path::PathBuf::from(""));
// Before we even start executing the program, set the units. // Before we even start executing the program, set the units.
@ -2285,10 +2363,10 @@ impl ExecutorContext {
) )
.await?; .await?;
self.inner_execute(program, &mut exec_state, crate::executor::BodyType::Root) self.inner_execute(&program.ast, exec_state, crate::executor::BodyType::Root)
.await?; .await?;
let session_data = self.engine.get_session_data(); let session_data = self.engine.get_session_data();
Ok((exec_state, session_data)) Ok(session_data)
} }
/// Execute an AST's program. /// Execute an AST's program.
@ -2539,23 +2617,15 @@ impl ExecutorContext {
/// Execute the program, then get a PNG screenshot. /// Execute the program, then get a PNG screenshot.
pub async fn execute_and_prepare_snapshot( pub async fn execute_and_prepare_snapshot(
&self, &self,
program: NodeRef<'_, Program>, program: &Program,
id_generator: IdGenerator, exec_state: &mut ExecState,
project_directory: Option<String>,
) -> Result<TakeSnapshot> { ) -> Result<TakeSnapshot> {
self.execute_and_prepare(program, id_generator, project_directory) self.execute_and_prepare(program, exec_state).await
.await
.map(|(_state, snap)| snap)
} }
/// Execute the program, return the interpreter and outputs. /// Execute the program, return the interpreter and outputs.
pub async fn execute_and_prepare( pub async fn execute_and_prepare(&self, program: &Program, exec_state: &mut ExecState) -> Result<TakeSnapshot> {
&self, self.run(program, exec_state).await?;
program: NodeRef<'_, Program>,
id_generator: IdGenerator,
project_directory: Option<String>,
) -> Result<(ExecState, TakeSnapshot)> {
let state = self.run(program, None, id_generator, project_directory).await?;
// Zoom to fit. // Zoom to fit.
self.engine self.engine
@ -2588,7 +2658,7 @@ impl ExecutorContext {
else { else {
anyhow::bail!("Unexpected response from engine: {:?}", resp); anyhow::bail!("Unexpected response from engine: {:?}", resp);
}; };
Ok((state, contents)) Ok(contents)
} }
} }
@ -2695,7 +2765,7 @@ mod tests {
use crate::ast::types::{Identifier, Node, Parameter}; use crate::ast::types::{Identifier, Node, Parameter};
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> { pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
let program = crate::parser::top_level_parse(code)?; let program = Program::parse(code)?;
let ctx = ExecutorContext { let ctx = ExecutorContext {
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)), engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
@ -2704,7 +2774,8 @@ mod tests {
settings: Default::default(), settings: Default::default(),
context_type: ContextType::Mock, context_type: ContextType::Mock,
}; };
let exec_state = ctx.run(&program, None, IdGenerator::default(), None).await?; let mut exec_state = ExecState::default();
ctx.run(&program, &mut exec_state).await?;
Ok(exec_state.memory) Ok(exec_state.memory)
} }
@ -3033,14 +3104,14 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_get_member_of_array_with_function() { async fn test_get_member_of_array_with_function() {
let ast = r#"fn box = (array) => { let ast = r#"fn box = (arr) => {
let myBox =startSketchOn('XY') let myBox =startSketchOn('XY')
|> startProfileAt(array[0], %) |> startProfileAt(arr[0], %)
|> line([0, array[1]], %) |> line([0, arr[1]], %)
|> line([array[2], 0], %) |> line([arr[2], 0], %)
|> line([0, -array[1]], %) |> line([0, -arr[1]], %)
|> close(%) |> close(%)
|> extrude(array[3], %) |> extrude(arr[3], %)
return myBox return myBox
} }

View File

@ -13,26 +13,112 @@ macro_rules! println {
} }
} }
pub mod ast; mod ast;
pub mod coredump; mod coredump;
pub mod docs; mod docs;
pub mod engine; mod engine;
pub mod errors; mod errors;
pub mod executor; mod executor;
pub mod fs; mod fs;
mod function_param; mod function_param;
pub mod lint; pub mod lint;
pub mod lsp; mod lsp;
pub mod parser; mod parser;
pub mod settings; mod settings;
#[cfg(test)] #[cfg(test)]
mod simulation_tests; mod simulation_tests;
pub mod std; mod std;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod test_server; pub mod test_server;
pub mod thread; mod thread;
pub mod token; mod token;
mod unparser; mod unparser;
pub mod walk; mod walk;
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
pub mod wasm; mod wasm;
pub use ast::modify::modify_ast_for_sketch;
pub use ast::types::{FormatOptions, ModuleId};
pub use coredump::CoreDump;
pub use engine::{EngineManager, ExecutionKind};
pub use errors::KclError;
pub use executor::{ExecState, ExecutorContext, ExecutorSettings, SourceRange};
pub use lsp::copilot::Backend as CopilotLspBackend;
pub use lsp::kcl::Backend as KclLspBackend;
pub use lsp::kcl::Server as KclLspServerSubCommand;
pub use settings::types::{project::ProjectConfiguration, Configuration, UnitLength};
pub use token::lexer;
// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
// Ideally we wouldn't export these things at all, they should only be used for testing.
pub mod exec {
pub use crate::executor::{DefaultPlanes, IdGenerator, KclValue, PlaneType, ProgramMemory, Sketch};
}
#[cfg(target_arch = "wasm32")]
pub mod wasm_engine {
pub use crate::coredump::wasm::{CoreDumpManager, CoreDumper};
pub use crate::engine::conn_wasm::{EngineCommandManager, EngineConnection};
pub use crate::fs::wasm::FileSystemManager;
}
#[cfg(not(target_arch = "wasm32"))]
pub mod native_engine {
pub use crate::engine::conn::EngineConnection;
}
pub mod std_utils {
pub use crate::std::utils::{get_tangential_arc_to_info, is_points_ccw_wasm, TangentialArcInfoInput};
}
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Program {
#[serde(flatten)]
ast: ast::types::Node<ast::types::Program>,
}
#[cfg(any(test, feature = "lsp-test-util"))]
pub use lsp::test_util::copilot_lsp_server;
#[cfg(any(test, feature = "lsp-test-util"))]
pub use lsp::test_util::kcl_lsp_server;
impl Program {
pub fn parse(input: &str) -> Result<Program, KclError> {
let module_id = ModuleId::default();
let tokens = token::lexer(input, module_id)?;
let parser = parser::Parser::new(tokens);
let ast = parser.ast()?;
Ok(Program { ast })
}
/// Deserialize the ast from a stringified json
pub fn compute_digest(&mut self) -> ast::types::digest::Digest {
self.ast.compute_digest()
}
pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
self.ast.lint_all()
}
pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
self.ast.lint(rule)
}
pub fn recast(&self) -> String {
// Use the default options until we integrate into the UI the ability to change them.
self.ast.recast(&Default::default(), 0)
}
pub fn recast_with_options(&self, options: &FormatOptions) -> String {
self.ast.recast(options, 0)
}
}
impl From<ast::types::Node<ast::types::Program>> for Program {
fn from(ast: ast::types::Node<ast::types::Program>) -> Program {
Self { ast }
}
}

View File

@ -2,7 +2,6 @@ mod camel_case;
mod offset_plane; mod offset_plane;
mod std_lib_args; mod std_lib_args;
#[allow(unused_imports)]
pub use camel_case::{lint_object_properties, lint_variables, Z0001}; pub use camel_case::{lint_object_properties, lint_variables, Z0001};
pub use offset_plane::{lint_should_be_offset_plane, Z0003}; pub use offset_plane::{lint_should_be_offset_plane, Z0003};
pub use std_lib_args::{lint_call_expressions, Z0002}; pub use std_lib_args::{lint_call_expressions, Z0002};

View File

@ -1,4 +1,4 @@
pub mod checks; pub mod checks;
pub mod rule; mod rule;
pub use rule::{Discovered, Finding}; pub use rule::{Discovered, Finding, Rule};

View File

@ -1,4 +1,5 @@
//! The copilot lsp server for ghost text. //! The copilot lsp server for ghost text.
#![allow(dead_code)]
pub mod cache; pub mod cache;
pub mod types; pub mod types;
@ -26,6 +27,7 @@ use tower_lsp::{
use crate::lsp::{ use crate::lsp::{
backend::Backend as _, backend::Backend as _,
copilot::cache::CopilotCache,
copilot::types::{ copilot::types::{
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo, CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams, CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
@ -131,6 +133,38 @@ impl crate::lsp::backend::Backend for Backend {
} }
impl Backend { impl Backend {
#[cfg(target_arch = "wasm32")]
pub fn new_wasm(
client: tower_lsp::Client,
fs: crate::fs::wasm::FileSystemManager,
zoo_client: kittycad::Client,
dev_mode: bool,
) -> Self {
Self::new(client, crate::fs::FileManager::new(fs), zoo_client, dev_mode)
}
pub fn new(
client: tower_lsp::Client,
fs: crate::fs::FileManager,
zoo_client: kittycad::Client,
dev_mode: bool,
) -> Self {
Self {
client,
fs: Arc::new(fs),
workspace_folders: Default::default(),
code_map: Default::default(),
editor_info: Arc::new(RwLock::new(CopilotEditorInfo::default())),
cache: Arc::new(CopilotCache::new()),
telemetry: Default::default(),
zoo_client,
is_initialized: Default::default(),
diagnostics_map: Default::default(),
dev_mode,
}
}
/// Get completions from the kittycad api. /// Get completions from the kittycad api.
pub async fn get_completions(&self, language: String, prompt: String, suffix: String) -> Result<Vec<String>> { pub async fn get_completions(&self, language: String, prompt: String, suffix: String) -> Result<Vec<String>> {
let body = kittycad::types::KclCodeCompletionRequest { let body = kittycad::types::KclCodeCompletionRequest {

View File

@ -1,4 +1,5 @@
//! Functions for the `kcl` lsp server. //! Functions for the `kcl` lsp server.
#![allow(dead_code)]
use std::{ use std::{
collections::HashMap, collections::HashMap,
@ -40,11 +41,11 @@ use tower_lsp::{
}; };
use crate::{ use crate::{
ast::types::{Expr, ModuleId, Node, NodeRef, VariableKind}, ast::types::{Expr, ModuleId, Node, VariableKind},
executor::{IdGenerator, SourceRange},
lsp::{backend::Backend as _, util::IntoDiagnostic}, lsp::{backend::Backend as _, util::IntoDiagnostic},
parser::PIPE_OPERATOR, parser::PIPE_OPERATOR,
token::TokenType, token::TokenType,
ExecState, Program, SourceRange,
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -122,6 +123,73 @@ pub struct Backend {
pub is_initialized: Arc<RwLock<bool>>, pub is_initialized: Arc<RwLock<bool>>,
} }
impl Backend {
#[cfg(target_arch = "wasm32")]
pub fn new_wasm(
client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>,
fs: crate::fs::wasm::FileSystemManager,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
) -> Result<Self, String> {
Self::with_file_manager(
client,
executor_ctx,
crate::fs::FileManager::new(fs),
zoo_client,
can_send_telemetry,
)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn new(
client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
) -> Result<Self, String> {
Self::with_file_manager(
client,
executor_ctx,
crate::fs::FileManager::new(),
zoo_client,
can_send_telemetry,
)
}
fn with_file_manager(
client: Client,
executor_ctx: Option<crate::executor::ExecutorContext>,
fs: crate::fs::FileManager,
zoo_client: kittycad::Client,
can_send_telemetry: bool,
) -> Result<Self, String> {
let stdlib = crate::std::StdLib::new();
let stdlib_completions = get_completions_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
let stdlib_signatures = get_signatures_from_stdlib(&stdlib).map_err(|e| e.to_string())?;
Ok(Self {
client,
fs: Arc::new(fs),
stdlib_completions,
stdlib_signatures,
zoo_client,
can_send_telemetry,
can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
executor_ctx: Arc::new(RwLock::new(executor_ctx)),
workspace_folders: Default::default(),
token_map: Default::default(),
ast_map: Default::default(),
memory_map: Default::default(),
code_map: Default::default(),
diagnostics_map: Default::default(),
symbols_map: Default::default(),
semantic_tokens_map: Default::default(),
is_initialized: Default::default(),
})
}
}
// Implement the shared backend trait for the language server. // Implement the shared backend trait for the language server.
#[async_trait::async_trait] #[async_trait::async_trait]
impl crate::lsp::backend::Backend for Backend { impl crate::lsp::backend::Backend for Backend {
@ -289,7 +357,7 @@ impl crate::lsp::backend::Backend for Backend {
// Execute the code if we have an executor context. // Execute the code if we have an executor context.
// This function automatically executes if we should & updates the diagnostics if we got // This function automatically executes if we should & updates the diagnostics if we got
// errors. // errors.
if self.execute(&params, &ast).await.is_err() { if self.execute(&params, &ast.into()).await.is_err() {
return; return;
} }
@ -572,7 +640,7 @@ impl Backend {
self.client.publish_diagnostics(params.uri.clone(), items, None).await; self.client.publish_diagnostics(params.uri.clone(), items, None).await;
} }
async fn execute(&self, params: &TextDocumentItem, ast: NodeRef<'_, crate::ast::types::Program>) -> Result<()> { async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
// Check if we can execute. // Check if we can execute.
if !self.can_execute().await { if !self.can_execute().await {
return Ok(()); return Ok(());
@ -589,25 +657,22 @@ impl Backend {
return Ok(()); return Ok(());
} }
let mut id_generator = IdGenerator::default(); let mut exec_state = ExecState::default();
// Clear the scene, before we execute so it's not fugly as shit. // Clear the scene, before we execute so it's not fugly as shit.
executor_ctx executor_ctx
.engine .engine
.clear_scene(&mut id_generator, SourceRange::default()) .clear_scene(&mut exec_state.id_generator, SourceRange::default())
.await?; .await?;
let exec_state = match executor_ctx.run(ast, None, id_generator, None).await { if let Err(err) = executor_ctx.run(ast, &mut exec_state).await {
Ok(exec_state) => exec_state, self.memory_map.remove(params.uri.as_str());
Err(err) => { self.add_to_diagnostics(params, &[err], false).await;
self.memory_map.remove(params.uri.as_str());
self.add_to_diagnostics(params, &[err], false).await;
// Since we already published the diagnostics we don't really care about the error // Since we already published the diagnostics we don't really care about the error
// string. // string.
return Err(anyhow::anyhow!("failed to execute code")); return Err(anyhow::anyhow!("failed to execute code"));
} }
};
self.memory_map self.memory_map
.insert(params.uri.to_string(), exec_state.memory.clone()); .insert(params.uri.to_string(), exec_state.memory.clone());

View File

@ -2,7 +2,7 @@ use std::collections::BTreeMap;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tower_lsp::{ use tower_lsp::{
lsp_types::{SemanticTokenModifier, SemanticTokenType}, lsp_types::{Diagnostic, SemanticTokenModifier, SemanticTokenType},
LanguageServer, LanguageServer,
}; };
@ -2369,7 +2369,14 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
// Get the diagnostics. // Get the diagnostics.
let diagnostics = server.diagnostics_map.get("file:///test.kcl"); let diagnostics = server.diagnostics_map.get("file:///test.kcl");
assert!(diagnostics.is_none()); if let Some(diagnostics) = diagnostics {
let ds: Vec<Diagnostic> = diagnostics.to_owned();
eprintln!("Expected no diagnostics, but found some.");
for d in ds {
eprintln!("{:?}: {}", d.severity, d.message);
}
panic!();
}
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]

View File

@ -12,6 +12,7 @@ pub(crate) mod parser_impl;
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%"; pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
pub const PIPE_OPERATOR: &str = "|>"; pub const PIPE_OPERATOR: &str = "|>";
#[cfg(test)]
/// Parse the given KCL code into an AST. This is the top-level. /// Parse the given KCL code into an AST. This is the top-level.
pub fn top_level_parse(code: &str) -> Result<Node<Program>, KclError> { pub fn top_level_parse(code: &str) -> Result<Node<Program>, KclError> {
let module_id = ModuleId::default(); let module_id = ModuleId::default();

View File

@ -2040,11 +2040,39 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use itertools::Itertools;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use super::*; use super::*;
use crate::ast::types::{BodyItem, Expr, ModuleId, VariableKind}; use crate::ast::types::{BodyItem, Expr, ModuleId, VariableKind};
fn assert_reserved(word: &str) {
// Try to use it as a variable name.
let code = format!(r#"{} = 0"#, word);
let result = crate::parser::top_level_parse(code.as_str());
let err = result.unwrap_err();
// Which token causes the error may change. In "return = 0", for
// example, "return" is the problem.
assert!(
err.message().starts_with("Unexpected token: ")
|| err
.message()
.starts_with("Cannot assign a variable to a reserved keyword: "),
"Error message is: {}",
err.message(),
);
}
#[test]
fn reserved_words() {
// Since these are stored in a set, we sort to make the tests
// deterministic.
for word in crate::token::RESERVED_WORDS.keys().sorted() {
assert_reserved(word);
}
assert_reserved("import");
}
#[test] #[test]
fn parse_args() { fn parse_args() {
for (i, (test, expected_len)) in [("someVar", 1), ("5, 3", 2), (r#""a""#, 1)].into_iter().enumerate() { for (i, (test, expected_len)) in [("someVar", 1), ("5, 3", 2), (r#""a""#, 1)].into_iter().enumerate() {

View File

@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize};
use validator::{Validate, ValidateRange}; use validator::{Validate, ValidateRange};
const DEFAULT_THEME_COLOR: f64 = 264.5; const DEFAULT_THEME_COLOR: f64 = 264.5;
pub const DEFAULT_PROJECT_KCL_FILE: &str = "main.kcl";
const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn"; const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
/// High level configuration. /// High level configuration.

File diff suppressed because it is too large Load Diff

View File

@ -692,7 +692,7 @@ macro_rules! let_field_of {
impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat { impl<'a> FromKclValue<'a> for crate::std::import::ImportFormat {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?; let obj = arg.as_object()?;
let_field_of!(obj, typ "type"); let_field_of!(obj, typ "format");
match typ { match typ {
"fbx" => Some(Self::Fbx {}), "fbx" => Some(Self::Fbx {}),
"gltf" => Some(Self::Gltf {}), "gltf" => Some(Self::Gltf {}),
@ -794,6 +794,45 @@ impl<'a> FromKclValue<'a> for crate::std::planes::StandardPlane {
} }
} }
impl<'a> FromKclValue<'a> for crate::executor::Plane {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
if let Some(plane) = arg.as_plane() {
return Some(plane.clone());
}
let obj = arg.as_object()?;
let_field_of!(obj, id);
let_field_of!(obj, value);
let_field_of!(obj, origin);
let_field_of!(obj, x_axis "xAxis");
let_field_of!(obj, y_axis "yAxis");
let_field_of!(obj, z_axis "zAxis");
let_field_of!(obj, meta "__meta");
Some(Self {
id,
value,
origin,
x_axis,
y_axis,
z_axis,
meta,
})
}
}
impl<'a> FromKclValue<'a> for crate::executor::PlaneType {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let plane_type = match arg.as_str()? {
"XY" | "xy" => Self::XY,
"XZ" | "xz" => Self::XZ,
"YZ" | "yz" => Self::YZ,
"Custom" => Self::Custom,
_ => return None,
};
Some(plane_type)
}
}
impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength { impl<'a> FromKclValue<'a> for kittycad_modeling_cmds::units::UnitLength {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let s = arg.as_str()?; let s = arg.as_str()?;
@ -1032,6 +1071,15 @@ impl<'a> FromKclValue<'a> for super::sketch::ArcData {
} }
} }
impl<'a> FromKclValue<'a> for super::sketch::ArcToData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?;
let_field_of!(obj, end);
let_field_of!(obj, interior);
Some(Self { end, interior })
}
}
impl<'a> FromKclValue<'a> for super::revolve::RevolveData { impl<'a> FromKclValue<'a> for super::revolve::RevolveData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let obj = arg.as_object()?; let obj = arg.as_object()?;
@ -1264,11 +1312,15 @@ impl<'a> FromKclValue<'a> for crate::executor::Solid {
impl<'a> FromKclValue<'a> for super::sketch::SketchData { impl<'a> FromKclValue<'a> for super::sketch::SketchData {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> { fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let case1 = super::sketch::PlaneData::from_kcl_val; // Order is critical since PlaneData is a subset of Plane.
let case2 = crate::executor::Solid::from_kcl_val; let case1 = crate::executor::Plane::from_kcl_val;
let case2 = super::sketch::PlaneData::from_kcl_val;
let case3 = crate::executor::Solid::from_kcl_val;
case1(arg) case1(arg)
.map(Box::new)
.map(Self::Plane) .map(Self::Plane)
.or_else(|| case2(arg).map(Box::new).map(Self::Solid)) .or_else(|| case2(arg).map(Self::PlaneOrientation))
.or_else(|| case3(arg).map(Box::new).map(Self::Solid))
} }
} }

View File

@ -107,14 +107,14 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ///
/// // This function adds an array of numbers. /// // This function adds an array of numbers.
/// // It uses the `reduce` function, to call the `add` function on every /// // It uses the `reduce` function, to call the `add` function on every
/// // element of the `array` parameter. The starting value is 0. /// // element of the `arr` parameter. The starting value is 0.
/// fn sum = (array) => { return reduce(array, 0, add) } /// fn sum = (arr) => { return reduce(arr, 0, add) }
/// ///
/// /* /// /*
/// The above is basically like this pseudo-code: /// The above is basically like this pseudo-code:
/// fn sum(array): /// fn sum(arr):
/// let sumSoFar = 0 /// let sumSoFar = 0
/// for i in array: /// for i in arr:
/// sumSoFar = add(sumSoFar, i) /// sumSoFar = add(sumSoFar, i)
/// return sumSoFar /// return sumSoFar
/// */ /// */
@ -127,8 +127,8 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// // This example works just like the previous example above, but it uses /// // This example works just like the previous example above, but it uses
/// // an anonymous `add` function as its parameter, instead of declaring a /// // an anonymous `add` function as its parameter, instead of declaring a
/// // named function outside. /// // named function outside.
/// array = [1, 2, 3] /// arr = [1, 2, 3]
/// sum = reduce(array, 0, (i, result_so_far) => { return i + result_so_far }) /// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
/// ///
/// // We use `assertEqual` to check that our `sum` function gives the /// // We use `assertEqual` to check that our `sum` function gives the
/// // expected result. It's good to check your work! /// // expected result. It's good to check your work!

View File

@ -42,7 +42,7 @@ const ZOO_COORD_SYSTEM: System = System {
/// Import format specifier /// Import format specifier
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)] #[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))] #[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
#[serde(tag = "type")] #[serde(tag = "format")]
pub enum ImportFormat { pub enum ImportFormat {
/// Autodesk Filmbox (FBX) format /// Autodesk Filmbox (FBX) format
#[serde(rename = "fbx")] #[serde(rename = "fbx")]
@ -152,7 +152,7 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run
/// const model = import("tests/inputs/cube.obj", {type: "obj", units: "m"}) /// const model = import("tests/inputs/cube.obj", {format: "obj", units: "m"})
/// ``` /// ```
/// ///
/// ```no_run /// ```no_run

View File

@ -4,7 +4,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
ast::types::{BodyItem, Expr, FunctionExpression, Node, Program}, ast::types::{FunctionExpression, Program},
docs::{StdLibFn, StdLibFnData}, docs::{StdLibFn, StdLibFnData},
}; };
@ -77,18 +77,3 @@ impl Serialize for Box<dyn KclStdLibFn> {
self.to_json().unwrap().serialize(serializer) self.to_json().unwrap().serialize(serializer)
} }
} }
/// Parse a KCL program. Expect it to have a single body item, which is a function.
/// Return the program and its single function.
/// Return None if those expectations aren't met.
pub fn extract_function(source: &str) -> Option<(Node<Program>, crate::ast::types::BoxNode<FunctionExpression>)> {
let src = crate::parser::top_level_parse(source).ok()?;
assert_eq!(src.body.len(), 1);
let BodyItem::ExpressionStatement(expr) = src.body.last()? else {
panic!("expected expression statement");
};
let Expr::FunctionExpression(function) = expr.expression.clone() else {
panic!("expected function expr");
};
Some((src, function))
}

View File

@ -48,8 +48,6 @@ pub type StdFn = fn(
Args, Args,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>; ) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>;
pub type FnMap = HashMap<String, StdFn>;
lazy_static! { lazy_static! {
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![ static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
Box::new(LegLen), Box::new(LegLen),
@ -91,6 +89,7 @@ lazy_static! {
Box::new(crate::std::sketch::ProfileStart), Box::new(crate::std::sketch::ProfileStart),
Box::new(crate::std::sketch::Close), Box::new(crate::std::sketch::Close),
Box::new(crate::std::sketch::Arc), Box::new(crate::std::sketch::Arc),
Box::new(crate::std::sketch::ArcTo),
Box::new(crate::std::sketch::TangentialArc), Box::new(crate::std::sketch::TangentialArc),
Box::new(crate::std::sketch::TangentialArcTo), Box::new(crate::std::sketch::TangentialArcTo),
Box::new(crate::std::sketch::TangentialArcToRelative), Box::new(crate::std::sketch::TangentialArcToRelative),

View File

@ -1,17 +1,20 @@
//! Standard library plane helpers. //! Standard library plane helpers.
use derive_docs::stdlib; use derive_docs::stdlib;
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Color, ModelingCmd};
use kittycad_modeling_cmds as kcmc;
use schemars::JsonSchema; use schemars::JsonSchema;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{ use crate::{
errors::KclError, errors::KclError,
executor::{ExecState, KclValue, Plane}, executor::{ExecState, KclValue, Plane, PlaneType},
std::{sketch::PlaneData, Args}, std::{sketch::PlaneData, Args},
}; };
/// One of the standard planes. /// One of the standard planes.
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, JsonSchema)] #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub enum StandardPlane { pub enum StandardPlane {
/// The XY plane. /// The XY plane.
@ -50,8 +53,8 @@ impl From<StandardPlane> for PlaneData {
/// Offset a plane by a distance along its normal. /// Offset a plane by a distance along its normal.
pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn offset_plane(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?; let (std_plane, offset): (StandardPlane, f64) = args.get_data_and_float()?;
let plane_data = inner_offset_plane(std_plane, offset, exec_state).await?; let plane = inner_offset_plane(std_plane, offset, exec_state).await?;
let plane = Plane::from_plane_data(plane_data, exec_state); make_offset_plane_in_engine(&plane, exec_state, &args).await?;
Ok(KclValue::Plane(Box::new(plane))) Ok(KclValue::Plane(Box::new(plane)))
} }
@ -144,11 +147,14 @@ async fn inner_offset_plane(
std_plane: StandardPlane, std_plane: StandardPlane,
offset: f64, offset: f64,
exec_state: &mut ExecState, exec_state: &mut ExecState,
) -> Result<PlaneData, KclError> { ) -> Result<Plane, KclError> {
// Convert to the plane type. // Convert to the plane type.
let plane_data: PlaneData = std_plane.into(); let plane_data: PlaneData = std_plane.into();
// Convert to a plane. // Convert to a plane.
let mut plane = Plane::from_plane_data(plane_data, exec_state); let mut plane = Plane::from_plane_data(plane_data, exec_state);
// Though offset planes are derived from standard planes, they are not
// standard planes themselves.
plane.value = PlaneType::Custom;
match std_plane { match std_plane {
StandardPlane::XY => { StandardPlane::XY => {
@ -171,10 +177,44 @@ async fn inner_offset_plane(
} }
} }
Ok(PlaneData::Plane { Ok(plane)
origin: Box::new(plane.origin), }
x_axis: Box::new(plane.x_axis),
y_axis: Box::new(plane.y_axis), // Engine-side effectful creation of an actual plane object.
z_axis: Box::new(plane.z_axis), // offset planes are shown by default, and hidden by default if they
}) // are used as a sketch plane. That hiding command is sent within inner_start_profile_at
async fn make_offset_plane_in_engine(plane: &Plane, exec_state: &mut ExecState, args: &Args) -> Result<(), KclError> {
// Create new default planes.
let default_size = 100.0;
let color = Color {
r: 0.6,
g: 0.6,
b: 0.6,
a: 0.3,
};
args.batch_modeling_cmd(
plane.id,
ModelingCmd::from(mcmd::MakePlane {
clobber: false,
origin: plane.origin.into(),
size: LengthUnit(default_size),
x_axis: plane.x_axis.into(),
y_axis: plane.y_axis.into(),
hide: Some(false),
}),
)
.await?;
// Set the color.
args.batch_modeling_cmd(
exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::PlaneSetColor {
color,
plane_id: plane.id,
}),
)
.await?;
Ok(())
} }

View File

@ -894,7 +894,7 @@ pub async fn start_sketch_at(exec_state: &mut ExecState, args: Args) -> Result<K
async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args: Args) -> Result<Sketch, KclError> { async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args: Args) -> Result<Sketch, KclError> {
// Let's assume it's the XY plane for now, this is just for backwards compatibility. // Let's assume it's the XY plane for now, this is just for backwards compatibility.
let xy_plane = PlaneData::XY; let xy_plane = PlaneData::XY;
let sketch_surface = inner_start_sketch_on(SketchData::Plane(xy_plane), None, exec_state, &args).await?; let sketch_surface = inner_start_sketch_on(SketchData::PlaneOrientation(xy_plane), None, exec_state, &args).await?;
let sketch = inner_start_profile_at(data, sketch_surface, None, exec_state, args).await?; let sketch = inner_start_profile_at(data, sketch_surface, None, exec_state, args).await?;
Ok(sketch) Ok(sketch)
} }
@ -905,11 +905,12 @@ async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args:
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase", untagged)] #[serde(rename_all = "camelCase", untagged)]
pub enum SketchData { pub enum SketchData {
Plane(PlaneData), PlaneOrientation(PlaneData),
Plane(Box<Plane>),
Solid(Box<Solid>), Solid(Box<Solid>),
} }
/// Data for a plane. /// Orientation data that can be used to construct a plane, not a plane in itself.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
@ -1069,10 +1070,11 @@ async fn inner_start_sketch_on(
args: &Args, args: &Args,
) -> Result<SketchSurface, KclError> { ) -> Result<SketchSurface, KclError> {
match data { match data {
SketchData::Plane(plane_data) => { SketchData::PlaneOrientation(plane_data) => {
let plane = start_sketch_on_plane(plane_data, exec_state, args).await?; let plane = make_sketch_plane_from_orientation(plane_data, exec_state, args).await?;
Ok(SketchSurface::Plane(plane)) Ok(SketchSurface::Plane(plane))
} }
SketchData::Plane(plane) => Ok(SketchSurface::Plane(plane)),
SketchData::Solid(solid) => { SketchData::Solid(solid) => {
let Some(tag) = tag else { let Some(tag) = tag else {
return Err(KclError::Type(KclErrorDetails { return Err(KclError::Type(KclErrorDetails {
@ -1106,7 +1108,7 @@ async fn start_sketch_on_face(
})) }))
} }
async fn start_sketch_on_plane( async fn make_sketch_plane_from_orientation(
data: PlaneData, data: PlaneData,
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: &Args, args: &Args,
@ -1122,10 +1124,10 @@ async fn start_sketch_on_plane(
plane.id = match data { plane.id = match data {
PlaneData::XY => default_planes.xy, PlaneData::XY => default_planes.xy,
PlaneData::XZ => default_planes.xz,
PlaneData::YZ => default_planes.yz,
PlaneData::NegXY => default_planes.neg_xy, PlaneData::NegXY => default_planes.neg_xy,
PlaneData::XZ => default_planes.xz,
PlaneData::NegXZ => default_planes.neg_xz, PlaneData::NegXZ => default_planes.neg_xz,
PlaneData::YZ => default_planes.yz,
PlaneData::NegYZ => default_planes.neg_yz, PlaneData::NegYZ => default_planes.neg_yz,
PlaneData::Plane { PlaneData::Plane {
origin, origin,
@ -1210,11 +1212,26 @@ pub(crate) async fn inner_start_profile_at(
exec_state: &mut ExecState, exec_state: &mut ExecState,
args: Args, args: Args,
) -> Result<Sketch, KclError> { ) -> Result<Sketch, KclError> {
if let SketchSurface::Face(face) = &sketch_surface { match &sketch_surface {
// Flush the batch for our fillets/chamfers if there are any. SketchSurface::Face(face) => {
// If we do not do these for sketch on face, things will fail with face does not exist. // Flush the batch for our fillets/chamfers if there are any.
args.flush_batch_for_solid_set(exec_state, face.solid.clone().into()) // If we do not do these for sketch on face, things will fail with face does not exist.
args.flush_batch_for_solid_set(exec_state, face.solid.clone().into())
.await?;
}
SketchSurface::Plane(plane) if !plane.is_standard() => {
// Hide whatever plane we are sketching on.
// This is especially helpful for offset planes, which would be visible otherwise.
args.batch_end_cmd(
exec_state.id_generator.next_uuid(),
ModelingCmd::from(mcmd::ObjectVisible {
object_id: plane.id,
hidden: true,
}),
)
.await?; .await?;
}
_ => {}
} }
// Enter sketch mode on the surface. // Enter sketch mode on the surface.
@ -1469,6 +1486,17 @@ pub enum ArcData {
}, },
} }
/// Data to draw a three point arc (arcTo).
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)]
#[serde(rename_all = "camelCase")]
pub struct ArcToData {
/// End point of the arc. A point in 3D space
pub end: [f64; 2],
/// Interior point of the arc. A point in 3D space
pub interior: [f64; 2],
}
/// Draw an arc. /// Draw an arc.
pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?; let (data, sketch, tag): (ArcData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
@ -1499,7 +1527,7 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
/// radius: 16 /// radius: 16
/// }, %) /// }, %)
/// |> close(%) /// |> close(%)
// const example = extrude(10, exampleSketch) /// const example = extrude(10, exampleSketch)
/// ``` /// ```
#[stdlib { #[stdlib {
name = "arc", name = "arc",
@ -1578,6 +1606,104 @@ pub(crate) async fn inner_arc(
Ok(new_sketch) Ok(new_sketch)
} }
/// Draw a three point arc.
pub async fn arc_to(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (data, sketch, tag): (ArcToData, Sketch, Option<TagNode>) = args.get_data_and_sketch_and_tag()?;
let new_sketch = inner_arc_to(data, sketch, tag, exec_state, args).await?;
Ok(KclValue::Sketch {
value: Box::new(new_sketch),
})
}
/// Draw a 3 point arc.
///
/// The arc is constructed such that the start point is the current position of the sketch and two more points defined as the end and interior point.
/// The interior point is placed between the start point and end point. The radius of the arc will be controlled by how far the interior point is placed from
/// the start and end.
///
/// ```no_run
/// const exampleSketch = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %)
/// |> arcTo({
/// end: [10,0],
/// interior: [5,5]
/// }, %)
/// |> close(%)
/// const example = extrude(10, exampleSketch)
/// ```
#[stdlib {
name = "arcTo",
}]
pub(crate) async fn inner_arc_to(
data: ArcToData,
sketch: Sketch,
tag: Option<TagNode>,
exec_state: &mut ExecState,
args: Args,
) -> Result<Sketch, KclError> {
let from: Point2d = sketch.current_pen_position()?;
let id = exec_state.id_generator.next_uuid();
// The start point is taken from the path you are extending.
args.batch_modeling_cmd(
id,
ModelingCmd::from(mcmd::ExtendPath {
path: sketch.id.into(),
segment: PathSegment::ArcTo {
end: kcmc::shared::Point3d {
x: LengthUnit(data.end[0]),
y: LengthUnit(data.end[1]),
z: LengthUnit(0.0),
},
interior: kcmc::shared::Point3d {
x: LengthUnit(data.interior[0]),
y: LengthUnit(data.interior[1]),
z: LengthUnit(0.0),
},
relative: false,
},
}),
)
.await?;
let start = [from.x, from.y];
let interior = [data.interior[0], data.interior[1]];
let end = [data.end[0], data.end[1]];
// compute the center of the circle since we do not have the value returned from the engine
let center = calculate_circle_center(start, interior, end);
// compute the radius since we do not have the value returned from the engine
// Pick any of the 3 points since they all lie along the circle
let sum_of_square_differences =
(center[0] - start[0] * center[0] - start[0]) + (center[1] - start[1] * center[1] - start[1]);
let radius = sum_of_square_differences.sqrt();
let current_path = Path::Arc {
base: BasePath {
from: from.into(),
to: data.end,
tag: tag.clone(),
geo_meta: GeoMeta {
id,
metadata: args.source_range.into(),
},
},
center,
radius,
};
let mut new_sketch = sketch.clone();
if let Some(tag) = &tag {
new_sketch.add_tag(tag, &current_path);
}
new_sketch.paths.push(current_path);
Ok(new_sketch)
}
/// Data to draw a tangential arc. /// Data to draw a tangential arc.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
#[ts(export)] #[ts(export)]
@ -1897,6 +2023,42 @@ async fn inner_tangential_arc_to_relative(
Ok(new_sketch) Ok(new_sketch)
} }
// Calculate the center of 3 points
// To calculate the center of the 3 point circle 2 perpendicular lines are created
// These perpendicular lines will intersect at the center of the circle.
fn calculate_circle_center(p1: [f64; 2], p2: [f64; 2], p3: [f64; 2]) -> [f64; 2] {
// y2 - y1
let y_2_1 = p2[1] - p1[1];
// y3 - y2
let y_3_2 = p3[1] - p2[1];
// x2 - x1
let x_2_1 = p2[0] - p1[0];
// x3 - x2
let x_3_2 = p3[0] - p2[0];
// Slope of two perpendicular lines
let slope_a = y_2_1 / x_2_1;
let slope_b = y_3_2 / x_3_2;
// Values for line intersection
// y1 - y3
let y_1_3 = p1[1] - p3[1];
// x1 + x2
let x_1_2 = p1[0] + p2[0];
// x2 + x3
let x_2_3 = p2[0] + p3[0];
// y1 + y2
let y_1_2 = p1[1] + p2[1];
// Solve for the intersection of these two lines
let numerator = (slope_a * slope_b * y_1_3) + (slope_b * x_1_2) - (slope_a * x_2_3);
let x = numerator / (2.0 * (slope_b - slope_a));
let y = ((-1.0 / slope_a) * (x - (x_1_2 / 2.0))) + (y_1_2 / 2.0);
[x, y]
}
/// Data to draw a bezier curve. /// Data to draw a bezier curve.
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
#[ts(export)] #[ts(export)]
@ -2073,7 +2235,7 @@ mod tests {
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use crate::{executor::TagIdentifier, std::sketch::PlaneData}; use crate::{executor::TagIdentifier, std::sketch::calculate_circle_center, std::sketch::PlaneData};
#[test] #[test]
fn test_deserialize_plane_data() { fn test_deserialize_plane_data() {
@ -2144,4 +2306,11 @@ mod tests {
crate::std::sketch::FaceTag::StartOrEnd(crate::std::sketch::StartOrEnd::Start) crate::std::sketch::FaceTag::StartOrEnd(crate::std::sketch::StartOrEnd::Start)
); );
} }
#[test]
fn test_circle_center() {
let actual = calculate_circle_center([0.0, 0.0], [5.0, 5.0], [10.0, 0.0]);
assert_eq!(actual[0], 5.0);
assert_eq!(actual[1], 0.0);
}
} }

View File

@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
#[ts(export)] #[ts(export)]
pub struct Uint(f64); pub struct Uint(f64);
#[allow(dead_code)]
impl Uint { impl Uint {
pub fn new(value: f64) -> Self { pub fn new(value: f64) -> Self {
if value < 0.0 { if value < 0.0 {

View File

@ -53,20 +53,6 @@ pub fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
Angle::default() Angle::default()
} }
pub fn clockwise_sign(points: &[Point2d]) -> i32 {
let mut sum = 0.0;
for i in 0..points.len() {
let current_point = points[i];
let next_point = points[(i + 1) % points.len()];
sum += (next_point.x - current_point.x) * (next_point.y + current_point.y);
}
if sum >= 0.0 {
1
} else {
-1
}
}
pub fn normalize_rad(angle: f64) -> f64 { pub fn normalize_rad(angle: f64) -> f64 {
let draft = angle % (2.0 * PI); let draft = angle % (2.0 * PI);
if draft < 0.0 { if draft < 0.0 {
@ -76,32 +62,6 @@ pub fn normalize_rad(angle: f64) -> f64 {
} }
} }
/// Calculates the distance between two points.
///
/// # Examples
///
/// ```
/// use kcl_lib::executor::Point2d;
///
/// assert_eq!(
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d { x: 0.0, y: 5.0 }),
/// 5.0
/// );
/// assert_eq!(
/// kcl_lib::std::utils::distance_between_points(Point2d::ZERO, Point2d { x: 3.0, y: 4.0 }),
/// 5.0
/// );
/// ```
#[allow(dead_code)]
pub fn distance_between_points(point_a: Point2d, point_b: Point2d) -> f64 {
let x1 = point_a.x;
let y1 = point_a.y;
let x2 = point_b.x;
let y2 = point_b.y;
((y2 - y1).powi(2) + (x2 - x1).powi(2)).sqrt()
}
pub fn calculate_intersection_of_two_lines(line1: &[Point2d; 2], line2_angle: f64, line2_point: Point2d) -> Point2d { pub fn calculate_intersection_of_two_lines(line1: &[Point2d; 2], line2_angle: f64, line2_point: Point2d) -> Point2d {
let line2_point_b = Point2d { let line2_point_b = Point2d {
x: line2_point.x + f64::cos(line2_angle.to_radians()) * 10.0, x: line2_point.x + f64::cos(line2_angle.to_radians()) * 10.0,
@ -563,6 +523,7 @@ pub struct TangentialArcInfoInput {
} }
/// Structure to hold the output data from calculating tangential arc information. /// Structure to hold the output data from calculating tangential arc information.
#[allow(dead_code)]
pub struct TangentialArcInfoOutput { pub struct TangentialArcInfoOutput {
/// The center point of the arc. /// The center point of the arc.
pub center: Coords2d, pub center: Coords2d,

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