Compare commits
14 Commits
exports-na
...
v0.27.0
Author | SHA1 | Date | |
---|---|---|---|
30afa65ccf | |||
a2f9e70d18 | |||
986675fe89 | |||
d8ce5ad8bd | |||
1a9926be8a | |||
54b5774f9e | |||
66bbbf81e2 | |||
652519aeae | |||
f826afb32d | |||
f71fafdece | |||
16b7544d69 | |||
34f019305b | |||
79e06b3a00 | |||
24bc4fcd8c |
File diff suppressed because one or more lines are too long
41
docs/kcl/arcTo.md
Normal file
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
@ -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
@ -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 }, %)
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
4675
docs/kcl/std.json
4675
docs/kcl/std.json
File diff suppressed because it is too large
Load Diff
22
docs/kcl/types/ArcToData.md
Normal file
22
docs/kcl/types/ArcToData.md
Normal 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 |
|
||||||
|
|
||||||
|
|
@ -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 |
|
||||||
|
|
||||||
|
@ -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
27
docs/kcl/types/Plane.md
Normal 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 plane’s X axis be? | No |
|
||||||
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||||
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
@ -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.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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()
|
||||||
})
|
})
|
||||||
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -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()
|
||||||
|
@ -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: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -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 |
@ -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": {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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(),
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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.')
|
||||||
|
@ -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', () => {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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 =
|
||||||
|
@ -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
|
||||||
|
@ -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(),
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
||||||
| {
|
| {
|
||||||
|
15
src/wasm-lib/Cargo.lock
generated
15
src/wasm-lib/Cargo.lock
generated
@ -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"
|
||||||
|
@ -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",
|
||||||
]
|
]
|
||||||
|
@ -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)]
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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>>()
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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"
|
|
@ -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()
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
@ -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),
|
||||||
};
|
};
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
|
@ -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"] }
|
||||||
|
@ -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");
|
||||||
|
@ -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,
|
||||||
|
@ -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");
|
||||||
|
@ -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) {
|
||||||
|
@ -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(),
|
||||||
);
|
);
|
||||||
|
@ -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");
|
||||||
|
@ -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) {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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>| {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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};
|
||||||
|
@ -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};
|
||||||
|
@ -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 {
|
||||||
|
@ -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(¶ms, &ast).await.is_err() {
|
if self.execute(¶ms, &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());
|
||||||
|
@ -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")]
|
||||||
|
@ -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();
|
||||||
|
@ -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() {
|
||||||
|
@ -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
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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!
|
||||||
|
@ -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
|
||||||
|
@ -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))
|
|
||||||
}
|
|
||||||
|
@ -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),
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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, ¤t_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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
Reference in New Issue
Block a user