Compare commits

..

142 Commits

Author SHA1 Message Date
df8977407c make delet of extrusions work for multi profile 2025-02-13 10:06:34 -05:00
42016b7335 get delet working for walls 2025-02-13 10:06:34 -05:00
09f3e6e170 Add support for deleting sketches on caps (not walls) 2025-02-13 10:06:34 -05:00
3747db01b8 Delete paths associated with sketch when the sketch plane is deleted 2025-02-13 10:05:41 -05:00
bf1a42f6c0 remove stale comment 2025-02-13 21:06:36 +11:00
b4d1a36bd9 fix closing profile bug (tangentArcTo being ignored) 2025-02-13 20:55:57 +11:00
b937934834 fix close profiles 2025-02-13 19:38:09 +11:00
0208eecefa fix delete of circle profile 2025-02-13 19:06:38 +11:00
9999e4e62f fix changing tools part way through circle/rect tools 2025-02-13 19:01:28 +11:00
b390e3e083 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-13 17:41:10 +11:00
a572d7b0db Fix to not send engine commands when only importing functions or constants (#5371)
* Change to not send engine commands when in isolated mode

* Update output since not sending engine commands
2025-02-12 22:37:34 -05:00
1f405b9327 Remove annotations from non-code-meta and store them in nodes (#5360)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-13 16:17:09 +13:00
9a89926749 clean up for face codeRef 2025-02-13 12:18:07 +11:00
d7f834f23d Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-13 12:02:32 +11:00
0874891dd0 pass thru the kcl version (#5366)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-02-12 18:55:34 -05:00
500d92d1fc Merge branch 'main' into kurt-multi-profile-again 2025-02-12 18:34:02 -05:00
59d0e079a1 Make ProgramMemory and the internals of ExecState private (#5364)
* Make ProgramMemory and the internals of ExecState private

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

* snapshot test changes

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-13 11:59:57 +13:00
bd10c65bdb Fix edit/select sketch-on-cap via feature tree 2025-02-12 17:51:15 -05:00
b9862baed0 bump kittycad.ts & pull thru project_name (#5359)
Signed-off-by: Jess Frazelle <github@jessfraz.com>
2025-02-12 16:44:47 -06:00
592c5259c6 KCL: Update patternTransform and 2d to use kwargs (#5348)
* KCL: Update patternTransform and 2d to use kwargs

* Update docs

* Convert segment functions to use keyword args

* Regenerate docs, change branch of kcl-samples
2025-02-12 21:47:02 +00:00
039092ec24 Update output after adding cap-to-path graph edge 2025-02-12 13:54:39 -05:00
950f5cebfd Options and docs for foreign imports (#5351)
* Annotations for imports of foreign files

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

* Document foreign imports

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-13 06:24:27 +13:00
8a920b6cb7 Prevent double write to KCL code on revolve 2025-02-12 11:58:20 -05:00
c1db093e0f nit 2025-02-12 19:24:36 +11:00
5f8ae22b04 Update src/clientSideScene/segments.ts
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-02-12 19:23:30 +11:00
dc7b901eb9 remove file tree from diff 2025-02-12 19:21:07 +11:00
60dcf9d79e rename error 2025-02-12 19:08:30 +11:00
520f899ad4 Update src/lib/rectangleTool.ts
Co-authored-by: Frank Noirot <frank@zoo.dev>
2025-02-12 18:58:43 +11:00
41340c8bf0 explain function name 2025-02-12 18:51:05 +11:00
a78ec6cd17 fix snapshot test 2025-02-12 18:46:23 +11:00
de526ae36e make other startSketchOn's work 2025-02-12 18:19:39 +11:00
4a8e582865 fix going into edit sketch 2025-02-12 17:28:06 +11:00
1854064274 add path ids to cap 2025-02-12 17:07:32 +11:00
4a8897be4b Merge branch 'main' into kurt-multi-profile-again 2025-02-11 19:30:27 -05:00
4c0ea136e0 Remove artifact graph mind maps (#5349)
* Remove artifact graph mind maps

* Delete mind map snap files
2025-02-11 19:28:00 -05:00
81c0035adc Remove unneeded AST test in TypeScript (#5343) 2025-02-11 23:55:05 +00:00
83f458fc36 Merge branch 'main' into kurt-multi-profile-again 2025-02-11 17:58:00 -05:00
c68e5d7774 KCL: Linear/circular pattern in stdlib should use kwargs (#5315)
Part of https://github.com/KittyCAD/modeling-app/issues/4600
2025-02-11 16:06:47 -06:00
ed8a0e4aaa Fix formatting to preserve annotations when program is otherwise empty (#5310)
* Fix formatting to preserve annotations when program is otherwise empty

* Change to not allocate extra String
2025-02-11 21:58:48 +00:00
322f398049 ProgramMemory refactor - eliminate copies of memory (#5273)
* refactor program memory for encapsulation and remove special treatment of return values

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

* Refactor ProgramMemory to isolate mutation

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

* Refactor ProgramMemory

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

* Generated output

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

* Cache the result of executing modules for their items

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

* Compress envs when popped

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

* Remove the last traces of geometry from the memory module

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

* docs

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

* Fixups

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

* Frontend

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

* Improve Environment GC

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

* Fixup mock execution frontend and interpreter, misc bug fixes

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

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
2025-02-11 21:22:56 +00:00
bc4d254297 Release the Share Link feature (#5228)
* WIP: Release the Share Link feature
Fixes #5227

* Clean up
2025-02-11 16:06:24 -05:00
10b7a772bf Release kcl-test-server 0.1.21 (#5345) 2025-02-11 14:05:31 -06:00
a686fe914b Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-11 17:33:43 +11:00
cba2349064 fix profile start snap bug 2025-02-11 17:31:49 +11:00
5ae92bcf5c Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-11 15:46:56 +11:00
ad8e306bdb Fix clippy warning 2025-02-10 19:46:33 -05:00
508e1c919c Update output after changing back to skipping none and both 2025-02-10 19:18:44 -05:00
58ec6100c4 Change back to skipping face cap none and both 2025-02-10 19:18:17 -05:00
11eceefedf Fix to use correct source ranges
This also reduces cloning.
2025-02-10 18:39:57 -05:00
11a678df5b Update output from simulation tests 2025-02-10 18:36:38 -05:00
22c0003eb1 Update src/lang/modifyAst.ts
Co-authored-by: Jonathan Tran <jonnytran@gmail.com>
2025-02-11 07:52:35 +11:00
8f0a40ba6e remove onboarding test changes 2025-02-11 07:30:16 +11:00
89b0ccb090 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-11 07:25:52 +11:00
5d22308ad2 remove test skip 2025-02-11 07:21:17 +11:00
5580631c8f remove bad doc comment 2025-02-11 07:21:04 +11:00
e1494c9f16 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-10 11:53:51 +00:00
4a0d852a3c A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-10 11:37:53 +00:00
b213834316 clean up 2025-02-10 22:23:34 +11:00
1d8348c2cf 2 test for three point circle 2025-02-10 21:44:55 +11:00
2227287c9d 1 test for three point circle 2025-02-10 21:29:17 +11:00
680fc30682 add draft point for first click of three point circ 2025-02-10 18:45:14 +11:00
40fb6a44d3 fmt 2025-02-10 18:44:05 +11:00
5713bfd9fa fix up state diagram 2025-02-10 17:46:42 +11:00
77902d550f make three point have cross hair to make it consistent with othe rtools 2025-02-10 17:45:57 +11:00
db895d6801 small bug 2025-02-10 17:45:35 +11:00
3e1f8584ea test works now without me changing anything big-fixed-itself 2025-02-10 16:31:51 +11:00
2501a98cd9 clippy again 2025-02-10 16:11:14 +11:00
e60b0e64ba Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-10 16:01:27 +11:00
3379cc489a is clippy happy now? 2025-02-10 16:00:52 +11:00
a8b7328f65 commint snaps? 2025-02-10 15:59:43 +11:00
ab6995bde3 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-10 15:43:06 +11:00
6df5e70186 update docs 2025-02-10 15:24:38 +11:00
6b1cc36911 more test tweaks 2025-02-10 13:27:20 +11:00
6a16e47491 tsc 2025-02-10 12:37:37 +11:00
f4f0533179 fix electron specific test 2025-02-10 12:36:03 +11:00
6360b8acac fix more tests 2025-02-10 11:08:00 +11:00
064a41d675 fix more tests and add a fix me 2025-02-10 10:00:14 +11:00
e075622a7f fix sweep point-and-click test 2025-02-10 09:52:51 +11:00
7a7929211a Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-10 09:08:59 +11:00
1f5f42963d fix some tests 2025-02-07 17:00:05 +11:00
235e6a1056 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-07 13:50:26 +11:00
09cfbc1837 fix unit test 2025-02-07 13:50:16 +11:00
319029235c fix circ dep 2025-02-07 12:59:19 +11:00
a7f4b0f037 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-07 12:02:42 +11:00
d3afa38bd5 small thing 2025-02-06 23:18:02 +11:00
45416df215 maybe fix a unit test 2025-02-06 22:39:29 +11:00
ee54cdd27a lint and typing 2025-02-06 22:34:21 +11:00
84ae567f37 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-02-06 11:21:42 +00:00
f94671f1bb Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-06 22:09:21 +11:00
bcf3790266 remove duplicate test 2025-02-06 22:09:13 +11:00
d70ebca165 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-06 17:40:24 +11:00
d8a9abba69 overlay fix 2025-02-06 17:16:51 +11:00
0fd18c14ef more rust fixes 2025-02-05 14:29:39 +11:00
36d4830c34 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-05 13:56:55 +11:00
4ce6054e64 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-05 13:56:44 +11:00
ced49f8ddc fix rust 2025-02-05 12:50:58 +11:00
e063622139 more 2025-02-05 12:42:23 +11:00
42178fa649 more 2025-02-05 12:34:19 +11:00
4bb23bc917 bad attempts at fixing rust 2025-02-05 12:27:58 +11:00
72272d5d98 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-05 11:49:09 +11:00
5ef0a1e75f Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-05 11:48:43 +11:00
d8dc49b08a some things needed for multi-profile tests 2025-02-04 15:55:57 +11:00
87eabef450 fix sketch on face after updates to rust side artifact graph 2025-02-04 15:23:35 +11:00
40e4f2236f Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-04 15:18:30 +11:00
663076f790 add face codef ref for walls and caps 2025-02-04 15:17:50 +11:00
f2c76b0509 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-04 06:46:35 +11:00
481bef859a cargo fmt 2025-02-03 22:12:05 +11:00
1a67d344ee Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-03 22:08:13 +11:00
774e3efcb7 fix types 2025-02-03 22:06:24 +11:00
4ec44690bf fmt 2025-02-03 18:37:16 +11:00
d2f0865f95 Merge remote-tracking branch 'origin' into kurt-multi-profile-again 2025-02-03 18:36:38 +11:00
84d17454e9 get overlays working for circle three point 2025-02-03 17:53:08 +11:00
5a5138a703 most of the fix for 3 point circle 2025-01-31 21:26:19 +11:00
33468c4c96 rust changes to make three point confrom to same as others since we're not ready with name params yet 2025-01-31 13:31:04 +11:00
b3467bbe5a wip 2025-01-27 17:36:29 -05:00
90086488b5 WIP 2025-01-24 15:58:54 -05:00
32e8975799 wip 2025-01-17 15:37:28 -05:00
648616c667 Merge branch 'main' into lf94/kurt-bring-back-multi-profile 2025-01-16 11:13:59 -05:00
482487cf57 Merge branch 'main' into lf94/kurt-bring-back-multi-profile 2025-01-15 14:07:47 -05:00
5fe3023be9 Fix partial execution 2025-01-10 15:39:25 -05:00
30397ba7ab Fix up all the tests 2025-01-09 15:59:11 -05:00
3344208c63 Trigger CI 2025-01-08 21:28:36 -05:00
fcf3272ad2 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2025-01-09 02:17:14 +00:00
d3e4b123d0 Merge branch 'main' into kurt-bring-back-multi-profile 2025-01-08 21:08:47 -05:00
2bb548c000 Trigger CI 2024-12-20 11:11:39 -05:00
b09c240e36 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2024-12-20 12:32:43 +00:00
6c9d14af93 partial fixes 2024-12-20 23:24:15 +11:00
0642e49189 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2024-12-20 23:21:32 +11:00
6add1d73ad chore: disabled file watcher which prevents faster file write (#4835) 2024-12-20 23:21:21 +11:00
68c89746c7 trigger CI 2024-12-20 22:14:49 +11:00
9f323c207c A snapshot a day keeps the bugs away! 📷🐛 (OS: windows-16-cores) 2024-12-20 11:06:49 +00:00
7197b6c85d A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-ubuntu-8-cores) 2024-12-20 10:54:43 +00:00
913f2641c3 A snapshot a day keeps the bugs away! 📷🐛 (OS: namespace-profile-macos-8-cores) 2024-12-20 10:53:02 +00:00
e9086c54ba lint 2024-12-20 21:46:48 +11:00
9f93346dc6 lint 2024-12-20 21:45:04 +11:00
1b9f5f20f5 Merge remote-tracking branch 'origin' into kurt-bring-back-multi-profile 2024-12-20 21:43:41 +11:00
3865637c61 Add Rust side artifacts for startSketchOn face or plane (#4834)
* Add Rust side artifacts for startSketchOn face or plane

* move ast digging

---------

Co-authored-by: Kurt Hutten Irev-Dev <k.hutten@protonmail.ch>
2024-12-19 11:03:21 +11:00
2c40e8a97c trigger CI 2024-12-18 10:12:18 +11:00
c696f0837a A snapshot a day keeps the bugs away! 📷🐛 (OS: ubuntu-latest-8-cores) 2024-12-17 22:58:41 +00:00
30edf2ad56 Merge remote-tracking branch 'origin' into kurt-bring-back-multi-profile 2024-12-18 09:53:34 +11:00
e60cabb193 fix poor 1000ms wait UX 2024-12-18 08:44:35 +11:00
1e9cf6f256 Revert "Revert multi-profile (#4812)"
This reverts commit efe8089b08.
2024-12-18 07:08:41 +11:00
465 changed files with 214587 additions and 182815 deletions

File diff suppressed because one or more lines are too long

View File

@ -33,7 +33,14 @@ helix(revolutions: number, angle_start: number, ccw?: bool, radius: number, axis
```js
// Create a helix around the Z axis.
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = 'Z')
helixPath = helix(
angleStart = 0,
ccw = true,
revolutions = 5,
length = 10,
radius = 5,
axis = 'Z',
)
// Create a spring by sweeping around the helix path.
springSketch = startSketchOn('YZ')
@ -49,7 +56,14 @@ helper001 = startSketchOn('XZ')
|> startProfileAt([0, 0], %)
|> line(end = [0, 10], tag = $edge001)
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = edge001)
helixPath = helix(
angleStart = 0,
ccw = true,
revolutions = 5,
length = 10,
radius = 5,
axis = edge001,
)
// Create a spring by sweeping around the helix path.
springSketch = startSketchOn('XY')
@ -61,12 +75,19 @@ springSketch = startSketchOn('XY')
```js
// Create a helix around a custom axis.
helixPath = helix(angleStart = 0, ccw = true, revolutions = 5, length = 10, radius = 5, axis = {
helixPath = helix(
angleStart = 0,
ccw = true,
revolutions = 5,
length = 10,
radius = 5,
axis = {
custom = {
axis = [0, 0, 1.0],
origin = [0, 0.25, 0]
}
})
},
)
// Create a spring by sweeping around the helix path.
springSketch = startSketchOn('XY')

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ lastSegX(sketch: Sketch) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
### Returns

View File

@ -17,7 +17,7 @@ lastSegY(sketch: Sketch) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | The sketch whose line segment is being queried | Yes |
### Returns

File diff suppressed because one or more lines are too long

View File

@ -57,3 +57,55 @@ Imported symbols can be renamed for convenience or to avoid name collisions.
```
import increment as inc, decrement as dec from "util.kcl"
```
## Importing files from other CAD systems
`import` can also be used to import files from other CAD systems. The format of the statement is the
same as for KCL files. You can only import the whole file, not items from it. E.g.,
```
import "tests/inputs/cube.obj"
// Use `cube` just like a KCL object.
```
```
import "tests/inputs/cube-2.sldprt" as cube
// Use `cube` just like a KCL object.
```
You can make the file format explicit using a format attribute (useful if using a different
extension), e.g.,
```
@(format = obj)
import "tests/inputs/cube"
```
For formats lacking unit data (such as STL, OBJ, or PLY files), the default
unit of measurement is millimeters. Alternatively you may specify the unit
by using an attirbute. Likewise, you can also specify a coordinate system. E.g.,
```
@(unitLength = ft, coords = opengl)
import "tests/inputs/cube.obj"
```
When importing a GLTF file, the bin file will be imported as well.
Import paths are relative to the current project directory. Imports currently only work when
using the native Modeling App, not in the browser.
### Supported values
File formats: `fbx`, `gltf`/`glb`, `obj`+, `ply`+, `sldprt`, `step`/`stp`, `stl`+. (Those marked with a
'+' support customising the length unit and coordinate system).
Length units: `mm` (the default), `cm`, `m`, `inch`, `ft`, `yd`.
Coordinate systems:
- `zoo` (the default), forward: -Y, up: +Z, handedness: right
- `opengl`, forward: +Z, up: +Y, handedness: right
- `vulkan`, forward: +Z, up: -Y, handedness: left

View File

@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch some number of times along a partial or
complete circle some specified number of times. Each object may additionally be rotated along the circle, ensuring orentation of the solid with respect to the center of the circle is maintained.
```js
patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch]
patternCircular2d(sketch_set: SketchSet, instances: integer, center: [number], arc_degrees: number, rotate_duplicates: bool, use_original?: bool) -> [Sketch]
```
@ -17,8 +17,12 @@ patternCircular2d(data: CircularPattern2dData, sketch_set: SketchSet) -> [Sketch
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`CircularPattern2dData`](/docs/kcl/types/CircularPattern2dData) | Data for a circular pattern on a 2D sketch. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | Which sketch(es) to pattern | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `center` | `[number]` | The center about which to make the pattern. This is a 2D vector. | Yes |
| `arc_degrees` | `number` | The arc angle (in degrees) to place the repetitions. Must be greater than 0. | Yes |
| `rotate_duplicates` | `bool` | Whether or not to rotate the duplicates as they are copied. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns
@ -34,12 +38,12 @@ exampleSketch = startSketchOn('XZ')
|> line(end = [-1, 0])
|> line(end = [0, -5])
|> close()
|> patternCircular2d({
|> patternCircular2d(
center = [0, 0],
instances = 13,
arcDegrees = 360,
rotateDuplicates = true
}, %)
rotateDuplicates = true,
)
example = extrude(exampleSketch, length = 1)
```

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Repeat a 2-dimensional sketch along some dimension, with a dynamic amount
of distance between each repetition, some specified number of times.
```js
patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?: bool) -> [Sketch]
patternLinear2d(sketch_set: SketchSet, instances: integer, distance: number, axis: [number], use_original?: bool) -> [Sketch]
```
@ -17,9 +17,11 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `data` | [`LinearPattern2dData`](/docs/kcl/types/LinearPattern2dData) | Data for a linear pattern on a 2D sketch. | Yes |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | A sketch or a group of sketches. | Yes |
| `use_original` | `bool` | | No |
| `sketch_set` | [`SketchSet`](/docs/kcl/types/SketchSet) | The sketch(es) to duplicate | Yes |
| `instances` | `integer` | The number of total instances. Must be greater than or equal to 1. This includes the original entity. For example, if instances is 2, there will be two copies -- the original, and one new copy. If instances is 1, this has no effect. | Yes |
| `distance` | `number` | Distance between each repetition. Also known as 'spacing'. | Yes |
| `axis` | `[number]` | The axis of the pattern. A 2D vector. | Yes |
| `use_original` | `bool` | If the target was sketched on an extrusion, setting this will use the original sketch as the target, not the entire joined solid. Defaults to false. | No |
### Returns
@ -31,11 +33,7 @@ patternLinear2d(data: LinearPattern2dData, sketch_set: SketchSet, use_original?:
```js
exampleSketch = startSketchOn('XZ')
|> circle({ center = [0, 0], radius = 1 }, %)
|> patternLinear2d({
axis = [1, 0],
instances = 7,
distance = 4
}, %)
|> patternLinear2d(axis = [1, 0], instances = 7, distance = 4)
example = extrude(exampleSketch, length = 1)
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -17,7 +17,7 @@ segAng(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEnd(tag: TagIdentifier) -> [number]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEndX(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segEndY(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segLen(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStart(tag: TagIdentifier) -> [number]
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStartX(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -17,7 +17,7 @@ segStartY(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

File diff suppressed because it is too large Load Diff

View File

@ -59,7 +59,14 @@ sweepSketch = startSketchOn('XY')
// Create a helix around the Z axis.
helixPath = helix(angleStart = 0, ccw = true, revolutions = 4, length = 10, radius = 5, axis = 'Z')
helixPath = helix(
angleStart = 0,
ccw = true,
revolutions = 4,
length = 10,
radius = 5,
axis = 'Z',
)
// Create a spring by sweeping around the helix path.
springSketch = startSketchOn('YZ')

View File

@ -17,7 +17,7 @@ tangentToEnd(tag: TagIdentifier) -> number
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | | Yes |
| `tag` | [`TagIdentifier`](/docs/kcl/types#tag-identifier) | The line segment being queried by its tag | Yes |
### Returns

View File

@ -0,0 +1,16 @@
---
title: "EnvironmentRef"
excerpt: ""
layout: manual
---
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)

View File

@ -19,8 +19,8 @@ A face.
| `id` |`string`| The id of the face. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A face. | No |

View File

@ -311,7 +311,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `memory` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -351,6 +351,23 @@ Data for an imported geometry.
----
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Tombstone`| | No |
| `value` |`null`| | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
----

View File

@ -98,6 +98,29 @@ a complete arc
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----
A base path.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CircleThreePoint`| | No |
| `p1` |`[number, number]`| Point 1 of the circle | No |
| `p2` |`[number, number]`| Point 2 of the circle | No |
| `p3` |`[number, number]`| Point 3 of the circle | No |
| `from` |`[number, number]`| The from point. | No |
| `to` |`[number, number]`| The to point. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
----
A path that is horizontal.

View File

@ -20,8 +20,8 @@ A plane.
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A plane. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `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 |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A plane. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -17,6 +17,5 @@ layout: manual
|----------|------|-------------|----------|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
| `currentEnv` |`integer`| | No |
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |

View File

@ -29,8 +29,8 @@ A plane.
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |[`PlaneType`](/docs/kcl/types/PlaneType)| A sketch type. | No |
| `origin` |[`Point3d`](/docs/kcl/types/Point3d)| Origin of the plane. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the planes Y axis be? | No |
| `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 |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
@ -53,8 +53,8 @@ A face.
| `id` |`string`| The id of the face. | No |
| `artifactId` |[`ArtifactId`](/docs/kcl/types/ArtifactId)| The artifact ID. | No |
| `value` |`string`| The tag of the face. | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the faces Y axis be? | No |
| `xAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's X axis be? | No |
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face's Y axis be? | No |
| `zAxis` |[`Point3d`](/docs/kcl/types/Point3d)| The z-axis (normal). | No |
| `solid` |[`Solid`](/docs/kcl/types/Solid)| The solid the face is on. | No |
| `units` |[`UnitLen`](/docs/kcl/types/UnitLen)| A sketch type. | No |

View File

@ -0,0 +1,16 @@
---
title: "SnapshotRef"
excerpt: "An index pointing to a snapshot within a specific (unspecified) environment."
layout: manual
---
An index pointing to a snapshot within a specific (unspecified) environment.
**Type:** `integer` (`uint`)

View File

@ -54,23 +54,26 @@ async function doBasicSketch(
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await expect(u.codeLocator).toContainText(
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
)
}
await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(500)
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(${commonPoints.num1}, %)`)
}
await page.waitForTimeout(500)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
} else {
@ -79,8 +82,10 @@ async function doBasicSketch(
await page.waitForTimeout(200)
await page.mouse.click(startXPx, 500 - PUR * 20)
if (openPanes.includes('code')) {
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
@ -137,8 +142,10 @@ async function doBasicSketch(
// Open the code pane.
await u.openKclCodePanel()
await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
await expect(u.codeLocator)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(${commonPoints.num1}, %, $seg01)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(-segLen(seg01), %)`)

View File

@ -43,8 +43,7 @@ test.describe(
},
}
const code = `sketch001 = startSketchOn('${plane}')
|> startProfileAt([0.9, -1.22], %)`
const code = `sketch001 = startSketchOn('${plane}')profile001 = startProfileAt([0.9, -1.22], sketch001)`
await u.openDebugPanel()

View File

@ -1,6 +1,6 @@
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import { uuidv4 } from 'lib/utils'
import { isArray, uuidv4 } from 'lib/utils'
import {
closeDebugPanel,
doAndWaitForImageDiff,
@ -9,13 +9,15 @@ import {
sendCustomCmd,
} from '../test-utils'
type mouseParams = {
type MouseParams = {
pixelDiff?: number
shouldDbClick?: boolean
delay?: number
}
type mouseDragToParams = mouseParams & {
type MouseDragToParams = MouseParams & {
fromPoint: { x: number; y: number }
}
type mouseDragFromParams = mouseParams & {
type MouseDragFromParams = MouseParams & {
toPoint: { x: number; y: number }
}
@ -26,12 +28,12 @@ type SceneSerialised = {
}
}
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
type DblClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
type ClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
type MoveHandler = (moveParams?: MouseParams) => Promise<void | boolean>
type DblClickHandler = (clickParams?: MouseParams) => Promise<void | boolean>
type DragToHandler = (dragParams: MouseDragToParams) => Promise<void | boolean>
type DragFromHandler = (
dragParams: mouseDragFromParams
dragParams: MouseDragFromParams
) => Promise<void | boolean>
export class SceneFixture {
@ -77,17 +79,26 @@ export class SceneFixture {
{ steps }: { steps: number } = { steps: 20 }
): [ClickHandler, MoveHandler, DblClickHandler] =>
[
(clickParams?: mouseParams) => {
(clickParams?: MouseParams) => {
if (clickParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() => this.page.mouse.click(x, y),
() =>
clickParams?.shouldDbClick
? this.page.mouse.dblclick(x, y, {
delay: clickParams?.delay || 0,
})
: this.page.mouse.click(x, y, {
delay: clickParams?.delay || 0,
}),
clickParams.pixelDiff
)
}
return this.page.mouse.click(x, y)
return clickParams?.shouldDbClick
? this.page.mouse.dblclick(x, y, { delay: clickParams?.delay || 0 })
: this.page.mouse.click(x, y, { delay: clickParams?.delay || 0 })
},
(moveParams?: mouseParams) => {
(moveParams?: MouseParams) => {
if (moveParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
@ -97,7 +108,7 @@ export class SceneFixture {
}
return this.page.mouse.move(x, y, { steps })
},
(clickParams?: mouseParams) => {
(clickParams?: MouseParams) => {
if (clickParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
@ -114,7 +125,7 @@ export class SceneFixture {
{ steps }: { steps: number } = { steps: 20 }
): [DragToHandler, DragFromHandler] =>
[
(dragToParams: mouseDragToParams) => {
(dragToParams: MouseDragToParams) => {
if (dragToParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
@ -131,7 +142,7 @@ export class SceneFixture {
targetPosition: { x, y },
})
},
(dragFromParams: mouseDragFromParams) => {
(dragFromParams: MouseDragFromParams) => {
if (dragFromParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
@ -219,7 +230,7 @@ export class SceneFixture {
}
expectPixelColor = async (
colour: [number, number, number],
colour: [number, number, number] | [number, number, number][],
coords: { x: number; y: number },
diff: number
) => {
@ -241,22 +252,36 @@ export class SceneFixture {
}
}
function isColourArray(
colour: [number, number, number] | [number, number, number][]
): colour is [number, number, number][] {
return isArray(colour[0])
}
export async function expectPixelColor(
page: Page,
colour: [number, number, number],
colour: [number, number, number] | [number, number, number][],
coords: { x: number; y: number },
diff: number
) {
let finalValue = colour
await expect
.poll(async () => {
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
if (!pixel) return null
finalValue = pixel
return pixel.every(
(channel, index) => Math.abs(channel - colour[index]) < diff
)
})
.poll(
async () => {
const pixel = (await getPixelRGBs(page)(coords, 1))[0]
if (!pixel) return null
finalValue = pixel
if (!isColourArray(colour)) {
return pixel.every(
(channel, index) => Math.abs(channel - colour[index]) < diff
)
}
return colour.some((c) =>
c.every((channel, index) => Math.abs(pixel[index] - channel) < diff)
)
},
{ timeout: 10_000 }
)
.toBeTruthy()
.catch((cause) => {
throw new Error(

View File

@ -23,7 +23,10 @@ export class ToolbarFixture {
helixButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
tangentialArcBtn!: Locator
circleBtn!: Locator
rectangleBtn!: Locator
lengthConstraintBtn!: Locator
exitSketchBtn!: Locator
editSketchBtn!: Locator
fileTreeBtn!: Locator
@ -53,7 +56,10 @@ export class ToolbarFixture {
this.helixButton = page.getByTestId('helix')
this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')
this.tangentialArcBtn = page.getByTestId('tangential-arc')
this.circleBtn = page.getByTestId('circle-center')
this.rectangleBtn = page.getByTestId('corner-rectangle')
this.lengthConstraintBtn = page.getByTestId('constraint-length')
this.exitSketchBtn = page.getByTestId('sketch-exit')
this.editSketchBtn = page.getByText('Edit Sketch')
this.fileTreeBtn = page.locator('[id="files-button-holder"]')
@ -119,6 +125,25 @@ export class ToolbarFixture {
await expect(this.exeIndicator).toBeVisible({ timeout: 15_000 })
}
}
selectCenterRectangle = async () => {
await this.page
.getByRole('button', { name: 'caret down Corner rectangle:' })
.click()
await expect(
this.page.getByTestId('dropdown-center-rectangle')
).toBeVisible()
await this.page.getByTestId('dropdown-center-rectangle').click()
}
selectCircleThreePoint = async () => {
await this.page
.getByRole('button', { name: 'caret down Center circle:' })
.click()
await expect(
this.page.getByTestId('dropdown-circle-three-points')
).toBeVisible()
await this.page.getByTestId('dropdown-circle-three-points').click()
}
async closePane(paneId: SidebarType) {
return closePane(this.page, paneId + SIDEBAR_BUTTON_SUFFIX)

View File

@ -219,18 +219,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
105.26
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`,
afterRectangle1stClickSnippet:
'startProfileAt([205.96, 254.59], sketch002)',
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|>close()`,
})
await sketchOnAChamfer({
@ -251,19 +246,15 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
afterChamferSelectSnippet:
'sketch003 = startSketchOn(extrude001, seg04)',
afterRectangle1stClickSnippet: 'startProfileAt([-209.64, 255.28], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([
segAng(rectangleSegmentA003) - 90,
106.84
], %, $rectangleSegmentB002)
|> angledLine([
segAng(rectangleSegmentA003),
-segLen(rectangleSegmentA003)
], %, $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`,
afterRectangle1stClickSnippet:
'startProfileAt([-209.64, 255.28], sketch003)',
afterRectangle2ndClickSnippet: `angledLine([0,11.56],%,$rectangleSegmentA003)
|>angledLine([segAng(rectangleSegmentA003)-90,106.84],%)
|>angledLine([segAng(rectangleSegmentA003),-segLen(rectangleSegmentA003)],%)
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|>close()`,
})
await sketchOnAChamfer({
clickCoords: { x: 677, y: 87 },
cameraPos: { x: -6200, y: 1500, z: 6200 },
@ -276,19 +267,14 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
]
}, %)`,
afterChamferSelectSnippet:
'sketch003 = startSketchOn(extrude001, seg04)',
afterRectangle1stClickSnippet: 'startProfileAt([75.8, 317.2], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([
segAng(rectangleSegmentA003) - 90,
106.84
], %, $rectangleSegmentB002)
|> angledLine([
segAng(rectangleSegmentA003),
-segLen(rectangleSegmentA003)
], %, $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`,
'sketch004 = startSketchOn(extrude001, seg05)',
afterRectangle1stClickSnippet:
'startProfileAt([82.57, 322.96], sketch004)',
afterRectangle2ndClickSnippet: `angledLine([0,11.16],%,$rectangleSegmentA004)
|>angledLine([segAng(rectangleSegmentA004)-90,103.07],%)
|>angledLine([segAng(rectangleSegmentA004),-segLen(rectangleSegmentA004)],%)
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|>close()`,
})
/// last one
await sketchOnAChamfer({
@ -301,104 +287,98 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
}, %)`,
afterChamferSelectSnippet:
'sketch005 = startSketchOn(extrude001, seg06)',
afterRectangle1stClickSnippet: 'startProfileAt([-23.43, 19.69], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([
segAng(rectangleSegmentA005) - 90,
84.07
], %, $rectangleSegmentB004)
|> angledLine([
segAng(rectangleSegmentA005),
-segLen(rectangleSegmentA005)
], %, $rectangleSegmentC004)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`,
afterRectangle1stClickSnippet:
'startProfileAt([-23.43, 19.69], sketch005)',
afterRectangle2ndClickSnippet: `angledLine([0,9.1],%,$rectangleSegmentA005)
|>angledLine([segAng(rectangleSegmentA005)-90,84.07],%)
|>angledLine([segAng(rectangleSegmentA005),-segLen(rectangleSegmentA005)],%)
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|>close()`,
})
await test.step('verify at the end of the test that final code is what is expected', async () => {
await editor.expectEditor.toContain(
`sketch001 = startSketchOn('XZ')
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
217.26
], %, $seg01)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $yo)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer({
length = 30,
tags = [getOppositeEdge(seg01)]
}, %, $seg03)
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(seg02)]
}, %, $seg05)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %, $seg06)
sketch005 = startSketchOn(extrude001, seg06)
profile004 = startProfileAt([-23.43, 19.69], sketch005)
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([
segAng(rectangleSegmentA005) - 90,
84.07
], %)
|> angledLine([
segAng(rectangleSegmentA005),
-segLen(rectangleSegmentA005)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch004 = startSketchOn(extrude001, seg05)
profile003 = startProfileAt([82.57, 322.96], sketch004)
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|> angledLine([
segAng(rectangleSegmentA004) - 90,
103.07
], %)
|> angledLine([
segAng(rectangleSegmentA004),
-segLen(rectangleSegmentA004)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch003 = startSketchOn(extrude001, seg04)
profile002 = startProfileAt([-209.64, 255.28], sketch003)
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([
segAng(rectangleSegmentA003) - 90,
106.84
], %)
|> angledLine([
segAng(rectangleSegmentA003),
-segLen(rectangleSegmentA003)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(extrude001, seg03)
profile001 = startProfileAt([205.96, 254.59], sketch002)
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
105.26
], %)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
|> startProfileAt([75.8, 317.2], %) // [$startCapTag, $EndCapTag]
|> angledLine([0, 268.43], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
217.26
], %, $seg01)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %, $yo)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)], tag = $seg02)
|> close()
extrude001 = extrude(sketch001, length = 100)
|> chamfer({
length = 30,
tags = [getOppositeEdge(seg01)]
}, %, $seg03)
|> chamfer({ length = 30, tags = [seg01] }, %, $seg04)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(seg02)]
}, %, $seg05)
|> chamfer({
length = 30,
tags = [getNextAdjacentEdge(yo)]
}, %, $seg06)
sketch005 = startSketchOn(extrude001, seg06)
|> startProfileAt([-23.43,19.69], %)
|> angledLine([0, 9.1], %, $rectangleSegmentA005)
|> angledLine([
segAng(rectangleSegmentA005) - 90,
84.07
], %, $rectangleSegmentB004)
|> angledLine([
segAng(rectangleSegmentA005),
-segLen(rectangleSegmentA005)
], %, $rectangleSegmentC004)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch004 = startSketchOn(extrude001, seg05)
|> startProfileAt([82.57,322.96], %)
|> angledLine([0, 11.16], %, $rectangleSegmentA004)
|> angledLine([
segAng(rectangleSegmentA004) - 90,
103.07
], %, $rectangleSegmentB003)
|> angledLine([
segAng(rectangleSegmentA004),
-segLen(rectangleSegmentA004)
], %, $rectangleSegmentC003)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch003 = startSketchOn(extrude001, seg04)
|> startProfileAt([-209.64,255.28], %)
|> angledLine([0, 11.56], %, $rectangleSegmentA003)
|> angledLine([
segAng(rectangleSegmentA003) - 90,
106.84
], %, $rectangleSegmentB002)
|> angledLine([
segAng(rectangleSegmentA003),
-segLen(rectangleSegmentA003)
], %, $rectangleSegmentC002)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(extrude001, seg03)
|> startProfileAt([205.96,254.59], %)
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
105.26
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
`,
`,
{ shouldNormalise: true }
)
})
@ -443,18 +423,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
beforeChamferSnippetEnd: '}, extrude001)',
afterChamferSelectSnippet:
'sketch002 = startSketchOn(extrude001, seg03)',
afterRectangle1stClickSnippet: 'startProfileAt([205.96, 254.59], %)',
afterRectangle2ndClickSnippet: `angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
105.26
], %, $rectangleSegmentB001)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()`,
afterRectangle1stClickSnippet:
'startProfileAt([205.96, 254.59], sketch002)',
afterRectangle2ndClickSnippet: `angledLine([0,11.39],%,$rectangleSegmentA002)
|>angledLine([segAng(rectangleSegmentA002)-90,105.26],%)
|>angledLine([segAng(rectangleSegmentA002),-segLen(rectangleSegmentA002)],%)
|>line(endAbsolute=[profileStartX(%),profileStartY(%)])
|>close()`,
})
await editor.expectEditor.toContain(
`sketch001 = startSketchOn('XZ')
@ -484,17 +459,17 @@ chamf = chamfer({
]
}, %)
sketch002 = startSketchOn(extrude001, seg03)
|> startProfileAt([205.96, 254.59], %)
profile001 = startProfileAt([205.96, 254.59], sketch002)
|> angledLine([0, 11.39], %, $rectangleSegmentA002)
|> angledLine([
segAng(rectangleSegmentA002) - 90,
105.26
], %, $rectangleSegmentB001)
], %)
|> angledLine([
segAng(rectangleSegmentA002),
-segLen(rectangleSegmentA002)
], %, $rectangleSegmentC001)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
], %)
|> line(endAbsolute=[profileStartX(%), profileStartY(%)])
|> close()
`,
{ shouldNormalise: true }
@ -561,10 +536,10 @@ sketch002 = startSketchOn(extrude001, seg03)
const expectedCodeSnippets = {
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], sketch001)`,
segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], sketch001)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], sketch001)`,
}
await test.step(`Start a sketch on the XZ plane`, async () => {
@ -605,6 +580,7 @@ sketch002 = startSketchOn(extrude001, seg03)
expectedCodeSnippets.afterSegmentDraggedOnYAxis
)
})
await editor.page.waitForTimeout(1000)
})
test(`Verify user can double-click to edit a sketch`, async ({
@ -1117,13 +1093,14 @@ openSketch = startSketchOn('XY')
}) => {
// One dumb hardcoded screen pixel value
const testPoint = { x: 620, y: 257 }
const expectedOutput = `helix001 = helix(revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5)`
const expectedOutput = `helix001 = helix( revolutions = 1, angleStart = 360, counterClockWise = false, radius = 5, axis = 'X', length = 5,)`
const expectedLine = `revolutions=1,`
await homePage.goToModelingScene()
await test.step(`Look for the red of the default plane`, async () => {
await scene.expectPixelColor([96, 52, 52], testPoint, 15)
})
// await test.step(`Look for the red of the default plane`, async () => {
// await scene.expectPixelColor([96, 52, 52], testPoint, 15)
// })
await test.step(`Go through the command bar flow`, async () => {
await toolbar.helixButton.click()
await cmdBar.expectState({
@ -1154,7 +1131,7 @@ openSketch = startSketchOn('XY')
await editor.expectEditor.toContain(expectedOutput)
await editor.expectState({
diagnostics: [],
activeLines: [expectedOutput],
activeLines: [expectedLine],
highlightedCode: '',
})
// Red plane is now gone, white helix is there
@ -1396,12 +1373,12 @@ sketch002 = startSketchOn('XZ')
await clickOnSketch2()
await page.waitForTimeout(500)
await cmdBar.progressCmdBar()
await toolbar.openPane('code')
await page.waitForTimeout(500)
})
await test.step(`Confirm code is added to the editor, scene has changed`, async () => {
await scene.expectPixelColor([135, 64, 73], testPoint, 15)
await toolbar.openPane('code')
await editor.expectEditor.toContain(sweepDeclaration)
await editor.expectState({
diagnostics: [],

View File

@ -192,11 +192,11 @@ extrude001 = extrude(sketch001, length = 50)
|> line(end = [0, -1])
|> close()
|> extrude(length = 1)
|> patternLinear3d({
axis: [1, 0, 1],
repetitions: 3,
distance: 6
}, %)`
|> patternLinear3d(
axis = [1, 0, 1],
repetitions = 3,
distance = 6,
)`
)
})
await page.setBodyDimensions({ width: 1000, height: 500 })

File diff suppressed because it is too large Load Diff

View File

@ -444,8 +444,7 @@ test(
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
code += `
|> startProfileAt([7.19, -9.7], %)`
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
await expect(page.locator('.cm-content')).toHaveText(code)
await page.waitForTimeout(100)
@ -456,7 +455,9 @@ test(
mask: [page.getByTestId('model-state-indicator')],
})
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
const lineEndClick = () =>
page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await lineEndClick()
await page.waitForTimeout(100)
code += `
@ -467,6 +468,15 @@ test(
.getByRole('button', { name: 'arc Tangential Arc', exact: true })
.click()
// click on the end of the profile to continue it
await page.waitForTimeout(300)
await lineEndClick()
await page.waitForTimeout(100)
// click to continue profile
await page.mouse.move(813, 392, { steps: 10 })
await page.waitForTimeout(100)
await page.mouse.move(startXPx + PUR * 30, 500 - PUR * 20, { steps: 10 })
await page.waitForTimeout(1000)
@ -589,8 +599,7 @@ test(
mask: [page.getByTestId('model-state-indicator')],
})
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn('XZ')
|> circle({ center = [14.44, -2.44], radius = 1 }, %)`
`sketch001 = startSketchOn('XZ')profile001 = circle({ center = [14.44, -2.44], radius = 1 }, sketch001)`
)
}
)
@ -634,8 +643,7 @@ test.describe(
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
code += `
|> startProfileAt([7.19, -9.7], %)`
code += `profile001 = startProfileAt([7.19, -9.7], sketch001)`
await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100)
@ -653,6 +661,10 @@ test.describe(
.click()
await page.waitForTimeout(100)
// click to continue profile
await page.mouse.click(813, 392)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += `
@ -739,8 +751,7 @@ test.describe(
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
code += `
|> startProfileAt([182.59, -246.32], %)`
code += `profile001 = startProfileAt([182.59, -246.32], sketch001)`
await expect(u.codeLocator).toHaveText(code)
await page.waitForTimeout(100)
@ -758,6 +769,10 @@ test.describe(
.click()
await page.waitForTimeout(100)
// click to continue profile
await page.mouse.click(813, 392)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 30, 500 - PUR * 20)
code += `

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -1,6 +1,7 @@
import { test, expect } from './zoo-test'
import { commonPoints, getUtils } from './test-utils'
import { EngineCommand } from 'lang/std/artifactGraph'
import { uuidv4 } from 'lib/utils'
test.describe('Test network and connection issues', () => {
test(
@ -111,18 +112,17 @@ test.describe('Test network and connection issues', () => {
const startXPx = 600
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await page.waitForTimeout(100)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> xLine(${commonPoints.num1}, %)`)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(${commonPoints.num1}, %)`)
// Expect the network to be up
await expect(networkToggle).toContainText('Connected')
@ -169,7 +169,9 @@ test.describe('Test network and connection issues', () => {
await page.mouse.click(100, 100)
// select a line
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
await page
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
.click()
// enter sketch again
await u.doAndWaitForCmd(
@ -183,11 +185,36 @@ test.describe('Test network and connection issues', () => {
await page.waitForTimeout(150)
const camCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
center: { x: 109, y: 0, z: -152 },
vantage: { x: 115, y: -505, z: -152 },
up: { x: 0, y: 0, z: 1 },
},
}
const updateCamCommand: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_get_settings',
},
}
await u.sendCustomCmd(camCommand)
await page.waitForTimeout(100)
await u.sendCustomCmd(updateCamCommand)
await page.waitForTimeout(100)
// click to continue profile
await page.mouse.click(1007, 400)
await page.waitForTimeout(100)
// Ensure we can continue sketching
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %)
profile001 = startProfileAt([12.34, -12.34], sketch001)
|> xLine(12.34, %)
|> line(end = [-12.34, 12.34])
@ -197,7 +224,7 @@ test.describe('Test network and connection issues', () => {
await expect.poll(u.normalisedEditorCode)
.toBe(`sketch001 = startSketchOn('XZ')
|> startProfileAt([12.34, -12.34], %)
profile001 = startProfileAt([12.34, -12.34], sketch001)
|> xLine(12.34, %)
|> line(end = [-12.34, 12.34])
|> xLine(-12.34, %)

View File

@ -19,7 +19,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
|> line(end = [20, 0])
|> line(end = [0, 20])
|> xLine(-20, %)
`
`
)
})
@ -673,7 +673,7 @@ test.describe('Testing constraints', { tag: ['@skipWin'] }, () => {
},
] as const
for (const { testName, addVariable, value, constraint } of cases) {
test(`${testName}`, async ({ context, homePage, page }) => {
test(`${testName}`, async ({ context, homePage, page, editor }) => {
// constants and locators
const cmdBarKclInput = page
.getByTestId('cmd-bar-arg-value')
@ -706,7 +706,9 @@ part002 = startSketchOn('XZ')
await page.setBodyDimensions({ width: 1200, height: 500 })
await homePage.goToModelingScene()
await u.waitForPageLoad()
await editor.scrollToText('line(end = [74.36, 130.4])', true)
await page.getByText('line(end = [74.36, 130.4])').click()
await page.getByRole('button', { name: 'Edit Sketch' }).click()

View File

@ -66,33 +66,34 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
const startXPx = 600
await u.closeDebugPanel()
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)`)
await expect(page.locator('.cm-content')).toHaveText(
`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)`
)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 10)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> xLine(${commonPoints.num1}, %)`)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|> xLine(${commonPoints.num1}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)`)
await page.waitForTimeout(100)
await page.mouse.click(startXPx, 500 - PUR * 20)
await expect(page.locator('.cm-content'))
.toHaveText(`sketch001 = startSketchOn('XZ')
|> startProfileAt(${commonPoints.startAt}, %)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${
commonPoints.startAt
}, sketch001)
|> xLine(${commonPoints.num1}, %)
|> yLine(${commonPoints.num1 + 0.01}, %)
|> xLine(${commonPoints.num2 * -1}, %)`)
// deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -259,66 +260,88 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
localStorage.setItem(
'persistCode',
`sketch001 = startSketchOn('XZ')
|> startProfileAt([-79.26, 95.04], %)
|> line(end = [112.54, 127.64], tag = $seg02)
|> line(end = [170.36, -121.61], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 50)
sketch005 = startSketchOn(extrude001, 'END')
|> startProfileAt([23.24, 136.52], %)
|> line(end = [-8.44, 36.61])
|> line(end = [49.4, 2.05])
|> line(end = [29.69, -46.95])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch003 = startSketchOn(extrude001, seg01)
|> startProfileAt([21.23, 17.81], %)
|> line(end = [51.97, 21.32])
|> line(end = [4.07, -22.75])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(extrude001, seg02)
|> startProfileAt([-100.54, 16.99], %)
|> line(end = [0, 20.03])
|> line(end = [62.61, 0], tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude002 = extrude(sketch002, length = 50)
sketch004 = startSketchOn(extrude002, seg03)
|> startProfileAt([57.07, 134.77], %)
|> line(end = [-4.72, 22.84])
|> line(end = [28.8, 6.71])
|> line(end = [9.19, -25.33])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude003 = extrude(sketch004, length = 20)
pipeLength = 40
pipeSmallDia = 10
pipeLargeDia = 20
thickness = 0.5
part009 = startSketchOn('XY')
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|> line(end = [thickness, 0])
|> line(end = [0, -1])
|> angledLineToX({
angle = 60,
to = pipeSmallDia + thickness
}, %)
|> line(end = [0, -pipeLength])
|> angledLineToX({
angle = -60,
to = pipeLargeDia + thickness
}, %)
|> line(end = [0, -1])
|> line(end = [-thickness, 0])
|> line(end = [0, 1])
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|> line(end = [0, pipeLength])
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|> close()
rev = revolve({ axis: 'y' }, part009)
`
|> startProfileAt([-79.26, 95.04], %)
|> line(end = [112.54, 127.64], tag = $seg02)
|> line(end = [170.36, -121.61], tag = $seg01)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude001 = extrude(sketch001, length = 50)
sketch005 = startSketchOn(extrude001, 'END')
|> startProfileAt([23.24, 136.52], %)
|> line(end = [-8.44, 36.61])
|> line(end = [49.4, 2.05])
|> line(end = [29.69, -46.95])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch003 = startSketchOn(extrude001, seg01)
|> startProfileAt([21.23, 17.81], %)
|> line(end = [51.97, 21.32])
|> line(end = [4.07, -22.75])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
sketch002 = startSketchOn(extrude001, seg02)
|> startProfileAt([-100.54, 16.99], %)
|> line(end = [0, 20.03])
|> line(end = [62.61, 0], tag = $seg03)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude002 = extrude(sketch002, length = 50)
sketch004 = startSketchOn(extrude002, seg03)
|> startProfileAt([57.07, 134.77], %)
|> line(end = [-4.72, 22.84])
|> line(end = [28.8, 6.71])
|> line(end = [9.19, -25.33])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
extrude003 = extrude(sketch004, length = 20)
pipeLength = 40
pipeSmallDia = 10
pipeLargeDia = 20
thickness = 0.5
part009 = startSketchOn('XY')
|> startProfileAt([pipeLargeDia - (thickness / 2), 38], %)
|> line(end = [thickness, 0])
|> line(end = [0, -1])
|> angledLineToX({
angle = 60,
to = pipeSmallDia + thickness
}, %)
|> line(end = [0, -pipeLength])
|> angledLineToX({
angle = -60,
to = pipeLargeDia + thickness
}, %)
|> line(end = [0, -1])
|> line(end = [-thickness, 0])
|> line(end = [0, 1])
|> angledLineToX({ angle = 120, to = pipeSmallDia }, %)
|> line(end = [0, pipeLength])
|> angledLineToX({ angle = 60, to = pipeLargeDia }, %)
|> close()
rev = revolve({ axis = 'y' }, part009)
sketch006 = startSketchOn('XY')
profile001 = circle({
center = [42.91, -70.42],
radius = 17.96
}, sketch006)
profile002 = startProfileAt([86.92, -63.81], sketch006)
|> angledLine([0, 63.81], %, $rectangleSegmentA001)
|> angledLine([
segAng(rectangleSegmentA001) - 90,
17.05
], %)
|> angledLine([
segAng(rectangleSegmentA001),
-segLen(rectangleSegmentA001)
], %)
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
profile003 = startProfileAt([40.16, -120.48], sketch006)
|> line(end = [26.95, 24.21])
|> line(end = [20.91, -28.61])
|> line(end = [32.46, 18.71])
`
)
}, KCL_DEFAULT_LENGTH)
await page.setBodyDimensions({ width: 1000, height: 500 })
@ -347,9 +370,10 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
})
await page.waitForTimeout(100)
const revolve = { x: 646, y: 248 }
const revolve = { x: 635, y: 253 }
const parentExtrude = { x: 915, y: 133 }
const solid2d = { x: 770, y: 167 }
const individualProfile = { x: 694, y: 432 }
// DELETE REVOLVE
await page.mouse.click(revolve.x, revolve.y)
@ -415,6 +439,20 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200)
await expect(u.codeLocator).not.toContainText(`sketch005 = startSketchOn({`)
// Delete a single profile
await page.mouse.click(individualProfile.x, individualProfile.y)
await page.waitForTimeout(100)
const codeToBeDeletedSnippet =
'profile003 = startProfileAt([40.16, -120.48], sketch006)'
await expect(page.locator('.cm-activeLine')).toHaveText(
' |> line(end = [20.91, -28.61])'
)
await u.clearCommandLogs()
await page.keyboard.press('Backspace')
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
await page.waitForTimeout(200)
await expect(u.codeLocator).not.toContainText(codeToBeDeletedSnippet)
})
test("Deleting solid that the AST mod can't handle results in a toast message", async ({
page,
@ -1216,12 +1254,15 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await page.waitForTimeout(600)
const firstClickCoords = { x: 650, y: 200 } as const
// Place a point because the line tool will exit if no points are pressed
await page.mouse.click(650, 200)
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
await page.waitForTimeout(600)
// Code before exiting the tool
let previousCodeContent = await page.locator('.cm-content').innerText()
let previousCodeContent = (
await page.locator('.cm-content').innerText()
).replace(/\s+/g, '')
// deselect the line tool by clicking it
await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -1233,14 +1274,23 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
await page.mouse.click(750, 200)
await page.waitForTimeout(100)
// expect no change
await expect(page.locator('.cm-content')).toHaveText(previousCodeContent)
await expect
.poll(async () => {
let str = await page.locator('.cm-content').innerText()
str = str.replace(/\s+/g, '')
return str
})
.toBe(previousCodeContent)
// select line tool again
await page.getByRole('button', { name: 'line Line', exact: true }).click()
await u.closeDebugPanel()
// Click to continue profile
await page.mouse.click(firstClickCoords.x, firstClickCoords.y)
await page.waitForTimeout(100)
// line tool should work as expected again
await page.mouse.click(700, 200)
await expect(page.locator('.cm-content')).not.toHaveText(

View File

@ -209,8 +209,13 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
// Draw a line
await page.mouse.move(700, 200, { steps: 5 })
await page.mouse.click(700, 200)
await page.mouse.move(800, 250, { steps: 5 })
await page.mouse.click(800, 250)
const secondMousePosition = { x: 800, y: 250 }
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
steps: 5,
})
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
// Unequip line tool
await page.keyboard.press('Escape')
// Make sure we didn't pop out of sketch mode.
@ -219,11 +224,23 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn
// Equip arc tool
await page.keyboard.press('a')
await expect(arcButton).toHaveAttribute('aria-pressed', 'true')
// click in the same position again to continue the profile
await page.mouse.move(secondMousePosition.x, secondMousePosition.y, {
steps: 5,
})
await page.mouse.click(secondMousePosition.x, secondMousePosition.y)
await page.mouse.move(1000, 100, { steps: 5 })
await page.mouse.click(1000, 100)
await page.keyboard.press('Escape')
await page.keyboard.press('l')
await expect(lineButton).toHaveAttribute('aria-pressed', 'true')
await expect(arcButton).toHaveAttribute('aria-pressed', 'false')
await expect
.poll(async () => {
await page.keyboard.press('l')
return lineButton.getAttribute('aria-pressed')
})
.toBe('true')
// Do not close the sketch.
// On close it will exit sketch mode.
@ -519,9 +536,9 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
await expect.poll(u.normalisedEditorCode).toContain(
u.normalisedCode(`sketch002 = startSketchOn(extrude001, seg01)
|> startProfileAt([-12.94, 6.6], %)
|> line(end = [2.45, -0.2])
|> line(end = [-2.6, -1.25])
profile001 = startProfileAt([-12.34, 12.34], sketch002)
|> line(end = [12.34, -12.34])
|> line(end = [-12.34, -12.34])
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|> close()
`)
@ -537,9 +554,8 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
await page.getByText('startProfileAt([-12').click()
await expect(page.getByRole('button', { name: 'Edit Sketch' })).toBeVisible()
await page.getByRole('button', { name: 'Edit Sketch' }).click()
await page.waitForTimeout(400)
await page.waitForTimeout(150)
await page.setBodyDimensions({ width: 1200, height: 1200 })
await page.waitForTimeout(500)
await page.setViewportSize({ width: 1200, height: 1200 })
await u.openAndClearDebugPanel()
await u.updateCamPosition([452, -152, 1166])
await u.closeDebugPanel()

View File

@ -26,7 +26,7 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@headlessui/react": "^1.7.19",
"@headlessui/tailwindcss": "^0.2.0",
"@kittycad/lib": "2.0.13",
"@kittycad/lib": "2.0.17",
"@lezer/highlight": "^1.2.1",
"@lezer/lr": "^1.4.1",
"@react-hook/resize-observer": "^2.0.1",
@ -85,7 +85,7 @@
"fmt": "prettier --write ./src *.ts *.json *.js ./e2e ./packages",
"fmt-check": "prettier --check ./src *.ts *.json *.js ./e2e ./packages",
"fetch:wasm": "./get-latest-wasm-bundle.sh",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-appearance/manifest.json",
"fetch:samples": "echo \"Fetching latest KCL samples...\" && curl -o public/kcl-samples-manifest-fallback.json https://raw.githubusercontent.com/KittyCAD/kcl-samples/achalmers/kw-pattern-transform2/manifest.json",
"isomorphic-copy-wasm": "(copy src/wasm-lib/pkg/wasm_lib_bg.wasm public || cp src/wasm-lib/pkg/wasm_lib_bg.wasm public)",
"build:wasm-dev": "yarn wasm-prep && (cd src/wasm-lib && wasm-pack build --dev --target web --out-dir pkg && cargo test -p kcl-lib export_bindings) && yarn isomorphic-copy-wasm && yarn fmt",
"build:wasm": "yarn wasm-prep && cd src/wasm-lib && wasm-pack build --release --target web --out-dir pkg && cargo test -p kcl-lib export_bindings && cd ../.. && yarn isomorphic-copy-wasm && yarn fmt",

View File

@ -34,6 +34,13 @@
"title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "cycloidal-gear/main.kcl",
"multipleFiles": false,
"title": "Cycloidal Gear",
"description": "A cycloidal gear is a gear with a continuous, curved tooth profile. They are used in watchmaking and high precision robotics actuation"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "dodecahedron/main.kcl",
@ -48,6 +55,13 @@
"title": "Enclosure",
"description": "An enclosure body and sealing lid for storing items"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "exhaust-manifold/main.kcl",
"multipleFiles": false,
"title": "Exhaust Manifold",
"description": "A welded exhaust header for an inline 4-cylinder engine"
},
{
"file": "main.kcl",
"pathFromProjectDirectoryToFirstFile": "flange-with-patterns/main.kcl",

View File

@ -5,7 +5,6 @@ import { useModelingContext } from 'hooks/useModelingContext'
import { useNetworkContext } from 'hooks/useNetworkContext'
import { NetworkHealthState } from 'hooks/useNetworkStatus'
import { ActionButton } from 'components/ActionButton'
import { isSingleCursorInPipe } from 'lang/queryAst'
import { useKclContext } from 'lang/KclProvider'
import { ActionButtonDropdown } from 'components/ActionButtonDropdown'
import { useHotkeys } from 'react-hotkeys-hook'
@ -21,6 +20,7 @@ import {
} from 'lib/toolbar'
import { isDesktop } from 'lib/isDesktop'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
import { isCursorInFunctionDefinition } from 'lang/queryAst'
import { commandBarActor } from 'machines/commandBarMachine'
import { isArray } from 'lib/utils'
@ -37,7 +37,12 @@ export function Toolbar({
const buttonBorderClassName = '!border-transparent'
const sketchPathId = useMemo(() => {
if (!isSingleCursorInPipe(context.selectionRanges, kclManager.ast))
if (
isCursorInFunctionDefinition(
kclManager.ast,
context.selectionRanges.graphSelections[0]
)
)
return false
return isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,

View File

@ -31,7 +31,6 @@ import {
recast,
defaultSourceRange,
resultIsOk,
ProgramMemory,
topLevelRange,
} from 'lang/wasm'
import { CustomIcon, CustomIconName } from 'components/CustomIcon'
@ -125,14 +124,7 @@ export const ClientSideScene = ({
'mouseup',
toSync(sceneInfra.onMouseUp, reportRejection)
)
sceneEntitiesManager
.tearDownSketch()
.then(() => {
// no op
})
.catch((e) => {
console.error(e)
})
sceneEntitiesManager.tearDownSketch({ removeAxis: true })
}
}, [])
@ -153,7 +145,8 @@ export const ClientSideScene = ({
state.matches({ Sketch: 'Line tool' }) ||
state.matches({ Sketch: 'Tangential arc to' }) ||
state.matches({ Sketch: 'Rectangle tool' }) ||
state.matches({ Sketch: 'Circle tool' })
state.matches({ Sketch: 'Circle tool' }) ||
state.matches({ Sketch: 'Circle three point tool' })
) {
cursor = 'crosshair'
} else {
@ -191,12 +184,15 @@ const Overlays = () => {
style={{ zIndex: '99999999' }}
>
{Object.entries(context.segmentOverlays)
.filter((a) => a[1].visible)
.map(([pathToNodeString, overlay], index) => {
.flatMap((a) =>
a[1].map((b) => ({ pathToNodeString: a[0], overlay: b }))
)
.filter((a) => a.overlay.visible)
.map(({ pathToNodeString, overlay }, index) => {
return (
<Overlay
overlay={overlay}
key={pathToNodeString}
key={pathToNodeString + String(index)}
pathToNodeString={pathToNodeString}
overlayIndex={index}
/>
@ -237,11 +233,17 @@ const Overlay = ({
const constraints =
callExpression.type === 'CallExpression'
? getConstraintInfo(callExpression, codeManager.code, overlay.pathToNode)
? getConstraintInfo(
callExpression,
codeManager.code,
overlay.pathToNode,
overlay.filterValue
)
: getConstraintInfoKw(
callExpression,
codeManager.code,
overlay.pathToNode
overlay.pathToNode,
overlay.filterValue
)
const offset = 20 // px
@ -261,7 +263,6 @@ const Overlay = ({
state.matches({ Sketch: 'Tangential arc to' }) ||
state.matches({ Sketch: 'Rectangle tool' })
)
return (
<div className={`absolute w-0 h-0`}>
<div
@ -319,17 +320,18 @@ const Overlay = ({
this will likely change soon when we implement multi-profile so we'll leave it for now
issue: https://github.com/KittyCAD/modeling-app/issues/3910
*/}
{callExpression?.callee?.name !== 'circle' && (
<SegmentMenu
verticalPosition={
overlay.windowCoords[1] > window.innerHeight / 2
? 'top'
: 'bottom'
}
pathToNode={overlay.pathToNode}
stdLibFnName={constraints[0]?.stdLibFnName}
/>
)}
{callExpression?.callee?.name !== 'circle' &&
callExpression?.callee?.name !== 'circleThreePoint' && (
<SegmentMenu
verticalPosition={
overlay.windowCoords[1] > window.innerHeight / 2
? 'top'
: 'bottom'
}
pathToNode={overlay.pathToNode}
stdLibFnName={constraints[0]?.stdLibFnName}
/>
)}
</div>
)}
</div>
@ -425,7 +427,7 @@ export async function deleteSegment({
modifiedAst = deleteSegmentFromPipeExpression(
dependentRanges,
modifiedAst,
kclManager.programMemory,
kclManager.variables,
codeManager.code,
pathToNode
)
@ -439,8 +441,8 @@ export async function deleteSegment({
const testExecute = await executeAst({
ast: modifiedAst,
engineCommandManager: engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
isMock: true,
usePrevMemory: false,
})
if (testExecute.errors.length) {
toast.error('Segment tag used outside of current Sketch. Could not delete.')
@ -450,6 +452,8 @@ export async function deleteSegment({
if (!sketchDetails) return
await sceneEntitiesManager.updateAstAndRejigSketch(
pathToNode,
sketchDetails.sketchNodePaths,
sketchDetails.planeNodePath,
modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -675,7 +679,7 @@ const ConstraintSymbol = ({
shallowPath,
argPosition,
kclManager.ast,
kclManager.programMemory
kclManager.variables
)
if (!transform) return

File diff suppressed because it is too large Load Diff

View File

@ -182,13 +182,15 @@ export class SceneInfra {
callbacks: (() => SegmentOverlayPayload | null)[] = []
_overlayCallbacks(callbacks: (() => SegmentOverlayPayload | null)[]) {
const segmentOverlayPayload: SegmentOverlayPayload = {
type: 'set-many',
type: 'add-many',
overlays: {},
}
callbacks.forEach((cb) => {
const overlay = cb()
if (overlay?.type === 'set-one') {
segmentOverlayPayload.overlays[overlay.pathToNodeString] = overlay.seg
} else if (overlay?.type === 'add-many') {
Object.assign(segmentOverlayPayload.overlays, overlay.overlays)
}
})
this.modelingSend({
@ -213,25 +215,27 @@ export class SceneInfra {
overlayThrottleMap: { [pathToNodeString: string]: number } = {}
updateOverlayDetails({
arrowGroup,
handle,
group,
isHandlesVisible,
from,
to,
angle,
hasThreeDotMenu,
}: {
arrowGroup: Group
handle: Group
group: Group
isHandlesVisible: boolean
from: Coords2d
to: Coords2d
hasThreeDotMenu: boolean
angle?: number
}): SegmentOverlayPayload | null {
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) {
if (!group.userData.draft && group.userData.pathToNode && handle) {
const vector = new Vector3(0, 0, 0)
// Get the position of the object3D in world space
arrowGroup.getWorldPosition(vector)
handle.getWorldPosition(vector)
// Project that position to screen space
vector.project(this.camControls.camera)
@ -244,13 +248,16 @@ export class SceneInfra {
return {
type: 'set-one',
pathToNodeString,
seg: {
windowCoords: [x, y],
angle: _angle,
group,
pathToNode: group.userData.pathToNode,
visible: isHandlesVisible,
},
seg: [
{
windowCoords: [x, y],
angle: _angle,
group,
pathToNode: group.userData.pathToNode,
visible: isHandlesVisible,
hasThreeDotMenu,
},
],
}
}
return null

View File

@ -31,6 +31,12 @@ import {
CIRCLE_SEGMENT,
CIRCLE_SEGMENT_BODY,
CIRCLE_SEGMENT_DASH,
CIRCLE_THREE_POINT_HANDLE1,
CIRCLE_THREE_POINT_HANDLE2,
CIRCLE_THREE_POINT_HANDLE3,
CIRCLE_THREE_POINT_SEGMENT,
CIRCLE_THREE_POINT_SEGMENT_BODY,
CIRCLE_THREE_POINT_SEGMENT_DASH,
EXTRA_SEGMENT_HANDLE,
EXTRA_SEGMENT_OFFSET_PX,
HIDE_HOVER_SEGMENT_LENGTH,
@ -56,11 +62,16 @@ import {
} from './sceneInfra'
import { Themes, getThemeColorForThreeJs } from 'lib/theme'
import { normaliseAngle, roundOff } from 'lib/utils'
import { SegmentOverlayPayload } from 'machines/modelingMachine'
import {
SegmentOverlay,
SegmentOverlayPayload,
SegmentOverlays,
} from 'machines/modelingMachine'
import { SegmentInputs } from 'lang/std/stdTypes'
import { err } from 'lib/trap'
import { editorManager, sceneInfra } from 'lib/singletons'
import { sceneInfra } from 'lib/singletons'
import { Selections } from 'lib/selections'
import { calculate_circle_from_3_points } from 'wasm-lib/pkg/wasm_lib'
import { commandBarActor } from 'machines/commandBarMachine'
interface CreateSegmentArgs {
@ -307,11 +318,12 @@ class StraightSegment implements SegmentUtils {
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
handle: arrowGroup,
group,
isHandlesVisible,
from,
to,
hasThreeDotMenu: true,
})
}
}
@ -483,12 +495,13 @@ class TangentialArcToSegment implements SegmentUtils {
)
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
handle: arrowGroup,
group,
isHandlesVisible,
from,
to,
angle,
hasThreeDotMenu: true,
})
}
}
@ -684,35 +697,255 @@ class CircleSegment implements SegmentUtils {
}
return () =>
sceneInfra.updateOverlayDetails({
arrowGroup,
handle: arrowGroup,
group,
isHandlesVisible,
from: from,
to: [center[0], center[1]],
angle: Math.PI / 4,
hasThreeDotMenu: true,
})
}
}
class CircleThreePointSegment implements SegmentUtils {
init: SegmentUtils['init'] = ({
input,
id,
pathToNode,
isDraftSegment,
scale = 1,
theme,
isSelected = false,
sceneInfra,
prevSegment,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
}
const { p1, p2, p3 } = input
const { center_x, center_y, radius } = calculate_circle_from_3_points(
p1[0],
p1[1],
p2[0],
p2[1],
p3[0],
p3[1]
)
const center: [number, number] = [center_x, center_y]
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const group = new Group()
const geometry = createArcGeometry({
center,
radius,
startAngle: 0,
endAngle: Math.PI * 2,
ccw: true,
isDashed: isDraftSegment,
scale,
})
const mat = new MeshBasicMaterial({ color })
const arcMesh = new Mesh(geometry, mat)
const meshType = isDraftSegment
? CIRCLE_THREE_POINT_SEGMENT_DASH
: CIRCLE_THREE_POINT_SEGMENT_BODY
const handle1 = createCircleThreePointHandle(
scale,
theme,
CIRCLE_THREE_POINT_HANDLE1,
color
)
const handle2 = createCircleThreePointHandle(
scale,
theme,
CIRCLE_THREE_POINT_HANDLE2,
color
)
const handle3 = createCircleThreePointHandle(
scale,
theme,
CIRCLE_THREE_POINT_HANDLE3,
color
)
arcMesh.userData.type = meshType
arcMesh.name = meshType
group.userData = {
type: CIRCLE_THREE_POINT_SEGMENT,
draft: isDraftSegment,
id,
p1,
p2,
p3,
ccw: true,
prevSegment,
pathToNode,
isSelected,
baseColor,
}
group.name = CIRCLE_THREE_POINT_SEGMENT
group.add(arcMesh, handle1, handle2, handle3)
const updateOverlaysCallback = this.update({
prevSegment,
input,
group,
scale,
sceneInfra,
})
if (err(updateOverlaysCallback)) return updateOverlaysCallback
return {
group,
updateOverlaysCallback,
}
}
update: SegmentUtils['update'] = ({
input,
group,
scale = 1,
sceneInfra,
}) => {
if (input.type !== 'circle-three-point-segment') {
return new Error('Invalid segment type')
}
const { p1, p2, p3 } = input
group.userData.p1 = p1
group.userData.p2 = p2
group.userData.p3 = p3
const { center_x, center_y, radius } = calculate_circle_from_3_points(
p1[0],
p1[1],
p2[0],
p2[1],
p3[0],
p3[1]
)
const center: [number, number] = [center_x, center_y]
const points = [p1, p2, p3]
const handles = [
CIRCLE_THREE_POINT_HANDLE1,
CIRCLE_THREE_POINT_HANDLE2,
CIRCLE_THREE_POINT_HANDLE3,
].map((handle) => group.getObjectByName(handle) as Group)
handles.forEach((handle, i) => {
const point = points[i]
if (handle && point) {
handle.position.set(point[0], point[1], 0)
handle.scale.set(scale, scale, scale)
handle.visible = true
}
})
const pxLength = (2 * radius * Math.PI) / scale
const shouldHideIdle = pxLength < HIDE_SEGMENT_LENGTH
const shouldHideHover = pxLength < HIDE_HOVER_SEGMENT_LENGTH
const hoveredParent =
sceneInfra.hoveredObject &&
getParentGroup(sceneInfra.hoveredObject, [CIRCLE_SEGMENT])
let isHandlesVisible = !shouldHideIdle
if (hoveredParent && hoveredParent?.uuid === group?.uuid) {
isHandlesVisible = !shouldHideHover
}
const circleSegmentBody = group.children.find(
(child) => child.userData.type === CIRCLE_THREE_POINT_SEGMENT_BODY
) as Mesh
if (circleSegmentBody) {
const newGeo = createArcGeometry({
radius,
center,
startAngle: 0,
endAngle: Math.PI * 2,
ccw: true,
scale,
})
circleSegmentBody.geometry = newGeo
}
const circleSegmentBodyDashed = group.getObjectByName(
CIRCLE_THREE_POINT_SEGMENT_DASH
)
if (circleSegmentBodyDashed instanceof Mesh) {
// consider throttling the whole updateTangentialArcToSegment
// if there are more perf considerations going forward
circleSegmentBodyDashed.geometry = createArcGeometry({
center,
radius,
ccw: true,
// make the start end where the handle is
startAngle: Math.PI * 0.25,
endAngle: Math.PI * 2.25,
isDashed: true,
scale,
})
}
return () => {
const overlays: SegmentOverlays = {}
const points = [p1, p2, p3]
const overlayDetails = handles.map((handle, index) => {
const currentPoint = points[index]
const angle = Math.atan2(
currentPoint[1] - center[1],
currentPoint[0] - center[0]
)
return sceneInfra.updateOverlayDetails({
handle,
group,
isHandlesVisible,
from: [0, 0],
to: [center[0], center[1]],
angle: angle,
hasThreeDotMenu: index === 0,
})
})
const segmentOverlays: SegmentOverlay[] = []
overlayDetails.forEach((payload, index) => {
if (payload?.type === 'set-one') {
overlays[payload.pathToNodeString] = payload.seg
segmentOverlays.push({
...payload.seg[0],
filterValue: index === 0 ? 'p1' : index === 1 ? 'p2' : 'p3',
})
}
})
const segmentOverlayPayload: SegmentOverlayPayload = {
type: 'set-one',
pathToNodeString:
overlayDetails[0]?.type === 'set-one'
? overlayDetails[0].pathToNodeString
: '',
seg: segmentOverlays,
}
return segmentOverlayPayload
}
}
}
export function createProfileStartHandle({
from,
isDraft = false,
scale = 1,
theme,
isSelected,
size = 12,
...rest
}: {
from: Coords2d
scale?: number
theme: Themes
isSelected?: boolean
size?: number
} & (
| { isDraft: true }
| { isDraft: false; id: string; pathToNode: PathToNode }
)) {
const group = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
const geometry = new BoxGeometry(size, size, size) // in pixels scaled later
const baseColor = getThemeColorForThreeJs(theme)
const color = isSelected ? 0x0000ff : baseColor
const body = new MeshBasicMaterial({ color })
@ -774,6 +1007,29 @@ function createCircleCenterHandle(
circleCenterGroup.scale.set(scale, scale, scale)
return circleCenterGroup
}
function createCircleThreePointHandle(
scale = 1,
theme: Themes,
name: `circle-three-point-handle${'1' | '2' | '3'}`,
color?: number
): Group {
const circleCenterGroup = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
const baseColor = getThemeColorForThreeJs(theme)
const body = new MeshBasicMaterial({ color })
const mesh = new Mesh(geometry, body)
circleCenterGroup.add(mesh)
circleCenterGroup.userData = {
type: name,
baseColor,
}
circleCenterGroup.name = name
circleCenterGroup.scale.set(scale, scale, scale)
return circleCenterGroup
}
function createExtraSegmentHandle(
scale: number,
@ -1100,4 +1356,5 @@ export const segmentUtils = {
straight: new StraightSegment(),
tangentialArcTo: new TangentialArcToSegment(),
circle: new CircleSegment(),
circleThreePoint: new CircleThreePointSegment(),
} as const

View File

@ -1,11 +1,5 @@
import { useEffect, useState, useRef } from 'react'
import {
parse,
BinaryPart,
Expr,
ProgramMemory,
resultIsOk,
} from '../lang/wasm'
import { parse, BinaryPart, Expr, resultIsOk, VariableMap } from '../lang/wasm'
import {
createIdentifier,
createLiteral,
@ -100,7 +94,7 @@ export function useCalc({
newVariableInsertIndex: number
setNewVariableName: (a: string) => void
} {
const { programMemory } = useKclContext()
const { variables } = useKclContext()
const { context } = useModelingContext()
const selectionRange =
context.selectionRanges?.graphSelections[0]?.codeRef?.range
@ -127,7 +121,7 @@ export function useCalc({
}, [])
useEffect(() => {
if (programMemory.has(newVariableName)) {
if (variables[newVariableName]) {
setIsNewVariableNameUnique(false)
} else {
setIsNewVariableNameUnique(true)
@ -135,14 +129,14 @@ export function useCalc({
}, [newVariableName])
useEffect(() => {
if (!programMemory || !selectionRange) return
if (!variables || !selectionRange) return
const varInfo = findAllPreviousVariables(
kclManager.ast,
kclManager.programMemory,
kclManager.variables,
selectionRange
)
setAvailableVarInfo(varInfo)
}, [kclManager.ast, kclManager.programMemory, selectionRange])
}, [kclManager.ast, kclManager.variables, selectionRange])
useEffect(() => {
try {
@ -150,9 +144,9 @@ export function useCalc({
const pResult = parse(code)
if (trap(pResult) || !resultIsOk(pResult)) return
const ast = pResult.program
const _programMem: ProgramMemory = ProgramMemory.empty()
const _variables: VariableMap = {}
for (const { key, value } of availableVarInfo.variables) {
const error = _programMem.set(key, {
const error = (_variables[key] = {
type: 'String',
value,
__meta: [],
@ -163,8 +157,8 @@ export function useCalc({
executeAst({
ast,
engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: kclManager.programMemory.clone(),
isMock: true,
variables,
}).then(({ execState }) => {
const resultDeclaration = ast.body.find(
(a) =>
@ -174,7 +168,7 @@ export function useCalc({
const init =
resultDeclaration?.type === 'VariableDeclaration' &&
resultDeclaration?.declaration.init
const result = execState.memory?.get('__result__')?.value
const result = execState.variables['__result__']?.value
setCalcResult(typeof result === 'number' ? String(result) : 'NAN')
init && setValueNode(init)
})

View File

@ -157,8 +157,6 @@ export const LspProvider = ({ children }: { children: React.ReactNode }) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
plugin.requestSemanticTokens()
break
case 'kcl/memoryUpdated':
break
}
} catch (error) {
console.error(error)

View File

@ -25,7 +25,7 @@ import { useSetupEngineManager } from 'hooks/useSetupEngineManager'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import {
isCursorInSketchCommandRange,
updatePathToNodeFromMap,
updateSketchDetailsNodePaths,
} from 'lang/util'
import {
kclManager,
@ -65,17 +65,30 @@ import {
replaceValueAtNodePath,
sketchOnExtrudedFace,
sketchOnOffsetPlane,
splitPipedProfile,
startSketchOnDefault,
} from 'lang/modifyAst'
import { PathToNode, Program, parse, recast, resultIsOk } from 'lang/wasm'
import { artifactIsPlaneWithPaths, isSingleCursorInPipe } from 'lang/queryAst'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import {
PathToNode,
Program,
VariableDeclaration,
parse,
recast,
resultIsOk,
} from 'lang/wasm'
import {
artifactIsPlaneWithPaths,
doesSketchPipeNeedSplitting,
getNodeFromPath,
isCursorInFunctionDefinition,
traverse,
} from 'lang/queryAst'
import { exportFromEngine } from 'lib/exportFromEngine'
import { Models } from '@kittycad/lib/dist/types/src'
import toast from 'react-hot-toast'
import { useLoaderData, useNavigate, useSearchParams } from 'react-router-dom'
import { letEngineAnimateAndSyncCamAfter } from 'clientSideScene/CameraControls'
import { err, reportRejection, trap } from 'lib/trap'
import { err, reportRejection, trap, reject } from 'lib/trap'
import {
ExportIntent,
EngineConnectionStateType,
@ -86,10 +99,16 @@ import { useFileContext } from 'hooks/useFileContext'
import { uuidv4 } from 'lib/utils'
import { IndexLoaderData } from 'lib/types'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import {
getFaceCodeRef,
getPathsFromArtifact,
getPlaneFromArtifact,
} from 'lang/std/artifactGraph'
import { promptToEditFlow } from 'lib/promptToEdit'
import { kclEditorActor } from 'machines/kclEditorMachine'
import { commandBarActor } from 'machines/commandBarMachine'
import { useToken } from 'machines/appMachine'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
type MachineContext<T extends AnyStateMachine> = {
state: StateFrom<T>
@ -254,7 +273,11 @@ export const ModelingMachineProvider = ({
'Set Segment Overlays': assign({
segmentOverlays: ({ context: { segmentOverlays }, event }) => {
if (event.type !== 'Set Segment Overlays') return {}
if (event.data.type === 'set-many') return event.data.overlays
if (event.data.type === 'add-many')
return {
...segmentOverlays,
...event.data.overlays,
}
if (event.data.type === 'set-one')
return {
...segmentOverlays,
@ -287,7 +310,7 @@ export const ModelingMachineProvider = ({
return {
sketchDetails: {
...sketchDetails,
sketchPathToNode: event.data,
sketchEntryNodePath: event.data,
},
}
}),
@ -483,9 +506,17 @@ export const ModelingMachineProvider = ({
selectionRanges: setSelections.selection,
sketchDetails: {
...sketchDetails,
sketchPathToNode:
setSelections.updatedPathToNode ||
sketchDetails?.sketchPathToNode ||
sketchEntryNodePath:
setSelections.updatedSketchEntryNodePath ||
sketchDetails?.sketchEntryNodePath ||
[],
sketchNodePaths:
setSelections.updatedSketchNodePaths ||
sketchDetails?.sketchNodePaths ||
[],
planeNodePath:
setSelections.updatedPlaneNodePath ||
sketchDetails?.planeNodePath ||
[],
},
}
@ -638,7 +669,12 @@ export const ModelingMachineProvider = ({
if (artifactIsPlaneWithPaths(selectionRanges)) {
return true
}
if (!isSingleCursorInPipe(selectionRanges, kclManager.ast))
if (
isCursorInFunctionDefinition(
kclManager.ast,
selectionRanges.graphSelections[0]
)
)
return false
return !!isCursorInSketchCommandRange(
engineCommandManager.artifactGraph,
@ -666,13 +702,33 @@ export const ModelingMachineProvider = ({
async ({ input: { sketchDetails } }) => {
if (!sketchDetails) return
if (kclManager.ast.body.length) {
// this assumes no changes have been made to the sketch besides what we did when entering the sketch
// i.e. doesn't account for user's adding code themselves, maybe we need store a flag userEditedSinceSketchMode?
const newAst = structuredClone(kclManager.ast)
const varDecIndex = sketchDetails.sketchPathToNode[1][0]
const varDecIndex = sketchDetails.planeNodePath[1][0]
const varDec = getNodeFromPath<VariableDeclaration>(
newAst,
sketchDetails.planeNodePath,
'VariableDeclaration'
)
if (err(varDec)) return reject(new Error('No varDec'))
const variableName = varDec.node.declaration.id.name
let isIdentifierUsed = false
traverse(newAst, {
enter: (node) => {
if (
node.type === 'Identifier' &&
node.name === variableName
) {
isIdentifierUsed = true
}
},
})
if (isIdentifierUsed) return
// remove body item at varDecIndex
newAst.body = newAst.body.filter((_, i) => i !== varDecIndex)
await kclManager.executeAstMock(newAst)
await codeManager.updateEditorWithAstAndWriteToFile(newAst)
}
sceneInfra.setCallbacks({
onClick: () => {},
@ -682,7 +738,7 @@ export const ModelingMachineProvider = ({
}
),
'animate-to-face': fromPromise(async ({ input }) => {
if (!input) return undefined
if (!input) return null
if (input.type === 'extrudeFace' || input.type === 'offsetPlane') {
const sketched =
input.type === 'extrudeFace'
@ -709,7 +765,9 @@ export const ModelingMachineProvider = ({
await letEngineAnimateAndSyncCamAfter(engineCommandManager, id)
sceneInfra.camControls.syncDirection = 'clientToEngine'
return {
sketchPathToNode: pathToNewSketchNode,
sketchEntryNodePath: [],
planeNodePath: pathToNewSketchNode,
sketchNodePaths: [],
zAxis: input.zAxis,
yAxis: input.yAxis,
origin: input.position,
@ -730,7 +788,9 @@ export const ModelingMachineProvider = ({
)
return {
sketchPathToNode: pathToNode,
sketchEntryNodePath: [],
planeNodePath: pathToNode,
sketchNodePaths: [],
zAxis: input.zAxis,
yAxis: input.yAxis,
origin: [0, 0, 0],
@ -739,21 +799,49 @@ export const ModelingMachineProvider = ({
}),
'animate-to-sketch': fromPromise(
async ({ input: { selectionRanges } }) => {
const sourceRange =
selectionRanges.graphSelections[0]?.codeRef?.range
const sketchPathToNode = getNodePathFromSourceRange(
kclManager.ast,
sourceRange
const plane = getPlaneFromArtifact(
selectionRanges.graphSelections[0].artifact,
engineCommandManager.artifactGraph
)
const info = await getSketchOrientationDetails(
sketchPathToNode || []
if (err(plane)) return Promise.reject(plane)
const sketch = Object.values(kclManager.execState.variables).find(
(variable) =>
variable?.type === 'Sketch' &&
variable.value.artifactId === plane.pathIds[0]
)
if (!sketch || sketch.type !== 'Sketch')
return Promise.reject(new Error('No sketch'))
const info = await getSketchOrientationDetails(sketch.value)
await letEngineAnimateAndSyncCamAfter(
engineCommandManager,
info?.sketchDetails?.faceId || ''
)
const sketchArtifact = engineCommandManager.artifactGraph.get(
plane.pathIds[0]
)
if (sketchArtifact?.type !== 'path')
return Promise.reject(new Error('No sketch artifact'))
const sketchPaths = getPathsFromArtifact({
artifact: engineCommandManager.artifactGraph.get(plane.id),
sketchPathToNode: sketchArtifact?.codeRef?.pathToNode,
artifactGraph: engineCommandManager.artifactGraph,
ast: kclManager.ast,
})
if (err(sketchPaths)) return Promise.reject(sketchPaths)
let codeRef = getFaceCodeRef(plane)
if (!codeRef) return Promise.reject(new Error('No plane codeRef'))
// codeRef.pathToNode is not always populated correctly
const planeNodePath = getNodePathFromSourceRange(
kclManager.ast,
codeRef.range
)
return {
sketchPathToNode: sketchPathToNode || [],
sketchEntryNodePath: sketchArtifact.codeRef.pathToNode || [],
sketchNodePaths: sketchPaths,
planeNodePath,
zAxis: info.sketchDetails.zAxis || null,
yAxis: info.sketchDetails.yAxis || null,
origin: info.sketchDetails.origin.map(
@ -766,7 +854,7 @@ export const ModelingMachineProvider = ({
'Get horizontal info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await applyConstraintHorzVertDistance({
constraint: 'setHorzDistance',
selectionRanges,
@ -778,13 +866,23 @@ export const ModelingMachineProvider = ({
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -805,13 +903,15 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
'Get vertical info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await applyConstraintHorzVertDistance({
constraint: 'setVertDistance',
selectionRanges,
@ -822,13 +922,23 @@ export const ModelingMachineProvider = ({
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -849,7 +959,9 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
@ -859,14 +971,15 @@ export const ModelingMachineProvider = ({
selectionRanges,
})
if (err(info)) return Promise.reject(info)
const { modifiedAst, pathToNodeMap } = await (info.enabled
? applyConstraintAngleBetween({
selectionRanges,
})
: applyConstraintAngleLength({
selectionRanges,
angleOrLength: 'setAngle',
}))
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await (info.enabled
? applyConstraintAngleBetween({
selectionRanges,
})
: applyConstraintAngleLength({
selectionRanges,
angleOrLength: 'setAngle',
}))
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
@ -875,13 +988,23 @@ export const ModelingMachineProvider = ({
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -902,7 +1025,9 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
@ -917,20 +1042,30 @@ export const ModelingMachineProvider = ({
length: lengthValue,
})
if (err(constraintResult)) return Promise.reject(constraintResult)
const { modifiedAst, pathToNodeMap } = constraintResult
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
constraintResult
const pResult = parse(recast(modifiedAst))
if (trap(pResult) || !resultIsOk(pResult))
return Promise.reject(new Error('Unexpected compilation error'))
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -951,13 +1086,15 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
'Get perpendicular distance info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await applyConstraintIntersect({
selectionRanges,
})
@ -967,13 +1104,22 @@ export const ModelingMachineProvider = ({
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -994,13 +1140,15 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
'Get ABS X info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await applyConstraintAbsDistance({
constraint: 'xAbs',
selectionRanges,
@ -1011,13 +1159,22 @@ export const ModelingMachineProvider = ({
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -1038,13 +1195,15 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
'Get ABS Y info': fromPromise(
async ({ input: { selectionRanges, sketchDetails } }) => {
const { modifiedAst, pathToNodeMap } =
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
await applyConstraintAbsDistance({
constraint: 'yAbs',
selectionRanges,
@ -1055,13 +1214,22 @@ export const ModelingMachineProvider = ({
const _modifiedAst = pResult.program
if (!sketchDetails)
return Promise.reject(new Error('No sketch details'))
const updatedPathToNode = updatePathToNodeFromMap(
sketchDetails.sketchPathToNode,
pathToNodeMap
)
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
_modifiedAst,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -1082,7 +1250,9 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
@ -1102,9 +1272,11 @@ export const ModelingMachineProvider = ({
let result: {
modifiedAst: Node<Program>
pathToReplaced: PathToNode | null
exprInsertIndex: number
} = {
modifiedAst: parsed,
pathToReplaced: null,
exprInsertIndex: -1,
}
// If the user provided a constant name,
// we need to insert the named constant
@ -1134,6 +1306,7 @@ export const ModelingMachineProvider = ({
result = {
modifiedAst: parseResultAfterInsertion.program,
pathToReplaced: astAfterReplacement.pathToReplaced,
exprInsertIndex: astAfterReplacement.exprInsertIndex,
}
} else if ('valueText' in data.namedValue) {
// If they didn't provide a constant name,
@ -1164,10 +1337,22 @@ export const ModelingMachineProvider = ({
parsed = parsed as Node<Program>
if (!result.pathToReplaced)
return Promise.reject(new Error('No path to replaced node'))
const {
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
} = updateSketchDetailsNodePaths({
sketchEntryNodePath: sketchDetails.sketchEntryNodePath,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
exprInsertIndex: result.exprInsertIndex,
})
const updatedAst =
await sceneEntitiesManager.updateAstAndRejigSketch(
result.pathToReplaced || [],
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
parsed,
sketchDetails.zAxis,
sketchDetails.yAxis,
@ -1188,7 +1373,191 @@ export const ModelingMachineProvider = ({
return {
selectionType: 'completeSelection',
selection,
updatedPathToNode: result.pathToReplaced,
updatedSketchEntryNodePath,
updatedSketchNodePaths,
updatedPlaneNodePath,
}
}
),
'set-up-draft-circle': fromPromise(
async ({ input: { sketchDetails, data } }) => {
if (!sketchDetails || !data)
return reject('No sketch details or data')
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
const result = await sceneEntitiesManager.setupDraftCircle(
sketchDetails.sketchEntryNodePath,
sketchDetails.sketchNodePaths,
sketchDetails.planeNodePath,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
data
)
if (err(result)) return reject(result)
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
return result
}
),
'set-up-draft-circle-three-point': fromPromise(
async ({ input: { sketchDetails, data } }) => {
if (!sketchDetails || !data)
return reject('No sketch details or data')
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
const result =
await sceneEntitiesManager.setupDraftCircleThreePoint(
sketchDetails.sketchEntryNodePath,
sketchDetails.sketchNodePaths,
sketchDetails.planeNodePath,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
data.p1,
data.p2
)
if (err(result)) return reject(result)
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
return result
}
),
'set-up-draft-rectangle': fromPromise(
async ({ input: { sketchDetails, data } }) => {
if (!sketchDetails || !data)
return reject('No sketch details or data')
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
const result = await sceneEntitiesManager.setupDraftRectangle(
sketchDetails.sketchEntryNodePath,
sketchDetails.sketchNodePaths,
sketchDetails.planeNodePath,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
data
)
if (err(result)) return reject(result)
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
return result
}
),
'set-up-draft-center-rectangle': fromPromise(
async ({ input: { sketchDetails, data } }) => {
if (!sketchDetails || !data)
return reject('No sketch details or data')
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
const result = await sceneEntitiesManager.setupDraftCenterRectangle(
sketchDetails.sketchEntryNodePath,
sketchDetails.sketchNodePaths,
sketchDetails.planeNodePath,
sketchDetails.zAxis,
sketchDetails.yAxis,
sketchDetails.origin,
data
)
if (err(result)) return reject(result)
await codeManager.updateEditorWithAstAndWriteToFile(kclManager.ast)
return result
}
),
'setup-client-side-sketch-segments': fromPromise(
async ({ input: { sketchDetails, selectionRanges } }) => {
if (!sketchDetails) return
if (!sketchDetails.sketchEntryNodePath.length) return
if (Object.keys(sceneEntitiesManager.activeSegments).length > 0) {
sceneEntitiesManager.tearDownSketch({ removeAxis: false })
}
sceneInfra.resetMouseListeners()
await sceneEntitiesManager.setupSketch({
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
sketchNodePaths: sketchDetails.sketchNodePaths,
forward: sketchDetails.zAxis,
up: sketchDetails.yAxis,
position: sketchDetails.origin,
maybeModdedAst: kclManager.ast,
selectionRanges,
})
sceneInfra.resetMouseListeners()
sceneEntitiesManager.setupSketchIdleCallbacks({
sketchEntryNodePath: sketchDetails?.sketchEntryNodePath || [],
forward: sketchDetails.zAxis,
up: sketchDetails.yAxis,
position: sketchDetails.origin,
sketchNodePaths: sketchDetails.sketchNodePaths,
planeNodePath: sketchDetails.planeNodePath,
// We will want to pass sketchTools here
// to add their interactions
})
// We will want to update the context with sketchTools.
// They'll be used for their .destroy() in tearDownSketch
return undefined
}
),
'split-sketch-pipe-if-needed': fromPromise(
async ({ input: { sketchDetails } }) => {
if (!sketchDetails) return reject('No sketch details')
const existingSketchInfoNoOp = {
updatedEntryNodePath: sketchDetails.sketchEntryNodePath,
updatedSketchNodePaths: sketchDetails.sketchNodePaths,
updatedPlaneNodePath: sketchDetails.planeNodePath,
expressionIndexToDelete: -1,
} as const
if (
!sketchDetails.sketchNodePaths.length &&
sketchDetails.planeNodePath.length
) {
// new sketch, no profiles yet
return existingSketchInfoNoOp
}
const doesNeedSplitting = doesSketchPipeNeedSplitting(
kclManager.ast,
sketchDetails.sketchEntryNodePath
)
if (err(doesNeedSplitting)) return reject(doesNeedSplitting)
let moddedAst: Program = structuredClone(kclManager.ast)
let pathToProfile = sketchDetails.sketchEntryNodePath
let updatedSketchNodePaths = sketchDetails.sketchNodePaths
if (doesNeedSplitting) {
const splitResult = splitPipedProfile(
moddedAst,
sketchDetails.sketchEntryNodePath
)
if (err(splitResult)) return reject(splitResult)
moddedAst = splitResult.modifiedAst
pathToProfile = splitResult.pathToProfile
updatedSketchNodePaths = [pathToProfile]
}
const indexToDelete = sketchDetails?.expressionIndexToDelete || -1
if (indexToDelete >= 0) {
// this is the expression that was added when as sketch tool was used but not completed
// i.e first click for the center of the circle, but not the second click for the radius
// we added a circle to editor, but they bailed out early so we should remove it
moddedAst.body.splice(indexToDelete, 1)
// make sure the deleted expression is removed from the sketchNodePaths
updatedSketchNodePaths = updatedSketchNodePaths.filter(
(path) => path[1][0] !== indexToDelete
)
// if the deleted expression was the entryNodePath, we should just make it the first sketchNodePath
// as a safe default
pathToProfile =
pathToProfile[1][0] !== indexToDelete
? pathToProfile
: updatedSketchNodePaths[0]
}
await kclManager.executeAstMock(moddedAst)
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
return {
updatedEntryNodePath: pathToProfile,
updatedSketchNodePaths: updatedSketchNodePaths,
updatedPlaneNodePath: sketchDetails.planeNodePath,
expressionIndexToDelete: -1,
}
}
),
@ -1199,6 +1568,7 @@ export const ModelingMachineProvider = ({
selections: input.selection,
token,
artifactGraph: engineCommandManager.artifactGraph,
projectName: context.project.name,
})
}),
},

View File

@ -13,12 +13,7 @@ import {
getOperationLabel,
stdLibMap,
} from 'lib/operations'
import {
codeManager,
editorManager,
engineCommandManager,
kclManager,
} from 'lib/singletons'
import { editorManager, engineCommandManager, kclManager } from 'lib/singletons'
import { ComponentProps, useEffect, useMemo, useRef, useState } from 'react'
import { Operation } from 'wasm-lib/kcl/bindings/Operation'
import { Actor, Prop } from 'xstate'
@ -67,7 +62,7 @@ export const FeatureTreePane = () => {
)
: null
if (!artifact || !('codeRef' in artifact)) {
if (!artifact) {
modelingSend({
type: 'Set selection',
data: {

View File

@ -1,6 +1,6 @@
import { processMemory } from './MemoryPane'
import { enginelessExecutor } from '../../../lib/testHelpers'
import { assertParse, initPromise, ProgramMemory } from '../../../lang/wasm'
import { assertParse, initPromise } from '../../../lang/wasm'
beforeAll(async () => {
await initPromise
@ -29,15 +29,11 @@ describe('processMemory', () => {
|> line(endAbsolute = [2.15, 4.32])
// |> rx(90, %)`
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, ProgramMemory.empty())
const output = processMemory(execState.memory)
const execState = await enginelessExecutor(ast)
const output = processMemory(execState.variables)
expect(output.myVar).toEqual(5)
expect(output.otherVar).toEqual(3)
expect(output).toEqual({
HALF_TURN: 180,
QUARTER_TURN: 90,
THREE_QUARTER_TURN: 270,
ZERO: 0,
myVar: 5,
myFn: '__function(a)__',
otherVar: 3,

View File

@ -2,10 +2,10 @@ import toast from 'react-hot-toast'
import ReactJson from 'react-json-view'
import { useMemo } from 'react'
import {
ProgramMemory,
Path,
ExtrudeSurface,
sketchFromKclValueOptional,
VariableMap,
} from 'lang/wasm'
import { useKclContext } from 'lang/KclProvider'
import { useResolvedTheme } from 'hooks/useResolvedTheme'
@ -15,12 +15,12 @@ import Tooltip from 'components/Tooltip'
import { useModelingContext } from 'hooks/useModelingContext'
export const MemoryPaneMenu = () => {
const { programMemory } = useKclContext()
const { variables } = useKclContext()
function copyProgramMemoryToClipboard() {
if (globalThis && 'navigator' in globalThis) {
navigator.clipboard
.writeText(JSON.stringify(programMemory))
.writeText(JSON.stringify(variables))
.then(() => toast.success('Program memory copied to clipboard'))
.catch((e) =>
trap(new Error('Failed to copy program memory to clipboard'))
@ -50,12 +50,9 @@ export const MemoryPaneMenu = () => {
export const MemoryPane = () => {
const theme = useResolvedTheme()
const { programMemory } = useKclContext()
const { variables } = useKclContext()
const { state } = useModelingContext()
const ProcessedMemory = useMemo(
() => processMemory(programMemory),
[programMemory]
)
const ProcessedMemory = useMemo(() => processMemory(variables), [variables])
return (
<div className="h-full relative">
<div className="absolute inset-0 p-2 flex flex-col items-start">
@ -85,9 +82,10 @@ export const MemoryPane = () => {
)
}
export const processMemory = (programMemory: ProgramMemory) => {
export const processMemory = (variables: VariableMap) => {
const processedMemory: any = {}
for (const [key, val] of programMemory?.visibleEntries()) {
for (const [key, val] of Object.entries(variables)) {
if (val === undefined) continue
if (
val.type === 'Sketch' ||
// @ts-ignore

View File

@ -19,7 +19,6 @@ import { commandBarActor } from 'machines/commandBarMachine'
import { useSelector } from '@xstate/react'
import { copyFileShareLink } from 'lib/links'
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { IS_NIGHTLY_OR_DEBUG } from 'routes/Settings'
import { useToken } from 'machines/appMachine'
const ProjectSidebarMenu = ({
@ -194,7 +193,7 @@ function ProjectMenuPopover({
id: 'share-link',
Element: 'button',
children: 'Share current part (via Zoo link)',
disabled: !(IS_NIGHTLY_OR_DEBUG && findCommand(shareCommandInfo)),
disabled: !findCommand(shareCommandInfo),
onClick: async () => {
await copyFileShareLink({
token: token ?? '',

View File

@ -2,7 +2,12 @@ import { SVGProps } from 'react'
export const Spinner = (props: SVGProps<SVGSVGElement>) => {
return (
<svg viewBox="0 0 10 10" className={'w-8 h-8'} {...props}>
<svg
data-testid="spinner"
viewBox="0 0 10 10"
className={'w-8 h-8'}
{...props}
>
<circle
cx="5"
cy="5"

View File

@ -95,7 +95,7 @@ export function applyConstraintEqualAngle({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform

View File

@ -93,7 +93,7 @@ export function applyConstraintEqualLength({
ast: kclManager.ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform)) return transform
const { modifiedAst, pathToNodeMap } = transform

View File

@ -1,6 +1,6 @@
import { toolTips } from 'lang/langHelpers'
import { Selections } from 'lib/selections'
import { Program, ProgramMemory, Expr } from '../../lang/wasm'
import { Program, Expr, VariableMap } from '../../lang/wasm'
import { getNodeFromPath } from '../../lang/queryAst'
import {
PathToNodeMap,
@ -51,7 +51,7 @@ export function applyConstraintHorzVert(
selectionRanges: Selections,
horOrVert: 'vertical' | 'horizontal',
ast: Node<Program>,
programMemory: ProgramMemory
memVars: VariableMap
):
| {
modifiedAst: Node<Program>
@ -66,7 +66,7 @@ export function applyConstraintHorzVert(
ast,
selectionRanges,
transformInfos,
programMemory,
memVars,
referenceSegName: '',
})
}

View File

@ -46,7 +46,7 @@ export function intersectInfo({
isLinesParallelAndConstrained(
kclManager.ast,
engineCommandManager.artifactGraph,
kclManager.programMemory,
kclManager.variables,
selectionRanges.graphSelections[0],
selectionRanges.graphSelections[1]
)
@ -136,6 +136,7 @@ export async function applyConstraintIntersect({
}): Promise<{
modifiedAst: Node<Program>
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const info = intersectInfo({
selectionRanges,
@ -147,7 +148,7 @@ export async function applyConstraintIntersect({
ast: structuredClone(kclManager.ast),
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transform1)) return Promise.reject(transform1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -174,6 +175,7 @@ export async function applyConstraintIntersect({
return {
modifiedAst,
pathToNodeMap,
exprInsertIndex: -1,
}
}
// transform again but forcing certain values
@ -184,7 +186,7 @@ export async function applyConstraintIntersect({
ast: kclManager.ast,
selectionRanges: forcedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -192,6 +194,7 @@ export async function applyConstraintIntersect({
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transform2
let exprInsertIndex = -1
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
@ -204,9 +207,11 @@ export async function applyConstraintIntersect({
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
})
exprInsertIndex = newVariableInsertIndex
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
exprInsertIndex,
}
}

View File

@ -28,7 +28,7 @@ export function removeConstrainingValuesInfo({
| Error {
const _nodes = selectionRanges.graphSelections.map(({ codeRef }) => {
const tmp = getNodeFromPath<Expr>(kclManager.ast, codeRef.pathToNode)
if (err(tmp)) return tmp
if (tmp instanceof Error) return tmp
return tmp.node
})
const _err1 = _nodes.find(err)
@ -88,7 +88,7 @@ export function applyRemoveConstrainingValues({
ast: kclManager.ast,
selectionRanges: updatedSelectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
}

View File

@ -92,6 +92,7 @@ export async function applyConstraintAbsDistance({
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const info = absDistanceInfo({
selectionRanges,
@ -104,7 +105,7 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(transform1)) return Promise.reject(transform1)
@ -124,13 +125,14 @@ export async function applyConstraintAbsDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
if (err(transform2)) return Promise.reject(transform2)
const { modifiedAst: _modifiedAst, pathToNodeMap } = transform2
let exprInsertIndex = -1
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
@ -143,8 +145,9 @@ export async function applyConstraintAbsDistance({
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
})
exprInsertIndex = newVariableInsertIndex
}
return { modifiedAst: _modifiedAst, pathToNodeMap }
return { modifiedAst: _modifiedAst, pathToNodeMap, exprInsertIndex }
}
export function applyConstraintAxisAlign({
@ -172,7 +175,7 @@ export function applyConstraintAxisAlign({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})

View File

@ -86,6 +86,7 @@ export async function applyConstraintAngleBetween({
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const info = angleBetweenInfo({ selectionRanges })
if (err(info)) return Promise.reject(info)
@ -95,7 +96,7 @@ export async function applyConstraintAngleBetween({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transformed1)) return Promise.reject(transformed1)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -122,6 +123,7 @@ export async function applyConstraintAngleBetween({
return {
modifiedAst,
pathToNodeMap,
exprInsertIndex: -1,
}
}
@ -133,7 +135,7 @@ export async function applyConstraintAngleBetween({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
@ -141,6 +143,7 @@ export async function applyConstraintAngleBetween({
const { modifiedAst: _modifiedAst, pathToNodeMap: _pathToNodeMap } =
transformed2
let exprInsertIndex = -1
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
@ -153,9 +156,11 @@ export async function applyConstraintAngleBetween({
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
})
exprInsertIndex = newVariableInsertIndex
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap: _pathToNodeMap,
exprInsertIndex,
}
}

View File

@ -87,15 +87,13 @@ export function horzVertDistanceInfo({
export async function applyConstraintHorzVertDistance({
selectionRanges,
constraint,
// TODO align will always be false (covered by synconous applyConstraintHorzVertAlign), remove it
isAlign = false,
}: {
selectionRanges: Selections
constraint: 'setHorzDistance' | 'setVertDistance'
isAlign?: false
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const info = horzVertDistanceInfo({
selectionRanges: selectionRanges,
@ -107,7 +105,7 @@ export async function applyConstraintHorzVertDistance({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
})
if (err(transformed)) return Promise.reject(transformed)
const { modifiedAst, tagInfo, valueUsedInTransform, pathToNodeMap } =
@ -133,25 +131,25 @@ export async function applyConstraintHorzVertDistance({
return {
modifiedAst,
pathToNodeMap,
exprInsertIndex: -1,
}
} else {
if (!isExprBinaryPart(valueNode))
return Promise.reject('Invalid valueNode, is not a BinaryPart')
let finalValue = isAlign
? createLiteral(0)
: removeDoubleNegatives(valueNode, sign, variableName)
let finalValue = removeDoubleNegatives(valueNode, sign, variableName)
// transform again but forcing certain values
const transformed = transformSecondarySketchLinesTagFirst({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceSegName: segName,
forceValueUsedInTransform: finalValue,
})
if (err(transformed)) return Promise.reject(transformed)
const { modifiedAst: _modifiedAst, pathToNodeMap } = transformed
let exprInsertIndex = -1
if (variableName) {
const newBody = [..._modifiedAst.body]
newBody.splice(
@ -164,10 +162,12 @@ export async function applyConstraintHorzVertDistance({
const index = pathToNode.findIndex((a) => a[0] === 'body') + 1
pathToNode[index][0] = Number(pathToNode[index][0]) + 1
})
exprInsertIndex = newVariableInsertIndex
}
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
exprInsertIndex,
}
}
}
@ -195,7 +195,7 @@ export function applyConstraintHorzVertAlign({
ast: kclManager.ast,
selectionRanges,
transformInfos,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
forceValueUsedInTransform: finalValue,
})
if (err(retval)) return retval

View File

@ -74,10 +74,14 @@ export async function applyConstraintLength({
}: {
length: KclCommandValue
selectionRanges: Selections
}) {
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const ast = kclManager.ast
const angleLength = angleLengthInfo({ selectionRanges })
if (err(angleLength)) return angleLength
if (err(angleLength)) return Promise.reject(angleLength)
const { transforms } = angleLength
let distanceExpression: Expr = length.valueAst
@ -98,14 +102,14 @@ export async function applyConstraintLength({
}
if (!isExprBinaryPart(distanceExpression)) {
return new Error('Invalid valueNode, is not a BinaryPart')
return Promise.reject('Invalid valueNode, is not a BinaryPart')
}
const retval = transformAstSketchLines({
ast,
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: distanceExpression,
})
@ -116,6 +120,12 @@ export async function applyConstraintLength({
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
exprInsertIndex:
'variableName' in length &&
length.variableName &&
length.insertIndex !== undefined
? length.insertIndex
: -1,
}
}
@ -128,6 +138,7 @@ export async function applyConstraintAngleLength({
}): Promise<{
modifiedAst: Program
pathToNodeMap: PathToNodeMap
exprInsertIndex: number
}> {
const angleLength = angleLengthInfo({ selectionRanges, angleOrLength })
if (err(angleLength)) return Promise.reject(angleLength)
@ -137,7 +148,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
})
if (err(sketched)) return Promise.reject(sketched)
@ -189,7 +200,7 @@ export async function applyConstraintAngleLength({
ast: structuredClone(kclManager.ast),
selectionRanges,
transformInfos: transforms,
programMemory: kclManager.programMemory,
memVars: kclManager.variables,
referenceSegName: '',
forceValueUsedInTransform: finalValue,
})
@ -212,5 +223,6 @@ export async function applyConstraintAngleLength({
return {
modifiedAst: _modifiedAst,
pathToNodeMap,
exprInsertIndex: variableName ? newVariableInsertIndex : -1,
}
}

View File

@ -55,7 +55,7 @@ export function useConvertToVariable(range?: SourceRange) {
const { modifiedAst: _modifiedAst, pathToReplacedNode } =
moveValueIntoNewVariable(
ast,
kclManager.programMemory,
kclManager.variables,
range || context.selectionRanges.graphSelections[0]?.codeRef?.range,
variableName
)

View File

@ -7,7 +7,7 @@ import { KCLError } from './errors'
const KclContext = createContext({
code: codeManager?.code || '',
programMemory: kclManager?.programMemory,
variables: kclManager?.variables,
ast: kclManager?.ast,
isExecuting: kclManager?.isExecuting,
diagnostics: kclManager?.diagnostics,
@ -31,7 +31,7 @@ export function KclContextProvider({
// Both the code state and the editor state start off with the same code.
const [code, setCode] = useState(loadedCode || codeManager.code)
const [programMemory, setProgramMemory] = useState(kclManager.programMemory)
const [variables, setVariables] = useState(kclManager.variables)
const [ast, setAst] = useState(kclManager.ast)
const [isExecuting, setIsExecuting] = useState(false)
const [diagnostics, setDiagnostics] = useState<Diagnostic[]>([])
@ -44,7 +44,7 @@ export function KclContextProvider({
setCode,
})
kclManager.registerCallBacks({
setProgramMemory,
setVariables,
setAst,
setLogs,
setErrors,
@ -58,7 +58,7 @@ export function KclContextProvider({
<KclContext.Provider
value={{
code,
programMemory,
variables,
ast,
isExecuting,
diagnostics,

View File

@ -16,14 +16,16 @@ import {
clearSceneAndBustCache,
emptyExecState,
ExecState,
getKclVersion,
initPromise,
KclValue,
parse,
PathToNode,
Program,
ProgramMemory,
recast,
SourceRange,
topLevelRange,
VariableMap,
} from 'lang/wasm'
import { getNodeFromPath, getSettingsAnnotation } from './queryAst'
import { codeManager, editorManager, sceneInfra } from 'lib/singletons'
@ -58,11 +60,12 @@ export class KclManager {
nonCodeNodes: {},
startNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
}
private _execState: ExecState = emptyExecState()
private _programMemory: ProgramMemory = ProgramMemory.empty()
lastSuccessfulProgramMemory: ProgramMemory = ProgramMemory.empty()
private _variables: VariableMap = {}
lastSuccessfulVariables: VariableMap = {}
lastSuccessfulOperations: Operation[] = []
private _logs: string[] = []
private _errors: KCLError[] = []
@ -73,12 +76,15 @@ export class KclManager {
private _hasErrors = false
private _switchedFiles = false
private _fileSettings: KclSettingsAnnotation = {}
private _kclVersion: string | undefined = undefined
engineCommandManager: EngineCommandManager
private _isExecutingCallback: (arg: boolean) => void = () => {}
private _astCallBack: (arg: Node<Program>) => void = () => {}
private _programMemoryCallBack: (arg: ProgramMemory) => void = () => {}
private _variablesCallBack: (arg: {
[key in string]?: KclValue | undefined
}) => void = () => {}
private _logsCallBack: (arg: string[]) => void = () => {}
private _kclErrorsCallBack: (errors: KCLError[]) => void = () => {}
private _diagnosticsCallback: (errors: Diagnostic[]) => void = () => {}
@ -97,24 +103,34 @@ export class KclManager {
this._switchedFiles = switchedFiles
}
get programMemory() {
return this._programMemory
get variables() {
return this._variables
}
// This is private because callers should be setting the entire execState.
private set programMemory(programMemory) {
this._programMemory = programMemory
this._programMemoryCallBack(programMemory)
private set variables(variables) {
this._variables = variables
this._variablesCallBack(variables)
}
private set execState(execState) {
this._execState = execState
this.programMemory = execState.memory
this.variables = execState.variables
}
get execState() {
return this._execState
}
// Get the kcl version from the wasm module
// and store it in the singleton
// so we don't waste time getting it multiple times
get kclVersion() {
if (this._kclVersion === undefined) {
this._kclVersion = getKclVersion()
}
return this._kclVersion
}
get errors() {
return this._errors
}
@ -201,7 +217,7 @@ export class KclManager {
}
registerCallBacks({
setProgramMemory,
setVariables,
setAst,
setLogs,
setErrors,
@ -209,7 +225,7 @@ export class KclManager {
setIsExecuting,
setWasmInitFailed,
}: {
setProgramMemory: (arg: ProgramMemory) => void
setVariables: (arg: VariableMap) => void
setAst: (arg: Node<Program>) => void
setLogs: (arg: string[]) => void
setErrors: (errors: KCLError[]) => void
@ -217,7 +233,7 @@ export class KclManager {
setIsExecuting: (arg: boolean) => void
setWasmInitFailed: (arg: boolean) => void
}) {
this._programMemoryCallBack = setProgramMemory
this._variablesCallBack = setVariables
this._astCallBack = setAst
this._logsCallBack = setLogs
this._kclErrorsCallBack = setErrors
@ -240,7 +256,8 @@ export class KclManager {
nonCodeNodes: {},
startNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
}
}
@ -329,6 +346,7 @@ export class KclManager {
ast,
path: codeManager.currentFilePath || undefined,
engineCommandManager: this.engineCommandManager,
isMock: false,
})
// Program was not interrupted, setup the scene
@ -385,17 +403,16 @@ export class KclManager {
this.addDiagnostics(isInterrupted ? [] : kclErrorsToDiagnostics(errors))
this.execState = execState
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations
}
this.ast = { ...ast }
// updateArtifactGraph relies on updated executeState/programMemory
// updateArtifactGraph relies on updated executeState/variables
this.engineCommandManager.updateArtifactGraph(execState.artifactGraph)
this._executeCallback()
if (!isInterrupted) {
sceneInfra.modelingSend({ type: 'code edit during sketch' })
}
this.engineCommandManager.addCommandLog({
type: 'execution-done',
data: null,
@ -442,16 +459,16 @@ export class KclManager {
const { logs, errors, execState } = await executeAst({
ast: newAst,
engineCommandManager: this.engineCommandManager,
// We make sure to send an empty program memory to denote we mean mock mode.
programMemoryOverride: ProgramMemory.empty(),
isMock: true,
})
this._logs = logs
this.addDiagnostics(kclErrorsToDiagnostics(errors))
this._execState = execState
this._programMemory = execState.memory
this._variables = execState.variables
if (!errors.length) {
this.lastSuccessfulProgramMemory = execState.memory
this.lastSuccessfulVariables = execState.variables
this.lastSuccessfulOperations = execState.operations
}
}

View File

@ -1,59 +0,0 @@
import { assertParse, initPromise, parse } from './wasm'
import { err } from 'lib/trap'
beforeAll(async () => {
await initPromise
})
describe('testing AST', () => {
test('5 + 6', () => {
const ast = assertParse('5 +6')
delete (ast as any).nonCodeMeta
expect(ast.body).toEqual([
{
type: 'ExpressionStatement',
start: 0,
end: 4,
expression: {
type: 'BinaryExpression',
start: 0,
end: 4,
left: {
type: 'Literal',
start: 0,
end: 1,
value: {
suffix: 'None',
value: 5,
},
raw: '5',
},
operator: '+',
right: {
type: 'Literal',
start: 3,
end: 4,
value: {
suffix: 'None',
value: 6,
},
raw: '6',
},
},
},
])
})
})
describe('parsing errors', () => {
it('should return an error when there is a unexpected closed curly brace', async () => {
const code = `const myVar = startSketchAt([}], %)`
const result = parse(code)
if (err(result)) throw result
const error = result.errors[0]
expect(error.message).toBe('Array is missing a closing bracket(`]`)')
expect(error.sourceRange).toEqual([28, 29, 0])
})
})

View File

@ -15,8 +15,7 @@ const mySketch001 = startSketchOn('XY')
|> line(endAbsolute = [0.46, -5.82])
// |> rx(45, %)`
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
const sketch001 = execState.variables['mySketch001']
expect(sketch001).toEqual({
type: 'Sketch',
value: {
@ -73,8 +72,7 @@ const mySketch001 = startSketchOn('XY')
// |> rx(45, %)
|> extrude(length = 2)`
const execState = await enginelessExecutor(assertParse(code))
// @ts-ignore
const sketch001 = execState.memory.get('mySketch001')
const sketch001 = execState.variables['mySketch001']
expect(sketch001).toEqual({
type: 'Solid',
value: {
@ -165,9 +163,9 @@ const sk2 = startSketchOn('XY')
`
const execState = await enginelessExecutor(assertParse(code))
const programMemory = execState.memory
const variables = execState.variables
// @ts-ignore
const geos = [programMemory.get('theExtrude'), programMemory.get('sk2')]
const geos = [variables['theExtrude'], variables['sk2']]
expect(geos).toEqual([
{
type: 'Solid',

View File

@ -2,12 +2,12 @@ import fs from 'node:fs'
import {
assertParse,
ProgramMemory,
Sketch,
initPromise,
sketchFromKclValue,
defaultArtifactGraph,
topLevelRange,
VariableMap,
} from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import { KCLError } from './errors'
@ -21,13 +21,13 @@ describe('test executor', () => {
const code = `const myVar = 5
const newVar = myVar + 1`
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(5)
expect(mem.get('newVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(5)
expect(mem['newVar']?.value).toBe(6)
})
it('test assigning a var with a string', async () => {
const code = `const myVar = "a str"`
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe('a str')
expect(mem['myVar']?.value).toBe('a str')
})
it('test assigning a var by cont concatenating two strings string execute', async () => {
const code = fs.readFileSync(
@ -35,7 +35,7 @@ const newVar = myVar + 1`
'utf-8'
)
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe('a str another str')
expect(mem['myVar']?.value).toBe('a str another str')
})
it('fn funcN = () => {} execute', async () => {
const mem = await exe(
@ -47,8 +47,8 @@ const newVar = myVar + 1`
'const magicNum = funcN(9, theVar)',
].join('\n')
)
expect(mem.get('theVar')?.value).toBe(60)
expect(mem.get('magicNum')?.value).toBe(69)
expect(mem['theVar']?.value).toBe(60)
expect(mem['magicNum']?.value).toBe(69)
})
it('sketch declaration', async () => {
let code = `const mySketch = startSketchOn('XY')
@ -60,7 +60,7 @@ const newVar = myVar + 1`
`
const mem = await exe(code)
// geo is three js buffer geometry and is very bloated to have in tests
const sk = mem.get('mySketch')
const sk = mem['mySketch']
expect(sk?.type).toEqual('Sketch')
if (sk?.type !== 'Sketch') {
return
@ -117,7 +117,7 @@ const newVar = myVar + 1`
'const myVar = 5 + 1 |> myFn(%)',
].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7)
expect(mem['myVar']?.value).toBe(7)
})
// Enable rotations #152
@ -130,15 +130,15 @@ const newVar = myVar + 1`
// 'const rotated = rx(90, mySk1)',
// ].join('\n')
// const mem = await exe(code)
// expect(mem.get('mySk1')?.value).toHaveLength(3)
// expect(mem.get('rotated')?.type).toBe('Sketch')
// expect(mem['mySk1']?.value).toHaveLength(3)
// expect(mem['rotated')?.type).toBe('Sketch']
// if (
// mem.get('mySk1')?.type !== 'Sketch' ||
// mem.get('rotated')?.type !== 'Sketch'
// mem['mySk1']?.type !== 'Sketch' ||
// mem['rotated']?.type !== 'Sketch'
// )
// throw new Error('not a sketch')
// expect(mem.get('mySk1')?.rotation).toEqual([0, 0, 0, 1])
// expect(mem.get('rotated')?.rotation.map((a) => a.toFixed(4))).toEqual([
// expect(mem['mySk1']?.rotation).toEqual([0, 0, 0, 1])
// expect(mem['rotated']?.rotation.map((a) => a.toFixed(4))).toEqual([
// '0.7071',
// '0.0000',
// '0.0000',
@ -157,7 +157,7 @@ const newVar = myVar + 1`
// ' |> rx(90, %)',
].join('\n')
const mem = await exe(code)
expect(mem.get('mySk1')).toEqual({
expect(mem['mySk1']).toEqual({
type: 'Sketch',
value: {
type: 'Sketch',
@ -236,7 +236,7 @@ const newVar = myVar + 1`
)
const mem = await exe(code)
// TODO path to node is probably wrong here, zero indexes are not correct
expect(mem.get('three')).toEqual({
expect(mem['three']).toEqual({
type: 'Number',
value: 3,
__meta: [
@ -245,7 +245,7 @@ const newVar = myVar + 1`
},
],
})
expect(mem.get('yo')).toEqual({
expect(mem['yo']).toEqual({
type: 'Array',
value: [
{ type: 'Number', value: 1, __meta: [{ sourceRange: [28, 29, 0] }] },
@ -263,9 +263,6 @@ const newVar = myVar + 1`
},
],
})
// Check that there are no other variables or environments.
expect(mem.numEnvironments()).toBe(1)
expect(mem.numVariables(0)).toBe(2)
})
it('execute object expression', async () => {
const code = [
@ -273,7 +270,7 @@ const newVar = myVar + 1`
"const yo = {aStr: 'str', anum: 2, identifier: three, binExp: 4 + 5}",
].join('\n')
const mem = await exe(code)
expect(mem.get('yo')).toEqual({
expect(mem['yo']).toEqual({
type: 'Object',
value: {
aStr: {
@ -309,7 +306,7 @@ const newVar = myVar + 1`
'\n'
)
const mem = await exe(code)
expect(mem.get('myVar')).toEqual({
expect(mem['myVar']).toEqual({
type: 'String',
value: '123',
__meta: [
@ -325,80 +322,80 @@ describe('testing math operators', () => {
it('can sum', async () => {
const code = ['const myVar = 1 + 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3)
expect(mem['myVar']?.value).toBe(3)
})
it('can subtract', async () => {
const code = ['const myVar = 1 - 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-1)
expect(mem['myVar']?.value).toBe(-1)
})
it('can multiply', async () => {
const code = ['const myVar = 1 * 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(2)
expect(mem['myVar']?.value).toBe(2)
})
it('can divide', async () => {
const code = ['const myVar = 1 / 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(0.5)
expect(mem['myVar']?.value).toBe(0.5)
})
it('can modulus', async () => {
const code = ['const myVar = 5 % 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(1)
expect(mem['myVar']?.value).toBe(1)
})
it('can do multiple operations', async () => {
const code = ['const myVar = 1 + 2 * 3'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7)
expect(mem['myVar']?.value).toBe(7)
})
it('big example with parans', async () => {
const code = ['const myVar = 1 + 2 * (3 - 4) / -5 + 6'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(7.4)
expect(mem['myVar']?.value).toBe(7.4)
})
it('with identifier', async () => {
const code = ['const yo = 6', 'const myVar = yo / 2'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(3)
expect(mem['myVar']?.value).toBe(3)
})
it('with lots of testing', async () => {
const code = ['const myVar = 2 * ((2 + 3 ) / 4 + 5)'].join('\n')
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(12.5)
expect(mem['myVar']?.value).toBe(12.5)
})
it('with callExpression at start', async () => {
const code = 'const myVar = min(4, 100) + 2'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with callExpression at end', async () => {
const code = 'const myVar = 2 + min(4, 100)'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with nested callExpression', async () => {
const code = 'const myVar = 2 + min(100, legLen(5, 3))'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(6)
expect(mem['myVar']?.value).toBe(6)
})
it('with unaryExpression', async () => {
const code = 'const myVar = -min(100, 3)'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(-3)
expect(mem['myVar']?.value).toBe(-3)
})
it('with unaryExpression in callExpression', async () => {
const code = 'const myVar = min(-legLen(5, 4), 5)'
const code2 = 'const myVar = min(5 , -legLen(5, 4))'
const mem = await exe(code)
const mem2 = await exe(code2)
expect(mem.get('myVar')?.value).toBe(-3)
expect(mem.get('myVar')?.value).toBe(mem2.get('myVar')?.value)
expect(mem['myVar']?.value).toBe(-3)
expect(mem['myVar']?.value).toBe(mem2['myVar']?.value)
})
it('with unaryExpression in ArrayExpression', async () => {
const code = 'const myVar = [1,-legLen(5, 4)]'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toEqual([
expect(mem['myVar']?.value).toEqual([
{
__meta: [
{
@ -426,7 +423,7 @@ describe('testing math operators', () => {
'|> line(end = [-2.21, -legLen(5, min(3, 999))])',
].join('\n')
const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
const sketch = sketchFromKclValue(mem['part001'], 'part001')
// result of `-legLen(5, min(3, 999))` should be -4
const yVal = (sketch as Sketch).paths?.[0]?.to?.[1]
expect(yVal).toBe(-4)
@ -444,7 +441,7 @@ describe('testing math operators', () => {
``,
].join('\n')
const mem = await exe(code)
const sketch = sketchFromKclValue(mem.get('part001'), 'part001')
const sketch = sketchFromKclValue(mem['part001'], 'part001')
// expect -legLen(segLen('seg01'), myVar) to equal -4 setting the y value back to 0
expect((sketch as Sketch).paths?.[1]?.from).toEqual([3, 4])
expect((sketch as Sketch).paths?.[1]?.to).toEqual([6, 0])
@ -454,7 +451,7 @@ describe('testing math operators', () => {
)
const removedUnaryExpMem = await exe(removedUnaryExp)
const removedUnaryExpMemSketch = sketchFromKclValue(
removedUnaryExpMem.get('part001'),
removedUnaryExpMem['part001'],
'part001'
)
@ -464,12 +461,12 @@ describe('testing math operators', () => {
it('with nested callExpression and binaryExpression', async () => {
const code = 'const myVar = 2 + min(100, -1 + legLen(5, 3))'
const mem = await exe(code)
expect(mem.get('myVar')?.value).toBe(5)
expect(mem['myVar']?.value).toBe(5)
})
it('can do power of math', async () => {
const code = 'const myNeg2 = 4 ^ 2 - 3 ^ 2 * 2'
const mem = await exe(code)
expect(mem.get('myNeg2')?.value).toBe(-2)
expect(mem['myNeg2']?.value).toBe(-2)
})
})
@ -498,12 +495,9 @@ const theExtrude = startSketchOn('XY')
// helpers
async function exe(
code: string,
programMemory: ProgramMemory = ProgramMemory.empty()
) {
async function exe(code: string, variables: VariableMap = {}) {
const ast = assertParse(code)
const execState = await enginelessExecutor(ast, programMemory)
return execState.memory
const execState = await enginelessExecutor(ast, true, undefined, variables)
return execState.variables
}

View File

@ -1,4 +1,4 @@
import { assertParse, initPromise, programMemoryInit } from './wasm'
import { assertParse, initPromise } from './wasm'
import { enginelessExecutor } from '../lib/testHelpers'
import path from 'node:path'
@ -32,7 +32,7 @@ child_process.spawnSync('git', [
'clone',
'--single-branch',
'--branch',
'achalmers/kw-appearance',
'achalmers/kw-pattern-transform2',
URL_GIT_KCL_SAMPLES,
DIR_KCL_SAMPLES,
])
@ -72,7 +72,7 @@ describe('Test KCL Samples from public Github repository', () => {
const ast = assertParse(code)
await enginelessExecutor(
ast,
programMemoryInit(),
false,
file.pathFromProjectDirectoryToFirstFile
)
},

View File

@ -1,12 +1,12 @@
import {
Program,
executor,
ProgramMemory,
executeWithEngine,
executeMock,
kclLint,
emptyExecState,
ExecState,
VariableMap,
} from 'lang/wasm'
import { enginelessExecutor } from 'lib/testHelpers'
import { EngineCommandManager } from 'lang/std/engineConnection'
import { KCLError } from 'lang/errors'
import { Diagnostic } from '@codemirror/lint'
@ -27,6 +27,7 @@ export type ToolTip =
| 'angledLineThatIntersects'
| 'tangentialArcTo'
| 'circle'
| 'circleThreePoint'
export const toolTips: Array<ToolTip> = [
'line',
@ -42,20 +43,23 @@ export const toolTips: Array<ToolTip> = [
'yLineTo',
'angledLineThatIntersects',
'tangentialArcTo',
'circleThreePoint',
]
export async function executeAst({
ast,
path,
engineCommandManager,
// If you set programMemoryOverride we assume you mean mock mode. Since that
// is the only way to go about it.
programMemoryOverride,
isMock,
usePrevMemory,
variables,
}: {
ast: Node<Program>
path?: string
engineCommandManager: EngineCommandManager
programMemoryOverride?: ProgramMemory
isMock: boolean
usePrevMemory?: boolean
variables?: VariableMap
isInterrupted?: boolean
}): Promise<{
logs: string[]
@ -64,12 +68,11 @@ export async function executeAst({
isInterrupted: boolean
}> {
try {
const execState = await (programMemoryOverride
? enginelessExecutor(ast, programMemoryOverride, path)
: executor(ast, engineCommandManager, path))
const execState = await (isMock
? executeMock(ast, usePrevMemory, path, variables)
: executeWithEngine(ast, engineCommandManager, path))
await engineCommandManager.waitForAllCommands()
return {
logs: [],
errors: [],

View File

@ -3,7 +3,6 @@ import {
recast,
initPromise,
Identifier,
SourceRange,
topLevelRange,
LiteralValue,
Literal,
@ -25,6 +24,7 @@ import {
deleteSegmentFromPipeExpression,
removeSingleConstraintInfo,
deleteFromSelection,
splitPipedProfile,
} from './modifyAst'
import { enginelessExecutor } from '../lib/testHelpers'
import { findUsesOfTagInPipe } from './queryAst'
@ -134,7 +134,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -142,7 +142,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -150,7 +150,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -158,7 +158,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -166,7 +166,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -174,7 +174,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -182,7 +182,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -190,7 +190,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
{
type: 'Identifier',
@ -198,7 +198,7 @@ describe('Testing findUniqueName', () => {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
},
] satisfies Node<Identifier>[]),
'yo',
@ -217,7 +217,8 @@ describe('Testing addSketchTo', () => {
end: 0,
moduleId: 0,
nonCodeMeta: { nonCodeNodes: {}, startNodes: [] },
trivia: [],
innerAttrs: [],
outerAttrs: [],
},
'yz'
)
@ -315,7 +316,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('100 + 100') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -329,7 +330,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('2.8') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -343,7 +344,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('def(')
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -357,7 +358,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('jkl(') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -371,7 +372,7 @@ yo2 = hmm([identifierGuy + 5])`
const startIndex = code.indexOf('identifierGuy +') + 1
const { modifiedAst } = moveValueIntoNewVariable(
ast,
execState.memory,
execState.variables,
topLevelRange(startIndex, startIndex),
'newVar'
)
@ -557,7 +558,7 @@ describe('Testing deleteSegmentFromPipeExpression', () => {
const modifiedAst = deleteSegmentFromPipeExpression(
[],
ast,
execState.memory,
execState.variables,
code,
pathToNode
)
@ -639,7 +640,7 @@ ${!replace1 ? ` |> ${line}\n` : ''} |> angledLine([-65, ${
const modifiedAst = deleteSegmentFromPipeExpression(
dependentSegments,
ast,
execState.memory,
execState.variables,
code,
pathToNode
)
@ -745,7 +746,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode,
argPosition,
ast,
execState.memory
execState.variables
)
if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst)
@ -794,7 +795,7 @@ describe('Testing removeSingleConstraintInfo', () => {
pathToNode,
argPosition,
ast,
execState.memory
execState.variables
)
if (!mod) return new Error('mod is undefined')
const recastCode = recast(mod.modifiedAst)
@ -978,7 +979,7 @@ sketch002 = startSketchOn({
codeRef: codeRefFromRange(range, ast),
artifact,
},
execState.memory,
execState.variables,
async () => {
await new Promise((resolve) => setTimeout(resolve, 100))
return {
@ -995,3 +996,63 @@ sketch002 = startSketchOn({
}
)
})
describe('Testing splitPipedProfile', () => {
it('should split the pipe expression correctly', () => {
const codeBefore = `part001 = startSketchOn('XZ')
|> startProfileAt([1, 2], %)
|> line([3, 4], %)
|> line([5, 6], %)
|> close(%)
extrude001 = extrude(5, part001)
`
const expectedCodeAfter = `sketch001 = startSketchOn('XZ')
part001 = startProfileAt([1, 2], sketch001)
|> line([3, 4], %)
|> line([5, 6], %)
|> close(%)
extrude001 = extrude(5, part001)
`
const ast = assertParse(codeBefore)
const codeOfInterest = `startSketchOn('XZ')`
const range: [number, number, number] = [
codeBefore.indexOf(codeOfInterest),
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
0,
]
const pathToPipe = getNodePathFromSourceRange(ast, range)
const result = splitPipedProfile(ast, pathToPipe)
if (err(result)) throw result
const newCode = recast(result.modifiedAst)
if (err(newCode)) throw newCode
expect(newCode.trim()).toBe(expectedCodeAfter.trim())
})
it('should return error for already split pipe', () => {
const codeBefore = `sketch001 = startSketchOn('XZ')
part001 = startProfileAt([1, 2], sketch001)
|> line([3, 4], %)
|> line([5, 6], %)
|> close(%)
extrude001 = extrude(5, part001)
`
const ast = assertParse(codeBefore)
const codeOfInterest = `startProfileAt([1, 2], sketch001)`
const range: [number, number, number] = [
codeBefore.indexOf(codeOfInterest),
codeBefore.indexOf(codeOfInterest) + codeOfInterest.length,
0,
]
const pathToPipe = getNodePathFromSourceRange(ast, range)
const result = splitPipedProfile(ast, pathToPipe)
expect(result instanceof Error).toBe(true)
})
})

View File

@ -18,11 +18,14 @@ import {
UnaryExpression,
BinaryExpression,
PathToNode,
ProgramMemory,
SourceRange,
sketchFromKclValue,
isPathToNodeNumber,
parse,
formatNumber,
ArtifactGraph,
VariableMap,
KclValue,
} from './wasm'
import {
isNodeSafeToReplacePath,
@ -31,6 +34,8 @@ import {
getNodeFromPath,
isNodeSafeToReplace,
traverse,
getBodyIndex,
isCallExprWithName,
ARG_INDEX_FIELD,
LABELED_ARG_FIELD,
} from './queryAst'
@ -48,7 +53,7 @@ import {
transformAstSketchLines,
} from './std/sketchcombos'
import { DefaultPlaneStr } from 'lib/planes'
import { isOverlap, roundOff } from 'lib/utils'
import { isArray, isOverlap, roundOff } from 'lib/utils'
import { KCL_DEFAULT_CONSTANT_PREFIXES } from 'lib/constants'
import { SimplifiedArgDetails } from './std/stdTypes'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
@ -56,8 +61,19 @@ import { Models } from '@kittycad/lib'
import { ExtrudeFacePlane } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { KclExpressionWithVariable } from 'lib/commandTypes'
import {
Artifact,
expandCap,
expandPlane,
expandWall,
getArtifactOfTypes,
getArtifactsOfTypes,
getPathsFromArtifact,
} from './std/artifactGraph'
import { BodyItem } from 'wasm-lib/kcl/bindings/BodyItem'
import { findKwArg } from './util'
import { deleteEdgeTreatment } from './modifyAst/addEdgeTreatment'
import { engineCommandManager } from 'lib/singletons'
export function startSketchOnDefault(
node: Node<Program>,
@ -90,41 +106,54 @@ export function startSketchOnDefault(
}
}
export function addStartProfileAt(
export function insertNewStartProfileAt(
node: Node<Program>,
pathToNode: PathToNode,
at: [number, number]
): { modifiedAst: Node<Program>; pathToNode: PathToNode } | Error {
const _node1 = getNodeFromPath<VariableDeclaration>(
sketchEntryNodePath: PathToNode,
sketchNodePaths: PathToNode[],
planeNodePath: PathToNode,
at: [number, number],
insertType: 'start' | 'end' = 'end'
):
| {
modifiedAst: Node<Program>
updatedSketchNodePaths: PathToNode[]
updatedEntryNodePath: PathToNode
}
| Error {
const varDec = getNodeFromPath<VariableDeclarator>(
node,
pathToNode,
'VariableDeclaration'
planeNodePath,
'VariableDeclarator'
)
if (err(_node1)) return _node1
const variableDeclaration = _node1.node
if (variableDeclaration.type !== 'VariableDeclaration') {
return new Error('variableDeclaration.init.type !== PipeExpression')
}
const _node = { ...node }
const init = variableDeclaration.declaration.init
const startProfileAt = createCallExpressionStdLib('startProfileAt', [
createArrayExpression([
createLiteral(roundOff(at[0])),
createLiteral(roundOff(at[1])),
]),
createPipeSubstitution(),
])
if (init.type === 'PipeExpression') {
init.body.splice(1, 0, startProfileAt)
} else {
variableDeclaration.declaration.init = createPipeExpression([
init,
startProfileAt,
if (err(varDec)) return varDec
if (varDec.node.type !== 'VariableDeclarator') return new Error('not a var')
const newExpression = createVariableDeclaration(
findUniqueName(node, 'profile'),
createCallExpressionStdLib('startProfileAt', [
createArrayExpression([
createLiteral(roundOff(at[0])),
createLiteral(roundOff(at[1])),
]),
createIdentifier(varDec.node.id.name),
])
}
)
const insertIndex = getInsertIndex(sketchNodePaths, planeNodePath, insertType)
const _node = structuredClone(node)
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
_node.body.splice(insertIndex, 0, newExpression)
const { updatedEntryNodePath, updatedSketchNodePaths } =
updateSketchNodePathsWithInsertIndex({
insertIndex,
insertType,
sketchNodePaths,
})
return {
modifiedAst: _node,
pathToNode,
updatedSketchNodePaths,
updatedEntryNodePath,
}
}
@ -224,8 +253,21 @@ export function mutateKwArg(
for (let i = 0; i < node.arguments.length; i++) {
const arg = node.arguments[i]
if (arg.label.name === label) {
node.arguments[i].arg = val
return true
if (isLiteralArrayOrStatic(val) && isLiteralArrayOrStatic(arg.arg)) {
node.arguments[i].arg = val
return true
} else if (
arg.arg.type === 'ArrayExpression' &&
val.type === 'ArrayExpression'
) {
const arrExp = arg.arg
arrExp.elements.forEach((element, i) => {
if (isLiteralArrayOrStatic(element)) {
arrExp.elements[i] = val.elements[i]
}
})
return true
}
}
}
node.arguments.push(createLabeledArg(label, val))
@ -278,7 +320,7 @@ export function mutateObjExpProp(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
})
}
}
@ -288,15 +330,17 @@ export function mutateObjExpProp(
export function extrudeSketch({
node,
pathToNode,
shouldPipe = false,
distance = createLiteral(4),
extrudeName,
artifact,
artifactGraph,
}: {
node: Node<Program>
pathToNode: PathToNode
shouldPipe?: boolean
distance: Expr
extrudeName?: string
artifactGraph: ArtifactGraph
artifact?: Artifact
}):
| {
modifiedAst: Node<Program>
@ -304,10 +348,16 @@ export function extrudeSketch({
pathToExtrudeArg: PathToNode
}
| Error {
const orderedSketchNodePaths = getPathsFromArtifact({
artifact: artifact,
sketchPathToNode: pathToNode,
artifactGraph,
ast: node,
})
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
const _node = structuredClone(node)
const _node1 = getNodeFromPath(_node, pathToNode)
if (err(_node1)) return _node1
const { node: sketchExpression } = _node1
// determine if sketchExpression is in a pipeExpression or not
const _node2 = getNodeFromPath<PipeExpression>(
@ -316,9 +366,6 @@ export function extrudeSketch({
'PipeExpression'
)
if (err(_node2)) return _node2
const { node: pipeExpression } = _node2
const isInPipeExpression = pipeExpression.type === 'PipeExpression'
const _node3 = getNodeFromPath<VariableDeclarator>(
_node,
@ -326,54 +373,27 @@ export function extrudeSketch({
'VariableDeclarator'
)
if (err(_node3)) return _node3
const { node: variableDeclarator, shallowPath: pathToDecleration } = _node3
const { node: variableDeclarator } = _node3
const sketchToExtrude = shouldPipe
? createPipeSubstitution()
: createIdentifier(variableDeclarator.id.name)
const extrudeCall = createCallExpressionStdLibKw('extrude', sketchToExtrude, [
createLabeledArg('length', distance),
])
const extrudeCall = createCallExpressionStdLibKw(
'extrude',
createIdentifier(variableDeclarator.id.name),
[createLabeledArg('length', distance)]
)
// index of the 'length' arg above. If you reorder the labeled args above,
// make sure to update this too.
const argIndex = 0
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...pipeExpression.body, extrudeCall]
: [sketchExpression as any, extrudeCall]
)
variableDeclarator.init = pipeChain
const pathToExtrudeArg: PathToNode = [
...pathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpressionKw'],
[argIndex, ARG_INDEX_FIELD],
['arg', LABELED_ARG_FIELD],
]
return {
modifiedAst: _node,
pathToNode,
pathToExtrudeArg,
}
}
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name =
extrudeName ?? findUniqueName(node, KCL_DEFAULT_CONSTANT_PREFIXES.EXTRUDE)
const VariableDeclaration = createVariableDeclaration(name, extrudeCall)
const sketchIndexInPathToNode =
pathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = pathToDecleration[
sketchIndexInPathToNode
][0] as number
const lastSketchNodePath =
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
const sketchIndexInBody = Number(lastSketchNodePath[1][0])
_node.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToExtrudeArg: PathToNode = [
@ -891,7 +911,7 @@ export function createLiteral(value: LiteralValue | number): Node<Literal> {
moduleId: 0,
value,
raw,
trivia: [],
outerAttrs: [],
}
}
@ -901,7 +921,7 @@ export function createTagDeclarator(value: string): Node<TagDeclarator> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
value,
}
@ -913,7 +933,7 @@ export function createIdentifier(name: string): Node<Identifier> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
}
@ -925,7 +945,7 @@ export function createPipeSubstitution(): Node<PipeSubstitution> {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
}
}
@ -938,13 +958,13 @@ export function createCallExpressionStdLib(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -962,13 +982,13 @@ export function createCallExpressionStdLibKw(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -986,13 +1006,13 @@ export function createCallExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
callee: {
type: 'Identifier',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
name,
},
@ -1008,7 +1028,7 @@ export function createArrayExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
nonCodeMeta: nonCodeMetaEmpty(),
elements,
@ -1023,7 +1043,7 @@ export function createPipeExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
body,
nonCodeMeta: nonCodeMetaEmpty(),
@ -1041,14 +1061,14 @@ export function createVariableDeclaration(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
declaration: {
type: 'VariableDeclarator',
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
id: createIdentifier(varName),
init,
@ -1066,7 +1086,7 @@ export function createObjectExpression(properties: {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
nonCodeMeta: nonCodeMetaEmpty(),
properties: Object.entries(properties).map(([key, value]) => ({
@ -1074,7 +1094,7 @@ export function createObjectExpression(properties: {
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
key: createIdentifier(key),
value,
@ -1091,7 +1111,7 @@ export function createUnaryExpression(
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
operator,
argument,
@ -1108,7 +1128,7 @@ export function createBinaryExpression([left, operator, right]: [
start: 0,
end: 0,
moduleId: 0,
trivia: [],
outerAttrs: [],
operator,
left,
@ -1218,7 +1238,7 @@ export function replaceValueAtNodePath({
export function moveValueIntoNewVariablePath(
ast: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
pathToNode: PathToNode,
variableName: string
): {
@ -1231,11 +1251,7 @@ export function moveValueIntoNewVariablePath(
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariablesPath(
ast,
programMemory,
pathToNode
)
const { insertIndex } = findAllPreviousVariablesPath(ast, memVars, pathToNode)
let _node = structuredClone(ast)
const boop = replacer(_node, variableName)
if (trap(boop)) return { modifiedAst: ast }
@ -1251,7 +1267,7 @@ export function moveValueIntoNewVariablePath(
export function moveValueIntoNewVariable(
ast: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
sourceRange: SourceRange,
variableName: string
): {
@ -1263,11 +1279,7 @@ export function moveValueIntoNewVariable(
const { isSafe, value, replacer } = meta
if (!isSafe || value.type === 'Identifier') return { modifiedAst: ast }
const { insertIndex } = findAllPreviousVariables(
ast,
programMemory,
sourceRange
)
const { insertIndex } = findAllPreviousVariables(ast, memVars, sourceRange)
let _node = structuredClone(ast)
const replaced = replacer(_node, variableName)
if (trap(replaced)) return { modifiedAst: ast }
@ -1289,7 +1301,7 @@ export function moveValueIntoNewVariable(
export function deleteSegmentFromPipeExpression(
dependentRanges: SourceRange[],
modifiedAst: Node<Program>,
programMemory: ProgramMemory,
memVars: VariableMap,
code: string,
pathToNode: PathToNode
): Node<Program> | Error {
@ -1321,7 +1333,7 @@ export function deleteSegmentFromPipeExpression(
callExp.shallowPath,
constraintInfo.argPosition,
_modifiedAst,
programMemory
memVars
)
if (!transform) return
_modifiedAst = transform.modifiedAst
@ -1350,7 +1362,7 @@ export function removeSingleConstraintInfo(
pathToCallExp: PathToNode,
argDetails: SimplifiedArgDetails,
ast: Node<Program>,
programMemory: ProgramMemory
memVars: VariableMap
):
| {
modifiedAst: Node<Program>
@ -1367,7 +1379,7 @@ export function removeSingleConstraintInfo(
ast,
selectionRanges: [pathToCallExp],
transformInfos: [transform],
programMemory,
memVars,
referenceSegName: '',
})
if (err(retval)) return false
@ -1377,11 +1389,44 @@ export function removeSingleConstraintInfo(
export async function deleteFromSelection(
ast: Node<Program>,
selection: Selection,
programMemory: ProgramMemory,
variables: VariableMap,
getFaceDetails: (id: string) => Promise<Models['FaceIsPlanar_type']> = () =>
({} as any)
): Promise<Node<Program> | Error> {
const astClone = structuredClone(ast)
if (
(selection.artifact?.type === 'plane' ||
selection.artifact?.type === 'cap' ||
selection.artifact?.type === 'wall') &&
selection.artifact?.pathIds?.length
) {
const plane =
selection.artifact.type === 'plane'
? expandPlane(selection.artifact, engineCommandManager.artifactGraph)
: selection.artifact.type === 'wall'
? expandWall(selection.artifact, engineCommandManager.artifactGraph)
: expandCap(selection.artifact, engineCommandManager.artifactGraph)
for (const path of plane.paths.sort(
(a, b) => b.codeRef.range[0] - a.codeRef.range[0]
)) {
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
path.codeRef.pathToNode,
'VariableDeclarator'
)
if (err(varDec)) return varDec
const bodyIndex = Number(varDec.shallowPath[1][0])
astClone.body.splice(bodyIndex, 1)
}
// If it's a cap, we're not going to continue and try to
// delete the extrusion
if (
selection.artifact.type === 'cap' ||
selection.artifact.type === 'wall'
) {
return astClone
}
}
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
selection?.codeRef?.pathToNode,
@ -1460,59 +1505,108 @@ export async function deleteFromSelection(
if (extrudeNameToDelete) {
await new Promise((resolve) => {
;(async () => {
let currentVariableName = ''
const pathsDependingOnExtrude: Array<{
path: PathToNode
sketchName: string
variable: KclValue
}> = []
traverse(astClone, {
leave: (node) => {
if (node.type === 'VariableDeclaration') {
currentVariableName = ''
}
},
enter: (node, path) => {
;(async () => {
if (node.type === 'VariableDeclaration') {
currentVariableName = node.declaration.id.name
}
if (
// match startSketchOn(${extrudeNameToDelete})
node.type === 'CallExpression' &&
node.callee.name === 'startSketchOn' &&
node.arguments[0].type === 'Identifier' &&
node.arguments[0].name === extrudeNameToDelete
) {
pathsDependingOnExtrude.push({
path,
sketchName: currentVariableName,
})
}
})().catch(reportRejection)
},
})
const roundLiteral = (x: number) => createLiteral(roundOff(x))
const modificationDetails: {
parent: PipeExpression['body']
parentPipe: PipeExpression['body']
parentInit: VariableDeclarator
faceDetails: Models['FaceIsPlanar_type']
lastKey: number
lastKey: number | string
}[] = []
for (const { path, sketchName } of pathsDependingOnExtrude) {
const parent = getNodeFromPath<PipeExpression['body']>(
const wallArtifact =
selection.artifact?.type === 'wall'
? selection.artifact
: selection.artifact?.type === 'segment' &&
selection.artifact.surfaceId
? getArtifactOfTypes(
{ key: selection.artifact.surfaceId, types: ['wall'] },
engineCommandManager.artifactGraph
)
: null
if (err(wallArtifact)) return
if (wallArtifact) {
const sweep = getArtifactOfTypes(
{ key: wallArtifact.sweepId, types: ['sweep'] },
engineCommandManager.artifactGraph
)
if (err(sweep)) return
const wallsWithDependencies = Array.from(
getArtifactsOfTypes(
{ keys: sweep.surfaceIds, types: ['wall', 'cap'] },
engineCommandManager.artifactGraph
).values()
).filter((wall) => wall?.pathIds?.length)
const wallIds = wallsWithDependencies.map((wall) => wall.id)
Object.entries(variables).forEach(([key, _var]) => {
if (
_var?.type === 'Face' &&
wallIds.includes(_var.value.artifactId)
) {
const pathToStartSketchOn = getNodePathFromSourceRange(
astClone,
_var.value.__meta[0].sourceRange
)
pathsDependingOnExtrude.push({
path: pathToStartSketchOn,
variable: _var,
})
}
if (
_var?.type === 'Sketch' &&
_var.value.on.type === 'face' &&
wallIds.includes(_var.value.on.artifactId)
) {
const pathToStartSketchOn = getNodePathFromSourceRange(
astClone,
_var.value.on.__meta[0].sourceRange
)
pathsDependingOnExtrude.push({
path: pathToStartSketchOn,
variable: {
type: 'Face',
value: _var.value.on,
},
})
}
})
}
for (const { path, variable } of pathsDependingOnExtrude) {
// `parentPipe` and `parentInit` are the exact same node, but because it could either be an array or on object node
// putting them in two different variables was the only way to get TypeScript to stop complaining
// the reason why we're grabbing the parent and the last key is because we want to mutate the ast
// so `parent[lastKey]` does the trick, if there's a better way of doing this I'm all years
const parentPipe = getNodeFromPath<PipeExpression['body']>(
astClone,
path.slice(0, -1)
)
if (err(parent)) {
const parentInit = getNodeFromPath<VariableDeclarator>(
astClone,
path.slice(0, -1)
)
if (err(parentPipe) || err(parentInit)) {
return
}
const sketchToPreserve = sketchFromKclValue(
programMemory.get(sketchName),
sketchName
)
if (err(sketchToPreserve)) return sketchToPreserve
if (!variable) return new Error('Could not find sketch')
const artifactId =
variable.type === 'Sketch'
? variable.value.artifactId
: variable.type === 'Face'
? variable.value.artifactId
: ''
if (!artifactId) return new Error('Sketch not on anything')
const onId =
variable.type === 'Sketch'
? variable.value.on.id
: variable.type === 'Face'
? variable.value.id
: ''
if (!onId) return new Error('Sketch not on anything')
// Can't kick off multiple requests at once as getFaceDetails
// is three engine calls in one and they conflict
const faceDetails = await getFaceDetails(sketchToPreserve.on.id)
const faceDetails = await getFaceDetails(onId)
if (
!(
faceDetails.origin &&
@ -1523,14 +1617,20 @@ export async function deleteFromSelection(
) {
return
}
const lastKey = Number(path.slice(-1)[0][0])
const lastKey = path.slice(-1)[0][0]
modificationDetails.push({
parent: parent.node,
parentPipe: parentPipe.node,
parentInit: parentInit.node,
faceDetails,
lastKey,
})
}
for (const { parent, faceDetails, lastKey } of modificationDetails) {
for (const {
parentInit,
parentPipe,
faceDetails,
lastKey,
} of modificationDetails) {
if (
!(
faceDetails.origin &&
@ -1541,7 +1641,7 @@ export async function deleteFromSelection(
) {
continue
}
parent[lastKey] = createCallExpressionStdLib('startSketchOn', [
const expression = createCallExpressionStdLib('startSketchOn', [
createObjectExpression({
plane: createObjectExpression({
origin: createObjectExpression({
@ -1567,6 +1667,14 @@ export async function deleteFromSelection(
}),
}),
])
if (
parentInit.type === 'VariableDeclarator' &&
lastKey === 'init'
) {
parentInit[lastKey] = expression
} else if (isArray(parentPipe) && typeof lastKey === 'number') {
parentPipe[lastKey] = expression
}
}
resolve(true)
})().catch(reportRejection)
@ -1578,15 +1686,29 @@ export async function deleteFromSelection(
return deleteEdgeTreatment(astClone, selection)
} else if (varDec.node.init.type === 'PipeExpression') {
const pipeBody = varDec.node.init.body
const doNotDeleteProfileIfItHasBeenExtruded = !(
selection?.artifact?.type === 'segment' && selection?.artifact?.surfaceId
)
if (
pipeBody[0].type === 'CallExpression' &&
pipeBody[0].callee.name === 'startSketchOn'
doNotDeleteProfileIfItHasBeenExtruded &&
(pipeBody[0].callee.name === 'startSketchOn' ||
pipeBody[0].callee.name === 'startProfileAt')
) {
// remove varDec
const varDecIndex = varDec.shallowPath[1][0] as number
astClone.body.splice(varDecIndex, 1)
return astClone
}
} else if (
// single expression profiles
(varDec.node.init.type === 'CallExpressionKw' ||
varDec.node.init.type === 'CallExpression') &&
['circleThreePoint', 'circle'].includes(varDec.node.init.callee.name)
) {
const varDecIndex = varDec.shallowPath[1][0] as number
astClone.body.splice(varDecIndex, 1)
return astClone
}
return new Error('Selection not recognised, could not delete')
@ -1596,6 +1718,167 @@ const nonCodeMetaEmpty = () => {
return { nonCodeNodes: {}, startNodes: [], start: 0, end: 0 }
}
export const createLabeledArg = (name: string, arg: Expr): LabeledArg => {
return { label: createIdentifier(name), arg, type: 'LabeledArg' }
export function getInsertIndex(
sketchNodePaths: PathToNode[],
planeNodePath: PathToNode,
insertType: 'start' | 'end'
) {
let minIndex = 0
let maxIndex = 0
for (const path of sketchNodePaths) {
const index = Number(path[1][0])
if (index < minIndex) minIndex = index
if (index > maxIndex) maxIndex = index
}
const insertIndex = !sketchNodePaths.length
? Number(planeNodePath[1][0]) + 1
: insertType === 'start'
? minIndex
: maxIndex + 1
return insertIndex
}
export function updateSketchNodePathsWithInsertIndex({
insertIndex,
insertType,
sketchNodePaths,
}: {
insertIndex: number
insertType: 'start' | 'end'
sketchNodePaths: PathToNode[]
}): {
updatedEntryNodePath: PathToNode
updatedSketchNodePaths: PathToNode[]
} {
// TODO the rest of this function will not be robust to work for sketches defined within a function declaration
const newExpressionPathToNode: PathToNode = [
['body', ''],
[insertIndex, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
]
let updatedSketchNodePaths = structuredClone(sketchNodePaths)
if (insertType === 'start') {
updatedSketchNodePaths = updatedSketchNodePaths.map((path) => {
path[1][0] = Number(path[1][0]) + 1
return path
})
updatedSketchNodePaths.unshift(newExpressionPathToNode)
} else {
updatedSketchNodePaths.push(newExpressionPathToNode)
}
return {
updatedSketchNodePaths,
updatedEntryNodePath: newExpressionPathToNode,
}
}
/**
*
* Split the following pipe expression into
* ```ts
* part001 = startSketchOn('XZ')
|> startProfileAt([1, 2], %)
|> line([3, 4], %)
|> line([5, 6], %)
|> close(%)
extrude001 = extrude(5, part001)
```
into
```ts
sketch001 = startSketchOn('XZ')
part001 = startProfileAt([1, 2], sketch001)
|> line([3, 4], %)
|> line([5, 6], %)
|> close(%)
extrude001 = extrude(5, part001)
```
Notice that the `startSketchOn` is what gets the new variable name, this is so part001 still has the same data as before
making it safe for later code that uses part001 (the extrude in this example)
*
*/
export function splitPipedProfile(
ast: Program,
pathToPipe: PathToNode
):
| {
modifiedAst: Program
pathToProfile: PathToNode
pathToPlane: PathToNode
}
| Error {
const _ast = structuredClone(ast)
const varDec = getNodeFromPath<VariableDeclaration>(
_ast,
pathToPipe,
'VariableDeclaration'
)
if (err(varDec)) return varDec
if (
varDec.node.type !== 'VariableDeclaration' ||
varDec.node.declaration.init.type !== 'PipeExpression'
) {
return new Error('pathToNode does not point to pipe')
}
const init = varDec.node.declaration.init
const firstCall = init.body[0]
if (!isCallExprWithName(firstCall, 'startSketchOn'))
return new Error('First call is not startSketchOn')
const secondCall = init.body[1]
if (!isCallExprWithName(secondCall, 'startProfileAt'))
return new Error('Second call is not startProfileAt')
const varName = varDec.node.declaration.id.name
const newVarName = findUniqueName(_ast, 'sketch')
const secondCallArgs = structuredClone(secondCall.arguments)
secondCallArgs[1] = createIdentifier(newVarName)
const firstCallOfNewPipe = createCallExpression(
'startProfileAt',
secondCallArgs
)
const newSketch = createVariableDeclaration(
newVarName,
varDec.node.declaration.init.body[0]
)
const newProfile = createVariableDeclaration(
varName,
varDec.node.declaration.init.body.length <= 2
? firstCallOfNewPipe
: createPipeExpression([
firstCallOfNewPipe,
...varDec.node.declaration.init.body.slice(2),
])
)
const index = getBodyIndex(pathToPipe)
if (err(index)) return index
_ast.body.splice(index, 1, newSketch, newProfile)
const pathToPlane = structuredClone(pathToPipe)
const pathToProfile = structuredClone(pathToPipe)
pathToProfile[1][0] = index + 1
return {
modifiedAst: _ast,
pathToProfile,
pathToPlane,
}
}
export function createNodeFromExprSnippet(
strings: TemplateStringsArray,
...expressions: any[]
): Node<BodyItem> | Error {
const code = strings.reduce(
(acc, str, i) => acc + str + (expressions[i] || ''),
''
)
let program = parse(code)
if (err(program)) return program
const node = program.program?.body[0]
if (!node) return new Error('No node found')
return node
}
export const createLabeledArg = (label: string, arg: Expr): LabeledArg => {
return { label: createIdentifier(label), arg, type: 'LabeledArg' }
}

View File

@ -298,7 +298,7 @@ export function getPathToExtrudeForSegmentSelection(
const sketchVar = varDecNode.node.declaration.id.name
const sketch = sketchFromKclValue(
dependencies.kclManager.programMemory.get(sketchVar),
dependencies.kclManager.variables[sketchVar],
sketchVar
)
if (trap(sketch)) return sketch

View File

@ -5,9 +5,9 @@ import {
PathToNode,
Expr,
CallExpression,
PipeExpression,
VariableDeclarator,
CallExpressionKw,
ArtifactGraph,
} from 'lang/wasm'
import { Selections } from 'lib/selections'
import { Node } from 'wasm-lib/kcl/bindings/Node'
@ -16,7 +16,6 @@ import {
createCallExpressionStdLib,
createObjectExpression,
createIdentifier,
createPipeExpression,
findUniqueName,
createVariableDeclaration,
} from 'lang/modifyAst'
@ -26,14 +25,18 @@ import {
mutateAstWithTagForSketchSegment,
getEdgeTagCall,
} from 'lang/modifyAst/addEdgeTreatment'
import { Artifact, getPathsFromArtifact } from 'lang/std/artifactGraph'
import { kclManager } from 'lib/singletons'
export function revolveSketch(
ast: Node<Program>,
pathToSketchNode: PathToNode,
shouldPipe = false,
angle: Expr = createLiteral(4),
axisOrEdge: string,
axis: string,
edge: Selections
edge: Selections,
artifactGraph: ArtifactGraph,
artifact?: Artifact
):
| {
modifiedAst: Node<Program>
@ -41,6 +44,13 @@ export function revolveSketch(
pathToRevolveArg: PathToNode
}
| Error {
const orderedSketchNodePaths = getPathsFromArtifact({
artifact: artifact,
sketchPathToNode: pathToSketchNode,
artifactGraph,
ast: kclManager.ast,
})
if (err(orderedSketchNodePaths)) return orderedSketchNodePaths
const clonedAst = structuredClone(ast)
const sketchNode = getNodeFromPath(clonedAst, pathToSketchNode)
if (err(sketchNode)) return sketchNode
@ -82,29 +92,13 @@ export function revolveSketch(
generatedAxis = createLiteral(axis)
}
/* Original Code */
const { node: sketchExpression } = sketchNode
// determine if sketchExpression is in a pipeExpression or not
const sketchPipeExpressionNode = getNodeFromPath<PipeExpression>(
clonedAst,
pathToSketchNode,
'PipeExpression'
)
if (err(sketchPipeExpressionNode)) return sketchPipeExpressionNode
const { node: sketchPipeExpression } = sketchPipeExpressionNode
const isInPipeExpression = sketchPipeExpression.type === 'PipeExpression'
const sketchVariableDeclaratorNode = getNodeFromPath<VariableDeclarator>(
clonedAst,
pathToSketchNode,
'VariableDeclarator'
)
if (err(sketchVariableDeclaratorNode)) return sketchVariableDeclaratorNode
const {
node: sketchVariableDeclarator,
shallowPath: sketchPathToDecleration,
} = sketchVariableDeclaratorNode
const { node: sketchVariableDeclarator } = sketchVariableDeclaratorNode
if (!generatedAxis) return new Error('Generated axis selection is missing.')
@ -116,41 +110,16 @@ export function revolveSketch(
createIdentifier(sketchVariableDeclarator.id.name),
])
if (shouldPipe) {
const pipeChain = createPipeExpression(
isInPipeExpression
? [...sketchPipeExpression.body, revolveCall]
: [sketchExpression as any, revolveCall]
)
sketchVariableDeclarator.init = pipeChain
const pathToRevolveArg: PathToNode = [
...sketchPathToDecleration,
['init', 'VariableDeclarator'],
['body', ''],
[pipeChain.body.length - 1, 'index'],
['arguments', 'CallExpression'],
[0, 'index'],
]
return {
modifiedAst: clonedAst,
pathToSketchNode,
pathToRevolveArg,
}
}
// We're not creating a pipe expression,
// but rather a separate constant for the extrusion
const name = findUniqueName(clonedAst, KCL_DEFAULT_CONSTANT_PREFIXES.REVOLVE)
const VariableDeclaration = createVariableDeclaration(name, revolveCall)
const sketchIndexInPathToNode =
sketchPathToDecleration.findIndex((a) => a[0] === 'body') + 1
const sketchIndexInBody = sketchPathToDecleration[sketchIndexInPathToNode][0]
let insertIndex = sketchIndexInBody
if (typeof insertIndex !== 'number')
return new Error('expected insertIndex to be a number')
const lastSketchNodePath =
orderedSketchNodePaths[orderedSketchNodePaths.length - 1]
let sketchIndexInBody = Number(lastSketchNodePath[1][0])
if (typeof sketchIndexInBody !== 'number') {
return new Error('expected sketchIndexInBody to be a number')
}
// If an axis was selected in KCL, find the max index to insert the revolve command
if (axisDeclaration) {
@ -161,14 +130,14 @@ export function revolveSketch(
if (typeof axisIndex !== 'number')
return new Error('expected axisIndex to be a number')
insertIndex = Math.max(insertIndex, axisIndex)
sketchIndexInBody = Math.max(sketchIndexInBody, axisIndex)
}
clonedAst.body.splice(insertIndex + 1, 0, VariableDeclaration)
clonedAst.body.splice(sketchIndexInBody + 1, 0, VariableDeclaration)
const pathToRevolveArg: PathToNode = [
['body', ''],
[insertIndex + 1, 'index'],
[sketchIndexInBody + 1, 'index'],
['declaration', 'VariableDeclaration'],
['init', 'VariableDeclarator'],
['arguments', 'CallExpression'],

View File

@ -9,7 +9,6 @@ import {
CallExpression,
VariableDeclarator,
} from './wasm'
import { ProgramMemory } from 'lang/wasm'
import {
findAllPreviousVariables,
isNodeSafeToReplace,
@ -63,7 +62,7 @@ variableBelowShouldNotBeIncluded = 3
const { variables, bodyPath, insertIndex } = findAllPreviousVariables(
ast,
execState.memory,
execState.variables,
topLevelRange(rangeStart, rangeStart)
)
expect(variables).toEqual([
@ -398,7 +397,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(true)
})
@ -418,7 +417,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(100, 101), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(true)
})
@ -432,7 +431,7 @@ part001 = startSketchAt([-1.41, 3.46])
selection: {
codeRef: codeRefFromRange(topLevelRange(10, 11), ast),
},
programMemory: execState.memory,
memVars: execState.variables,
})
expect(result).toEqual(false)
})
@ -722,7 +721,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCallExpressionsToPipe({
node: ast,
programMemory: ProgramMemory.empty(),
variables: {},
pathToNode: sketchPathToNode,
expressions: [
createCallExpressionStdLib(
@ -777,7 +776,7 @@ describe('Testing specific sketch getNodeFromPath workflow', () => {
const sketchPathToNode = getNodePathFromSourceRange(ast, sketchRange)
const modifiedAst = addCloseToPipe({
node: ast,
programMemory: ProgramMemory.empty(),
variables: {},
pathToNode: sketchPathToNode,
})

View File

@ -2,7 +2,6 @@ import { ToolTip } from 'lang/langHelpers'
import { Selection, Selections } from 'lib/selections'
import {
ArrayExpression,
ArtifactGraph,
BinaryExpression,
CallExpression,
CallExpressionKw,
@ -13,7 +12,6 @@ import {
PathToNode,
PipeExpression,
Program,
ProgramMemory,
ReturnStatement,
sketchFromKclValue,
sketchFromKclValueOptional,
@ -23,9 +21,11 @@ import {
VariableDeclaration,
VariableDeclarator,
recast,
ArtifactGraph,
kclSettings,
unitLenToUnitLength,
unitAngToUnitAngle,
VariableMap,
} from './wasm'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { createIdentifier, splitPathAtLastIndex } from './modifyAst'
@ -37,10 +37,11 @@ import {
getConstraintType,
} from './std/sketchcombos'
import { err, Reason } from 'lib/trap'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { findKwArg } from './util'
import { codeRefFromRange } from './std/artifactGraph'
import { FunctionExpression } from 'wasm-lib/kcl/bindings/FunctionExpression'
import { ImportStatement } from 'wasm-lib/kcl/bindings/ImportStatement'
import { KclSettingsAnnotation } from 'lib/settings/settingsTypes'
export const LABELED_ARG_FIELD = 'LabeledArg -> Arg'
@ -287,7 +288,7 @@ export interface PrevVariable<T> {
export function findAllPreviousVariablesPath(
ast: Program,
programMemory: ProgramMemory,
memVars: VariableMap,
path: PathToNode,
type: 'number' | 'string' = 'number'
): {
@ -325,7 +326,7 @@ export function findAllPreviousVariablesPath(
bodyItems?.forEach?.((item) => {
if (item.type !== 'VariableDeclaration' || item.end > startRange) return
const varName = item.declaration.id.name
const varValue = programMemory?.get(varName)
const varValue = memVars[varName]
if (!varValue || typeof varValue?.value !== type) return
variables.push({
key: varName,
@ -342,7 +343,7 @@ export function findAllPreviousVariablesPath(
export function findAllPreviousVariables(
ast: Program,
programMemory: ProgramMemory,
memVars: VariableMap,
sourceRange: SourceRange,
type: 'number' | 'string' = 'number'
): {
@ -351,13 +352,19 @@ export function findAllPreviousVariables(
insertIndex: number
} {
const path = getNodePathFromSourceRange(ast, sourceRange)
return findAllPreviousVariablesPath(ast, programMemory, path, type)
return findAllPreviousVariablesPath(ast, memVars, path, type)
}
type ReplacerFn = (
_ast: Node<Program>,
varName: string
) => { modifiedAst: Node<Program>; pathToReplaced: PathToNode } | Error
) =>
| {
modifiedAst: Node<Program>
pathToReplaced: PathToNode
exprInsertIndex: number
}
| Error
export function isNodeSafeToReplacePath(
ast: Program,
@ -409,7 +416,7 @@ export function isNodeSafeToReplacePath(
if (err(_nodeToReplace)) return _nodeToReplace
const nodeToReplace = _nodeToReplace.node as any
nodeToReplace[last[0]] = identifier
return { modifiedAst: _ast, pathToReplaced }
return { modifiedAst: _ast, pathToReplaced, exprInsertIndex: index }
}
const hasPipeSub = isTypeInValue(finVal as Expr, 'PipeSubstitution')
@ -479,7 +486,7 @@ function isTypeInArrayExp(
export function isLinesParallelAndConstrained(
ast: Program,
artifactGraph: ArtifactGraph,
programMemory: ProgramMemory,
memVars: VariableMap,
primaryLine: Selection,
secondaryLine: Selection
):
@ -509,7 +516,7 @@ export function isLinesParallelAndConstrained(
if (err(_varDec)) return _varDec
const varDec = _varDec.node
const varName = (varDec as VariableDeclaration)?.declaration.id?.name
const sg = sketchFromKclValue(programMemory?.get(varName), varName)
const sg = sketchFromKclValue(memVars[varName], varName)
if (err(sg)) return sg
const _primarySegment = getSketchSegmentFromSourceRange(
sg,
@ -518,8 +525,15 @@ export function isLinesParallelAndConstrained(
if (err(_primarySegment)) return _primarySegment
const primarySegment = _primarySegment.segment
const _varDec2 = getNodeFromPath(ast, secondaryPath, 'VariableDeclaration')
if (err(_varDec2)) return _varDec2
const varDec2 = _varDec2.node
const varName2 = (varDec2 as VariableDeclaration)?.declaration.id?.name
const sg2 = sketchFromKclValue(memVars[varName2], varName2)
if (err(sg2)) return sg2
const _segment = getSketchSegmentFromSourceRange(
sg,
sg2,
secondaryLine?.codeRef?.range
)
if (err(_segment)) return _segment
@ -589,11 +603,11 @@ export function isLinesParallelAndConstrained(
export function hasExtrudeSketch({
ast,
selection,
programMemory,
memVars,
}: {
ast: Program
selection: Selection
programMemory: ProgramMemory
memVars: VariableMap
}): boolean {
const varDecMeta = getNodeFromPath<VariableDeclaration>(
ast,
@ -607,7 +621,7 @@ export function hasExtrudeSketch({
const varDec = varDecMeta.node
if (varDec.type !== 'VariableDeclaration') return false
const varName = varDec.declaration.id.name
const varValue = programMemory?.get(varName)
const varValue = memVars[varName]
return (
varValue?.type === 'Solid' ||
!(sketchFromKclValueOptional(varValue, varName) instanceof Reason)
@ -871,6 +885,59 @@ export function getObjExprProperty(
return { expr: node.properties[index].value, index }
}
export function isCursorInFunctionDefinition(
ast: Node<Program>,
selectionRanges: Selection
): boolean {
if (!selectionRanges?.codeRef?.pathToNode) return false
const node = getNodeFromPath<FunctionExpression>(
ast,
selectionRanges.codeRef.pathToNode,
'FunctionExpression'
)
if (err(node)) return false
if (node.node.type === 'FunctionExpression') return true
return false
}
export function getBodyIndex(pathToNode: PathToNode): number | Error {
const index = Number(pathToNode[1][0])
if (Number.isInteger(index)) return index
return new Error('Expected number index')
}
export function isCallExprWithName(
expr: Expr | CallExpression,
name: string
): expr is CallExpression {
if (expr.type === 'CallExpression' && expr.callee.type === 'Identifier') {
return expr.callee.name === name
}
return false
}
export function doesSketchPipeNeedSplitting(
ast: Node<Program>,
pathToPipe: PathToNode
): boolean | Error {
const varDec = getNodeFromPath<VariableDeclarator>(
ast,
pathToPipe,
'VariableDeclarator'
)
if (err(varDec)) return varDec
if (varDec.node.type !== 'VariableDeclarator') return new Error('Not a var')
const pipeExpression = varDec.node.init
if (pipeExpression.type !== 'PipeExpression') return false
const [firstPipe, secondPipe] = pipeExpression.body
if (!firstPipe || !secondPipe) return false
if (
isCallExprWithName(firstPipe, 'startSketchOn') &&
isCallExprWithName(secondPipe, 'startProfileAt')
)
return true
return false
}
/**
* Given KCL, returns the settings annotation object if it exists.
*/

View File

@ -82,6 +82,7 @@ function moreNodePathFromSourceRange(
return moreNodePathFromSourceRange(arg, sourceRange, path)
}
}
return path
}
return path
}

View File

@ -1,4 +1,5 @@
import {
Expr,
Artifact,
ArtifactGraph,
ArtifactId,
@ -18,7 +19,8 @@ import {
import { Models } from '@kittycad/lib'
import { getNodePathFromSourceRange } from 'lang/queryAstNodePathUtils'
import { err } from 'lib/trap'
import { codeManager } from 'lib/singletons'
import { Cap, Plane, Wall } from 'wasm-lib/kcl/bindings/Artifact'
import { CapSubType } from 'wasm-lib/kcl/bindings/Artifact'
export type { Artifact, ArtifactId, SegmentArtifact } from 'lang/wasm'
@ -37,10 +39,28 @@ export interface PlaneArtifactRich extends BaseArtifact {
codeRef: CodeRef
}
export interface CapArtifactRich extends BaseArtifact {
type: 'cap'
subType: CapSubType
faceCodeRef: CodeRef
edgeCuts: Array<EdgeCut>
paths: Array<PathArtifact>
sweep?: SweepArtifact
}
export interface WallArtifactRich extends BaseArtifact {
type: 'wall'
id: ArtifactId
segment: PathArtifact
edgeCuts: Array<EdgeCut>
sweep: SweepArtifact
paths: Array<PathArtifact>
faceCodeRef: CodeRef
}
export interface PathArtifactRich extends BaseArtifact {
type: 'path'
/** A path must always lie on a plane */
plane: PlaneArtifact | WallArtifact
plane: PlaneArtifact | WallArtifact | CapArtifact
/** A path must always contain 0 or more segments */
segments: Array<SegmentArtifact>
/** A path may not result in a sweep artifact */
@ -51,7 +71,7 @@ export interface PathArtifactRich extends BaseArtifact {
interface SegmentArtifactRich extends BaseArtifact {
type: 'segment'
path: PathArtifact
surf?: WallArtifact
surf: WallArtifact
edges: Array<SweepEdge>
edgeCut?: EdgeCut
codeRef: CodeRef
@ -151,6 +171,73 @@ export function expandPlane(
}
}
export function expandWall(
wall: WallArtifact,
artifactGraph: ArtifactGraph
): WallArtifactRich {
const { pathIds, sweepId: _s, edgeCutEdgeIds, ...keptProperties } = wall
const paths = pathIds?.length
? Array.from(
getArtifactsOfTypes(
{ keys: wall.pathIds, types: ['path'] },
artifactGraph
).values()
)
: []
const sweep = artifactGraph.get(wall.sweepId) as SweepArtifact
const edgeCuts = edgeCutEdgeIds?.length
? Array.from(
getArtifactsOfTypes(
{ keys: wall.edgeCutEdgeIds, types: ['edgeCut'] },
artifactGraph
).values()
)
: []
const segment = artifactGraph.get(wall.segId) as PathArtifact
return {
type: 'wall',
...keptProperties,
paths,
sweep,
segment,
edgeCuts,
}
}
export function expandCap(
cap: CapArtifact,
artifactGraph: ArtifactGraph
): CapArtifactRich {
const { pathIds, sweepId: _s, edgeCutEdgeIds, ...keptProperties } = cap
const paths = pathIds?.length
? Array.from(
getArtifactsOfTypes(
{ keys: cap.pathIds, types: ['path'] },
artifactGraph
).values()
)
: []
const maybeSweep = getArtifactOfTypes(
{ key: cap.sweepId, types: ['sweep'] },
artifactGraph
)
const sweep = err(maybeSweep) ? undefined : maybeSweep
const edgeCuts = edgeCutEdgeIds?.length
? Array.from(
getArtifactsOfTypes(
{ keys: cap.edgeCutEdgeIds, types: ['edgeCut'] },
artifactGraph
).values()
)
: []
return {
type: 'cap',
...keptProperties,
paths,
sweep,
edgeCuts,
}
}
export function expandPath(
path: PathArtifact,
artifactGraph: ArtifactGraph
@ -239,6 +326,7 @@ export function expandSegment(
if (err(path)) return path
if (err(surf)) return surf
if (err(edgeCut)) return edgeCut
if (!surf) return new Error('Segment does not have a surface')
return {
type: 'segment',
@ -410,6 +498,186 @@ export function codeRefFromRange(range: SourceRange, ast: Program): CodeRef {
}
}
function getPlaneFromPath(
path: PathArtifact,
graph: ArtifactGraph
): PlaneArtifact | WallArtifact | CapArtifact | Error {
const plane = getArtifactOfTypes(
{ key: path.planeId, types: ['plane', 'wall', 'cap'] },
graph
)
if (err(plane)) return plane
return plane
}
function getPlaneFromSegment(
segment: SegmentArtifact,
graph: ArtifactGraph
): PlaneArtifact | WallArtifact | CapArtifact | Error {
const path = getArtifactOfTypes(
{ key: segment.pathId, types: ['path'] },
graph
)
if (err(path)) return path
return getPlaneFromPath(path, graph)
}
function getPlaneFromSolid2D(
solid2D: Solid2D,
graph: ArtifactGraph
): PlaneArtifact | WallArtifact | CapArtifact | Error {
const path = getArtifactOfTypes(
{ key: solid2D.pathId, types: ['path'] },
graph
)
if (err(path)) return path
return getPlaneFromPath(path, graph)
}
function getPlaneFromSweepEdge(edge: SweepEdge, graph: ArtifactGraph) {
const sweep = getArtifactOfTypes(
{ key: edge.sweepId, types: ['sweep'] },
graph
)
if (err(sweep)) return sweep
const path = getArtifactOfTypes({ key: sweep.pathId, types: ['path'] }, graph)
if (err(path)) return path
return getPlaneFromPath(path, graph)
}
export function getPlaneFromArtifact(
artifact: Artifact | undefined,
graph: ArtifactGraph
): PlaneArtifact | WallArtifact | CapArtifact | Error {
if (!artifact) return new Error(`Artifact is undefined`)
if (artifact.type === 'plane') return artifact
if (artifact.type === 'path') return getPlaneFromPath(artifact, graph)
if (artifact.type === 'segment') return getPlaneFromSegment(artifact, graph)
if (artifact.type === 'solid2d') return getPlaneFromSolid2D(artifact, graph)
if (artifact.type === 'wall' || artifact.type === 'cap') return artifact
if (artifact.type === 'sweepEdge')
return getPlaneFromSweepEdge(artifact, graph)
return new Error(`Artifact type ${artifact.type} does not have a plane`)
}
const onlyConsecutivePaths = (
orderedNodePaths: PathToNode[],
originalPath: PathToNode,
ast: Program
): PathToNode[] => {
const isExprSafe = (index: number, ast: Program): boolean => {
// we allow expressions between profiles, but only basic math expressions 5 + 6 etc
// because 5 + doSomeMath() might be okay, but we can't know if it's an abstraction on a stdlib
// call that involves a engine call, and we can't have that in sketch-mode/mock-execution
const expr = ast.body?.[index]
if (!expr) {
return false
}
if (expr.type === 'ImportStatement' || expr.type === 'ReturnStatement') {
return false
}
if (expr.type === 'VariableDeclaration') {
const init = expr.declaration?.init
if (!init) return false
if (init.type === 'CallExpression') {
return false
}
if (init.type === 'BinaryExpression' && isNodeSafe(init)) {
return true
}
if (init.type === 'Literal' || init.type === 'MemberExpression') {
return true
}
}
return false
}
const originalIndex = Number(
orderedNodePaths.find(
(path) => path[1][0] === originalPath[1][0]
)?.[1]?.[0] || 0
)
const minIndex = Number(orderedNodePaths[0][1][0])
const maxIndex = Number(orderedNodePaths[orderedNodePaths.length - 1][1][0])
const pathIndexMap: any = {}
orderedNodePaths.forEach((path) => {
const bodyIndex = Number(path[1][0])
pathIndexMap[bodyIndex] = path
})
const safePaths: PathToNode[] = []
// traverse expressions in either direction from the profile selected
// when the user entered sketch mode
for (let i = originalIndex; i <= maxIndex; i++) {
if (pathIndexMap[i]) {
safePaths.push(pathIndexMap[i])
} else if (!isExprSafe(i, ast)) {
break
}
}
for (let i = originalIndex - 1; i >= minIndex; i--) {
if (pathIndexMap[i]) {
safePaths.unshift(pathIndexMap[i])
} else if (!isExprSafe(i, ast)) {
break
}
}
return safePaths
}
export function getPathsFromPlaneArtifact(
planeArtifact: PlaneArtifact,
artifactGraph: ArtifactGraph,
ast: Program
): PathToNode[] {
const nodePaths: PathToNode[] = []
for (const pathId of planeArtifact.pathIds) {
const path = artifactGraph.get(pathId)
if (!path) continue
if ('codeRef' in path && path.codeRef) {
// TODO should figure out why upstream the path is bad
const isNodePathBad = path.codeRef.pathToNode.length < 2
nodePaths.push(
isNodePathBad
? getNodePathFromSourceRange(ast, path.codeRef.range)
: path.codeRef.pathToNode
)
}
}
return onlyConsecutivePaths(nodePaths, nodePaths[0], ast)
}
export function getPathsFromArtifact({
sketchPathToNode,
artifact,
artifactGraph,
ast,
}: {
sketchPathToNode: PathToNode
artifact?: Artifact
artifactGraph: ArtifactGraph
ast: Program
}): PathToNode[] | Error {
const plane = getPlaneFromArtifact(artifact, artifactGraph)
if (err(plane)) return plane
const paths = getArtifactsOfTypes(
{ keys: plane.pathIds, types: ['path'] },
artifactGraph
)
let nodePaths = [...paths.values()]
.map((path) => path.codeRef.pathToNode)
.sort((a, b) => Number(a[1][0]) - Number(b[1][0]))
return onlyConsecutivePaths(nodePaths, sketchPathToNode, ast)
}
function isNodeSafe(node: Expr): boolean {
if (node.type === 'Literal' || node.type === 'MemberExpression') {
return true
}
if (node.type === 'BinaryExpression') {
return isNodeSafe(node.left) && isNodeSafe(node.right)
}
return false
}
/**
* Get an artifact from a code source range
*/
@ -418,12 +686,24 @@ export function getArtifactFromRange(
artifactGraph: ArtifactGraph
): Artifact | null {
for (const artifact of artifactGraph.values()) {
if ('codeRef' in artifact) {
const codeRef = getFaceCodeRef(artifact)
if (codeRef) {
const match =
artifact.codeRef?.range[0] === range[0] &&
artifact.codeRef.range[1] === range[1]
codeRef?.range[0] === range[0] && codeRef.range[1] === range[1]
if (match) return artifact
}
}
return null
}
export function getFaceCodeRef(
artifact: Artifact | Plane | Wall | Cap
): CodeRef | null {
if ('faceCodeRef' in artifact) {
return artifact.faceCodeRef
}
if ('codeRef' in artifact) {
return artifact.codeRef
}
return null
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

After

Width:  |  Height:  |  Size: 560 KiB

View File

@ -124,7 +124,7 @@ describe('testing changeSketchArguments', () => {
const sourceStart = code.indexOf(lineToChange)
const changeSketchArgsRetVal = changeSketchArguments(
ast,
execState.memory,
execState.variables,
{
type: 'sourceRange',
sourceRange: topLevelRange(
@ -160,7 +160,7 @@ mySketch001 = startSketchOn('XY')
expect(sourceStart).toBe(89)
const newSketchLnRetVal = addNewSketchLn({
node: ast,
programMemory: execState.memory,
variables: execState.variables,
input: {
type: 'straight-segment',
from: [0, 0],
@ -190,7 +190,7 @@ mySketch001 = startSketchOn('XY')
const modifiedAst2 = addCloseToPipe({
node: ast,
programMemory: execState.memory,
variables: execState.variables,
pathToNode: [
['body', ''],
[0, 'index'],

View File

@ -1,5 +1,4 @@
import {
ProgramMemory,
Path,
Sketch,
SourceRange,
@ -14,6 +13,7 @@ import {
Identifier,
sketchFromKclValue,
topLevelRange,
VariableMap,
} from 'lang/wasm'
import {
getNodeFromPath,
@ -66,7 +66,12 @@ import { perpendicularDistance } from 'sketch-helpers'
import { TagDeclarator } from 'wasm-lib/kcl/bindings/TagDeclarator'
import { EdgeCutInfo } from 'machines/modelingMachine'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { findKwArg, findKwArgAny, findKwArgAnyIndex } from 'lang/util'
import {
findKwArg,
findKwArgWithIndex,
findKwArgAny,
findKwArgAnyIndex,
} from 'lang/util'
export const ARG_TAG = 'tag'
export const ARG_END = 'end'
@ -76,6 +81,9 @@ const STRAIGHT_SEGMENT_ERR = new Error(
'Invalid input, expected "straight-segment"'
)
const ARC_SEGMENT_ERR = new Error('Invalid input, expected "arc-segment"')
const CIRCLE_THREE_POINT_SEGMENT_ERR = new Error(
'Invalid input, expected "circle-three-point-segment"'
)
export type Coords2d = [number, number]
@ -171,7 +179,8 @@ const commonConstraintInfoHelper = (
}
],
code: string,
pathToNode: PathToNode
pathToNode: PathToNode,
filterValue?: string
) => {
if (callExp.type !== 'CallExpression' && callExp.type !== 'CallExpressionKw')
return []
@ -295,7 +304,8 @@ const horzVertConstraintInfoHelper = (
stdLibFnName: ConstrainInfo['stdLibFnName'],
abbreviatedInput: AbbreviatedInput,
code: string,
pathToNode: PathToNode
pathToNode: PathToNode,
filterValue?: string
) => {
if (callExp.type !== 'CallExpression') return []
const firstArg = callExp.arguments?.[0]
@ -355,7 +365,7 @@ function getTagKwArg(): SketchLineHelperKw['getTag'] {
export const line: SketchLineHelperKw = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -494,7 +504,7 @@ export const line: SketchLineHelperKw = {
export const lineTo: SketchLineHelperKw = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -502,13 +512,14 @@ export const lineTo: SketchLineHelperKw = {
}) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const to = segmentInput.to
const _node = { ...node }
const _node = structuredClone(node)
const nodeMeta = getNodeFromPath<PipeExpression | CallExpressionKw>(
_node,
pathToNode,
'PipeExpression'
)
if (err(nodeMeta)) return nodeMeta
const { node: pipe } = nodeMeta
const nodeMeta2 = getNodeFromPath<VariableDeclarator>(
_node,
@ -783,11 +794,11 @@ export const xLine: SketchLineHelper = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node }
const _node = structuredClone(node)
const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression')
if (err(_node1)) return _node1
const { node: pipe } = _node1
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
if (err(varDec)) return varDec
const dec = varDec.node.declaration
const newVal = createLiteral(roundOff(to[0] - from[0], 2))
@ -802,7 +813,11 @@ export const xLine: SketchLineHelper = {
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp
if (dec.init.type === 'PipeExpression') {
dec.init.body[callIndex] = callExp
} else {
dec.init = callExp
}
return {
modifiedAst: _node,
pathToNode,
@ -814,7 +829,11 @@ export const xLine: SketchLineHelper = {
newVal,
createPipeSubstitution(),
])
pipe.body = [...pipe.body, newLine]
if (dec.init.type === 'PipeExpression') {
dec.init.body = [...dec.init.body, newLine]
} else {
dec.init = createPipeExpression([dec.init, newLine])
}
return { modifiedAst: _node, pathToNode }
},
updateArgs: ({ node, pathToNode, input }) => {
@ -851,11 +870,11 @@ export const yLine: SketchLineHelper = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { from, to } = segmentInput
const _node = { ...node }
const _node = structuredClone(node)
const getNode = getNodeFromPathCurry(_node, pathToNode)
const _node1 = getNode<PipeExpression>('PipeExpression')
if (err(_node1)) return _node1
const { node: pipe } = _node1
const varDec = getNode<VariableDeclaration>('VariableDeclaration')
if (err(varDec)) return varDec
const dec = varDec.node.declaration
const newVal = createLiteral(roundOff(to[1] - from[1], 2))
if (replaceExistingCallback) {
const { index: callIndex } = splitPathAtPipeExpression(pathToNode)
@ -868,7 +887,11 @@ export const yLine: SketchLineHelper = {
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
pipe.body[callIndex] = callExp
if (dec.init.type === 'PipeExpression') {
dec.init.body[callIndex] = callExp
} else {
dec.init = callExp
}
return {
modifiedAst: _node,
pathToNode,
@ -880,7 +903,11 @@ export const yLine: SketchLineHelper = {
newVal,
createPipeSubstitution(),
])
pipe.body = [...pipe.body, newLine]
if (dec.init.type === 'PipeExpression') {
dec.init.body = [...dec.init.body, newLine]
} else {
dec.init = createPipeExpression([dec.init, newLine])
}
return { modifiedAst: _node, pathToNode }
},
updateArgs: ({ node, pathToNode, input }) => {
@ -1220,6 +1247,295 @@ export const circle: SketchLineHelper = {
]
},
}
export const circleThreePoint: SketchLineHelperKw = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'circle-three-point-segment') {
return CIRCLE_THREE_POINT_SEGMENT_ERR
}
const { p1, p2, p3 } = segmentInput
const _node = structuredClone(node)
const nodeMeta = getNodeFromPath<VariableDeclaration>(
_node,
pathToNode,
'VariableDeclaration'
)
if (err(nodeMeta)) return nodeMeta
const { node: varDec } = nodeMeta
const createRoundedLiteral = (val: number) =>
createLiteral(roundOff(val, 2))
if (replaceExistingCallback) {
const result = replaceExistingCallback([
{
type: 'arrayInObject',
index: 0,
key: 'p1',
argType: 'xAbsolute',
expr: createRoundedLiteral(p1[0]),
},
{
type: 'arrayInObject',
index: 1,
key: 'p1',
argType: 'yAbsolute',
expr: createRoundedLiteral(p1[1]),
},
{
type: 'arrayInObject',
index: 0,
key: 'p2',
argType: 'xAbsolute',
expr: createRoundedLiteral(p2[0]),
},
{
type: 'arrayInObject',
index: 1,
key: 'p2',
argType: 'yAbsolute',
expr: createRoundedLiteral(p2[1]),
},
{
type: 'arrayInObject',
index: 0,
key: 'p3',
argType: 'xAbsolute',
expr: createRoundedLiteral(p3[0]),
},
{
type: 'arrayInObject',
index: 1,
key: 'p3',
argType: 'yAbsolute',
expr: createRoundedLiteral(p3[1]),
},
])
if (err(result)) return result
const { callExp, valueUsedInTransform } = result
varDec.declaration.init = callExp
return {
modifiedAst: _node,
pathToNode,
valueUsedInTransform,
}
}
return new Error('replaceExistingCallback is missing')
},
updateArgs: ({ node, pathToNode, input }) => {
if (input.type !== 'circle-three-point-segment') {
return CIRCLE_THREE_POINT_SEGMENT_ERR
}
const { p1, p2, p3 } = input
const _node = { ...node }
const nodeMeta = getNodeFromPath<CallExpressionKw>(_node, pathToNode)
if (err(nodeMeta)) return nodeMeta
const { node: callExpression, shallowPath } = nodeMeta
const createRounded2DPointArr = (point: [number, number]) =>
createArrayExpression([
createLiteral(roundOff(point[0], 2)),
createLiteral(roundOff(point[1], 2)),
])
const newP1 = createRounded2DPointArr(p1)
const newP2 = createRounded2DPointArr(p2)
const newP3 = createRounded2DPointArr(p3)
mutateKwArg('p1', callExpression, newP1)
mutateKwArg('p2', callExpression, newP2)
mutateKwArg('p3', callExpression, newP3)
return {
modifiedAst: _node,
pathToNode: shallowPath,
}
},
getTag: getTagKwArg(),
addTag: addTagKw(),
getConstraintInfo: (callExp, code, pathToNode, filterValue) => {
if (callExp.type !== 'CallExpressionKw') return []
const p1Details = findKwArgWithIndex('p1', callExp)
const p2Details = findKwArgWithIndex('p2', callExp)
const p3Details = findKwArgWithIndex('p3', callExp)
if (!p1Details || !p2Details || !p3Details) return []
if (
p1Details.expr.type !== 'ArrayExpression' ||
p2Details.expr.type !== 'ArrayExpression' ||
p3Details.expr.type !== 'ArrayExpression'
)
return []
const pathToP1ArrayExpression: PathToNode = [
...pathToNode,
['arguments', 'CallExpressionKw'],
[p1Details.argIndex, 'arg index'],
['arg', 'labeledArg -> Arg'],
['elements', 'ArrayExpression'],
]
const pathToP2ArrayExpression: PathToNode = [
...pathToNode,
['arguments', 'CallExpressionKw'],
[p2Details.argIndex, 'arg index'],
['arg', 'labeledArg -> Arg'],
['elements', 'ArrayExpression'],
]
const pathToP3ArrayExpression: PathToNode = [
...pathToNode,
['arguments', 'CallExpressionKw'],
[p3Details.argIndex, 'arg index'],
['arg', 'labeledArg -> Arg'],
['elements', 'ArrayExpression'],
]
const pathToP1XArg: PathToNode = [...pathToP1ArrayExpression, [0, 'index']]
const pathToP1YArg: PathToNode = [...pathToP1ArrayExpression, [1, 'index']]
const pathToP2XArg: PathToNode = [...pathToP2ArrayExpression, [0, 'index']]
const pathToP2YArg: PathToNode = [...pathToP2ArrayExpression, [1, 'index']]
const pathToP3XArg: PathToNode = [...pathToP3ArrayExpression, [0, 'index']]
const pathToP3YArg: PathToNode = [...pathToP3ArrayExpression, [1, 'index']]
const constraints: (ConstrainInfo & { filterValue: string })[] = [
{
stdLibFnName: 'circleThreePoint',
type: 'xAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[0]),
sourceRange: [
p1Details.expr.elements[0].start,
p1Details.expr.elements[0].end,
0,
],
pathToNode: pathToP1XArg,
value: code.slice(
p1Details.expr.elements[0].start,
p1Details.expr.elements[0].end
),
argPosition: {
type: 'arrayInObject',
index: 0,
key: 'p1',
},
filterValue: 'p1',
},
{
stdLibFnName: 'circleThreePoint',
type: 'yAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p1Details.expr.elements[1]),
sourceRange: [
p1Details.expr.elements[1].start,
p1Details.expr.elements[1].end,
0,
],
pathToNode: pathToP1YArg,
value: code.slice(
p1Details.expr.elements[1].start,
p1Details.expr.elements[1].end
),
argPosition: {
type: 'arrayInObject',
index: 1,
key: 'p1',
},
filterValue: 'p1',
},
{
stdLibFnName: 'circleThreePoint',
type: 'xAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[0]),
sourceRange: [
p2Details.expr.elements[0].start,
p2Details.expr.elements[0].end,
0,
],
pathToNode: pathToP2XArg,
value: code.slice(
p2Details.expr.elements[0].start,
p2Details.expr.elements[0].end
),
argPosition: {
type: 'arrayInObject',
index: 0,
key: 'p2',
},
filterValue: 'p2',
},
{
stdLibFnName: 'circleThreePoint',
type: 'yAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p2Details.expr.elements[1]),
sourceRange: [
p2Details.expr.elements[1].start,
p2Details.expr.elements[1].end,
0,
],
pathToNode: pathToP2YArg,
value: code.slice(
p2Details.expr.elements[1].start,
p2Details.expr.elements[1].end
),
argPosition: {
type: 'arrayInObject',
index: 1,
key: 'p2',
},
filterValue: 'p2',
},
{
stdLibFnName: 'circleThreePoint',
type: 'xAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[0]),
sourceRange: [
p3Details.expr.elements[0].start,
p3Details.expr.elements[0].end,
0,
],
pathToNode: pathToP3XArg,
value: code.slice(
p3Details.expr.elements[0].start,
p3Details.expr.elements[0].end
),
argPosition: {
type: 'arrayInObject',
index: 0,
key: 'p3',
},
filterValue: 'p3',
},
{
stdLibFnName: 'circleThreePoint',
type: 'yAbsolute',
isConstrained: isNotLiteralArrayOrStatic(p3Details.expr.elements[1]),
sourceRange: [
p3Details.expr.elements[1].start,
p3Details.expr.elements[1].end,
0,
],
pathToNode: pathToP3YArg,
value: code.slice(
p3Details.expr.elements[1].start,
p3Details.expr.elements[1].end
),
argPosition: {
type: 'arrayInObject',
index: 1,
key: 'p3',
},
filterValue: 'p3',
},
]
const finalConstraints: ConstrainInfo[] = []
constraints.forEach((constraint) => {
if (!filterValue) {
finalConstraints.push(constraint)
}
if (filterValue && constraint.filterValue === filterValue) {
finalConstraints.push(constraint)
}
})
return finalConstraints
},
}
export const angledLine: SketchLineHelper = {
add: ({ node, pathToNode, segmentInput, replaceExistingCallback }) => {
if (segmentInput.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
@ -1313,7 +1629,7 @@ export const angledLine: SketchLineHelper = {
export const angledLineOfXLength: SketchLineHelper = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -1337,10 +1653,7 @@ export const angledLineOfXLength: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = sketchFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
const sketch = sketchFromKclValue(variables[variableName], variableName)
if (err(sketch)) {
return sketch
}
@ -1429,7 +1742,7 @@ export const angledLineOfXLength: SketchLineHelper = {
export const angledLineOfYLength: SketchLineHelper = {
add: ({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
replaceExistingCallback,
@ -1452,10 +1765,7 @@ export const angledLineOfYLength: SketchLineHelper = {
if (err(nodeMeta2)) return nodeMeta2
const { node: varDec } = nodeMeta2
const variableName = varDec.id.name
const sketch = sketchFromKclValue(
previousProgramMemory?.get(variableName),
variableName
)
const sketch = sketchFromKclValue(variables[variableName], variableName)
if (err(sketch)) return sketch
const angle = createLiteral(roundOff(getAngle(from, to), 0))
@ -1793,7 +2103,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
}
return new Error('not implemented')
},
updateArgs: ({ node, pathToNode, input, previousProgramMemory }) => {
updateArgs: ({ node, pathToNode, input, variables }) => {
if (input.type !== 'straight-segment') return STRAIGHT_SEGMENT_ERR
const { to, from } = input
const _node = { ...node }
@ -1820,10 +2130,7 @@ export const angledLineThatIntersects: SketchLineHelper = {
const { node: varDec } = nodeMeta2
const varName = varDec.declaration.id.name
const sketch = sketchFromKclValue(
previousProgramMemory.get(varName),
varName
)
const sketch = sketchFromKclValue(variables[varName], varName)
if (err(sketch)) return sketch
const intersectPath = sketch.paths.find(
({ tag }: Path) => tag && tag.value === intersectTagName
@ -1954,7 +2261,8 @@ export const updateStartProfileAtArgs: SketchLineHelper['updateArgs'] = ({
startNodes: [],
nonCodeNodes: [],
},
trivia: [],
innerAttrs: [],
outerAttrs: [],
},
pathToNode,
}
@ -1992,11 +2300,12 @@ export const sketchLineHelperMap: { [key: string]: SketchLineHelper } = {
export const sketchLineHelperMapKw: { [key: string]: SketchLineHelperKw } = {
line,
lineTo,
circleThreePoint,
} as const
export function changeSketchArguments(
node: Node<Program>,
programMemory: ProgramMemory,
variables: VariableMap,
sourceRangeOrPath:
| {
type: 'sourceRange'
@ -2030,7 +2339,7 @@ export function changeSketchArguments(
return updateArgs({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: shallowPath,
input,
})
@ -2047,7 +2356,7 @@ export function changeSketchArguments(
return updateArgs({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: shallowPath,
input,
})
@ -2059,30 +2368,36 @@ export function changeSketchArguments(
export function getConstraintInfo(
callExpression: Node<CallExpression>,
code: string,
pathToNode: PathToNode
pathToNode: PathToNode,
filterValue?: string
): ConstrainInfo[] {
const fnName = callExpression?.callee?.name || ''
if (!(fnName in sketchLineHelperMap)) return []
return sketchLineHelperMap[fnName].getConstraintInfo(
callExpression,
code,
pathToNode
pathToNode,
filterValue
)
}
export function getConstraintInfoKw(
callExpression: Node<CallExpressionKw>,
code: string,
pathToNode: PathToNode
pathToNode: PathToNode,
filterValue?: string
): ConstrainInfo[] {
const fnName = callExpression?.callee?.name || ''
const isAbsolute = findKwArg('endAbsolute', callExpression) !== undefined
const isAbsolute =
fnName === 'circleThreePoint' ||
findKwArg('endAbsolute', callExpression) !== undefined
if (!(fnName in sketchLineHelperMapKw)) return []
const correctFnName = fnName === 'line' && isAbsolute ? 'lineTo' : fnName
return sketchLineHelperMapKw[correctFnName].getConstraintInfo(
callExpression,
code,
pathToNode
pathToNode,
filterValue
)
}
@ -2112,7 +2427,7 @@ export function compareVec2Epsilon2(
interface CreateLineFnCallArgs {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
input: SegmentInputs
fnName: ToolTip
pathToNode: PathToNode
@ -2121,7 +2436,7 @@ interface CreateLineFnCallArgs {
export function addNewSketchLn({
node: _node,
programMemory: previousProgramMemory,
variables,
fnName,
pathToNode,
input: segmentInput,
@ -2151,7 +2466,7 @@ export function addNewSketchLn({
)
return add({
node,
previousProgramMemory,
variables,
pathToNode,
segmentInput,
spliceBetween,
@ -2164,7 +2479,7 @@ export function addCallExpressionsToPipe({
expressions,
}: {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
expressions: Node<CallExpression | CallExpressionKw>[]
}) {
@ -2188,7 +2503,7 @@ export function addCloseToPipe({
pathToNode,
}: {
node: Program
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
}) {
const _node = { ...node }
@ -2209,7 +2524,7 @@ export function addCloseToPipe({
export function replaceSketchLine({
node,
programMemory,
variables,
pathToNode: _pathToNode,
fnName,
segmentInput,
@ -2217,7 +2532,7 @@ export function replaceSketchLine({
referencedSegment,
}: {
node: Node<Program>
programMemory: ProgramMemory
variables: VariableMap
pathToNode: PathToNode
fnName: ToolTip
segmentInput: SegmentInputs
@ -2241,7 +2556,7 @@ export function replaceSketchLine({
: sketchLineHelperMap[fnName]
const addRetVal = add({
node: _node,
previousProgramMemory: programMemory,
variables,
pathToNode: _pathToNode,
referencedSegment,
segmentInput,
@ -2306,8 +2621,6 @@ function addTagToChamfer(
if (err(variableDec)) return variableDec
const isPipeExpression = pipeExpr.node.type === 'PipeExpression'
console.log('pipeExpr', pipeExpr, variableDec)
// const callExpr = isPipeExpression ? pipeExpr.node.body[pipeIndex] : variableDec.node.init
const callExpr = isPipeExpression
? pipeExpr.node.body[pipeIndex]
: variableDec.node.init
@ -2388,7 +2701,6 @@ function addTagToChamfer(
if (isPipeExpression) {
pipeExpr.node.body.splice(pipeIndex, 0, newExpressionToInsert)
} else {
console.log('yo', createPipeExpression([newExpressionToInsert, callExpr]))
callExpr.arguments[1] = createPipeSubstitution()
variableDec.node.init = createPipeExpression([
newExpressionToInsert,
@ -2536,7 +2848,7 @@ function addTagKw(): addTagFn {
start: callExpr.node.start,
end: callExpr.node.end,
moduleId: callExpr.node.moduleId,
trivia: callExpr.node.trivia,
outerAttrs: callExpr.node.outerAttrs,
})
}
@ -2727,6 +3039,8 @@ export function isAbsoluteLine(lineCall: CallExpressionKw): boolean | Error {
return new Error(
`line call has neither ${ARG_END} nor ${ARG_END_ABSOLUTE} params`
)
case 'circleThreePoint':
return false
}
return new Error(`Unknown sketch function ${name}`)
}

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