Compare commits
19 Commits
codex/fix-
...
pierremtb/
Author | SHA1 | Date | |
---|---|---|---|
1513a929ee | |||
fb35fdcc38 | |||
e76ba9921c | |||
b19acd550d | |||
f3e9d110c0 | |||
658497da1d | |||
bd01059a92 | |||
57a977e6be | |||
94b0cc1f3e | |||
5734cc7fc3 | |||
3168c22de7 | |||
3c94fe9047 | |||
cdd6b56d42 | |||
75ac3bc61b | |||
29d511d085 | |||
b0a41939e8 | |||
7d2c1061ba | |||
d768073d17 | |||
dc8496c62e |
@ -42,8 +42,6 @@ The 3D view in Design Studio is just a video stream from our hosted geometry eng
|
|||||||
|
|
||||||
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
|
We recommend downloading the latest application binary from our [releases](https://github.com/KittyCAD/modeling-app/releases) page. If you don't see your platform or architecture supported there, please file an issue.
|
||||||
|
|
||||||
If you'd like to try out upcoming changes sooner, you can also download those from our [nightly releases](https://zoo.dev/modeling-app/download/nightly) page.
|
|
||||||
|
|
||||||
## Developing
|
## Developing
|
||||||
|
|
||||||
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
|
Finally, if you'd like to run a development build or contribute to the project, please visit our [contributor guide](CONTRIBUTING.md) to get started.
|
||||||
|
@ -177,7 +177,7 @@ You can also import the whole module. This is useful if you want to use the
|
|||||||
result of a module as a variable, like a part.
|
result of a module as a variable, like a part.
|
||||||
|
|
||||||
```norun
|
```norun
|
||||||
import "tests/inputs/cube.kcl" as cube
|
import "cube.kcl"
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
```
|
```
|
||||||
@ -241,7 +241,7 @@ If you want to have multiple instances of the same object, you can use the
|
|||||||
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
[`clone`](/docs/kcl/clone) function. This will render a new instance of the object in memory.
|
||||||
|
|
||||||
```norun
|
```norun
|
||||||
import cube from "tests/inputs/cube.kcl"
|
import cube from "cube.kcl"
|
||||||
|
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
@ -257,7 +257,7 @@ separate objects in memory, and can be manipulated independently.
|
|||||||
Here is an example with a file from another CAD system:
|
Here is an example with a file from another CAD system:
|
||||||
|
|
||||||
```kcl
|
```kcl
|
||||||
import "tests/inputs/cube.step" as cube
|
import "tests/inputs/cube.step"
|
||||||
|
|
||||||
cube
|
cube
|
||||||
|> translate(x=10)
|
|> translate(x=10)
|
||||||
|
@ -12,7 +12,7 @@ reduce(
|
|||||||
@array: [any],
|
@array: [any],
|
||||||
initial: any,
|
initial: any,
|
||||||
f: fn(any, accum: any): any,
|
f: fn(any, accum: any): any,
|
||||||
): [any]
|
): any
|
||||||
```
|
```
|
||||||
|
|
||||||
Take a starting value. Then, for each element of an array, calculate the next value,
|
Take a starting value. Then, for each element of an array, calculate the next value,
|
||||||
@ -28,7 +28,7 @@ using the previous value and the element.
|
|||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`[any]`](/docs/kcl-std/types/std-types-any)
|
[`any`](/docs/kcl-std/types/std-types-any)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -11,7 +11,7 @@ Compute the length of the given leg.
|
|||||||
legLen(
|
legLen(
|
||||||
hypotenuse: number(Length),
|
hypotenuse: number(Length),
|
||||||
leg: number(Length),
|
leg: number(Length),
|
||||||
): number(deg)
|
): number(Length)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ legLen(
|
|||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`number(deg)`](/docs/kcl-std/types/std-types-number) - A number.
|
[`number(Length)`](/docs/kcl-std/types/std-types-number) - A number.
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -17,7 +17,7 @@ revolve(
|
|||||||
bidirectionalAngle?: number(Angle),
|
bidirectionalAngle?: number(Angle),
|
||||||
tagStart?: tag,
|
tagStart?: tag,
|
||||||
tagEnd?: tag,
|
tagEnd?: tag,
|
||||||
): Solid
|
): [Solid; 1+]
|
||||||
```
|
```
|
||||||
|
|
||||||
This, like extrude, is able to create a 3-dimensional solid from a
|
This, like extrude, is able to create a 3-dimensional solid from a
|
||||||
@ -46,7 +46,7 @@ revolved around the same axis.
|
|||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
|
||||||
[`Solid`](/docs/kcl-std/types/std-types-Solid) - A solid is a collection of extruded surfaces.
|
[`[Solid; 1+]`](/docs/kcl-std/types/std-types-Solid)
|
||||||
|
|
||||||
|
|
||||||
### Examples
|
### Examples
|
||||||
|
@ -14,8 +14,6 @@ mirror2d(
|
|||||||
): Sketch
|
): Sketch
|
||||||
```
|
```
|
||||||
|
|
||||||
Only works on unclosed sketches for now.
|
|
||||||
|
|
||||||
Mirror occurs around a local sketch axis rather than a global axis.
|
Mirror occurs around a local sketch axis rather than a global axis.
|
||||||
|
|
||||||
### Arguments
|
### Arguments
|
||||||
|
@ -12,8 +12,8 @@ patternCircular2d(
|
|||||||
@sketchSet: [Sketch],
|
@sketchSet: [Sketch],
|
||||||
instances: number,
|
instances: number,
|
||||||
center: Point2d,
|
center: Point2d,
|
||||||
arcDegrees: number,
|
arcDegrees?: number,
|
||||||
rotateDuplicates: bool,
|
rotateDuplicates?: bool,
|
||||||
useOriginal?: bool,
|
useOriginal?: bool,
|
||||||
): [Sketch]
|
): [Sketch]
|
||||||
```
|
```
|
||||||
@ -27,8 +27,8 @@ patternCircular2d(
|
|||||||
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes |
|
| `sketchSet` | [`[Sketch]`](/docs/kcl-std/types/std-types-Sketch) | Which sketch(es) to pattern | Yes |
|
||||||
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
| `center` | [`Point2d`](/docs/kcl-std/types/std-types-Point2d) | The center about which to make the pattern. This is a 2D vector. | Yes |
|
||||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No |
|
||||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | Yes |
|
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No |
|
||||||
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
@ -13,8 +13,8 @@ patternCircular3d(
|
|||||||
instances: number,
|
instances: number,
|
||||||
axis: [number],
|
axis: [number],
|
||||||
center: Point3d,
|
center: Point3d,
|
||||||
arcDegrees: number,
|
arcDegrees?: number,
|
||||||
rotateDuplicates: bool,
|
rotateDuplicates?: bool,
|
||||||
useOriginal?: bool,
|
useOriginal?: bool,
|
||||||
): [Solid]
|
): [Solid]
|
||||||
```
|
```
|
||||||
@ -29,8 +29,8 @@ patternCircular3d(
|
|||||||
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
| `instances` | [`number`](/docs/kcl-std/types/std-types-number) | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
|
||||||
| `axis` | [`[number]`](/docs/kcl-std/types/std-types-number) | The axis around which to make the pattern. This is a 3D vector | Yes |
|
| `axis` | [`[number]`](/docs/kcl-std/types/std-types-number) | The axis around which to make the pattern. This is a 3D vector | Yes |
|
||||||
| `center` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | The center about which to make the pattern. This is a 3D vector. | Yes |
|
| `center` | [`Point3d`](/docs/kcl-std/types/std-types-Point3d) | The center about which to make the pattern. This is a 3D vector. | Yes |
|
||||||
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
|
| `arcDegrees` | [`number`](/docs/kcl-std/types/std-types-number) | The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360. | No |
|
||||||
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. | Yes |
|
| `rotateDuplicates` | [`bool`](/docs/kcl-std/types/std-types-bool) | Whether or not to rotate the duplicates as they are copied. Defaults to true. | No |
|
||||||
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
| `useOriginal` | [`bool`](/docs/kcl-std/types/std-types-bool) | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
|
||||||
|
|
||||||
### Returns
|
### Returns
|
||||||
|
@ -127364,9 +127364,10 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||||
"title": "double",
|
"title": "Nullable_double",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double",
|
"format": "double",
|
||||||
|
"nullable": true,
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Sketch": {
|
"Sketch": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -128959,9 +128960,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": false,
|
||||||
"includeInSnippet": true,
|
"description": "The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360.",
|
||||||
"description": "The arc angle (in degrees) to place the repetitions. Must be greater than 0.",
|
|
||||||
"labelRequired": true
|
"labelRequired": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -128969,8 +128969,9 @@
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||||
"title": "Boolean",
|
"title": "Nullable_Boolean",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"nullable": true,
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Sketch": {
|
"Sketch": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -130563,9 +130564,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": false,
|
||||||
"includeInSnippet": true,
|
"description": "Whether or not to rotate the duplicates as they are copied. Defaults to true.",
|
||||||
"description": "Whether or not to rotate the duplicates as they are copied.",
|
|
||||||
"labelRequired": true
|
"labelRequired": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -140234,9 +140234,10 @@
|
|||||||
"type": "number",
|
"type": "number",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||||
"title": "double",
|
"title": "Nullable_double",
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"format": "double",
|
"format": "double",
|
||||||
|
"nullable": true,
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Solid": {
|
"Solid": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -141829,9 +141830,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": false,
|
||||||
"includeInSnippet": true,
|
"description": "The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360.",
|
||||||
"description": "The arc angle (in degrees) to place the repetitions. Must be greater than 0.",
|
|
||||||
"labelRequired": true
|
"labelRequired": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -141839,8 +141839,9 @@
|
|||||||
"type": "bool",
|
"type": "bool",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
"$schema": "https://spec.openapis.org/oas/3.0/schema/2019-04-02#/definitions/Schema",
|
||||||
"title": "Boolean",
|
"title": "Nullable_Boolean",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
|
"nullable": true,
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"Solid": {
|
"Solid": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -143433,9 +143434,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": true,
|
"required": false,
|
||||||
"includeInSnippet": true,
|
"description": "Whether or not to rotate the duplicates as they are copied. Defaults to true.",
|
||||||
"description": "Whether or not to rotate the duplicates as they are copied.",
|
|
||||||
"labelRequired": true
|
"labelRequired": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -156307,7 +156307,11 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
[
|
[
|
||||||
"exampleSketch = startSketchOn(XZ)\n |> circle(center = [0, 0], radius = 1)\n |> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)\n\nexample = extrude(exampleSketch, length = 1)",
|
"// / Pattern using a named axis.\n\n\nexampleSketch = startSketchOn(XZ)\n |> circle(center = [0, 0], radius = 1)\n |> patternLinear2d(axis = X, instances = 7, distance = 4)\n\nexample = extrude(exampleSketch, length = 1)",
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"// / Pattern using a raw axis.\n\n\nexampleSketch = startSketchOn(XZ)\n |> circle(center = [0, 0], radius = 1)\n |> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)\n\nexample = extrude(exampleSketch, length = 1)",
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -165963,7 +165967,11 @@
|
|||||||
"deprecated": false,
|
"deprecated": false,
|
||||||
"examples": [
|
"examples": [
|
||||||
[
|
[
|
||||||
"exampleSketch = startSketchOn(XZ)\n |> startProfile(at = [0, 0])\n |> line(end = [0, 2])\n |> line(end = [3, 1])\n |> line(end = [0, -4])\n |> close()\n\nexample = extrude(exampleSketch, length = 1)\n |> patternLinear3d(axis = [1, 0, 1], instances = 7, distance = 6)",
|
"// / Pattern using a named axis.\n\n\nexampleSketch = startSketchOn(XZ)\n |> startProfile(at = [0, 0])\n |> line(end = [0, 2])\n |> line(end = [3, 1])\n |> line(end = [0, -4])\n |> close()\n\nexample = extrude(exampleSketch, length = 1)\n |> patternLinear3d(axis = X, instances = 7, distance = 6)",
|
||||||
|
false
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"// / Pattern using a raw axis.\n\n\nexampleSketch = startSketchOn(XZ)\n |> startProfile(at = [0, 0])\n |> line(end = [0, 2])\n |> line(end = [3, 1])\n |> line(end = [0, -4])\n |> close()\n\nexample = extrude(exampleSketch, length = 1)\n |> patternLinear3d(axis = [1, 0, 1], instances = 7, distance = 6)",
|
||||||
false
|
false
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
|
@ -36,7 +36,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// Click the line of code for xLine.
|
// Click the line of code for xLine.
|
||||||
await page.getByText(`close()`).click() // TODO remove this and reinstate // await topHorzSegmentClick()
|
await page.getByText(`startProfile(at = [-10, -10])`).click()
|
||||||
|
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
@ -45,10 +45,10 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -56,7 +56,7 @@ test.describe('Command bar tests', () => {
|
|||||||
stage: 'review',
|
stage: 'review',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 segment',
|
Profiles: '1 profile',
|
||||||
Length: '5',
|
Length: '5',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -286,7 +286,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await cmdBar.cmdOptions.getByText('Extrude').click()
|
await cmdBar.cmdOptions.getByText('Extrude').click()
|
||||||
|
|
||||||
// Assert that we're on the selection step
|
// Assert that we're on the selection step
|
||||||
await expect(page.getByRole('button', { name: 'sketches' })).toBeDisabled()
|
await expect(page.getByRole('button', { name: 'Profiles' })).toBeDisabled()
|
||||||
// Select a face
|
// Select a face
|
||||||
await page.mouse.move(700, 200)
|
await page.mouse.move(700, 200)
|
||||||
await page.mouse.click(700, 200)
|
await page.mouse.click(700, 200)
|
||||||
@ -399,7 +399,6 @@ test.describe('Command bar tests', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await page.goto(page.url() + targetURL)
|
await page.goto(page.url() + targetURL)
|
||||||
expect(page.url()).toContain(targetURL)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Submit the command`, async () => {
|
await test.step(`Submit the command`, async () => {
|
||||||
@ -410,7 +409,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: '',
|
Method: '',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'method',
|
highlightedHeaderArg: 'method',
|
||||||
@ -421,7 +420,7 @@ test.describe('Command bar tests', () => {
|
|||||||
commandName: 'Import file from URL',
|
commandName: 'Import file from URL',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'New project',
|
Method: 'New project',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -463,7 +462,6 @@ test.describe('Command bar tests', () => {
|
|||||||
sortBy: 'last-modified-desc',
|
sortBy: 'last-modified-desc',
|
||||||
})
|
})
|
||||||
await page.goto(page.url() + targetURL)
|
await page.goto(page.url() + targetURL)
|
||||||
expect(page.url()).toContain(targetURL)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step(`Submit the command`, async () => {
|
await test.step(`Submit the command`, async () => {
|
||||||
@ -474,7 +472,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: '',
|
Method: '',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'method',
|
highlightedHeaderArg: 'method',
|
||||||
@ -487,7 +485,7 @@ test.describe('Command bar tests', () => {
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'Existing project',
|
Method: 'Existing project',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
ProjectName: '',
|
ProjectName: '',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
@ -500,7 +498,7 @@ test.describe('Command bar tests', () => {
|
|||||||
headerArguments: {
|
headerArguments: {
|
||||||
Method: 'Existing project',
|
Method: 'Existing project',
|
||||||
ProjectName: 'testProjectDir',
|
ProjectName: 'testProjectDir',
|
||||||
Name: 'test',
|
Name: 'main.kcl',
|
||||||
Code: '1 line',
|
Code: '1 line',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -510,7 +508,7 @@ test.describe('Command bar tests', () => {
|
|||||||
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
await test.step(`Ensure we created the project and are in the modeling scene`, async () => {
|
||||||
await editor.expectEditor.toContain('extrusionDistance = 12')
|
await editor.expectEditor.toContain('extrusionDistance = 12')
|
||||||
await toolbar.openPane('files')
|
await toolbar.openPane('files')
|
||||||
await toolbar.expectFileTreeState(['main.kcl', 'test.kcl'])
|
await toolbar.expectFileTreeState(['main-1.kcl', 'main.kcl'])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -661,4 +659,27 @@ c = 3 + a`
|
|||||||
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
`a = 5b = a * amyParameter001 = ${newValue}c = 3 + a`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Command palette can be opened via query parameter', async ({
|
||||||
|
page,
|
||||||
|
homePage,
|
||||||
|
cmdBar,
|
||||||
|
}) => {
|
||||||
|
await page.goto(`${page.url()}/?cmd=app.theme&groupId=settings`)
|
||||||
|
await homePage.expectState({
|
||||||
|
projectCards: [],
|
||||||
|
sortBy: 'last-modified-desc',
|
||||||
|
})
|
||||||
|
await cmdBar.expectState({
|
||||||
|
stage: 'arguments',
|
||||||
|
commandName: 'Settings · app · theme',
|
||||||
|
currentArgKey: 'value',
|
||||||
|
currentArgValue: '',
|
||||||
|
headerArguments: {
|
||||||
|
Level: 'user',
|
||||||
|
Value: '',
|
||||||
|
},
|
||||||
|
highlightedHeaderArg: 'value',
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1138,7 +1138,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
@ -1148,7 +1148,7 @@ sketch001 = startSketchOn(XZ)
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Length: '5',
|
Length: '5',
|
||||||
},
|
},
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
|
@ -146,9 +146,7 @@ export class CmdBarFixture {
|
|||||||
await this.cmdBarOpenBtn.click()
|
await this.cmdBarOpenBtn.click()
|
||||||
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
await expect(this.page.getByPlaceholder('Search commands')).toBeVisible()
|
||||||
if (selectCmd === 'promptToEdit') {
|
if (selectCmd === 'promptToEdit') {
|
||||||
const promptEditCommand = this.page.getByText(
|
const promptEditCommand = this.selectOption({ name: 'Text-to-CAD Edit' })
|
||||||
'Use Zoo AI to edit your parts and code.'
|
|
||||||
)
|
|
||||||
await expect(promptEditCommand.first()).toBeVisible()
|
await expect(promptEditCommand.first()).toBeVisible()
|
||||||
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
await promptEditCommand.first().scrollIntoViewIfNeeded()
|
||||||
await promptEditCommand.first().click()
|
await promptEditCommand.first().click()
|
||||||
|
@ -121,11 +121,13 @@ export class HomePageFixture {
|
|||||||
await projectCard.click()
|
await projectCard.click()
|
||||||
}
|
}
|
||||||
|
|
||||||
goToModelingScene = async (name: string = 'testDefault') => {
|
/** Returns the project name in case caller has used the default and needs it */
|
||||||
|
goToModelingScene = async (name = 'testDefault') => {
|
||||||
// On web this is a no-op. There is no project view.
|
// On web this is a no-op. There is no project view.
|
||||||
if (process.env.PLATFORM === 'web') return
|
if (process.env.PLATFORM === 'web') return ''
|
||||||
|
|
||||||
await this.createAndGoToProject(name)
|
await this.createAndGoToProject(name)
|
||||||
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
isNativeFileMenuCreated = async () => {
|
isNativeFileMenuCreated = async () => {
|
||||||
|
@ -252,7 +252,7 @@ test.describe(
|
|||||||
tronApp,
|
tronApp,
|
||||||
'Edit.Modify with Zoo Text-To-CAD'
|
'Edit.Modify with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||||
})
|
})
|
||||||
await test.step('Modeling.Edit.Edit parameter', async () => {
|
await test.step('Modeling.Edit.Edit parameter', async () => {
|
||||||
await page.waitForTimeout(250)
|
await page.waitForTimeout(250)
|
||||||
@ -518,7 +518,7 @@ test.describe(
|
|||||||
'Design.Create with Zoo Text-To-CAD'
|
'Design.Create with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.toBeOpened()
|
await cmdBar.toBeOpened()
|
||||||
await cmdBar.expectCommandName('Text to CAD')
|
await cmdBar.expectCommandName('Text-to-CAD Create')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
await test.step('Modeling.Design.Modify with Zoo Text-To-CAD', async () => {
|
||||||
@ -528,7 +528,7 @@ test.describe(
|
|||||||
'Design.Modify with Zoo Text-To-CAD'
|
'Design.Modify with Zoo Text-To-CAD'
|
||||||
)
|
)
|
||||||
await cmdBar.toBeOpened()
|
await cmdBar.toBeOpened()
|
||||||
await cmdBar.expectCommandName('Prompt-to-edit')
|
await cmdBar.expectCommandName('Text-to-CAD Edit')
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Modeling.Help.KCL code samples', async () => {
|
await test.step('Modeling.Help.KCL code samples', async () => {
|
||||||
|
@ -78,8 +78,8 @@ test.describe('Point-and-click tests', () => {
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '', Length: '' },
|
headerArguments: { Profiles: '', Length: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -87,7 +87,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: { Sketches: '1 face', Length: '' },
|
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
@ -98,7 +98,7 @@ test.describe('Point-and-click tests', () => {
|
|||||||
|
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '1 face', Length: '5' },
|
headerArguments: { Profiles: '1 profile', Length: '5' },
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -1634,15 +1634,15 @@ sketch002 = startSketchOn(plane001)
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '' },
|
headerArguments: { Profiles: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await selectSketches()
|
await selectSketches()
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '2 faces' },
|
headerArguments: { Profiles: '2 profiles' },
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.submit()
|
await cmdBar.submit()
|
||||||
@ -1658,14 +1658,14 @@ sketch002 = startSketchOn(plane001)
|
|||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: { Sketches: '' },
|
headerArguments: { Profiles: '' },
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: { Sketches: '2 faces' },
|
headerArguments: { Profiles: '2 profiles' },
|
||||||
commandName: 'Loft',
|
commandName: 'Loft',
|
||||||
})
|
})
|
||||||
await cmdBar.submit()
|
await cmdBar.submit()
|
||||||
@ -1830,10 +1830,10 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await clickOnSketch1()
|
await clickOnSketch1()
|
||||||
@ -1844,7 +1844,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1857,7 +1857,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1867,7 +1867,7 @@ sketch002 = startSketchOn(XZ)
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -1968,10 +1968,10 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
})
|
})
|
||||||
await editor.scrollToText(circleCode)
|
await editor.scrollToText(circleCode)
|
||||||
@ -1983,7 +1983,7 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -1997,7 +1997,7 @@ profile001 = ${circleCode}`
|
|||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '',
|
Path: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'path',
|
highlightedHeaderArg: 'path',
|
||||||
@ -2007,7 +2007,7 @@ profile001 = ${circleCode}`
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '1 face',
|
Profiles: '1 profile',
|
||||||
Path: '1 helix',
|
Path: '1 helix',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -3691,7 +3691,7 @@ tag=$rectangleSegmentC002,
|
|||||||
await scene.settled(cmdBar)
|
await scene.settled(cmdBar)
|
||||||
|
|
||||||
// select line of code
|
// select line of code
|
||||||
const codeToSelection = `segAng(rectangleSegmentA002) - 90,`
|
const codeToSelection = `startProfile(at = [-66.77, 84.81])`
|
||||||
// revolve
|
// revolve
|
||||||
await editor.scrollToText(codeToSelection)
|
await editor.scrollToText(codeToSelection)
|
||||||
await page.getByText(codeToSelection).click()
|
await page.getByText(codeToSelection).click()
|
||||||
@ -4634,10 +4634,10 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4646,7 +4646,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Length: '',
|
Length: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
@ -4657,7 +4657,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Length: '1',
|
Length: '1',
|
||||||
},
|
},
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
@ -4728,11 +4728,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Sweep',
|
commandName: 'Sweep',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4741,7 +4741,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'path',
|
currentArgKey: 'path',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Path: '',
|
Path: '',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -4754,7 +4754,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
Path: '1 segment',
|
Path: '1 segment',
|
||||||
Sectional: '',
|
Sectional: '',
|
||||||
},
|
},
|
||||||
@ -4825,11 +4825,11 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'sketches',
|
currentArgKey: 'sketches',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '',
|
Profiles: '',
|
||||||
AxisOrEdge: '',
|
AxisOrEdge: '',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
},
|
},
|
||||||
highlightedHeaderArg: 'sketches',
|
highlightedHeaderArg: 'Profiles',
|
||||||
commandName: 'Revolve',
|
commandName: 'Revolve',
|
||||||
})
|
})
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
@ -4838,7 +4838,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'axisOrEdge',
|
currentArgKey: 'axisOrEdge',
|
||||||
currentArgValue: '',
|
currentArgValue: '',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: '',
|
AxisOrEdge: '',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
},
|
},
|
||||||
@ -4854,7 +4854,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
currentArgKey: 'angle',
|
currentArgKey: 'angle',
|
||||||
currentArgValue: '360',
|
currentArgValue: '360',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: 'Edge',
|
AxisOrEdge: 'Edge',
|
||||||
Edge: '1 segment',
|
Edge: '1 segment',
|
||||||
Angle: '',
|
Angle: '',
|
||||||
@ -4867,7 +4867,7 @@ path001 = startProfile(sketch001, at = [0, 0])
|
|||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'review',
|
stage: 'review',
|
||||||
headerArguments: {
|
headerArguments: {
|
||||||
Sketches: '2 faces',
|
Profiles: '2 profiles',
|
||||||
AxisOrEdge: 'Edge',
|
AxisOrEdge: 'Edge',
|
||||||
Edge: '1 segment',
|
Edge: '1 segment',
|
||||||
Angle: '180',
|
Angle: '180',
|
||||||
|
@ -995,8 +995,8 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
|||||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||||
await u.closeDebugPanel()
|
await u.closeDebugPanel()
|
||||||
|
|
||||||
// click "line(end = [1.32, 0.38])"
|
// click profile in code
|
||||||
await page.getByText(`line(end = [1.32, 0.38])`).click()
|
await page.getByText(`startProfile(at = [-0.45, 0.87])`).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeEnabled(
|
||||||
{ timeout: 10_000 }
|
{ timeout: 10_000 }
|
||||||
@ -1014,14 +1014,14 @@ profile001 = startProfile(sketch001, at = [${roundOff(scale * 69.6)}, ${roundOff
|
|||||||
// click extrude
|
// click extrude
|
||||||
await toolbar.extrudeButton.click()
|
await toolbar.extrudeButton.click()
|
||||||
|
|
||||||
// sketch selection should already have been made. "Sketches: 1 face" only show up when the selection has been made already
|
// sketch selection should already have been made.
|
||||||
// otherwise the cmdbar would be waiting for a selection.
|
// otherwise the cmdbar would be waiting for a selection.
|
||||||
await cmdBar.progressCmdBar()
|
await cmdBar.progressCmdBar()
|
||||||
await cmdBar.expectState({
|
await cmdBar.expectState({
|
||||||
stage: 'arguments',
|
stage: 'arguments',
|
||||||
currentArgKey: 'length',
|
currentArgKey: 'length',
|
||||||
currentArgValue: '5',
|
currentArgValue: '5',
|
||||||
headerArguments: { Sketches: '1 segment', Length: '' },
|
headerArguments: { Profiles: '1 profile', Length: '' },
|
||||||
highlightedHeaderArg: 'length',
|
highlightedHeaderArg: 'length',
|
||||||
commandName: 'Extrude',
|
commandName: 'Extrude',
|
||||||
})
|
})
|
||||||
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 57 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 59 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 135 KiB |
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 134 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 68 KiB |
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 69 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
@ -4,9 +4,10 @@ import type { Page } from '@playwright/test'
|
|||||||
|
|
||||||
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
import { createProject, getUtils } from '@e2e/playwright/test-utils'
|
||||||
import { expect, test } from '@e2e/playwright/zoo-test'
|
import { expect, test } from '@e2e/playwright/zoo-test'
|
||||||
|
import type { CmdBarFixture } from '@e2e/playwright/fixtures/cmdBarFixture'
|
||||||
|
|
||||||
test.describe('Text-to-CAD tests', () => {
|
test.describe('Text-to-CAD tests', () => {
|
||||||
test('basic lego happy case', async ({ page, homePage }) => {
|
test('basic lego happy case', async ({ page, homePage, cmdBar }) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await test.step('Set up', async () => {
|
await test.step('Set up', async () => {
|
||||||
@ -15,7 +16,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
})
|
})
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -56,6 +61,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
test('success model, then ignore success toast, user can create new prompt from command bar', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -64,7 +70,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x6 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x6 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -82,7 +92,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
await expect(successToastMessage).toBeVisible({ timeout: 15000 })
|
||||||
|
|
||||||
// Can send a new prompt from the command bar.
|
// Can send a new prompt from the command bar.
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -100,6 +114,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('you can reject text-to-cad output and it does nothing', async ({
|
test('you can reject text-to-cad output and it does nothing', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -108,7 +123,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -141,6 +160,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can dismiss', async ({
|
test('sending a bad prompt fails, can dismiss', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -150,7 +170,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
const randomPrompt = `aslkdfja;` + Date.now() + `FFFFEIWJF`
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, randomPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
randomPrompt,
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -188,6 +212,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can start over from toast', async ({
|
test('sending a bad prompt fails, can start over from toast', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -197,7 +222,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
const badPrompt = 'akjsndladf lajbhflauweyfaaaljhr472iouafyvsssssss'
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -256,6 +281,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
test('sending a bad prompt fails, can ignore toast, can start over from command bar', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -265,7 +291,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
const badPrompt = 'akjsndladflajbhflauweyf15;'
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, badPrompt)
|
await sendPromptFromCommandBarAndSetExistingProject(page, badPrompt, cmdBar)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -292,7 +318,11 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
await expect(page.getByText(`Text-to-CAD failed`)).toBeVisible()
|
||||||
|
|
||||||
// They should be able to try again from the command bar.
|
// They should be able to try again from the command bar.
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -310,17 +340,40 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('ensure you can shift+enter in the prompt box', async ({
|
test('ensure you can shift+enter in the prompt box', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||||
|
|
||||||
await homePage.goToModelingScene()
|
const projectName = await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
const promptWithNewline = `a 2x4\nlego`
|
const promptWithNewline = `a 2x4\nlego`
|
||||||
|
|
||||||
await page.getByTestId('text-to-cad').click()
|
await test.step('Get to the prompt step to test', async () => {
|
||||||
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||||
|
|
||||||
|
await cmdBar.currentArgumentInput.fill('existing')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.currentArgumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Text-to-CAD Create',
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'prompt',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'prompt',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: projectName,
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Type the prompt.
|
// Type the prompt.
|
||||||
await page.keyboard.type('a 2x4')
|
await page.keyboard.type('a 2x4')
|
||||||
@ -354,6 +407,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('can do many at once and get many prompts back, and interact with many', async ({
|
test('can do many at once and get many prompts back, and interact with many', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
// Let this test run longer since we've seen it timeout.
|
// Let this test run longer since we've seen it timeout.
|
||||||
test.setTimeout(180_000)
|
test.setTimeout(180_000)
|
||||||
@ -365,11 +419,23 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x8 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x8 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x10 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'a 2x10 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
// Look out for the toast message
|
// Look out for the toast message
|
||||||
@ -440,6 +506,7 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
test('can do many at once with errors, clicking dismiss error does not dismiss all', async ({
|
||||||
page,
|
page,
|
||||||
homePage,
|
homePage,
|
||||||
|
cmdBar,
|
||||||
}) => {
|
}) => {
|
||||||
const u = await getUtils(page)
|
const u = await getUtils(page)
|
||||||
|
|
||||||
@ -448,11 +515,16 @@ test.describe('Text-to-CAD tests', () => {
|
|||||||
await homePage.goToModelingScene()
|
await homePage.goToModelingScene()
|
||||||
await u.waitForPageLoad()
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, 'a 2x4 lego')
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
|
||||||
await sendPromptFromCommandBarTriggeredByButton(
|
|
||||||
page,
|
page,
|
||||||
'alkjsdnlajshdbfjlhsbdf a;askjdnf'
|
'a 2x4 lego',
|
||||||
|
cmdBar
|
||||||
|
)
|
||||||
|
|
||||||
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
'alkjsdnlajshdbfjlhsbdf a;askjdnf',
|
||||||
|
cmdBar
|
||||||
)
|
)
|
||||||
|
|
||||||
// Find the toast.
|
// Find the toast.
|
||||||
@ -526,7 +598,9 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
const cmdSearchBar = page.getByPlaceholder('Search commands')
|
||||||
await expect(cmdSearchBar).toBeVisible()
|
await expect(cmdSearchBar).toBeVisible()
|
||||||
|
|
||||||
const textToCadCommand = page.getByText('Use the Zoo Text-to-CAD API')
|
const textToCadCommand = page.getByRole('option', {
|
||||||
|
name: 'Text-to-CAD Create',
|
||||||
|
})
|
||||||
await expect(textToCadCommand.first()).toBeVisible()
|
await expect(textToCadCommand.first()).toBeVisible()
|
||||||
// Click the Text-to-CAD command
|
// Click the Text-to-CAD command
|
||||||
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
await textToCadCommand.first().scrollIntoViewIfNeeded()
|
||||||
@ -544,29 +618,67 @@ async function _sendPromptFromCommandBar(page: Page, promptStr: string) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendPromptFromCommandBarTriggeredByButton(
|
async function sendPromptFromCommandBarAndSetExistingProject(
|
||||||
page: Page,
|
page: Page,
|
||||||
promptStr: string
|
promptStr: string,
|
||||||
|
cmdBar: CmdBarFixture,
|
||||||
|
projectName = 'testDefault'
|
||||||
) {
|
) {
|
||||||
await page.waitForTimeout(1000)
|
await page.waitForTimeout(1000)
|
||||||
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
await test.step(`Send prompt from command bar: ${promptStr}`, async () => {
|
||||||
await page.getByTestId('text-to-cad').click()
|
await cmdBar.openCmdBar()
|
||||||
|
await cmdBar.selectOption({ name: 'Text-to-CAD Create' }).click()
|
||||||
|
|
||||||
// Enter the prompt.
|
await cmdBar.expectState({
|
||||||
const prompt = page.getByRole('textbox', { name: 'Prompt' })
|
commandName: 'Text-to-CAD Create',
|
||||||
await expect(prompt.first()).toBeVisible()
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'method',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'method',
|
||||||
|
headerArguments: {
|
||||||
|
Method: '',
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill('existing')
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
// Type the prompt.
|
await cmdBar.expectState({
|
||||||
await page.keyboard.type(promptStr)
|
commandName: 'Text-to-CAD Create',
|
||||||
await page.waitForTimeout(200)
|
stage: 'arguments',
|
||||||
await page.keyboard.press('Enter')
|
currentArgKey: 'projectName',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'projectName',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: '',
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill(projectName)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
|
|
||||||
|
await cmdBar.expectState({
|
||||||
|
commandName: 'Text-to-CAD Create',
|
||||||
|
stage: 'arguments',
|
||||||
|
currentArgKey: 'prompt',
|
||||||
|
currentArgValue: '',
|
||||||
|
highlightedHeaderArg: 'prompt',
|
||||||
|
headerArguments: {
|
||||||
|
Method: 'Existing project',
|
||||||
|
ProjectName: projectName,
|
||||||
|
Prompt: '',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
await cmdBar.currentArgumentInput.fill(promptStr)
|
||||||
|
await cmdBar.progressCmdBar()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
test(
|
test(
|
||||||
'Text-to-CAD functionality',
|
'Text-to-CAD functionality',
|
||||||
{ tag: '@electron' },
|
{ tag: '@electron' },
|
||||||
async ({ context, page }, testInfo) => {
|
async ({ context, page, cmdBar }, testInfo) => {
|
||||||
const projectName = 'project-000'
|
const projectName = 'project-000'
|
||||||
const prompt = 'lego 2x4'
|
const prompt = 'lego 2x4'
|
||||||
const textToCadFileName = 'lego-2x4.kcl'
|
const textToCadFileName = 'lego-2x4.kcl'
|
||||||
@ -603,7 +715,12 @@ test(
|
|||||||
await openKclCodePanel()
|
await openKclCodePanel()
|
||||||
|
|
||||||
await test.step(`Test file creation`, async () => {
|
await test.step(`Test file creation`, async () => {
|
||||||
await sendPromptFromCommandBarTriggeredByButton(page, prompt)
|
await sendPromptFromCommandBarAndSetExistingProject(
|
||||||
|
page,
|
||||||
|
prompt,
|
||||||
|
cmdBar,
|
||||||
|
projectName
|
||||||
|
)
|
||||||
// File is considered created if it shows up in the Project Files pane
|
// File is considered created if it shows up in the Project Files pane
|
||||||
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
await expect(textToCadFileButton).toBeVisible({ timeout: 20_000 })
|
||||||
expect(fileExists()).toBeTruthy()
|
expect(fileExists()).toBeTruthy()
|
||||||
@ -773,12 +890,12 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
).toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -913,7 +1030,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
await page.getByRole('button', { name: 'Reject' }).click()
|
await page.getByRole('button', { name: 'Reject' }).click()
|
||||||
@ -961,7 +1078,7 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@ -1213,18 +1330,14 @@ test.describe('Mocked Text-to-CAD API tests', { tag: ['@skipWin'] }, () => {
|
|||||||
)
|
)
|
||||||
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
await expect(page.getByTestId('app-header-file-name')).toBeVisible()
|
||||||
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
await expect(page.getByTestId('app-header-file-name')).toContainText(
|
||||||
'2x2x2-cube.kcl'
|
'main.kcl'
|
||||||
)
|
)
|
||||||
|
|
||||||
// Check file is created
|
// Check file is created
|
||||||
await u.openFilePanel()
|
await u.openFilePanel()
|
||||||
await expect(
|
|
||||||
page.getByTestId('file-tree-item').getByText('2x2x2-cube.kcl')
|
|
||||||
).toBeVisible()
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByTestId('file-tree-item').getByText('main.kcl')
|
page.getByTestId('file-tree-item').getByText('main.kcl')
|
||||||
).not.toBeVisible()
|
).toBeVisible()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -49,12 +49,16 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
|||||||
[](countersunk-plate/main.kcl)
|
[](countersunk-plate/main.kcl)
|
||||||
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
|
#### [cpu-cooler](cpu-cooler/main.kcl) ([screenshot](screenshots/cpu-cooler.png))
|
||||||
[](cpu-cooler/main.kcl)
|
[](cpu-cooler/main.kcl)
|
||||||
|
#### [curtain-wall-anchor-plate](curtain-wall-anchor-plate/main.kcl) ([screenshot](screenshots/curtain-wall-anchor-plate.png))
|
||||||
|
[](curtain-wall-anchor-plate/main.kcl)
|
||||||
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
|
#### [cycloidal-gear](cycloidal-gear/main.kcl) ([screenshot](screenshots/cycloidal-gear.png))
|
||||||
[](cycloidal-gear/main.kcl)
|
[](cycloidal-gear/main.kcl)
|
||||||
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
#### [dodecahedron](dodecahedron/main.kcl) ([screenshot](screenshots/dodecahedron.png))
|
||||||
[](dodecahedron/main.kcl)
|
[](dodecahedron/main.kcl)
|
||||||
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
#### [enclosure](enclosure/main.kcl) ([screenshot](screenshots/enclosure.png))
|
||||||
[](enclosure/main.kcl)
|
[](enclosure/main.kcl)
|
||||||
|
#### [engine-valve](engine-valve/main.kcl) ([screenshot](screenshots/engine-valve.png))
|
||||||
|
[](engine-valve/main.kcl)
|
||||||
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
#### [exhaust-manifold](exhaust-manifold/main.kcl) ([screenshot](screenshots/exhaust-manifold.png))
|
||||||
[](exhaust-manifold/main.kcl)
|
[](exhaust-manifold/main.kcl)
|
||||||
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
|
#### [flange](flange/main.kcl) ([screenshot](screenshots/flange.png))
|
||||||
@ -103,6 +107,8 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
|||||||
[](mounting-plate/main.kcl)
|
[](mounting-plate/main.kcl)
|
||||||
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
#### [multi-axis-robot](multi-axis-robot/main.kcl) ([screenshot](screenshots/multi-axis-robot.png))
|
||||||
[](multi-axis-robot/main.kcl)
|
[](multi-axis-robot/main.kcl)
|
||||||
|
#### [pdu-faceplate](pdu-faceplate/main.kcl) ([screenshot](screenshots/pdu-faceplate.png))
|
||||||
|
[](pdu-faceplate/main.kcl)
|
||||||
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
|
#### [pillow-block-bearing](pillow-block-bearing/main.kcl) ([screenshot](screenshots/pillow-block-bearing.png))
|
||||||
[](pillow-block-bearing/main.kcl)
|
[](pillow-block-bearing/main.kcl)
|
||||||
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
|
#### [pipe](pipe/main.kcl) ([screenshot](screenshots/pipe.png))
|
||||||
@ -119,16 +125,24 @@ When you submit a PR to add or modify KCL samples, images will be generated and
|
|||||||
[](router-template-cross-bar/main.kcl)
|
[](router-template-cross-bar/main.kcl)
|
||||||
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
|
#### [router-template-slate](router-template-slate/main.kcl) ([screenshot](screenshots/router-template-slate.png))
|
||||||
[](router-template-slate/main.kcl)
|
[](router-template-slate/main.kcl)
|
||||||
|
#### [sash-window](sash-window/main.kcl) ([screenshot](screenshots/sash-window.png))
|
||||||
|
[](sash-window/main.kcl)
|
||||||
#### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png))
|
#### [sheet-metal-bracket](sheet-metal-bracket/main.kcl) ([screenshot](screenshots/sheet-metal-bracket.png))
|
||||||
[](sheet-metal-bracket/main.kcl)
|
[](sheet-metal-bracket/main.kcl)
|
||||||
|
#### [shepherds-hook-bolt](shepherds-hook-bolt/main.kcl) ([screenshot](screenshots/shepherds-hook-bolt.png))
|
||||||
|
[](shepherds-hook-bolt/main.kcl)
|
||||||
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
|
#### [socket-head-cap-screw](socket-head-cap-screw/main.kcl) ([screenshot](screenshots/socket-head-cap-screw.png))
|
||||||
[](socket-head-cap-screw/main.kcl)
|
[](socket-head-cap-screw/main.kcl)
|
||||||
|
#### [spinning-highrise-tower](spinning-highrise-tower/main.kcl) ([screenshot](screenshots/spinning-highrise-tower.png))
|
||||||
|
[](spinning-highrise-tower/main.kcl)
|
||||||
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
|
#### [spur-gear](spur-gear/main.kcl) ([screenshot](screenshots/spur-gear.png))
|
||||||
[](spur-gear/main.kcl)
|
[](spur-gear/main.kcl)
|
||||||
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
|
#### [spur-reduction-gearset](spur-reduction-gearset/main.kcl) ([screenshot](screenshots/spur-reduction-gearset.png))
|
||||||
[](spur-reduction-gearset/main.kcl)
|
[](spur-reduction-gearset/main.kcl)
|
||||||
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
|
#### [surgical-drill-guide](surgical-drill-guide/main.kcl) ([screenshot](screenshots/surgical-drill-guide.png))
|
||||||
[](surgical-drill-guide/main.kcl)
|
[](surgical-drill-guide/main.kcl)
|
||||||
|
#### [thermal-block-insert](thermal-block-insert/main.kcl) ([screenshot](screenshots/thermal-block-insert.png))
|
||||||
|
[](thermal-block-insert/main.kcl)
|
||||||
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
|
#### [tooling-nest-block](tooling-nest-block/main.kcl) ([screenshot](screenshots/tooling-nest-block.png))
|
||||||
[](tooling-nest-block/main.kcl)
|
[](tooling-nest-block/main.kcl)
|
||||||
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
|
#### [utility-sink](utility-sink/main.kcl) ([screenshot](screenshots/utility-sink.png))
|
||||||
|
155
public/kcl-samples/curtain-wall-anchor-plate/main.kcl
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Curtain Wall Anchor Plate
|
||||||
|
// A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening
|
||||||
|
|
||||||
|
// Set units in millimeters (mm)
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define parameters
|
||||||
|
slabPlateBaseLength = 300
|
||||||
|
slabPlateHookLength = 80
|
||||||
|
slabPlateWidth = 200
|
||||||
|
slabPlateThickness = 8
|
||||||
|
offsetSlabRail = 200
|
||||||
|
|
||||||
|
// Generate L-shaped anchor profile with base and hook flange
|
||||||
|
// Includes fillets at internal and external corners for strength and safety
|
||||||
|
fn lProfileFn(lengthBase, lengthHook, width, thickness) {
|
||||||
|
profilePlane = startSketchOn(offsetPlane(XZ, offset = -width / 2))
|
||||||
|
profileShape = startProfile(profilePlane, at = [0, 0])
|
||||||
|
|> yLine(length = lengthHook, tag = $hookOutside)
|
||||||
|
|> xLine(length = thickness)
|
||||||
|
|> yLine(length = thickness - lengthHook, tag = $hookInside)
|
||||||
|
|> xLine(length = lengthBase - thickness, tag = $baseInside)
|
||||||
|
|> yLine(length = -thickness)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $baseOutside)
|
||||||
|
|> close()
|
||||||
|
profileBody = extrude(profileShape, length = width)
|
||||||
|
|> fillet(
|
||||||
|
radius = thickness,
|
||||||
|
tags = [
|
||||||
|
getCommonEdge(faces = [baseInside, hookInside])
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> fillet(
|
||||||
|
radius = thickness * 2,
|
||||||
|
tags = [
|
||||||
|
getCommonEdge(faces = [baseOutside, hookOutside])
|
||||||
|
],
|
||||||
|
)
|
||||||
|
return profileBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a hexagonal shape used for bolt and nut heads
|
||||||
|
fn hexagonFn(plane, radius) {
|
||||||
|
shape = startProfile(plane, at = [-radius, 0])
|
||||||
|
|> angledLine(angle = 60, length = radius)
|
||||||
|
|> xLine(length = radius)
|
||||||
|
|> angledLine(angle = -60, length = radius)
|
||||||
|
|> angledLine(angle = -120, length = radius)
|
||||||
|
|> xLine(length = -radius)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
return shape
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build a bolt with a hexagonal head and cylindrical shaft
|
||||||
|
fn boltFn(diameter, length) {
|
||||||
|
boltHeadPlane = startSketchOn(XY)
|
||||||
|
boltHeadShape = hexagonFn(plane = boltHeadPlane, radius = diameter)
|
||||||
|
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||||
|
boltPlane = startSketchOn(boltHeadBody, face = START)
|
||||||
|
boltShape = circle(boltPlane, center = [0, 0], radius = diameter / 2)
|
||||||
|
boltBody = extrude(boltShape, length = length)
|
||||||
|
return boltBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct a bolt assembly with base plate and hex nut
|
||||||
|
// Assembles all parts for realistic anchor simulation
|
||||||
|
fn boltWithPlateAndNutFn(diameter, length, gap) {
|
||||||
|
plateSide = diameter * 3
|
||||||
|
plateplane = startSketchOn(offsetPlane(XY, offset = -gap))
|
||||||
|
plateShape = startProfile(plateplane, at = [-plateSide / 2, -plateSide / 2])
|
||||||
|
|> yLine(length = plateSide)
|
||||||
|
|> xLine(length = plateSide)
|
||||||
|
|> yLine(length = -plateSide)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
plateBody = extrude(plateShape, length = -diameter * 0.3)
|
||||||
|
nutPlane = startSketchOn(plateBody, face = START)
|
||||||
|
boltHeadShape = hexagonFn(plane = nutPlane, radius = 12)
|
||||||
|
boltHeadBody = extrude(boltHeadShape, length = diameter * 0.7)
|
||||||
|
boltBody = boltFn(diameter = diameter, length = gap + diameter + 3)
|
||||||
|
mergedBody = union([boltHeadBody, boltBody])
|
||||||
|
return mergedBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the plate geometry with a vertical hook for slab attachment
|
||||||
|
slabPlate = lProfileFn(
|
||||||
|
lengthBase = slabPlateBaseLength,
|
||||||
|
lengthHook = slabPlateHookLength,
|
||||||
|
width = slabPlateWidth,
|
||||||
|
thickness = slabPlateThickness,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define oblong holes for bolts, allowing positional adjustment
|
||||||
|
wideHoleWidth = 12
|
||||||
|
wideHoleLength = 60
|
||||||
|
wideHoleOffset = 30
|
||||||
|
|
||||||
|
// Two slots mirrored across the plate width
|
||||||
|
wideHolePlane = startSketchOn(XY)
|
||||||
|
wideHoleShape = startProfile(
|
||||||
|
wideHolePlane,
|
||||||
|
at = [
|
||||||
|
-(wideHoleLength - wideHoleWidth) / 2,
|
||||||
|
wideHoleWidth / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> xLine(length = wideHoleLength - wideHoleWidth)
|
||||||
|
|> tangentialArc(endAbsolute = [
|
||||||
|
(wideHoleLength - wideHoleWidth) / 2,
|
||||||
|
-wideHoleWidth / 2
|
||||||
|
])
|
||||||
|
|> xLine(length = wideHoleWidth - wideHoleLength)
|
||||||
|
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = offsetSlabRail,
|
||||||
|
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||||
|
z = -1,
|
||||||
|
)
|
||||||
|
wideHoleVoidLeft = extrude(wideHoleShape, length = slabPlateThickness + 2)
|
||||||
|
wideHoleVoidRight = clone(wideHoleVoidLeft)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = slabPlateWidth - (wideHoleOffset * 2),
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cut the holes into the anchor plate body
|
||||||
|
slabPlatePunchOne = subtract([slabPlate], tools = [wideHoleVoidLeft])
|
||||||
|
slabPlatePunchTwo = subtract([slabPlatePunchOne], tools = [wideHoleVoidRight])
|
||||||
|
|
||||||
|
// Add two bolt assemblies into the oblong slots
|
||||||
|
// Properly rotated and spaced to match anchor hole layout
|
||||||
|
slabPlateBolts = boltWithPlateAndNutFn(diameter = 10, length = 20, gap = slabPlateThickness + 5)
|
||||||
|
|> rotate(
|
||||||
|
%,
|
||||||
|
roll = 180,
|
||||||
|
pitch = 0,
|
||||||
|
yaw = 0,
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = offsetSlabRail,
|
||||||
|
y = wideHoleOffset - (slabPlateWidth / 2),
|
||||||
|
z = 5,
|
||||||
|
)
|
||||||
|
|> patternLinear3d(
|
||||||
|
%,
|
||||||
|
instances = 2,
|
||||||
|
distance = slabPlateWidth - (wideHoleOffset * 2),
|
||||||
|
axis = [0, -1, 0],
|
||||||
|
)
|
79
public/kcl-samples/engine-valve/main.kcl
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// Engine Valve
|
||||||
|
// A mechanical valve used in internal combustion engines to control intake or exhaust flow
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define parameters
|
||||||
|
valveDiameter = 30
|
||||||
|
valveLength = 120
|
||||||
|
valveHeadLength = valveDiameter * 1.0
|
||||||
|
valveHeadThickness = 3
|
||||||
|
stemDiameter = 6
|
||||||
|
stemHeadLength = 9
|
||||||
|
stemLength = valveLength - valveHeadLength - stemHeadLength
|
||||||
|
|
||||||
|
// Create the valve head
|
||||||
|
valveRadius = valveDiameter / 2
|
||||||
|
valveHeadPlane = startSketchOn(XZ)
|
||||||
|
valveHeadShape = startProfile(valveHeadPlane, at = [-0.01, valveHeadLength])
|
||||||
|
|> xLine(length = 0.01 - (stemDiameter / 2))
|
||||||
|
|> line(endAbsolute = [0.01 - (stemDiameter / 2), valveRadius])
|
||||||
|
|> tangentialArc(endAbsolute = [-0.8 * valveRadius, valveHeadThickness], tag = $seg01)
|
||||||
|
|> tangentialArc(endAbsolute = [-valveRadius, 0])
|
||||||
|
|> xLine(length = 0.3 * valveRadius)
|
||||||
|
|> arc(
|
||||||
|
interiorAbsolute = [
|
||||||
|
-0.34 * valveRadius,
|
||||||
|
0.08 * valveRadius
|
||||||
|
],
|
||||||
|
endAbsolute = [
|
||||||
|
-0.02 * valveRadius,
|
||||||
|
0.11 * valveRadius
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
valveHead = revolve(valveHeadShape, angle = 360, axis = Y)
|
||||||
|
|
||||||
|
// Create the valve stem
|
||||||
|
valveStemSketch = startSketchOn(offsetPlane(XY, offset = valveHeadLength))
|
||||||
|
|> circle(center = [0, 0], radius = stemDiameter / 2)
|
||||||
|
|> extrude(length = stemLength - valveHeadLength - stemHeadLength)
|
||||||
|
|
||||||
|
// Create the valve stem end
|
||||||
|
stepLength = stemHeadLength / 10
|
||||||
|
step1 = startSketchOn(valveStemSketch, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||||
|
|> extrude(%, length = stepLength * 2)
|
||||||
|
step2 = startSketchOn(step1, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||||
|
|> extrude(%, length = stepLength)
|
||||||
|
step3 = startSketchOn(step2, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||||
|
|> extrude(%, length = stepLength)
|
||||||
|
step4 = startSketchOn(step3, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||||
|
|> extrude(%, length = stepLength)
|
||||||
|
step5 = startSketchOn(step4, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.9)
|
||||||
|
|> extrude(%, length = stepLength)
|
||||||
|
step6 = startSketchOn(step5, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = stemDiameter / 2 * 0.8)
|
||||||
|
|> extrude(%, length = stepLength)
|
||||||
|
step7 = startSketchOn(step6, face = END)
|
||||||
|
|> circle(
|
||||||
|
%,
|
||||||
|
center = [0, 0],
|
||||||
|
radius = stemDiameter / 2 * 0.9,
|
||||||
|
tag = $seg02,
|
||||||
|
)
|
||||||
|
|> extrude(%, length = stepLength * 3, tagEnd = $capEnd001)
|
||||||
|
|> chamfer(
|
||||||
|
length = 0.5,
|
||||||
|
tags = [
|
||||||
|
getCommonEdge(faces = [seg02, capEnd001])
|
||||||
|
],
|
||||||
|
)
|
@ -147,6 +147,16 @@
|
|||||||
"removable-sticker.kcl"
|
"removable-sticker.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "curtain-wall-anchor-plate/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Curtain Wall Anchor Plate",
|
||||||
|
"description": "A structural steel L-plate used to anchor curtain wall systems to concrete slabs, with elongated holes for adjustability and bolts with nuts and base plates for secure fastening",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
|
||||||
@ -177,6 +187,16 @@
|
|||||||
"main.kcl"
|
"main.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "engine-valve/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Engine Valve",
|
||||||
|
"description": "A mechanical valve used in internal combustion engines to control intake or exhaust flow",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
|
||||||
@ -422,6 +442,16 @@
|
|||||||
"robot-rotating-base.kcl"
|
"robot-rotating-base.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "pdu-faceplate/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Power Distribution Unit (PDU) faceplate with European plug sockets and switch",
|
||||||
|
"description": "Designed for standard 19-inch rack systems with 1U height and 8 sockets",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "pillow-block-bearing/main.kcl",
|
||||||
@ -512,6 +542,16 @@
|
|||||||
"main.kcl"
|
"main.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "sash-window/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Sash Window",
|
||||||
|
"description": "A traditional wooden sash window with two vertically sliding panels and a central locking mechanism",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "sheet-metal-bracket/main.kcl",
|
||||||
@ -522,6 +562,16 @@
|
|||||||
"main.kcl"
|
"main.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "shepherds-hook-bolt/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Shepherd’s Hook Bolt",
|
||||||
|
"description": "A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "socket-head-cap-screw/main.kcl",
|
||||||
@ -532,6 +582,16 @@
|
|||||||
"main.kcl"
|
"main.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "spinning-highrise-tower/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Spinning Highrise Tower",
|
||||||
|
"description": "A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "spur-gear/main.kcl",
|
||||||
@ -562,6 +622,16 @@
|
|||||||
"main.kcl"
|
"main.kcl"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"file": "main.kcl",
|
||||||
|
"pathFromProjectDirectoryToFirstFile": "thermal-block-insert/main.kcl",
|
||||||
|
"multipleFiles": false,
|
||||||
|
"title": "Thermal Block Insert",
|
||||||
|
"description": "Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency",
|
||||||
|
"files": [
|
||||||
|
"main.kcl"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"file": "main.kcl",
|
"file": "main.kcl",
|
||||||
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",
|
"pathFromProjectDirectoryToFirstFile": "tooling-nest-block/main.kcl",
|
||||||
|
240
public/kcl-samples/pdu-faceplate/main.kcl
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
// Power Distribution Unit (PDU) faceplate with European plug sockets and switch
|
||||||
|
// Designed for standard 19-inch rack systems with 1U height and 8 sockets
|
||||||
|
|
||||||
|
// Set units in millimeters (mm)
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define the dimensions
|
||||||
|
// Width fits standard 19” rack, height is 1U, depth is variable
|
||||||
|
faceplateWidth = 482.6 // this is standardized to fit 19-inch racks)
|
||||||
|
faceplateHeight = 44.45 // usually 1U (44.45 mm), but can be 2U (88.9 mm) or more
|
||||||
|
faceplateDepth = 100 // varies by manufacturer, but commonly between 100 mm and 300 mm
|
||||||
|
|
||||||
|
|
||||||
|
// Define dimensions of side supports (width and thickness)
|
||||||
|
supportWidth = 50
|
||||||
|
supportThickness = 3
|
||||||
|
|
||||||
|
// Main body of the PDU faceplate with integrated rack mounting flanges
|
||||||
|
faceplateShape = startSketchOn(offsetPlane(XY, offset = -faceplateHeight / 2))
|
||||||
|
|> startProfile(%, at = [-faceplateWidth / 2 - supportWidth, 0])
|
||||||
|
|> yLine(length = supportThickness)
|
||||||
|
|> xLine(length = supportWidth)
|
||||||
|
|> yLine(length = faceplateDepth - supportThickness)
|
||||||
|
|> xLine(length = faceplateWidth)
|
||||||
|
|> yLine(length = supportThickness - faceplateDepth)
|
||||||
|
|> xLine(length = supportWidth)
|
||||||
|
|> yLine(length = -supportThickness)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg01)
|
||||||
|
|> close()
|
||||||
|
faceplateBody = extrude(faceplateShape, length = faceplateHeight)
|
||||||
|
faceplateFrontFace = startSketchOn(faceplateBody, face = seg01)
|
||||||
|
|
||||||
|
// Creates recessed volume within the faceplate for inserting modules
|
||||||
|
nestWall = 2
|
||||||
|
nestWidth = faceplateWidth - (nestWall * 2)
|
||||||
|
nestHeight = faceplateHeight - (nestWall * 2)
|
||||||
|
nestDepth = faceplateDepth - nestWall
|
||||||
|
nestShape = startProfile(faceplateFrontFace, at = [-nestWidth / 2, nestHeight / 2])
|
||||||
|
|> xLine(length = nestWidth)
|
||||||
|
|> yLine(length = -nestHeight)
|
||||||
|
|> xLine(length = -nestWidth)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
nestVoid = extrude(nestShape, length = -nestDepth)
|
||||||
|
|
||||||
|
// Spacer block on the left side, used to position components correctly
|
||||||
|
moduleHeight = nestHeight
|
||||||
|
moduleWidth = nestHeight
|
||||||
|
moduleDepth = nestHeight
|
||||||
|
|
||||||
|
leftSpacerWidth = moduleWidth * 1.5
|
||||||
|
leftSpacerPosition = leftSpacerWidth / 2 - (nestWidth / 2)
|
||||||
|
|
||||||
|
fn boxModuleFn(width) {
|
||||||
|
shape = startSketchOn(XZ)
|
||||||
|
|> startProfile(%, at = [-width / 2, moduleHeight / 2])
|
||||||
|
|> xLine(length = width)
|
||||||
|
|> yLine(length = -moduleHeight)
|
||||||
|
|> xLine(length = -width)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
body = extrude(shape, length = -moduleDepth)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
leftSpacerShape = boxModuleFn(width = leftSpacerWidth)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = leftSpacerPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module for power switch including front plate and red rocker button
|
||||||
|
switchPosition = leftSpacerPosition + leftSpacerWidth / 2 + moduleWidth / 2
|
||||||
|
swtichWidth = moduleWidth
|
||||||
|
|
||||||
|
// Switch Body
|
||||||
|
switchBody = boxModuleFn(width = moduleWidth)
|
||||||
|
|
||||||
|
// Switch Plate
|
||||||
|
swtichPlateWidth = 20
|
||||||
|
switchPlateHeight = 30
|
||||||
|
switchPlateThickness = 3
|
||||||
|
switchPlateShape = startSketchOn(switchBody, face = END)
|
||||||
|
|> startProfile(
|
||||||
|
%,
|
||||||
|
at = [
|
||||||
|
-swtichPlateWidth / 2,
|
||||||
|
-switchPlateHeight / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> yLine(length = switchPlateHeight)
|
||||||
|
|> xLine(length = swtichPlateWidth)
|
||||||
|
|> yLine(length = -switchPlateHeight)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
switchPlateBody = extrude(switchPlateShape, length = switchPlateThickness)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = switchPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Switch Button
|
||||||
|
switchButtonHeight = 26
|
||||||
|
swtichButtonWidth = 15
|
||||||
|
switchButtonShape = startSketchOn(offsetPlane(-YZ, offset = -swtichButtonWidth / 2))
|
||||||
|
|> startProfile(
|
||||||
|
%,
|
||||||
|
at = [
|
||||||
|
switchPlateThickness,
|
||||||
|
switchButtonHeight / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> line(end = [3, -1])
|
||||||
|
|> arc(interiorAbsolute = [6, 0], endAbsolute = [12, -9])
|
||||||
|
|> line(endAbsolute = [
|
||||||
|
switchPlateThickness,
|
||||||
|
-switchButtonHeight / 2
|
||||||
|
])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
switchButtonBody = extrude(switchButtonShape, length = swtichButtonWidth)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = switchPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|> appearance(%, color = "#ff0000")
|
||||||
|
|
||||||
|
// Spacer between switch and plug modules for layout alignment
|
||||||
|
secondSpacerWidth = moduleWidth / 2
|
||||||
|
secondSpacerPosition = switchPosition + swtichWidth / 2 + secondSpacerWidth / 2
|
||||||
|
secondSpacerBody = boxModuleFn(width = secondSpacerWidth)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = secondSpacerPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
// European power plug modules with circular sockets and two-pin holes
|
||||||
|
// 8 identical sockets, each with grounding notch and dual-pin recesses
|
||||||
|
powerPlugWidth = moduleWidth
|
||||||
|
powerPlugCount = 8
|
||||||
|
powerPlugOveralWidth = powerPlugWidth * powerPlugCount
|
||||||
|
firstPowerPlugPosition = secondSpacerPosition + secondSpacerWidth / 2 + powerPlugWidth / 2
|
||||||
|
lastPowerPlugPosition = firstPowerPlugPosition + powerPlugWidth * (powerPlugCount - 1)
|
||||||
|
powerPlugBody = boxModuleFn(width = powerPlugWidth)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = firstPowerPlugPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
plugShape = startSketchOn(powerPlugBody, face = END)
|
||||||
|
|> circle(%, center = [0, 0], radius = 17)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = firstPowerPlugPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
plugBody = extrude(plugShape, length = -20)
|
||||||
|
plugHoleDistance = 20
|
||||||
|
plugHoleShape = startSketchOn(plugBody, face = START)
|
||||||
|
|> circle(%, center = [-plugHoleDistance / 2, 0], radius = 3)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = firstPowerPlugPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = 2,
|
||||||
|
distance = plugHoleDistance,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
plugHoleBody = extrude(plugHoleShape, length = -5)
|
||||||
|
|> patternLinear3d(
|
||||||
|
%,
|
||||||
|
instances = powerPlugCount,
|
||||||
|
distance = powerPlugWidth,
|
||||||
|
axis = [1, 0, 0],
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rightmost spacer to fill in remaining horizontal space
|
||||||
|
rightSpacerWidth = nestWidth / 2 - lastPowerPlugPosition - (powerPlugWidth / 2)
|
||||||
|
rightSpacerPosition = lastPowerPlugPosition + powerPlugWidth / 2 + rightSpacerWidth / 2
|
||||||
|
rightSpacerBody = boxModuleFn(width = rightSpacerWidth)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = rightSpacerPosition,
|
||||||
|
y = 0,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Rack mounting holes on flanges, elongated for alignment flexibility
|
||||||
|
holeWidth = 25
|
||||||
|
holeDiameter = 5
|
||||||
|
holeStraightSegment = holeWidth - holeDiameter
|
||||||
|
holeVerticalDistance = faceplateHeight * 0.3
|
||||||
|
|
||||||
|
holeShapes = startProfile(
|
||||||
|
faceplateFrontFace,
|
||||||
|
at = [
|
||||||
|
-holeStraightSegment / 2,
|
||||||
|
holeDiameter / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> xLine(length = holeStraightSegment)
|
||||||
|
|> tangentialArc(endAbsolute = [
|
||||||
|
holeStraightSegment / 2,
|
||||||
|
-holeDiameter / 2
|
||||||
|
])
|
||||||
|
|> xLine(length = -holeStraightSegment)
|
||||||
|
|> tangentialArc(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = -faceplateWidth / 2 - (supportWidth / 2),
|
||||||
|
y = 0,
|
||||||
|
z = -holeVerticalDistance,
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = 3,
|
||||||
|
distance = holeVerticalDistance,
|
||||||
|
axis = [0, 1],
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = 2,
|
||||||
|
distance = faceplateWidth + supportWidth,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
holeVoid = extrude(holeShapes, length = -supportThickness)
|
214
public/kcl-samples/sash-window/main.kcl
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Sash Window
|
||||||
|
// A traditional wooden sash window with two vertically sliding panels and a central locking mechanism
|
||||||
|
|
||||||
|
// Set units in millimeters (mm)
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Window state: 0 for closed, 1 for open
|
||||||
|
windowState = 0
|
||||||
|
|
||||||
|
// Basic window dimensions
|
||||||
|
windowWidth = 500
|
||||||
|
windowHeight = 1000
|
||||||
|
|
||||||
|
// Frame thickness and depth
|
||||||
|
frameWidth = 30
|
||||||
|
frameDepth = 50
|
||||||
|
|
||||||
|
// Number of divisions per sash (horizontal and vertical)
|
||||||
|
sashOpeningCountHorizontal = 2
|
||||||
|
sashOpeningCountVertical = 1
|
||||||
|
|
||||||
|
// Derived dimensions
|
||||||
|
sashWidth = windowWidth - (frameWidth * 2)
|
||||||
|
sashHeight = (windowHeight - (frameWidth * 2)) / 2 + frameWidth / 2
|
||||||
|
sashDepth = frameDepth / 2 - 2
|
||||||
|
sashTravelDistance = sashHeight * windowState * 0.8
|
||||||
|
|
||||||
|
// Function to create panel with frame and openings
|
||||||
|
fn panelFn(plane, offset, width, height, depth, perimeter, divisionThickness, openingCountHorizontal, openingCountVertical) {
|
||||||
|
// Create panel base shape
|
||||||
|
panelPlane = startSketchOn(offsetPlane(XZ, offset = offset))
|
||||||
|
panelShape = startProfile(panelPlane, at = [-width / 2, -height / 2])
|
||||||
|
|> yLine(length = height)
|
||||||
|
|> xLine(length = width)
|
||||||
|
|> yLine(length = -height)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
panelBody = extrude(panelShape, length = depth)
|
||||||
|
|
||||||
|
// Create opening grid within the panel
|
||||||
|
voidAreaWidth = width - (perimeter * 2)
|
||||||
|
voidAreaHeight = height - (perimeter * 2)
|
||||||
|
|
||||||
|
divisionTotalThicknessHorizontal = divisionThickness * openingCountHorizontal - divisionThickness
|
||||||
|
divisionTotalThicknessVertical = divisionThickness * openingCountVertical - divisionThickness
|
||||||
|
voidWidth = (voidAreaWidth - divisionTotalThicknessHorizontal) / openingCountHorizontal
|
||||||
|
voidHeight = (voidAreaHeight - divisionTotalThicknessVertical) / openingCountVertical
|
||||||
|
|
||||||
|
voidStepHorizontal = voidWidth + divisionThickness
|
||||||
|
voidStepVertical = voidHeight + divisionThickness
|
||||||
|
voidPlane = startSketchOn(panelBody, face = END)
|
||||||
|
voidShape = startProfile(
|
||||||
|
voidPlane,
|
||||||
|
at = [
|
||||||
|
-voidAreaWidth / 2,
|
||||||
|
-voidAreaHeight / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> yLine(length = voidHeight)
|
||||||
|
|> xLine(length = voidWidth)
|
||||||
|
|> yLine(length = -voidHeight)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = openingCountHorizontal,
|
||||||
|
distance = voidStepHorizontal,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = openingCountVertical,
|
||||||
|
distance = voidStepVertical,
|
||||||
|
axis = [0, 1],
|
||||||
|
)
|
||||||
|
voidBody = extrude(voidShape, length = -depth)
|
||||||
|
|> appearance(color = "#a55e2c")
|
||||||
|
return panelBody
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create main window frame
|
||||||
|
frame = panelFn(
|
||||||
|
plane = XZ,
|
||||||
|
offset = -frameDepth / 2,
|
||||||
|
width = windowWidth,
|
||||||
|
height = windowHeight,
|
||||||
|
depth = frameDepth,
|
||||||
|
perimeter = frameWidth,
|
||||||
|
divisionThickness = 10,
|
||||||
|
openingCountHorizontal = 1,
|
||||||
|
openingCountVertical = 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create bottom sliding sash
|
||||||
|
bottomSash = panelFn(
|
||||||
|
plane = XZ,
|
||||||
|
offset = (frameDepth / 2 - sashDepth) / 2,
|
||||||
|
width = sashWidth,
|
||||||
|
height = sashHeight,
|
||||||
|
depth = sashDepth,
|
||||||
|
perimeter = frameWidth,
|
||||||
|
divisionThickness = 10,
|
||||||
|
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||||
|
openingCountVertical = sashOpeningCountVertical,
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
z = frameWidth / 2 - (sashHeight / 2),
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
z = sashTravelDistance,
|
||||||
|
) // open / close
|
||||||
|
|
||||||
|
// Latch mechanism on bottom sash
|
||||||
|
// Create latch plate
|
||||||
|
latchPlateWidth = 13
|
||||||
|
latchPlateLength = 30
|
||||||
|
latchPlateThickness = 1
|
||||||
|
|
||||||
|
latchPlatePlane = startSketchOn(offsetPlane(XY, offset = frameWidth / 2))
|
||||||
|
latchPlateShape = startProfile(
|
||||||
|
latchPlatePlane,
|
||||||
|
at = [
|
||||||
|
-latchPlateLength / 2,
|
||||||
|
-latchPlateWidth / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> yLine(length = latchPlateWidth)
|
||||||
|
|> xLine(length = latchPlateLength)
|
||||||
|
|> yLine(length = -latchPlateWidth)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
latchPlateBody = extrude(latchPlateShape, length = latchPlateThickness)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = -frameDepth / 4,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
z = sashTravelDistance,
|
||||||
|
) // open / close
|
||||||
|
|
||||||
|
// Create latch cylinder
|
||||||
|
latchCylinderHeight = 5
|
||||||
|
latchCylinderPlane = startSketchOn(offsetPlane(latchPlatePlane, offset = latchPlateThickness))
|
||||||
|
latchCylinderShape = startProfile(latchCylinderPlane, at = [40, -1])
|
||||||
|
|> xLine(length = -35)
|
||||||
|
|> arc(interiorAbsolute = [-5, 0], endAbsolute = [5, 1])
|
||||||
|
|> xLine(length = 35)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
latchCylinderBody = extrude(latchCylinderShape, length = latchCylinderHeight)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = -frameDepth / 4,
|
||||||
|
z = 0,
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
z = sashTravelDistance,
|
||||||
|
) // open / close
|
||||||
|
|> rotate(
|
||||||
|
%,
|
||||||
|
roll = 0,
|
||||||
|
pitch = 0,
|
||||||
|
yaw = -90 * windowState,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create top fixed sash
|
||||||
|
topSash = panelFn(
|
||||||
|
plane = XZ,
|
||||||
|
offset = -(frameDepth / 2 - sashDepth) / 2 - sashDepth,
|
||||||
|
width = sashWidth,
|
||||||
|
height = sashHeight,
|
||||||
|
depth = sashDepth,
|
||||||
|
perimeter = frameWidth,
|
||||||
|
divisionThickness = 10,
|
||||||
|
openingCountHorizontal = sashOpeningCountHorizontal,
|
||||||
|
openingCountVertical = sashOpeningCountVertical,
|
||||||
|
)
|
||||||
|
|> translate(
|
||||||
|
%,
|
||||||
|
x = 0,
|
||||||
|
y = 0,
|
||||||
|
z = sashHeight / 2 - (frameWidth / 2),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create latch nut on the top sash
|
||||||
|
latchNutPlane = startSketchOn(XZ)
|
||||||
|
latchNutShape = startProfile(
|
||||||
|
latchNutPlane,
|
||||||
|
at = [
|
||||||
|
-latchPlateLength / 2,
|
||||||
|
-latchPlateWidth / 2
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> yLine(length = latchPlateWidth)
|
||||||
|
|> xLine(length = latchPlateLength)
|
||||||
|
|> yLine(length = -latchPlateWidth)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
latchNutPlateBody = extrude(latchNutShape, length = latchPlateThickness)
|
BIN
public/kcl-samples/screenshots/curtain-wall-anchor-plate.png
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
public/kcl-samples/screenshots/engine-valve.png
Normal file
After Width: | Height: | Size: 61 KiB |
BIN
public/kcl-samples/screenshots/pdu-faceplate.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
public/kcl-samples/screenshots/sash-window.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
public/kcl-samples/screenshots/shepherds-hook-bolt.png
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
public/kcl-samples/screenshots/spinning-highrise-tower.png
Normal file
After Width: | Height: | Size: 178 KiB |
BIN
public/kcl-samples/screenshots/thermal-block-insert.png
Normal file
After Width: | Height: | Size: 78 KiB |
89
public/kcl-samples/shepherds-hook-bolt/main.kcl
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Shepherd’s Hook Bolt
|
||||||
|
// A bent bolt with a curved hook, typically used for hanging or anchoring loads. The threaded end allows secure attachment to surfaces or materials, while the curved hook resists pull-out under tension.
|
||||||
|
|
||||||
|
// Set units in millimeters (mm)
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define bolt geometry parameters
|
||||||
|
boltDiameter = 5
|
||||||
|
hookRadius = 12
|
||||||
|
shankLength = 5
|
||||||
|
threadedEndLength = 30
|
||||||
|
nutDistance = 20
|
||||||
|
hookStartAngle = 290
|
||||||
|
hookEndAngle = 150
|
||||||
|
|
||||||
|
approximatePitch = boltDiameter * 0.15
|
||||||
|
threadDepth = 0.6134 * approximatePitch
|
||||||
|
innerRadius = boltDiameter / 2 - threadDepth
|
||||||
|
boltNumberOfRevolutions = threadedEndLength / approximatePitch
|
||||||
|
|
||||||
|
// Helper values for computing geometry transitions between straight shaft and hook arc
|
||||||
|
hypotenuse = hookRadius / cos(hookStartAngle - 270)
|
||||||
|
side = sqrt(pow(hypotenuse, exp = 2) - pow(hookRadius, exp = 2))
|
||||||
|
shankOffset = hypotenuse + side
|
||||||
|
|
||||||
|
// Converts polar coordinates to cartesian points for drawing arcs
|
||||||
|
fn polarToCartesian(radius, angle) {
|
||||||
|
x = radius * cos(angle)
|
||||||
|
y = radius * sin(angle)
|
||||||
|
return [x, y]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the hook and shank profile path
|
||||||
|
// Includes straight segment and two connected arcs forming the hook
|
||||||
|
hookProfilePlane = startSketchOn(XZ)
|
||||||
|
hookProfileShape = startProfile(hookProfilePlane, at = [0, -shankOffset - shankLength])
|
||||||
|
|> line(endAbsolute = [0, -shankOffset])
|
||||||
|
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookStartAngle))
|
||||||
|
|> tangentialArc(endAbsolute = polarToCartesian(radius = hookRadius, angle = hookEndAngle), tag = $hook)
|
||||||
|
|
||||||
|
// Create the circular cross-section used for sweeping along the hook path
|
||||||
|
hookSectionPlane = offsetPlane(XY, offset = -shankOffset - shankLength)
|
||||||
|
hookSectionShape = circle(hookSectionPlane, center = [0, 0], radius = boltDiameter / 2)
|
||||||
|
|
||||||
|
// Sweep the section along the hook profile to form the main body of the hook bolt
|
||||||
|
hookBody = sweep(hookSectionShape, path = hookProfileShape, sectional = true)
|
||||||
|
|
||||||
|
// Add a cylindrical tip at the hook end
|
||||||
|
tipPlane = startSketchOn(hookBody, face = END)
|
||||||
|
tipShape = circle(
|
||||||
|
tipPlane,
|
||||||
|
center = [hookRadius, 0],
|
||||||
|
radius = boltDiameter / 2,
|
||||||
|
tag = $seg01,
|
||||||
|
)
|
||||||
|
tipBody = extrude(
|
||||||
|
tipShape,
|
||||||
|
length = hookRadius * 0.5,
|
||||||
|
tagStart = $startTag,
|
||||||
|
tagEnd = $capEnd001,
|
||||||
|
)
|
||||||
|
|> fillet(
|
||||||
|
radius = boltDiameter / 4,
|
||||||
|
tags = [
|
||||||
|
getCommonEdge(faces = [seg01, capEnd001])
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create the threaded end of the bolt
|
||||||
|
|
||||||
|
// Construct the triangular profile for thread cutting
|
||||||
|
boltThreadSectionPlane = startSketchOn(XZ)
|
||||||
|
boltThreadSectionShapeForRevolve = startProfile(
|
||||||
|
boltThreadSectionPlane,
|
||||||
|
at = [
|
||||||
|
innerRadius,
|
||||||
|
-shankOffset - shankLength - threadedEndLength
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> line(end = [threadDepth, approximatePitch / 2])
|
||||||
|
|> line(end = [-threadDepth, approximatePitch / 2])
|
||||||
|
|> patternLinear2d(axis = [0, 1], instances = boltNumberOfRevolutions, distance = approximatePitch)
|
||||||
|
|> xLine(length = -innerRadius * 0.9)
|
||||||
|
|> yLine(length = -threadedEndLength)
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close()
|
||||||
|
|
||||||
|
// Create a revolved solid representing the thread geometry by repeating and revolving the profile around the shaft
|
||||||
|
boltThreadRevolve = revolve(boltThreadSectionShapeForRevolve, angle = 360, axis = Y)
|
93
public/kcl-samples/spinning-highrise-tower/main.kcl
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Spinning Highrise Tower
|
||||||
|
// A conceptual high-rise tower with a central core and rotating floor slabs, demonstrating dynamic form through vertical repetition and transformation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@settings(defaultLengthUnit = m, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define global parameters for floor geometry and building layout
|
||||||
|
floorCount = 17
|
||||||
|
floorHeight = 5
|
||||||
|
slabWidth = 30
|
||||||
|
slabThickness = 0.5
|
||||||
|
rotationAngleStep = 5
|
||||||
|
handrailHeight = 1.2
|
||||||
|
handrailThickness = 0.3
|
||||||
|
balconyDepth = 3
|
||||||
|
|
||||||
|
// Calculate facade and core geometry from parameters
|
||||||
|
facadeWidth = slabWidth - (balconyDepth * 2)
|
||||||
|
facadeHeight = floorHeight - slabThickness
|
||||||
|
coreHeight = floorCount * floorHeight - slabThickness
|
||||||
|
frameSide = 0.1
|
||||||
|
windowTargetWidth = 6
|
||||||
|
windowTargetCount = facadeWidth / windowTargetWidth
|
||||||
|
windowCount = round(windowTargetCount)
|
||||||
|
windowWidth = facadeWidth / windowCount
|
||||||
|
|
||||||
|
// Helper function: Creates a box from a center plane with given width and height
|
||||||
|
fn boxFn(plane, width, height) {
|
||||||
|
shape = startSketchOn(plane)
|
||||||
|
|> startProfile(%, at = [-width / 2, -width / 2])
|
||||||
|
|> line(%, end = [0, width])
|
||||||
|
|> line(%, end = [width, 0])
|
||||||
|
|> line(%, end = [0, -width])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close(%)
|
||||||
|
body = extrude(shape, length = height)
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function: Defines transformation (translation and rotation) for each floor
|
||||||
|
fn transformFn(@i) {
|
||||||
|
return {
|
||||||
|
translate = [0, 0, i * floorHeight],
|
||||||
|
rotation = { angle = rotationAngleStep * i }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create building base
|
||||||
|
baseThickness = 0.2
|
||||||
|
baseSlab = boxFn(plane = XY, width = slabWidth, height = -baseThickness)
|
||||||
|
|> appearance(%, color = "#dbd7d2")
|
||||||
|
|
||||||
|
// Create ground platform beneath the base
|
||||||
|
goundSize = 50
|
||||||
|
groundBody = boxFn(plane = offsetPlane(XY, offset = -baseThickness), width = goundSize, height = -5)
|
||||||
|
|> appearance(%, color = "#3a3631")
|
||||||
|
|
||||||
|
// Create a single slab with handrail height to be reused with pattern
|
||||||
|
slabAndHandrailGeometry = boxFn(plane = offsetPlane(XY, offset = floorHeight - slabThickness), width = slabWidth, height = slabThickness + handrailHeight)
|
||||||
|
slabVoidStart = -slabWidth / 2 + handrailThickness
|
||||||
|
slabVoidWidth = slabWidth - (handrailThickness * 2)
|
||||||
|
slabVoidShape = startSketchOn(slabAndHandrailGeometry, face = END)
|
||||||
|
|> startProfile(%, at = [slabVoidStart, slabVoidStart])
|
||||||
|
|> line(%, end = [0, slabVoidWidth])
|
||||||
|
|> line(%, end = [slabVoidWidth, 0])
|
||||||
|
|> line(%, end = [0, -slabVoidWidth])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
// Generate and pattern slabs with voids across all floors
|
||||||
|
slabBody = extrude(slabVoidShape, length = -handrailHeight)
|
||||||
|
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||||
|
|> appearance(%, color = "#dbd7d2")
|
||||||
|
|
||||||
|
// Create structural core of the tower
|
||||||
|
coreLength = 10
|
||||||
|
coreWidth = 8
|
||||||
|
core = startSketchOn(XY)
|
||||||
|
|> startProfile(%, at = [-coreLength / 2, -coreWidth / 2])
|
||||||
|
|> line(%, end = [0, coreWidth])
|
||||||
|
|> line(%, end = [coreLength, 0])
|
||||||
|
|> line(%, end = [-0.22, -coreWidth])
|
||||||
|
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(%, length = coreHeight)
|
||||||
|
|
||||||
|
// Create facade panels for each floor
|
||||||
|
facadeStart = facadeWidth / 2
|
||||||
|
facadeGeometry = boxFn(plane = XY, width = facadeWidth, height = facadeHeight)
|
||||||
|
|> patternTransform(instances = floorCount, transform = transformFn)
|
||||||
|
|> appearance(%, color = "#151819")
|
61
public/kcl-samples/thermal-block-insert/main.kcl
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Thermal Block Insert
|
||||||
|
// Interlocking insulation insert for masonry walls, designed with a tongue-and-groove profile for modular alignment and thermal efficiency
|
||||||
|
|
||||||
|
// Set units in millimeters (mm)
|
||||||
|
@settings(defaultLengthUnit = mm, kclVersion = 1.0)
|
||||||
|
|
||||||
|
// Define overall dimensions of the insert block
|
||||||
|
insertLength = 400
|
||||||
|
insertHeight = 200
|
||||||
|
insertThickness = 50
|
||||||
|
|
||||||
|
// Define tongue-and-groove profile parameters for interlocking geometry
|
||||||
|
setbackFactor = 0.25 // spacing between tongues
|
||||||
|
tongueTargetCount = insertLength / 80
|
||||||
|
tongueCount = round(tongueTargetCount)
|
||||||
|
tongueLength = insertLength / (tongueCount * (1 + setbackFactor * 2) + 1)
|
||||||
|
tongueGap = tongueLength * setbackFactor * 2
|
||||||
|
tongueStep = tongueLength + tongueGap
|
||||||
|
tongueDepth = tongueLength * 0.5
|
||||||
|
tongueSetback = tongueLength * setbackFactor
|
||||||
|
|
||||||
|
// Function to create one side of the repeating tongue geometry along the block edge
|
||||||
|
fn tongueBlockFn() {
|
||||||
|
tongueSingleBlock = xLine(length = tongueLength)
|
||||||
|
|> line(end = [-tongueSetback, tongueDepth])
|
||||||
|
|> xLine(length = tongueLength)
|
||||||
|
|> line(end = [-tongueSetback, -tongueDepth])
|
||||||
|
|> patternLinear2d(
|
||||||
|
%,
|
||||||
|
instances = tongueCount,
|
||||||
|
distance = tongueStep,
|
||||||
|
axis = [1, 0],
|
||||||
|
)
|
||||||
|
|> xLine(length = tongueLength)
|
||||||
|
return tongueSingleBlock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create top-side profile with tongues
|
||||||
|
tongueShape = startSketchOn(XY)
|
||||||
|
|> startProfile(%, at = [-insertLength / 2, insertThickness / 2])
|
||||||
|
|> tongueBlockFn()
|
||||||
|
|> yLine(length = -insertThickness / 2)
|
||||||
|
|> xLine(length = -insertLength)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
// Create bottom-side profile with grooves (inverse of tongue)
|
||||||
|
grooveShape = startSketchOn(XY)
|
||||||
|
|> startProfile(
|
||||||
|
%,
|
||||||
|
at = [
|
||||||
|
-insertLength / 2,
|
||||||
|
-insertThickness / 2 - tongueDepth
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|> tongueBlockFn()
|
||||||
|
|> yLine(length = insertThickness / 2 + tongueDepth)
|
||||||
|
|> xLine(length = -insertLength)
|
||||||
|
|> close(%)
|
||||||
|
|
||||||
|
// Extrude both tongue and groove profiles to form the final thermal insert block
|
||||||
|
insertShape = extrude([tongueShape, grooveShape], length = insertHeight)
|
@ -995,7 +995,7 @@ mod tests {
|
|||||||
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
let snippet = pattern_fn.to_autocomplete_snippet().unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
snippet,
|
snippet,
|
||||||
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}], arcDegrees = ${8:3.14}, rotateDuplicates = ${9:false})"#
|
r#"patternCircular3d(${0:%}, instances = ${1:10}, axis = [${2:3.14}, ${3:3.14}, ${4:3.14}], center = [${5:3.14}, ${6:3.14}, ${7:3.14}])"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,18 +2,17 @@ use fnv::FnvHashMap;
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use kittycad_modeling_cmds::{
|
use kittycad_modeling_cmds::{
|
||||||
self as kcmc,
|
self as kcmc,
|
||||||
id::ModelingCmdId,
|
|
||||||
ok_response::OkModelingCmdResponse,
|
ok_response::OkModelingCmdResponse,
|
||||||
shared::ExtrusionFaceCapType,
|
shared::ExtrusionFaceCapType,
|
||||||
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
|
websocket::{BatchResponse, OkWebSocketResponseData, WebSocketResponse},
|
||||||
EnableSketchMode, ModelingCmd,
|
EnableSketchMode, ModelingCmd,
|
||||||
};
|
};
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{ser::SerializeSeq, Serialize};
|
use serde::{ser::SerializeSeq, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::KclErrorDetails,
|
errors::KclErrorDetails,
|
||||||
|
execution::ArtifactId,
|
||||||
parsing::ast::types::{Node, Program},
|
parsing::ast::types::{Node, Program},
|
||||||
KclError, NodePath, SourceRange,
|
KclError, NodePath, SourceRange,
|
||||||
};
|
};
|
||||||
@ -58,52 +57,6 @@ impl PartialOrd for ArtifactCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash, ts_rs::TS, JsonSchema)]
|
|
||||||
#[ts(export_to = "Artifact.ts")]
|
|
||||||
pub struct ArtifactId(Uuid);
|
|
||||||
|
|
||||||
impl ArtifactId {
|
|
||||||
pub fn new(uuid: Uuid) -> Self {
|
|
||||||
Self(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Uuid> for ArtifactId {
|
|
||||||
fn from(uuid: Uuid) -> Self {
|
|
||||||
Self::new(uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Uuid> for ArtifactId {
|
|
||||||
fn from(uuid: &Uuid) -> Self {
|
|
||||||
Self::new(*uuid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ArtifactId> for Uuid {
|
|
||||||
fn from(id: ArtifactId) -> Self {
|
|
||||||
id.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ArtifactId> for Uuid {
|
|
||||||
fn from(id: &ArtifactId) -> Self {
|
|
||||||
id.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ModelingCmdId> for ArtifactId {
|
|
||||||
fn from(id: ModelingCmdId) -> Self {
|
|
||||||
Self::new(*id.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ModelingCmdId> for ArtifactId {
|
|
||||||
fn from(id: &ModelingCmdId) -> Self {
|
|
||||||
Self::new(*id.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type DummyPathToNode = Vec<()>;
|
pub type DummyPathToNode = Vec<()>;
|
||||||
|
|
||||||
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
|
fn serialize_dummy_path_to_node<S>(_path_to_node: &DummyPathToNode, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
@ -308,6 +308,11 @@ impl ArtifactGraph {
|
|||||||
// a child of the line above it.
|
// a child of the line above it.
|
||||||
let label = label.unwrap_or("");
|
let label = label.unwrap_or("");
|
||||||
if code_ref.node_path.is_empty() {
|
if code_ref.node_path.is_empty() {
|
||||||
|
if !code_ref.range.module_id().is_top_level() {
|
||||||
|
// This is pointing to another module. We don't care about
|
||||||
|
// these. It's okay that it's missing, for now.
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
return writeln!(output, "{prefix} %% {label}Missing NodePath");
|
return writeln!(output, "{prefix} %% {label}Missing NodePath");
|
||||||
}
|
}
|
||||||
writeln!(output, "{prefix} %% {label}{:?}", code_ref.node_path.steps)
|
writeln!(output, "{prefix} %% {label}{:?}", code_ref.node_path.steps)
|
||||||
|
@ -3,7 +3,7 @@ use schemars::JsonSchema;
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use super::{types::NumericType, ArtifactId, KclValue};
|
use super::{types::NumericType, ArtifactId, KclValue};
|
||||||
use crate::{docs::StdLibFn, ModuleId, SourceRange};
|
use crate::{ModuleId, SourceRange};
|
||||||
|
|
||||||
/// A CAD modeling operation for display in the feature tree, AKA operations
|
/// A CAD modeling operation for display in the feature tree, AKA operations
|
||||||
/// timeline.
|
/// timeline.
|
||||||
@ -13,21 +13,6 @@ use crate::{docs::StdLibFn, ModuleId, SourceRange};
|
|||||||
pub enum Operation {
|
pub enum Operation {
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
StdLibCall {
|
StdLibCall {
|
||||||
/// The standard library function being called.
|
|
||||||
#[serde(flatten)]
|
|
||||||
std_lib_fn: StdLibFnRef,
|
|
||||||
/// The unlabeled argument to the function.
|
|
||||||
unlabeled_arg: Option<OpArg>,
|
|
||||||
/// The labeled keyword arguments to the function.
|
|
||||||
labeled_args: IndexMap<String, OpArg>,
|
|
||||||
/// The source range of the operation in the source code.
|
|
||||||
source_range: SourceRange,
|
|
||||||
/// True if the operation resulted in an error.
|
|
||||||
#[serde(default, skip_serializing_if = "is_false")]
|
|
||||||
is_error: bool,
|
|
||||||
},
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
KclStdLibCall {
|
|
||||||
name: String,
|
name: String,
|
||||||
/// The unlabeled argument to the function.
|
/// The unlabeled argument to the function.
|
||||||
unlabeled_arg: Option<OpArg>,
|
unlabeled_arg: Option<OpArg>,
|
||||||
@ -57,19 +42,12 @@ impl PartialOrd for Operation {
|
|||||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
Some(match (self, other) {
|
Some(match (self, other) {
|
||||||
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
(Self::StdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||||
(Self::StdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
|
||||||
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
(Self::StdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||||
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
(Self::StdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||||
(Self::KclStdLibCall { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
|
||||||
(Self::KclStdLibCall { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
|
||||||
(Self::KclStdLibCall { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
|
||||||
(Self::KclStdLibCall { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
|
||||||
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
(Self::GroupBegin { source_range: a, .. }, Self::GroupBegin { source_range: b, .. }) => a.cmp(b),
|
||||||
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
(Self::GroupBegin { source_range: a, .. }, Self::StdLibCall { source_range: b, .. }) => a.cmp(b),
|
||||||
(Self::GroupBegin { source_range: a, .. }, Self::KclStdLibCall { source_range: b, .. }) => a.cmp(b),
|
|
||||||
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
(Self::GroupBegin { .. }, Self::GroupEnd) => std::cmp::Ordering::Less,
|
||||||
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
|
(Self::GroupEnd, Self::StdLibCall { .. }) => std::cmp::Ordering::Greater,
|
||||||
(Self::GroupEnd, Self::KclStdLibCall { .. }) => std::cmp::Ordering::Greater,
|
|
||||||
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
|
(Self::GroupEnd, Self::GroupBegin { .. }) => std::cmp::Ordering::Greater,
|
||||||
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
|
(Self::GroupEnd, Self::GroupEnd) => std::cmp::Ordering::Equal,
|
||||||
})
|
})
|
||||||
@ -81,7 +59,6 @@ impl Operation {
|
|||||||
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
|
pub(crate) fn set_std_lib_call_is_error(&mut self, is_err: bool) {
|
||||||
match self {
|
match self {
|
||||||
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
Self::StdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
||||||
Self::KclStdLibCall { ref mut is_error, .. } => *is_error = is_err,
|
|
||||||
Self::GroupBegin { .. } | Self::GroupEnd => {}
|
Self::GroupBegin { .. } | Self::GroupEnd => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,6 +84,7 @@ pub enum Group {
|
|||||||
labeled_args: IndexMap<String, OpArg>,
|
labeled_args: IndexMap<String, OpArg>,
|
||||||
},
|
},
|
||||||
/// A whole-module import use.
|
/// A whole-module import use.
|
||||||
|
#[allow(dead_code)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
ModuleInstance {
|
ModuleInstance {
|
||||||
/// The name of the module being used.
|
/// The name of the module being used.
|
||||||
@ -135,54 +113,6 @@ impl OpArg {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A reference to a standard library function. This exists to implement
|
|
||||||
/// `PartialEq` and `Eq` for `Operation`.
|
|
||||||
#[derive(Debug, Clone, Serialize, ts_rs::TS, JsonSchema)]
|
|
||||||
#[ts(export_to = "Operation.ts")]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct StdLibFnRef {
|
|
||||||
// The following doc comment gets inlined into Operation, overriding what's
|
|
||||||
// there, in the generated TS. We serialize to its name. Renaming the
|
|
||||||
// field to "name" allows it to match the other variant.
|
|
||||||
/// The standard library function being called.
|
|
||||||
#[serde(
|
|
||||||
rename = "name",
|
|
||||||
serialize_with = "std_lib_fn_name",
|
|
||||||
deserialize_with = "std_lib_fn_from_name"
|
|
||||||
)]
|
|
||||||
#[ts(type = "string", rename = "name")]
|
|
||||||
pub std_lib_fn: Box<dyn StdLibFn>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StdLibFnRef {
|
|
||||||
pub(crate) fn new(std_lib_fn: Box<dyn StdLibFn>) -> Self {
|
|
||||||
Self { std_lib_fn }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Box<dyn StdLibFn>> for StdLibFnRef {
|
|
||||||
fn from(std_lib_fn: &Box<dyn StdLibFn>) -> Self {
|
|
||||||
Self::new(std_lib_fn.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for StdLibFnRef {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
self.std_lib_fn.name() == other.std_lib_fn.name()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for StdLibFnRef {}
|
|
||||||
|
|
||||||
#[expect(clippy::borrowed_box, reason = "Explicit Box is needed for serde")]
|
|
||||||
fn std_lib_fn_name<S>(std_lib_fn: &Box<dyn StdLibFn>, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: serde::Serializer,
|
|
||||||
{
|
|
||||||
let name = std_lib_fn.name();
|
|
||||||
serializer.serialize_str(&name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_false(b: &bool) -> bool {
|
fn is_false(b: &bool) -> bool {
|
||||||
!*b
|
!*b
|
||||||
}
|
}
|
||||||
|
@ -1,42 +1,32 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use async_recursion::async_recursion;
|
use async_recursion::async_recursion;
|
||||||
use indexmap::IndexMap;
|
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
annotations,
|
annotations,
|
||||||
|
fn_call::Args,
|
||||||
kcl_value::{FunctionSource, TypeDef},
|
kcl_value::{FunctionSource, TypeDef},
|
||||||
memory,
|
memory,
|
||||||
state::ModuleState,
|
state::ModuleState,
|
||||||
types::{NumericType, PrimitiveType, RuntimeType},
|
types::{NumericType, PrimitiveType, RuntimeType},
|
||||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, TagEngineInfo,
|
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, PlaneType, StatementKind,
|
||||||
TagIdentifier,
|
TagIdentifier,
|
||||||
},
|
},
|
||||||
fmt,
|
fmt,
|
||||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||||
parsing::ast::types::{
|
parsing::ast::types::{
|
||||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||||
BinaryPart, BodyItem, CallExpressionKw, Expr, FunctionExpression, IfExpression, ImportPath, ImportSelector,
|
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
|
||||||
ItemVisibility, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef,
|
LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
|
||||||
ObjectExpression, PipeExpression, Program, TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
||||||
},
|
},
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
std::{
|
std::args::TyF64,
|
||||||
args::{Arg, Args, KwArgs, TyF64},
|
|
||||||
FunctionKind,
|
|
||||||
},
|
|
||||||
CompilationError,
|
CompilationError,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum StatementKind<'a> {
|
|
||||||
Declaration { name: &'a str },
|
|
||||||
Expression,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> StatementKind<'a> {
|
impl<'a> StatementKind<'a> {
|
||||||
fn expect_name(&self) -> &'a str {
|
fn expect_name(&self) -> &'a str {
|
||||||
match self {
|
match self {
|
||||||
@ -594,7 +584,7 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_recursion]
|
#[async_recursion]
|
||||||
async fn execute_expr<'a: 'async_recursion>(
|
pub(super) async fn execute_expr<'a: 'async_recursion>(
|
||||||
&self,
|
&self,
|
||||||
init: &Expr,
|
init: &Expr,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
@ -787,7 +777,7 @@ impl BinaryPart {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Node<Name> {
|
impl Node<Name> {
|
||||||
async fn get_result<'a>(
|
pub(super) async fn get_result<'a>(
|
||||||
&self,
|
&self,
|
||||||
exec_state: &'a mut ExecState,
|
exec_state: &'a mut ExecState,
|
||||||
ctx: &ExecutorContext,
|
ctx: &ExecutorContext,
|
||||||
@ -1305,300 +1295,6 @@ async fn inner_execute_pipe_body(
|
|||||||
Ok(final_output)
|
Ok(final_output)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node<CallExpressionKw> {
|
|
||||||
#[async_recursion]
|
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
|
||||||
let fn_name = &self.callee;
|
|
||||||
let callsite: SourceRange = self.into();
|
|
||||||
|
|
||||||
// Build a hashmap from argument labels to the final evaluated values.
|
|
||||||
let mut fn_args = IndexMap::with_capacity(self.arguments.len());
|
|
||||||
let mut errors = Vec::new();
|
|
||||||
for arg_expr in &self.arguments {
|
|
||||||
let source_range = SourceRange::from(arg_expr.arg.clone());
|
|
||||||
let metadata = Metadata { source_range };
|
|
||||||
let value = ctx
|
|
||||||
.execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
let arg = Arg::new(value, source_range);
|
|
||||||
match &arg_expr.label {
|
|
||||||
Some(l) => {
|
|
||||||
fn_args.insert(l.name.clone(), arg);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
if let Some(id) = arg_expr.arg.ident_name() {
|
|
||||||
fn_args.insert(id.to_owned(), arg);
|
|
||||||
} else {
|
|
||||||
errors.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Evaluate the unlabeled first param, if any exists.
|
|
||||||
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
|
||||||
let source_range = SourceRange::from(arg_expr.clone());
|
|
||||||
let metadata = Metadata { source_range };
|
|
||||||
let value = ctx
|
|
||||||
.execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let label = arg_expr.ident_name().map(str::to_owned);
|
|
||||||
|
|
||||||
Some((label, Arg::new(value, source_range)))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut args = Args::new_kw(
|
|
||||||
KwArgs {
|
|
||||||
unlabeled,
|
|
||||||
labeled: fn_args,
|
|
||||||
errors,
|
|
||||||
},
|
|
||||||
self.into(),
|
|
||||||
ctx.clone(),
|
|
||||||
exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
|
|
||||||
);
|
|
||||||
match ctx.stdlib.get_either(fn_name) {
|
|
||||||
FunctionKind::Core(func) => {
|
|
||||||
if func.deprecated() {
|
|
||||||
exec_state.warn(CompilationError::err(
|
|
||||||
self.callee.as_source_range(),
|
|
||||||
format!("`{fn_name}` is deprecated, see the docs for a recommended replacement"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
let formals = func.args(false);
|
|
||||||
|
|
||||||
// If it's possible the input arg was meant to be labelled and we probably don't want to use
|
|
||||||
// it as the input arg, then treat it as labelled.
|
|
||||||
if let Some((Some(label), _)) = &args.kw_args.unlabeled {
|
|
||||||
if (formals.iter().all(|a| a.label_required) || exec_state.pipe_value().is_some())
|
|
||||||
&& formals.iter().any(|a| &a.name == label && a.label_required)
|
|
||||||
&& !args.kw_args.labeled.contains_key(label)
|
|
||||||
{
|
|
||||||
let (label, arg) = args.kw_args.unlabeled.take().unwrap();
|
|
||||||
args.kw_args.labeled.insert(label.unwrap(), arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
let op = if func.feature_tree_operation() {
|
|
||||||
let op_labeled_args = args
|
|
||||||
.kw_args
|
|
||||||
.labeled
|
|
||||||
.iter()
|
|
||||||
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
|
|
||||||
.collect();
|
|
||||||
Some(Operation::StdLibCall {
|
|
||||||
std_lib_fn: (&func).into(),
|
|
||||||
unlabeled_arg: args
|
|
||||||
.unlabeled_kw_arg_unconverted()
|
|
||||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
|
||||||
labeled_args: op_labeled_args,
|
|
||||||
source_range: callsite,
|
|
||||||
is_error: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
for (label, arg) in &args.kw_args.labeled {
|
|
||||||
match formals.iter().find(|p| &p.name == label) {
|
|
||||||
Some(p) => {
|
|
||||||
if !p.label_required {
|
|
||||||
exec_state.err(CompilationError::err(
|
|
||||||
arg.source_range,
|
|
||||||
format!(
|
|
||||||
"The function `{fn_name}` expects an unlabeled first parameter (`{label}`), but it is labelled in the call"
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
exec_state.err(CompilationError::err(
|
|
||||||
arg.source_range,
|
|
||||||
format!("`{label}` is not an argument of `{fn_name}`"),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt to call the function.
|
|
||||||
let mut return_value = {
|
|
||||||
// Don't early-return in this block.
|
|
||||||
exec_state.mut_stack().push_new_env_for_rust_call();
|
|
||||||
let result = func.std_lib_fn()(exec_state, args).await;
|
|
||||||
exec_state.mut_stack().pop_env();
|
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
if let Some(mut op) = op {
|
|
||||||
op.set_std_lib_call_is_error(result.is_err());
|
|
||||||
// Track call operation. We do this after the call
|
|
||||||
// since things like patternTransform may call user code
|
|
||||||
// before running, and we will likely want to use the
|
|
||||||
// return value. The call takes ownership of the args,
|
|
||||||
// so we need to build the op before the call.
|
|
||||||
exec_state.global.operations.push(op);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}?;
|
|
||||||
|
|
||||||
update_memory_for_tags_of_geometry(&mut return_value, exec_state)?;
|
|
||||||
|
|
||||||
Ok(return_value)
|
|
||||||
}
|
|
||||||
FunctionKind::UserDefined => {
|
|
||||||
// Clone the function so that we can use a mutable reference to
|
|
||||||
// exec_state.
|
|
||||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
|
||||||
|
|
||||||
let Some(fn_src) = func.as_fn() else {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
message: "cannot call this because it isn't a function".to_string(),
|
|
||||||
source_ranges: vec![callsite],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let return_value = fn_src
|
|
||||||
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
|
||||||
.await
|
|
||||||
.map_err(|e| {
|
|
||||||
// Add the call expression to the source ranges.
|
|
||||||
e.add_source_ranges(vec![callsite])
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let result = return_value.ok_or_else(move || {
|
|
||||||
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
|
||||||
// We want to send the source range of the original function.
|
|
||||||
if let KclValue::Function { meta, .. } = func {
|
|
||||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
|
||||||
};
|
|
||||||
KclError::UndefinedValue(KclErrorDetails {
|
|
||||||
message: format!("Result of user-defined function {} is undefined", fn_name),
|
|
||||||
source_ranges,
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
|
|
||||||
// If the return result is a sketch or solid, we want to update the
|
|
||||||
// memory for the tags of the group.
|
|
||||||
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
|
||||||
// and it works.
|
|
||||||
match result {
|
|
||||||
KclValue::Sketch { value } => {
|
|
||||||
for (name, tag) in value.tags.iter() {
|
|
||||||
if exec_state.stack().cur_frame_contains(name) {
|
|
||||||
exec_state.mut_stack().update(name, |v, _| {
|
|
||||||
v.as_mut_tag().unwrap().merge_info(tag);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
exec_state
|
|
||||||
.mut_stack()
|
|
||||||
.add(
|
|
||||||
name.to_owned(),
|
|
||||||
KclValue::TagIdentifier(Box::new(tag.clone())),
|
|
||||||
SourceRange::default(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KclValue::Solid { ref mut value } => {
|
|
||||||
for v in &value.value {
|
|
||||||
if let Some(tag) = v.get_tag() {
|
|
||||||
// Get the past tag and update it.
|
|
||||||
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
|
||||||
let mut t = t.clone();
|
|
||||||
let Some(info) = t.get_cur_info() else {
|
|
||||||
return Err(KclError::Internal(KclErrorDetails {
|
|
||||||
message: format!("Tag {} does not have path info", tag.name),
|
|
||||||
source_ranges: vec![tag.into()],
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut info = info.clone();
|
|
||||||
info.surface = Some(v.clone());
|
|
||||||
info.sketch = value.id;
|
|
||||||
t.info.push((exec_state.stack().current_epoch(), info));
|
|
||||||
t
|
|
||||||
} else {
|
|
||||||
// It's probably a fillet or a chamfer.
|
|
||||||
// Initialize it.
|
|
||||||
TagIdentifier {
|
|
||||||
value: tag.name.clone(),
|
|
||||||
info: vec![(
|
|
||||||
exec_state.stack().current_epoch(),
|
|
||||||
TagEngineInfo {
|
|
||||||
id: v.get_id(),
|
|
||||||
surface: Some(v.clone()),
|
|
||||||
path: None,
|
|
||||||
sketch: value.id,
|
|
||||||
},
|
|
||||||
)],
|
|
||||||
meta: vec![Metadata {
|
|
||||||
source_range: tag.clone().into(),
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// update the sketch tags.
|
|
||||||
value.sketch.merge_tags(Some(&tag_id).into_iter());
|
|
||||||
|
|
||||||
if exec_state.stack().cur_frame_contains(&tag.name) {
|
|
||||||
exec_state.mut_stack().update(&tag.name, |v, _| {
|
|
||||||
v.as_mut_tag().unwrap().merge_info(&tag_id);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
exec_state
|
|
||||||
.mut_stack()
|
|
||||||
.add(
|
|
||||||
tag.name.clone(),
|
|
||||||
KclValue::TagIdentifier(Box::new(tag_id)),
|
|
||||||
SourceRange::default(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the stale sketch in memory and update it.
|
|
||||||
if !value.sketch.tags.is_empty() {
|
|
||||||
let sketches_to_update: Vec<_> = exec_state
|
|
||||||
.stack()
|
|
||||||
.find_keys_in_current_env(|v| match v {
|
|
||||||
KclValue::Sketch { value: sk } => sk.original_id == value.sketch.original_id,
|
|
||||||
_ => false,
|
|
||||||
})
|
|
||||||
.cloned()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for k in sketches_to_update {
|
|
||||||
exec_state.mut_stack().update(&k, |v, _| {
|
|
||||||
let sketch = v.as_mut_sketch().unwrap();
|
|
||||||
sketch.merge_tags(value.sketch.tags.values());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
|
||||||
for v in value {
|
|
||||||
update_memory_for_tags_of_geometry(v, exec_state)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Node<TagDeclarator> {
|
impl Node<TagDeclarator> {
|
||||||
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||||
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
|
||||||
@ -1893,409 +1589,6 @@ impl Node<PipeExpression> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_check_params_kw(
|
|
||||||
fn_name: Option<&str>,
|
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
|
||||||
args: &mut KwArgs,
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
) -> Result<(), KclError> {
|
|
||||||
// If it's possible the input arg was meant to be labelled and we probably don't want to use
|
|
||||||
// it as the input arg, then treat it as labelled.
|
|
||||||
if let Some((Some(label), _)) = &args.unlabeled {
|
|
||||||
if (function_expression.params.iter().all(|p| p.labeled) || exec_state.pipe_value().is_some())
|
|
||||||
&& function_expression
|
|
||||||
.params
|
|
||||||
.iter()
|
|
||||||
.any(|p| &p.identifier.name == label && p.labeled)
|
|
||||||
&& !args.labeled.contains_key(label)
|
|
||||||
{
|
|
||||||
let (label, arg) = args.unlabeled.take().unwrap();
|
|
||||||
args.labeled.insert(label.unwrap(), arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (label, arg) in &mut args.labeled {
|
|
||||||
match function_expression.params.iter().find(|p| &p.identifier.name == label) {
|
|
||||||
Some(p) => {
|
|
||||||
if !p.labeled {
|
|
||||||
exec_state.err(CompilationError::err(
|
|
||||||
arg.source_range,
|
|
||||||
format!(
|
|
||||||
"{} expects an unlabeled first parameter (`{label}`), but it is labelled in the call",
|
|
||||||
fn_name
|
|
||||||
.map(|n| format!("The function `{}`", n))
|
|
||||||
.unwrap_or_else(|| "This function".to_owned()),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ty) = &p.type_ {
|
|
||||||
arg.value = arg
|
|
||||||
.value
|
|
||||||
.coerce(
|
|
||||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
|
|
||||||
exec_state,
|
|
||||||
)
|
|
||||||
.map_err(|e| {
|
|
||||||
let mut message = format!(
|
|
||||||
"{label} requires a value with type `{}`, but found {}",
|
|
||||||
ty.inner,
|
|
||||||
arg.value.human_friendly_type(),
|
|
||||||
);
|
|
||||||
if let Some(ty) = e.explicit_coercion {
|
|
||||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
|
||||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
|
||||||
}
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message,
|
|
||||||
source_ranges: vec![arg.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
exec_state.err(CompilationError::err(
|
|
||||||
arg.source_range,
|
|
||||||
format!(
|
|
||||||
"`{label}` is not an argument of {}",
|
|
||||||
fn_name
|
|
||||||
.map(|n| format!("`{}`", n))
|
|
||||||
.unwrap_or_else(|| "this function".to_owned()),
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !args.errors.is_empty() {
|
|
||||||
let actuals = args.labeled.keys();
|
|
||||||
let formals: Vec<_> = function_expression
|
|
||||||
.params
|
|
||||||
.iter()
|
|
||||||
.filter_map(|p| {
|
|
||||||
if !p.labeled {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let name = &p.identifier.name;
|
|
||||||
if actuals.clone().any(|a| a == name) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(format!("`{name}`"))
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let suggestion = if formals.is_empty() {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
format!("; suggested labels: {}", formals.join(", "))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut errors = args.errors.iter().map(|e| {
|
|
||||||
CompilationError::err(
|
|
||||||
e.source_range,
|
|
||||||
format!("This argument needs a label, but it doesn't have one{suggestion}"),
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let first = errors.next().unwrap();
|
|
||||||
errors.for_each(|e| exec_state.err(e));
|
|
||||||
|
|
||||||
return Err(KclError::Semantic(first.into()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(arg) = &mut args.unlabeled {
|
|
||||||
if let Some(p) = function_expression.params.iter().find(|p| !p.labeled) {
|
|
||||||
if let Some(ty) = &p.type_ {
|
|
||||||
arg.1.value = arg
|
|
||||||
.1
|
|
||||||
.value
|
|
||||||
.coerce(
|
|
||||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
|
|
||||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
|
||||||
exec_state,
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"The input argument of {} requires a value with type `{}`, but found {}",
|
|
||||||
fn_name
|
|
||||||
.map(|n| format!("`{}`", n))
|
|
||||||
.unwrap_or_else(|| "this function".to_owned()),
|
|
||||||
ty.inner,
|
|
||||||
arg.1.value.human_friendly_type()
|
|
||||||
),
|
|
||||||
source_ranges: vec![arg.1.source_range],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assign_args_to_params_kw(
|
|
||||||
fn_name: Option<&str>,
|
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
|
||||||
mut args: Args,
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
) -> Result<(), KclError> {
|
|
||||||
type_check_params_kw(fn_name, function_expression, &mut args.kw_args, exec_state)?;
|
|
||||||
|
|
||||||
// Add the arguments to the memory. A new call frame should have already
|
|
||||||
// been created.
|
|
||||||
let source_ranges = vec![function_expression.into()];
|
|
||||||
|
|
||||||
for param in function_expression.params.iter() {
|
|
||||||
if param.labeled {
|
|
||||||
let arg = args.kw_args.labeled.get(¶m.identifier.name);
|
|
||||||
let arg_val = match arg {
|
|
||||||
Some(arg) => arg.value.clone(),
|
|
||||||
None => match param.default_value {
|
|
||||||
Some(ref default_val) => KclValue::from_default_param(default_val.clone(), exec_state),
|
|
||||||
None => {
|
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges,
|
|
||||||
message: format!(
|
|
||||||
"This function requires a parameter {}, but you haven't passed it one.",
|
|
||||||
param.identifier.name
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
exec_state
|
|
||||||
.mut_stack()
|
|
||||||
.add(param.identifier.name.clone(), arg_val, (¶m.identifier).into())?;
|
|
||||||
} else {
|
|
||||||
let unlabelled = args.unlabeled_kw_arg_unconverted();
|
|
||||||
|
|
||||||
let Some(unlabeled) = unlabelled else {
|
|
||||||
let param_name = ¶m.identifier.name;
|
|
||||||
return Err(if args.kw_args.labeled.contains_key(param_name) {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges,
|
|
||||||
message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges,
|
|
||||||
message: "This function expects an unlabeled first parameter, but you haven't passed it one."
|
|
||||||
.to_owned(),
|
|
||||||
})
|
|
||||||
});
|
|
||||||
};
|
|
||||||
exec_state.mut_stack().add(
|
|
||||||
param.identifier.name.clone(),
|
|
||||||
unlabeled.value.clone(),
|
|
||||||
(¶m.identifier).into(),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn coerce_result_type(
|
|
||||||
result: Result<Option<KclValue>, KclError>,
|
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
) -> Result<Option<KclValue>, KclError> {
|
|
||||||
if let Ok(Some(val)) = result {
|
|
||||||
if let Some(ret_ty) = &function_expression.return_type {
|
|
||||||
let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
|
|
||||||
.map_err(|e| KclError::Semantic(e.into()))?;
|
|
||||||
let val = val.coerce(&ty, exec_state).map_err(|_| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"This function requires its result to be of type `{}`, but found {}",
|
|
||||||
ty.human_friendly_type(),
|
|
||||||
val.human_friendly_type(),
|
|
||||||
),
|
|
||||||
source_ranges: ret_ty.as_source_ranges(),
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
Ok(Some(val))
|
|
||||||
} else {
|
|
||||||
Ok(Some(val))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn call_user_defined_function_kw(
|
|
||||||
fn_name: Option<&str>,
|
|
||||||
args: Args,
|
|
||||||
memory: EnvironmentRef,
|
|
||||||
function_expression: NodeRef<'_, FunctionExpression>,
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
ctx: &ExecutorContext,
|
|
||||||
) -> Result<Option<KclValue>, KclError> {
|
|
||||||
// Create a new environment to execute the function body in so that local
|
|
||||||
// variables shadow variables in the parent scope. The new environment's
|
|
||||||
// parent should be the environment of the closure.
|
|
||||||
exec_state.mut_stack().push_new_env_for_call(memory);
|
|
||||||
if let Err(e) = assign_args_to_params_kw(fn_name, function_expression, args, exec_state) {
|
|
||||||
exec_state.mut_stack().pop_env();
|
|
||||||
return Err(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute the function body using the memory we just created.
|
|
||||||
let result = ctx
|
|
||||||
.exec_block(&function_expression.body, exec_state, BodyType::Block)
|
|
||||||
.await;
|
|
||||||
let mut result = result.map(|_| {
|
|
||||||
exec_state
|
|
||||||
.stack()
|
|
||||||
.get(memory::RETURN_NAME, function_expression.as_source_range())
|
|
||||||
.ok()
|
|
||||||
.cloned()
|
|
||||||
});
|
|
||||||
|
|
||||||
result = coerce_result_type(result, function_expression, exec_state);
|
|
||||||
|
|
||||||
// Restore the previous memory.
|
|
||||||
exec_state.mut_stack().pop_env();
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FunctionSource {
|
|
||||||
pub async fn call_kw(
|
|
||||||
&self,
|
|
||||||
fn_name: Option<String>,
|
|
||||||
exec_state: &mut ExecState,
|
|
||||||
ctx: &ExecutorContext,
|
|
||||||
mut args: Args,
|
|
||||||
callsite: SourceRange,
|
|
||||||
) -> Result<Option<KclValue>, KclError> {
|
|
||||||
match self {
|
|
||||||
FunctionSource::Std { func, ast, props } => {
|
|
||||||
if props.deprecated {
|
|
||||||
exec_state.warn(CompilationError::err(
|
|
||||||
callsite,
|
|
||||||
format!(
|
|
||||||
"`{}` is deprecated, see the docs for a recommended replacement",
|
|
||||||
props.name
|
|
||||||
),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
type_check_params_kw(Some(&props.name), ast, &mut args.kw_args, exec_state)?;
|
|
||||||
|
|
||||||
if let Some(arg) = &mut args.kw_args.unlabeled {
|
|
||||||
if let Some(p) = ast.params.iter().find(|p| !p.labeled) {
|
|
||||||
if let Some(ty) = &p.type_ {
|
|
||||||
arg.1.value = arg
|
|
||||||
.1
|
|
||||||
.value
|
|
||||||
.coerce(
|
|
||||||
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.1.source_range)
|
|
||||||
.map_err(|e| KclError::Semantic(e.into()))?,
|
|
||||||
exec_state,
|
|
||||||
)
|
|
||||||
.map_err(|_| {
|
|
||||||
KclError::Semantic(KclErrorDetails {
|
|
||||||
message: format!(
|
|
||||||
"The input argument of {} requires a value with type `{}`, but found {}",
|
|
||||||
props.name,
|
|
||||||
ty.inner,
|
|
||||||
arg.1.value.human_friendly_type(),
|
|
||||||
),
|
|
||||||
source_ranges: vec![callsite],
|
|
||||||
})
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
let op = if props.include_in_feature_tree {
|
|
||||||
let op_labeled_args = args
|
|
||||||
.kw_args
|
|
||||||
.labeled
|
|
||||||
.iter()
|
|
||||||
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
|
|
||||||
.collect();
|
|
||||||
Some(Operation::KclStdLibCall {
|
|
||||||
name: fn_name.unwrap_or_default(),
|
|
||||||
unlabeled_arg: args
|
|
||||||
.unlabeled_kw_arg_unconverted()
|
|
||||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
|
||||||
labeled_args: op_labeled_args,
|
|
||||||
source_range: callsite,
|
|
||||||
is_error: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
// Attempt to call the function.
|
|
||||||
exec_state.mut_stack().push_new_env_for_rust_call();
|
|
||||||
let mut result = {
|
|
||||||
// Don't early-return in this block.
|
|
||||||
let result = func(exec_state, args).await;
|
|
||||||
exec_state.mut_stack().pop_env();
|
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
if let Some(mut op) = op {
|
|
||||||
op.set_std_lib_call_is_error(result.is_err());
|
|
||||||
// Track call operation. We do this after the call
|
|
||||||
// since things like patternTransform may call user code
|
|
||||||
// before running, and we will likely want to use the
|
|
||||||
// return value. The call takes ownership of the args,
|
|
||||||
// so we need to build the op before the call.
|
|
||||||
exec_state.global.operations.push(op);
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}?;
|
|
||||||
|
|
||||||
update_memory_for_tags_of_geometry(&mut result, exec_state)?;
|
|
||||||
|
|
||||||
Ok(Some(result))
|
|
||||||
}
|
|
||||||
FunctionSource::User { ast, memory, .. } => {
|
|
||||||
// Track call operation.
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
{
|
|
||||||
let op_labeled_args = args
|
|
||||||
.kw_args
|
|
||||||
.labeled
|
|
||||||
.iter()
|
|
||||||
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
|
|
||||||
.collect();
|
|
||||||
exec_state.global.operations.push(Operation::GroupBegin {
|
|
||||||
group: Group::FunctionCall {
|
|
||||||
name: fn_name.clone(),
|
|
||||||
function_source_range: ast.as_source_range(),
|
|
||||||
unlabeled_arg: args
|
|
||||||
.kw_args
|
|
||||||
.unlabeled
|
|
||||||
.as_ref()
|
|
||||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
|
|
||||||
labeled_args: op_labeled_args,
|
|
||||||
},
|
|
||||||
source_range: callsite,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let result =
|
|
||||||
call_user_defined_function_kw(fn_name.as_deref(), args, *memory, ast, exec_state, ctx).await;
|
|
||||||
|
|
||||||
// Track return operation.
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
exec_state.global.operations.push(Operation::GroupEnd);
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
FunctionSource::None => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -2305,151 +1598,10 @@ mod test {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
exec::UnitType,
|
exec::UnitType,
|
||||||
execution::{memory::Stack, parse_execute, ContextType},
|
execution::{parse_execute, ContextType},
|
||||||
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
|
||||||
ExecutorSettings, UnitLen,
|
ExecutorSettings, UnitLen,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
|
||||||
async fn test_assign_args_to_params() {
|
|
||||||
// Set up a little framework for this test.
|
|
||||||
fn mem(number: usize) -> KclValue {
|
|
||||||
KclValue::Number {
|
|
||||||
value: number as f64,
|
|
||||||
ty: NumericType::count(),
|
|
||||||
meta: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn ident(s: &'static str) -> Node<Identifier> {
|
|
||||||
Node::no_src(Identifier {
|
|
||||||
name: s.to_owned(),
|
|
||||||
digest: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
fn opt_param(s: &'static str) -> Parameter {
|
|
||||||
Parameter {
|
|
||||||
identifier: ident(s),
|
|
||||||
type_: None,
|
|
||||||
default_value: Some(DefaultParamVal::none()),
|
|
||||||
labeled: true,
|
|
||||||
digest: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn req_param(s: &'static str) -> Parameter {
|
|
||||||
Parameter {
|
|
||||||
identifier: ident(s),
|
|
||||||
type_: None,
|
|
||||||
default_value: None,
|
|
||||||
labeled: true,
|
|
||||||
digest: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
|
|
||||||
let mut program_memory = Stack::new_for_tests();
|
|
||||||
for (name, item) in items {
|
|
||||||
program_memory
|
|
||||||
.add(name.clone(), item.clone(), SourceRange::default())
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
program_memory
|
|
||||||
}
|
|
||||||
// Declare the test cases.
|
|
||||||
for (test_name, params, args, expected) in [
|
|
||||||
("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
|
|
||||||
(
|
|
||||||
"all params required, and all given, should be OK",
|
|
||||||
vec![req_param("x")],
|
|
||||||
vec![("x", mem(1))],
|
|
||||||
Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"all params required, none given, should error",
|
|
||||||
vec![req_param("x")],
|
|
||||||
vec![],
|
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: vec![SourceRange::default()],
|
|
||||||
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"all params optional, none given, should be OK",
|
|
||||||
vec![opt_param("x")],
|
|
||||||
vec![],
|
|
||||||
Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mixed params, too few given",
|
|
||||||
vec![req_param("x"), opt_param("y")],
|
|
||||||
vec![],
|
|
||||||
Err(KclError::Semantic(KclErrorDetails {
|
|
||||||
source_ranges: vec![SourceRange::default()],
|
|
||||||
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mixed params, minimum given, should be OK",
|
|
||||||
vec![req_param("x"), opt_param("y")],
|
|
||||||
vec![("x", mem(1))],
|
|
||||||
Ok(additional_program_memory(&[
|
|
||||||
("x".to_owned(), mem(1)),
|
|
||||||
("y".to_owned(), KclValue::none()),
|
|
||||||
])),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"mixed params, maximum given, should be OK",
|
|
||||||
vec![req_param("x"), opt_param("y")],
|
|
||||||
vec![("x", mem(1)), ("y", mem(2))],
|
|
||||||
Ok(additional_program_memory(&[
|
|
||||||
("x".to_owned(), mem(1)),
|
|
||||||
("y".to_owned(), mem(2)),
|
|
||||||
])),
|
|
||||||
),
|
|
||||||
] {
|
|
||||||
// Run each test.
|
|
||||||
let func_expr = &Node::no_src(FunctionExpression {
|
|
||||||
params,
|
|
||||||
body: Program::empty(),
|
|
||||||
return_type: None,
|
|
||||||
digest: None,
|
|
||||||
});
|
|
||||||
let labeled = args
|
|
||||||
.iter()
|
|
||||||
.map(|(name, value)| {
|
|
||||||
let arg = Arg::new(value.clone(), SourceRange::default());
|
|
||||||
((*name).to_owned(), arg)
|
|
||||||
})
|
|
||||||
.collect::<IndexMap<_, _>>();
|
|
||||||
let exec_ctxt = ExecutorContext {
|
|
||||||
engine: Arc::new(Box::new(
|
|
||||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
|
||||||
)),
|
|
||||||
fs: Arc::new(crate::fs::FileManager::new()),
|
|
||||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
|
||||||
settings: Default::default(),
|
|
||||||
context_type: ContextType::Mock,
|
|
||||||
};
|
|
||||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
|
||||||
exec_state.mod_local.stack = Stack::new_for_tests();
|
|
||||||
|
|
||||||
let args = Args::new_kw(
|
|
||||||
KwArgs {
|
|
||||||
unlabeled: None,
|
|
||||||
labeled,
|
|
||||||
errors: Vec::new(),
|
|
||||||
},
|
|
||||||
SourceRange::default(),
|
|
||||||
exec_ctxt,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let actual =
|
|
||||||
assign_args_to_params_kw(None, func_expr, args, &mut exec_state).map(|_| exec_state.mod_local.stack);
|
|
||||||
assert_eq!(
|
|
||||||
actual, expected,
|
|
||||||
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn ascription() {
|
async fn ascription() {
|
||||||
let program = r#"
|
let program = r#"
|
||||||
|
979
rust/kcl-lib/src/execution/fn_call.rs
Normal file
@ -0,0 +1,979 @@
|
|||||||
|
use async_recursion::async_recursion;
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
use crate::execution::cad_op::{Group, OpArg, OpKclValue, Operation};
|
||||||
|
use crate::{
|
||||||
|
docs::StdLibFn,
|
||||||
|
errors::{KclError, KclErrorDetails},
|
||||||
|
execution::{
|
||||||
|
kcl_value::FunctionSource, memory, types::RuntimeType, BodyType, ExecState, ExecutorContext, KclValue,
|
||||||
|
Metadata, StatementKind, TagEngineInfo, TagIdentifier,
|
||||||
|
},
|
||||||
|
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
|
||||||
|
source_range::SourceRange,
|
||||||
|
std::StdFn,
|
||||||
|
CompilationError,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::types::ArrayLen;
|
||||||
|
use super::EnvironmentRef;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Args {
|
||||||
|
/// Positional args.
|
||||||
|
pub args: Vec<Arg>,
|
||||||
|
/// Keyword arguments
|
||||||
|
pub kw_args: KwArgs,
|
||||||
|
pub source_range: SourceRange,
|
||||||
|
pub ctx: ExecutorContext,
|
||||||
|
/// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
|
||||||
|
/// Otherwise it's None.
|
||||||
|
pub pipe_value: Option<Arg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
|
||||||
|
Self {
|
||||||
|
args,
|
||||||
|
kw_args: Default::default(),
|
||||||
|
source_range,
|
||||||
|
ctx,
|
||||||
|
pipe_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect the given keyword arguments.
|
||||||
|
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
|
||||||
|
Self {
|
||||||
|
args: Default::default(),
|
||||||
|
kw_args,
|
||||||
|
source_range,
|
||||||
|
ctx,
|
||||||
|
pipe_value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the unlabeled keyword argument. If not set, returns None.
|
||||||
|
pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
|
||||||
|
self.kw_args
|
||||||
|
.unlabeled
|
||||||
|
.as_ref()
|
||||||
|
.map(|(_, a)| a)
|
||||||
|
.or(self.args.first())
|
||||||
|
.or(self.pipe_value.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Arg {
|
||||||
|
/// The evaluated argument.
|
||||||
|
pub value: KclValue,
|
||||||
|
/// The source range of the unevaluated argument.
|
||||||
|
pub source_range: SourceRange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Arg {
|
||||||
|
pub fn new(value: KclValue, source_range: SourceRange) -> Self {
|
||||||
|
Self { value, source_range }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn synthetic(value: KclValue) -> Self {
|
||||||
|
Self {
|
||||||
|
value,
|
||||||
|
source_range: SourceRange::synthetic(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source_ranges(&self) -> Vec<SourceRange> {
|
||||||
|
vec![self.source_range]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct KwArgs {
|
||||||
|
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
|
||||||
|
/// If the argument was a local variable, then the first element of the tuple is its name
|
||||||
|
/// which may be used to treat this arg as a labelled arg.
|
||||||
|
pub unlabeled: Option<(Option<String>, Arg)>,
|
||||||
|
/// Labeled args.
|
||||||
|
pub labeled: IndexMap<String, Arg>,
|
||||||
|
pub errors: Vec<Arg>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KwArgs {
|
||||||
|
/// How many arguments are there?
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
/// Are there no arguments?
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.labeled.len() == 0 && self.unlabeled.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FunctionDefinition<'a> {
|
||||||
|
input_arg: Option<(String, Option<Type>)>,
|
||||||
|
named_args: IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
|
||||||
|
return_type: Option<Node<Type>>,
|
||||||
|
deprecated: bool,
|
||||||
|
include_in_feature_tree: bool,
|
||||||
|
is_std: bool,
|
||||||
|
body: FunctionBody<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum FunctionBody<'a> {
|
||||||
|
Rust(StdFn),
|
||||||
|
Kcl(&'a Node<Program>, EnvironmentRef),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a FunctionSource> for FunctionDefinition<'a> {
|
||||||
|
fn from(value: &'a FunctionSource) -> Self {
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn args_from_ast(
|
||||||
|
ast: &FunctionExpression,
|
||||||
|
) -> (
|
||||||
|
Option<(String, Option<Type>)>,
|
||||||
|
IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
|
||||||
|
) {
|
||||||
|
let mut input_arg = None;
|
||||||
|
let mut named_args = IndexMap::new();
|
||||||
|
for p in &ast.params {
|
||||||
|
if !p.labeled {
|
||||||
|
input_arg = Some((p.identifier.name.clone(), p.type_.as_ref().map(|t| t.inner.clone())));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
named_args.insert(
|
||||||
|
p.identifier.name.clone(),
|
||||||
|
(p.default_value.clone(), p.type_.as_ref().map(|t| t.inner.clone())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
(input_arg, named_args)
|
||||||
|
}
|
||||||
|
|
||||||
|
match value {
|
||||||
|
FunctionSource::Std { func, ast, props } => {
|
||||||
|
let (input_arg, named_args) = args_from_ast(ast);
|
||||||
|
FunctionDefinition {
|
||||||
|
input_arg,
|
||||||
|
named_args,
|
||||||
|
return_type: ast.return_type.clone(),
|
||||||
|
deprecated: props.deprecated,
|
||||||
|
include_in_feature_tree: props.include_in_feature_tree,
|
||||||
|
is_std: true,
|
||||||
|
body: FunctionBody::Rust(*func),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FunctionSource::User { ast, memory, .. } => {
|
||||||
|
let (input_arg, named_args) = args_from_ast(ast);
|
||||||
|
FunctionDefinition {
|
||||||
|
input_arg,
|
||||||
|
named_args,
|
||||||
|
return_type: ast.return_type.clone(),
|
||||||
|
deprecated: false,
|
||||||
|
include_in_feature_tree: true,
|
||||||
|
// TODO I think this might be wrong for pure Rust std functions
|
||||||
|
is_std: false,
|
||||||
|
body: FunctionBody::Kcl(&ast.body, *memory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FunctionSource::None => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&dyn StdLibFn> for FunctionDefinition<'static> {
|
||||||
|
fn from(value: &dyn StdLibFn) -> Self {
|
||||||
|
let mut input_arg = None;
|
||||||
|
let mut named_args = IndexMap::new();
|
||||||
|
for a in value.args(false) {
|
||||||
|
if !a.label_required {
|
||||||
|
input_arg = Some((a.name.clone(), None));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
named_args.insert(
|
||||||
|
a.name.clone(),
|
||||||
|
(
|
||||||
|
if a.required {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(DefaultParamVal::none())
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
FunctionDefinition {
|
||||||
|
input_arg,
|
||||||
|
named_args,
|
||||||
|
return_type: None,
|
||||||
|
deprecated: value.deprecated(),
|
||||||
|
include_in_feature_tree: value.feature_tree_operation(),
|
||||||
|
is_std: true,
|
||||||
|
body: FunctionBody::Rust(value.std_lib_fn()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Node<CallExpressionKw> {
|
||||||
|
#[async_recursion]
|
||||||
|
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||||
|
let fn_name = &self.callee;
|
||||||
|
let callsite: SourceRange = self.into();
|
||||||
|
|
||||||
|
// Build a hashmap from argument labels to the final evaluated values.
|
||||||
|
let mut fn_args = IndexMap::with_capacity(self.arguments.len());
|
||||||
|
let mut errors = Vec::new();
|
||||||
|
for arg_expr in &self.arguments {
|
||||||
|
let source_range = SourceRange::from(arg_expr.arg.clone());
|
||||||
|
let metadata = Metadata { source_range };
|
||||||
|
let value = ctx
|
||||||
|
.execute_expr(&arg_expr.arg, exec_state, &metadata, &[], StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
let arg = Arg::new(value, source_range);
|
||||||
|
match &arg_expr.label {
|
||||||
|
Some(l) => {
|
||||||
|
fn_args.insert(l.name.clone(), arg);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
if let Some(id) = arg_expr.arg.ident_name() {
|
||||||
|
fn_args.insert(id.to_owned(), arg);
|
||||||
|
} else {
|
||||||
|
errors.push(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate the unlabeled first param, if any exists.
|
||||||
|
let unlabeled = if let Some(ref arg_expr) = self.unlabeled {
|
||||||
|
let source_range = SourceRange::from(arg_expr.clone());
|
||||||
|
let metadata = Metadata { source_range };
|
||||||
|
let value = ctx
|
||||||
|
.execute_expr(arg_expr, exec_state, &metadata, &[], StatementKind::Expression)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let label = arg_expr.ident_name().map(str::to_owned);
|
||||||
|
|
||||||
|
Some((label, Arg::new(value, source_range)))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let args = Args::new_kw(
|
||||||
|
KwArgs {
|
||||||
|
unlabeled,
|
||||||
|
labeled: fn_args,
|
||||||
|
errors,
|
||||||
|
},
|
||||||
|
self.into(),
|
||||||
|
ctx.clone(),
|
||||||
|
exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
|
||||||
|
);
|
||||||
|
|
||||||
|
match ctx.stdlib.get_rust_function(fn_name) {
|
||||||
|
Some(func) => {
|
||||||
|
let def: FunctionDefinition = (&*func).into();
|
||||||
|
// All std lib functions return a value, so the unwrap is safe.
|
||||||
|
def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
|
||||||
|
.await
|
||||||
|
.map(Option::unwrap)
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// Clone the function so that we can use a mutable reference to
|
||||||
|
// exec_state.
|
||||||
|
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||||
|
|
||||||
|
let Some(fn_src) = func.as_fn() else {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
message: "cannot call this because it isn't a function".to_string(),
|
||||||
|
source_ranges: vec![callsite],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let return_value = fn_src
|
||||||
|
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
// Add the call expression to the source ranges.
|
||||||
|
e.add_source_ranges(vec![callsite])
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let result = return_value.ok_or_else(move || {
|
||||||
|
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
||||||
|
// We want to send the source range of the original function.
|
||||||
|
if let KclValue::Function { meta, .. } = func {
|
||||||
|
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||||
|
};
|
||||||
|
KclError::UndefinedValue(KclErrorDetails {
|
||||||
|
message: format!("Result of user-defined function {} is undefined", fn_name),
|
||||||
|
source_ranges,
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionDefinition<'_> {
|
||||||
|
pub async fn call_kw(
|
||||||
|
&self,
|
||||||
|
fn_name: Option<String>,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
ctx: &ExecutorContext,
|
||||||
|
mut args: Args,
|
||||||
|
callsite: SourceRange,
|
||||||
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
if self.deprecated {
|
||||||
|
exec_state.warn(CompilationError::err(
|
||||||
|
callsite,
|
||||||
|
format!(
|
||||||
|
"{} is deprecated, see the docs for a recommended replacement",
|
||||||
|
match &fn_name {
|
||||||
|
Some(n) => format!("`{n}`"),
|
||||||
|
None => "This function".to_owned(),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
type_check_params_kw(fn_name.as_deref(), self, &mut args.kw_args, exec_state)?;
|
||||||
|
|
||||||
|
// Don't early return until the stack frame is popped!
|
||||||
|
self.body.prep_mem(exec_state);
|
||||||
|
|
||||||
|
let op = if self.include_in_feature_tree {
|
||||||
|
let op_labeled_args = args
|
||||||
|
.kw_args
|
||||||
|
.labeled
|
||||||
|
.iter()
|
||||||
|
.map(|(k, arg)| (k.clone(), OpArg::new(OpKclValue::from(&arg.value), arg.source_range)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if self.is_std {
|
||||||
|
Some(Operation::StdLibCall {
|
||||||
|
name: fn_name.clone().unwrap_or_else(|| "unknown function".to_owned()),
|
||||||
|
unlabeled_arg: args
|
||||||
|
.unlabeled_kw_arg_unconverted()
|
||||||
|
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
||||||
|
labeled_args: op_labeled_args,
|
||||||
|
source_range: callsite,
|
||||||
|
is_error: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
exec_state.push_op(Operation::GroupBegin {
|
||||||
|
group: Group::FunctionCall {
|
||||||
|
name: fn_name.clone(),
|
||||||
|
function_source_range: self.as_source_range().unwrap(),
|
||||||
|
unlabeled_arg: args
|
||||||
|
.kw_args
|
||||||
|
.unlabeled
|
||||||
|
.as_ref()
|
||||||
|
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
|
||||||
|
labeled_args: op_labeled_args,
|
||||||
|
},
|
||||||
|
source_range: callsite,
|
||||||
|
});
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = match &self.body {
|
||||||
|
FunctionBody::Rust(f) => f(exec_state, args).await.map(Some),
|
||||||
|
FunctionBody::Kcl(f, _) => {
|
||||||
|
if let Err(e) = assign_args_to_params_kw(self, args, exec_state) {
|
||||||
|
exec_state.mut_stack().pop_env();
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.exec_block(f, exec_state, BodyType::Block).await.map(|_| {
|
||||||
|
exec_state
|
||||||
|
.stack()
|
||||||
|
.get(memory::RETURN_NAME, f.as_source_range())
|
||||||
|
.ok()
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exec_state.mut_stack().pop_env();
|
||||||
|
|
||||||
|
if let Some(mut op) = op {
|
||||||
|
op.set_std_lib_call_is_error(result.is_err());
|
||||||
|
// Track call operation. We do this after the call
|
||||||
|
// since things like patternTransform may call user code
|
||||||
|
// before running, and we will likely want to use the
|
||||||
|
// return value. The call takes ownership of the args,
|
||||||
|
// so we need to build the op before the call.
|
||||||
|
exec_state.push_op(op);
|
||||||
|
} else if !self.is_std {
|
||||||
|
exec_state.push_op(Operation::GroupEnd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_std {
|
||||||
|
if let Ok(Some(result)) = &mut result {
|
||||||
|
update_memory_for_tags_of_geometry(result, exec_state)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coerce_result_type(result, self, exec_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Postcondition: result.is_some() if function is not in the standard library.
|
||||||
|
fn as_source_range(&self) -> Option<SourceRange> {
|
||||||
|
match &self.body {
|
||||||
|
FunctionBody::Rust(_) => None,
|
||||||
|
FunctionBody::Kcl(p, _) => Some(p.as_source_range()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionBody<'_> {
|
||||||
|
fn prep_mem(&self, exec_state: &mut ExecState) {
|
||||||
|
match self {
|
||||||
|
FunctionBody::Rust(_) => exec_state.mut_stack().push_new_env_for_rust_call(),
|
||||||
|
FunctionBody::Kcl(_, memory) => exec_state.mut_stack().push_new_env_for_call(*memory),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionSource {
|
||||||
|
pub async fn call_kw(
|
||||||
|
&self,
|
||||||
|
fn_name: Option<String>,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
ctx: &ExecutorContext,
|
||||||
|
args: Args,
|
||||||
|
callsite: SourceRange,
|
||||||
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
let def: FunctionDefinition = self.into();
|
||||||
|
def.call_kw(fn_name, exec_state, ctx, args, callsite).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_memory_for_tags_of_geometry(result: &mut KclValue, exec_state: &mut ExecState) -> Result<(), KclError> {
|
||||||
|
// If the return result is a sketch or solid, we want to update the
|
||||||
|
// memory for the tags of the group.
|
||||||
|
// TODO: This could probably be done in a better way, but as of now this was my only idea
|
||||||
|
// and it works.
|
||||||
|
match result {
|
||||||
|
KclValue::Sketch { value } => {
|
||||||
|
for (name, tag) in value.tags.iter() {
|
||||||
|
if exec_state.stack().cur_frame_contains(name) {
|
||||||
|
exec_state.mut_stack().update(name, |v, _| {
|
||||||
|
v.as_mut_tag().unwrap().merge_info(tag);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exec_state
|
||||||
|
.mut_stack()
|
||||||
|
.add(
|
||||||
|
name.to_owned(),
|
||||||
|
KclValue::TagIdentifier(Box::new(tag.clone())),
|
||||||
|
SourceRange::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KclValue::Solid { ref mut value } => {
|
||||||
|
for v in &value.value {
|
||||||
|
if let Some(tag) = v.get_tag() {
|
||||||
|
// Get the past tag and update it.
|
||||||
|
let tag_id = if let Some(t) = value.sketch.tags.get(&tag.name) {
|
||||||
|
let mut t = t.clone();
|
||||||
|
let Some(info) = t.get_cur_info() else {
|
||||||
|
return Err(KclError::Internal(KclErrorDetails {
|
||||||
|
message: format!("Tag {} does not have path info", tag.name),
|
||||||
|
source_ranges: vec![tag.into()],
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut info = info.clone();
|
||||||
|
info.surface = Some(v.clone());
|
||||||
|
info.sketch = value.id;
|
||||||
|
t.info.push((exec_state.stack().current_epoch(), info));
|
||||||
|
t
|
||||||
|
} else {
|
||||||
|
// It's probably a fillet or a chamfer.
|
||||||
|
// Initialize it.
|
||||||
|
TagIdentifier {
|
||||||
|
value: tag.name.clone(),
|
||||||
|
info: vec![(
|
||||||
|
exec_state.stack().current_epoch(),
|
||||||
|
TagEngineInfo {
|
||||||
|
id: v.get_id(),
|
||||||
|
surface: Some(v.clone()),
|
||||||
|
path: None,
|
||||||
|
sketch: value.id,
|
||||||
|
},
|
||||||
|
)],
|
||||||
|
meta: vec![Metadata {
|
||||||
|
source_range: tag.clone().into(),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// update the sketch tags.
|
||||||
|
value.sketch.merge_tags(Some(&tag_id).into_iter());
|
||||||
|
|
||||||
|
if exec_state.stack().cur_frame_contains(&tag.name) {
|
||||||
|
exec_state.mut_stack().update(&tag.name, |v, _| {
|
||||||
|
v.as_mut_tag().unwrap().merge_info(&tag_id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
exec_state
|
||||||
|
.mut_stack()
|
||||||
|
.add(
|
||||||
|
tag.name.clone(),
|
||||||
|
KclValue::TagIdentifier(Box::new(tag_id)),
|
||||||
|
SourceRange::default(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the stale sketch in memory and update it.
|
||||||
|
if !value.sketch.tags.is_empty() {
|
||||||
|
let sketches_to_update: Vec<_> = exec_state
|
||||||
|
.stack()
|
||||||
|
.find_keys_in_current_env(|v| match v {
|
||||||
|
KclValue::Sketch { value: sk } => sk.original_id == value.sketch.original_id,
|
||||||
|
_ => false,
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
for k in sketches_to_update {
|
||||||
|
exec_state.mut_stack().update(&k, |v, _| {
|
||||||
|
let sketch = v.as_mut_sketch().unwrap();
|
||||||
|
sketch.merge_tags(value.sketch.tags.values());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
|
||||||
|
for v in value {
|
||||||
|
update_memory_for_tags_of_geometry(v, exec_state)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn type_check_params_kw(
|
||||||
|
fn_name: Option<&str>,
|
||||||
|
fn_def: &FunctionDefinition<'_>,
|
||||||
|
args: &mut KwArgs,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
) -> Result<(), KclError> {
|
||||||
|
// If it's possible the input arg was meant to be labelled and we probably don't want to use
|
||||||
|
// it as the input arg, then treat it as labelled.
|
||||||
|
if let Some((Some(label), _)) = &args.unlabeled {
|
||||||
|
if (fn_def.input_arg.is_none() || exec_state.pipe_value().is_some())
|
||||||
|
&& fn_def.named_args.iter().any(|p| p.0 == label)
|
||||||
|
&& !args.labeled.contains_key(label)
|
||||||
|
{
|
||||||
|
let (label, arg) = args.unlabeled.take().unwrap();
|
||||||
|
args.labeled.insert(label.unwrap(), arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (label, arg) in &mut args.labeled {
|
||||||
|
match fn_def.named_args.get(label) {
|
||||||
|
Some((_, ty)) => {
|
||||||
|
if let Some(ty) = ty {
|
||||||
|
arg.value = arg
|
||||||
|
.value
|
||||||
|
.coerce(
|
||||||
|
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::Semantic(e.into()))?,
|
||||||
|
exec_state,
|
||||||
|
)
|
||||||
|
.map_err(|e| {
|
||||||
|
let mut message = format!(
|
||||||
|
"{label} requires a value with type `{}`, but found {}",
|
||||||
|
ty,
|
||||||
|
arg.value.human_friendly_type(),
|
||||||
|
);
|
||||||
|
if let Some(ty) = e.explicit_coercion {
|
||||||
|
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||||
|
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||||
|
}
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message,
|
||||||
|
source_ranges: vec![arg.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
exec_state.err(CompilationError::err(
|
||||||
|
arg.source_range,
|
||||||
|
format!(
|
||||||
|
"`{label}` is not an argument of {}",
|
||||||
|
fn_name
|
||||||
|
.map(|n| format!("`{}`", n))
|
||||||
|
.unwrap_or_else(|| "this function".to_owned()),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.errors.is_empty() {
|
||||||
|
let actuals = args.labeled.keys();
|
||||||
|
let formals: Vec<_> = fn_def
|
||||||
|
.named_args
|
||||||
|
.keys()
|
||||||
|
.filter_map(|name| {
|
||||||
|
if actuals.clone().any(|a| a == name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(format!("`{name}`"))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let suggestion = if formals.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("; suggested labels: {}", formals.join(", "))
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut errors = args.errors.iter().map(|e| {
|
||||||
|
CompilationError::err(
|
||||||
|
e.source_range,
|
||||||
|
format!("This argument needs a label, but it doesn't have one{suggestion}"),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let first = errors.next().unwrap();
|
||||||
|
errors.for_each(|e| exec_state.err(e));
|
||||||
|
|
||||||
|
return Err(KclError::Semantic(first.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(arg) = &mut args.unlabeled {
|
||||||
|
if let Some((_, Some(ty))) = &fn_def.input_arg {
|
||||||
|
arg.1.value = arg
|
||||||
|
.1
|
||||||
|
.value
|
||||||
|
.coerce(
|
||||||
|
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.1.source_range)
|
||||||
|
.map_err(|e| KclError::Semantic(e.into()))?,
|
||||||
|
exec_state,
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"The input argument of {} requires a value with type `{}`, but found {}",
|
||||||
|
fn_name
|
||||||
|
.map(|n| format!("`{}`", n))
|
||||||
|
.unwrap_or_else(|| "this function".to_owned()),
|
||||||
|
ty,
|
||||||
|
arg.1.value.human_friendly_type()
|
||||||
|
),
|
||||||
|
source_ranges: vec![arg.1.source_range],
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
} else if let Some((name, _)) = &fn_def.input_arg {
|
||||||
|
if let Some(arg) = args.labeled.get(name) {
|
||||||
|
exec_state.err(CompilationError::err(
|
||||||
|
arg.source_range,
|
||||||
|
format!(
|
||||||
|
"{} expects an unlabeled first parameter (`@{name}`), but it is labelled in the call",
|
||||||
|
fn_name
|
||||||
|
.map(|n| format!("The function `{}`", n))
|
||||||
|
.unwrap_or_else(|| "This function".to_owned()),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_args_to_params_kw(
|
||||||
|
fn_def: &FunctionDefinition<'_>,
|
||||||
|
args: Args,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
) -> Result<(), KclError> {
|
||||||
|
// Add the arguments to the memory. A new call frame should have already
|
||||||
|
// been created.
|
||||||
|
let source_ranges = fn_def.as_source_range().into_iter().collect();
|
||||||
|
|
||||||
|
for (name, (default, _)) in fn_def.named_args.iter() {
|
||||||
|
let arg = args.kw_args.labeled.get(name);
|
||||||
|
match arg {
|
||||||
|
Some(arg) => {
|
||||||
|
exec_state.mut_stack().add(
|
||||||
|
name.clone(),
|
||||||
|
arg.value.clone(),
|
||||||
|
arg.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
None => match default {
|
||||||
|
Some(ref default_val) => {
|
||||||
|
let value = KclValue::from_default_param(default_val.clone(), exec_state);
|
||||||
|
exec_state
|
||||||
|
.mut_stack()
|
||||||
|
.add(name.clone(), value, default_val.source_range())?;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: format!(
|
||||||
|
"This function requires a parameter {}, but you haven't passed it one.",
|
||||||
|
name
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((param_name, _)) = &fn_def.input_arg {
|
||||||
|
let unlabelled = args.unlabeled_kw_arg_unconverted();
|
||||||
|
|
||||||
|
let Some(unlabeled) = unlabelled else {
|
||||||
|
return Err(if args.kw_args.labeled.contains_key(param_name) {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: format!("The function does declare a parameter named '{param_name}', but this parameter doesn't use a label. Try removing the `{param_name}:`"),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges,
|
||||||
|
message: "This function expects an unlabeled first parameter, but you haven't passed it one."
|
||||||
|
.to_owned(),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
};
|
||||||
|
exec_state.mut_stack().add(
|
||||||
|
param_name.clone(),
|
||||||
|
unlabeled.value.clone(),
|
||||||
|
unlabeled.source_ranges().pop().unwrap_or(SourceRange::synthetic()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn coerce_result_type(
|
||||||
|
result: Result<Option<KclValue>, KclError>,
|
||||||
|
fn_def: &FunctionDefinition<'_>,
|
||||||
|
exec_state: &mut ExecState,
|
||||||
|
) -> Result<Option<KclValue>, KclError> {
|
||||||
|
if let Ok(Some(val)) = result {
|
||||||
|
if let Some(ret_ty) = &fn_def.return_type {
|
||||||
|
let mut ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
|
||||||
|
.map_err(|e| KclError::Semantic(e.into()))?;
|
||||||
|
// Treat `[T; 1+]` as `T | [T; 1+]` (which can't yet be expressed in our syntax of types).
|
||||||
|
// This is a very specific hack which exists because some std functions can produce arrays
|
||||||
|
// but usually only make a singleton and the frontend expects the singleton.
|
||||||
|
// If we can make the frontend work on arrays (or at least arrays of length 1), then this
|
||||||
|
// can be removed.
|
||||||
|
// I believe this is safe, since anywhere which requires an array should coerce the singleton
|
||||||
|
// to an array and we only do this hack for return values.
|
||||||
|
if let RuntimeType::Array(inner, ArrayLen::NonEmpty) = &ty {
|
||||||
|
ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
|
||||||
|
}
|
||||||
|
let val = val.coerce(&ty, exec_state).map_err(|_| {
|
||||||
|
KclError::Semantic(KclErrorDetails {
|
||||||
|
message: format!(
|
||||||
|
"This function requires its result to be of type `{}`, but found {}",
|
||||||
|
ty.human_friendly_type(),
|
||||||
|
val.human_friendly_type(),
|
||||||
|
),
|
||||||
|
source_ranges: ret_ty.as_source_ranges(),
|
||||||
|
})
|
||||||
|
})?;
|
||||||
|
Ok(Some(val))
|
||||||
|
} else {
|
||||||
|
Ok(Some(val))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use crate::{
|
||||||
|
execution::{memory::Stack, parse_execute, types::NumericType, ContextType},
|
||||||
|
parsing::ast::types::{DefaultParamVal, Identifier, Parameter},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn test_assign_args_to_params() {
|
||||||
|
// Set up a little framework for this test.
|
||||||
|
fn mem(number: usize) -> KclValue {
|
||||||
|
KclValue::Number {
|
||||||
|
value: number as f64,
|
||||||
|
ty: NumericType::count(),
|
||||||
|
meta: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn ident(s: &'static str) -> Node<Identifier> {
|
||||||
|
Node::no_src(Identifier {
|
||||||
|
name: s.to_owned(),
|
||||||
|
digest: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn opt_param(s: &'static str) -> Parameter {
|
||||||
|
Parameter {
|
||||||
|
identifier: ident(s),
|
||||||
|
type_: None,
|
||||||
|
default_value: Some(DefaultParamVal::none()),
|
||||||
|
labeled: true,
|
||||||
|
digest: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn req_param(s: &'static str) -> Parameter {
|
||||||
|
Parameter {
|
||||||
|
identifier: ident(s),
|
||||||
|
type_: None,
|
||||||
|
default_value: None,
|
||||||
|
labeled: true,
|
||||||
|
digest: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn additional_program_memory(items: &[(String, KclValue)]) -> Stack {
|
||||||
|
let mut program_memory = Stack::new_for_tests();
|
||||||
|
for (name, item) in items {
|
||||||
|
program_memory
|
||||||
|
.add(name.clone(), item.clone(), SourceRange::default())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
program_memory
|
||||||
|
}
|
||||||
|
// Declare the test cases.
|
||||||
|
for (test_name, params, args, expected) in [
|
||||||
|
("empty", Vec::new(), Vec::new(), Ok(additional_program_memory(&[]))),
|
||||||
|
(
|
||||||
|
"all params required, and all given, should be OK",
|
||||||
|
vec![req_param("x")],
|
||||||
|
vec![("x", mem(1))],
|
||||||
|
Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all params required, none given, should error",
|
||||||
|
vec![req_param("x")],
|
||||||
|
vec![],
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"all params optional, none given, should be OK",
|
||||||
|
vec![opt_param("x")],
|
||||||
|
vec![],
|
||||||
|
Ok(additional_program_memory(&[("x".to_owned(), KclValue::none())])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, too few given",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![],
|
||||||
|
Err(KclError::Semantic(KclErrorDetails {
|
||||||
|
source_ranges: vec![SourceRange::default()],
|
||||||
|
message: "This function requires a parameter x, but you haven't passed it one.".to_owned(),
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, minimum given, should be OK",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![("x", mem(1))],
|
||||||
|
Ok(additional_program_memory(&[
|
||||||
|
("x".to_owned(), mem(1)),
|
||||||
|
("y".to_owned(), KclValue::none()),
|
||||||
|
])),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mixed params, maximum given, should be OK",
|
||||||
|
vec![req_param("x"), opt_param("y")],
|
||||||
|
vec![("x", mem(1)), ("y", mem(2))],
|
||||||
|
Ok(additional_program_memory(&[
|
||||||
|
("x".to_owned(), mem(1)),
|
||||||
|
("y".to_owned(), mem(2)),
|
||||||
|
])),
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
// Run each test.
|
||||||
|
let func_expr = Node::no_src(FunctionExpression {
|
||||||
|
params,
|
||||||
|
body: Program::empty(),
|
||||||
|
return_type: None,
|
||||||
|
digest: None,
|
||||||
|
});
|
||||||
|
let func_src = FunctionSource::User {
|
||||||
|
ast: Box::new(func_expr),
|
||||||
|
settings: Default::default(),
|
||||||
|
memory: EnvironmentRef::dummy(),
|
||||||
|
};
|
||||||
|
let labeled = args
|
||||||
|
.iter()
|
||||||
|
.map(|(name, value)| {
|
||||||
|
let arg = Arg::new(value.clone(), SourceRange::default());
|
||||||
|
((*name).to_owned(), arg)
|
||||||
|
})
|
||||||
|
.collect::<IndexMap<_, _>>();
|
||||||
|
let exec_ctxt = ExecutorContext {
|
||||||
|
engine: Arc::new(Box::new(
|
||||||
|
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||||
|
)),
|
||||||
|
fs: Arc::new(crate::fs::FileManager::new()),
|
||||||
|
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||||
|
settings: Default::default(),
|
||||||
|
context_type: ContextType::Mock,
|
||||||
|
};
|
||||||
|
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||||
|
exec_state.mod_local.stack = Stack::new_for_tests();
|
||||||
|
|
||||||
|
let args = Args::new_kw(
|
||||||
|
KwArgs {
|
||||||
|
unlabeled: None,
|
||||||
|
labeled,
|
||||||
|
errors: Vec::new(),
|
||||||
|
},
|
||||||
|
SourceRange::default(),
|
||||||
|
exec_ctxt,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
let actual = assign_args_to_params_kw(&(&func_src).into(), args, &mut exec_state)
|
||||||
|
.map(|_| exec_state.mod_local.stack);
|
||||||
|
assert_eq!(
|
||||||
|
actual, expected,
|
||||||
|
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn type_check_user_args() {
|
||||||
|
let program = r#"fn makeMessage(prefix: string, suffix: string) {
|
||||||
|
return prefix + suffix
|
||||||
|
}
|
||||||
|
|
||||||
|
msg1 = makeMessage(prefix = "world", suffix = " hello")
|
||||||
|
msg2 = makeMessage(prefix = 1, suffix = 3)"#;
|
||||||
|
let err = parse_execute(program).await.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err.message(),
|
||||||
|
"prefix requires a value with type `string`, but found number(default units)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -8,12 +8,12 @@ use parse_display::{Display, FromStr};
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
use crate::execution::ArtifactId;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{PlaneName, DEFAULT_PLANE_INFO},
|
engine::{PlaneName, DEFAULT_PLANE_INFO},
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{types::NumericType, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
|
execution::{
|
||||||
|
types::NumericType, ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen,
|
||||||
|
},
|
||||||
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
|
||||||
std::{args::TyF64, sketch::PlaneData},
|
std::{args::TyF64, sketch::PlaneData},
|
||||||
};
|
};
|
||||||
@ -256,7 +256,6 @@ pub struct Helix {
|
|||||||
/// The id of the helix.
|
/// The id of the helix.
|
||||||
pub value: uuid::Uuid,
|
pub value: uuid::Uuid,
|
||||||
/// The artifact ID.
|
/// The artifact ID.
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
/// Number of revolutions.
|
/// Number of revolutions.
|
||||||
pub revolutions: f64,
|
pub revolutions: f64,
|
||||||
@ -278,7 +277,6 @@ pub struct Plane {
|
|||||||
/// The id of the plane.
|
/// The id of the plane.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
/// The artifact ID.
|
/// The artifact ID.
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
// The code for the plane either a string or custom.
|
// The code for the plane either a string or custom.
|
||||||
pub value: PlaneType,
|
pub value: PlaneType,
|
||||||
@ -508,7 +506,6 @@ impl Plane {
|
|||||||
let id = exec_state.next_uuid();
|
let id = exec_state.next_uuid();
|
||||||
Ok(Plane {
|
Ok(Plane {
|
||||||
id,
|
id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: id.into(),
|
artifact_id: id.into(),
|
||||||
info: PlaneInfo::try_from(value.clone())?,
|
info: PlaneInfo::try_from(value.clone())?,
|
||||||
value: value.into(),
|
value: value.into(),
|
||||||
@ -530,7 +527,6 @@ pub struct Face {
|
|||||||
/// The id of the face.
|
/// The id of the face.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
/// The artifact ID.
|
/// The artifact ID.
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
/// The tag of the face.
|
/// The tag of the face.
|
||||||
pub value: String,
|
pub value: String,
|
||||||
@ -584,7 +580,6 @@ pub struct Sketch {
|
|||||||
pub tags: IndexMap<String, TagIdentifier>,
|
pub tags: IndexMap<String, TagIdentifier>,
|
||||||
/// The original id of the sketch. This stays the same even if the sketch is
|
/// The original id of the sketch. This stays the same even if the sketch is
|
||||||
/// is sketched on face etc.
|
/// is sketched on face etc.
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
#[ts(skip)]
|
#[ts(skip)]
|
||||||
pub original_id: uuid::Uuid,
|
pub original_id: uuid::Uuid,
|
||||||
@ -748,7 +743,6 @@ pub struct Solid {
|
|||||||
/// The id of the solid.
|
/// The id of the solid.
|
||||||
pub id: uuid::Uuid,
|
pub id: uuid::Uuid,
|
||||||
/// The artifact ID of the solid. Unlike `id`, this doesn't change.
|
/// The artifact ID of the solid. Unlike `id`, this doesn't change.
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
pub artifact_id: ArtifactId,
|
pub artifact_id: ArtifactId,
|
||||||
/// The extrude surfaces.
|
/// The extrude surfaces.
|
||||||
pub value: Vec<ExtrudeSurface>,
|
pub value: Vec<ExtrudeSurface>,
|
||||||
|
@ -352,8 +352,8 @@ impl KclValue {
|
|||||||
pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
|
pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
|
||||||
match param {
|
match param {
|
||||||
DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
|
DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
|
||||||
DefaultParamVal::KclNone(none) => KclValue::KclNone {
|
DefaultParamVal::KclNone(value) => KclValue::KclNone {
|
||||||
value: none,
|
value,
|
||||||
meta: Default::default(),
|
meta: Default::default(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -820,7 +820,7 @@ impl PartialEq for Stack {
|
|||||||
pub struct EnvironmentRef(usize, usize);
|
pub struct EnvironmentRef(usize, usize);
|
||||||
|
|
||||||
impl EnvironmentRef {
|
impl EnvironmentRef {
|
||||||
fn dummy() -> Self {
|
pub fn dummy() -> Self {
|
||||||
Self(usize::MAX, 0)
|
Self(usize::MAX, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,9 +4,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
pub use artifact::{
|
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
||||||
Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, CodeRef, StartSketchOnFace, StartSketchOnPlane,
|
|
||||||
};
|
|
||||||
use cache::OldAstState;
|
use cache::OldAstState;
|
||||||
pub use cache::{bust_cache, clear_mem_cache};
|
pub use cache::{bust_cache, clear_mem_cache};
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
@ -22,11 +20,12 @@ use kcmc::{
|
|||||||
websocket::{ModelingSessionData, OkWebSocketResponseData},
|
websocket::{ModelingSessionData, OkWebSocketResponseData},
|
||||||
ImageFormat, ModelingCmd,
|
ImageFormat, ModelingCmd,
|
||||||
};
|
};
|
||||||
use kittycad_modeling_cmds as kcmc;
|
use kittycad_modeling_cmds::{self as kcmc, id::ModelingCmdId};
|
||||||
pub use memory::EnvironmentRef;
|
pub use memory::EnvironmentRef;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
pub use state::{ExecState, MetaSettings};
|
pub use state::{ExecState, MetaSettings};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
use crate::execution::artifact::build_artifact_graph;
|
use crate::execution::artifact::build_artifact_graph;
|
||||||
@ -51,9 +50,9 @@ pub(crate) mod annotations;
|
|||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
mod artifact;
|
mod artifact;
|
||||||
pub(crate) mod cache;
|
pub(crate) mod cache;
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
mod cad_op;
|
mod cad_op;
|
||||||
mod exec_ast;
|
mod exec_ast;
|
||||||
|
pub mod fn_call;
|
||||||
mod geometry;
|
mod geometry;
|
||||||
mod id_generator;
|
mod id_generator;
|
||||||
mod import;
|
mod import;
|
||||||
@ -63,6 +62,11 @@ mod state;
|
|||||||
pub mod typed_path;
|
pub mod typed_path;
|
||||||
pub(crate) mod types;
|
pub(crate) mod types;
|
||||||
|
|
||||||
|
enum StatementKind<'a> {
|
||||||
|
Declaration { name: &'a str },
|
||||||
|
Expression,
|
||||||
|
}
|
||||||
|
|
||||||
/// Outcome of executing a program. This is used in TS.
|
/// Outcome of executing a program. This is used in TS.
|
||||||
#[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)]
|
#[derive(Debug, Clone, Serialize, ts_rs::TS, PartialEq)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
@ -1324,6 +1328,51 @@ impl ExecutorContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Serialize, PartialEq, Eq, Ord, PartialOrd, Hash, ts_rs::TS, JsonSchema)]
|
||||||
|
pub struct ArtifactId(Uuid);
|
||||||
|
|
||||||
|
impl ArtifactId {
|
||||||
|
pub fn new(uuid: Uuid) -> Self {
|
||||||
|
Self(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Uuid> for ArtifactId {
|
||||||
|
fn from(uuid: Uuid) -> Self {
|
||||||
|
Self::new(uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Uuid> for ArtifactId {
|
||||||
|
fn from(uuid: &Uuid) -> Self {
|
||||||
|
Self::new(*uuid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ArtifactId> for Uuid {
|
||||||
|
fn from(id: ArtifactId) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ArtifactId> for Uuid {
|
||||||
|
fn from(id: &ArtifactId) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ModelingCmdId> for ArtifactId {
|
||||||
|
fn from(id: ModelingCmdId) -> Self {
|
||||||
|
Self::new(*id.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&ModelingCmdId> for ArtifactId {
|
||||||
|
fn from(id: &ModelingCmdId) -> Self {
|
||||||
|
Self::new(*id.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclError> {
|
pub(crate) async fn parse_execute(code: &str) -> Result<ExecTestResults, KclError> {
|
||||||
parse_execute_with_project_dir(code, None).await
|
parse_execute_with_project_dir(code, None).await
|
||||||
|
@ -9,11 +9,12 @@ use serde::{Deserialize, Serialize};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
#[cfg(feature = "artifact-graph")]
|
||||||
use crate::execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId, Operation};
|
use crate::execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails, Severity},
|
errors::{KclError, KclErrorDetails, Severity},
|
||||||
execution::{
|
execution::{
|
||||||
annotations,
|
annotations,
|
||||||
|
cad_op::Operation,
|
||||||
id_generator::IdGenerator,
|
id_generator::IdGenerator,
|
||||||
memory::{ProgramMemory, Stack},
|
memory::{ProgramMemory, Stack},
|
||||||
types,
|
types,
|
||||||
@ -201,6 +202,13 @@ impl ExecState {
|
|||||||
self.global.artifacts.insert(id, artifact);
|
self.global.artifacts.insert(id, artifact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn push_op(&mut self, op: Operation) {
|
||||||
|
#[cfg(feature = "artifact-graph")]
|
||||||
|
self.global.operations.push(op);
|
||||||
|
#[cfg(not(feature = "artifact-graph"))]
|
||||||
|
drop(op);
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn next_module_id(&self) -> ModuleId {
|
pub(super) fn next_module_id(&self) -> ModuleId {
|
||||||
ModuleId::from_usize(self.global.path_to_source_id.len())
|
ModuleId::from_usize(self.global.path_to_source_id.len())
|
||||||
}
|
}
|
||||||
|
@ -1112,7 +1112,6 @@ impl KclValue {
|
|||||||
let id = exec_state.mod_local.id_generator.next_uuid();
|
let id = exec_state.mod_local.id_generator.next_uuid();
|
||||||
let plane = Plane {
|
let plane = Plane {
|
||||||
id,
|
id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: id.into(),
|
artifact_id: id.into(),
|
||||||
info: PlaneInfo {
|
info: PlaneInfo {
|
||||||
origin,
|
origin,
|
||||||
|
@ -3374,6 +3374,13 @@ impl DefaultParamVal {
|
|||||||
pub(crate) fn none() -> Self {
|
pub(crate) fn none() -> Self {
|
||||||
Self::KclNone(KclNone::default())
|
Self::KclNone(KclNone::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn source_range(&self) -> SourceRange {
|
||||||
|
match self {
|
||||||
|
DefaultParamVal::Literal(l) => l.as_source_range(),
|
||||||
|
DefaultParamVal::KclNone(_) => SourceRange::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameter of a KCL function.
|
/// Parameter of a KCL function.
|
||||||
|
@ -42,7 +42,8 @@ impl Test {
|
|||||||
|
|
||||||
/// Read in the entry point file and return its contents as a string.
|
/// Read in the entry point file and return its contents as a string.
|
||||||
pub fn read(&self) -> String {
|
pub fn read(&self) -> String {
|
||||||
std::fs::read_to_string(&self.entry_point).expect("Failed to read file: {filename}")
|
std::fs::read_to_string(&self.entry_point)
|
||||||
|
.unwrap_or_else(|e| panic!("Failed to read file: {:?} due to {e}", self.entry_point))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3213,3 +3214,66 @@ mod subtract_regression07 {
|
|||||||
super::execute(TEST_NAME, true).await
|
super::execute(TEST_NAME, true).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
mod subtract_regression08 {
|
||||||
|
const TEST_NAME: &str = "subtract_regression08";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn unparse() {
|
||||||
|
super::unparse(TEST_NAME).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod subtract_regression09 {
|
||||||
|
const TEST_NAME: &str = "subtract_regression09";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn unparse() {
|
||||||
|
super::unparse(TEST_NAME).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod subtract_regression10 {
|
||||||
|
const TEST_NAME: &str = "subtract_regression10";
|
||||||
|
|
||||||
|
/// Test parsing KCL.
|
||||||
|
#[test]
|
||||||
|
fn parse() {
|
||||||
|
super::parse(TEST_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn unparse() {
|
||||||
|
super::unparse(TEST_NAME).await
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test that KCL is executed correctly.
|
||||||
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
|
async fn kcl_test_execute() {
|
||||||
|
super::execute(TEST_NAME, true).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use indexmap::IndexMap;
|
|
||||||
use kcmc::{
|
use kcmc::{
|
||||||
websocket::{ModelingCmdReq, OkWebSocketResponseData},
|
websocket::{ModelingCmdReq, OkWebSocketResponseData},
|
||||||
ModelingCmd,
|
ModelingCmd,
|
||||||
@ -15,8 +14,8 @@ use crate::{
|
|||||||
execution::{
|
execution::{
|
||||||
kcl_value::FunctionSource,
|
kcl_value::FunctionSource,
|
||||||
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitLen, UnitType},
|
types::{NumericType, PrimitiveType, RuntimeType, UnitAngle, UnitLen, UnitType},
|
||||||
ExecState, ExecutorContext, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PlaneInfo, Sketch,
|
ExecState, ExtrudeSurface, Helix, KclObjectFields, KclValue, Metadata, PlaneInfo, Sketch, SketchSurface, Solid,
|
||||||
SketchSurface, Solid, TagIdentifier,
|
TagIdentifier,
|
||||||
},
|
},
|
||||||
parsing::ast::types::TagNode,
|
parsing::ast::types::TagNode,
|
||||||
source_range::SourceRange,
|
source_range::SourceRange,
|
||||||
@ -28,56 +27,11 @@ use crate::{
|
|||||||
ModuleId,
|
ModuleId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub use crate::execution::fn_call::Args;
|
||||||
|
|
||||||
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
|
const ERROR_STRING_SKETCH_TO_SOLID_HELPER: &str =
|
||||||
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
|
"You can convert a sketch (2D) into a Solid (3D) by calling a function like `extrude` or `revolve`";
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Arg {
|
|
||||||
/// The evaluated argument.
|
|
||||||
pub value: KclValue,
|
|
||||||
/// The source range of the unevaluated argument.
|
|
||||||
pub source_range: SourceRange,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Arg {
|
|
||||||
pub fn new(value: KclValue, source_range: SourceRange) -> Self {
|
|
||||||
Self { value, source_range }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn synthetic(value: KclValue) -> Self {
|
|
||||||
Self {
|
|
||||||
value,
|
|
||||||
source_range: SourceRange::synthetic(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source_ranges(&self) -> Vec<SourceRange> {
|
|
||||||
vec![self.source_range]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
|
||||||
pub struct KwArgs {
|
|
||||||
/// Unlabeled keyword args. Currently only the first arg can be unlabeled.
|
|
||||||
/// If the argument was a local variable, then the first element of the tuple is its name
|
|
||||||
/// which may be used to treat this arg as a labelled arg.
|
|
||||||
pub unlabeled: Option<(Option<String>, Arg)>,
|
|
||||||
/// Labeled args.
|
|
||||||
pub labeled: IndexMap<String, Arg>,
|
|
||||||
pub errors: Vec<Arg>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KwArgs {
|
|
||||||
/// How many arguments are there?
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.labeled.len() + if self.unlabeled.is_some() { 1 } else { 0 }
|
|
||||||
}
|
|
||||||
/// Are there no arguments?
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
self.labeled.len() == 0 && self.unlabeled.is_none()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||||
#[ts(export)]
|
#[ts(export)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
@ -153,41 +107,7 @@ impl JsonSchema for TyF64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Args {
|
|
||||||
/// Positional args.
|
|
||||||
pub args: Vec<Arg>,
|
|
||||||
/// Keyword arguments
|
|
||||||
pub kw_args: KwArgs,
|
|
||||||
pub source_range: SourceRange,
|
|
||||||
pub ctx: ExecutorContext,
|
|
||||||
/// If this call happens inside a pipe (|>) expression, this holds the LHS of that |>.
|
|
||||||
/// Otherwise it's None.
|
|
||||||
pub pipe_value: Option<Arg>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Args {
|
impl Args {
|
||||||
pub fn new(args: Vec<Arg>, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
|
|
||||||
Self {
|
|
||||||
args,
|
|
||||||
kw_args: Default::default(),
|
|
||||||
source_range,
|
|
||||||
ctx,
|
|
||||||
pipe_value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect the given keyword arguments.
|
|
||||||
pub fn new_kw(kw_args: KwArgs, source_range: SourceRange, ctx: ExecutorContext, pipe_value: Option<Arg>) -> Self {
|
|
||||||
Self {
|
|
||||||
args: Default::default(),
|
|
||||||
kw_args,
|
|
||||||
source_range,
|
|
||||||
ctx,
|
|
||||||
pipe_value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a keyword argument. If not set, returns None.
|
/// Get a keyword argument. If not set, returns None.
|
||||||
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
|
pub(crate) fn get_kw_arg_opt<'a, T>(&'a self, label: &str) -> Result<Option<T>, KclError>
|
||||||
where
|
where
|
||||||
@ -339,16 +259,6 @@ impl Args {
|
|||||||
.collect::<Result<Vec<_>, _>>()
|
.collect::<Result<Vec<_>, _>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the unlabeled keyword argument. If not set, returns None.
|
|
||||||
pub(crate) fn unlabeled_kw_arg_unconverted(&self) -> Option<&Arg> {
|
|
||||||
self.kw_args
|
|
||||||
.unlabeled
|
|
||||||
.as_ref()
|
|
||||||
.map(|(_, a)| a)
|
|
||||||
.or(self.args.first())
|
|
||||||
.or(self.pipe_value.as_ref())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the unlabeled keyword argument. If not set, returns Err. If it
|
/// Get the unlabeled keyword argument. If not set, returns Err. If it
|
||||||
/// can't be converted to the given type, returns Err.
|
/// can't be converted to the given type, returns Err.
|
||||||
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
|
||||||
@ -1283,6 +1193,32 @@ impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrEdgeReference {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis2dOrPoint2d {
|
||||||
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
|
let case1 = |arg: &KclValue| {
|
||||||
|
let obj = arg.as_object()?;
|
||||||
|
let_field_of!(obj, direction);
|
||||||
|
let_field_of!(obj, origin);
|
||||||
|
Some(Self::Axis { direction, origin })
|
||||||
|
};
|
||||||
|
let case2 = <[TyF64; 2]>::from_kcl_val;
|
||||||
|
case1(arg).or_else(|| case2(arg).map(Self::Point))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromKclValue<'a> for super::axis_or_reference::Axis3dOrPoint3d {
|
||||||
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
|
let case1 = |arg: &KclValue| {
|
||||||
|
let obj = arg.as_object()?;
|
||||||
|
let_field_of!(obj, direction);
|
||||||
|
let_field_of!(obj, origin);
|
||||||
|
Some(Self::Axis { direction, origin })
|
||||||
|
};
|
||||||
|
let case2 = <[TyF64; 3]>::from_kcl_val;
|
||||||
|
case1(arg).or_else(|| case2(arg).map(Self::Point))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> FromKclValue<'a> for i64 {
|
impl<'a> FromKclValue<'a> for i64 {
|
||||||
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
|
||||||
match arg {
|
match arg {
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
use super::{
|
|
||||||
args::{Arg, KwArgs},
|
|
||||||
Args,
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
|
fn_call::{Arg, Args, KwArgs},
|
||||||
kcl_value::{FunctionSource, KclValue},
|
kcl_value::{FunctionSource, KclValue},
|
||||||
types::RuntimeType,
|
types::RuntimeType,
|
||||||
ExecState,
|
ExecState,
|
||||||
|
@ -21,3 +21,41 @@ pub enum Axis3dOrEdgeReference {
|
|||||||
/// Tagged edge.
|
/// Tagged edge.
|
||||||
Edge(EdgeReference),
|
Edge(EdgeReference),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A 2D axis or a raw point2d.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Axis2dOrPoint2d {
|
||||||
|
/// 2D axis and origin.
|
||||||
|
Axis { direction: [TyF64; 2], origin: [TyF64; 2] },
|
||||||
|
/// Raw point2d.
|
||||||
|
Point([TyF64; 2]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Axis2dOrPoint2d {
|
||||||
|
/// Convert to a 2D axis.
|
||||||
|
pub fn to_point2d(&self) -> [TyF64; 2] {
|
||||||
|
match self {
|
||||||
|
Axis2dOrPoint2d::Axis { direction, origin: _ } => direction.clone(),
|
||||||
|
Axis2dOrPoint2d::Point(point) => point.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A 3D axis or a raw point3d.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum Axis3dOrPoint3d {
|
||||||
|
/// 3D axis and origin.
|
||||||
|
Axis { direction: [TyF64; 3], origin: [TyF64; 3] },
|
||||||
|
/// Raw point3d.
|
||||||
|
Point([TyF64; 3]),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Axis3dOrPoint3d {
|
||||||
|
/// Convert to a 3D axis.
|
||||||
|
pub fn to_point3d(&self) -> [TyF64; 3] {
|
||||||
|
match self {
|
||||||
|
Axis3dOrPoint3d::Axis { direction, origin: _ } => direction.clone(),
|
||||||
|
Axis3dOrPoint3d::Point(point) => point.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -59,10 +59,7 @@ async fn inner_clone(
|
|||||||
let mut new_sketch = sketch.clone();
|
let mut new_sketch = sketch.clone();
|
||||||
new_sketch.id = new_id;
|
new_sketch.id = new_id;
|
||||||
new_sketch.original_id = new_id;
|
new_sketch.original_id = new_id;
|
||||||
#[cfg(feature = "artifact-graph")]
|
new_sketch.artifact_id = new_id.into();
|
||||||
{
|
|
||||||
new_sketch.artifact_id = new_id.into();
|
|
||||||
}
|
|
||||||
GeometryWithImportedGeometry::Sketch(new_sketch)
|
GeometryWithImportedGeometry::Sketch(new_sketch)
|
||||||
}
|
}
|
||||||
GeometryWithImportedGeometry::Solid(solid) => {
|
GeometryWithImportedGeometry::Solid(solid) => {
|
||||||
@ -72,10 +69,7 @@ async fn inner_clone(
|
|||||||
let mut new_solid = solid.clone();
|
let mut new_solid = solid.clone();
|
||||||
new_solid.id = new_id;
|
new_solid.id = new_id;
|
||||||
new_solid.sketch.original_id = new_id;
|
new_solid.sketch.original_id = new_id;
|
||||||
#[cfg(feature = "artifact-graph")]
|
new_solid.artifact_id = new_id.into();
|
||||||
{
|
|
||||||
new_solid.artifact_id = new_id.into();
|
|
||||||
}
|
|
||||||
GeometryWithImportedGeometry::Solid(new_solid)
|
GeometryWithImportedGeometry::Solid(new_solid)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -118,10 +112,7 @@ async fn fix_tags_and_references(
|
|||||||
// Make the sketch id the new geometry id.
|
// Make the sketch id the new geometry id.
|
||||||
solid.sketch.id = new_geometry_id;
|
solid.sketch.id = new_geometry_id;
|
||||||
solid.sketch.original_id = new_geometry_id;
|
solid.sketch.original_id = new_geometry_id;
|
||||||
#[cfg(feature = "artifact-graph")]
|
solid.sketch.artifact_id = new_geometry_id.into();
|
||||||
{
|
|
||||||
solid.sketch.artifact_id = new_geometry_id.into();
|
|
||||||
}
|
|
||||||
|
|
||||||
fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state).await?;
|
fix_sketch_tags_and_references(&mut solid.sketch, &entity_id_map, exec_state).await?;
|
||||||
|
|
||||||
@ -148,7 +139,6 @@ async fn fix_tags_and_references(
|
|||||||
// information.
|
// information.
|
||||||
let new_solid = do_post_extrude(
|
let new_solid = do_post_extrude(
|
||||||
&solid.sketch,
|
&solid.sketch,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
new_geometry_id.into(),
|
new_geometry_id.into(),
|
||||||
crate::std::args::TyF64::new(
|
crate::std::args::TyF64::new(
|
||||||
solid.height,
|
solid.height,
|
||||||
@ -332,10 +322,8 @@ clonedCube = clone(cube)
|
|||||||
|
|
||||||
assert_ne!(cube.id, cloned_cube.id);
|
assert_ne!(cube.id, cloned_cube.id);
|
||||||
assert_ne!(cube.original_id, cloned_cube.original_id);
|
assert_ne!(cube.original_id, cloned_cube.original_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
||||||
assert_eq!(cloned_cube.original_id, cloned_cube.id);
|
assert_eq!(cloned_cube.original_id, cloned_cube.id);
|
||||||
|
|
||||||
@ -384,12 +372,9 @@ clonedCube = clone(cube)
|
|||||||
assert_ne!(cube.id, cloned_cube.id);
|
assert_ne!(cube.id, cloned_cube.id);
|
||||||
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
||||||
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
||||||
|
|
||||||
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
||||||
@ -501,12 +486,9 @@ clonedCube = clone(cube)
|
|||||||
assert_ne!(cube.id, cloned_cube.id);
|
assert_ne!(cube.id, cloned_cube.id);
|
||||||
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
||||||
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
||||||
|
|
||||||
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
||||||
@ -576,12 +558,9 @@ clonedCube = clone(cube)
|
|||||||
assert_ne!(cube.id, cloned_cube.id);
|
assert_ne!(cube.id, cloned_cube.id);
|
||||||
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
||||||
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
||||||
|
|
||||||
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
for (path, cloned_path) in cube.sketch.paths.iter().zip(cloned_cube.sketch.paths.iter()) {
|
||||||
@ -679,12 +658,9 @@ clonedCube = clone(cube)
|
|||||||
assert_ne!(cube.id, cloned_cube.id);
|
assert_ne!(cube.id, cloned_cube.id);
|
||||||
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
assert_ne!(cube.sketch.id, cloned_cube.sketch.id);
|
||||||
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
assert_ne!(cube.sketch.original_id, cloned_cube.sketch.original_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
assert_ne!(cube.artifact_id, cloned_cube.artifact_id);
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
assert_ne!(cube.sketch.artifact_id, cloned_cube.sketch.artifact_id);
|
||||||
|
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
assert_eq!(cloned_cube.artifact_id, cloned_cube.id.into());
|
||||||
|
|
||||||
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
|
for (value, cloned_value) in cube.value.iter().zip(cloned_cube.value.iter()) {
|
||||||
|
@ -17,11 +17,12 @@ use kittycad_modeling_cmds::{self as kcmc};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::args::TyF64;
|
use super::args::TyF64;
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
use crate::execution::ArtifactId;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{types::RuntimeType, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface, Solid},
|
execution::{
|
||||||
|
types::RuntimeType, ArtifactId, ExecState, ExtrudeSurface, GeoMeta, KclValue, Path, Sketch, SketchSurface,
|
||||||
|
Solid,
|
||||||
|
},
|
||||||
parsing::ast::types::TagNode,
|
parsing::ast::types::TagNode,
|
||||||
std::Args,
|
std::Args,
|
||||||
};
|
};
|
||||||
@ -210,7 +211,6 @@ async fn inner_extrude(
|
|||||||
solids.push(
|
solids.push(
|
||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
sketch,
|
sketch,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
id.into(),
|
id.into(),
|
||||||
length.clone(),
|
length.clone(),
|
||||||
false,
|
false,
|
||||||
@ -238,7 +238,7 @@ pub(crate) struct NamedCapTags<'a> {
|
|||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
pub(crate) async fn do_post_extrude<'a>(
|
pub(crate) async fn do_post_extrude<'a>(
|
||||||
sketch: &Sketch,
|
sketch: &Sketch,
|
||||||
#[cfg(feature = "artifact-graph")] solid_id: ArtifactId,
|
solid_id: ArtifactId,
|
||||||
length: TyF64,
|
length: TyF64,
|
||||||
sectional: bool,
|
sectional: bool,
|
||||||
named_cap_tags: &'a NamedCapTags<'a>,
|
named_cap_tags: &'a NamedCapTags<'a>,
|
||||||
@ -431,7 +431,6 @@ pub(crate) async fn do_post_extrude<'a>(
|
|||||||
// that we passed in to the function, but it's actually the id of the
|
// that we passed in to the function, but it's actually the id of the
|
||||||
// sketch.
|
// sketch.
|
||||||
id: sketch.id,
|
id: sketch.id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: solid_id,
|
artifact_id: solid_id,
|
||||||
value: new_value,
|
value: new_value,
|
||||||
meta: sketch.meta.clone(),
|
meta: sketch.meta.clone(),
|
||||||
|
@ -110,7 +110,6 @@ async fn inner_helix(
|
|||||||
|
|
||||||
let helix_result = Box::new(HelixValue {
|
let helix_result = Box::new(HelixValue {
|
||||||
value: id,
|
value: id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: id.into(),
|
artifact_id: id.into(),
|
||||||
revolutions,
|
revolutions,
|
||||||
angle_start,
|
angle_start,
|
||||||
|
@ -177,7 +177,6 @@ async fn inner_loft(
|
|||||||
Ok(Box::new(
|
Ok(Box::new(
|
||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
&sketch,
|
&sketch,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
id.into(),
|
id.into(),
|
||||||
TyF64::new(0.0, NumericType::mm()),
|
TyF64::new(0.0, NumericType::mm()),
|
||||||
false,
|
false,
|
||||||
|
@ -17,8 +17,6 @@ use crate::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
/// Mirror a sketch.
|
/// Mirror a sketch.
|
||||||
///
|
|
||||||
/// Only works on unclosed sketches for now.
|
|
||||||
pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||||
let axis = args.get_kw_arg_typed(
|
let axis = args.get_kw_arg_typed(
|
||||||
@ -35,8 +33,6 @@ pub async fn mirror_2d(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Mirror a sketch.
|
/// Mirror a sketch.
|
||||||
///
|
|
||||||
/// Only works on unclosed sketches for now.
|
|
||||||
async fn inner_mirror_2d(
|
async fn inner_mirror_2d(
|
||||||
sketches: Vec<Sketch>,
|
sketches: Vec<Sketch>,
|
||||||
axis: Axis2dOrEdgeReference,
|
axis: Axis2dOrEdgeReference,
|
||||||
|
@ -328,14 +328,14 @@ impl StdLib {
|
|||||||
self.fns.get(name).cloned()
|
self.fns.get(name).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_either(&self, name: &Name) -> FunctionKind {
|
pub fn get_rust_function(&self, name: &Name) -> Option<Box<dyn StdLibFn>> {
|
||||||
if let Some(name) = name.local_ident() {
|
if let Some(name) = name.local_ident() {
|
||||||
if let Some(f) = self.get(name.inner) {
|
if let Some(f) = self.get(name.inner) {
|
||||||
return FunctionKind::Core(f);
|
return Some(f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionKind::UserDefined
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn contains_key(&self, key: &str) -> bool {
|
pub fn contains_key(&self, key: &str) -> bool {
|
||||||
@ -349,11 +349,5 @@ impl Default for StdLib {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum FunctionKind {
|
|
||||||
Core(Box<dyn StdLibFn>),
|
|
||||||
UserDefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
||||||
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
||||||
|
@ -15,21 +15,24 @@ use kittycad_modeling_cmds::{
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::{
|
|
||||||
args::{Arg, KwArgs},
|
|
||||||
utils::{point_3d_to_mm, point_to_mm},
|
|
||||||
};
|
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{KclError, KclErrorDetails},
|
errors::{KclError, KclErrorDetails},
|
||||||
execution::{
|
execution::{
|
||||||
|
fn_call::{Arg, Args, KwArgs},
|
||||||
kcl_value::FunctionSource,
|
kcl_value::FunctionSource,
|
||||||
types::{NumericType, RuntimeType},
|
types::{NumericType, PrimitiveType, RuntimeType},
|
||||||
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Sketch, Solid,
|
ExecState, Geometries, Geometry, KclObjectFields, KclValue, Sketch, Solid,
|
||||||
},
|
},
|
||||||
std::{args::TyF64, Args},
|
std::{
|
||||||
|
args::TyF64,
|
||||||
|
axis_or_reference::Axis2dOrPoint2d,
|
||||||
|
utils::{point_3d_to_mm, point_to_mm},
|
||||||
|
},
|
||||||
ExecutorContext, SourceRange,
|
ExecutorContext, SourceRange,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::axis_or_reference::Axis3dOrPoint3d;
|
||||||
|
|
||||||
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
|
const MUST_HAVE_ONE_INSTANCE: &str = "There must be at least 1 instance of your geometry";
|
||||||
|
|
||||||
/// Repeat some 3D solid, changing each repetition slightly.
|
/// Repeat some 3D solid, changing each repetition slightly.
|
||||||
@ -742,9 +745,17 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||||
let instances: u32 = args.get_kw_arg("instances")?;
|
let instances: u32 = args.get_kw_arg("instances")?;
|
||||||
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
||||||
let axis: [TyF64; 2] = args.get_kw_arg_typed("axis", &RuntimeType::point2d(), exec_state)?;
|
let axis: Axis2dOrPoint2d = args.get_kw_arg_typed(
|
||||||
|
"axis",
|
||||||
|
&RuntimeType::Union(vec![
|
||||||
|
RuntimeType::Primitive(PrimitiveType::Axis2d),
|
||||||
|
RuntimeType::point2d(),
|
||||||
|
]),
|
||||||
|
exec_state,
|
||||||
|
)?;
|
||||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||||
|
|
||||||
|
let axis = axis.to_point2d();
|
||||||
if axis[0].n == 0.0 && axis[1].n == 0.0 {
|
if axis[0].n == 0.0 && axis[1].n == 0.0 {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message:
|
message:
|
||||||
@ -762,6 +773,22 @@ pub async fn pattern_linear_2d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// of distance between each repetition, some specified number of times.
|
/// of distance between each repetition, some specified number of times.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
/// /// Pattern using a named axis.
|
||||||
|
///
|
||||||
|
/// exampleSketch = startSketchOn(XZ)
|
||||||
|
/// |> circle(center = [0, 0], radius = 1)
|
||||||
|
/// |> patternLinear2d(
|
||||||
|
/// axis = X,
|
||||||
|
/// instances = 7,
|
||||||
|
/// distance = 4
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// example = extrude(exampleSketch, length = 1)
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// /// Pattern using a raw axis.
|
||||||
|
///
|
||||||
/// exampleSketch = startSketchOn(XZ)
|
/// exampleSketch = startSketchOn(XZ)
|
||||||
/// |> circle(center = [0, 0], radius = 1)
|
/// |> circle(center = [0, 0], radius = 1)
|
||||||
/// |> patternLinear2d(
|
/// |> patternLinear2d(
|
||||||
@ -821,9 +848,17 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
let solids = args.get_unlabeled_kw_arg_typed("solids", &RuntimeType::solids(), exec_state)?;
|
||||||
let instances: u32 = args.get_kw_arg("instances")?;
|
let instances: u32 = args.get_kw_arg("instances")?;
|
||||||
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
let distance: TyF64 = args.get_kw_arg_typed("distance", &RuntimeType::length(), exec_state)?;
|
||||||
let axis: [TyF64; 3] = args.get_kw_arg_typed("axis", &RuntimeType::point3d(), exec_state)?;
|
let axis: Axis3dOrPoint3d = args.get_kw_arg_typed(
|
||||||
|
"axis",
|
||||||
|
&RuntimeType::Union(vec![
|
||||||
|
RuntimeType::Primitive(PrimitiveType::Axis3d),
|
||||||
|
RuntimeType::point3d(),
|
||||||
|
]),
|
||||||
|
exec_state,
|
||||||
|
)?;
|
||||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||||
|
|
||||||
|
let axis = axis.to_point3d();
|
||||||
if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 {
|
if axis[0].n == 0.0 && axis[1].n == 0.0 && axis[2].n == 0.0 {
|
||||||
return Err(KclError::Semantic(KclErrorDetails {
|
return Err(KclError::Semantic(KclErrorDetails {
|
||||||
message:
|
message:
|
||||||
@ -841,6 +876,26 @@ pub async fn pattern_linear_3d(exec_state: &mut ExecState, args: Args) -> Result
|
|||||||
/// of distance between each repetition, some specified number of times.
|
/// of distance between each repetition, some specified number of times.
|
||||||
///
|
///
|
||||||
/// ```no_run
|
/// ```no_run
|
||||||
|
/// /// Pattern using a named axis.
|
||||||
|
///
|
||||||
|
/// exampleSketch = startSketchOn(XZ)
|
||||||
|
/// |> startProfile(at = [0, 0])
|
||||||
|
/// |> line(end = [0, 2])
|
||||||
|
/// |> line(end = [3, 1])
|
||||||
|
/// |> line(end = [0, -4])
|
||||||
|
/// |> close()
|
||||||
|
///
|
||||||
|
/// example = extrude(exampleSketch, length = 1)
|
||||||
|
/// |> patternLinear3d(
|
||||||
|
/// axis = X,
|
||||||
|
/// instances = 7,
|
||||||
|
/// distance = 6
|
||||||
|
/// )
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// /// Pattern using a raw axis.
|
||||||
|
///
|
||||||
/// exampleSketch = startSketchOn(XZ)
|
/// exampleSketch = startSketchOn(XZ)
|
||||||
/// |> startProfile(at = [0, 0])
|
/// |> startProfile(at = [0, 0])
|
||||||
/// |> line(end = [0, 2])
|
/// |> line(end = [0, 2])
|
||||||
@ -959,9 +1014,9 @@ struct CircularPattern2dData {
|
|||||||
/// The center about which to make the pattern. This is a 2D vector.
|
/// The center about which to make the pattern. This is a 2D vector.
|
||||||
pub center: [TyF64; 2],
|
pub center: [TyF64; 2],
|
||||||
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||||
pub arc_degrees: f64,
|
pub arc_degrees: Option<f64>,
|
||||||
/// Whether or not to rotate the duplicates as they are copied.
|
/// Whether or not to rotate the duplicates as they are copied.
|
||||||
pub rotate_duplicates: bool,
|
pub rotate_duplicates: Option<bool>,
|
||||||
/// If the target being patterned is itself a pattern, then, should you use the original solid,
|
/// If the target being patterned is itself a pattern, then, should you use the original solid,
|
||||||
/// or the pattern?
|
/// or the pattern?
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -983,9 +1038,9 @@ struct CircularPattern3dData {
|
|||||||
/// The center about which to make the pattern. This is a 3D vector.
|
/// The center about which to make the pattern. This is a 3D vector.
|
||||||
pub center: [TyF64; 3],
|
pub center: [TyF64; 3],
|
||||||
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
/// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||||
pub arc_degrees: f64,
|
pub arc_degrees: Option<f64>,
|
||||||
/// Whether or not to rotate the duplicates as they are copied.
|
/// Whether or not to rotate the duplicates as they are copied.
|
||||||
pub rotate_duplicates: bool,
|
pub rotate_duplicates: Option<bool>,
|
||||||
/// If the target being patterned is itself a pattern, then, should you use the original solid,
|
/// If the target being patterned is itself a pattern, then, should you use the original solid,
|
||||||
/// or the pattern?
|
/// or the pattern?
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@ -1040,14 +1095,14 @@ impl CircularPattern {
|
|||||||
RepetitionsNeeded::from(n)
|
RepetitionsNeeded::from(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn arc_degrees(&self) -> f64 {
|
pub fn arc_degrees(&self) -> Option<f64> {
|
||||||
match self {
|
match self {
|
||||||
CircularPattern::TwoD(lp) => lp.arc_degrees,
|
CircularPattern::TwoD(lp) => lp.arc_degrees,
|
||||||
CircularPattern::ThreeD(lp) => lp.arc_degrees,
|
CircularPattern::ThreeD(lp) => lp.arc_degrees,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotate_duplicates(&self) -> bool {
|
pub fn rotate_duplicates(&self) -> Option<bool> {
|
||||||
match self {
|
match self {
|
||||||
CircularPattern::TwoD(lp) => lp.rotate_duplicates,
|
CircularPattern::TwoD(lp) => lp.rotate_duplicates,
|
||||||
CircularPattern::ThreeD(lp) => lp.rotate_duplicates,
|
CircularPattern::ThreeD(lp) => lp.rotate_duplicates,
|
||||||
@ -1067,15 +1122,15 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
let sketches = args.get_unlabeled_kw_arg_typed("sketches", &RuntimeType::sketches(), exec_state)?;
|
||||||
let instances: u32 = args.get_kw_arg("instances")?;
|
let instances: u32 = args.get_kw_arg("instances")?;
|
||||||
let center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
|
let center: [TyF64; 2] = args.get_kw_arg_typed("center", &RuntimeType::point2d(), exec_state)?;
|
||||||
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
let arc_degrees: Option<TyF64> = args.get_kw_arg_opt_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
||||||
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
|
let rotate_duplicates: Option<bool> = args.get_kw_arg_opt("rotateDuplicates")?;
|
||||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||||
|
|
||||||
let sketches = inner_pattern_circular_2d(
|
let sketches = inner_pattern_circular_2d(
|
||||||
sketches,
|
sketches,
|
||||||
instances,
|
instances,
|
||||||
center,
|
center,
|
||||||
arc_degrees.n,
|
arc_degrees.map(|x| x.n),
|
||||||
rotate_duplicates,
|
rotate_duplicates,
|
||||||
use_original,
|
use_original,
|
||||||
exec_state,
|
exec_state,
|
||||||
@ -1114,8 +1169,8 @@ pub async fn pattern_circular_2d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
sketch_set = { docs = "Which sketch(es) to pattern" },
|
sketch_set = { docs = "Which sketch(es) to pattern" },
|
||||||
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."},
|
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."},
|
||||||
center = { docs = "The center about which to make the pattern. This is a 2D vector."},
|
center = { docs = "The center about which to make the pattern. This is a 2D vector."},
|
||||||
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
|
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360."},
|
||||||
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied."},
|
rotate_duplicates= { docs = "Whether or not to rotate the duplicates as they are copied. Defaults to true."},
|
||||||
use_original= { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false."},
|
use_original= { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false."},
|
||||||
},
|
},
|
||||||
tags = ["sketch"]
|
tags = ["sketch"]
|
||||||
@ -1125,8 +1180,8 @@ async fn inner_pattern_circular_2d(
|
|||||||
sketch_set: Vec<Sketch>,
|
sketch_set: Vec<Sketch>,
|
||||||
instances: u32,
|
instances: u32,
|
||||||
center: [TyF64; 2],
|
center: [TyF64; 2],
|
||||||
arc_degrees: f64,
|
arc_degrees: Option<f64>,
|
||||||
rotate_duplicates: bool,
|
rotate_duplicates: Option<bool>,
|
||||||
use_original: Option<bool>,
|
use_original: Option<bool>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
args: Args,
|
args: Args,
|
||||||
@ -1180,9 +1235,9 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
// The center about which to make the pattern. This is a 3D vector.
|
// The center about which to make the pattern. This is a 3D vector.
|
||||||
let center: [TyF64; 3] = args.get_kw_arg_typed("center", &RuntimeType::point3d(), exec_state)?;
|
let center: [TyF64; 3] = args.get_kw_arg_typed("center", &RuntimeType::point3d(), exec_state)?;
|
||||||
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
// The arc angle (in degrees) to place the repetitions. Must be greater than 0.
|
||||||
let arc_degrees: TyF64 = args.get_kw_arg_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
let arc_degrees: Option<TyF64> = args.get_kw_arg_opt_typed("arcDegrees", &RuntimeType::degrees(), exec_state)?;
|
||||||
// Whether or not to rotate the duplicates as they are copied.
|
// Whether or not to rotate the duplicates as they are copied.
|
||||||
let rotate_duplicates: bool = args.get_kw_arg("rotateDuplicates")?;
|
let rotate_duplicates: Option<bool> = args.get_kw_arg_opt("rotateDuplicates")?;
|
||||||
// If the target being patterned is itself a pattern, then, should you use the original solid,
|
// If the target being patterned is itself a pattern, then, should you use the original solid,
|
||||||
// or the pattern?
|
// or the pattern?
|
||||||
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
let use_original: Option<bool> = args.get_kw_arg_opt("useOriginal")?;
|
||||||
@ -1192,7 +1247,7 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
instances,
|
instances,
|
||||||
[axis[0].n, axis[1].n, axis[2].n],
|
[axis[0].n, axis[1].n, axis[2].n],
|
||||||
center,
|
center,
|
||||||
arc_degrees.n,
|
arc_degrees.map(|x| x.n),
|
||||||
rotate_duplicates,
|
rotate_duplicates,
|
||||||
use_original,
|
use_original,
|
||||||
exec_state,
|
exec_state,
|
||||||
@ -1230,8 +1285,8 @@ pub async fn pattern_circular_3d(exec_state: &mut ExecState, args: Args) -> Resu
|
|||||||
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."},
|
instances = { docs = "The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect."},
|
||||||
axis = { docs = "The axis around which to make the pattern. This is a 3D vector"},
|
axis = { docs = "The axis around which to make the pattern. This is a 3D vector"},
|
||||||
center = { docs = "The center about which to make the pattern. This is a 3D vector."},
|
center = { docs = "The center about which to make the pattern. This is a 3D vector."},
|
||||||
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0."},
|
arc_degrees = { docs = "The arc angle (in degrees) to place the repetitions. Must be greater than 0. Defaults to 360."},
|
||||||
rotate_duplicates = { docs = "Whether or not to rotate the duplicates as they are copied."},
|
rotate_duplicates = { docs = "Whether or not to rotate the duplicates as they are copied. Defaults to true."},
|
||||||
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false."},
|
use_original = { docs = "If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false."},
|
||||||
},
|
},
|
||||||
tags = ["solid"]
|
tags = ["solid"]
|
||||||
@ -1242,8 +1297,8 @@ async fn inner_pattern_circular_3d(
|
|||||||
instances: u32,
|
instances: u32,
|
||||||
axis: [f64; 3],
|
axis: [f64; 3],
|
||||||
center: [TyF64; 3],
|
center: [TyF64; 3],
|
||||||
arc_degrees: f64,
|
arc_degrees: Option<f64>,
|
||||||
rotate_duplicates: bool,
|
rotate_duplicates: Option<bool>,
|
||||||
use_original: Option<bool>,
|
use_original: Option<bool>,
|
||||||
exec_state: &mut ExecState,
|
exec_state: &mut ExecState,
|
||||||
args: Args,
|
args: Args,
|
||||||
@ -1327,8 +1382,8 @@ async fn pattern_circular(
|
|||||||
z: LengthUnit(center[2]),
|
z: LengthUnit(center[2]),
|
||||||
},
|
},
|
||||||
num_repetitions,
|
num_repetitions,
|
||||||
arc_degrees: data.arc_degrees(),
|
arc_degrees: data.arc_degrees().unwrap_or(360.0),
|
||||||
rotate_duplicates: data.rotate_duplicates(),
|
rotate_duplicates: data.rotate_duplicates().unwrap_or(true),
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -196,7 +196,6 @@ async fn inner_revolve(
|
|||||||
solids.push(
|
solids.push(
|
||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
sketch,
|
sketch,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
id.into(),
|
id.into(),
|
||||||
TyF64::new(0.0, NumericType::mm()),
|
TyF64::new(0.0, NumericType::mm()),
|
||||||
false,
|
false,
|
||||||
|
@ -1233,7 +1233,6 @@ async fn start_sketch_on_face(
|
|||||||
|
|
||||||
Ok(Box::new(Face {
|
Ok(Box::new(Face {
|
||||||
id: extrude_plane_id,
|
id: extrude_plane_id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: extrude_plane_id.into(),
|
artifact_id: extrude_plane_id.into(),
|
||||||
value: tag.to_string(),
|
value: tag.to_string(),
|
||||||
// TODO: get this from the extrude plane data.
|
// TODO: get this from the extrude plane data.
|
||||||
@ -1414,7 +1413,6 @@ pub(crate) async fn inner_start_profile(
|
|||||||
let sketch = Sketch {
|
let sketch = Sketch {
|
||||||
id: path_id,
|
id: path_id,
|
||||||
original_id: path_id,
|
original_id: path_id,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
artifact_id: path_id.into(),
|
artifact_id: path_id.into(),
|
||||||
on: sketch_surface.clone(),
|
on: sketch_surface.clone(),
|
||||||
paths: vec![],
|
paths: vec![],
|
||||||
|
@ -218,7 +218,6 @@ async fn inner_sweep(
|
|||||||
solids.push(
|
solids.push(
|
||||||
do_post_extrude(
|
do_post_extrude(
|
||||||
sketch,
|
sketch,
|
||||||
#[cfg(feature = "artifact-graph")]
|
|
||||||
id.into(),
|
id.into(),
|
||||||
TyF64::new(0.0, NumericType::mm()),
|
TyF64::new(0.0, NumericType::mm()),
|
||||||
sectional.unwrap_or(false),
|
sectional.unwrap_or(false),
|
||||||
|
@ -133,7 +133,7 @@ export fn reduce(
|
|||||||
initial: any,
|
initial: any,
|
||||||
/// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`.
|
/// Run once per item in the input `array`. This function takes an item from the array, and the previous output from `f` (or `initial` on the very first run). The final time `f` is run, its output is returned as the final output from `reduce`.
|
||||||
f: fn(any, accum: any): any,
|
f: fn(any, accum: any): any,
|
||||||
): [any] {}
|
): any {}
|
||||||
|
|
||||||
/// Append an element to the end of an array.
|
/// Append an element to the end of an array.
|
||||||
///
|
///
|
||||||
|
@ -448,7 +448,7 @@ export fn legLen(
|
|||||||
hypotenuse: number(Length),
|
hypotenuse: number(Length),
|
||||||
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
|
/// The length of one of the triangle's legs (i.e. non-hypotenuse side).
|
||||||
leg: number(Length),
|
leg: number(Length),
|
||||||
): number(deg) {}
|
): number(Length) {}
|
||||||
|
|
||||||
/// Compute the angle of the given leg for x.
|
/// Compute the angle of the given leg for x.
|
||||||
///
|
///
|
||||||
|
@ -277,4 +277,4 @@ export fn revolve(
|
|||||||
tagStart?: tag,
|
tagStart?: tag,
|
||||||
/// A named tag for the face at the end of the revolve.
|
/// A named tag for the face at the end of the revolve.
|
||||||
tagEnd?: tag,
|
tagEnd?: tag,
|
||||||
): Solid {}
|
): [Solid; 1+] {}
|
||||||
|
@ -5,8 +5,6 @@
|
|||||||
|
|
||||||
/// Mirror a sketch.
|
/// Mirror a sketch.
|
||||||
///
|
///
|
||||||
/// Only works on unclosed sketches for now.
|
|
||||||
///
|
|
||||||
/// Mirror occurs around a local sketch axis rather than a global axis.
|
/// Mirror occurs around a local sketch axis rather than a global axis.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
@ -4,19 +4,30 @@ description: Operations executed angled_line.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
|
"unlabeledArg": {
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"artifactId": "[uuid]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -35,17 +46,6 @@ description: Operations executed angled_line.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
|
||||||
"unlabeledArg": {
|
|
||||||
"value": {
|
|
||||||
"type": "Sketch",
|
|
||||||
"value": {
|
|
||||||
"artifactId": "[uuid]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceRange": []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,8 +4,8 @@ description: Error from executing array_elem_pop_empty_fail.kcl
|
|||||||
---
|
---
|
||||||
KCL Semantic error
|
KCL Semantic error
|
||||||
|
|
||||||
× semantic: The input argument of `std::array::pop` requires a value with
|
× semantic: The input argument of `pop` requires a value with type `[any;
|
||||||
│ type `[any; 1+]`, but found [any; 0]
|
│ 1+]`, but found [any; 0]
|
||||||
╭─[2:8]
|
╭─[2:8]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ fail = pop(arr)
|
2 │ fail = pop(arr)
|
||||||
@ -15,8 +15,8 @@ KCL Semantic error
|
|||||||
╰────
|
╰────
|
||||||
╰─▶ KCL Semantic error
|
╰─▶ KCL Semantic error
|
||||||
|
|
||||||
× semantic: The input argument of `std::array::pop` requires a value
|
× semantic: The input argument of `pop` requires a value with type
|
||||||
│ with type `[any; 1+]`, but found [any; 0]
|
│ `[any; 1+]`, but found [any; 0]
|
||||||
╭─[2:12]
|
╭─[2:12]
|
||||||
1 │ arr = []
|
1 │ arr = []
|
||||||
2 │ fail = pop(arr)
|
2 │ fail = pop(arr)
|
||||||
|
@ -4,19 +4,30 @@ description: Operations executed artifact_graph_example_code1.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
|
"unlabeledArg": {
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"artifactId": "[uuid]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -35,21 +46,10 @@ description: Operations executed artifact_graph_example_code1.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
|
||||||
"unlabeledArg": {
|
|
||||||
"value": {
|
|
||||||
"type": "Sketch",
|
|
||||||
"value": {
|
|
||||||
"artifactId": "[uuid]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceRange": []
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "KclStdLibCall",
|
"type": "StdLibCall",
|
||||||
"name": "fillet",
|
"name": "fillet",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -94,6 +94,17 @@ description: Operations executed artifact_graph_example_code1.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
|
"unlabeledArg": {
|
||||||
|
"value": {
|
||||||
|
"type": "Solid",
|
||||||
|
"value": {
|
||||||
|
"artifactId": "[uuid]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"face": {
|
"face": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -104,20 +115,20 @@ description: Operations executed artifact_graph_example_code1.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "startSketchOn",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Solid",
|
"type": "Sketch",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -136,17 +147,6 @@ description: Operations executed artifact_graph_example_code1.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
|
||||||
"unlabeledArg": {
|
|
||||||
"value": {
|
|
||||||
"type": "Sketch",
|
|
||||||
"value": {
|
|
||||||
"artifactId": "[uuid]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceRange": []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,29 +4,29 @@ description: Operations executed artifact_graph_example_code_no_3d.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,7 +4,7 @@ description: Operations executed artifact_graph_example_code_offset_planes.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"type": "KclStdLibCall",
|
"type": "StdLibCall",
|
||||||
"name": "offsetPlane",
|
"name": "offsetPlane",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -34,7 +34,7 @@ description: Operations executed artifact_graph_example_code_offset_planes.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "KclStdLibCall",
|
"type": "StdLibCall",
|
||||||
"name": "offsetPlane",
|
"name": "offsetPlane",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -64,7 +64,7 @@ description: Operations executed artifact_graph_example_code_offset_planes.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "KclStdLibCall",
|
"type": "StdLibCall",
|
||||||
"name": "offsetPlane",
|
"name": "offsetPlane",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -94,16 +94,16 @@ description: Operations executed artifact_graph_example_code_offset_planes.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -4,19 +4,30 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
|
"unlabeledArg": {
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"artifactId": "[uuid]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -35,20 +46,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Sketch",
|
"type": "Solid",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"face": {
|
"face": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -59,20 +70,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "startSketchOn",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Solid",
|
"type": "Sketch",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -91,20 +102,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Sketch",
|
"type": "Solid",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"face": {
|
"face": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -114,20 +125,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "startSketchOn",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Solid",
|
"type": "Sketch",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -146,20 +157,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Sketch",
|
"type": "Solid",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"face": {
|
"face": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -170,20 +181,20 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "startSketchOn",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
},
|
||||||
|
{
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Solid",
|
"type": "Sketch",
|
||||||
"value": {
|
"value": {
|
||||||
"artifactId": "[uuid]"
|
"artifactId": "[uuid]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
},
|
|
||||||
{
|
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -202,17 +213,6 @@ description: Operations executed artifact_graph_sketch_on_face_etc.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
|
||||||
"unlabeledArg": {
|
|
||||||
"value": {
|
|
||||||
"type": "Sketch",
|
|
||||||
"value": {
|
|
||||||
"artifactId": "[uuid]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceRange": []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2,42 +2,26 @@
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph path3 [Path]
|
subgraph path3 [Path]
|
||||||
3["Path<br>[74, 114, 1]"]
|
3["Path<br>[74, 114, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
5["Segment<br>[120, 137, 1]"]
|
5["Segment<br>[120, 137, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
6["Segment<br>[143, 161, 1]"]
|
6["Segment<br>[143, 161, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
7["Segment<br>[167, 185, 1]"]
|
7["Segment<br>[167, 185, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
8["Segment<br>[191, 247, 1]"]
|
8["Segment<br>[191, 247, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
9["Segment<br>[253, 260, 1]"]
|
9["Segment<br>[253, 260, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
15[Solid2d]
|
15[Solid2d]
|
||||||
end
|
end
|
||||||
subgraph path4 [Path]
|
subgraph path4 [Path]
|
||||||
4["Path<br>[74, 112, 2]"]
|
4["Path<br>[74, 112, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
10["Segment<br>[118, 135, 2]"]
|
10["Segment<br>[118, 135, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
11["Segment<br>[141, 159, 2]"]
|
11["Segment<br>[141, 159, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
12["Segment<br>[165, 183, 2]"]
|
12["Segment<br>[165, 183, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
13["Segment<br>[189, 245, 2]"]
|
13["Segment<br>[189, 245, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
14["Segment<br>[251, 258, 2]"]
|
14["Segment<br>[251, 258, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
16[Solid2d]
|
16[Solid2d]
|
||||||
end
|
end
|
||||||
1["Plane<br>[47, 64, 1]"]
|
1["Plane<br>[47, 64, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
2["Plane<br>[47, 64, 2]"]
|
2["Plane<br>[47, 64, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
17["Sweep Extrusion<br>[266, 288, 1]"]
|
17["Sweep Extrusion<br>[266, 288, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
18["Sweep Extrusion<br>[264, 286, 2]"]
|
18["Sweep Extrusion<br>[264, 286, 2]"]
|
||||||
%% Missing NodePath
|
|
||||||
19[Wall]
|
19[Wall]
|
||||||
%% face_code_ref=Missing NodePath
|
%% face_code_ref=Missing NodePath
|
||||||
20[Wall]
|
20[Wall]
|
||||||
|
@ -2,22 +2,16 @@
|
|||||||
flowchart LR
|
flowchart LR
|
||||||
subgraph path3 [Path]
|
subgraph path3 [Path]
|
||||||
3["Path<br>[195, 230, 1]"]
|
3["Path<br>[195, 230, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
5["Segment<br>[195, 230, 1]"]
|
5["Segment<br>[195, 230, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
8[Solid2d]
|
8[Solid2d]
|
||||||
end
|
end
|
||||||
subgraph path4 [Path]
|
subgraph path4 [Path]
|
||||||
4["Path<br>[111, 146, 3]"]
|
4["Path<br>[111, 146, 3]"]
|
||||||
%% Missing NodePath
|
|
||||||
6["Segment<br>[111, 146, 3]"]
|
6["Segment<br>[111, 146, 3]"]
|
||||||
%% Missing NodePath
|
|
||||||
7[Solid2d]
|
7[Solid2d]
|
||||||
end
|
end
|
||||||
1["Plane<br>[172, 189, 1]"]
|
1["Plane<br>[172, 189, 1]"]
|
||||||
%% Missing NodePath
|
|
||||||
2["Plane<br>[88, 105, 3]"]
|
2["Plane<br>[88, 105, 3]"]
|
||||||
%% Missing NodePath
|
|
||||||
1 --- 3
|
1 --- 3
|
||||||
2 --- 4
|
2 --- 4
|
||||||
3 --- 5
|
3 --- 5
|
||||||
|
@ -4,19 +4,30 @@ description: Operations executed basic_fillet_cube_close_opposite.kcl
|
|||||||
---
|
---
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"labeledArgs": {},
|
|
||||||
"name": "startSketchOn",
|
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
"type": "StdLibCall",
|
||||||
|
"name": "startSketchOn",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
"type": "Plane",
|
"type": "Plane",
|
||||||
"artifact_id": "[uuid]"
|
"artifact_id": "[uuid]"
|
||||||
},
|
},
|
||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
},
|
||||||
|
"labeledArgs": {},
|
||||||
|
"sourceRange": []
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"type": "StdLibCall",
|
||||||
|
"name": "extrude",
|
||||||
|
"unlabeledArg": {
|
||||||
|
"value": {
|
||||||
|
"type": "Sketch",
|
||||||
|
"value": {
|
||||||
|
"artifactId": "[uuid]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sourceRange": []
|
||||||
|
},
|
||||||
"labeledArgs": {
|
"labeledArgs": {
|
||||||
"length": {
|
"length": {
|
||||||
"value": {
|
"value": {
|
||||||
@ -35,21 +46,10 @@ description: Operations executed basic_fillet_cube_close_opposite.kcl
|
|||||||
"sourceRange": []
|
"sourceRange": []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "extrude",
|
"sourceRange": []
|
||||||
"sourceRange": [],
|
|
||||||
"type": "StdLibCall",
|
|
||||||
"unlabeledArg": {
|
|
||||||
"value": {
|
|
||||||
"type": "Sketch",
|
|
||||||
"value": {
|
|
||||||
"artifactId": "[uuid]"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"sourceRange": []
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "KclStdLibCall",
|
"type": "StdLibCall",
|
||||||
"name": "fillet",
|
"name": "fillet",
|
||||||
"unlabeledArg": {
|
"unlabeledArg": {
|
||||||
"value": {
|
"value": {
|
||||||
|