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)
|
||||
* [`angledLineToY`](kcl/angledLineToY)
|
||||
* [`arc`](kcl/arc)
|
||||
* [`arcTo`](kcl/arcTo)
|
||||
* [`asin`](kcl/asin)
|
||||
* [`assert`](kcl/assert)
|
||||
* [`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
|
||||
fn drawCircle = (id) => {
|
||||
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.
|
||||
@ -47,7 +47,7 @@ r = 10 // radius
|
||||
// Call `map`, using an anonymous function instead of a named one.
|
||||
circles = map([1..3], (id) => {
|
||||
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.
|
||||
|
||||
```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
|
||||
|
||||
[`PlaneData`](/docs/kcl/types/PlaneData) - Data for a plane.
|
||||
[`Plane`](/docs/kcl/types/Plane) - A plane.
|
||||
|
||||
|
||||
### Examples
|
||||
|
@ -96,24 +96,24 @@ fn cube = (length, center) => {
|
||||
p3 = [l + x, -l + y]
|
||||
|
||||
return startSketchAt(p0)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
|> lineTo(p0, %)
|
||||
|> close(%)
|
||||
|> extrude(length, %)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
|> lineTo(p0, %)
|
||||
|> close(%)
|
||||
|> extrude(length, %)
|
||||
}
|
||||
|
||||
width = 20
|
||||
fn transform = (i) => {
|
||||
return {
|
||||
// Move down each time.
|
||||
translate: [0, 0, -i * width],
|
||||
// Make the cube longer, wider and flatter each time.
|
||||
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
|
||||
// Turn by 15 degrees each time.
|
||||
rotation: { angle: 15 * i, origin: "local" }
|
||||
}
|
||||
// Move down each time.
|
||||
translate: [0, 0, -i * width],
|
||||
// Make the cube longer, wider and flatter each time.
|
||||
scale: [pow(1.1, i), pow(1.1, i), pow(0.9, i)],
|
||||
// Turn by 15 degrees each time.
|
||||
rotation: { angle: 15 * i, origin: "local" }
|
||||
}
|
||||
}
|
||||
|
||||
myCubes = cube(width, [100, 0])
|
||||
@ -133,25 +133,25 @@ fn cube = (length, center) => {
|
||||
p3 = [l + x, -l + y]
|
||||
|
||||
return startSketchAt(p0)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
|> lineTo(p0, %)
|
||||
|> close(%)
|
||||
|> extrude(length, %)
|
||||
|> lineTo(p1, %)
|
||||
|> lineTo(p2, %)
|
||||
|> lineTo(p3, %)
|
||||
|> lineTo(p0, %)
|
||||
|> close(%)
|
||||
|> extrude(length, %)
|
||||
}
|
||||
|
||||
width = 20
|
||||
fn transform = (i) => {
|
||||
return {
|
||||
translate: [0, 0, -i * width],
|
||||
rotation: {
|
||||
angle: 90 * i,
|
||||
// Rotate around the overall scene's origin.
|
||||
origin: "global"
|
||||
translate: [0, 0, -i * width],
|
||||
rotation: {
|
||||
angle: 90 * i,
|
||||
// Rotate around the overall scene's origin.
|
||||
origin: "global"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
myCubes = cube(width, [100, 100])
|
||||
|> patternTransform(4, transform, %)
|
||||
```
|
||||
@ -168,16 +168,16 @@ t = 0.005 // taper factor [0-1)
|
||||
fn transform = (replicaId) => {
|
||||
scale = r * abs(1 - (t * replicaId)) * (5 + cos(replicaId / 8))
|
||||
return {
|
||||
translate: [0, 0, replicaId * 10],
|
||||
scale: [scale, scale, 0]
|
||||
}
|
||||
translate: [0, 0, replicaId * 10],
|
||||
scale: [scale, scale, 0]
|
||||
}
|
||||
}
|
||||
// Each layer is just a pretty thin cylinder.
|
||||
fn layer = () => {
|
||||
return startSketchOn("XY")
|
||||
// or some other plane idk
|
||||
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|
||||
|> extrude(h, %)
|
||||
// or some other plane idk
|
||||
|> circle({ center: [0, 0], radius: 1 }, %, $tag1)
|
||||
|> extrude(h, %)
|
||||
}
|
||||
// The vase is 100 layers tall.
|
||||
// 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.
|
||||
// It uses the `reduce` function, to call the `add` function on every
|
||||
// element of the `array` parameter. The starting value is 0.
|
||||
fn sum = (array) => {
|
||||
return reduce(array, 0, add)
|
||||
// element of the `arr` parameter. The starting value is 0.
|
||||
fn sum = (arr) => {
|
||||
return reduce(arr, 0, add)
|
||||
}
|
||||
|
||||
/* The above is basically like this pseudo-code:
|
||||
fn sum(array):
|
||||
fn sum(arr):
|
||||
let sumSoFar = 0
|
||||
for i in array:
|
||||
for i in arr:
|
||||
sumSoFar = add(sumSoFar, i)
|
||||
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
|
||||
// an anonymous `add` function as its parameter, instead of declaring a
|
||||
// named function outside.
|
||||
array = [1, 2, 3]
|
||||
sum = reduce(array, 0, (i, result_so_far) => {
|
||||
arr = [1, 2, 3]
|
||||
sum = reduce(arr, 0, (i, result_so_far) => {
|
||||
return i + result_so_far
|
||||
})
|
||||
|
||||
@ -89,7 +89,7 @@ fn decagon = (radius) => {
|
||||
x = cos(stepAngle * i) * radius
|
||||
y = sin(stepAngle * i) * radius
|
||||
return lineTo([x, y], partialDecagon)
|
||||
})
|
||||
})
|
||||
|
||||
return fullDecagon
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ cube = startSketchAt([0, 0])
|
||||
|
||||
fn cylinder = (radius, tag) => {
|
||||
return startSketchAt([0, 0])
|
||||
|> circle({ radius: radius, center: segEnd(tag) }, %)
|
||||
|> extrude(radius, %)
|
||||
|> circle({ radius: radius, center: segEnd(tag) }, %)
|
||||
|> extrude(radius, %)
|
||||
}
|
||||
|
||||
cylinder(1, line1)
|
||||
|
@ -38,11 +38,11 @@ cube = startSketchAt([0, 0])
|
||||
|
||||
fn cylinder = (radius, tag) => {
|
||||
return startSketchAt([0, 0])
|
||||
|> circle({
|
||||
radius: radius,
|
||||
center: segStart(tag)
|
||||
}, %)
|
||||
|> extrude(radius, %)
|
||||
|> circle({
|
||||
radius: radius,
|
||||
center: segStart(tag)
|
||||
}, %)
|
||||
|> extrude(radius, %)
|
||||
}
|
||||
|
||||
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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `gltf`| | No |
|
||||
| `format` |enum: `gltf`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -56,7 +56,7 @@ Wavefront OBJ format.
|
||||
|
||||
| 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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `sldprt`| | No |
|
||||
| `format` |enum: `sldprt`| | No |
|
||||
|
||||
|
||||
----
|
||||
@ -108,7 +108,7 @@ ISO 10303-21 (STEP) format.
|
||||
|
||||
| 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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `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 |
|
||||
| `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 |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Plane`| | No |
|
||||
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
||||
| `id` |`string`| The id of the plane. | No |
|
||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | 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"
|
||||
excerpt: "Data for a plane."
|
||||
excerpt: "Orientation data that can be used to construct a plane, not a plane in itself."
|
||||
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.
|
||||
|
||||
|
@ -62,6 +62,8 @@ test(
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
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
|
||||
await exportButton.click()
|
||||
@ -96,7 +98,7 @@ test(
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile('output.gltf')
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -106,8 +108,8 @@ test(
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
})
|
||||
|
||||
@ -138,6 +140,8 @@ test(
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
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
|
||||
await exportButton.click()
|
||||
@ -171,7 +175,7 @@ test(
|
||||
.poll(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile('output.gltf')
|
||||
const outputGltf = await fsp.readFile(exportFileName)
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -181,8 +185,8 @@ test(
|
||||
)
|
||||
.toBeGreaterThan(100_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
// clean up exported file
|
||||
await fsp.rm(exportFileName)
|
||||
})
|
||||
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(
|
||||
async () => {
|
||||
try {
|
||||
const outputGltf = await fsp.readFile('output.gltf')
|
||||
const outputGltf = await fsp.readFile('main.gltf')
|
||||
return outputGltf.byteLength
|
||||
} catch (e) {
|
||||
return 0
|
||||
@ -257,8 +257,8 @@ test.describe('Can export from electron app', () => {
|
||||
)
|
||||
.toBeGreaterThan(300_000)
|
||||
|
||||
// clean up output.gltf
|
||||
await fsp.rm('output.gltf')
|
||||
// clean up exported file
|
||||
await fsp.rm('main.gltf')
|
||||
})
|
||||
|
||||
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) =>
|
||||
t.includes('.gltf')
|
||||
)[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}`
|
||||
}
|
||||
|
||||
|
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",
|
||||
"version": "0.26.5",
|
||||
"version": "0.27.0",
|
||||
"private": true,
|
||||
"productName": "Zoo Modeling App",
|
||||
"author": {
|
||||
|
@ -47,6 +47,7 @@ import {
|
||||
VariableDeclaration,
|
||||
VariableDeclarator,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
} from 'lang/wasm'
|
||||
import {
|
||||
engineCommandManager,
|
||||
@ -92,7 +93,7 @@ import {
|
||||
updateCenterRectangleSketch,
|
||||
} from 'lib/rectangleTool'
|
||||
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 { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||
@ -1178,6 +1179,11 @@ export class SceneEntities {
|
||||
await kclManager.executeAstMock(_ast)
|
||||
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({
|
||||
ast: _ast,
|
||||
useFakeExecutor: true,
|
||||
@ -1692,10 +1698,13 @@ export class SceneEntities {
|
||||
this.sceneProgramMemory = programMemory
|
||||
|
||||
const maybeSketch = programMemory.get(variableDeclarationName)
|
||||
let sketch = undefined
|
||||
const sg = sketchFromKclValue(maybeSketch, variableDeclarationName)
|
||||
if (!err(sg)) {
|
||||
sketch = sg
|
||||
let sketch: Sketch | undefined
|
||||
const sk = sketchFromKclValueOptional(
|
||||
maybeSketch,
|
||||
variableDeclarationName
|
||||
)
|
||||
if (!(sk instanceof Reason)) {
|
||||
sketch = sk
|
||||
} else if ((maybeSketch as Solid).sketch) {
|
||||
sketch = (maybeSketch as Solid).sketch
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ import {
|
||||
import {
|
||||
moveValueIntoNewVariablePath,
|
||||
sketchOnExtrudedFace,
|
||||
sketchOnOffsetPlane,
|
||||
startSketchOnDefault,
|
||||
} from 'lang/modifyAst'
|
||||
import { Program, parse, recast } from 'lang/wasm'
|
||||
@ -483,7 +484,7 @@ export const ModelingMachineProvider = ({
|
||||
engineCommandManager.exportInfo = {
|
||||
intent: ExportIntent.Save,
|
||||
// This never gets used its only for make.
|
||||
name: '',
|
||||
name: file?.name?.replace('.kcl', `.${event.data.type}`) || '',
|
||||
}
|
||||
|
||||
const format = {
|
||||
@ -636,13 +637,16 @@ export const ModelingMachineProvider = ({
|
||||
),
|
||||
'animate-to-face': fromPromise(async ({ input }) => {
|
||||
if (!input) return undefined
|
||||
if (input.type === 'extrudeFace') {
|
||||
const sketched = sketchOnExtrudedFace(
|
||||
kclManager.ast,
|
||||
input.sketchPathToNode,
|
||||
input.extrudePathToNode,
|
||||
input.faceInfo
|
||||
)
|
||||
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
||||
const sketched =
|
||||
input.type === 'extrudeFace'
|
||||
? sketchOnExtrudedFace(
|
||||
kclManager.ast,
|
||||
input.sketchPathToNode,
|
||||
input.extrudePathToNode,
|
||||
input.faceInfo
|
||||
)
|
||||
: sketchOnOffsetPlane(kclManager.ast, input.pathToNode)
|
||||
if (err(sketched)) {
|
||||
const sketchedError = new Error(
|
||||
'Incompatible face, please try another'
|
||||
@ -654,10 +658,9 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
await kclManager.executeAstMock(modifiedAst)
|
||||
|
||||
await letEngineAnimateAndSyncCamAfter(
|
||||
engineCommandManager,
|
||||
input.faceId
|
||||
)
|
||||
const id =
|
||||
input.type === 'extrudeFace' ? input.faceId : input.planeId
|
||||
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||
return {
|
||||
sketchPathToNode: pathToNewSketchNode,
|
||||
|
@ -43,6 +43,7 @@ import {
|
||||
completionKeymap,
|
||||
} from '@codemirror/autocomplete'
|
||||
import CodeEditor from './CodeEditor'
|
||||
import { codeManagerHistoryCompartment } from 'lang/codeManager'
|
||||
|
||||
export const editorShortcutMeta = {
|
||||
formatCode: {
|
||||
@ -89,7 +90,7 @@ export const KclEditorPane = () => {
|
||||
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
|
||||
}),
|
||||
lineHighlightField,
|
||||
history(),
|
||||
codeManagerHistoryCompartment.of(history()),
|
||||
closeBrackets(),
|
||||
codeFolding(),
|
||||
keymap.of([
|
||||
@ -121,7 +122,6 @@ export const KclEditorPane = () => {
|
||||
lineNumbers(),
|
||||
highlightActiveLineGutter(),
|
||||
highlightSpecialChars(),
|
||||
history(),
|
||||
foldGutter(),
|
||||
EditorState.allowMultipleSelections.of(true),
|
||||
indentOnInput(),
|
||||
|
@ -5,12 +5,12 @@ import {
|
||||
ProgramMemory,
|
||||
Path,
|
||||
ExtrudeSurface,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
} from 'lang/wasm'
|
||||
import { useKclContext } from 'lang/KclProvider'
|
||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||
import { ActionButton } from 'components/ActionButton'
|
||||
import { err, trap } from 'lib/trap'
|
||||
import { Reason, trap } from 'lib/trap'
|
||||
import Tooltip from 'components/Tooltip'
|
||||
import { useModelingContext } from 'hooks/useModelingContext'
|
||||
|
||||
@ -93,13 +93,13 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
||||
// @ts-ignore
|
||||
val.type !== 'Function'
|
||||
) {
|
||||
const sg = sketchFromKclValue(val, key)
|
||||
const sk = sketchFromKclValueOptional(val, key)
|
||||
if (val.type === 'Solid') {
|
||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||
return rest
|
||||
})
|
||||
} else if (!err(sg)) {
|
||||
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||
} else if (!(sk instanceof Reason)) {
|
||||
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||
return rest
|
||||
})
|
||||
} else {
|
||||
|
@ -88,6 +88,10 @@ export function useEngineConnectionSubscriptions() {
|
||||
? [codeRef.range]
|
||||
: [codeRef.range, consumedCodeRef.range]
|
||||
)
|
||||
} else if (artifact?.type === 'plane') {
|
||||
const codeRef = artifact.codeRef
|
||||
if (err(codeRef)) return
|
||||
editorManager.setHighlightRange([codeRef.range])
|
||||
} else {
|
||||
editorManager.setHighlightRange([[0, 0]])
|
||||
}
|
||||
@ -186,8 +190,42 @@ export function useEngineConnectionSubscriptions() {
|
||||
})
|
||||
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 artifact = engineCommandManager.artifactGraph.get(faceId)
|
||||
const extrusion = getSweepFromSuspectedSweepSurface(
|
||||
faceId,
|
||||
engineCommandManager.artifactGraph
|
||||
|
@ -6,14 +6,17 @@ import { isDesktop } from 'lib/isDesktop'
|
||||
import toast from 'react-hot-toast'
|
||||
import { editorManager } from 'lib/singletons'
|
||||
import { Annotation, Transaction } from '@codemirror/state'
|
||||
import { KeyBinding } from '@codemirror/view'
|
||||
import { EditorView, KeyBinding } from '@codemirror/view'
|
||||
import { recast, Program } from 'lang/wasm'
|
||||
import { err } from 'lib/trap'
|
||||
import { Compartment } from '@codemirror/state'
|
||||
import { history } from '@codemirror/commands'
|
||||
|
||||
const PERSIST_CODE_KEY = 'persistCode'
|
||||
|
||||
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
|
||||
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
|
||||
export const codeManagerHistoryCompartment = new Compartment()
|
||||
|
||||
export default class CodeManager {
|
||||
private _code: string = bracket
|
||||
@ -90,9 +93,12 @@ export default class CodeManager {
|
||||
/**
|
||||
* Update the code in the editor.
|
||||
*/
|
||||
updateCodeEditor(code: string): void {
|
||||
updateCodeEditor(code: string, clearHistory?: boolean): void {
|
||||
this.code = code
|
||||
if (editorManager.editorView) {
|
||||
if (clearHistory) {
|
||||
clearCodeMirrorHistory(editorManager.editorView)
|
||||
}
|
||||
editorManager.editorView.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
@ -101,7 +107,7 @@ export default class CodeManager {
|
||||
},
|
||||
annotations: [
|
||||
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.
|
||||
*/
|
||||
updateCodeStateEditor(code: string): void {
|
||||
updateCodeStateEditor(code: string, clearHistory?: boolean): void {
|
||||
if (this._code !== code) {
|
||||
this.code = 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
|
||||
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,
|
||||
SourceRange,
|
||||
sketchFromKclValue,
|
||||
isPathToNodeNumber,
|
||||
} from './wasm'
|
||||
import {
|
||||
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 =>
|
||||
splitPathAtLastIndex(pathToNode).index
|
||||
|
||||
|
@ -77,22 +77,30 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
||||
code.indexOf(expectedExtrudeSnippet),
|
||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
||||
]
|
||||
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
const expedtedExtrudeNodeResult = getNodeFromPath<VariableDeclarator>(
|
||||
ast,
|
||||
expedtedExtrudePath
|
||||
)
|
||||
if (err(expedtedExtrudeNodeResult)) {
|
||||
return expedtedExtrudeNodeResult
|
||||
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||
const expectedExtrudeNodeResult = getNodeFromPath<
|
||||
VariableDeclarator | CallExpression
|
||||
>(ast, expectedExtrudePath)
|
||||
if (err(expectedExtrudeNodeResult)) {
|
||||
return expectedExtrudeNodeResult
|
||||
}
|
||||
const expectedExtrudeNode = expedtedExtrudeNodeResult.node
|
||||
const init = expectedExtrudeNode.init
|
||||
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
|
||||
return new Error(
|
||||
'Expected extrude expression is not a CallExpression or PipeExpression'
|
||||
)
|
||||
const expectedExtrudeNode = expectedExtrudeNodeResult.node
|
||||
|
||||
// check whether extrude is in the sketch pipe
|
||||
const extrudeInSketchPipe = expectedExtrudeNode.type === 'CallExpression'
|
||||
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
|
||||
@ -160,6 +168,23 @@ extrude001 = extrude(-15, sketch001)`
|
||||
expectedExtrudeSnippet
|
||||
)
|
||||
}, 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 () => {
|
||||
const code = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-30, 30], %)
|
||||
@ -296,6 +321,34 @@ extrude001 = extrude(-15, sketch001)`
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> close(%)
|
||||
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] }, %)`
|
||||
|
||||
await runModifyAstCloneWithFilletAndTag(
|
||||
|
@ -146,7 +146,7 @@ export function modifyAstCloneWithFilletAndTag(
|
||||
|
||||
// Modify the extrude expression to include this fillet expression
|
||||
// CallExpression - no fillet
|
||||
// PipeExpression - fillet exists
|
||||
// PipeExpression - fillet exists or extrude in sketch pipe
|
||||
|
||||
let pathToFilletNode: PathToNode = []
|
||||
|
||||
@ -167,15 +167,7 @@ export function modifyAstCloneWithFilletAndTag(
|
||||
)
|
||||
pathToFilletNodes.push(pathToFilletNode)
|
||||
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
||||
// 2. case when fillet exists
|
||||
|
||||
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.')
|
||||
}
|
||||
// 2. case when fillet exists or extrude in sketch pipe
|
||||
|
||||
// mutate the extrude node with the new fillet call
|
||||
extrudeDeclarator.init.body.push(filletCall)
|
||||
@ -317,14 +309,14 @@ function locateExtrudeDeclarator(
|
||||
node: Program,
|
||||
pathToExtrudeNode: PathToNode
|
||||
): { extrudeDeclarator: VariableDeclarator } | Error {
|
||||
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
||||
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
|
||||
node,
|
||||
pathToExtrudeNode,
|
||||
'VariableDeclaration'
|
||||
)
|
||||
if (err(extrudeChunk)) return extrudeChunk
|
||||
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
|
||||
|
||||
const { node: extrudeVarDecl } = extrudeChunk
|
||||
const { node: extrudeVarDecl } = nodeOfExtrudeCall
|
||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||
if (!extrudeDeclarator) {
|
||||
return new Error('Extrude Declarator not found.')
|
||||
|
@ -530,14 +530,25 @@ describe('Testing hasSketchPipeBeenExtruded', () => {
|
||||
|> line([-17.67, 0.85], %)
|
||||
|> close(%)
|
||||
extrude001 = extrude(10, sketch001)
|
||||
sketch002 = startSketchOn(extrude001, $seg01)
|
||||
sketch002 = startSketchOn(extrude001, seg01)
|
||||
|> startProfileAt([-12.94, 6.6], %)
|
||||
|> line([2.45, -0.2], %)
|
||||
|> line([-2, -1.25], %)
|
||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||
|> 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)
|
||||
if (err(ast)) throw ast
|
||||
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
|
||||
@ -552,7 +563,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
||||
)
|
||||
expect(extruded).toBeTruthy()
|
||||
})
|
||||
it('find sketch002 NOT pipe to be extruded', async () => {
|
||||
it('identifies sketch002 pipe as not extruded', async () => {
|
||||
const ast = parse(exampleCode)
|
||||
if (err(ast)) throw ast
|
||||
const lineOfInterest = `line([2.45, -0.2], %)`
|
||||
@ -567,6 +578,21 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
||||
)
|
||||
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', () => {
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
ProgramMemory,
|
||||
ReturnStatement,
|
||||
sketchFromKclValue,
|
||||
sketchFromKclValueOptional,
|
||||
SourceRange,
|
||||
SyntaxType,
|
||||
VariableDeclaration,
|
||||
@ -27,7 +28,7 @@ import {
|
||||
getConstraintLevelFromSourceRange,
|
||||
getConstraintType,
|
||||
} from './std/sketchcombos'
|
||||
import { err } from 'lib/trap'
|
||||
import { err, Reason } from 'lib/trap'
|
||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||
|
||||
@ -846,7 +847,8 @@ export function hasExtrudeSketch({
|
||||
const varName = varDec.declarations[0].id.name
|
||||
const varValue = programMemory?.get(varName)
|
||||
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) {
|
||||
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
|
||||
const { node: pipeExpression } = _node
|
||||
if (pipeExpression.type !== 'PipeExpression') return false
|
||||
@ -940,19 +946,33 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||
const varDec = _varDec.node
|
||||
if (varDec.type !== 'VariableDeclarator') return false
|
||||
let extruded = false
|
||||
traverse(ast as any, {
|
||||
// option 1: extrude or revolve is called in the sketch pipe
|
||||
traverse(pipeExpression, {
|
||||
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
|
||||
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
|
||||
) {
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -98,12 +98,22 @@ sketch004 = startSketchOn(extrude003, seg02)
|
||||
|> close(%)
|
||||
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
|
||||
const codeToWriteCacheFor = {
|
||||
exampleCode1,
|
||||
sketchOnFaceOnFaceEtc,
|
||||
exampleCodeNo3D,
|
||||
exampleCodeOffsetPlanes,
|
||||
} as const
|
||||
|
||||
type CodeKey = keyof typeof codeToWriteCacheFor
|
||||
@ -165,6 +175,52 @@ afterAll(() => {
|
||||
})
|
||||
|
||||
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:', () => {
|
||||
let ast: Program
|
||||
let theMap: ReturnType<typeof createArtifactGraph>
|
||||
|
@ -249,7 +249,20 @@ export function getArtifactsToUpdate({
|
||||
const cmd = command.cmd
|
||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
||||
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 pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
||||
const codeRef =
|
||||
|
@ -1631,7 +1631,11 @@ export class EngineCommandManager extends EventTarget {
|
||||
|
||||
switch (this.exportInfo.intent) {
|
||||
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?.reject)
|
||||
break
|
||||
|
@ -32,7 +32,7 @@ import { CoreDumpManager } from 'lib/coredump'
|
||||
import openWindow from 'lib/openWindow'
|
||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||
import { TEST } from 'env'
|
||||
import { err } from 'lib/trap'
|
||||
import { err, Reason } from 'lib/trap'
|
||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||
import { DeepPartial } from 'lib/types'
|
||||
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 const isPathToNodeNumber = (
|
||||
pathToNode: string | number
|
||||
): pathToNode is number => {
|
||||
return typeof pathToNode === 'number'
|
||||
}
|
||||
|
||||
export interface ExecState {
|
||||
memory: ProgramMemory
|
||||
idGenerator: IdGenerator
|
||||
@ -359,10 +365,10 @@ export class ProgramMemory {
|
||||
}
|
||||
|
||||
// TODO: In the future, make the parameter be a KclValue.
|
||||
export function sketchFromKclValue(
|
||||
export function sketchFromKclValueOptional(
|
||||
obj: any,
|
||||
varName: string | null
|
||||
): Sketch | Error {
|
||||
): Sketch | Reason {
|
||||
if (obj?.value?.type === 'Sketch') return obj.value
|
||||
if (obj?.value?.type === 'Solid') return obj.value.sketch
|
||||
if (obj?.type === 'Solid') return obj.sketch
|
||||
@ -371,15 +377,26 @@ export function sketchFromKclValue(
|
||||
}
|
||||
const actualType = obj?.value?.type ?? obj?.type
|
||||
if (actualType) {
|
||||
console.log(obj)
|
||||
return new Error(
|
||||
return new Reason(
|
||||
`Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.`
|
||||
)
|
||||
} 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 (
|
||||
node: Node<Program>,
|
||||
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||
|
@ -68,7 +68,16 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
|
||||
}
|
||||
|
||||
// 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>.
|
||||
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 })
|
||||
}
|
||||
return zip.generateAsync({ type: 'array' }).then((contents) => {
|
||||
return save_({ name: 'output.zip', contents }, toastId)
|
||||
return save_({ name: `${fileName || 'output'}.zip`, contents }, toastId)
|
||||
})
|
||||
} else {
|
||||
files[0].name = fileName || files[0].name
|
||||
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
|
||||
// the file system and not the editor.
|
||||
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
|
||||
|
@ -2,6 +2,23 @@ import toast from 'react-hot-toast'
|
||||
|
||||
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.
|
||||
*/
|
||||
|
@ -159,6 +159,15 @@ export type DefaultPlane = {
|
||||
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 =
|
||||
| {
|
||||
type: 'set-one'
|
||||
@ -198,7 +207,7 @@ export type ModelingMachineEvent =
|
||||
| { type: 'Sketch On Face' }
|
||||
| {
|
||||
type: 'Select default plane'
|
||||
data: DefaultPlane | ExtrudeFacePlane
|
||||
data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
|
||||
}
|
||||
| {
|
||||
type: 'Set selection'
|
||||
@ -1394,7 +1403,7 @@ export const modelingMachine = setup({
|
||||
}
|
||||
),
|
||||
'animate-to-face': fromPromise(
|
||||
async (_: { input?: ExtrudeFacePlane | DefaultPlane }) => {
|
||||
async (_: { input?: ExtrudeFacePlane | DefaultPlane | OffsetPlane }) => {
|
||||
return {} as
|
||||
| undefined
|
||||
| {
|
||||
|
15
src/wasm-lib/Cargo.lock
generated
15
src/wasm-lib/Cargo.lock
generated
@ -1589,6 +1589,8 @@ dependencies = [
|
||||
"console",
|
||||
"lazy_static",
|
||||
"linked-hash-map",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"regex",
|
||||
"serde",
|
||||
"similar",
|
||||
@ -1689,6 +1691,7 @@ dependencies = [
|
||||
"databake",
|
||||
"derive-docs",
|
||||
"expectorate",
|
||||
"fnv",
|
||||
"form_urlencoded",
|
||||
"futures",
|
||||
"git_rev",
|
||||
@ -1734,18 +1737,6 @@ dependencies = [
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kcl-macros"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"databake",
|
||||
"kcl-lib",
|
||||
"pretty_assertions",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.87",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "kcl-test-server"
|
||||
version = "0.1.16"
|
||||
|
@ -68,7 +68,6 @@ debug = "line-tables-only"
|
||||
members = [
|
||||
"derive-docs",
|
||||
"kcl",
|
||||
"kcl-macros",
|
||||
"kcl-test-server",
|
||||
"kcl-to-core",
|
||||
]
|
||||
|
@ -173,11 +173,11 @@ fn do_stdlib_inner(
|
||||
quote! {
|
||||
let code_blocks = vec![#(#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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
}).collect::<Vec<String>>()
|
||||
}
|
||||
} else {
|
||||
@ -748,8 +748,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
||||
quote! {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn #test_name_mock() {
|
||||
let program = crate::parser::top_level_parse(#code_block).unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse(#code_block).unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
||||
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,
|
||||
};
|
||||
|
||||
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)]
|
||||
|
@ -2,8 +2,7 @@
|
||||
mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::parser::top_level_parse("someFn()").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("someFn()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -15,7 +14,9 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
mod test_examples_someFn {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_someFn0() {
|
||||
let program = crate::parser::top_level_parse("someFn()").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("someFn()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -15,7 +14,9 @@ mod test_examples_someFn {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nshow")
|
||||
.unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -17,7 +15,9 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -36,9 +36,7 @@ mod test_examples_show {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show1() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -50,7 +48,9 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for Show {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ mod test_examples_my_func {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_my_func0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmyFunc")
|
||||
.unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -17,7 +15,9 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -36,9 +36,7 @@ mod test_examples_my_func {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_my_func1() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -50,7 +48,9 @@ mod test_examples_my_func {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for MyFunc {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -3,9 +3,7 @@ mod test_examples_line_to {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_line_to0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nlineTo")
|
||||
.unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nlineTo").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -17,7 +15,9 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -36,9 +36,7 @@ mod test_examples_line_to {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_line_to1() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -50,7 +48,9 @@ mod test_examples_line_to {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -157,10 +157,10 @@ impl crate::docs::StdLibFn for LineTo {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -3,8 +3,7 @@ mod test_examples_min {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_min0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
crate::Program::parse("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +15,9 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -35,9 +36,7 @@ mod test_examples_min {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_min1() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmin").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nmin").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -49,7 +48,9 @@ mod test_examples_min {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -148,10 +149,10 @@ impl crate::docs::StdLibFn for Min {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_import {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_import0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,9 +2,7 @@
|
||||
mod test_examples_show {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_show0() {
|
||||
let program =
|
||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -2,8 +2,7 @@
|
||||
mod test_examples_some_function {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn test_mock_example_some_function0() {
|
||||
let program = crate::parser::top_level_parse("someFunction()").unwrap();
|
||||
let id_generator = crate::executor::IdGenerator::default();
|
||||
let program = crate::Program::parse("someFunction()").unwrap();
|
||||
let ctx = crate::executor::ExecutorContext {
|
||||
engine: std::sync::Arc::new(Box::new(
|
||||
crate::engine::conn_mock::EngineConnection::new()
|
||||
@ -15,7 +14,9 @@ mod test_examples_some_function {
|
||||
settings: Default::default(),
|
||||
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)]
|
||||
@ -106,10 +107,10 @@ impl crate::docs::StdLibFn for SomeFunction {
|
||||
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();
|
||||
options.insert_final_newline = false;
|
||||
program.recast(&options, 0)
|
||||
program.ast.recast(&options, 0)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
}
|
||||
|
@ -1,29 +1,20 @@
|
||||
cnr := "cargo nextest run"
|
||||
cita := "cargo insta test --accept"
|
||||
|
||||
# Create a new KCL snapshot test from `tests/inputs/my-test.kcl`.
|
||||
new-test name:
|
||||
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
|
||||
TWENTY_TWENTY=overwrite {{cnr}} --test executor -E 'test(=visuals::{{name}})'
|
||||
|
||||
# Run the same lint checks we run in CI.
|
||||
lint:
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
|
||||
# Generate the stdlib image artifacts
|
||||
# Then run the stdlib docs generation
|
||||
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
|
||||
|
||||
# Create a new KCL deterministic simulation test case.
|
||||
new-sim-test test_name kcl_program 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.
|
||||
new-sim-test test_name render_to_png="true":
|
||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
|
||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse
|
||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse
|
||||
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},
|
||||
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::{
|
||||
sync::{mpsc, oneshot},
|
||||
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}")),
|
||||
};
|
||||
let RequestBody { kcl_program, test_name } = body;
|
||||
let module_id = ModuleId::default();
|
||||
let parser = match kcl_lib::token::lexer(&kcl_program, module_id) {
|
||||
Ok(ts) => kcl_lib::parser::Parser::new(ts),
|
||||
Err(e) => return bad_request(format!("tokenization error: {e}")),
|
||||
};
|
||||
let program = match parser.ast() {
|
||||
|
||||
let program = match Program::parse(&kcl_program) {
|
||||
Ok(pr) => pr,
|
||||
Err(e) => return bad_request(format!("Parse error: {e}")),
|
||||
};
|
||||
|
||||
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.
|
||||
// There's no actual KCL associated with this reset_scene call.
|
||||
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
|
||||
{
|
||||
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 (done_tx, done_rx) = oneshot::channel::<()>();
|
||||
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,
|
||||
Err(e) => return kcl_err(e),
|
||||
};
|
||||
|
@ -1,9 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_lib::{
|
||||
engine::ExecutionKind,
|
||||
errors::KclError,
|
||||
executor::{DefaultPlanes, IdGenerator},
|
||||
exec::{DefaultPlanes, IdGenerator},
|
||||
ExecutionKind, KclError,
|
||||
};
|
||||
use kittycad_modeling_cmds::{
|
||||
self as kcmc,
|
||||
@ -23,8 +22,8 @@ const NEED_PLANES: bool = true;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EngineConnection {
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::executor::SourceRange)>>>,
|
||||
batch: Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
batch_end: Arc<Mutex<IndexMap<uuid::Uuid, (WebSocketRequest, kcl_lib::SourceRange)>>>,
|
||||
core_test: Arc<Mutex<String>>,
|
||||
default_planes: Arc<RwLock<Option<DefaultPlanes>>>,
|
||||
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]
|
||||
impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::executor::SourceRange)>>> {
|
||||
impl kcl_lib::EngineManager for EngineConnection {
|
||||
fn batch(&self) -> Arc<Mutex<Vec<(WebSocketRequest, kcl_lib::SourceRange)>>> {
|
||||
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()
|
||||
}
|
||||
|
||||
@ -378,7 +377,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
async fn default_planes(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
source_range: kcl_lib::executor::SourceRange,
|
||||
source_range: kcl_lib::SourceRange,
|
||||
) -> Result<DefaultPlanes, KclError> {
|
||||
if NEED_PLANES {
|
||||
{
|
||||
@ -400,7 +399,7 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
async fn clear_scene_post_hook(
|
||||
&self,
|
||||
_id_generator: &mut IdGenerator,
|
||||
_source_range: kcl_lib::executor::SourceRange,
|
||||
_source_range: kcl_lib::SourceRange,
|
||||
) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
@ -408,9 +407,9 @@ impl kcl_lib::engine::EngineManager for EngineConnection {
|
||||
async fn inner_send_modeling_cmd(
|
||||
&self,
|
||||
id: uuid::Uuid,
|
||||
_source_range: kcl_lib::executor::SourceRange,
|
||||
_source_range: kcl_lib::SourceRange,
|
||||
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> {
|
||||
match cmd {
|
||||
WebSocketRequest::ModelingCmdBatchReq(ModelingBatch {
|
||||
|
@ -1,5 +1,5 @@
|
||||
use anyhow::Result;
|
||||
use kcl_lib::executor::{ExecutorContext, IdGenerator};
|
||||
use kcl_lib::{ExecState, ExecutorContext};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -7,21 +7,15 @@ mod conn_mock_core;
|
||||
|
||||
///Converts the given kcl code to an engine test
|
||||
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 ref_result = Arc::clone(&result);
|
||||
|
||||
let ctx = ExecutorContext {
|
||||
engine: Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).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 ctx = ExecutorContext::new_forwarded_mock(Arc::new(Box::new(
|
||||
crate::conn_mock_core::EngineConnection::new(ref_result).await?,
|
||||
)));
|
||||
ctx.run(&program, &mut ExecState::default()).await?;
|
||||
|
||||
let result = result.lock().expect("mutex lock").clone();
|
||||
Ok(result)
|
||||
|
@ -21,6 +21,7 @@ convert_case = "0.6.0"
|
||||
dashmap = "6.1.0"
|
||||
databake = { version = "0.1.8", features = ["derive"] }
|
||||
derive-docs = { version = "0.1.29", path = "../derive-docs" }
|
||||
fnv = "1.0.7"
|
||||
form_urlencoded = "1.2.1"
|
||||
futures = { version = "0.3.31" }
|
||||
git_rev = "0.1.0"
|
||||
@ -86,7 +87,7 @@ expectorate = "1.1.0"
|
||||
handlebars = "6.2.0"
|
||||
iai = "0.1"
|
||||
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"
|
||||
pretty_assertions = "1.4.1"
|
||||
tokio = { version = "1.40.0", features = ["rt-multi-thread", "macros", "time"] }
|
||||
|
@ -1,12 +1,5 @@
|
||||
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) {
|
||||
for (name, file) in [
|
||||
("pipes_on_pipes", PIPES_PROGRAM),
|
||||
@ -16,28 +9,20 @@ pub fn bench_parse(c: &mut Criterion) {
|
||||
("mike_stress_test", MIKE_STRESS_TEST_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| {
|
||||
let tok = tokens.clone();
|
||||
b.iter(move || {
|
||||
let parser = kcl_lib::parser::Parser::new(tok.clone());
|
||||
black_box(parser.ast().unwrap());
|
||||
black_box(kcl_lib::Program::parse(file).unwrap());
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn lex(program: &str, module_id: kcl_lib::ast::types::ModuleId) {
|
||||
black_box(kcl_lib::token::lexer(program, module_id).unwrap());
|
||||
}
|
||||
|
||||
criterion_group!(benches, bench_lex, bench_parse);
|
||||
criterion_group!(benches, bench_parse);
|
||||
criterion_main!(benches);
|
||||
|
||||
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 CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.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");
|
||||
|
@ -1,32 +1,7 @@
|
||||
use iai::black_box;
|
||||
|
||||
pub fn parse(program: &str) {
|
||||
let module_id = kcl_lib::ast::types::ModuleId::default();
|
||||
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());
|
||||
black_box(kcl_lib::Program::parse(program).unwrap());
|
||||
}
|
||||
|
||||
fn parse_kitt() {
|
||||
@ -46,11 +21,6 @@ fn parse_lsystem() {
|
||||
}
|
||||
|
||||
iai::main! {
|
||||
lex_kitt,
|
||||
lex_pipes,
|
||||
lex_cube,
|
||||
lex_math,
|
||||
lex_lsystem,
|
||||
parse_kitt,
|
||||
parse_pipes,
|
||||
parse_cube,
|
||||
|
@ -9,7 +9,7 @@ pub fn bench_digest(c: &mut Criterion) {
|
||||
("mike_stress_test", MIKE_STRESS_TEST_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| {
|
||||
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 CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.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");
|
||||
|
@ -1,5 +1,5 @@
|
||||
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;
|
||||
|
||||
pub fn bench_execute(c: &mut Criterion) {
|
||||
|
@ -3,7 +3,7 @@ use iai::black_box;
|
||||
async fn execute_server_rack_heavy() {
|
||||
let code = SERVER_RACK_HEAVY_PROGRAM;
|
||||
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
|
||||
.unwrap(),
|
||||
);
|
||||
@ -12,7 +12,7 @@ async fn execute_server_rack_heavy() {
|
||||
async fn execute_server_rack_lite() {
|
||||
let code = SERVER_RACK_LITE_PROGRAM;
|
||||
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
|
||||
.unwrap(),
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
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 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 CUBE_PROGRAM: &str = include_str!("../../tests/executor/inputs/cube.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 LSYSTEM_PROGRAM: &str = include_str!("../../tests/executor/inputs/lsystem.kcl");
|
||||
|
@ -1,5 +1,5 @@
|
||||
use iai::black_box;
|
||||
use kcl_lib::lsp::test_util::kcl_lsp_server;
|
||||
use kcl_lib::kcl_lsp_server;
|
||||
use tower_lsp::LanguageServer;
|
||||
|
||||
async fn kcl_lsp_semantic_tokens(code: &str) {
|
||||
|
@ -9,11 +9,12 @@ use kittycad_modeling_cmds as kcmc;
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, CallExpression, ConstraintLevel, FormatOptions, Literal, PipeExpression, PipeSubstitution,
|
||||
Program, VariableDeclarator,
|
||||
VariableDeclarator,
|
||||
},
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::{Point2d, SourceRange},
|
||||
Program,
|
||||
};
|
||||
|
||||
use super::types::{ModuleId, Node};
|
||||
@ -22,13 +23,13 @@ type Point3d = kcmc::shared::Point3d<f64>;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// The control point data for a curve or line.
|
||||
pub struct ControlPointData {
|
||||
struct ControlPointData {
|
||||
/// The control points for the curve or line.
|
||||
pub points: Vec<Point3d>,
|
||||
points: Vec<Point3d>,
|
||||
/// The command that created this curve or line.
|
||||
pub command: PathCommand,
|
||||
_command: PathCommand,
|
||||
/// The id of the curve or line.
|
||||
pub id: uuid::Uuid,
|
||||
_id: uuid::Uuid,
|
||||
}
|
||||
|
||||
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.
|
||||
pub async fn modify_ast_for_sketch(
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: &mut Node<Program>,
|
||||
program: &mut Program,
|
||||
module_id: ModuleId,
|
||||
// The name of the sketch.
|
||||
sketch_name: &str,
|
||||
@ -50,7 +51,7 @@ pub async fn modify_ast_for_sketch(
|
||||
// If it is, we cannot modify it.
|
||||
|
||||
// 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 {
|
||||
super::types::Definition::Variable(var) => var.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 {
|
||||
points: data.control_points.clone(),
|
||||
command: segment.command,
|
||||
id: (*command_id).into(),
|
||||
_command: segment.command,
|
||||
_id: (*command_id).into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -179,12 +180,12 @@ pub async fn modify_ast_for_sketch(
|
||||
)?;
|
||||
|
||||
// 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.
|
||||
*program = crate::parser::parse(&recasted, module_id)?;
|
||||
*program = crate::parser::parse(&recasted, module_id)?.into();
|
||||
|
||||
Ok(recasted)
|
||||
}
|
||||
|
@ -223,7 +223,7 @@ impl Node<Program> {
|
||||
/// Check the provided Program for any lint findings.
|
||||
pub fn lint<'a, RuleT>(&'a self, rule: RuleT) -> Result<Vec<crate::lint::Discovered>>
|
||||
where
|
||||
RuleT: crate::lint::rule::Rule<'a>,
|
||||
RuleT: crate::lint::Rule<'a>,
|
||||
{
|
||||
let v = Arc::new(Mutex::new(vec![]));
|
||||
crate::walk::walk(self, &|node: crate::walk::Node<'a>| {
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Core dump related structures and functions.
|
||||
#![allow(dead_code)]
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod local;
|
||||
|
@ -26,14 +26,14 @@ type Point3D = kcmc::shared::Point3d<f64>;
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, Program, TagDeclarator,
|
||||
TagNode,
|
||||
BodyItem, Expr, FunctionExpression, ItemVisibility, KclNone, ModuleId, Node, NodeRef, TagDeclarator, TagNode,
|
||||
},
|
||||
engine::{EngineManager, ExecutionKind},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
fs::{FileManager, FileSystem},
|
||||
settings::types::UnitLength,
|
||||
std::{FnAsArg, StdLib},
|
||||
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.
|
||||
/// 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.
|
||||
#[allow(clippy::vec_box)]
|
||||
pub fn find_solids_on_sketch(&self, sketch_id: uuid::Uuid) -> Vec<Box<Solid>> {
|
||||
self.environments
|
||||
.iter()
|
||||
@ -537,6 +538,7 @@ impl Geometry {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum Geometries {
|
||||
Sketches(Vec<Box<Sketch>>),
|
||||
Solids(Vec<Box<Solid>>),
|
||||
@ -555,6 +557,7 @@ impl From<Geometry> for Geometries {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum SketchSet {
|
||||
Sketch(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)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type", rename_all = "camelCase")]
|
||||
#[allow(clippy::vec_box)]
|
||||
pub enum SolidSet {
|
||||
Solid(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)]
|
||||
@ -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> {
|
||||
if let KclValue::Solid(value) = &self {
|
||||
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.
|
||||
/// With a kittycad client.
|
||||
/// 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
|
||||
/// variables.
|
||||
#[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.
|
||||
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)
|
||||
}
|
||||
|
||||
@ -2223,48 +2318,31 @@ impl ExecutorContext {
|
||||
|
||||
pub async fn reset_scene(
|
||||
&self,
|
||||
id_generator: &mut IdGenerator,
|
||||
exec_state: &mut ExecState,
|
||||
source_range: crate::executor::SourceRange,
|
||||
) -> Result<()> {
|
||||
self.engine.clear_scene(id_generator, source_range).await?;
|
||||
self.engine
|
||||
.clear_scene(&mut exec_state.id_generator, source_range)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
/// You can optionally pass in some initialization memory.
|
||||
/// Kurt uses this for partial execution.
|
||||
pub async fn run(
|
||||
&self,
|
||||
program: NodeRef<'_, crate::ast::types::Program>,
|
||||
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)
|
||||
pub async fn run(&self, program: &Program, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||
self.run_with_session_data(program, exec_state).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Perform the execution of a program.
|
||||
/// You can optionally pass in some initialization memory.
|
||||
/// Kurt uses this for partial execution.
|
||||
pub async fn run_with_session_data(
|
||||
&self,
|
||||
program: NodeRef<'_, crate::ast::types::Program>,
|
||||
memory: Option<ProgramMemory>,
|
||||
id_generator: IdGenerator,
|
||||
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()
|
||||
};
|
||||
program: &Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<Option<ModelingSessionData>, KclError> {
|
||||
// TODO: Use the top-level file's path.
|
||||
exec_state.add_module(std::path::PathBuf::from(""));
|
||||
// Before we even start executing the program, set the units.
|
||||
@ -2285,10 +2363,10 @@ impl ExecutorContext {
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.inner_execute(program, &mut exec_state, crate::executor::BodyType::Root)
|
||||
self.inner_execute(&program.ast, exec_state, crate::executor::BodyType::Root)
|
||||
.await?;
|
||||
let session_data = self.engine.get_session_data();
|
||||
Ok((exec_state, session_data))
|
||||
Ok(session_data)
|
||||
}
|
||||
|
||||
/// Execute an AST's program.
|
||||
@ -2539,23 +2617,15 @@ impl ExecutorContext {
|
||||
/// Execute the program, then get a PNG screenshot.
|
||||
pub async fn execute_and_prepare_snapshot(
|
||||
&self,
|
||||
program: NodeRef<'_, Program>,
|
||||
id_generator: IdGenerator,
|
||||
project_directory: Option<String>,
|
||||
program: &Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<TakeSnapshot> {
|
||||
self.execute_and_prepare(program, id_generator, project_directory)
|
||||
.await
|
||||
.map(|(_state, snap)| snap)
|
||||
self.execute_and_prepare(program, exec_state).await
|
||||
}
|
||||
|
||||
/// Execute the program, return the interpreter and outputs.
|
||||
pub async fn execute_and_prepare(
|
||||
&self,
|
||||
program: NodeRef<'_, Program>,
|
||||
id_generator: IdGenerator,
|
||||
project_directory: Option<String>,
|
||||
) -> Result<(ExecState, TakeSnapshot)> {
|
||||
let state = self.run(program, None, id_generator, project_directory).await?;
|
||||
pub async fn execute_and_prepare(&self, program: &Program, exec_state: &mut ExecState) -> Result<TakeSnapshot> {
|
||||
self.run(program, exec_state).await?;
|
||||
|
||||
// Zoom to fit.
|
||||
self.engine
|
||||
@ -2588,7 +2658,7 @@ impl ExecutorContext {
|
||||
else {
|
||||
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};
|
||||
|
||||
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 {
|
||||
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
||||
@ -2704,7 +2774,8 @@ mod tests {
|
||||
settings: Default::default(),
|
||||
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)
|
||||
}
|
||||
@ -3033,14 +3104,14 @@ for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
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')
|
||||
|> startProfileAt(array[0], %)
|
||||
|> line([0, array[1]], %)
|
||||
|> line([array[2], 0], %)
|
||||
|> line([0, -array[1]], %)
|
||||
|> startProfileAt(arr[0], %)
|
||||
|> line([0, arr[1]], %)
|
||||
|> line([arr[2], 0], %)
|
||||
|> line([0, -arr[1]], %)
|
||||
|> close(%)
|
||||
|> extrude(array[3], %)
|
||||
|> extrude(arr[3], %)
|
||||
|
||||
return myBox
|
||||
}
|
||||
|
@ -13,26 +13,112 @@ macro_rules! println {
|
||||
}
|
||||
}
|
||||
|
||||
pub mod ast;
|
||||
pub mod coredump;
|
||||
pub mod docs;
|
||||
pub mod engine;
|
||||
pub mod errors;
|
||||
pub mod executor;
|
||||
pub mod fs;
|
||||
mod ast;
|
||||
mod coredump;
|
||||
mod docs;
|
||||
mod engine;
|
||||
mod errors;
|
||||
mod executor;
|
||||
mod fs;
|
||||
mod function_param;
|
||||
pub mod lint;
|
||||
pub mod lsp;
|
||||
pub mod parser;
|
||||
pub mod settings;
|
||||
mod lsp;
|
||||
mod parser;
|
||||
mod settings;
|
||||
#[cfg(test)]
|
||||
mod simulation_tests;
|
||||
pub mod std;
|
||||
mod std;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod test_server;
|
||||
pub mod thread;
|
||||
pub mod token;
|
||||
mod thread;
|
||||
mod token;
|
||||
mod unparser;
|
||||
pub mod walk;
|
||||
mod walk;
|
||||
#[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 std_lib_args;
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub use camel_case::{lint_object_properties, lint_variables, Z0001};
|
||||
pub use offset_plane::{lint_should_be_offset_plane, Z0003};
|
||||
pub use std_lib_args::{lint_call_expressions, Z0002};
|
||||
|
@ -1,4 +1,4 @@
|
||||
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.
|
||||
#![allow(dead_code)]
|
||||
|
||||
pub mod cache;
|
||||
pub mod types;
|
||||
@ -26,6 +27,7 @@ use tower_lsp::{
|
||||
|
||||
use crate::lsp::{
|
||||
backend::Backend as _,
|
||||
copilot::cache::CopilotCache,
|
||||
copilot::types::{
|
||||
CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
|
||||
CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
|
||||
@ -131,6 +133,38 @@ impl crate::lsp::backend::Backend for 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.
|
||||
pub async fn get_completions(&self, language: String, prompt: String, suffix: String) -> Result<Vec<String>> {
|
||||
let body = kittycad::types::KclCodeCompletionRequest {
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Functions for the `kcl` lsp server.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
@ -40,11 +41,11 @@ use tower_lsp::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
ast::types::{Expr, ModuleId, Node, NodeRef, VariableKind},
|
||||
executor::{IdGenerator, SourceRange},
|
||||
ast::types::{Expr, ModuleId, Node, VariableKind},
|
||||
lsp::{backend::Backend as _, util::IntoDiagnostic},
|
||||
parser::PIPE_OPERATOR,
|
||||
token::TokenType,
|
||||
ExecState, Program, SourceRange,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
@ -122,6 +123,73 @@ pub struct Backend {
|
||||
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.
|
||||
#[async_trait::async_trait]
|
||||
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.
|
||||
// This function automatically executes if we should & updates the diagnostics if we got
|
||||
// errors.
|
||||
if self.execute(¶ms, &ast).await.is_err() {
|
||||
if self.execute(¶ms, &ast.into()).await.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -572,7 +640,7 @@ impl Backend {
|
||||
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.
|
||||
if !self.can_execute().await {
|
||||
return Ok(());
|
||||
@ -589,25 +657,22 @@ impl Backend {
|
||||
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.
|
||||
executor_ctx
|
||||
.engine
|
||||
.clear_scene(&mut id_generator, SourceRange::default())
|
||||
.clear_scene(&mut exec_state.id_generator, SourceRange::default())
|
||||
.await?;
|
||||
|
||||
let exec_state = match executor_ctx.run(ast, None, id_generator, None).await {
|
||||
Ok(exec_state) => exec_state,
|
||||
Err(err) => {
|
||||
self.memory_map.remove(params.uri.as_str());
|
||||
self.add_to_diagnostics(params, &[err], false).await;
|
||||
if let Err(err) = executor_ctx.run(ast, &mut exec_state).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
|
||||
// string.
|
||||
return Err(anyhow::anyhow!("failed to execute code"));
|
||||
}
|
||||
};
|
||||
// Since we already published the diagnostics we don't really care about the error
|
||||
// string.
|
||||
return Err(anyhow::anyhow!("failed to execute code"));
|
||||
}
|
||||
|
||||
self.memory_map
|
||||
.insert(params.uri.to_string(), exec_state.memory.clone());
|
||||
|
@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||
|
||||
use pretty_assertions::assert_eq;
|
||||
use tower_lsp::{
|
||||
lsp_types::{SemanticTokenModifier, SemanticTokenType},
|
||||
lsp_types::{Diagnostic, SemanticTokenModifier, SemanticTokenType},
|
||||
LanguageServer,
|
||||
};
|
||||
|
||||
@ -2369,7 +2369,14 @@ async fn kcl_test_kcl_lsp_diagnostics_on_execution_error() {
|
||||
|
||||
// Get the diagnostics.
|
||||
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")]
|
||||
|
@ -12,6 +12,7 @@ pub(crate) mod parser_impl;
|
||||
pub const PIPE_SUBSTITUTION_OPERATOR: &str = "%";
|
||||
pub const PIPE_OPERATOR: &str = "|>";
|
||||
|
||||
#[cfg(test)]
|
||||
/// Parse the given KCL code into an AST. This is the top-level.
|
||||
pub fn top_level_parse(code: &str) -> Result<Node<Program>, KclError> {
|
||||
let module_id = ModuleId::default();
|
||||
|
@ -2040,11 +2040,39 @@ fn fn_call(i: TokenSlice) -> PResult<Node<CallExpression>> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use itertools::Itertools;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
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]
|
||||
fn parse_args() {
|
||||
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};
|
||||
|
||||
const DEFAULT_THEME_COLOR: f64 = 264.5;
|
||||
pub const DEFAULT_PROJECT_KCL_FILE: &str = "main.kcl";
|
||||
const DEFAULT_PROJECT_NAME_TEMPLATE: &str = "project-$nnn";
|
||||
|
||||
/// 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 {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let obj = arg.as_object()?;
|
||||
let_field_of!(obj, typ "type");
|
||||
let_field_of!(obj, typ "format");
|
||||
match typ {
|
||||
"fbx" => Some(Self::Fbx {}),
|
||||
"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 {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
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 {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
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 {
|
||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||
let case1 = super::sketch::PlaneData::from_kcl_val;
|
||||
let case2 = crate::executor::Solid::from_kcl_val;
|
||||
// Order is critical since PlaneData is a subset of Plane.
|
||||
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)
|
||||
.map(Box::new)
|
||||
.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.
|
||||
/// // It uses the `reduce` function, to call the `add` function on every
|
||||
/// // element of the `array` parameter. The starting value is 0.
|
||||
/// fn sum = (array) => { return reduce(array, 0, add) }
|
||||
/// // element of the `arr` parameter. The starting value is 0.
|
||||
/// fn sum = (arr) => { return reduce(arr, 0, add) }
|
||||
///
|
||||
/// /*
|
||||
/// The above is basically like this pseudo-code:
|
||||
/// fn sum(array):
|
||||
/// fn sum(arr):
|
||||
/// let sumSoFar = 0
|
||||
/// for i in array:
|
||||
/// for i in arr:
|
||||
/// sumSoFar = add(sumSoFar, i)
|
||||
/// 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
|
||||
/// // an anonymous `add` function as its parameter, instead of declaring a
|
||||
/// // named function outside.
|
||||
/// array = [1, 2, 3]
|
||||
/// sum = reduce(array, 0, (i, result_so_far) => { return i + result_so_far })
|
||||
/// arr = [1, 2, 3]
|
||||
/// sum = reduce(arr, 0, (i, result_so_far) => { return i + result_so_far })
|
||||
///
|
||||
/// // We use `assertEqual` to check that our `sum` function gives the
|
||||
/// // expected result. It's good to check your work!
|
||||
|
@ -42,7 +42,7 @@ const ZOO_COORD_SYSTEM: System = System {
|
||||
/// Import format specifier
|
||||
#[derive(serde :: Serialize, serde :: Deserialize, PartialEq, Debug, Clone, schemars :: JsonSchema)]
|
||||
#[cfg_attr(feature = "tabled", derive(tabled::Tabled))]
|
||||
#[serde(tag = "type")]
|
||||
#[serde(tag = "format")]
|
||||
pub enum ImportFormat {
|
||||
/// Autodesk Filmbox (FBX) format
|
||||
#[serde(rename = "fbx")]
|
||||
@ -152,7 +152,7 @@ pub async fn import(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
/// ```
|
||||
///
|
||||
/// ```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
|
||||
|
@ -4,7 +4,7 @@ use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
ast::types::{BodyItem, Expr, FunctionExpression, Node, Program},
|
||||
ast::types::{FunctionExpression, Program},
|
||||
docs::{StdLibFn, StdLibFnData},
|
||||
};
|
||||
|
||||
@ -77,18 +77,3 @@ impl Serialize for Box<dyn KclStdLibFn> {
|
||||
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,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>;
|
||||
|
||||
pub type FnMap = HashMap<String, StdFn>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
|
||||
Box::new(LegLen),
|
||||
@ -91,6 +89,7 @@ lazy_static! {
|
||||
Box::new(crate::std::sketch::ProfileStart),
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::ArcTo),
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
Box::new(crate::std::sketch::TangentialArcTo),
|
||||
Box::new(crate::std::sketch::TangentialArcToRelative),
|
||||
|
@ -1,17 +1,20 @@
|
||||
//! Standard library plane helpers.
|
||||
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
errors::KclError,
|
||||
executor::{ExecState, KclValue, Plane},
|
||||
executor::{ExecState, KclValue, Plane, PlaneType},
|
||||
std::{sketch::PlaneData, Args},
|
||||
};
|
||||
|
||||
/// 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")]
|
||||
pub enum StandardPlane {
|
||||
/// The XY plane.
|
||||
@ -50,8 +53,8 @@ impl From<StandardPlane> for PlaneData {
|
||||
/// Offset a plane by a distance along its normal.
|
||||
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 plane_data = inner_offset_plane(std_plane, offset, exec_state).await?;
|
||||
let plane = Plane::from_plane_data(plane_data, exec_state);
|
||||
let plane = inner_offset_plane(std_plane, offset, exec_state).await?;
|
||||
make_offset_plane_in_engine(&plane, exec_state, &args).await?;
|
||||
Ok(KclValue::Plane(Box::new(plane)))
|
||||
}
|
||||
|
||||
@ -144,11 +147,14 @@ async fn inner_offset_plane(
|
||||
std_plane: StandardPlane,
|
||||
offset: f64,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<PlaneData, KclError> {
|
||||
) -> Result<Plane, KclError> {
|
||||
// Convert to the plane type.
|
||||
let plane_data: PlaneData = std_plane.into();
|
||||
// Convert to a plane.
|
||||
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 {
|
||||
StandardPlane::XY => {
|
||||
@ -171,10 +177,44 @@ async fn inner_offset_plane(
|
||||
}
|
||||
}
|
||||
|
||||
Ok(PlaneData::Plane {
|
||||
origin: Box::new(plane.origin),
|
||||
x_axis: Box::new(plane.x_axis),
|
||||
y_axis: Box::new(plane.y_axis),
|
||||
z_axis: Box::new(plane.z_axis),
|
||||
})
|
||||
Ok(plane)
|
||||
}
|
||||
|
||||
// Engine-side effectful creation of an actual plane object.
|
||||
// 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> {
|
||||
// Let's assume it's the XY plane for now, this is just for backwards compatibility.
|
||||
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?;
|
||||
Ok(sketch)
|
||||
}
|
||||
@ -905,11 +905,12 @@ async fn inner_start_sketch_at(data: [f64; 2], exec_state: &mut ExecState, args:
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", untagged)]
|
||||
pub enum SketchData {
|
||||
Plane(PlaneData),
|
||||
PlaneOrientation(PlaneData),
|
||||
Plane(Box<Plane>),
|
||||
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)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@ -1069,10 +1070,11 @@ async fn inner_start_sketch_on(
|
||||
args: &Args,
|
||||
) -> Result<SketchSurface, KclError> {
|
||||
match data {
|
||||
SketchData::Plane(plane_data) => {
|
||||
let plane = start_sketch_on_plane(plane_data, exec_state, args).await?;
|
||||
SketchData::PlaneOrientation(plane_data) => {
|
||||
let plane = make_sketch_plane_from_orientation(plane_data, exec_state, args).await?;
|
||||
Ok(SketchSurface::Plane(plane))
|
||||
}
|
||||
SketchData::Plane(plane) => Ok(SketchSurface::Plane(plane)),
|
||||
SketchData::Solid(solid) => {
|
||||
let Some(tag) = tag else {
|
||||
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,
|
||||
exec_state: &mut ExecState,
|
||||
args: &Args,
|
||||
@ -1122,10 +1124,10 @@ async fn start_sketch_on_plane(
|
||||
|
||||
plane.id = match data {
|
||||
PlaneData::XY => default_planes.xy,
|
||||
PlaneData::XZ => default_planes.xz,
|
||||
PlaneData::YZ => default_planes.yz,
|
||||
PlaneData::NegXY => default_planes.neg_xy,
|
||||
PlaneData::XZ => default_planes.xz,
|
||||
PlaneData::NegXZ => default_planes.neg_xz,
|
||||
PlaneData::YZ => default_planes.yz,
|
||||
PlaneData::NegYZ => default_planes.neg_yz,
|
||||
PlaneData::Plane {
|
||||
origin,
|
||||
@ -1210,11 +1212,26 @@ pub(crate) async fn inner_start_profile_at(
|
||||
exec_state: &mut ExecState,
|
||||
args: Args,
|
||||
) -> Result<Sketch, KclError> {
|
||||
if let SketchSurface::Face(face) = &sketch_surface {
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// 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())
|
||||
match &sketch_surface {
|
||||
SketchSurface::Face(face) => {
|
||||
// Flush the batch for our fillets/chamfers if there are any.
|
||||
// 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?;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// 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.
|
||||
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()?;
|
||||
@ -1499,7 +1527,7 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
/// radius: 16
|
||||
/// }, %)
|
||||
/// |> close(%)
|
||||
// const example = extrude(10, exampleSketch)
|
||||
/// const example = extrude(10, exampleSketch)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arc",
|
||||
@ -1578,6 +1606,104 @@ pub(crate) async fn inner_arc(
|
||||
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.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, JsonSchema, ts_rs::TS)]
|
||||
#[ts(export)]
|
||||
@ -1897,6 +2023,42 @@ async fn inner_tangential_arc_to_relative(
|
||||
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.
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
@ -2073,7 +2235,7 @@ mod tests {
|
||||
|
||||
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]
|
||||
fn test_deserialize_plane_data() {
|
||||
@ -2144,4 +2306,11 @@ mod tests {
|
||||
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)]
|
||||
pub struct Uint(f64);
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Uint {
|
||||
pub fn new(value: f64) -> Self {
|
||||
if value < 0.0 {
|
||||
|
@ -53,20 +53,6 @@ pub fn delta(from_angle: Angle, to_angle: Angle) -> Angle {
|
||||
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 {
|
||||
let draft = angle % (2.0 * PI);
|
||||
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 {
|
||||
let line2_point_b = Point2d {
|
||||
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.
|
||||
#[allow(dead_code)]
|
||||
pub struct TangentialArcInfoOutput {
|
||||
/// The center point of the arc.
|
||||
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