Compare commits
34 Commits
kcl-test-s
...
v0.27.0
Author | SHA1 | Date | |
---|---|---|---|
30afa65ccf | |||
a2f9e70d18 | |||
986675fe89 | |||
d8ce5ad8bd | |||
1a9926be8a | |||
54b5774f9e | |||
66bbbf81e2 | |||
652519aeae | |||
f826afb32d | |||
f71fafdece | |||
16b7544d69 | |||
34f019305b | |||
79e06b3a00 | |||
24bc4fcd8c | |||
97b9529c81 | |||
8e5fc02941 | |||
909690f3c7 | |||
14ba66378d | |||
b2e895e508 | |||
05f4f34269 | |||
fbb7b08b62 | |||
16c7a2457a | |||
c429bc6ed7 | |||
a0493cb332 | |||
b798f7da03 | |||
c5d051855f | |||
0b24216dc5 | |||
2d3841bf61 | |||
51a71a180e | |||
1ec25dfe96 | |||
8de29dd461 | |||
b11040c23c | |||
2bc4f076cb | |||
9e1cf90c81 |
4
.github/workflows/cargo-test.yml
vendored
@ -5,6 +5,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'src/wasm-lib/**.rs'
|
- 'src/wasm-lib/**.rs'
|
||||||
- 'src/wasm-lib/**.hbs'
|
- 'src/wasm-lib/**.hbs'
|
||||||
|
- 'src/wasm-lib/**.gen'
|
||||||
|
- 'src/wasm-lib/**.snap'
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
@ -15,6 +17,8 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- 'src/wasm-lib/**.rs'
|
- 'src/wasm-lib/**.rs'
|
||||||
- 'src/wasm-lib/**.hbs'
|
- 'src/wasm-lib/**.hbs'
|
||||||
|
- 'src/wasm-lib/**.gen'
|
||||||
|
- 'src/wasm-lib/**.snap'
|
||||||
- '**/Cargo.toml'
|
- '**/Cargo.toml'
|
||||||
- '**/Cargo.lock'
|
- '**/Cargo.lock'
|
||||||
- '**/rust-toolchain.toml'
|
- '**/rust-toolchain.toml'
|
||||||
|
2
Makefile
@ -19,7 +19,7 @@ $(XSTATE_TYPEGENS): $(TS_SRC)
|
|||||||
yarn xstate typegen 'src/**/*.ts?(x)'
|
yarn xstate typegen 'src/**/*.ts?(x)'
|
||||||
|
|
||||||
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
|
public/wasm_lib_bg.wasm: $(WASM_LIB_FILES)
|
||||||
yarn build:wasm-dev
|
yarn build:wasm
|
||||||
|
|
||||||
node_modules: package.json yarn.lock
|
node_modules: package.json yarn.lock
|
||||||
yarn install
|
yarn install
|
||||||
|
@ -110,7 +110,7 @@ Which commands from setup are one off vs need to be run every time?
|
|||||||
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
The following will need to be run when checking out a new commit and guarantees the build is not stale:
|
||||||
```bash
|
```bash
|
||||||
yarn install
|
yarn install
|
||||||
yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build
|
yarn build:wasm
|
||||||
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
yarn start # or yarn build:local && yarn serve for slower but more production-like build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "angleToMatchLengthX"
|
title: "angleToMatchLengthX"
|
||||||
excerpt: "Compute the angle (in degrees) in o"
|
excerpt: "Returns the angle to match the given length for x."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Compute the angle (in degrees) in o
|
Returns the angle to match the given length for x.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
41
docs/kcl/arcTo.md
Normal file
@ -7,6 +7,7 @@ layout: manual
|
|||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
* [Types](kcl/types)
|
* [Types](kcl/types)
|
||||||
|
* [Modules](kcl/modules)
|
||||||
* [Known Issues](kcl/KNOWN-ISSUES)
|
* [Known Issues](kcl/KNOWN-ISSUES)
|
||||||
* [`abs`](kcl/abs)
|
* [`abs`](kcl/abs)
|
||||||
* [`acos`](kcl/acos)
|
* [`acos`](kcl/acos)
|
||||||
@ -19,6 +20,7 @@ layout: manual
|
|||||||
* [`angledLineToX`](kcl/angledLineToX)
|
* [`angledLineToX`](kcl/angledLineToX)
|
||||||
* [`angledLineToY`](kcl/angledLineToY)
|
* [`angledLineToY`](kcl/angledLineToY)
|
||||||
* [`arc`](kcl/arc)
|
* [`arc`](kcl/arc)
|
||||||
|
* [`arcTo`](kcl/arcTo)
|
||||||
* [`asin`](kcl/asin)
|
* [`asin`](kcl/asin)
|
||||||
* [`assert`](kcl/assert)
|
* [`assert`](kcl/assert)
|
||||||
* [`assertEqual`](kcl/assertEqual)
|
* [`assertEqual`](kcl/assertEqual)
|
||||||
|
59
docs/kcl/modules.md
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
title: "KCL Modules"
|
||||||
|
excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
`KCL` allows splitting code up into multiple files. Each file is somewhat
|
||||||
|
isolated from other files as a separate module.
|
||||||
|
|
||||||
|
When you define a function, you can use `export` before it to make it available
|
||||||
|
to other modules.
|
||||||
|
|
||||||
|
```
|
||||||
|
// util.kcl
|
||||||
|
export fn increment = (x) => {
|
||||||
|
return x + 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Other files in the project can now import functions that have been exported.
|
||||||
|
This makes them available to use in another file.
|
||||||
|
|
||||||
|
```
|
||||||
|
// main.kcl
|
||||||
|
import increment from "util.kcl"
|
||||||
|
|
||||||
|
answer = increment(41)
|
||||||
|
```
|
||||||
|
|
||||||
|
Imported files _must_ be in the same project so that units are uniform across
|
||||||
|
modules. This means that it must be in the same directory.
|
||||||
|
|
||||||
|
Import statements must be at the top-level of a file. It is not allowed to have
|
||||||
|
an `import` statement inside a function or in the body of an if-else.
|
||||||
|
|
||||||
|
Multiple functions can be exported in a file.
|
||||||
|
|
||||||
|
```
|
||||||
|
// util.kcl
|
||||||
|
export fn increment = (x) => {
|
||||||
|
return x + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export fn decrement = (x) => {
|
||||||
|
return x - 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
When importing, you can import multiple functions at once.
|
||||||
|
|
||||||
|
```
|
||||||
|
import increment, decrement from "util.kcl"
|
||||||
|
```
|
||||||
|
|
||||||
|
Imported symbols can be renamed for convenience or to avoid name collisions.
|
||||||
|
|
||||||
|
```
|
||||||
|
import increment as inc, decrement as dec from "util.kcl"
|
||||||
|
```
|
9397
docs/kcl/std.json
22
docs/kcl/types/ArcToData.md
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: "ArcToData"
|
||||||
|
excerpt: "Data to draw a three point arc (arcTo)."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
Data to draw a three point arc (arcTo).
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `end` |`[number, number]`| End point of the arc. A point in 3D space | No |
|
||||||
|
| `interior` |`[number, number]`| Interior point of the arc. A point in 3D space | No |
|
||||||
|
|
||||||
|
|
@ -24,7 +24,7 @@ Autodesk Filmbox (FBX) format
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `fbx`| | No |
|
| `format` |enum: `fbx`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -40,7 +40,7 @@ Binary glTF 2.0. We refer to this as glTF since that is how our customers refer
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `gltf`| | No |
|
| `format` |enum: `gltf`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -56,7 +56,7 @@ Wavefront OBJ format.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `obj`| | No |
|
| `format` |enum: `obj`| | No |
|
||||||
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
||||||
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ The PLY Polygon File Format.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `ply`| | No |
|
| `format` |enum: `ply`| | No |
|
||||||
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
||||||
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ SolidWorks part (SLDPRT) format.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `sldprt`| | No |
|
| `format` |enum: `sldprt`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -108,7 +108,7 @@ ISO 10303-21 (STEP) format.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `step`| | No |
|
| `format` |enum: `step`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
@ -124,7 +124,7 @@ ST**ereo**L**ithography format.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `stl`| | No |
|
| `format` |enum: `stl`| | No |
|
||||||
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
| `coords` |[`System`](/docs/kcl/types/System)| Co-ordinate system of input data. Defaults to the [KittyCAD co-ordinate system. | No |
|
||||||
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
| `units` |[`UnitLength`](/docs/kcl/types/UnitLength)| The units of the input data. This is very important for correct scaling and when calculating physics properties like mass, etc. Defaults to millimeters. | No |
|
||||||
|
|
||||||
|
16
docs/kcl/types/KclNone.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: "KclNone"
|
||||||
|
excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application).
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -23,8 +23,110 @@ Any KCL value.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `UserVal`| | No |
|
| `type` |enum: `Uuid`| | No |
|
||||||
| `value` |``| | No |
|
| `value` |`string`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Bool`| | No |
|
||||||
|
| `value` |`boolean`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Number`| | No |
|
||||||
|
| `value` |`number`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Int`| | No |
|
||||||
|
| `value` |`integer`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `String`| | No |
|
||||||
|
| `value` |`string`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Array`| | No |
|
||||||
|
| `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`| | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Object`| | No |
|
||||||
|
| `value` |`object`| | No |
|
||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
@ -78,7 +180,7 @@ A plane.
|
|||||||
|
|
||||||
| Property | Type | Description | Required |
|
| Property | Type | Description | Required |
|
||||||
|----------|------|-------------|----------|
|
|----------|------|-------------|----------|
|
||||||
| `type` |enum: `Plane`| | No |
|
| `type` |enum: [`Plane`](/docs/kcl/types/Plane)| | No |
|
||||||
| `id` |`string`| The id of the plane. | No |
|
| `id` |`string`| The id of the plane. | No |
|
||||||
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
|
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| Any KCL value. | No |
|
||||||
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||||
@ -111,6 +213,38 @@ A face.
|
|||||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)| | No |
|
||||||
|
| `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: `Sketches`| | No |
|
||||||
|
| `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
An solid is a collection of extrude surfaces.
|
An solid is a collection of extrude surfaces.
|
||||||
|
|
||||||
@ -190,6 +324,23 @@ Data for an imported geometry.
|
|||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)| | No |
|
||||||
|
| `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
27
docs/kcl/types/Plane.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
title: "Plane"
|
||||||
|
excerpt: "A plane."
|
||||||
|
layout: manual
|
||||||
|
---
|
||||||
|
|
||||||
|
A plane.
|
||||||
|
|
||||||
|
**Type:** `object`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
| Property | Type | Description | Required |
|
||||||
|
|----------|------|-------------|----------|
|
||||||
|
| `id` |`string`| The id of the plane. | No |
|
||||||
|
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
|
||||||
|
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
|
||||||
|
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s X axis be? | No |
|
||||||
|
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s Y axis be? | No |
|
||||||
|
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
|
||||||
|
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||||
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
|||||||
---
|
---
|
||||||
title: "PlaneData"
|
title: "PlaneData"
|
||||||
excerpt: "Data for a plane."
|
excerpt: "Orientation data that can be used to construct a plane, not a plane in itself."
|
||||||
layout: manual
|
layout: manual
|
||||||
---
|
---
|
||||||
|
|
||||||
Data for a plane.
|
Orientation data that can be used to construct a plane, not a plane in itself.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,18 @@ Data for start sketch on. You can start a sketch on a plane or an solid.
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
----
|
||||||
|
Data for start sketch on. You can start a sketch on a plane or an solid.
|
||||||
|
|
||||||
|
[`Plane`](/docs/kcl/types/Plane)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
----
|
----
|
||||||
Data for start sketch on. You can start a sketch on a plane or an solid.
|
Data for start sketch on. You can start a sketch on a plane or an solid.
|
||||||
|
|
||||||
|
@ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
|||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
}
|
}
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
} else {
|
} else {
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
}
|
}
|
||||||
@ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
|||||||
if (openPanes.includes('code')) {
|
if (openPanes.includes('code')) {
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> lineTo([0, ${commonPoints.num3}], %)`)
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
@ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|
|||||||
await u.openKclCodePanel()
|
await u.openKclCodePanel()
|
||||||
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %, $seg01)
|
|> xLine(${commonPoints.num1}, %, $seg01)
|
||||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> angledLine([180, segLen(seg01)], %)`)
|
|> xLine(-segLen(seg01), %)`)
|
||||||
}
|
}
|
||||||
|
|
||||||
test.describe('Basic sketch', () => {
|
test.describe('Basic sketch', () => {
|
||||||
|
@ -62,6 +62,8 @@ test(
|
|||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||||
|
// The open file's name is `main.kcl`, so the export file name should be `main.gltf`
|
||||||
|
const exportFileName = `main.gltf`
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
await exportButton.click()
|
await exportButton.click()
|
||||||
@ -96,7 +98,7 @@ test(
|
|||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const outputGltf = await fsp.readFile('output.gltf')
|
const outputGltf = await fsp.readFile(exportFileName)
|
||||||
return outputGltf.byteLength
|
return outputGltf.byteLength
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0
|
return 0
|
||||||
@ -106,8 +108,8 @@ test(
|
|||||||
)
|
)
|
||||||
.toBeGreaterThan(300_000)
|
.toBeGreaterThan(300_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up exported file
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm(exportFileName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -138,6 +140,8 @@ test(
|
|||||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||||
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
const alreadyExportingToastMessage = page.getByText(`Already exporting`)
|
||||||
|
// The open file's name is `other.kcl`, so the export file name should be `other.gltf`
|
||||||
|
const exportFileName = `other.gltf`
|
||||||
|
|
||||||
// Click the export button
|
// Click the export button
|
||||||
await exportButton.click()
|
await exportButton.click()
|
||||||
@ -171,7 +175,7 @@ test(
|
|||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const outputGltf = await fsp.readFile('output.gltf')
|
const outputGltf = await fsp.readFile(exportFileName)
|
||||||
return outputGltf.byteLength
|
return outputGltf.byteLength
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0
|
return 0
|
||||||
@ -181,8 +185,8 @@ test(
|
|||||||
)
|
)
|
||||||
.toBeGreaterThan(100_000)
|
.toBeGreaterThan(100_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up exported file
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm(exportFileName)
|
||||||
})
|
})
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
})
|
})
|
||||||
|
@ -694,6 +694,9 @@ test.describe('Editor tests', () => {
|
|||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([3.14, 12], %)
|
|> startProfileAt([3.14, 12], %)
|
||||||
|> xLine(5, %) // lin`)
|
|> xLine(5, %) // lin`)
|
||||||
|
|
||||||
|
// expect there to be no KCL errors
|
||||||
|
await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('with tab to accept the completion', async ({ page }) => {
|
test('with tab to accept the completion', async ({ page }) => {
|
||||||
|
@ -1135,3 +1135,189 @@ _test.describe('Deleting items from the file pane', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
_test.describe(
|
||||||
|
'Undo and redo do not keep history when navigating between files',
|
||||||
|
() => {
|
||||||
|
_test(
|
||||||
|
`open a file, change something, open a different file, hitting undo should do nothing`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
const { page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(testDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
|
join(testDir, 'other.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectCard = page.getByText('testProject')
|
||||||
|
const otherFile = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
|
||||||
|
|
||||||
|
await _test.step(
|
||||||
|
'Open project and make a change to the file',
|
||||||
|
async () => {
|
||||||
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
// Get the text in the code locator.
|
||||||
|
const originalText = await u.codeLocator.innerText()
|
||||||
|
// Click in the editor and add some new lines.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
await page.keyboard.type(`sketch001 = startSketchOn('XY')
|
||||||
|
some other shit`)
|
||||||
|
|
||||||
|
// Ensure the content in the editor changed.
|
||||||
|
const newContent = await u.codeLocator.innerText()
|
||||||
|
|
||||||
|
expect(originalText !== newContent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await _test.step('navigate to other.kcl', async () => {
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
await otherFile.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||||
|
})
|
||||||
|
|
||||||
|
await _test.step('hit undo', async () => {
|
||||||
|
// Get the original content of the file.
|
||||||
|
const originalText = await u.codeLocator.innerText()
|
||||||
|
// Now hit undo
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(u.codeLocator).toContainText(originalText)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_test(
|
||||||
|
`open a file, change something, undo it, open a different file, hitting redo should do nothing`,
|
||||||
|
{ tag: '@electron' },
|
||||||
|
// Skip on windows i think the keybindings are different for redo.
|
||||||
|
async ({ browserName }, testInfo) => {
|
||||||
|
test.skip(process.platform === 'win32', 'Skip on windows')
|
||||||
|
const { page } = await setupElectron({
|
||||||
|
testInfo,
|
||||||
|
folderSetupFn: async (dir) => {
|
||||||
|
const testDir = join(dir, 'testProject')
|
||||||
|
await fsp.mkdir(testDir, { recursive: true })
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('cylinder.kcl'),
|
||||||
|
join(testDir, 'main.kcl')
|
||||||
|
)
|
||||||
|
await fsp.copyFile(
|
||||||
|
executorInputPath('basic_fillet_cube_end.kcl'),
|
||||||
|
join(testDir, 'other.kcl')
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const u = await getUtils(page)
|
||||||
|
await page.setViewportSize({ width: 1200, height: 500 })
|
||||||
|
page.on('console', console.log)
|
||||||
|
|
||||||
|
// Constants and locators
|
||||||
|
const projectCard = page.getByText('testProject')
|
||||||
|
const otherFile = page
|
||||||
|
.getByRole('listitem')
|
||||||
|
.filter({ has: page.getByRole('button', { name: 'other.kcl' }) })
|
||||||
|
|
||||||
|
const badContent = 'this shit'
|
||||||
|
await _test.step(
|
||||||
|
'Open project and make a change to the file',
|
||||||
|
async () => {
|
||||||
|
await projectCard.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
|
||||||
|
// Get the text in the code locator.
|
||||||
|
const originalText = await u.codeLocator.innerText()
|
||||||
|
// Click in the editor and add some new lines.
|
||||||
|
await u.codeLocator.click()
|
||||||
|
|
||||||
|
await page.keyboard.type(badContent)
|
||||||
|
|
||||||
|
// Ensure the content in the editor changed.
|
||||||
|
const newContent = await u.codeLocator.innerText()
|
||||||
|
|
||||||
|
expect(originalText !== newContent)
|
||||||
|
|
||||||
|
// Now hit undo
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(u.codeLocator).toContainText(originalText)
|
||||||
|
await expect(u.codeLocator).not.toContainText(badContent)
|
||||||
|
|
||||||
|
// Hit redo.
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(u.codeLocator).toContainText(originalText)
|
||||||
|
await expect(u.codeLocator).toContainText(badContent)
|
||||||
|
|
||||||
|
// Now hit undo
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(u.codeLocator).toContainText(originalText)
|
||||||
|
await expect(u.codeLocator).not.toContainText(badContent)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
await _test.step('navigate to other.kcl', async () => {
|
||||||
|
await u.openFilePanel()
|
||||||
|
|
||||||
|
await otherFile.click()
|
||||||
|
await u.waitForPageLoad()
|
||||||
|
await u.openKclCodePanel()
|
||||||
|
await _expect(u.codeLocator).toContainText('getOppositeEdge(thing)')
|
||||||
|
await expect(u.codeLocator).not.toContainText(badContent)
|
||||||
|
})
|
||||||
|
|
||||||
|
await _test.step('hit redo', async () => {
|
||||||
|
// Get the original content of the file.
|
||||||
|
const originalText = await u.codeLocator.innerText()
|
||||||
|
// Now hit redo
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
|
await page.keyboard.down('ControlOrMeta')
|
||||||
|
await page.keyboard.press('KeyZ')
|
||||||
|
await page.keyboard.up('ControlOrMeta')
|
||||||
|
await page.keyboard.up('Shift')
|
||||||
|
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
await expect(u.codeLocator).toContainText(originalText)
|
||||||
|
await expect(u.codeLocator).not.toContainText(badContent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -452,7 +452,7 @@ sketch002 = startSketchOn(extrude001, seg03)
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(`Verify axis and origin snapping`, async ({
|
test(`Verify axis, origin, and horizontal snapping`, async ({
|
||||||
app,
|
app,
|
||||||
editor,
|
editor,
|
||||||
toolbar,
|
toolbar,
|
||||||
@ -505,7 +505,7 @@ test(`Verify axis and origin snapping`, async ({
|
|||||||
const expectedCodeSnippets = {
|
const expectedCodeSnippets = {
|
||||||
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
|
||||||
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
|
||||||
segmentOnXAxis: `lineTo([${xAxisSloppy.kcl[0]}, ${xAxisSloppy.kcl[1]}], %)`,
|
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
|
||||||
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
|
||||||
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
|
||||||
}
|
}
|
||||||
|
@ -247,7 +247,7 @@ test.describe('Can export from electron app', () => {
|
|||||||
.poll(
|
.poll(
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
const outputGltf = await fsp.readFile('output.gltf')
|
const outputGltf = await fsp.readFile('main.gltf')
|
||||||
return outputGltf.byteLength
|
return outputGltf.byteLength
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 0
|
return 0
|
||||||
@ -257,8 +257,8 @@ test.describe('Can export from electron app', () => {
|
|||||||
)
|
)
|
||||||
.toBeGreaterThan(300_000)
|
.toBeGreaterThan(300_000)
|
||||||
|
|
||||||
// clean up output.gltf
|
// clean up exported file
|
||||||
await fsp.rm('output.gltf')
|
await fsp.rm('main.gltf')
|
||||||
})
|
})
|
||||||
|
|
||||||
await electronApp.close()
|
await electronApp.close()
|
||||||
|
@ -115,7 +115,7 @@ test.describe('Sketch tests', () => {
|
|||||||
'persistCode',
|
'persistCode',
|
||||||
`sketch001 = startSketchOn('XZ')
|
`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([4.61, -14.01], %)
|
|> startProfileAt([4.61, -14.01], %)
|
||||||
|> line([12.73, -0.09], %)
|
|> xLine(12.73, %)
|
||||||
|> tangentialArcTo([24.95, -5.38], %)`
|
|> tangentialArcTo([24.95, -5.38], %)`
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -156,7 +156,7 @@ test.describe('Sketch tests', () => {
|
|||||||
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
await expect.poll(u.normalisedEditorCode, { timeout: 1000 })
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
|> startProfileAt([12.34, -12.34], %)
|
||||||
|> line([-12.34, 12.34], %)
|
|> yLine(12.34, %)
|
||||||
|
|
||||||
`)
|
`)
|
||||||
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
}).toPass({ timeout: 40_000, intervals: [1_000] })
|
||||||
@ -645,7 +645,7 @@ test.describe('Sketch tests', () => {
|
|||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 }
|
||||||
const { toSU, click00r } = getMovementUtils({ center, page })
|
const { toSU, toU, click00r } = getMovementUtils({ center, page })
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
page.getByRole('button', { name: 'Start Sketch' })
|
page.getByRole('button', { name: 'Start Sketch' })
|
||||||
@ -674,16 +674,15 @@ test.describe('Sketch tests', () => {
|
|||||||
|
|
||||||
await click00r(50, 0)
|
await click00r(50, 0)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
codeStr += ` |> lineTo(${toSU([50, 0])}, %)`
|
codeStr += ` |> xLine(${toU(50, 0)[0]}, %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(0, 50)
|
await click00r(0, 50)
|
||||||
codeStr += ` |> line(${toSU([0, 50])}, %)`
|
codeStr += ` |> yLine(${toU(0, 50)[1]}, %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
let clickCoords = await click00r(-50, 0)
|
await click00r(-50, 0)
|
||||||
expect(clickCoords).not.toBeUndefined()
|
codeStr += ` |> xLine(${toU(-50, 0)[0]}, %)`
|
||||||
codeStr += ` |> lineTo(${toSU(clickCoords!)}, %)`
|
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
// exit the sketch, reset relative clicker
|
// exit the sketch, reset relative clicker
|
||||||
@ -712,15 +711,15 @@ test.describe('Sketch tests', () => {
|
|||||||
// TODO: I couldn't use `toSU` here because of some rounding error causing
|
// TODO: I couldn't use `toSU` here because of some rounding error causing
|
||||||
// it to be off by 0.01
|
// it to be off by 0.01
|
||||||
await click00r(30, 0)
|
await click00r(30, 0)
|
||||||
codeStr += ` |> lineTo([4.07, 0], %)`
|
codeStr += ` |> xLine(2.04, %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(0, 30)
|
await click00r(0, 30)
|
||||||
codeStr += ` |> line([0, -2.03], %)`
|
codeStr += ` |> yLine(-2.03, %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(-30, 0)
|
await click00r(-30, 0)
|
||||||
codeStr += ` |> line([-2.04, 0], %)`
|
codeStr += ` |> xLine(-2.04, %)`
|
||||||
await expect(u.codeLocator).toHaveText(codeStr)
|
await expect(u.codeLocator).toHaveText(codeStr)
|
||||||
|
|
||||||
await click00r(undefined, undefined)
|
await click00r(undefined, undefined)
|
||||||
@ -744,8 +743,8 @@ test.describe('Sketch tests', () => {
|
|||||||
|
|
||||||
const code = `sketch001 = startSketchOn('-XZ')
|
const code = `sketch001 = startSketchOn('-XZ')
|
||||||
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
|> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %)
|
||||||
|> line([${roundOff(scale * 139.19)}, 0], %)
|
|> xLine(${roundOff(scale * 139.19)}, %)
|
||||||
|> line([0, -${roundOff(scale * 139.2)}], %)
|
|> yLine(-${roundOff(scale * 139.2)}, %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)`
|
|> close(%)`
|
||||||
|
|
||||||
@ -1275,3 +1274,44 @@ test2.describe('Sketch mode should be toleratant to syntax errors', () => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test2.describe(`Sketching with offset planes`, () => {
|
||||||
|
test2(
|
||||||
|
`Can select an offset plane to sketch on`,
|
||||||
|
async ({ app, scene, toolbar, editor }) => {
|
||||||
|
// We seed the scene with a single offset plane
|
||||||
|
await app.initialise(`offsetPlane001 = offsetPlane("XY", 10)`)
|
||||||
|
|
||||||
|
const [planeClick, planeHover] = scene.makeMouseHelpers(650, 200)
|
||||||
|
|
||||||
|
await test2.step(`Start sketching on the offset plane`, async () => {
|
||||||
|
await toolbar.startSketchPlaneSelection()
|
||||||
|
|
||||||
|
await test2.step(`Hovering should highlight code`, async () => {
|
||||||
|
await planeHover()
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||||
|
diagnostics: [],
|
||||||
|
highlightedCode: 'offsetPlane("XY", 10)',
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
await test2.step(
|
||||||
|
`Clicking should select the plane and enter sketch mode`,
|
||||||
|
async () => {
|
||||||
|
await planeClick()
|
||||||
|
// Have to wait for engine-side animation to finish
|
||||||
|
await app.page.waitForTimeout(600)
|
||||||
|
await expect2(toolbar.lineBtn).toBeEnabled()
|
||||||
|
await editor.expectEditor.toContain('startSketchOn(offsetPlane001)')
|
||||||
|
await editor.expectState({
|
||||||
|
activeLines: [`offsetPlane001=offsetPlane("XY",10)`],
|
||||||
|
diagnostics: [],
|
||||||
|
highlightedCode: '',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
@ -283,7 +283,7 @@ part001 = startSketchOn('-XZ')
|
|||||||
const gltfFilename = filenames.filter((t: string) =>
|
const gltfFilename = filenames.filter((t: string) =>
|
||||||
t.includes('.gltf')
|
t.includes('.gltf')
|
||||||
)[0]
|
)[0]
|
||||||
if (!gltfFilename) throw new Error('No output.gltf in this archive')
|
if (!gltfFilename) throw new Error('No gLTF in this archive')
|
||||||
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
|
cliCommand = `export ZOO_TOKEN=${secrets.snapshottoken} && zoo file snapshot --output-format=png --src-format=${outputType} ${parentPath}/${gltfFilename} ${imagePath}`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -462,7 +462,7 @@ test(
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
|> line([7.25, 0], %)`
|
|> xLine(7.25, %)`
|
||||||
await expect(page.locator('.cm-content')).toHaveText(code)
|
await expect(page.locator('.cm-content')).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@ -647,7 +647,7 @@ test.describe(
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
|> line([7.25, 0], %)`
|
|> xLine(7.25, %)`
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await page
|
||||||
@ -752,7 +752,7 @@ test.describe(
|
|||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
|
||||||
code += `
|
code += `
|
||||||
|> line([184.3, 0], %)`
|
|> xLine(184.3, %)`
|
||||||
await expect(u.codeLocator).toHaveText(code)
|
await expect(u.codeLocator).toHaveText(code)
|
||||||
|
|
||||||
await page
|
await page
|
||||||
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 50 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB |
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
@ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
// Expect the network to be up
|
// Expect the network to be up
|
||||||
await expect(networkToggle).toContainText('Connected')
|
await expect(networkToggle).toContainText('Connected')
|
||||||
@ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
|> startProfileAt([12.34, -12.34], %)
|
||||||
|> line([12.34, 0], %)
|
|> xLine(12.34, %)
|
||||||
|> line([-12.34, 12.34], %)
|
|> line([-12.34, 12.34], %)
|
||||||
|
|
||||||
`)
|
`)
|
||||||
@ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => {
|
|||||||
await expect.poll(u.normalisedEditorCode)
|
await expect.poll(u.normalisedEditorCode)
|
||||||
.toBe(`sketch001 = startSketchOn('XZ')
|
.toBe(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt([12.34, -12.34], %)
|
|> startProfileAt([12.34, -12.34], %)
|
||||||
|> line([12.34, 0], %)
|
|> xLine(12.34, %)
|
||||||
|> line([-12.34, 12.34], %)
|
|> line([-12.34, 12.34], %)
|
||||||
|> lineTo([0, -12.34], %)
|
|> xLine(-12.34, %)
|
||||||
|
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
@ -305,7 +305,7 @@ export const getMovementUtils = (opts: any) => {
|
|||||||
return [last.x, last.y]
|
return [last.x, last.y]
|
||||||
}
|
}
|
||||||
|
|
||||||
return { toSU, click00r }
|
return { toSU, toU, click00r }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function waitForAuthAndLsp(page: Page) {
|
async function waitForAuthAndLsp(page: Page) {
|
||||||
|
@ -32,10 +32,17 @@ test.describe('Testing selections', () => {
|
|||||||
await u.waitForAuthSkipAppStart()
|
await u.waitForAuthSkipAppStart()
|
||||||
await u.openDebugPanel()
|
await u.openDebugPanel()
|
||||||
|
|
||||||
const xAxisClick = () =>
|
const yAxisClick = () =>
|
||||||
page.mouse.click(700, 253).then(() => page.waitForTimeout(100))
|
test.step('Click on Y axis', async () => {
|
||||||
|
await page.mouse.move(600, 200, { steps: 5 })
|
||||||
|
await page.mouse.click(600, 200)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
})
|
||||||
const xAxisClickAfterExitingSketch = () =>
|
const xAxisClickAfterExitingSketch = () =>
|
||||||
page.mouse.click(639, 278).then(() => page.waitForTimeout(100))
|
test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => {
|
||||||
|
await page.mouse.click(639, 278)
|
||||||
|
await page.waitForTimeout(100)
|
||||||
|
})
|
||||||
const emptySpaceHover = () =>
|
const emptySpaceHover = () =>
|
||||||
test.step('Hover over empty space', async () => {
|
test.step('Hover over empty space', async () => {
|
||||||
await page.mouse.move(700, 143, { steps: 5 })
|
await page.mouse.move(700, 143, { steps: 5 })
|
||||||
@ -80,23 +87,23 @@ test.describe('Testing selections', () => {
|
|||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)`)
|
|> xLine(${commonPoints.num1}, %)`)
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> line([0, ${commonPoints.num1 + 0.01}], %)`)
|
|> yLine(${commonPoints.num1 + 0.01}, %)`)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||||
await expect(page.locator('.cm-content'))
|
await expect(page.locator('.cm-content'))
|
||||||
.toHaveText(`sketch001 = startSketchOn('XZ')
|
.toHaveText(`sketch001 = startSketchOn('XZ')
|
||||||
|> startProfileAt(${commonPoints.startAt}, %)
|
|> startProfileAt(${commonPoints.startAt}, %)
|
||||||
|> line([${commonPoints.num1}, 0], %)
|
|> xLine(${commonPoints.num1}, %)
|
||||||
|> line([0, ${commonPoints.num1 + 0.01}], %)
|
|> yLine(${commonPoints.num1 + 0.01}, %)
|
||||||
|> lineTo([0, ${commonPoints.num3}], %)`)
|
|> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
|
|
||||||
// deselect line tool
|
// deselect line tool
|
||||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||||
@ -121,29 +128,31 @@ test.describe('Testing selections', () => {
|
|||||||
// now check clicking works including axis
|
// now check clicking works including axis
|
||||||
|
|
||||||
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
// click a segment hold shift and click an axis, see that a relevant constraint is enabled
|
||||||
await topHorzSegmentClick()
|
|
||||||
await page.keyboard.down('Shift')
|
|
||||||
const constrainButton = page.getByRole('button', {
|
const constrainButton = page.getByRole('button', {
|
||||||
name: 'Length: open menu',
|
name: 'Length: open menu',
|
||||||
})
|
})
|
||||||
const absYButton = page.getByRole('button', { name: 'Absolute Y' })
|
const absXButton = page.getByRole('button', { name: 'Absolute X' })
|
||||||
|
|
||||||
|
await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => {
|
||||||
|
await topHorzSegmentClick()
|
||||||
|
await page.keyboard.down('Shift')
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absXButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await yAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await absYButton.and(page.locator(':not([disabled])')).waitFor()
|
await absXButton.and(page.locator(':not([disabled])')).waitFor()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
// same selection but click the axis first
|
|
||||||
await xAxisClick()
|
await test.step(`Same selection but click the axis first`, async () => {
|
||||||
|
await yAxisClick()
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absXButton).toBeDisabled()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await topHorzSegmentClick()
|
await topHorzSegmentClick()
|
||||||
@ -151,23 +160,26 @@ test.describe('Testing selections', () => {
|
|||||||
|
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
|
|
||||||
// check the same selection again by putting cursor in code first then selecting axis
|
// check the same selection again by putting cursor in code first then selecting axis
|
||||||
|
await test.step(`Same selection but code selection then axis`, async () => {
|
||||||
await page
|
await page
|
||||||
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
|
.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`)
|
||||||
.click()
|
.click()
|
||||||
await page.keyboard.down('Shift')
|
await page.keyboard.down('Shift')
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await expect(absYButton).toBeDisabled()
|
await expect(absXButton).toBeDisabled()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await xAxisClick()
|
await yAxisClick()
|
||||||
await page.keyboard.up('Shift')
|
await page.keyboard.up('Shift')
|
||||||
await constrainButton.click()
|
await constrainButton.click()
|
||||||
await expect(absYButton).not.toBeDisabled()
|
await expect(absXButton).not.toBeDisabled()
|
||||||
|
})
|
||||||
|
|
||||||
// clear selection by clicking on nothing
|
// clear selection by clicking on nothing
|
||||||
await emptySpaceClick()
|
await emptySpaceClick()
|
||||||
@ -182,9 +194,7 @@ test.describe('Testing selections', () => {
|
|||||||
process.platform === 'linux' ? 'Control' : 'Meta'
|
process.platform === 'linux' ? 'Control' : 'Meta'
|
||||||
)
|
)
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
await page
|
await page.getByText(` |> xLine(${commonPoints.num2 * -1}, %)`).click()
|
||||||
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
|
|
||||||
.click()
|
|
||||||
|
|
||||||
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
await expect(page.locator('.cm-cursor')).toHaveCount(2)
|
||||||
await page.waitForTimeout(500)
|
await page.waitForTimeout(500)
|
||||||
@ -928,6 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
// test fillet button with the body in the scene
|
// test fillet button with the body in the scene
|
||||||
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
const codeToAdd = `${await u.codeLocator.allInnerTexts()}
|
||||||
extrude001 = extrude(10, sketch001)`
|
extrude001 = extrude(10, sketch001)`
|
||||||
|
await u.codeLocator.clear()
|
||||||
await u.codeLocator.fill(codeToAdd)
|
await u.codeLocator.fill(codeToAdd)
|
||||||
await selectSegment()
|
await selectSegment()
|
||||||
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled()
|
||||||
|
@ -258,7 +258,7 @@ test.describe('Testing settings', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
test.fixme(
|
||||||
`Project settings override user settings on desktop`,
|
`Project settings override user settings on desktop`,
|
||||||
{ tag: ['@electron', '@skipWin'] },
|
{ tag: ['@electron', '@skipWin'] },
|
||||||
async ({ browser: _ }, testInfo) => {
|
async ({ browser: _ }, testInfo) => {
|
||||||
@ -318,7 +318,6 @@ test.describe('Testing settings', () => {
|
|||||||
timeout: 5_000,
|
timeout: 5_000,
|
||||||
})
|
})
|
||||||
.toContain(`themeColor = "${userThemeColor}"`)
|
.toContain(`themeColor = "${userThemeColor}"`)
|
||||||
// Only close the button after we've confirmed
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await test.step('Set project theme color', async () => {
|
await test.step('Set project theme color', async () => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "zoo-modeling-app",
|
"name": "zoo-modeling-app",
|
||||||
"version": "0.26.3",
|
"version": "0.27.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"productName": "Zoo Modeling App",
|
"productName": "Zoo Modeling App",
|
||||||
"author": {
|
"author": {
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"codemirror": "^6.0.1",
|
"codemirror": "^6.0.1",
|
||||||
"decamelize": "^6.0.0",
|
"decamelize": "^6.0.0",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"electron-squirrel-startup": "^1.0.1",
|
||||||
"electron-updater": "^6.3.9",
|
"electron-updater": "6.3.0",
|
||||||
"fuse.js": "^7.0.0",
|
"fuse.js": "^7.0.0",
|
||||||
"html2canvas-pro": "^1.5.8",
|
"html2canvas-pro": "^1.5.8",
|
||||||
"isomorphic-fetch": "^3.0.0",
|
"isomorphic-fetch": "^3.0.0",
|
||||||
|
@ -141,6 +141,7 @@ export function Toolbar({
|
|||||||
>
|
>
|
||||||
{/* A menu item will either be a vertical line break, a button with a dropdown, or a single button */}
|
{/* A menu item will either be a vertical line break, a button with a dropdown, or a single button */}
|
||||||
{currentModeItems.map((maybeIconConfig, i) => {
|
{currentModeItems.map((maybeIconConfig, i) => {
|
||||||
|
// Vertical Line Break
|
||||||
if (maybeIconConfig === 'break') {
|
if (maybeIconConfig === 'break') {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -149,6 +150,7 @@ export function Toolbar({
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (Array.isArray(maybeIconConfig)) {
|
} else if (Array.isArray(maybeIconConfig)) {
|
||||||
|
// A button with a dropdown
|
||||||
return (
|
return (
|
||||||
<ActionButtonDropdown
|
<ActionButtonDropdown
|
||||||
Element="button"
|
Element="button"
|
||||||
@ -215,6 +217,7 @@ export function Toolbar({
|
|||||||
}
|
}
|
||||||
const itemConfig = maybeIconConfig
|
const itemConfig = maybeIconConfig
|
||||||
|
|
||||||
|
// A single button
|
||||||
return (
|
return (
|
||||||
<div className="relative" key={itemConfig.id}>
|
<div className="relative" key={itemConfig.id}>
|
||||||
<ActionButton
|
<ActionButton
|
||||||
|
@ -202,12 +202,20 @@ const Overlay = ({
|
|||||||
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
|
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
|
||||||
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
|
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
|
||||||
|
|
||||||
|
// It's possible for the pathToNode to request a newer AST node
|
||||||
|
// than what's available in the AST at the moment of query.
|
||||||
|
// It eventually settles on being updated.
|
||||||
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
const _node1 = getNodeFromPath<Node<CallExpression>>(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
overlay.pathToNode,
|
overlay.pathToNode,
|
||||||
'CallExpression'
|
'CallExpression'
|
||||||
)
|
)
|
||||||
if (err(_node1)) return
|
|
||||||
|
// For that reason, to prevent console noise, we do not use err here.
|
||||||
|
if (_node1 instanceof Error) {
|
||||||
|
console.warn('ast older than pathToNode, not fatal, eventually settles', '')
|
||||||
|
return
|
||||||
|
}
|
||||||
const callExpression = _node1.node
|
const callExpression = _node1.node
|
||||||
|
|
||||||
const constraints = getConstraintInfo(
|
const constraints = getConstraintInfo(
|
||||||
@ -637,10 +645,16 @@ const ConstraintSymbol = ({
|
|||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
kclManager.programMemory
|
kclManager.programMemory
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!transform) return
|
if (!transform) return
|
||||||
const { modifiedAst } = transform
|
const { modifiedAst } = transform
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
||||||
kclManager.updateAst(modifiedAst, true)
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
|
||||||
|
// Code editor will be updated in the modelingMachine.
|
||||||
|
const newCode = recast(modifiedAst)
|
||||||
|
if (err(newCode)) return
|
||||||
|
await codeManager.updateCodeEditor(newCode)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error', e)
|
console.log('error', e)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import {
|
|||||||
Vector3,
|
Vector3,
|
||||||
} from 'three'
|
} from 'three'
|
||||||
import {
|
import {
|
||||||
|
ANGLE_SNAP_THRESHOLD_DEGREES,
|
||||||
ARROWHEAD,
|
ARROWHEAD,
|
||||||
AXIS_GROUP,
|
AXIS_GROUP,
|
||||||
DRAFT_POINT,
|
DRAFT_POINT,
|
||||||
@ -46,6 +47,7 @@ import {
|
|||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
|
sketchFromKclValueOptional,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import {
|
import {
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
@ -88,13 +90,15 @@ import { EngineCommandManager } from 'lang/std/engineConnection'
|
|||||||
import {
|
import {
|
||||||
getRectangleCallExpressions,
|
getRectangleCallExpressions,
|
||||||
updateRectangleSketch,
|
updateRectangleSketch,
|
||||||
|
updateCenterRectangleSketch,
|
||||||
} from 'lib/rectangleTool'
|
} from 'lib/rectangleTool'
|
||||||
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
|
import { getThemeColorForThreeJs, Themes } from 'lib/theme'
|
||||||
import { err, reportRejection, trap } from 'lib/trap'
|
import { err, Reason, reportRejection, trap } from 'lib/trap'
|
||||||
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
|
||||||
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
|
||||||
import { SegmentInputs } from 'lang/std/stdTypes'
|
import { SegmentInputs } from 'lang/std/stdTypes'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
import { radToDeg } from 'three/src/math/MathUtils'
|
||||||
|
|
||||||
type DraftSegment = 'line' | 'tangentialArcTo'
|
type DraftSegment = 'line' | 'tangentialArcTo'
|
||||||
|
|
||||||
@ -451,6 +455,7 @@ export class SceneEntities {
|
|||||||
const { modifiedAst } = addStartProfileAtRes
|
const { modifiedAst } = addStartProfileAtRes
|
||||||
|
|
||||||
await kclManager.updateAst(modifiedAst, false)
|
await kclManager.updateAst(modifiedAst, false)
|
||||||
|
|
||||||
this.removeIntersectionPlane()
|
this.removeIntersectionPlane()
|
||||||
this.scene.remove(draftPointGroup)
|
this.scene.remove(draftPointGroup)
|
||||||
|
|
||||||
@ -683,7 +688,7 @@ export class SceneEntities {
|
|||||||
})
|
})
|
||||||
return nextAst
|
return nextAst
|
||||||
}
|
}
|
||||||
setUpDraftSegment = async (
|
setupDraftSegment = async (
|
||||||
sketchPathToNode: PathToNode,
|
sketchPathToNode: PathToNode,
|
||||||
forward: [number, number, number],
|
forward: [number, number, number],
|
||||||
up: [number, number, number],
|
up: [number, number, number],
|
||||||
@ -798,11 +803,24 @@ export class SceneEntities {
|
|||||||
(sceneObject) => sceneObject.object.name === X_AXIS
|
(sceneObject) => sceneObject.object.name === X_AXIS
|
||||||
)
|
)
|
||||||
|
|
||||||
const lastSegment = sketch.paths.slice(-1)[0]
|
const lastSegment = sketch.paths.slice(-1)[0] || sketch.start
|
||||||
const snappedPoint = {
|
const snappedPoint = {
|
||||||
x: intersectsYAxis ? 0 : intersection2d.x,
|
x: intersectsYAxis ? 0 : intersection2d.x,
|
||||||
y: intersectsXAxis ? 0 : intersection2d.y,
|
y: intersectsXAxis ? 0 : intersection2d.y,
|
||||||
}
|
}
|
||||||
|
// Get the angle between the previous segment (or sketch start)'s end and this one's
|
||||||
|
const angle = Math.atan2(
|
||||||
|
snappedPoint.y - lastSegment.to[1],
|
||||||
|
snappedPoint.x - lastSegment.to[0]
|
||||||
|
)
|
||||||
|
|
||||||
|
const isHorizontal =
|
||||||
|
radToDeg(Math.abs(angle)) < ANGLE_SNAP_THRESHOLD_DEGREES ||
|
||||||
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI)) <
|
||||||
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
||||||
|
const isVertical =
|
||||||
|
Math.abs(radToDeg(Math.abs(angle) - Math.PI / 2)) <
|
||||||
|
ANGLE_SNAP_THRESHOLD_DEGREES
|
||||||
|
|
||||||
let resolvedFunctionName: ToolTip = 'line'
|
let resolvedFunctionName: ToolTip = 'line'
|
||||||
|
|
||||||
@ -810,6 +828,12 @@ export class SceneEntities {
|
|||||||
// case-based logic for different segment types
|
// case-based logic for different segment types
|
||||||
if (lastSegment.type === 'TangentialArcTo') {
|
if (lastSegment.type === 'TangentialArcTo') {
|
||||||
resolvedFunctionName = 'tangentialArcTo'
|
resolvedFunctionName = 'tangentialArcTo'
|
||||||
|
} else if (isHorizontal) {
|
||||||
|
// If the angle between is 0 or 180 degrees (+/- the snapping angle), make the line an xLine
|
||||||
|
resolvedFunctionName = 'xLine'
|
||||||
|
} else if (isVertical) {
|
||||||
|
// If the angle between is 90 or 270 degrees (+/- the snapping angle), make the line a yLine
|
||||||
|
resolvedFunctionName = 'yLine'
|
||||||
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
|
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
|
||||||
// We consider a point placed on axes or origin to be absolute
|
// We consider a point placed on axes or origin to be absolute
|
||||||
resolvedFunctionName = 'lineTo'
|
resolvedFunctionName = 'lineTo'
|
||||||
@ -835,10 +859,11 @@ export class SceneEntities {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await kclManager.executeAstMock(modifiedAst)
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
|
|
||||||
if (intersectsProfileStart) {
|
if (intersectsProfileStart) {
|
||||||
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
sceneInfra.modelingSend({ type: 'CancelSketch' })
|
||||||
} else {
|
} else {
|
||||||
await this.setUpDraftSegment(
|
await this.setupDraftSegment(
|
||||||
sketchPathToNode,
|
sketchPathToNode,
|
||||||
forward,
|
forward,
|
||||||
up,
|
up,
|
||||||
@ -846,6 +871,8 @@ export class SceneEntities {
|
|||||||
segmentName
|
segmentName
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
|
||||||
},
|
},
|
||||||
onMove: (args) => {
|
onMove: (args) => {
|
||||||
this.onDragSegment({
|
this.onDragSegment({
|
||||||
@ -970,16 +997,192 @@ export class SceneEntities {
|
|||||||
if (trap(_node)) return
|
if (trap(_node)) return
|
||||||
const sketchInit = _node.node?.declarations?.[0]?.init
|
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||||
|
|
||||||
if (sketchInit.type === 'PipeExpression') {
|
if (sketchInit.type !== 'PipeExpression') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updateRectangleSketch(sketchInit, x, y, tags[0])
|
updateRectangleSketch(sketchInit, x, y, tags[0])
|
||||||
|
|
||||||
|
const newCode = recast(_ast)
|
||||||
|
let _recastAst = parse(newCode)
|
||||||
|
if (trap(_recastAst)) return
|
||||||
|
_ast = _recastAst
|
||||||
|
|
||||||
|
// Update the primary AST and unequip the rectangle tool
|
||||||
|
await kclManager.executeAstMock(_ast)
|
||||||
|
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
||||||
|
|
||||||
|
// lee: I had this at the bottom of the function, but it's
|
||||||
|
// possible sketchFromKclValue "fails" when sketching on a face,
|
||||||
|
// and this couldn't wouldn't run.
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
||||||
|
|
||||||
|
const { execState } = await executeAst({
|
||||||
|
ast: _ast,
|
||||||
|
useFakeExecutor: true,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
programMemoryOverride,
|
||||||
|
idGenerator: kclManager.execState.idGenerator,
|
||||||
|
})
|
||||||
|
const programMemory = execState.memory
|
||||||
|
|
||||||
|
// Prepare to update the THREEjs scene
|
||||||
|
this.sceneProgramMemory = programMemory
|
||||||
|
const sketch = sketchFromKclValue(
|
||||||
|
programMemory.get(variableDeclarationName),
|
||||||
|
variableDeclarationName
|
||||||
|
)
|
||||||
|
if (err(sketch)) return
|
||||||
|
const sgPaths = sketch.paths
|
||||||
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
|
// Update the starting segment of the THREEjs scene
|
||||||
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||||
|
// Update the rest of the segments of the THREEjs scene
|
||||||
|
sgPaths.forEach((seg, index) =>
|
||||||
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
setupDraftCenterRectangle = async (
|
||||||
|
sketchPathToNode: PathToNode,
|
||||||
|
forward: [number, number, number],
|
||||||
|
up: [number, number, number],
|
||||||
|
sketchOrigin: [number, number, number],
|
||||||
|
rectangleOrigin: [x: number, y: number]
|
||||||
|
) => {
|
||||||
|
let _ast = structuredClone(kclManager.ast)
|
||||||
|
const _node1 = getNodeFromPath<VariableDeclaration>(
|
||||||
|
_ast,
|
||||||
|
sketchPathToNode || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node1)) return Promise.reject(_node1)
|
||||||
|
|
||||||
|
// startSketchOn already exists
|
||||||
|
const variableDeclarationName =
|
||||||
|
_node1.node?.declarations?.[0]?.id?.name || ''
|
||||||
|
const startSketchOn = _node1.node?.declarations
|
||||||
|
const startSketchOnInit = startSketchOn?.[0]?.init
|
||||||
|
|
||||||
|
const tags: [string, string, string] = [
|
||||||
|
findUniqueName(_ast, 'rectangleSegmentA'),
|
||||||
|
findUniqueName(_ast, 'rectangleSegmentB'),
|
||||||
|
findUniqueName(_ast, 'rectangleSegmentC'),
|
||||||
|
]
|
||||||
|
|
||||||
|
startSketchOn[0].init = createPipeExpression([
|
||||||
|
startSketchOnInit,
|
||||||
|
...getRectangleCallExpressions(rectangleOrigin, tags),
|
||||||
|
])
|
||||||
|
|
||||||
|
let _recastAst = parse(recast(_ast))
|
||||||
|
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||||
|
_ast = _recastAst
|
||||||
|
|
||||||
|
const { programMemoryOverride, truncatedAst } = await this.setupSketch({
|
||||||
|
sketchPathToNode,
|
||||||
|
forward,
|
||||||
|
up,
|
||||||
|
position: sketchOrigin,
|
||||||
|
maybeModdedAst: _ast,
|
||||||
|
draftExpressionsIndices: { start: 0, end: 3 },
|
||||||
|
})
|
||||||
|
|
||||||
|
sceneInfra.setCallbacks({
|
||||||
|
onMove: async (args) => {
|
||||||
|
// Update the width and height of the draft rectangle
|
||||||
|
const pathToNodeTwo = structuredClone(sketchPathToNode)
|
||||||
|
pathToNodeTwo[1][0] = 0
|
||||||
|
|
||||||
|
const _node = getNodeFromPath<VariableDeclaration>(
|
||||||
|
truncatedAst,
|
||||||
|
pathToNodeTwo || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node)) return Promise.reject(_node)
|
||||||
|
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||||
|
|
||||||
|
const x = (args.intersectionPoint.twoD.x || 0) - rectangleOrigin[0]
|
||||||
|
const y = (args.intersectionPoint.twoD.y || 0) - rectangleOrigin[1]
|
||||||
|
|
||||||
|
if (sketchInit.type === 'PipeExpression') {
|
||||||
|
updateCenterRectangleSketch(
|
||||||
|
sketchInit,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
tags[0],
|
||||||
|
rectangleOrigin[0],
|
||||||
|
rectangleOrigin[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { execState } = await executeAst({
|
||||||
|
ast: truncatedAst,
|
||||||
|
useFakeExecutor: true,
|
||||||
|
engineCommandManager: this.engineCommandManager,
|
||||||
|
programMemoryOverride,
|
||||||
|
idGenerator: kclManager.execState.idGenerator,
|
||||||
|
})
|
||||||
|
const programMemory = execState.memory
|
||||||
|
this.sceneProgramMemory = programMemory
|
||||||
|
const sketch = sketchFromKclValue(
|
||||||
|
programMemory.get(variableDeclarationName),
|
||||||
|
variableDeclarationName
|
||||||
|
)
|
||||||
|
if (err(sketch)) return Promise.reject(sketch)
|
||||||
|
const sgPaths = sketch.paths
|
||||||
|
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
|
||||||
|
|
||||||
|
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
|
||||||
|
sgPaths.forEach((seg, index) =>
|
||||||
|
this.updateSegment(seg, index, 0, _ast, orthoFactor, sketch)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onClick: async (args) => {
|
||||||
|
// If there is a valid camera interaction that matches, do that instead
|
||||||
|
const interaction = sceneInfra.camControls.getInteractionType(
|
||||||
|
args.mouseEvent
|
||||||
|
)
|
||||||
|
if (interaction !== 'none') return
|
||||||
|
// Commit the rectangle to the full AST/code and return to sketch.idle
|
||||||
|
const cornerPoint = args.intersectionPoint?.twoD
|
||||||
|
if (!cornerPoint || args.mouseEvent.button !== 0) return
|
||||||
|
|
||||||
|
const x = roundOff((cornerPoint.x || 0) - rectangleOrigin[0])
|
||||||
|
const y = roundOff((cornerPoint.y || 0) - rectangleOrigin[1])
|
||||||
|
|
||||||
|
const _node = getNodeFromPath<VariableDeclaration>(
|
||||||
|
_ast,
|
||||||
|
sketchPathToNode || [],
|
||||||
|
'VariableDeclaration'
|
||||||
|
)
|
||||||
|
if (trap(_node)) return
|
||||||
|
const sketchInit = _node.node?.declarations?.[0]?.init
|
||||||
|
|
||||||
|
if (sketchInit.type === 'PipeExpression') {
|
||||||
|
updateCenterRectangleSketch(
|
||||||
|
sketchInit,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
tags[0],
|
||||||
|
rectangleOrigin[0],
|
||||||
|
rectangleOrigin[1]
|
||||||
|
)
|
||||||
|
|
||||||
let _recastAst = parse(recast(_ast))
|
let _recastAst = parse(recast(_ast))
|
||||||
if (trap(_recastAst)) return
|
if (trap(_recastAst)) return
|
||||||
_ast = _recastAst
|
_ast = _recastAst
|
||||||
|
|
||||||
// Update the primary AST and unequip the rectangle tool
|
// Update the primary AST and unequip the rectangle tool
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
sceneInfra.modelingSend({ type: 'Finish rectangle' })
|
sceneInfra.modelingSend({ type: 'Finish center rectangle' })
|
||||||
|
|
||||||
|
// lee: I had this at the bottom of the function, but it's
|
||||||
|
// possible sketchFromKclValue "fails" when sketching on a face,
|
||||||
|
// and this couldn't wouldn't run.
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
||||||
|
|
||||||
const { execState } = await executeAst({
|
const { execState } = await executeAst({
|
||||||
ast: _ast,
|
ast: _ast,
|
||||||
@ -1166,13 +1369,17 @@ export class SceneEntities {
|
|||||||
if (err(moddedResult)) return
|
if (err(moddedResult)) return
|
||||||
modded = moddedResult.modifiedAst
|
modded = moddedResult.modifiedAst
|
||||||
|
|
||||||
let _recastAst = parse(recast(modded))
|
const newCode = recast(modded)
|
||||||
|
if (err(newCode)) return
|
||||||
|
let _recastAst = parse(newCode)
|
||||||
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
if (trap(_recastAst)) return Promise.reject(_recastAst)
|
||||||
_ast = _recastAst
|
_ast = _recastAst
|
||||||
|
|
||||||
// Update the primary AST and unequip the rectangle tool
|
// Update the primary AST and unequip the rectangle tool
|
||||||
await kclManager.executeAstMock(_ast)
|
await kclManager.executeAstMock(_ast)
|
||||||
sceneInfra.modelingSend({ type: 'Finish circle' })
|
sceneInfra.modelingSend({ type: 'Finish circle' })
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(_ast)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@ -1208,6 +1415,7 @@ export class SceneEntities {
|
|||||||
forward,
|
forward,
|
||||||
position,
|
position,
|
||||||
})
|
})
|
||||||
|
await codeManager.writeToFile()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDrag: async ({
|
onDrag: async ({
|
||||||
@ -1490,10 +1698,13 @@ export class SceneEntities {
|
|||||||
this.sceneProgramMemory = programMemory
|
this.sceneProgramMemory = programMemory
|
||||||
|
|
||||||
const maybeSketch = programMemory.get(variableDeclarationName)
|
const maybeSketch = programMemory.get(variableDeclarationName)
|
||||||
let sketch = undefined
|
let sketch: Sketch | undefined
|
||||||
const sg = sketchFromKclValue(maybeSketch, variableDeclarationName)
|
const sk = sketchFromKclValueOptional(
|
||||||
if (!err(sg)) {
|
maybeSketch,
|
||||||
sketch = sg
|
variableDeclarationName
|
||||||
|
)
|
||||||
|
if (!(sk instanceof Reason)) {
|
||||||
|
sketch = sk
|
||||||
} else if ((maybeSketch as Solid).sketch) {
|
} else if ((maybeSketch as Solid).sketch) {
|
||||||
sketch = (maybeSketch as Solid).sketch
|
sketch = (maybeSketch as Solid).sketch
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,8 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
|
|||||||
|
|
||||||
export const X_AXIS = 'xAxis'
|
export const X_AXIS = 'xAxis'
|
||||||
export const Y_AXIS = 'yAxis'
|
export const Y_AXIS = 'yAxis'
|
||||||
|
/** If a segment angle is less than this many degrees off a meanginful angle it'll snap to it */
|
||||||
|
export const ANGLE_SNAP_THRESHOLD_DEGREES = 3
|
||||||
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
|
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
|
||||||
export const DRAFT_POINT_GROUP = 'draft-point-group'
|
export const DRAFT_POINT_GROUP = 'draft-point-group'
|
||||||
/** the THREEjs representation of a "snapped" point that is not yet placed */
|
/** the THREEjs representation of a "snapped" point that is not yet placed */
|
||||||
|
@ -145,7 +145,7 @@ export function useCalc({
|
|||||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||||
for (const { key, value } of availableVarInfo.variables) {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
const error = _programMem.set(key, {
|
const error = _programMem.set(key, {
|
||||||
type: 'UserVal',
|
type: 'String',
|
||||||
value,
|
value,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
})
|
})
|
||||||
|
@ -22,6 +22,7 @@ import usePlatform from 'hooks/usePlatform'
|
|||||||
import { FileEntry } from 'lib/project'
|
import { FileEntry } from 'lib/project'
|
||||||
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
import { useFileSystemWatcher } from 'hooks/useFileSystemWatcher'
|
||||||
import { normalizeLineEndings } from 'lib/codeEditor'
|
import { normalizeLineEndings } from 'lib/codeEditor'
|
||||||
|
import { reportRejection } from 'lib/trap'
|
||||||
|
|
||||||
function getIndentationCSS(level: number) {
|
function getIndentationCSS(level: number) {
|
||||||
return `calc(1rem * ${level + 1})`
|
return `calc(1rem * ${level + 1})`
|
||||||
@ -196,8 +197,7 @@ const FileTreeItem = ({
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't try to read a file that was removed.
|
if (isCurrentFile && eventType === 'change') {
|
||||||
if (isCurrentFile && eventType !== 'unlink') {
|
|
||||||
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
|
||||||
code = normalizeLineEndings(code)
|
code = normalizeLineEndings(code)
|
||||||
codeManager.updateCodeStateEditor(code)
|
codeManager.updateCodeStateEditor(code)
|
||||||
@ -242,7 +242,7 @@ const FileTreeItem = ({
|
|||||||
// Show the renaming form
|
// Show the renaming form
|
||||||
addCurrentItemToRenaming()
|
addCurrentItemToRenaming()
|
||||||
} else if (e.code === 'Space') {
|
} else if (e.code === 'Space') {
|
||||||
void handleClick()
|
void handleClick().catch(reportRejection)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,7 +293,7 @@ const FileTreeItem = ({
|
|||||||
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
style={{ paddingInlineStart: getIndentationCSS(level) }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.currentTarget.focus()
|
e.currentTarget.focus()
|
||||||
void handleClick()
|
void handleClick().catch(reportRejection)
|
||||||
}}
|
}}
|
||||||
onKeyUp={handleKeyUp}
|
onKeyUp={handleKeyUp}
|
||||||
>
|
>
|
||||||
|
@ -63,6 +63,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
moveValueIntoNewVariablePath,
|
moveValueIntoNewVariablePath,
|
||||||
sketchOnExtrudedFace,
|
sketchOnExtrudedFace,
|
||||||
|
sketchOnOffsetPlane,
|
||||||
startSketchOnDefault,
|
startSketchOnDefault,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { Program, parse, recast } from 'lang/wasm'
|
import { Program, parse, recast } from 'lang/wasm'
|
||||||
@ -304,6 +305,7 @@ export const ModelingMachineProvider = ({
|
|||||||
const dispatchSelection = (selection?: EditorSelection) => {
|
const dispatchSelection = (selection?: EditorSelection) => {
|
||||||
if (!selection) return // TODO less of hack for the below please
|
if (!selection) return // TODO less of hack for the below please
|
||||||
if (!editorManager.editorView) return
|
if (!editorManager.editorView) return
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!editorManager.editorView) return
|
if (!editorManager.editorView) return
|
||||||
editorManager.editorView.dispatch({
|
editorManager.editorView.dispatch({
|
||||||
@ -482,7 +484,7 @@ export const ModelingMachineProvider = ({
|
|||||||
engineCommandManager.exportInfo = {
|
engineCommandManager.exportInfo = {
|
||||||
intent: ExportIntent.Save,
|
intent: ExportIntent.Save,
|
||||||
// This never gets used its only for make.
|
// This never gets used its only for make.
|
||||||
name: '',
|
name: file?.name?.replace('.kcl', `.${event.data.type}`) || '',
|
||||||
}
|
}
|
||||||
|
|
||||||
const format = {
|
const format = {
|
||||||
@ -635,13 +637,16 @@ export const ModelingMachineProvider = ({
|
|||||||
),
|
),
|
||||||
'animate-to-face': fromPromise(async ({ input }) => {
|
'animate-to-face': fromPromise(async ({ input }) => {
|
||||||
if (!input) return undefined
|
if (!input) return undefined
|
||||||
if (input.type === 'extrudeFace') {
|
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
|
||||||
const sketched = sketchOnExtrudedFace(
|
const sketched =
|
||||||
|
input.type === 'extrudeFace'
|
||||||
|
? sketchOnExtrudedFace(
|
||||||
kclManager.ast,
|
kclManager.ast,
|
||||||
input.sketchPathToNode,
|
input.sketchPathToNode,
|
||||||
input.extrudePathToNode,
|
input.extrudePathToNode,
|
||||||
input.faceInfo
|
input.faceInfo
|
||||||
)
|
)
|
||||||
|
: sketchOnOffsetPlane(kclManager.ast, input.pathToNode)
|
||||||
if (err(sketched)) {
|
if (err(sketched)) {
|
||||||
const sketchedError = new Error(
|
const sketchedError = new Error(
|
||||||
'Incompatible face, please try another'
|
'Incompatible face, please try another'
|
||||||
@ -653,10 +658,9 @@ export const ModelingMachineProvider = ({
|
|||||||
|
|
||||||
await kclManager.executeAstMock(modifiedAst)
|
await kclManager.executeAstMock(modifiedAst)
|
||||||
|
|
||||||
await letEngineAnimateAndSyncCamAfter(
|
const id =
|
||||||
engineCommandManager,
|
input.type === 'extrudeFace' ? input.faceId : input.planeId
|
||||||
input.faceId
|
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
|
||||||
)
|
|
||||||
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
sceneInfra.camControls.syncDirection = 'clientToEngine'
|
||||||
return {
|
return {
|
||||||
sketchPathToNode: pathToNewSketchNode,
|
sketchPathToNode: pathToNewSketchNode,
|
||||||
@ -732,6 +736,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -768,6 +777,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -813,6 +827,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -846,6 +865,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -881,6 +905,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -917,6 +946,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -953,6 +987,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -999,6 +1038,11 @@ export const ModelingMachineProvider = ({
|
|||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
if (err(updatedAst)) return Promise.reject(updatedAst)
|
if (err(updatedAst)) return Promise.reject(updatedAst)
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(
|
||||||
|
updatedAst.newAst
|
||||||
|
)
|
||||||
|
|
||||||
const selection = updateSelections(
|
const selection = updateSelections(
|
||||||
{ 0: pathToReplacedNode },
|
{ 0: pathToReplacedNode },
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
|
@ -43,6 +43,7 @@ import {
|
|||||||
completionKeymap,
|
completionKeymap,
|
||||||
} from '@codemirror/autocomplete'
|
} from '@codemirror/autocomplete'
|
||||||
import CodeEditor from './CodeEditor'
|
import CodeEditor from './CodeEditor'
|
||||||
|
import { codeManagerHistoryCompartment } from 'lang/codeManager'
|
||||||
|
|
||||||
export const editorShortcutMeta = {
|
export const editorShortcutMeta = {
|
||||||
formatCode: {
|
formatCode: {
|
||||||
@ -89,7 +90,7 @@ export const KclEditorPane = () => {
|
|||||||
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
|
cursorBlinkRate: cursorBlinking.current ? 1200 : 0,
|
||||||
}),
|
}),
|
||||||
lineHighlightField,
|
lineHighlightField,
|
||||||
history(),
|
codeManagerHistoryCompartment.of(history()),
|
||||||
closeBrackets(),
|
closeBrackets(),
|
||||||
codeFolding(),
|
codeFolding(),
|
||||||
keymap.of([
|
keymap.of([
|
||||||
@ -121,7 +122,6 @@ export const KclEditorPane = () => {
|
|||||||
lineNumbers(),
|
lineNumbers(),
|
||||||
highlightActiveLineGutter(),
|
highlightActiveLineGutter(),
|
||||||
highlightSpecialChars(),
|
highlightSpecialChars(),
|
||||||
history(),
|
|
||||||
foldGutter(),
|
foldGutter(),
|
||||||
EditorState.allowMultipleSelections.of(true),
|
EditorState.allowMultipleSelections.of(true),
|
||||||
indentOnInput(),
|
indentOnInput(),
|
||||||
|
@ -5,12 +5,12 @@ import {
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
Path,
|
Path,
|
||||||
ExtrudeSurface,
|
ExtrudeSurface,
|
||||||
sketchFromKclValue,
|
sketchFromKclValueOptional,
|
||||||
} from 'lang/wasm'
|
} from 'lang/wasm'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
import { useResolvedTheme } from 'hooks/useResolvedTheme'
|
||||||
import { ActionButton } from 'components/ActionButton'
|
import { ActionButton } from 'components/ActionButton'
|
||||||
import { err, trap } from 'lib/trap'
|
import { Reason, trap } from 'lib/trap'
|
||||||
import Tooltip from 'components/Tooltip'
|
import Tooltip from 'components/Tooltip'
|
||||||
import { useModelingContext } from 'hooks/useModelingContext'
|
import { useModelingContext } from 'hooks/useModelingContext'
|
||||||
|
|
||||||
@ -89,17 +89,17 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
const processedMemory: any = {}
|
const processedMemory: any = {}
|
||||||
for (const [key, val] of programMemory?.visibleEntries()) {
|
for (const [key, val] of programMemory?.visibleEntries()) {
|
||||||
if (
|
if (
|
||||||
(val.type === 'UserVal' && val.value.type === 'Sketch') ||
|
val.type === 'Sketch' ||
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
(val.type !== 'Function' && val.type !== 'UserVal')
|
val.type !== 'Function'
|
||||||
) {
|
) {
|
||||||
const sg = sketchFromKclValue(val, key)
|
const sk = sketchFromKclValueOptional(val, key)
|
||||||
if (val.type === 'Solid') {
|
if (val.type === 'Solid') {
|
||||||
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
processedMemory[key] = val.value.map(({ ...rest }: ExtrudeSurface) => {
|
||||||
return rest
|
return rest
|
||||||
})
|
})
|
||||||
} else if (!err(sg)) {
|
} else if (!(sk instanceof Reason)) {
|
||||||
processedMemory[key] = sg.paths.map(({ __geoMeta, ...rest }: Path) => {
|
processedMemory[key] = sk.paths.map(({ __geoMeta, ...rest }: Path) => {
|
||||||
return rest
|
return rest
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -110,8 +110,6 @@ export const processMemory = (programMemory: ProgramMemory) => {
|
|||||||
processedMemory[key] = `__function(${(val as any)?.expression?.params
|
processedMemory[key] = `__function(${(val as any)?.expression?.params
|
||||||
?.map?.(({ identifier }: any) => identifier?.name || '')
|
?.map?.(({ identifier }: any) => identifier?.name || '')
|
||||||
.join(', ')})__`
|
.join(', ')})__`
|
||||||
} else {
|
|
||||||
processedMemory[key] = val.value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return processedMemory
|
return processedMemory
|
||||||
|
81
src/editor/manager.test.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import { editorManager } from 'lib/singletons'
|
||||||
|
import { Diagnostic } from '@codemirror/lint'
|
||||||
|
|
||||||
|
describe('EditorManager Class', () => {
|
||||||
|
describe('makeUniqueDiagnostics', () => {
|
||||||
|
it('should filter out duplicated diagnostics', () => {
|
||||||
|
const duplicatedDiagnostics: Diagnostic[] = [
|
||||||
|
{
|
||||||
|
from: 2,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 2,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 2,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const expected: Diagnostic[] = [
|
||||||
|
{
|
||||||
|
from: 2,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const actual = editorManager.makeUniqueDiagnostics(duplicatedDiagnostics)
|
||||||
|
expect(actual).toStrictEqual(expected)
|
||||||
|
})
|
||||||
|
it('should filter out duplicated diagnostic and keep some original ones', () => {
|
||||||
|
const duplicatedDiagnostics: Diagnostic[] = [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 88,
|
||||||
|
to: 99,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my super cool message',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const expected: Diagnostic[] = [
|
||||||
|
{
|
||||||
|
from: 0,
|
||||||
|
to: 10,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my cool message',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
from: 88,
|
||||||
|
to: 99,
|
||||||
|
severity: 'hint',
|
||||||
|
message: 'my super cool message',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const actual = editorManager.makeUniqueDiagnostics(duplicatedDiagnostics)
|
||||||
|
expect(actual).toStrictEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -24,10 +24,6 @@ export const modelingMachineEvent = modelingMachineAnnotation.of(true)
|
|||||||
const setDiagnosticsAnnotation = Annotation.define<boolean>()
|
const setDiagnosticsAnnotation = Annotation.define<boolean>()
|
||||||
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
export const setDiagnosticsEvent = setDiagnosticsAnnotation.of(true)
|
||||||
|
|
||||||
function diagnosticIsEqual(d1: Diagnostic, d2: Diagnostic): boolean {
|
|
||||||
return d1.from === d2.from && d1.to === d2.to && d1.message === d2.message
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class EditorManager {
|
export default class EditorManager {
|
||||||
private _editorView: EditorView | null = null
|
private _editorView: EditorView | null = null
|
||||||
private _copilotEnabled: boolean = true
|
private _copilotEnabled: boolean = true
|
||||||
@ -72,9 +68,10 @@ export default class EditorManager {
|
|||||||
// we cannot use <>.constructor.name since it will get destroyed
|
// we cannot use <>.constructor.name since it will get destroyed
|
||||||
// when packaging the application.
|
// when packaging the application.
|
||||||
const isTreeHighlightPlugin =
|
const isTreeHighlightPlugin =
|
||||||
e.value.hasOwnProperty('tree') &&
|
e?.value &&
|
||||||
e.value.hasOwnProperty('decoratedTo') &&
|
e.value?.hasOwnProperty('tree') &&
|
||||||
e.value.hasOwnProperty('decorations')
|
e.value?.hasOwnProperty('decoratedTo') &&
|
||||||
|
e.value?.hasOwnProperty('decorations')
|
||||||
|
|
||||||
if (isTreeHighlightPlugin) {
|
if (isTreeHighlightPlugin) {
|
||||||
let originalUpdate = e.value.update
|
let originalUpdate = e.value.update
|
||||||
@ -161,20 +158,29 @@ export default class EditorManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given an array of Diagnostics remove any duplicates by hashing a key
|
||||||
|
* in the format of from + ' ' + to + ' ' + message.
|
||||||
|
*/
|
||||||
|
makeUniqueDiagnostics(duplicatedDiagnostics: Diagnostic[]): Diagnostic[] {
|
||||||
|
const uniqueDiagnostics: Diagnostic[] = []
|
||||||
|
const seenDiagnostic: { [key: string]: boolean } = {}
|
||||||
|
|
||||||
|
duplicatedDiagnostics.forEach((diagnostic: Diagnostic) => {
|
||||||
|
const hash = `${diagnostic.from} ${diagnostic.to} ${diagnostic.message}`
|
||||||
|
if (!seenDiagnostic[hash]) {
|
||||||
|
uniqueDiagnostics.push(diagnostic)
|
||||||
|
seenDiagnostic[hash] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return uniqueDiagnostics
|
||||||
|
}
|
||||||
|
|
||||||
setDiagnostics(diagnostics: Diagnostic[]): void {
|
setDiagnostics(diagnostics: Diagnostic[]): void {
|
||||||
if (!this._editorView) return
|
if (!this._editorView) return
|
||||||
// Clear out any existing diagnostics that are the same.
|
// Clear out any existing diagnostics that are the same.
|
||||||
for (const diagnostic of diagnostics) {
|
diagnostics = this.makeUniqueDiagnostics(diagnostics)
|
||||||
for (const otherDiagnostic of diagnostics) {
|
|
||||||
if (diagnosticIsEqual(diagnostic, otherDiagnostic)) {
|
|
||||||
diagnostics = diagnostics.filter(
|
|
||||||
(d) => !diagnosticIsEqual(d, diagnostic)
|
|
||||||
)
|
|
||||||
diagnostics.push(diagnostic)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._editorView.dispatch({
|
this._editorView.dispatch({
|
||||||
effects: [setDiagnosticsEffect.of(diagnostics)],
|
effects: [setDiagnosticsEffect.of(diagnostics)],
|
||||||
|
@ -88,6 +88,10 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
? [codeRef.range]
|
? [codeRef.range]
|
||||||
: [codeRef.range, consumedCodeRef.range]
|
: [codeRef.range, consumedCodeRef.range]
|
||||||
)
|
)
|
||||||
|
} else if (artifact?.type === 'plane') {
|
||||||
|
const codeRef = artifact.codeRef
|
||||||
|
if (err(codeRef)) return
|
||||||
|
editorManager.setHighlightRange([codeRef.range])
|
||||||
} else {
|
} else {
|
||||||
editorManager.setHighlightRange([[0, 0]])
|
editorManager.setHighlightRange([[0, 0]])
|
||||||
}
|
}
|
||||||
@ -186,8 +190,42 @@ export function useEngineConnectionSubscriptions() {
|
|||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
const artifact =
|
||||||
|
engineCommandManager.artifactGraph.get(planeOrFaceId)
|
||||||
|
|
||||||
|
if (artifact?.type === 'plane') {
|
||||||
|
const planeInfo = await getFaceDetails(planeOrFaceId)
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Select default plane',
|
||||||
|
data: {
|
||||||
|
type: 'offsetPlane',
|
||||||
|
zAxis: [
|
||||||
|
planeInfo.z_axis.x,
|
||||||
|
planeInfo.z_axis.y,
|
||||||
|
planeInfo.z_axis.z,
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
planeInfo.y_axis.x,
|
||||||
|
planeInfo.y_axis.y,
|
||||||
|
planeInfo.y_axis.z,
|
||||||
|
],
|
||||||
|
position: [
|
||||||
|
planeInfo.origin.x,
|
||||||
|
planeInfo.origin.y,
|
||||||
|
planeInfo.origin.z,
|
||||||
|
].map((num) => num / sceneInfra._baseUnitMultiplier) as [
|
||||||
|
number,
|
||||||
|
number,
|
||||||
|
number
|
||||||
|
],
|
||||||
|
planeId: planeOrFaceId,
|
||||||
|
pathToNode: artifact.codeRef.pathToNode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artifact is likely an extrusion face
|
||||||
const faceId = planeOrFaceId
|
const faceId = planeOrFaceId
|
||||||
const artifact = engineCommandManager.artifactGraph.get(faceId)
|
|
||||||
const extrusion = getSweepFromSuspectedSweepSurface(
|
const extrusion = getSweepFromSuspectedSweepSurface(
|
||||||
faceId,
|
faceId,
|
||||||
engineCommandManager.artifactGraph
|
engineCommandManager.artifactGraph
|
||||||
|
@ -2,13 +2,13 @@ import {
|
|||||||
SetVarNameModal,
|
SetVarNameModal,
|
||||||
createSetVarNameModal,
|
createSetVarNameModal,
|
||||||
} from 'components/SetVarNameModal'
|
} from 'components/SetVarNameModal'
|
||||||
import { editorManager, kclManager } from 'lib/singletons'
|
import { editorManager, kclManager, codeManager } from 'lib/singletons'
|
||||||
import { reportRejection, trap } from 'lib/trap'
|
import { reportRejection, trap, err } from 'lib/trap'
|
||||||
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
import { moveValueIntoNewVariable } from 'lang/modifyAst'
|
||||||
import { isNodeSafeToReplace } from 'lang/queryAst'
|
import { isNodeSafeToReplace } from 'lang/queryAst'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useModelingContext } from './useModelingContext'
|
import { useModelingContext } from './useModelingContext'
|
||||||
import { PathToNode, SourceRange } from 'lang/wasm'
|
import { PathToNode, SourceRange, recast } from 'lang/wasm'
|
||||||
import { useKclContext } from 'lang/KclProvider'
|
import { useKclContext } from 'lang/KclProvider'
|
||||||
import { toSync } from 'lib/utils'
|
import { toSync } from 'lib/utils'
|
||||||
|
|
||||||
@ -57,6 +57,11 @@ export function useConvertToVariable(range?: SourceRange) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
await kclManager.updateAst(_modifiedAst, true)
|
await kclManager.updateAst(_modifiedAst, true)
|
||||||
|
|
||||||
|
const newCode = recast(_modifiedAst)
|
||||||
|
if (err(newCode)) return
|
||||||
|
codeManager.updateCodeEditor(newCode)
|
||||||
|
|
||||||
return pathToReplacedNode
|
return pathToReplacedNode
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log('error', e)
|
console.log('error', e)
|
||||||
|
@ -125,7 +125,7 @@ export class KclManager {
|
|||||||
if (this.lints.length > 0) {
|
if (this.lints.length > 0) {
|
||||||
diagnostics = diagnostics.concat(this.lints)
|
diagnostics = diagnostics.concat(this.lints)
|
||||||
}
|
}
|
||||||
editorManager.setDiagnostics(diagnostics)
|
editorManager?.setDiagnostics(diagnostics)
|
||||||
}
|
}
|
||||||
|
|
||||||
addKclErrors(kclErrors: KCLError[]) {
|
addKclErrors(kclErrors: KCLError[]) {
|
||||||
@ -357,9 +357,6 @@ export class KclManager {
|
|||||||
this.clearAst()
|
this.clearAst()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
codeManager.updateCodeEditor(newCode)
|
|
||||||
// Write the file to disk.
|
|
||||||
await codeManager.writeToFile()
|
|
||||||
this._ast = { ...newAst }
|
this._ast = { ...newAst }
|
||||||
|
|
||||||
const { logs, errors, execState } = await executeAst({
|
const { logs, errors, execState } = await executeAst({
|
||||||
@ -497,11 +494,6 @@ export class KclManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (execute) {
|
if (execute) {
|
||||||
// Call execute on the set ast.
|
|
||||||
// Update the code state and editor.
|
|
||||||
codeManager.updateCodeEditor(newCode)
|
|
||||||
// Write the file to disk.
|
|
||||||
await codeManager.writeToFile()
|
|
||||||
await this.executeAst({
|
await this.executeAst({
|
||||||
ast: astWithUpdatedSource,
|
ast: astWithUpdatedSource,
|
||||||
zoomToFit: optionalParams?.zoomToFit,
|
zoomToFit: optionalParams?.zoomToFit,
|
||||||
|
@ -18,8 +18,7 @@ const mySketch001 = startSketchOn('XY')
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const sketch001 = execState.memory.get('mySketch001')
|
const sketch001 = execState.memory.get('mySketch001')
|
||||||
expect(sketch001).toEqual({
|
expect(sketch001).toEqual({
|
||||||
type: 'UserVal',
|
type: 'Sketch',
|
||||||
__meta: [{ sourceRange: [46, 71, 0] }],
|
|
||||||
value: {
|
value: {
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
|
@ -6,12 +6,17 @@ import { isDesktop } from 'lib/isDesktop'
|
|||||||
import toast from 'react-hot-toast'
|
import toast from 'react-hot-toast'
|
||||||
import { editorManager } from 'lib/singletons'
|
import { editorManager } from 'lib/singletons'
|
||||||
import { Annotation, Transaction } from '@codemirror/state'
|
import { Annotation, Transaction } from '@codemirror/state'
|
||||||
import { KeyBinding } from '@codemirror/view'
|
import { EditorView, KeyBinding } from '@codemirror/view'
|
||||||
|
import { recast, Program } from 'lang/wasm'
|
||||||
|
import { err } from 'lib/trap'
|
||||||
|
import { Compartment } from '@codemirror/state'
|
||||||
|
import { history } from '@codemirror/commands'
|
||||||
|
|
||||||
const PERSIST_CODE_KEY = 'persistCode'
|
const PERSIST_CODE_KEY = 'persistCode'
|
||||||
|
|
||||||
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
|
const codeManagerUpdateAnnotation = Annotation.define<boolean>()
|
||||||
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
|
export const codeManagerUpdateEvent = codeManagerUpdateAnnotation.of(true)
|
||||||
|
export const codeManagerHistoryCompartment = new Compartment()
|
||||||
|
|
||||||
export default class CodeManager {
|
export default class CodeManager {
|
||||||
private _code: string = bracket
|
private _code: string = bracket
|
||||||
@ -88,9 +93,12 @@ export default class CodeManager {
|
|||||||
/**
|
/**
|
||||||
* Update the code in the editor.
|
* Update the code in the editor.
|
||||||
*/
|
*/
|
||||||
updateCodeEditor(code: string): void {
|
updateCodeEditor(code: string, clearHistory?: boolean): void {
|
||||||
this.code = code
|
this.code = code
|
||||||
if (editorManager.editorView) {
|
if (editorManager.editorView) {
|
||||||
|
if (clearHistory) {
|
||||||
|
clearCodeMirrorHistory(editorManager.editorView)
|
||||||
|
}
|
||||||
editorManager.editorView.dispatch({
|
editorManager.editorView.dispatch({
|
||||||
changes: {
|
changes: {
|
||||||
from: 0,
|
from: 0,
|
||||||
@ -99,7 +107,7 @@ export default class CodeManager {
|
|||||||
},
|
},
|
||||||
annotations: [
|
annotations: [
|
||||||
codeManagerUpdateEvent,
|
codeManagerUpdateEvent,
|
||||||
Transaction.addToHistory.of(true),
|
Transaction.addToHistory.of(!clearHistory),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -108,11 +116,11 @@ export default class CodeManager {
|
|||||||
/**
|
/**
|
||||||
* Update the code, state, and the code the code mirror editor sees.
|
* Update the code, state, and the code the code mirror editor sees.
|
||||||
*/
|
*/
|
||||||
updateCodeStateEditor(code: string): void {
|
updateCodeStateEditor(code: string, clearHistory?: boolean): void {
|
||||||
if (this._code !== code) {
|
if (this._code !== code) {
|
||||||
this.code = code
|
this.code = code
|
||||||
this.#updateState(code)
|
this.#updateState(code)
|
||||||
this.updateCodeEditor(code)
|
this.updateCodeEditor(code, clearHistory)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,6 +155,13 @@ export default class CodeManager {
|
|||||||
safeLSSetItem(PERSIST_CODE_KEY, this.code)
|
safeLSSetItem(PERSIST_CODE_KEY, this.code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateEditorWithAstAndWriteToFile(ast: Program) {
|
||||||
|
const newCode = recast(ast)
|
||||||
|
if (err(newCode)) return
|
||||||
|
this.updateCodeStateEditor(newCode)
|
||||||
|
await this.writeToFile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function safeLSGetItem(key: string) {
|
function safeLSGetItem(key: string) {
|
||||||
@ -158,3 +173,17 @@ function safeLSSetItem(key: string, value: string) {
|
|||||||
if (typeof window === 'undefined') return
|
if (typeof window === 'undefined') return
|
||||||
localStorage?.setItem(key, value)
|
localStorage?.setItem(key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearCodeMirrorHistory(view: EditorView) {
|
||||||
|
// Clear history
|
||||||
|
view.dispatch({
|
||||||
|
effects: [codeManagerHistoryCompartment.reconfigure([])],
|
||||||
|
annotations: [codeManagerUpdateEvent],
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add history back
|
||||||
|
view.dispatch({
|
||||||
|
effects: [codeManagerHistoryCompartment.reconfigure([history()])],
|
||||||
|
annotations: [codeManagerUpdateEvent],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -58,7 +58,13 @@ const newVar = myVar + 1`
|
|||||||
`
|
`
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
// geo is three js buffer geometry and is very bloated to have in tests
|
// geo is three js buffer geometry and is very bloated to have in tests
|
||||||
const minusGeo = mem.get('mySketch')?.value?.paths
|
const sk = mem.get('mySketch')
|
||||||
|
expect(sk?.type).toEqual('Sketch')
|
||||||
|
if (sk?.type !== 'Sketch') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const minusGeo = sk?.value?.paths
|
||||||
expect(minusGeo).toEqual([
|
expect(minusGeo).toEqual([
|
||||||
{
|
{
|
||||||
type: 'ToPoint',
|
type: 'ToPoint',
|
||||||
@ -150,7 +156,7 @@ const newVar = myVar + 1`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('mySk1')).toEqual({
|
expect(mem.get('mySk1')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'Sketch',
|
||||||
value: {
|
value: {
|
||||||
type: 'Sketch',
|
type: 'Sketch',
|
||||||
on: expect.any(Object),
|
on: expect.any(Object),
|
||||||
@ -215,7 +221,6 @@ const newVar = myVar + 1`
|
|||||||
id: expect.any(String),
|
id: expect.any(String),
|
||||||
__meta: [{ sourceRange: [39, 63, 0] }],
|
__meta: [{ sourceRange: [39, 63, 0] }],
|
||||||
},
|
},
|
||||||
__meta: [{ sourceRange: [39, 63, 0] }],
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('execute array expression', async () => {
|
it('execute array expression', async () => {
|
||||||
@ -225,7 +230,7 @@ const newVar = myVar + 1`
|
|||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
// TODO path to node is probably wrong here, zero indexes are not correct
|
// TODO path to node is probably wrong here, zero indexes are not correct
|
||||||
expect(mem.get('three')).toEqual({
|
expect(mem.get('three')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'Int',
|
||||||
value: 3,
|
value: 3,
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
@ -234,8 +239,17 @@ const newVar = myVar + 1`
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
expect(mem.get('yo')).toEqual({
|
expect(mem.get('yo')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'Array',
|
||||||
value: [1, '2', 3, 9],
|
value: [
|
||||||
|
{ type: 'Int', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
|
||||||
|
{ type: 'String', value: '2', __meta: [{ sourceRange: [31, 34, 0] }] },
|
||||||
|
{ type: 'Int', value: 3, __meta: [{ sourceRange: [14, 15, 0] }] },
|
||||||
|
{
|
||||||
|
type: 'Number',
|
||||||
|
value: 9,
|
||||||
|
__meta: [{ sourceRange: [43, 44, 0] }, { sourceRange: [47, 48, 0] }],
|
||||||
|
},
|
||||||
|
],
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
sourceRange: [27, 49, 0],
|
sourceRange: [27, 49, 0],
|
||||||
@ -253,8 +267,25 @@ const newVar = myVar + 1`
|
|||||||
].join('\n')
|
].join('\n')
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('yo')).toEqual({
|
expect(mem.get('yo')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'Object',
|
||||||
value: { aStr: 'str', anum: 2, identifier: 3, binExp: 9 },
|
value: {
|
||||||
|
aStr: {
|
||||||
|
type: 'String',
|
||||||
|
value: 'str',
|
||||||
|
__meta: [{ sourceRange: [34, 39, 0] }],
|
||||||
|
},
|
||||||
|
anum: { type: 'Int', value: 2, __meta: [{ sourceRange: [47, 48, 0] }] },
|
||||||
|
identifier: {
|
||||||
|
type: 'Int',
|
||||||
|
value: 3,
|
||||||
|
__meta: [{ sourceRange: [14, 15, 0] }],
|
||||||
|
},
|
||||||
|
binExp: {
|
||||||
|
type: 'Number',
|
||||||
|
value: 9,
|
||||||
|
__meta: [{ sourceRange: [77, 78, 0] }, { sourceRange: [81, 82, 0] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
sourceRange: [27, 83, 0],
|
sourceRange: [27, 83, 0],
|
||||||
@ -268,11 +299,11 @@ const newVar = myVar + 1`
|
|||||||
)
|
)
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')).toEqual({
|
expect(mem.get('myVar')).toEqual({
|
||||||
type: 'UserVal',
|
type: 'String',
|
||||||
value: '123',
|
value: '123',
|
||||||
__meta: [
|
__meta: [
|
||||||
{
|
{
|
||||||
sourceRange: [41, 50, 0],
|
sourceRange: [19, 24, 0],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
@ -356,7 +387,26 @@ describe('testing math operators', () => {
|
|||||||
it('with unaryExpression in ArrayExpression', async () => {
|
it('with unaryExpression in ArrayExpression', async () => {
|
||||||
const code = 'const myVar = [1,-legLen(5, 4)]'
|
const code = 'const myVar = [1,-legLen(5, 4)]'
|
||||||
const mem = await exe(code)
|
const mem = await exe(code)
|
||||||
expect(mem.get('myVar')?.value).toEqual([1, -3])
|
expect(mem.get('myVar')?.value).toEqual([
|
||||||
|
{
|
||||||
|
__meta: [
|
||||||
|
{
|
||||||
|
sourceRange: [15, 16, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'Int',
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
__meta: [
|
||||||
|
{
|
||||||
|
sourceRange: [17, 30, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
type: 'Number',
|
||||||
|
value: -3,
|
||||||
|
},
|
||||||
|
])
|
||||||
})
|
})
|
||||||
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
it('with unaryExpression in ArrayExpression in CallExpression, checking nothing funny happens when used in a sketch', async () => {
|
||||||
const code = [
|
const code = [
|
||||||
|
@ -55,18 +55,13 @@ describe('Test KCL Samples from public Github repository', () => {
|
|||||||
})
|
})
|
||||||
// Run through all of the files in the manifest json. This will allow us to be automatically updated
|
// Run through all of the files in the manifest json. This will allow us to be automatically updated
|
||||||
// with the latest changes in github. We won't be hard coding the filenames
|
// with the latest changes in github. We won't be hard coding the filenames
|
||||||
it(
|
files.forEach((file: KclSampleFile) => {
|
||||||
'should run through all the files',
|
it(`should parse ${file.filename} without errors`, async () => {
|
||||||
async () => {
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
const file: KclSampleFile = files[i]
|
|
||||||
const code = await getKclSampleCodeFromGithub(file.filename)
|
const code = await getKclSampleCodeFromGithub(file.filename)
|
||||||
const parsed = parse(code)
|
const parsed = parse(code)
|
||||||
assert(!(parsed instanceof Error))
|
assert(!(parsed instanceof Error))
|
||||||
}
|
}, 1000)
|
||||||
},
|
})
|
||||||
files.length * 1000
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when performing enginelessExecutor', () => {
|
describe('when performing enginelessExecutor', () => {
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
VariableDeclarator,
|
VariableDeclarator,
|
||||||
Expr,
|
Expr,
|
||||||
Literal,
|
Literal,
|
||||||
|
LiteralValue,
|
||||||
PipeSubstitution,
|
PipeSubstitution,
|
||||||
Identifier,
|
Identifier,
|
||||||
ArrayExpression,
|
ArrayExpression,
|
||||||
@ -18,6 +19,7 @@ import {
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
|
isPathToNodeNumber,
|
||||||
} from './wasm'
|
} from './wasm'
|
||||||
import {
|
import {
|
||||||
isNodeSafeToReplacePath,
|
isNodeSafeToReplacePath,
|
||||||
@ -525,6 +527,60 @@ export function sketchOnExtrudedFace(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modify the AST to create a new sketch using the variable declaration
|
||||||
|
* of an offset plane. The new sketch just has to come after the offset
|
||||||
|
* plane declaration.
|
||||||
|
*/
|
||||||
|
export function sketchOnOffsetPlane(
|
||||||
|
node: Node<Program>,
|
||||||
|
offsetPathToNode: PathToNode
|
||||||
|
) {
|
||||||
|
let _node = { ...node }
|
||||||
|
|
||||||
|
// Find the offset plane declaration
|
||||||
|
const offsetPlaneDeclarator = getNodeFromPath<VariableDeclarator>(
|
||||||
|
_node,
|
||||||
|
offsetPathToNode,
|
||||||
|
'VariableDeclarator',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
if (err(offsetPlaneDeclarator)) return offsetPlaneDeclarator
|
||||||
|
const { node: offsetPlaneNode } = offsetPlaneDeclarator
|
||||||
|
const offsetPlaneName = offsetPlaneNode.id.name
|
||||||
|
|
||||||
|
// Create a new sketch declaration
|
||||||
|
const newSketchName = findUniqueName(
|
||||||
|
node,
|
||||||
|
KCL_DEFAULT_CONSTANT_PREFIXES.SKETCH
|
||||||
|
)
|
||||||
|
const newSketch = createVariableDeclaration(
|
||||||
|
newSketchName,
|
||||||
|
createCallExpressionStdLib('startSketchOn', [
|
||||||
|
createIdentifier(offsetPlaneName),
|
||||||
|
]),
|
||||||
|
undefined,
|
||||||
|
'const'
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decide where to insert the new sketch declaration
|
||||||
|
const offsetIndex = offsetPathToNode[1][0]
|
||||||
|
|
||||||
|
if (!isPathToNodeNumber(offsetIndex)) {
|
||||||
|
return new Error('Expected offsetIndex to be a number')
|
||||||
|
}
|
||||||
|
// and insert it
|
||||||
|
_node.body.splice(offsetIndex + 1, 0, newSketch)
|
||||||
|
const newPathToNode = structuredClone(offsetPathToNode)
|
||||||
|
newPathToNode[1][0] = offsetIndex + 1
|
||||||
|
|
||||||
|
// Return the modified AST and the path to the new sketch declaration
|
||||||
|
return {
|
||||||
|
modifiedAst: _node,
|
||||||
|
pathToNode: newPathToNode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const getLastIndex = (pathToNode: PathToNode): number =>
|
export const getLastIndex = (pathToNode: PathToNode): number =>
|
||||||
splitPathAtLastIndex(pathToNode).index
|
splitPathAtLastIndex(pathToNode).index
|
||||||
|
|
||||||
@ -573,7 +629,7 @@ export function splitPathAtPipeExpression(pathToNode: PathToNode): {
|
|||||||
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
return splitPathAtPipeExpression(pathToNode.slice(0, -1))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createLiteral(value: string | number): Node<Literal> {
|
export function createLiteral(value: LiteralValue): Node<Literal> {
|
||||||
return {
|
return {
|
||||||
type: 'Literal',
|
type: 'Literal',
|
||||||
start: 0,
|
start: 0,
|
||||||
|
@ -77,15 +77,21 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
code.indexOf(expectedExtrudeSnippet),
|
code.indexOf(expectedExtrudeSnippet),
|
||||||
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
code.indexOf(expectedExtrudeSnippet) + expectedExtrudeSnippet.length,
|
||||||
]
|
]
|
||||||
const expedtedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
const expectedExtrudePath = getNodePathFromSourceRange(ast, extrudeRange)
|
||||||
const expedtedExtrudeNodeResult = getNodeFromPath<VariableDeclarator>(
|
const expectedExtrudeNodeResult = getNodeFromPath<
|
||||||
ast,
|
VariableDeclarator | CallExpression
|
||||||
expedtedExtrudePath
|
>(ast, expectedExtrudePath)
|
||||||
)
|
if (err(expectedExtrudeNodeResult)) {
|
||||||
if (err(expedtedExtrudeNodeResult)) {
|
return expectedExtrudeNodeResult
|
||||||
return expedtedExtrudeNodeResult
|
|
||||||
}
|
}
|
||||||
const expectedExtrudeNode = expedtedExtrudeNodeResult.node
|
const expectedExtrudeNode = expectedExtrudeNodeResult.node
|
||||||
|
|
||||||
|
// check whether extrude is in the sketch pipe
|
||||||
|
const extrudeInSketchPipe = expectedExtrudeNode.type === 'CallExpression'
|
||||||
|
if (extrudeInSketchPipe) {
|
||||||
|
return expectedExtrudeNode
|
||||||
|
}
|
||||||
|
if (!extrudeInSketchPipe) {
|
||||||
const init = expectedExtrudeNode.init
|
const init = expectedExtrudeNode.init
|
||||||
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
|
if (init.type !== 'CallExpression' && init.type !== 'PipeExpression') {
|
||||||
return new Error(
|
return new Error(
|
||||||
@ -94,6 +100,8 @@ const runGetPathToExtrudeForSegmentSelectionTest = async (
|
|||||||
}
|
}
|
||||||
return init
|
return init
|
||||||
}
|
}
|
||||||
|
return new Error('Expected extrude expression not found')
|
||||||
|
}
|
||||||
|
|
||||||
// ast
|
// ast
|
||||||
const astOrError = parse(code)
|
const astOrError = parse(code)
|
||||||
@ -160,6 +168,23 @@ extrude001 = extrude(-15, sketch001)`
|
|||||||
expectedExtrudeSnippet
|
expectedExtrudeSnippet
|
||||||
)
|
)
|
||||||
}, 5_000)
|
}, 5_000)
|
||||||
|
it('should return the correct paths when extrusion occurs within the sketch pipe', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(15, %)`
|
||||||
|
const selectedSegmentSnippet = `line([20, 0], %)`
|
||||||
|
const expectedExtrudeSnippet = `extrude(15, %)`
|
||||||
|
await runGetPathToExtrudeForSegmentSelectionTest(
|
||||||
|
code,
|
||||||
|
selectedSegmentSnippet,
|
||||||
|
expectedExtrudeSnippet
|
||||||
|
)
|
||||||
|
}, 5_000)
|
||||||
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
it('should return the correct paths for a valid selection and extrusion in case of several extrusions and sketches', async () => {
|
||||||
const code = `sketch001 = startSketchOn('XY')
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|> startProfileAt([-30, 30], %)
|
|> startProfileAt([-30, 30], %)
|
||||||
@ -296,6 +321,34 @@ extrude001 = extrude(-15, sketch001)`
|
|||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(-15, sketch001)
|
extrude001 = extrude(-15, sketch001)
|
||||||
|
|> fillet({ radius: 3, tags: [seg01] }, %)`
|
||||||
|
|
||||||
|
await runModifyAstCloneWithFilletAndTag(
|
||||||
|
code,
|
||||||
|
segmentSnippets,
|
||||||
|
radiusValue,
|
||||||
|
expectedCode
|
||||||
|
)
|
||||||
|
})
|
||||||
|
it('should add a fillet to the sketch pipe', async () => {
|
||||||
|
const code = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(-15, %)`
|
||||||
|
const segmentSnippets = ['line([0, -20], %)']
|
||||||
|
const radiusValue = 3
|
||||||
|
const expectedCode = `sketch001 = startSketchOn('XY')
|
||||||
|
|> startProfileAt([-10, 10], %)
|
||||||
|
|> line([20, 0], %)
|
||||||
|
|> line([0, -20], %, $seg01)
|
||||||
|
|> line([-20, 0], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(-15, %)
|
||||||
|> fillet({ radius: 3, tags: [seg01] }, %)`
|
|> fillet({ radius: 3, tags: [seg01] }, %)`
|
||||||
|
|
||||||
await runModifyAstCloneWithFilletAndTag(
|
await runModifyAstCloneWithFilletAndTag(
|
||||||
|
@ -35,7 +35,12 @@ import {
|
|||||||
ArtifactGraph,
|
ArtifactGraph,
|
||||||
getSweepFromSuspectedPath,
|
getSweepFromSuspectedPath,
|
||||||
} from 'lang/std/artifactGraph'
|
} from 'lang/std/artifactGraph'
|
||||||
import { kclManager, engineCommandManager, editorManager } from 'lib/singletons'
|
import {
|
||||||
|
kclManager,
|
||||||
|
engineCommandManager,
|
||||||
|
editorManager,
|
||||||
|
codeManager,
|
||||||
|
} from 'lib/singletons'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
// Apply Fillet To Selection
|
// Apply Fillet To Selection
|
||||||
@ -141,7 +146,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
|
|
||||||
// Modify the extrude expression to include this fillet expression
|
// Modify the extrude expression to include this fillet expression
|
||||||
// CallExpression - no fillet
|
// CallExpression - no fillet
|
||||||
// PipeExpression - fillet exists
|
// PipeExpression - fillet exists or extrude in sketch pipe
|
||||||
|
|
||||||
let pathToFilletNode: PathToNode = []
|
let pathToFilletNode: PathToNode = []
|
||||||
|
|
||||||
@ -162,15 +167,7 @@ export function modifyAstCloneWithFilletAndTag(
|
|||||||
)
|
)
|
||||||
pathToFilletNodes.push(pathToFilletNode)
|
pathToFilletNodes.push(pathToFilletNode)
|
||||||
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
} else if (extrudeDeclarator.init.type === 'PipeExpression') {
|
||||||
// 2. case when fillet exists
|
// 2. case when fillet exists or extrude in sketch pipe
|
||||||
|
|
||||||
const existingFilletCall = extrudeDeclarator.init.body.find((node) => {
|
|
||||||
return node.type === 'CallExpression' && node.callee.name === 'fillet'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!existingFilletCall || existingFilletCall.type !== 'CallExpression') {
|
|
||||||
return new Error('Fillet CallExpression not found.')
|
|
||||||
}
|
|
||||||
|
|
||||||
// mutate the extrude node with the new fillet call
|
// mutate the extrude node with the new fillet call
|
||||||
extrudeDeclarator.init.body.push(filletCall)
|
extrudeDeclarator.init.body.push(filletCall)
|
||||||
@ -253,6 +250,9 @@ async function updateAstAndFocus(
|
|||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
focusPath: pathToFilletNode,
|
focusPath: pathToFilletNode,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
|
|
||||||
if (updatedAst?.selections) {
|
if (updatedAst?.selections) {
|
||||||
editorManager.selectRange(updatedAst?.selections)
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
}
|
}
|
||||||
@ -309,14 +309,14 @@ function locateExtrudeDeclarator(
|
|||||||
node: Program,
|
node: Program,
|
||||||
pathToExtrudeNode: PathToNode
|
pathToExtrudeNode: PathToNode
|
||||||
): { extrudeDeclarator: VariableDeclarator } | Error {
|
): { extrudeDeclarator: VariableDeclarator } | Error {
|
||||||
const extrudeChunk = getNodeFromPath<VariableDeclaration>(
|
const nodeOfExtrudeCall = getNodeFromPath<VariableDeclaration>(
|
||||||
node,
|
node,
|
||||||
pathToExtrudeNode,
|
pathToExtrudeNode,
|
||||||
'VariableDeclaration'
|
'VariableDeclaration'
|
||||||
)
|
)
|
||||||
if (err(extrudeChunk)) return extrudeChunk
|
if (err(nodeOfExtrudeCall)) return nodeOfExtrudeCall
|
||||||
|
|
||||||
const { node: extrudeVarDecl } = extrudeChunk
|
const { node: extrudeVarDecl } = nodeOfExtrudeCall
|
||||||
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
const extrudeDeclarator = extrudeVarDecl.declarations[0]
|
||||||
if (!extrudeDeclarator) {
|
if (!extrudeDeclarator) {
|
||||||
return new Error('Extrude Declarator not found.')
|
return new Error('Extrude Declarator not found.')
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { parse, recast, initPromise, PathToNode } from './wasm'
|
import { parse, recast, initPromise, PathToNode, Identifier } from './wasm'
|
||||||
import {
|
import {
|
||||||
findAllPreviousVariables,
|
findAllPreviousVariables,
|
||||||
isNodeSafeToReplace,
|
isNodeSafeToReplace,
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
hasSketchPipeBeenExtruded,
|
hasSketchPipeBeenExtruded,
|
||||||
doesSceneHaveSweepableSketch,
|
doesSceneHaveSweepableSketch,
|
||||||
traverse,
|
traverse,
|
||||||
|
getNodeFromPath,
|
||||||
} from './queryAst'
|
} from './queryAst'
|
||||||
import { enginelessExecutor } from '../lib/testHelpers'
|
import { enginelessExecutor } from '../lib/testHelpers'
|
||||||
import {
|
import {
|
||||||
@ -266,6 +267,86 @@ describe('testing getNodePathFromSourceRange', () => {
|
|||||||
])
|
])
|
||||||
expect(selectWholeThing).toEqual(expected)
|
expect(selectWholeThing).toEqual(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('finds the node in if-else condition', () => {
|
||||||
|
const code = `y = 0
|
||||||
|
x = if x > y {
|
||||||
|
x + 1
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
}`
|
||||||
|
const searchLn = `x > y`
|
||||||
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
|
const ast = parse(code)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
|
||||||
|
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||||
|
expect(result).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[1, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['cond', 'IfExpression'],
|
||||||
|
['left', 'BinaryExpression'],
|
||||||
|
])
|
||||||
|
const _node = getNodeFromPath<Identifier>(ast, result)
|
||||||
|
if (err(_node)) throw _node
|
||||||
|
expect(_node.node.type).toEqual('Identifier')
|
||||||
|
expect(_node.node.name).toEqual('x')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the node in if-else then', () => {
|
||||||
|
const code = `y = 0
|
||||||
|
x = if x > y {
|
||||||
|
x + 1
|
||||||
|
} else {
|
||||||
|
y
|
||||||
|
}`
|
||||||
|
const searchLn = `x + 1`
|
||||||
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
|
const ast = parse(code)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
|
||||||
|
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||||
|
expect(result).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[1, 'index'],
|
||||||
|
['declarations', 'VariableDeclaration'],
|
||||||
|
[0, 'index'],
|
||||||
|
['init', ''],
|
||||||
|
['then_val', 'IfExpression'],
|
||||||
|
['body', 'IfExpression'],
|
||||||
|
[0, 'index'],
|
||||||
|
['expression', 'ExpressionStatement'],
|
||||||
|
['left', 'BinaryExpression'],
|
||||||
|
])
|
||||||
|
const _node = getNodeFromPath<Identifier>(ast, result)
|
||||||
|
if (err(_node)) throw _node
|
||||||
|
expect(_node.node.type).toEqual('Identifier')
|
||||||
|
expect(_node.node.name).toEqual('x')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the node in import statement item', () => {
|
||||||
|
const code = `import foo, bar as baz from 'thing.kcl'`
|
||||||
|
const searchLn = `bar`
|
||||||
|
const sourceIndex = code.indexOf(searchLn)
|
||||||
|
const ast = parse(code)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
|
||||||
|
const result = getNodePathFromSourceRange(ast, [sourceIndex, sourceIndex])
|
||||||
|
expect(result).toEqual([
|
||||||
|
['body', ''],
|
||||||
|
[0, 'index'],
|
||||||
|
['items', 'ImportStatement'],
|
||||||
|
[1, 'index'],
|
||||||
|
['name', 'ImportItem'],
|
||||||
|
])
|
||||||
|
const _node = getNodeFromPath<Identifier>(ast, result)
|
||||||
|
if (err(_node)) throw _node
|
||||||
|
expect(_node.node.type).toEqual('Identifier')
|
||||||
|
expect(_node.node.name).toEqual('bar')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('testing doesPipeHave', () => {
|
describe('testing doesPipeHave', () => {
|
||||||
@ -449,14 +530,25 @@ describe('Testing hasSketchPipeBeenExtruded', () => {
|
|||||||
|> line([-17.67, 0.85], %)
|
|> line([-17.67, 0.85], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
extrude001 = extrude(10, sketch001)
|
extrude001 = extrude(10, sketch001)
|
||||||
sketch002 = startSketchOn(extrude001, $seg01)
|
sketch002 = startSketchOn(extrude001, seg01)
|
||||||
|> startProfileAt([-12.94, 6.6], %)
|
|> startProfileAt([-12.94, 6.6], %)
|
||||||
|> line([2.45, -0.2], %)
|
|> line([2.45, -0.2], %)
|
||||||
|> line([-2, -1.25], %)
|
|> line([-2, -1.25], %)
|
||||||
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|> close(%)
|
|> close(%)
|
||||||
|
sketch003 = startSketchOn(extrude001, 'END')
|
||||||
|
|> startProfileAt([8.14, 2.8], %)
|
||||||
|
|> line([-1.24, 4.39], %)
|
||||||
|
|> line([3.79, 1.91], %)
|
||||||
|
|> line([1.77, -2.95], %)
|
||||||
|
|> line([3.12, 1.74], %)
|
||||||
|
|> line([1.91, -4.09], %)
|
||||||
|
|> line([-5.6, -2.75], %)
|
||||||
|
|> lineTo([profileStartX(%), profileStartY(%)], %)
|
||||||
|
|> close(%)
|
||||||
|
|> extrude(3.14, %)
|
||||||
`
|
`
|
||||||
it('finds sketch001 pipe to be extruded', async () => {
|
it('identifies sketch001 pipe as extruded (extrusion after pipe)', async () => {
|
||||||
const ast = parse(exampleCode)
|
const ast = parse(exampleCode)
|
||||||
if (err(ast)) throw ast
|
if (err(ast)) throw ast
|
||||||
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
|
const lineOfInterest = `line([4.99, -0.46], %, $seg01)`
|
||||||
@ -471,7 +563,7 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
)
|
)
|
||||||
expect(extruded).toBeTruthy()
|
expect(extruded).toBeTruthy()
|
||||||
})
|
})
|
||||||
it('find sketch002 NOT pipe to be extruded', async () => {
|
it('identifies sketch002 pipe as not extruded', async () => {
|
||||||
const ast = parse(exampleCode)
|
const ast = parse(exampleCode)
|
||||||
if (err(ast)) throw ast
|
if (err(ast)) throw ast
|
||||||
const lineOfInterest = `line([2.45, -0.2], %)`
|
const lineOfInterest = `line([2.45, -0.2], %)`
|
||||||
@ -486,6 +578,21 @@ sketch002 = startSketchOn(extrude001, $seg01)
|
|||||||
)
|
)
|
||||||
expect(extruded).toBeFalsy()
|
expect(extruded).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
it('identifies sketch003 pipe as extruded (extrusion within pipe)', async () => {
|
||||||
|
const ast = parse(exampleCode)
|
||||||
|
if (err(ast)) throw ast
|
||||||
|
const lineOfInterest = `|> line([3.12, 1.74], %)`
|
||||||
|
const characterIndex =
|
||||||
|
exampleCode.indexOf(lineOfInterest) + lineOfInterest.length
|
||||||
|
const extruded = hasSketchPipeBeenExtruded(
|
||||||
|
{
|
||||||
|
range: [characterIndex, characterIndex],
|
||||||
|
type: 'default',
|
||||||
|
},
|
||||||
|
ast
|
||||||
|
)
|
||||||
|
expect(extruded).toBeTruthy()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Testing doesSceneHaveSweepableSketch', () => {
|
describe('Testing doesSceneHaveSweepableSketch', () => {
|
||||||
|
@ -14,6 +14,7 @@ import {
|
|||||||
ProgramMemory,
|
ProgramMemory,
|
||||||
ReturnStatement,
|
ReturnStatement,
|
||||||
sketchFromKclValue,
|
sketchFromKclValue,
|
||||||
|
sketchFromKclValueOptional,
|
||||||
SourceRange,
|
SourceRange,
|
||||||
SyntaxType,
|
SyntaxType,
|
||||||
VariableDeclaration,
|
VariableDeclaration,
|
||||||
@ -27,7 +28,7 @@ import {
|
|||||||
getConstraintLevelFromSourceRange,
|
getConstraintLevelFromSourceRange,
|
||||||
getConstraintType,
|
getConstraintType,
|
||||||
} from './std/sketchcombos'
|
} from './std/sketchcombos'
|
||||||
import { err } from 'lib/trap'
|
import { err, Reason } from 'lib/trap'
|
||||||
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
|
||||||
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
import { Node } from 'wasm-lib/kcl/bindings/Node'
|
||||||
|
|
||||||
@ -317,6 +318,62 @@ function moreNodePathFromSourceRange(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
if (_node.type === 'PipeSubstitution' && isInRange) return path
|
||||||
|
|
||||||
|
if (_node.type === 'IfExpression' && isInRange) {
|
||||||
|
const { cond, then_val, else_ifs, final_else } = _node
|
||||||
|
if (cond.start <= start && cond.end >= end) {
|
||||||
|
path.push(['cond', 'IfExpression'])
|
||||||
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||||
|
}
|
||||||
|
if (then_val.start <= start && then_val.end >= end) {
|
||||||
|
path.push(['then_val', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||||
|
}
|
||||||
|
for (let i = 0; i < else_ifs.length; i++) {
|
||||||
|
const else_if = else_ifs[i]
|
||||||
|
if (else_if.start <= start && else_if.end >= end) {
|
||||||
|
path.push(['else_ifs', 'IfExpression'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
const { cond, then_val } = else_if
|
||||||
|
if (cond.start <= start && cond.end >= end) {
|
||||||
|
path.push(['cond', 'IfExpression'])
|
||||||
|
return moreNodePathFromSourceRange(cond, sourceRange, path)
|
||||||
|
}
|
||||||
|
path.push(['then_val', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(then_val, sourceRange, path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (final_else.start <= start && final_else.end >= end) {
|
||||||
|
path.push(['final_else', 'IfExpression'])
|
||||||
|
path.push(['body', 'IfExpression'])
|
||||||
|
return getNodePathFromSourceRange(final_else, sourceRange, path)
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_node.type === 'ImportStatement' && isInRange) {
|
||||||
|
const { items } = _node
|
||||||
|
for (let i = 0; i < items.length; i++) {
|
||||||
|
const item = items[i]
|
||||||
|
if (item.start <= start && item.end >= end) {
|
||||||
|
path.push(['items', 'ImportStatement'])
|
||||||
|
path.push([i, 'index'])
|
||||||
|
if (item.name.start <= start && item.name.end >= end) {
|
||||||
|
path.push(['name', 'ImportItem'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
if (item.alias && item.alias.start <= start && item.alias.end >= end) {
|
||||||
|
path.push(['alias', 'ImportItem'])
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
console.error('not implemented: ' + node.type)
|
console.error('not implemented: ' + node.type)
|
||||||
|
|
||||||
return path
|
return path
|
||||||
@ -790,7 +847,8 @@ export function hasExtrudeSketch({
|
|||||||
const varName = varDec.declarations[0].id.name
|
const varName = varDec.declarations[0].id.name
|
||||||
const varValue = programMemory?.get(varName)
|
const varValue = programMemory?.get(varName)
|
||||||
return (
|
return (
|
||||||
varValue?.type === 'Solid' || !err(sketchFromKclValue(varValue, varName))
|
varValue?.type === 'Solid' ||
|
||||||
|
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -871,7 +929,11 @@ export function findUsesOfTagInPipe(
|
|||||||
|
|
||||||
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
||||||
const path = getNodePathFromSourceRange(ast, selection.range)
|
const path = getNodePathFromSourceRange(ast, selection.range)
|
||||||
const _node = getNodeFromPath<PipeExpression>(ast, path, 'PipeExpression')
|
const _node = getNodeFromPath<Node<PipeExpression>>(
|
||||||
|
ast,
|
||||||
|
path,
|
||||||
|
'PipeExpression'
|
||||||
|
)
|
||||||
if (err(_node)) return false
|
if (err(_node)) return false
|
||||||
const { node: pipeExpression } = _node
|
const { node: pipeExpression } = _node
|
||||||
if (pipeExpression.type !== 'PipeExpression') return false
|
if (pipeExpression.type !== 'PipeExpression') return false
|
||||||
@ -884,6 +946,19 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
|||||||
const varDec = _varDec.node
|
const varDec = _varDec.node
|
||||||
if (varDec.type !== 'VariableDeclarator') return false
|
if (varDec.type !== 'VariableDeclarator') return false
|
||||||
let extruded = false
|
let extruded = false
|
||||||
|
// option 1: extrude or revolve is called in the sketch pipe
|
||||||
|
traverse(pipeExpression, {
|
||||||
|
enter(node) {
|
||||||
|
if (
|
||||||
|
node.type === 'CallExpression' &&
|
||||||
|
(node.callee.name === 'extrude' || node.callee.name === 'revolve')
|
||||||
|
) {
|
||||||
|
extruded = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// option 2: extrude or revolve is called in the separate pipe
|
||||||
|
if (!extruded) {
|
||||||
traverse(ast as any, {
|
traverse(ast as any, {
|
||||||
enter(node) {
|
enter(node) {
|
||||||
if (
|
if (
|
||||||
@ -897,6 +972,7 @@ export function hasSketchPipeBeenExtruded(selection: Selection, ast: Program) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
}
|
||||||
return extruded
|
return extruded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,12 +98,22 @@ sketch004 = startSketchOn(extrude003, seg02)
|
|||||||
|> close(%)
|
|> close(%)
|
||||||
extrude004 = extrude(3, sketch004)
|
extrude004 = extrude(3, sketch004)
|
||||||
`
|
`
|
||||||
|
const exampleCodeOffsetPlanes = `
|
||||||
|
offsetPlane001 = offsetPlane("XY", 20)
|
||||||
|
offsetPlane002 = offsetPlane("XZ", -50)
|
||||||
|
offsetPlane003 = offsetPlane("YZ", 10)
|
||||||
|
|
||||||
|
sketch002 = startSketchOn(offsetPlane001)
|
||||||
|
|> startProfileAt([0, 0], %)
|
||||||
|
|> line([6.78, 15.01], %)
|
||||||
|
`
|
||||||
|
|
||||||
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests
|
// add more code snippets here and use `getCommands` to get the orderedCommands and responseMap for more tests
|
||||||
const codeToWriteCacheFor = {
|
const codeToWriteCacheFor = {
|
||||||
exampleCode1,
|
exampleCode1,
|
||||||
sketchOnFaceOnFaceEtc,
|
sketchOnFaceOnFaceEtc,
|
||||||
exampleCodeNo3D,
|
exampleCodeNo3D,
|
||||||
|
exampleCodeOffsetPlanes,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
type CodeKey = keyof typeof codeToWriteCacheFor
|
type CodeKey = keyof typeof codeToWriteCacheFor
|
||||||
@ -165,6 +175,52 @@ afterAll(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('testing createArtifactGraph', () => {
|
describe('testing createArtifactGraph', () => {
|
||||||
|
describe('code with offset planes and a sketch:', () => {
|
||||||
|
let ast: Program
|
||||||
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
|
|
||||||
|
it('setup', () => {
|
||||||
|
// putting this logic in here because describe blocks runs before beforeAll has finished
|
||||||
|
const {
|
||||||
|
orderedCommands,
|
||||||
|
responseMap,
|
||||||
|
ast: _ast,
|
||||||
|
} = getCommands('exampleCodeOffsetPlanes')
|
||||||
|
ast = _ast
|
||||||
|
theMap = createArtifactGraph({ orderedCommands, responseMap, ast })
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`there should be one sketch`, () => {
|
||||||
|
const sketches = [...filterArtifacts({ types: ['path'] }, theMap)].map(
|
||||||
|
(path) => expandPath(path[1], theMap)
|
||||||
|
)
|
||||||
|
expect(sketches).toHaveLength(1)
|
||||||
|
sketches.forEach((path) => {
|
||||||
|
if (err(path)) throw path
|
||||||
|
expect(path.type).toBe('path')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`there should be three offsetPlanes`, () => {
|
||||||
|
const offsetPlanes = [
|
||||||
|
...filterArtifacts({ types: ['plane'] }, theMap),
|
||||||
|
].map((plane) => expandPlane(plane[1], theMap))
|
||||||
|
expect(offsetPlanes).toHaveLength(3)
|
||||||
|
offsetPlanes.forEach((path) => {
|
||||||
|
expect(path.type).toBe('plane')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`Only one offset plane should have a path`, () => {
|
||||||
|
const offsetPlanes = [
|
||||||
|
...filterArtifacts({ types: ['plane'] }, theMap),
|
||||||
|
].map((plane) => expandPlane(plane[1], theMap))
|
||||||
|
const offsetPlaneWithPaths = offsetPlanes.filter(
|
||||||
|
(plane) => plane.paths.length
|
||||||
|
)
|
||||||
|
expect(offsetPlaneWithPaths).toHaveLength(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
describe('code with an extrusion, fillet and sketch of face:', () => {
|
describe('code with an extrusion, fillet and sketch of face:', () => {
|
||||||
let ast: Program
|
let ast: Program
|
||||||
let theMap: ReturnType<typeof createArtifactGraph>
|
let theMap: ReturnType<typeof createArtifactGraph>
|
||||||
|
@ -249,7 +249,20 @@ export function getArtifactsToUpdate({
|
|||||||
const cmd = command.cmd
|
const cmd = command.cmd
|
||||||
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
const returnArr: ReturnType<typeof getArtifactsToUpdate> = []
|
||||||
if (!response) return returnArr
|
if (!response) return returnArr
|
||||||
if (cmd.type === 'enable_sketch_mode') {
|
if (cmd.type === 'make_plane' && range[1] !== 0) {
|
||||||
|
// If we're calling `make_plane` and the code range doesn't end at `0`
|
||||||
|
// it's not a default plane, but a custom one from the offsetPlane standard library function
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
artifact: {
|
||||||
|
type: 'plane',
|
||||||
|
pathIds: [],
|
||||||
|
codeRef: { range, pathToNode },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
} else if (cmd.type === 'enable_sketch_mode') {
|
||||||
const plane = getArtifact(currentPlaneId)
|
const plane = getArtifact(currentPlaneId)
|
||||||
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
const pathIds = plane?.type === 'plane' ? plane?.pathIds : []
|
||||||
const codeRef =
|
const codeRef =
|
||||||
@ -633,7 +646,7 @@ export function expandSweep(
|
|||||||
if (err(path)) return path
|
if (err(path)) return path
|
||||||
return {
|
return {
|
||||||
type: 'sweep',
|
type: 'sweep',
|
||||||
subType: 'extrusion',
|
subType: sweep.subType,
|
||||||
surfaces: Array.from(surfs.values()),
|
surfaces: Array.from(surfs.values()),
|
||||||
edges: Array.from(edges.values()),
|
edges: Array.from(edges.values()),
|
||||||
path,
|
path,
|
||||||
|
@ -1631,7 +1631,11 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
|
|
||||||
switch (this.exportInfo.intent) {
|
switch (this.exportInfo.intent) {
|
||||||
case ExportIntent.Save: {
|
case ExportIntent.Save: {
|
||||||
exportSave(event.data, this.pendingExport.toastId).then(() => {
|
exportSave({
|
||||||
|
data: event.data,
|
||||||
|
fileName: this.exportInfo.name,
|
||||||
|
toastId: this.pendingExport.toastId,
|
||||||
|
}).then(() => {
|
||||||
this.pendingExport?.resolve(null)
|
this.pendingExport?.resolve(null)
|
||||||
}, this.pendingExport?.reject)
|
}, this.pendingExport?.reject)
|
||||||
break
|
break
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
import { Selections } from 'lib/selections'
|
import { Selections } from 'lib/selections'
|
||||||
import { Program, PathToNode } from './wasm'
|
import {
|
||||||
|
Program,
|
||||||
|
PathToNode,
|
||||||
|
CallExpression,
|
||||||
|
Literal,
|
||||||
|
ArrayExpression,
|
||||||
|
BinaryExpression,
|
||||||
|
} from './wasm'
|
||||||
import { getNodeFromPath } from './queryAst'
|
import { getNodeFromPath } from './queryAst'
|
||||||
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
import { ArtifactGraph, filterArtifacts } from 'lang/std/artifactGraph'
|
||||||
import { isOverlap } from 'lib/utils'
|
import { isOverlap } from 'lib/utils'
|
||||||
@ -84,3 +91,19 @@ export function isCursorInSketchCommandRange(
|
|||||||
([, artifact]) => artifact.type === 'path'
|
([, artifact]) => artifact.type === 'path'
|
||||||
)?.[0] || false
|
)?.[0] || false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCallExpression(e: any): e is CallExpression {
|
||||||
|
return e && e.type === 'CallExpression'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isArrayExpression(e: any): e is ArrayExpression {
|
||||||
|
return e && e.type === 'ArrayExpression'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isLiteral(e: any): e is Literal {
|
||||||
|
return e && e.type === 'Literal'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isBinaryExpression(e: any): e is BinaryExpression {
|
||||||
|
return e && e.type === 'BinaryExpression'
|
||||||
|
}
|
||||||
|
@ -32,7 +32,7 @@ import { CoreDumpManager } from 'lib/coredump'
|
|||||||
import openWindow from 'lib/openWindow'
|
import openWindow from 'lib/openWindow'
|
||||||
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
import { DefaultPlanes } from 'wasm-lib/kcl/bindings/DefaultPlanes'
|
||||||
import { TEST } from 'env'
|
import { TEST } from 'env'
|
||||||
import { err } from 'lib/trap'
|
import { err, Reason } from 'lib/trap'
|
||||||
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
import { Configuration } from 'wasm-lib/kcl/bindings/Configuration'
|
||||||
import { DeepPartial } from 'lib/types'
|
import { DeepPartial } from 'lib/types'
|
||||||
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
import { ProjectConfiguration } from 'wasm-lib/kcl/bindings/ProjectConfiguration'
|
||||||
@ -62,6 +62,7 @@ export type { CallExpression } from '../wasm-lib/kcl/bindings/CallExpression'
|
|||||||
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
|
export type { VariableDeclarator } from '../wasm-lib/kcl/bindings/VariableDeclarator'
|
||||||
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
export type { BinaryPart } from '../wasm-lib/kcl/bindings/BinaryPart'
|
||||||
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
export type { Literal } from '../wasm-lib/kcl/bindings/Literal'
|
||||||
|
export type { LiteralValue } from '../wasm-lib/kcl/bindings/LiteralValue'
|
||||||
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
export type { ArrayExpression } from '../wasm-lib/kcl/bindings/ArrayExpression'
|
||||||
|
|
||||||
export type SyntaxType =
|
export type SyntaxType =
|
||||||
@ -81,6 +82,7 @@ export type SyntaxType =
|
|||||||
| 'PipeExpression'
|
| 'PipeExpression'
|
||||||
| 'PipeSubstitution'
|
| 'PipeSubstitution'
|
||||||
| 'Literal'
|
| 'Literal'
|
||||||
|
| 'LiteralValue'
|
||||||
| 'NonCodeNode'
|
| 'NonCodeNode'
|
||||||
| 'UnaryExpression'
|
| 'UnaryExpression'
|
||||||
|
|
||||||
@ -142,6 +144,12 @@ export const parse = (code: string | Error): Node<Program> | Error => {
|
|||||||
|
|
||||||
export type PathToNode = [string | number, string][]
|
export type PathToNode = [string | number, string][]
|
||||||
|
|
||||||
|
export const isPathToNodeNumber = (
|
||||||
|
pathToNode: string | number
|
||||||
|
): pathToNode is number => {
|
||||||
|
return typeof pathToNode === 'number'
|
||||||
|
}
|
||||||
|
|
||||||
export interface ExecState {
|
export interface ExecState {
|
||||||
memory: ProgramMemory
|
memory: ProgramMemory
|
||||||
idGenerator: IdGenerator
|
idGenerator: IdGenerator
|
||||||
@ -336,7 +344,7 @@ export class ProgramMemory {
|
|||||||
*/
|
*/
|
||||||
hasSketchOrSolid(): boolean {
|
hasSketchOrSolid(): boolean {
|
||||||
for (const node of this.visibleEntries().values()) {
|
for (const node of this.visibleEntries().values()) {
|
||||||
if (node.type === 'Solid' || node.value?.type === 'Sketch') {
|
if (node.type === 'Solid' || node.type === 'Sketch') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -357,10 +365,10 @@ export class ProgramMemory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: In the future, make the parameter be a KclValue.
|
// TODO: In the future, make the parameter be a KclValue.
|
||||||
export function sketchFromKclValue(
|
export function sketchFromKclValueOptional(
|
||||||
obj: any,
|
obj: any,
|
||||||
varName: string | null
|
varName: string | null
|
||||||
): Sketch | Error {
|
): Sketch | Reason {
|
||||||
if (obj?.value?.type === 'Sketch') return obj.value
|
if (obj?.value?.type === 'Sketch') return obj.value
|
||||||
if (obj?.value?.type === 'Solid') return obj.value.sketch
|
if (obj?.value?.type === 'Solid') return obj.value.sketch
|
||||||
if (obj?.type === 'Solid') return obj.sketch
|
if (obj?.type === 'Solid') return obj.sketch
|
||||||
@ -369,15 +377,26 @@ export function sketchFromKclValue(
|
|||||||
}
|
}
|
||||||
const actualType = obj?.value?.type ?? obj?.type
|
const actualType = obj?.value?.type ?? obj?.type
|
||||||
if (actualType) {
|
if (actualType) {
|
||||||
console.log(obj)
|
return new Reason(
|
||||||
return new Error(
|
|
||||||
`Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.`
|
`Expected ${varName} to be a sketch or solid, but it was ${actualType} instead.`
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return new Error(`Expected ${varName} to be a sketch, but it wasn't.`)
|
return new Reason(`Expected ${varName} to be a sketch, but it wasn't.`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: In the future, make the parameter be a KclValue.
|
||||||
|
export function sketchFromKclValue(
|
||||||
|
obj: any,
|
||||||
|
varName: string | null
|
||||||
|
): Sketch | Error {
|
||||||
|
const result = sketchFromKclValueOptional(obj, varName)
|
||||||
|
if (result instanceof Reason) {
|
||||||
|
return result.toError()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
export const executor = async (
|
export const executor = async (
|
||||||
node: Node<Program>,
|
node: Node<Program>,
|
||||||
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
programMemory: ProgramMemory | Error = ProgramMemory.empty(),
|
||||||
|
@ -68,7 +68,16 @@ const save_ = async (file: ModelingAppFile, toastId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Saves files locally from an export call.
|
// Saves files locally from an export call.
|
||||||
export async function exportSave(data: ArrayBuffer, toastId: string) {
|
// We override the file's name with one passed in from the client side.
|
||||||
|
export async function exportSave({
|
||||||
|
data,
|
||||||
|
fileName,
|
||||||
|
toastId,
|
||||||
|
}: {
|
||||||
|
data: ArrayBuffer
|
||||||
|
fileName: string
|
||||||
|
toastId: string
|
||||||
|
}) {
|
||||||
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
|
// This converts the ArrayBuffer to a Rust equivalent Vec<u8>.
|
||||||
let uintArray = new Uint8Array(data)
|
let uintArray = new Uint8Array(data)
|
||||||
|
|
||||||
@ -80,9 +89,10 @@ export async function exportSave(data: ArrayBuffer, toastId: string) {
|
|||||||
zip.file(file.name, new Uint8Array(file.contents), { binary: true })
|
zip.file(file.name, new Uint8Array(file.contents), { binary: true })
|
||||||
}
|
}
|
||||||
return zip.generateAsync({ type: 'array' }).then((contents) => {
|
return zip.generateAsync({ type: 'array' }).then((contents) => {
|
||||||
return save_({ name: 'output.zip', contents }, toastId)
|
return save_({ name: `${fileName || 'output'}.zip`, contents }, toastId)
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
files[0].name = fileName || files[0].name
|
||||||
return save_(files[0], toastId)
|
return save_(files[0], toastId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,16 @@ import {
|
|||||||
createUnaryExpression,
|
createUnaryExpression,
|
||||||
} from 'lang/modifyAst'
|
} from 'lang/modifyAst'
|
||||||
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
|
import { ArrayExpression, CallExpression, PipeExpression } from 'lang/wasm'
|
||||||
|
import { roundOff } from 'lib/utils'
|
||||||
|
import {
|
||||||
|
isCallExpression,
|
||||||
|
isArrayExpression,
|
||||||
|
isLiteral,
|
||||||
|
isBinaryExpression,
|
||||||
|
} from 'lang/util'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* It does not create the startSketchOn and it does not create the startProfileAt.
|
||||||
* Returns AST expressions for this KCL code:
|
* Returns AST expressions for this KCL code:
|
||||||
* const yo = startSketchOn('XY')
|
* const yo = startSketchOn('XY')
|
||||||
* |> startProfileAt([0, 0], %)
|
* |> startProfileAt([0, 0], %)
|
||||||
@ -92,3 +100,69 @@ export function updateRectangleSketch(
|
|||||||
createLiteral(Math.abs(y)), // This will be the height of the rectangle
|
createLiteral(Math.abs(y)), // This will be the height of the rectangle
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mutates the pipeExpression to update the center rectangle sketch
|
||||||
|
* @param pipeExpression
|
||||||
|
* @param x
|
||||||
|
* @param y
|
||||||
|
* @param tag
|
||||||
|
*/
|
||||||
|
export function updateCenterRectangleSketch(
|
||||||
|
pipeExpression: PipeExpression,
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
tag: string,
|
||||||
|
originX: number,
|
||||||
|
originY: number
|
||||||
|
) {
|
||||||
|
let startX = originX - Math.abs(deltaX)
|
||||||
|
let startY = originY - Math.abs(deltaY)
|
||||||
|
|
||||||
|
// pipeExpression.body[1] is startProfileAt
|
||||||
|
let callExpression = pipeExpression.body[1]
|
||||||
|
if (isCallExpression(callExpression)) {
|
||||||
|
const arrayExpression = callExpression.arguments[0]
|
||||||
|
if (isArrayExpression(arrayExpression)) {
|
||||||
|
callExpression.arguments[0] = createArrayExpression([
|
||||||
|
createLiteral(roundOff(startX)),
|
||||||
|
createLiteral(roundOff(startY)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const twoX = deltaX * 2
|
||||||
|
const twoY = deltaY * 2
|
||||||
|
|
||||||
|
callExpression = pipeExpression.body[2]
|
||||||
|
if (isCallExpression(callExpression)) {
|
||||||
|
const arrayExpression = callExpression.arguments[0]
|
||||||
|
if (isArrayExpression(arrayExpression)) {
|
||||||
|
const literal = arrayExpression.elements[0]
|
||||||
|
if (isLiteral(literal)) {
|
||||||
|
callExpression.arguments[0] = createArrayExpression([
|
||||||
|
createLiteral(literal.value),
|
||||||
|
createLiteral(Math.abs(twoX)),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
callExpression = pipeExpression.body[3]
|
||||||
|
if (isCallExpression(callExpression)) {
|
||||||
|
const arrayExpression = callExpression.arguments[0]
|
||||||
|
if (isArrayExpression(arrayExpression)) {
|
||||||
|
const binaryExpression = arrayExpression.elements[0]
|
||||||
|
if (isBinaryExpression(binaryExpression)) {
|
||||||
|
callExpression.arguments[0] = createArrayExpression([
|
||||||
|
createBinaryExpression([
|
||||||
|
createCallExpressionStdLib('segAng', [createIdentifier(tag)]),
|
||||||
|
binaryExpression.operator,
|
||||||
|
createLiteral(90),
|
||||||
|
]), // 90 offset from the previous line
|
||||||
|
createLiteral(Math.abs(twoY)), // This will be the height of the rectangle
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -124,7 +124,9 @@ export const fileLoader: LoaderFunction = async (
|
|||||||
// We explicitly do not write to the file here since we are loading from
|
// We explicitly do not write to the file here since we are loading from
|
||||||
// the file system and not the editor.
|
// the file system and not the editor.
|
||||||
codeManager.updateCurrentFilePath(currentFilePath)
|
codeManager.updateCurrentFilePath(currentFilePath)
|
||||||
codeManager.updateCodeStateEditor(code)
|
// We pass true on the end here to clear the code editor history.
|
||||||
|
// This way undo and redo are not super weird when opening new files.
|
||||||
|
codeManager.updateCodeStateEditor(code, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the file system manager to the project path
|
// Set the file system manager to the project path
|
||||||
|
@ -407,8 +407,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
status: 'available',
|
status: 'available',
|
||||||
title: 'Center circle',
|
title: 'Center circle',
|
||||||
disabled: (state) =>
|
disabled: (state) =>
|
||||||
!canRectangleOrCircleTool(state.context) &&
|
state.matches('Sketch no face') ||
|
||||||
!state.matches({ Sketch: 'Circle tool' }),
|
(!canRectangleOrCircleTool(state.context) &&
|
||||||
|
!state.matches({ Sketch: 'Circle tool' })),
|
||||||
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
|
isActive: (state) => state.matches({ Sketch: 'Circle tool' }),
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
state.matches({ Sketch: 'Circle tool' }) ? ['Esc', 'C'] : 'C',
|
||||||
@ -448,8 +449,9 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
icon: 'rectangle',
|
icon: 'rectangle',
|
||||||
status: 'available',
|
status: 'available',
|
||||||
disabled: (state) =>
|
disabled: (state) =>
|
||||||
!canRectangleOrCircleTool(state.context) &&
|
state.matches('Sketch no face') ||
|
||||||
!state.matches({ Sketch: 'Rectangle tool' }),
|
(!canRectangleOrCircleTool(state.context) &&
|
||||||
|
!state.matches({ Sketch: 'Rectangle tool' })),
|
||||||
title: 'Corner rectangle',
|
title: 'Corner rectangle',
|
||||||
hotkey: (state) =>
|
hotkey: (state) =>
|
||||||
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
state.matches({ Sketch: 'Rectangle tool' }) ? ['Esc', 'R'] : 'R',
|
||||||
@ -459,13 +461,33 @@ export const toolbarConfig: Record<ToolbarModeName, ToolbarMode> = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'center-rectangle',
|
id: 'center-rectangle',
|
||||||
onClick: () => console.error('Center rectangle not yet implemented'),
|
onClick: ({ modelingState, modelingSend }) =>
|
||||||
icon: 'rectangle',
|
modelingSend({
|
||||||
status: 'unavailable',
|
type: 'change tool',
|
||||||
|
data: {
|
||||||
|
tool: !modelingState.matches({
|
||||||
|
Sketch: 'Center Rectangle tool',
|
||||||
|
})
|
||||||
|
? 'center rectangle'
|
||||||
|
: 'none',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
icon: 'arc',
|
||||||
|
status: 'available',
|
||||||
|
disabled: (state) =>
|
||||||
|
state.matches('Sketch no face') ||
|
||||||
|
(!canRectangleOrCircleTool(state.context) &&
|
||||||
|
!state.matches({ Sketch: 'Center Rectangle tool' })),
|
||||||
title: 'Center rectangle',
|
title: 'Center rectangle',
|
||||||
showTitle: false,
|
hotkey: (state) =>
|
||||||
|
state.matches({ Sketch: 'Center Rectangle tool' })
|
||||||
|
? ['Esc', 'C']
|
||||||
|
: 'C',
|
||||||
description: 'Start drawing a rectangle from its center',
|
description: 'Start drawing a rectangle from its center',
|
||||||
links: [],
|
links: [],
|
||||||
|
isActive: (state) => {
|
||||||
|
return state.matches({ Sketch: 'Center Rectangle tool' })
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
|
@ -2,6 +2,23 @@ import toast from 'react-hot-toast'
|
|||||||
|
|
||||||
type ExcludeErr<T> = Exclude<T, Error>
|
type ExcludeErr<T> = Exclude<T, Error>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Similar to Error, but more lightweight, without the stack trace. It can also
|
||||||
|
* be used to represent a reason for not being able to provide an alternative,
|
||||||
|
* which isn't necessarily an error.
|
||||||
|
*/
|
||||||
|
export class Reason {
|
||||||
|
message: string
|
||||||
|
|
||||||
|
constructor(message: string) {
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
toError() {
|
||||||
|
return new Error(this.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is intentionally *not* exported due to misuse. We'd like to add a lint.
|
* This is intentionally *not* exported due to misuse. We'd like to add a lint.
|
||||||
*/
|
*/
|
||||||
|
@ -91,7 +91,7 @@ export function useCalculateKclExpression({
|
|||||||
const _programMem: ProgramMemory = ProgramMemory.empty()
|
const _programMem: ProgramMemory = ProgramMemory.empty()
|
||||||
for (const { key, value } of availableVarInfo.variables) {
|
for (const { key, value } of availableVarInfo.variables) {
|
||||||
const error = _programMem.set(key, {
|
const error = _programMem.set(key, {
|
||||||
type: 'UserVal',
|
type: 'String',
|
||||||
value,
|
value,
|
||||||
__meta: [],
|
__meta: [],
|
||||||
})
|
})
|
||||||
@ -115,6 +115,7 @@ export function useCalculateKclExpression({
|
|||||||
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
|
||||||
init && setValueNode(init)
|
init && setValueNode(init)
|
||||||
}
|
}
|
||||||
|
if (!value) return
|
||||||
execAstAndSetResult().catch(() => {
|
execAstAndSetResult().catch(() => {
|
||||||
setCalcResult('NAN')
|
setCalcResult('NAN')
|
||||||
setValueNode(null)
|
setValueNode(null)
|
||||||
|
@ -18,6 +18,7 @@ import {
|
|||||||
sceneEntitiesManager,
|
sceneEntitiesManager,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
editorManager,
|
editorManager,
|
||||||
|
codeManager,
|
||||||
} from 'lib/singletons'
|
} from 'lib/singletons'
|
||||||
import {
|
import {
|
||||||
horzVertInfo,
|
horzVertInfo,
|
||||||
@ -158,6 +159,15 @@ export type DefaultPlane = {
|
|||||||
yAxis: [number, number, number]
|
yAxis: [number, number, number]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OffsetPlane = {
|
||||||
|
type: 'offsetPlane'
|
||||||
|
position: [number, number, number]
|
||||||
|
planeId: string
|
||||||
|
pathToNode: PathToNode
|
||||||
|
zAxis: [number, number, number]
|
||||||
|
yAxis: [number, number, number]
|
||||||
|
}
|
||||||
|
|
||||||
export type SegmentOverlayPayload =
|
export type SegmentOverlayPayload =
|
||||||
| {
|
| {
|
||||||
type: 'set-one'
|
type: 'set-one'
|
||||||
@ -183,6 +193,7 @@ export type SketchTool =
|
|||||||
| 'line'
|
| 'line'
|
||||||
| 'tangentialArc'
|
| 'tangentialArc'
|
||||||
| 'rectangle'
|
| 'rectangle'
|
||||||
|
| 'center rectangle'
|
||||||
| 'circle'
|
| 'circle'
|
||||||
| 'none'
|
| 'none'
|
||||||
|
|
||||||
@ -196,7 +207,7 @@ export type ModelingMachineEvent =
|
|||||||
| { type: 'Sketch On Face' }
|
| { type: 'Sketch On Face' }
|
||||||
| {
|
| {
|
||||||
type: 'Select default plane'
|
type: 'Select default plane'
|
||||||
data: DefaultPlane | ExtrudeFacePlane
|
data: DefaultPlane | ExtrudeFacePlane | OffsetPlane
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'Set selection'
|
type: 'Set selection'
|
||||||
@ -237,6 +248,10 @@ export type ModelingMachineEvent =
|
|||||||
type: 'Add rectangle origin'
|
type: 'Add rectangle origin'
|
||||||
data: [x: number, y: number]
|
data: [x: number, y: number]
|
||||||
}
|
}
|
||||||
|
| {
|
||||||
|
type: 'Add center rectangle origin'
|
||||||
|
data: [x: number, y: number]
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
type: 'Add circle origin'
|
type: 'Add circle origin'
|
||||||
data: [x: number, y: number]
|
data: [x: number, y: number]
|
||||||
@ -277,6 +292,7 @@ export type ModelingMachineEvent =
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
| { type: 'Finish rectangle' }
|
| { type: 'Finish rectangle' }
|
||||||
|
| { type: 'Finish center rectangle' }
|
||||||
| { type: 'Finish circle' }
|
| { type: 'Finish circle' }
|
||||||
| { type: 'Artifact graph populated' }
|
| { type: 'Artifact graph populated' }
|
||||||
| { type: 'Artifact graph emptied' }
|
| { type: 'Artifact graph emptied' }
|
||||||
@ -505,6 +521,9 @@ export const modelingMachine = setup({
|
|||||||
'next is rectangle': ({ context: { sketchDetails, currentTool } }) =>
|
'next is rectangle': ({ context: { sketchDetails, currentTool } }) =>
|
||||||
currentTool === 'rectangle' &&
|
currentTool === 'rectangle' &&
|
||||||
canRectangleOrCircleTool({ sketchDetails }),
|
canRectangleOrCircleTool({ sketchDetails }),
|
||||||
|
'next is center rectangle': ({ context: { sketchDetails, currentTool } }) =>
|
||||||
|
currentTool === 'center rectangle' &&
|
||||||
|
canRectangleOrCircleTool({ sketchDetails }),
|
||||||
'next is circle': ({ context: { sketchDetails, currentTool } }) =>
|
'next is circle': ({ context: { sketchDetails, currentTool } }) =>
|
||||||
currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }),
|
currentTool === 'circle' && canRectangleOrCircleTool({ sketchDetails }),
|
||||||
'next is line': ({ context }) => context.currentTool === 'line',
|
'next is line': ({ context }) => context.currentTool === 'line',
|
||||||
@ -531,8 +550,10 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
'hide default planes': () => {
|
||||||
'hide default planes': () => kclManager.hidePlanes(),
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
kclManager.hidePlanes()
|
||||||
|
},
|
||||||
'reset sketch metadata': assign({
|
'reset sketch metadata': assign({
|
||||||
sketchDetails: null,
|
sketchDetails: null,
|
||||||
sketchEnginePathId: '',
|
sketchEnginePathId: '',
|
||||||
@ -595,7 +616,6 @@ export const modelingMachine = setup({
|
|||||||
if (trap(extrudeSketchRes)) return
|
if (trap(extrudeSketchRes)) return
|
||||||
const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
|
const { modifiedAst, pathToExtrudeArg } = extrudeSketchRes
|
||||||
|
|
||||||
store.videoElement?.pause()
|
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
focusPath: [pathToExtrudeArg],
|
focusPath: [pathToExtrudeArg],
|
||||||
zoomToFit: true,
|
zoomToFit: true,
|
||||||
@ -604,11 +624,9 @@ export const modelingMachine = setup({
|
|||||||
type: 'path',
|
type: 'path',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!engineCommandManager.engineConnection?.idleMode) {
|
|
||||||
store.videoElement?.play().catch((e) => {
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
console.warn('Video playing was prevented', e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (updatedAst?.selections) {
|
if (updatedAst?.selections) {
|
||||||
editorManager.selectRange(updatedAst?.selections)
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
}
|
}
|
||||||
@ -642,7 +660,6 @@ export const modelingMachine = setup({
|
|||||||
if (trap(revolveSketchRes)) return
|
if (trap(revolveSketchRes)) return
|
||||||
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
|
const { modifiedAst, pathToRevolveArg } = revolveSketchRes
|
||||||
|
|
||||||
store.videoElement?.pause()
|
|
||||||
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
const updatedAst = await kclManager.updateAst(modifiedAst, true, {
|
||||||
focusPath: [pathToRevolveArg],
|
focusPath: [pathToRevolveArg],
|
||||||
zoomToFit: true,
|
zoomToFit: true,
|
||||||
@ -651,11 +668,9 @@ export const modelingMachine = setup({
|
|||||||
type: 'path',
|
type: 'path',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!engineCommandManager.engineConnection?.idleMode) {
|
|
||||||
store.videoElement?.play().catch((e) => {
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
console.warn('Video playing was prevented', e)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (updatedAst?.selections) {
|
if (updatedAst?.selections) {
|
||||||
editorManager.selectRange(updatedAst?.selections)
|
editorManager.selectRange(updatedAst?.selections)
|
||||||
}
|
}
|
||||||
@ -685,6 +700,7 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
|
|
||||||
await kclManager.updateAst(modifiedAst, true)
|
await kclManager.updateAst(modifiedAst, true)
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(modifiedAst)
|
||||||
})().catch(reportRejection)
|
})().catch(reportRejection)
|
||||||
},
|
},
|
||||||
'AST fillet': ({ event }) => {
|
'AST fillet': ({ event }) => {
|
||||||
@ -702,6 +718,9 @@ export const modelingMachine = setup({
|
|||||||
radius
|
radius
|
||||||
)
|
)
|
||||||
if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult
|
if (err(applyFilletToSelectionResult)) return applyFilletToSelectionResult
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
},
|
},
|
||||||
'set selection filter to curves only': () => {
|
'set selection filter to curves only': () => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@ -758,25 +777,35 @@ export const modelingMachine = setup({
|
|||||||
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
'remove sketch grid': () => sceneEntitiesManager.removeSketchGrid(),
|
||||||
'set up draft line': ({ context: { sketchDetails } }) => {
|
'set up draft line': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.setUpDraftSegment(
|
sceneEntitiesManager
|
||||||
|
.setupDraftSegment(
|
||||||
sketchDetails.sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
sketchDetails.origin,
|
sketchDetails.origin,
|
||||||
'line'
|
'line'
|
||||||
)
|
)
|
||||||
|
.then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
'set up draft arc': ({ context: { sketchDetails } }) => {
|
'set up draft arc': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.setUpDraftSegment(
|
sceneEntitiesManager
|
||||||
|
.setupDraftSegment(
|
||||||
sketchDetails.sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
sketchDetails.origin,
|
sketchDetails.origin,
|
||||||
'tangentialArcTo'
|
'tangentialArcTo'
|
||||||
)
|
)
|
||||||
|
.then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
'listen for rectangle origin': ({ context: { sketchDetails } }) => {
|
'listen for rectangle origin': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
@ -795,6 +824,26 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'listen for center rectangle origin': ({ context: { sketchDetails } }) => {
|
||||||
|
if (!sketchDetails) return
|
||||||
|
// setupNoPointsListener has the code for startProfileAt onClick
|
||||||
|
sceneEntitiesManager.setupNoPointsListener({
|
||||||
|
sketchDetails,
|
||||||
|
afterClick: (args) => {
|
||||||
|
const twoD = args.intersectionPoint?.twoD
|
||||||
|
if (twoD) {
|
||||||
|
sceneInfra.modelingSend({
|
||||||
|
type: 'Add center rectangle origin',
|
||||||
|
data: [twoD.x, twoD.y],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.error('No intersection point found')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
'listen for circle origin': ({ context: { sketchDetails } }) => {
|
'listen for circle origin': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
sceneEntitiesManager.createIntersectionPlane()
|
sceneEntitiesManager.createIntersectionPlane()
|
||||||
@ -834,8 +883,28 @@ export const modelingMachine = setup({
|
|||||||
'set up draft rectangle': ({ context: { sketchDetails }, event }) => {
|
'set up draft rectangle': ({ context: { sketchDetails }, event }) => {
|
||||||
if (event.type !== 'Add rectangle origin') return
|
if (event.type !== 'Add rectangle origin') return
|
||||||
if (!sketchDetails || !event.data) return
|
if (!sketchDetails || !event.data) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.setupDraftRectangle(
|
sceneEntitiesManager
|
||||||
|
.setupDraftRectangle(
|
||||||
|
sketchDetails.sketchPathToNode,
|
||||||
|
sketchDetails.zAxis,
|
||||||
|
sketchDetails.yAxis,
|
||||||
|
sketchDetails.origin,
|
||||||
|
event.data
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
'set up draft center rectangle': ({
|
||||||
|
context: { sketchDetails },
|
||||||
|
event,
|
||||||
|
}) => {
|
||||||
|
if (event.type !== 'Add center rectangle origin') return
|
||||||
|
if (!sketchDetails || !event.data) return
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
sceneEntitiesManager.setupDraftCenterRectangle(
|
||||||
sketchDetails.sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -846,19 +915,26 @@ export const modelingMachine = setup({
|
|||||||
'set up draft circle': ({ context: { sketchDetails }, event }) => {
|
'set up draft circle': ({ context: { sketchDetails }, event }) => {
|
||||||
if (event.type !== 'Add circle origin') return
|
if (event.type !== 'Add circle origin') return
|
||||||
if (!sketchDetails || !event.data) return
|
if (!sketchDetails || !event.data) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.setupDraftCircle(
|
sceneEntitiesManager
|
||||||
|
.setupDraftCircle(
|
||||||
sketchDetails.sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
sketchDetails.origin,
|
sketchDetails.origin,
|
||||||
event.data
|
event.data
|
||||||
)
|
)
|
||||||
|
.then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
'set up draft line without teardown': ({ context: { sketchDetails } }) => {
|
'set up draft line without teardown': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.setUpDraftSegment(
|
sceneEntitiesManager
|
||||||
|
.setupDraftSegment(
|
||||||
sketchDetails.sketchPathToNode,
|
sketchDetails.sketchPathToNode,
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
@ -866,6 +942,9 @@ export const modelingMachine = setup({
|
|||||||
'line',
|
'line',
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
|
.then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
'show default planes': () => {
|
'show default planes': () => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
@ -882,12 +961,17 @@ export const modelingMachine = setup({
|
|||||||
'add axis n grid': ({ context: { sketchDetails } }) => {
|
'add axis n grid': ({ context: { sketchDetails } }) => {
|
||||||
if (!sketchDetails) return
|
if (!sketchDetails) return
|
||||||
if (localStorage.getItem('disableAxis')) return
|
if (localStorage.getItem('disableAxis')) return
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
sceneEntitiesManager.createSketchAxis(
|
sceneEntitiesManager.createSketchAxis(
|
||||||
sketchDetails.sketchPathToNode || [],
|
sketchDetails.sketchPathToNode || [],
|
||||||
sketchDetails.zAxis,
|
sketchDetails.zAxis,
|
||||||
sketchDetails.yAxis,
|
sketchDetails.yAxis,
|
||||||
sketchDetails.origin
|
sketchDetails.origin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
|
codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
},
|
},
|
||||||
'reset client scene mouse handlers': () => {
|
'reset client scene mouse handlers': () => {
|
||||||
// when not in sketch mode we don't need any mouse listeners
|
// when not in sketch mode we don't need any mouse listeners
|
||||||
@ -916,10 +1000,13 @@ export const modelingMachine = setup({
|
|||||||
'Delete segment': ({ context: { sketchDetails }, event }) => {
|
'Delete segment': ({ context: { sketchDetails }, event }) => {
|
||||||
if (event.type !== 'Delete segment') return
|
if (event.type !== 'Delete segment') return
|
||||||
if (!sketchDetails || !event.data) return
|
if (!sketchDetails || !event.data) return
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
||||||
deleteSegment({
|
deleteSegment({
|
||||||
pathToNode: event.data,
|
pathToNode: event.data,
|
||||||
sketchDetails,
|
sketchDetails,
|
||||||
|
}).then(() => {
|
||||||
|
return codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
'Reset Segment Overlays': () => sceneEntitiesManager.resetOverlays(),
|
'Reset Segment Overlays': () => sceneEntitiesManager.resetOverlays(),
|
||||||
@ -984,6 +1071,9 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: updateSelections(
|
selection: updateSelections(
|
||||||
@ -1018,6 +1108,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: updateSelections(
|
selection: updateSelections(
|
||||||
@ -1052,6 +1143,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
return {
|
return {
|
||||||
selectionType: 'completeSelection',
|
selectionType: 'completeSelection',
|
||||||
selection: updateSelections(
|
selection: updateSelections(
|
||||||
@ -1084,6 +1176,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1117,6 +1210,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1150,6 +1244,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1183,6 +1278,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1220,6 +1316,8 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
|
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1252,6 +1350,7 @@ export const modelingMachine = setup({
|
|||||||
)
|
)
|
||||||
if (trap(updatedAst, { suppress: true })) return
|
if (trap(updatedAst, { suppress: true })) return
|
||||||
if (!updatedAst) return
|
if (!updatedAst) return
|
||||||
|
await codeManager.updateEditorWithAstAndWriteToFile(updatedAst.newAst)
|
||||||
const updatedSelectionRanges = updateSelections(
|
const updatedSelectionRanges = updateSelections(
|
||||||
pathToNodeMap,
|
pathToNodeMap,
|
||||||
selectionRanges,
|
selectionRanges,
|
||||||
@ -1304,7 +1403,7 @@ export const modelingMachine = setup({
|
|||||||
}
|
}
|
||||||
),
|
),
|
||||||
'animate-to-face': fromPromise(
|
'animate-to-face': fromPromise(
|
||||||
async (_: { input?: ExtrudeFacePlane | DefaultPlane }) => {
|
async (_: { input?: ExtrudeFacePlane | DefaultPlane | OffsetPlane }) => {
|
||||||
return {} as
|
return {} as
|
||||||
| undefined
|
| undefined
|
||||||
| {
|
| {
|
||||||
@ -1556,7 +1655,7 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
entry: 'setup client side sketch segments',
|
entry: ['setup client side sketch segments'],
|
||||||
},
|
},
|
||||||
|
|
||||||
'Await horizontal distance info': {
|
'Await horizontal distance info': {
|
||||||
@ -1776,6 +1875,40 @@ export const modelingMachine = setup({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
'Center Rectangle tool': {
|
||||||
|
entry: ['listen for center rectangle origin'],
|
||||||
|
|
||||||
|
states: {
|
||||||
|
'Awaiting corner': {
|
||||||
|
on: {
|
||||||
|
'Finish center rectangle': 'Finished Center Rectangle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Awaiting origin': {
|
||||||
|
on: {
|
||||||
|
'Add center rectangle origin': {
|
||||||
|
target: 'Awaiting corner',
|
||||||
|
// TODO
|
||||||
|
actions: 'set up draft center rectangle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
'Finished Center Rectangle': {
|
||||||
|
always: '#Modeling.Sketch.SketchIdle',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
initial: 'Awaiting origin',
|
||||||
|
|
||||||
|
on: {
|
||||||
|
'change tool': {
|
||||||
|
target: 'Change Tool',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
'clean slate': {
|
'clean slate': {
|
||||||
always: 'SketchIdle',
|
always: 'SketchIdle',
|
||||||
},
|
},
|
||||||
@ -1801,7 +1934,7 @@ export const modelingMachine = setup({
|
|||||||
onError: 'SketchIdle',
|
onError: 'SketchIdle',
|
||||||
onDone: {
|
onDone: {
|
||||||
target: 'SketchIdle',
|
target: 'SketchIdle',
|
||||||
actions: ['Set selection'],
|
actions: 'Set selection',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1969,6 +2102,10 @@ export const modelingMachine = setup({
|
|||||||
target: 'Circle tool',
|
target: 'Circle tool',
|
||||||
guard: 'next is circle',
|
guard: 'next is circle',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
target: 'Center Rectangle tool',
|
||||||
|
guard: 'next is center rectangle',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
|
|
||||||
entry: 'assign tool in context',
|
entry: 'assign tool in context',
|
||||||
|
17
src/wasm-lib/Cargo.lock
generated
@ -737,7 +737,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
version = "0.1.29"
|
version = "0.1.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"Inflector",
|
"Inflector",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1589,6 +1589,8 @@ dependencies = [
|
|||||||
"console",
|
"console",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"linked-hash-map",
|
"linked-hash-map",
|
||||||
|
"pest",
|
||||||
|
"pest_derive",
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"similar",
|
"similar",
|
||||||
@ -1689,6 +1691,7 @@ dependencies = [
|
|||||||
"databake",
|
"databake",
|
||||||
"derive-docs",
|
"derive-docs",
|
||||||
"expectorate",
|
"expectorate",
|
||||||
|
"fnv",
|
||||||
"form_urlencoded",
|
"form_urlencoded",
|
||||||
"futures",
|
"futures",
|
||||||
"git_rev",
|
"git_rev",
|
||||||
@ -1734,18 +1737,6 @@ dependencies = [
|
|||||||
"zip",
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "kcl-macros"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"databake",
|
|
||||||
"kcl-lib",
|
|
||||||
"pretty_assertions",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.87",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "kcl-test-server"
|
name = "kcl-test-server"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
|
@ -68,7 +68,6 @@ debug = "line-tables-only"
|
|||||||
members = [
|
members = [
|
||||||
"derive-docs",
|
"derive-docs",
|
||||||
"kcl",
|
"kcl",
|
||||||
"kcl-macros",
|
|
||||||
"kcl-test-server",
|
"kcl-test-server",
|
||||||
"kcl-to-core",
|
"kcl-to-core",
|
||||||
]
|
]
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "derive-docs"
|
name = "derive-docs"
|
||||||
description = "A tool for generating documentation from Rust derive macros"
|
description = "A tool for generating documentation from Rust derive macros"
|
||||||
version = "0.1.29"
|
version = "0.1.30"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
repository = "https://github.com/KittyCAD/modeling-app"
|
||||||
|
@ -173,11 +173,11 @@ fn do_stdlib_inner(
|
|||||||
quote! {
|
quote! {
|
||||||
let code_blocks = vec![#(#cb),*];
|
let code_blocks = vec![#(#cb),*];
|
||||||
code_blocks.iter().map(|cb| {
|
code_blocks.iter().map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
|
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
}).collect::<Vec<String>>()
|
}).collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -748,8 +748,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
|||||||
quote! {
|
quote! {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn #test_name_mock() {
|
async fn #test_name_mock() {
|
||||||
let program = crate::parser::top_level_parse(#code_block).unwrap();
|
let program = crate::Program::parse(#code_block).unwrap();
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
engine: std::sync::Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await.unwrap())),
|
||||||
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
fs: std::sync::Arc::new(crate::fs::FileManager::new()),
|
||||||
@ -758,7 +757,7 @@ fn generate_code_block_test(fn_name: &str, code_block: &str, index: usize) -> pr
|
|||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default()).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
mod test_examples_someFn {
|
mod test_examples_someFn {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_someFn0() {
|
async fn test_mock_example_someFn0() {
|
||||||
let program = crate::parser::top_level_parse("someFn()").unwrap();
|
let program = crate::Program::parse("someFn()").unwrap();
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -15,7 +14,9 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
mod test_examples_someFn {
|
mod test_examples_someFn {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_someFn0() {
|
async fn test_mock_example_someFn0() {
|
||||||
let program = crate::parser::top_level_parse("someFn()").unwrap();
|
let program = crate::Program::parse("someFn()").unwrap();
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -15,7 +14,9 @@ mod test_examples_someFn {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -111,10 +112,10 @@ impl crate::docs::StdLibFn for SomeFn {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ mod test_examples_show {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_show0() {
|
async fn test_mock_example_show0() {
|
||||||
let program =
|
let program =
|
||||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nshow")
|
crate::Program::parse("This is another code block.\nyes sirrr.\nshow").unwrap();
|
||||||
.unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -17,7 +15,9 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -36,9 +36,7 @@ mod test_examples_show {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_show1() {
|
async fn test_mock_example_show1() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -50,7 +48,9 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_show {
|
mod test_examples_show {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_show0() {
|
async fn test_mock_example_show0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ mod test_examples_my_func {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_my_func0() {
|
async fn test_mock_example_my_func0() {
|
||||||
let program =
|
let program =
|
||||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmyFunc")
|
crate::Program::parse("This is another code block.\nyes sirrr.\nmyFunc").unwrap();
|
||||||
.unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -17,7 +15,9 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -36,9 +36,7 @@ mod test_examples_my_func {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_my_func1() {
|
async fn test_mock_example_my_func1() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmyFunc").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -50,7 +48,9 @@ mod test_examples_my_func {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -149,10 +149,10 @@ impl crate::docs::StdLibFn for MyFunc {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,7 @@ mod test_examples_line_to {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_line_to0() {
|
async fn test_mock_example_line_to0() {
|
||||||
let program =
|
let program =
|
||||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nlineTo")
|
crate::Program::parse("This is another code block.\nyes sirrr.\nlineTo").unwrap();
|
||||||
.unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -17,7 +15,9 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -36,9 +36,7 @@ mod test_examples_line_to {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_line_to1() {
|
async fn test_mock_example_line_to1() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nlineTo").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nlineTo").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -50,7 +48,9 @@ mod test_examples_line_to {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -157,10 +157,10 @@ impl crate::docs::StdLibFn for LineTo {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,7 @@ mod test_examples_min {
|
|||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_min0() {
|
async fn test_mock_example_min0() {
|
||||||
let program =
|
let program =
|
||||||
crate::parser::top_level_parse("This is another code block.\nyes sirrr.\nmin").unwrap();
|
crate::Program::parse("This is another code block.\nyes sirrr.\nmin").unwrap();
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +15,9 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -35,9 +36,7 @@ mod test_examples_min {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_min1() {
|
async fn test_mock_example_min1() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nmin").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nmin").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -49,7 +48,9 @@ mod test_examples_min {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -148,10 +149,10 @@ impl crate::docs::StdLibFn for Min {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_show {
|
mod test_examples_show {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_show0() {
|
async fn test_mock_example_show0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_import {
|
mod test_examples_import {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_import0() {
|
async fn test_mock_example_import0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_import {
|
mod test_examples_import {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_import0() {
|
async fn test_mock_example_import0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_import {
|
mod test_examples_import {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_import0() {
|
async fn test_mock_example_import0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nimport").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nimport").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_import {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Import {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,7 @@
|
|||||||
mod test_examples_show {
|
mod test_examples_show {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_show0() {
|
async fn test_mock_example_show0() {
|
||||||
let program =
|
let program = crate::Program::parse("This is code.\nIt does other shit.\nshow").unwrap();
|
||||||
crate::parser::top_level_parse("This is code.\nIt does other shit.\nshow").unwrap();
|
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -16,7 +14,9 @@ mod test_examples_show {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -112,10 +112,10 @@ impl crate::docs::StdLibFn for Show {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
mod test_examples_some_function {
|
mod test_examples_some_function {
|
||||||
#[tokio::test(flavor = "multi_thread")]
|
#[tokio::test(flavor = "multi_thread")]
|
||||||
async fn test_mock_example_some_function0() {
|
async fn test_mock_example_some_function0() {
|
||||||
let program = crate::parser::top_level_parse("someFunction()").unwrap();
|
let program = crate::Program::parse("someFunction()").unwrap();
|
||||||
let id_generator = crate::executor::IdGenerator::default();
|
|
||||||
let ctx = crate::executor::ExecutorContext {
|
let ctx = crate::executor::ExecutorContext {
|
||||||
engine: std::sync::Arc::new(Box::new(
|
engine: std::sync::Arc::new(Box::new(
|
||||||
crate::engine::conn_mock::EngineConnection::new()
|
crate::engine::conn_mock::EngineConnection::new()
|
||||||
@ -15,7 +14,9 @@ mod test_examples_some_function {
|
|||||||
settings: Default::default(),
|
settings: Default::default(),
|
||||||
context_type: crate::executor::ContextType::Mock,
|
context_type: crate::executor::ContextType::Mock,
|
||||||
};
|
};
|
||||||
ctx.run(&program, None, id_generator, None).await.unwrap();
|
ctx.run(&program, &mut crate::ExecState::default())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 5)]
|
||||||
@ -106,10 +107,10 @@ impl crate::docs::StdLibFn for SomeFunction {
|
|||||||
code_blocks
|
code_blocks
|
||||||
.iter()
|
.iter()
|
||||||
.map(|cb| {
|
.map(|cb| {
|
||||||
let program = crate::parser::top_level_parse(cb).unwrap();
|
let program = crate::Program::parse(cb).unwrap();
|
||||||
let mut options: crate::ast::types::FormatOptions = Default::default();
|
let mut options: crate::ast::types::FormatOptions = Default::default();
|
||||||
options.insert_final_newline = false;
|
options.insert_final_newline = false;
|
||||||
program.recast(&options, 0)
|
program.ast.recast(&options, 0)
|
||||||
})
|
})
|
||||||
.collect::<Vec<String>>()
|
.collect::<Vec<String>>()
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,18 @@
|
|||||||
cnr := "cargo nextest run"
|
cnr := "cargo nextest run"
|
||||||
cita := "cargo insta test --accept"
|
cita := "cargo insta test --accept"
|
||||||
|
|
||||||
# Create a new KCL snapshot test from `tests/inputs/my-test.kcl`.
|
# Run the same lint checks we run in CI.
|
||||||
new-test name:
|
|
||||||
echo "kcl_test!(\"{{name}}\", {{name}});" >> tests/executor/visuals.rs
|
|
||||||
TWENTY_TWENTY=overwrite {{cnr}} --test executor -E 'test(=visuals::{{name}})'
|
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
cargo clippy --workspace --all-targets -- -D warnings
|
cargo clippy --workspace --all-targets -- -D warnings
|
||||||
|
|
||||||
|
# Generate the stdlib image artifacts
|
||||||
|
# Then run the stdlib docs generation
|
||||||
redo-kcl-stdlib-docs:
|
redo-kcl-stdlib-docs:
|
||||||
|
TWENTY_TWENTY=overwrite {{cnr}} -p kcl-lib kcl_test_example
|
||||||
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
|
EXPECTORATE=overwrite {{cnr}} -p kcl-lib docs::gen_std_tests::test_generate_stdlib
|
||||||
|
|
||||||
# Create a new KCL deterministic simulation test case.
|
# Create a new KCL deterministic simulation test case.
|
||||||
new-sim-test test_name kcl_program render_to_png="true":
|
new-sim-test test_name render_to_png="true":
|
||||||
# Each test file gets its own directory. This will contain the KCL program, and its
|
|
||||||
# snapshotted artifacts (e.g. serialized tokens, serialized ASTs, program memory,
|
|
||||||
# PNG snapshots, etc).
|
|
||||||
mkdir kcl/tests/{{test_name}}
|
|
||||||
echo "{{kcl_program}}" > kcl/tests/{{test_name}}/input.kcl
|
|
||||||
# Add the various tests for this new test case.
|
|
||||||
cat kcl/tests/simtest.tmpl | sed "s/TEST_NAME_HERE/{{test_name}}/" | sed "s/RENDER_TO_PNG/{{render_to_png}}/" >> kcl/src/simulation_tests.rs
|
|
||||||
# Run all the tests for the first time, in the right order.
|
|
||||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
|
{{cita}} -p kcl-lib -- tests::{{test_name}}::tokenize
|
||||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse
|
{{cita}} -p kcl-lib -- tests::{{test_name}}::parse
|
||||||
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse
|
{{cita}} -p kcl-lib -- tests::{{test_name}}::unparse
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "kcl-macros"
|
|
||||||
description = "Macro for compiling KCL to its AST during Rust compile-time"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
license = "MIT"
|
|
||||||
repository = "https://github.com/KittyCAD/modeling-app"
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
proc-macro = true
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
databake = "0.1.8"
|
|
||||||
kcl-lib = { path = "../kcl" }
|
|
||||||
proc-macro2 = "1"
|
|
||||||
quote = "1"
|
|
||||||
syn = { version = "2.0.87", features = ["full"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pretty_assertions = "1.4.1"
|
|