Compare commits
44 Commits
jtran/test
...
v0.43.0
Author | SHA1 | Date | |
---|---|---|---|
57b366b2d0 | |||
6f3f5dbda9 | |||
8dd25715fb | |||
834f7133d8 | |||
8c5662e458 | |||
f37fc357af | |||
e27e9ecc63 | |||
78b42ea191 | |||
5d02a27122 | |||
49d52ce94b | |||
a572d7b0db | |||
1f405b9327 | |||
0874891dd0 | |||
59d0e079a1 | |||
b9862baed0 | |||
592c5259c6 | |||
950f5cebfd | |||
4c0ea136e0 | |||
81c0035adc | |||
c68e5d7774 | |||
ed8a0e4aaa | |||
322f398049 | |||
bc4d254297 | |||
10b7a772bf | |||
86ba586318 | |||
cc313afb89 | |||
d0c8311e41 | |||
28311d160a | |||
162856064b | |||
652f82e8c3 | |||
d4d9bf6c7f | |||
0e9d37c0a0 | |||
9c18060d73 | |||
e2be66b024 | |||
1bb372b642 | |||
627fbda671 | |||
74bdb72edc | |||
a9182bf357 | |||
5f14023404 | |||
9a3ac64603 | |||
67e60cb832 | |||
110037df79 | |||
30b1dae38a | |||
f20fc5b467 |
@ -1,5 +1,4 @@
|
||||
NODE_ENV=production
|
||||
DEV=false
|
||||
VITE_KC_API_WS_MODELING_URL=wss://api.zoo.dev/ws/modeling/commands
|
||||
VITE_KC_API_BASE_URL=https://api.zoo.dev
|
||||
VITE_KC_SITE_BASE_URL=https://zoo.dev
|
||||
|
1
.github/workflows/e2e-tests.yml
vendored
@ -3,7 +3,6 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -17,8 +17,8 @@ offsetPlane(std_plane: StandardPlane, offset: number) -> Plane
|
||||
|
||||
| Name | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `std_plane` | [`StandardPlane`](/docs/kcl/types/StandardPlane) | One of the standard planes. | Yes |
|
||||
| `offset` | `number` | | Yes |
|
||||
| `std_plane` | [`StandardPlane`](/docs/kcl/types/StandardPlane) | Which standard plane (e.g. XY) should this new plane be created from? | Yes |
|
||||
| `offset` | `number` | Distance from the standard plane this new plane will be created at. | Yes |
|
||||
|
||||
### Returns
|
||||
|
||||
@ -37,7 +37,7 @@ squareSketch = startSketchOn('XY')
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
circleSketch = startSketchOn(offsetPlane('XY', 150))
|
||||
circleSketch = startSketchOn(offsetPlane('XY', offset = 150))
|
||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
||||
|
||||
loft([squareSketch, circleSketch])
|
||||
@ -55,7 +55,7 @@ squareSketch = startSketchOn('XZ')
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
circleSketch = startSketchOn(offsetPlane('XZ', 150))
|
||||
circleSketch = startSketchOn(offsetPlane('XZ', offset = 150))
|
||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
||||
|
||||
loft([squareSketch, circleSketch])
|
||||
@ -73,7 +73,7 @@ squareSketch = startSketchOn('YZ')
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
circleSketch = startSketchOn(offsetPlane('YZ', 150))
|
||||
circleSketch = startSketchOn(offsetPlane('YZ', offset = 150))
|
||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
||||
|
||||
loft([squareSketch, circleSketch])
|
||||
@ -91,7 +91,7 @@ squareSketch = startSketchOn('-XZ')
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|
||||
circleSketch = startSketchOn(offsetPlane('-XZ', -150))
|
||||
circleSketch = startSketchOn(offsetPlane('-XZ', offset = -150))
|
||||
|> circle({ center = [0, 100], radius = 50 }, %)
|
||||
|
||||
loft([squareSketch, circleSketch])
|
||||
@ -106,7 +106,7 @@ startSketchOn("XY")
|
||||
|> circle({ radius = 10, center = [0, 0] }, %)
|
||||
|
||||
// Triangle on the plane 4 units above
|
||||
startSketchOn(offsetPlane("XY", 4))
|
||||
startSketchOn(offsetPlane("XY", offset = 4))
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> line(end = [10, 0])
|
||||
|> line(end = [0, 10])
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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)
|
||||
```
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
72975
docs/kcl/std.json
@ -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
|
||||
|
||||
|
16
docs/kcl/types/EnvironmentRef.md
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
title: "EnvironmentRef"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
[`SnapshotRef`](/docs/kcl/types/SnapshotRef)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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 face’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s 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 |
|
||||
|
@ -59,23 +59,7 @@ Any KCL value.
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Number`| | No |
|
||||
| `value` |`number`| | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Int`| | No |
|
||||
| `value` |`integer`| | No |
|
||||
| `ty` |[`NumericType`](/docs/kcl/types/NumericType)| Any KCL value. | No |
|
||||
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |
|
||||
|
||||
|
||||
@ -311,7 +295,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 +335,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 |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
||||
|
250
docs/kcl/types/NumericType.md
Normal file
@ -0,0 +1,250 @@
|
||||
---
|
||||
title: "NumericType"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Count`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Mm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Cm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `M`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Inches`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Feet`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Yards`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Length`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Angle`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Known`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Default`| | No |
|
||||
| `len` |[`UnitLen`](/docs/kcl/types/UnitLen)| | No |
|
||||
| `angle` |[`UnitAngle`](/docs/kcl/types/UnitAngle)| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Unknown`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Any`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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 plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s 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 |
|
||||
|
@ -17,6 +17,5 @@ layout: manual
|
||||
|----------|------|-------------|----------|
|
||||
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
|
||||
| `currentEnv` |`integer`| | No |
|
||||
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |
|
||||
|
||||
|
||||
|
@ -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 plane’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the plane’s 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 face’s X axis be? | No |
|
||||
| `yAxis` |[`Point3d`](/docs/kcl/types/Point3d)| What should the face’s 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 |
|
||||
|
16
docs/kcl/types/SnapshotRef.md
Normal 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`)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
47
docs/kcl/types/UnitAngle.md
Normal file
@ -0,0 +1,47 @@
|
||||
---
|
||||
title: "UnitAngle"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
186
docs/kcl/types/UnitType.md
Normal file
@ -0,0 +1,186 @@
|
||||
---
|
||||
title: "UnitType"
|
||||
excerpt: ""
|
||||
layout: manual
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Count`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Mm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Cm`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `M`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Inches`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Feet`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Yards`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Length`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
**This schema accepts exactly one of the following:**
|
||||
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Degrees`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Radians`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `Angle`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
|
||||
|
||||
|
@ -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), %)`)
|
||||
|
@ -19,6 +19,8 @@ test.describe(
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
// FIXME: Cannot use scene.waitForExecutionDone() since there is no KCL code
|
||||
await page.waitForTimeout(10000)
|
||||
await u.openDebugPanel()
|
||||
|
||||
const coord =
|
||||
@ -41,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()
|
||||
|
||||
|
@ -10,6 +10,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
test('Typing KCL errors induces a badge on the code pane button', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
|
||||
@ -30,11 +31,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// Ensure no badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
@ -175,7 +172,9 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await page.waitForTimeout(1000)
|
||||
// FIXME: await scene.waitForExecutionDone() does not work. It still fails.
|
||||
// I needed to increase this timeout to get this to pass.
|
||||
await page.waitForTimeout(10000)
|
||||
|
||||
// Ensure badge is present
|
||||
const codePaneButtonHolder = page.locator('#code-button-holder')
|
||||
@ -187,7 +186,7 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
// click in the editor to focus it
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
await page.waitForTimeout(500)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// go to the start of the editor and enter more text which will trigger
|
||||
// a lint error.
|
||||
@ -204,8 +203,9 @@ test.describe('Code pane and errors', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('ArrowUp')
|
||||
await page.keyboard.press('Home')
|
||||
await page.keyboard.type('foo_bar = 1')
|
||||
await page.waitForTimeout(500)
|
||||
await page.waitForTimeout(2000)
|
||||
await page.keyboard.press('Enter')
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// ensure we have a lint error
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
@ -174,6 +174,9 @@ test.describe('Command bar tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// FIXME: No KCL code, unable to wait for engine execution
|
||||
await page.waitForTimeout(10000)
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled()
|
||||
|
@ -9,8 +9,8 @@ import fsp from 'fs/promises'
|
||||
|
||||
test(
|
||||
'export works on the first try',
|
||||
{ tag: '@electron' },
|
||||
async ({ page, context }, testInfo) => {
|
||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||
async ({ page, context, scene }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
await Promise.all([fsp.mkdir(bracketDir, { recursive: true })])
|
||||
@ -118,8 +118,9 @@ test(
|
||||
// Close the file pane
|
||||
await u.closeFilePanel()
|
||||
|
||||
// wait for it to finish executing (todo: make this more robust)
|
||||
await page.waitForTimeout(1000)
|
||||
// FIXME: await scene.waitForExecutionDone() does not work. The modeling indicator stays in -receive-reliable and not execution done
|
||||
await page.waitForTimeout(10000)
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
|
@ -490,6 +490,11 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.press('ArrowLeft')
|
||||
await page.keyboard.press('ArrowRight')
|
||||
|
||||
// FIXME: lsp errors do not propagate to the frontend until engine is connected and code is executed
|
||||
// This timeout is to wait for engine connection. LSP and code execution errors should be handled differently
|
||||
// LSP can emit errors as fast as it waits and show them in the editor
|
||||
await page.waitForTimeout(10000)
|
||||
|
||||
// error in guter
|
||||
await expect(page.locator('.cm-lint-marker-info').first()).toBeVisible()
|
||||
|
||||
@ -641,7 +646,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
width = 0.500
|
||||
height = 0.500
|
||||
dia = 4
|
||||
|
||||
|
||||
fn squareHole = (l, w) => {
|
||||
squareHoleSketch = startSketchOn('XY')
|
||||
|> startProfileAt([-width / 2, -length / 2], %)
|
||||
@ -714,7 +719,7 @@ test.describe('Editor tests', { tag: ['@skipWin'] }, () => {
|
||||
|> line(end = [0, -10], tag = $revolveAxis)
|
||||
|> close()
|
||||
|> extrude(length = 10)
|
||||
|
||||
|
||||
sketch001 = startSketchOn(box, revolveAxis)
|
||||
|> startProfileAt([5, 10], %)
|
||||
|> line(end = [0, -10])
|
||||
|
@ -24,7 +24,7 @@ sketch001 = startSketchOn('XZ')
|
||||
revolve001 = revolve({ axis = "X" }, sketch001)
|
||||
triangle()
|
||||
|> extrude(length = 30)
|
||||
plane001 = offsetPlane('XY', 10)
|
||||
plane001 = offsetPlane('XY', offset = 10)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> startProfileAt([-20, 0], %)
|
||||
|> line(end = [5, -15])
|
||||
@ -35,7 +35,7 @@ sketch002 = startSketchOn(plane001)
|
||||
extrude001 = extrude(sketch002, length = 10)
|
||||
`
|
||||
|
||||
const FEAUTRE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|
||||
const FEATURE_TREE_SKETCH_CODE = `sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> angledLine([0, 4], %, $rectangleSegmentA001)
|
||||
|> angledLine([
|
||||
@ -54,7 +54,7 @@ sketch002 = startSketchOn(extrude001, rectangleSegmentB001)
|
||||
center = [-1, 2],
|
||||
radius = .5
|
||||
}, %)
|
||||
plane001 = offsetPlane('XZ', -5)
|
||||
plane001 = offsetPlane('XZ', offset = -5)
|
||||
sketch003 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 5 }, %)
|
||||
`
|
||||
@ -116,7 +116,7 @@ test.describe('Feature Tree pane', () => {
|
||||
await testViewSource({
|
||||
operationName: 'Offset Plane',
|
||||
operationIndex: 0,
|
||||
expectedActiveLine: "plane001 = offsetPlane('XY', 10)",
|
||||
expectedActiveLine: "plane001 = offsetPlane('XY', offset = 10)",
|
||||
})
|
||||
await testViewSource({
|
||||
operationName: 'Extrude',
|
||||
@ -153,33 +153,16 @@ test.describe('Feature Tree pane', () => {
|
||||
`User can edit sketch (but not on offset plane yet) from the feature tree`,
|
||||
{ tag: '@electron' },
|
||||
async ({ context, homePage, scene, editor, toolbar, page }) => {
|
||||
const unavailableToastMessage = page.getByText(
|
||||
'Editing sketches on faces or offset planes through the feature tree is not yet supported'
|
||||
)
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, FEATURE_TREE_SKETCH_CODE)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = join(dir, 'test-sample')
|
||||
await fsp.mkdir(bracketDir, { recursive: true })
|
||||
await fsp.writeFile(
|
||||
join(bracketDir, 'main.kcl'),
|
||||
FEAUTRE_TREE_SKETCH_CODE,
|
||||
'utf-8'
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('setup test', async () => {
|
||||
await homePage.expectState({
|
||||
projectCards: [
|
||||
{
|
||||
title: 'test-sample',
|
||||
fileCount: 1,
|
||||
},
|
||||
],
|
||||
sortBy: 'last-modified-desc',
|
||||
})
|
||||
await homePage.openProject('test-sample')
|
||||
await scene.waitForExecutionDone()
|
||||
await toolbar.openFeatureTreePane()
|
||||
await test.step('force re-exe', async () => {
|
||||
await page.waitForTimeout(1000)
|
||||
await editor.replaceCode('90', '91')
|
||||
await page.waitForTimeout(1500)
|
||||
})
|
||||
|
||||
await test.step('On a default plane should work', async () => {
|
||||
@ -199,24 +182,23 @@ test.describe('Feature Tree pane', () => {
|
||||
await test.step('On an extrude face should *not* work', async () => {
|
||||
// Tooltip is getting in the way of clicking, so I'm first closing the pane
|
||||
await toolbar.closeFeatureTreePane()
|
||||
await page.waitForTimeout(1000)
|
||||
await editor.replaceCode('91', '90')
|
||||
await page.waitForTimeout(2000)
|
||||
await (await toolbar.getFeatureTreeOperation('Sketch', 1)).dblclick()
|
||||
|
||||
await expect(
|
||||
unavailableToastMessage,
|
||||
'We should see a toast message about this'
|
||||
toolbar.exitSketchBtn,
|
||||
'We should be in sketch mode now'
|
||||
).toBeVisible()
|
||||
await unavailableToastMessage.waitFor({ state: 'detached' })
|
||||
// TODO - turn on once we update the artifactGraph in Rust
|
||||
// to include the proper source location for the extrude face
|
||||
// await expect(
|
||||
// toolbar.exitSketchBtn,
|
||||
// 'We should be in sketch mode now'
|
||||
// ).toBeVisible()
|
||||
// await editor.expectState({
|
||||
// highlightedCode: '',
|
||||
// diagnostics: [],
|
||||
// activeLines: ['|>circle({center=[-1,2],radius=.5},%)'],
|
||||
// })
|
||||
// await toolbar.exitSketchBtn.click()
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: [
|
||||
'sketch002=startSketchOn(extrude001,rectangleSegmentB001)',
|
||||
],
|
||||
})
|
||||
await toolbar.exitSketchBtn.click()
|
||||
})
|
||||
|
||||
await test.step('On an offset plane should *not* work', async () => {
|
||||
@ -226,7 +208,7 @@ test.describe('Feature Tree pane', () => {
|
||||
await editor.expectState({
|
||||
highlightedCode: '',
|
||||
diagnostics: [],
|
||||
activeLines: ['|>circle({center=[0,0],radius=5},%)'],
|
||||
activeLines: ['sketch003=startSketchOn(plane001)'],
|
||||
})
|
||||
await expect(
|
||||
toolbar.exitSketchBtn,
|
||||
@ -342,7 +324,8 @@ test.describe('Feature Tree pane', () => {
|
||||
toolbar,
|
||||
cmdBar,
|
||||
}) => {
|
||||
const testCode = (value: string) => `p = offsetPlane('XY', ${value})`
|
||||
const testCode = (value: string) =>
|
||||
`p = offsetPlane('XY', offset = ${value})`
|
||||
const initialInput = '10'
|
||||
const initialCode = testCode(initialInput)
|
||||
const newInput = '5 + 10'
|
||||
|
@ -112,6 +112,9 @@ export class CmdBarFixture {
|
||||
* and assumes we are past the `pickCommand` step.
|
||||
*/
|
||||
progressCmdBar = async (shouldFuzzProgressMethod = true) => {
|
||||
// FIXME: Progressing the command bar is a race condition. We have an async useEffect that reports the final state via useCalculateKclExpression. If this does not run quickly enough, it will not "fail" the continue because you can press continue if the state is not ready. E2E tests do not know this.
|
||||
// Wait 1250ms to assume the await executeAst of the KCL input field is finished
|
||||
await this.page.waitForTimeout(1250)
|
||||
if (shouldFuzzProgressMethod || Math.random() > 0.5) {
|
||||
const arrowButton = this.page.getByRole('button', {
|
||||
name: 'arrow right Continue',
|
||||
@ -128,6 +131,23 @@ export class CmdBarFixture {
|
||||
}
|
||||
}
|
||||
|
||||
// Added data-testid to the command bar buttons
|
||||
// command-bar-continue are the buttons to go to the next step
|
||||
// does not include the submit which is the final button press
|
||||
// aka the right arrow button
|
||||
continue = async () => {
|
||||
const continueButton = this.page.getByTestId('command-bar-continue')
|
||||
await continueButton.click()
|
||||
}
|
||||
|
||||
// Added data-testid to the command bar buttons
|
||||
// command-bar-submit is the button for the final step to submit
|
||||
// the command bar flow aka the checkmark button.
|
||||
submit = async () => {
|
||||
const submitButton = this.page.getByTestId('command-bar-submit')
|
||||
await submitButton.click()
|
||||
}
|
||||
|
||||
openCmdBar = async (selectCmd?: 'promptToEdit') => {
|
||||
// TODO why does this button not work in electron tests?
|
||||
// await this.cmdBarOpenBtn.click()
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -29,11 +29,13 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const [clickCircle, moveToCircle] = scene.makeMouseHelpers(582, 217)
|
||||
|
||||
await test.step('because there is sweepable geometry, verify extrude is enable when nothing is selected', async () => {
|
||||
await scene.clickNoWhere()
|
||||
// FIXME: Do not click, clicking removes the activeLines in future checks
|
||||
// await scene.clickNoWhere()
|
||||
await expect(toolbar.extrudeButton).toBeEnabled()
|
||||
})
|
||||
|
||||
@ -199,6 +201,7 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
}, file)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||
|
||||
@ -216,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({
|
||||
@ -248,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 },
|
||||
@ -273,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({
|
||||
@ -298,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 }
|
||||
)
|
||||
})
|
||||
@ -422,6 +405,7 @@ test.describe('Point-and-click tests', { tag: ['@skipWin'] }, () => {
|
||||
}, file)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const sketchOnAChamfer = _sketchOnAChamfer(page, editor, toolbar, scene)
|
||||
|
||||
@ -439,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')
|
||||
@ -480,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 }
|
||||
@ -557,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 () => {
|
||||
@ -601,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 ({
|
||||
@ -712,6 +692,330 @@ openSketch = startSketchOn('XY')
|
||||
})
|
||||
})
|
||||
|
||||
test(`Shift-click to select and deselect edges and faces`, async ({
|
||||
context,
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
// Code samples
|
||||
const initialCode = `sketch001 = startSketchOn('XY')
|
||||
|> startProfileAt([-12, -6], %)
|
||||
|> line(end = [0, 12])
|
||||
|> line(end = [24, 0])
|
||||
|> line(end = [0, -12])
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
|> extrude(%, length = -12)`
|
||||
|
||||
// Locators
|
||||
const upperEdgeLocation = { x: 600, y: 192 }
|
||||
const lowerEdgeLocation = { x: 600, y: 383 }
|
||||
const faceLocation = { x: 630, y: 290 }
|
||||
|
||||
// Click helpers
|
||||
const [clickOnUpperEdge] = scene.makeMouseHelpers(
|
||||
upperEdgeLocation.x,
|
||||
upperEdgeLocation.y
|
||||
)
|
||||
const [clickOnLowerEdge] = scene.makeMouseHelpers(
|
||||
lowerEdgeLocation.x,
|
||||
lowerEdgeLocation.y
|
||||
)
|
||||
const [clickOnFace] = scene.makeMouseHelpers(faceLocation.x, faceLocation.y)
|
||||
|
||||
// Colors
|
||||
const edgeColorWhite: [number, number, number] = [220, 220, 220] // varies from 192 to 255
|
||||
const edgeColorYellow: [number, number, number] = [251, 251, 40] // vaies from 12 to 67
|
||||
const faceColorGray: [number, number, number] = [168, 168, 168]
|
||||
const faceColorYellow: [number, number, number] = [155, 155, 155]
|
||||
const tolerance = 40
|
||||
const timeout = 150
|
||||
|
||||
// Setup
|
||||
await test.step(`Initial test setup`, async () => {
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Wait for the scene and stream to load
|
||||
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||
})
|
||||
|
||||
await test.step('Select and deselect a single edge', async () => {
|
||||
await test.step('Click the edge', async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
await clickOnUpperEdge()
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Shift-click the same edge to deselect', async () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnUpperEdge()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
await test.step('Select and deselect multiple objects', async () => {
|
||||
await test.step('Select both edges and the face', async () => {
|
||||
await test.step('Select the upper edge', async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
await clickOnUpperEdge()
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Select the lower edge (Shift-click)', async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
lowerEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnLowerEdge()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
lowerEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Select the face (Shift-click)', async () => {
|
||||
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnFace()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||
})
|
||||
})
|
||||
await test.step('Deselect them one by one', async () => {
|
||||
await test.step('Deselect the face (Shift-click)', async () => {
|
||||
await scene.expectPixelColor(faceColorYellow, faceLocation, tolerance)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnFace()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(faceColorGray, faceLocation, tolerance)
|
||||
})
|
||||
await test.step('Deselect the lower edge (Shift-click)', async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
lowerEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnLowerEdge()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
lowerEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Deselect the upper edge (Shift-click)', async () => {
|
||||
await scene.expectPixelColor(
|
||||
edgeColorYellow,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickOnUpperEdge()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
upperEdgeLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(`Shift-click to select and deselect sketch segments`, async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
editor,
|
||||
}) => {
|
||||
// Locators
|
||||
const firstPointLocation = { x: 200, y: 100 }
|
||||
const secondPointLocation = { x: 800, y: 100 }
|
||||
const thirdPointLocation = { x: 800, y: 400 }
|
||||
const fristSegmentLocation = { x: 750, y: 100 }
|
||||
const secondSegmentLocation = { x: 800, y: 150 }
|
||||
const planeLocation = { x: 700, y: 200 }
|
||||
|
||||
// Click helpers
|
||||
const [clickFirstPoint] = scene.makeMouseHelpers(
|
||||
firstPointLocation.x,
|
||||
firstPointLocation.y
|
||||
)
|
||||
const [clickSecondPoint] = scene.makeMouseHelpers(
|
||||
secondPointLocation.x,
|
||||
secondPointLocation.y
|
||||
)
|
||||
const [clickThirdPoint] = scene.makeMouseHelpers(
|
||||
thirdPointLocation.x,
|
||||
thirdPointLocation.y
|
||||
)
|
||||
const [clickFirstSegment] = scene.makeMouseHelpers(
|
||||
fristSegmentLocation.x,
|
||||
fristSegmentLocation.y
|
||||
)
|
||||
const [clickSecondSegment] = scene.makeMouseHelpers(
|
||||
secondSegmentLocation.x,
|
||||
secondSegmentLocation.y
|
||||
)
|
||||
const [clickPlane] = scene.makeMouseHelpers(
|
||||
planeLocation.x,
|
||||
planeLocation.y
|
||||
)
|
||||
|
||||
// Colors
|
||||
const edgeColorWhite: [number, number, number] = [220, 220, 220]
|
||||
const edgeColorBlue: [number, number, number] = [20, 20, 200]
|
||||
const backgroundColor: [number, number, number] = [30, 30, 30]
|
||||
const tolerance = 40
|
||||
const timeout = 150
|
||||
|
||||
// Setup
|
||||
await test.step(`Initial test setup`, async () => {
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// Wait for the scene and stream to load
|
||||
await scene.expectPixelColor(
|
||||
backgroundColor,
|
||||
secondPointLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('Select and deselect a single sketch segment', async () => {
|
||||
await test.step('Get into sketch mode', async () => {
|
||||
await editor.closePane()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickPlane()
|
||||
await page.waitForTimeout(1000)
|
||||
})
|
||||
await test.step('Draw sketch', async () => {
|
||||
await clickFirstPoint()
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickSecondPoint()
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickThirdPoint()
|
||||
await page.waitForTimeout(timeout)
|
||||
})
|
||||
await test.step('Deselect line tool', async () => {
|
||||
const btnLine = page.getByTestId('line')
|
||||
const btnLineAriaPressed = await btnLine.getAttribute('aria-pressed')
|
||||
if (btnLineAriaPressed === 'true') {
|
||||
await btnLine.click()
|
||||
}
|
||||
await page.waitForTimeout(timeout)
|
||||
})
|
||||
await test.step('Select the first segment', async () => {
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickFirstSegment()
|
||||
await page.waitForTimeout(timeout)
|
||||
await scene.expectPixelColor(
|
||||
edgeColorBlue,
|
||||
fristSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
secondSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Select the second segment (Shift-click)', async () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickSecondSegment()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorBlue,
|
||||
fristSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
await scene.expectPixelColor(
|
||||
edgeColorBlue,
|
||||
secondSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Deselect the first segment', async () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickFirstSegment()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
fristSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
await scene.expectPixelColor(
|
||||
edgeColorBlue,
|
||||
secondSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
await test.step('Deselect the second segment', async () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.waitForTimeout(timeout)
|
||||
await clickSecondSegment()
|
||||
await page.waitForTimeout(timeout)
|
||||
await page.keyboard.up('Shift')
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
fristSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
await scene.expectPixelColor(
|
||||
edgeColorWhite,
|
||||
secondSegmentLocation,
|
||||
tolerance
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test(`Offset plane point-and-click`, async ({
|
||||
context,
|
||||
page,
|
||||
@ -724,9 +1028,12 @@ openSketch = startSketchOn('XY')
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 700, y: 150 }
|
||||
const [clickOnXzPlane] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', 5)`
|
||||
const expectedOutput = `plane001 = offsetPlane('XZ', offset = 5)`
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
// FIXME: Since there is no KCL code loaded. We need to wait for the scene to load before we continue.
|
||||
// The engine may not be connected
|
||||
await page.waitForTimeout(15000)
|
||||
|
||||
await test.step(`Look for the blue of the XZ plane`, async () => {
|
||||
await scene.expectPixelColor([50, 51, 96], testPoint, 15)
|
||||
@ -786,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({
|
||||
@ -823,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
|
||||
@ -856,7 +1164,7 @@ openSketch = startSketchOn('XY')
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
`
|
||||
@ -942,7 +1250,7 @@ openSketch = startSketchOn('XY')
|
||||
}) => {
|
||||
const initialCode = `sketch001 = startSketchOn('XZ')
|
||||
|> circle({ center = [0, 0], radius = 30 }, %)
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
sketch002 = startSketchOn(plane001)
|
||||
|> circle({ center = [0, 0], radius = 20 }, %)
|
||||
loft001 = loft([sketch001, sketch002])
|
||||
@ -952,6 +1260,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
@ -988,7 +1297,7 @@ loft001 = loft([sketch001, sketch002])
|
||||
await page.waitForTimeout(1000)
|
||||
await clickOnSketch2()
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`
|
||||
plane001 = offsetPlane('XZ', 50)
|
||||
plane001 = offsetPlane('XZ', offset = 50)
|
||||
`)
|
||||
await page.keyboard.press('Backspace')
|
||||
// Check for sketch 1
|
||||
@ -1029,7 +1338,7 @@ sketch002 = startSketchOn('XZ')
|
||||
testPoint.x - 50,
|
||||
testPoint.y
|
||||
)
|
||||
const sweepDeclaration = 'sweep001 = sweep({ path = sketch002 }, sketch001)'
|
||||
const sweepDeclaration = 'sweep001 = sweep(sketch001, path = sketch002)'
|
||||
|
||||
await test.step(`Look for sketch001`, async () => {
|
||||
await toolbar.closePane('code')
|
||||
@ -1064,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: [],
|
||||
@ -1594,16 +1903,7 @@ extrude001 = extrude(sketch001, length = -12)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// verify modeling scene is loaded
|
||||
await scene.expectPixelColor(
|
||||
backgroundColor,
|
||||
secondEdgeLocation,
|
||||
lowTolerance
|
||||
)
|
||||
|
||||
// wait for stream to load
|
||||
await scene.expectPixelColor(bodyColor, bodyLocation, highTolerance)
|
||||
await scene.waitForExecutionDone()
|
||||
})
|
||||
|
||||
// Test 1: Command bar flow with preselected edges
|
||||
@ -1828,6 +2128,7 @@ chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// verify modeling scene is loaded
|
||||
await scene.expectPixelColor(
|
||||
@ -1950,6 +2251,7 @@ chamfer04 = chamfer({ length = 5, tags = [getOppositeEdge(seg02)]}, extrude001
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 575, y: 200 }
|
||||
@ -2048,6 +2350,7 @@ extrude001 = extrude(sketch001, length = 40)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 580, y: 180 }
|
||||
@ -2145,19 +2448,18 @@ extrude002 = extrude(sketch002, length = 50)
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
}, initialCode)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// One dumb hardcoded screen pixel value
|
||||
const testPoint = { x: 550, y: 295 }
|
||||
const testPoint = { x: 580, y: 320 }
|
||||
const [clickOnCap] = scene.makeMouseHelpers(testPoint.x, testPoint.y)
|
||||
const shellTarget = hasExtrudesInPipe ? 'sketch002' : 'extrude002'
|
||||
const shellDeclaration = `shell001 = shell(${shellTarget}, faces = ['end'], thickness = 5)`
|
||||
|
||||
await test.step(`Look for the grey of the shape`, async () => {
|
||||
await toolbar.closePane('code')
|
||||
await scene.expectPixelColor([128, 128, 128], testPoint, 15)
|
||||
await scene.expectPixelColor([113, 113, 113], testPoint, 15)
|
||||
})
|
||||
|
||||
await test.step(`Go through the command bar flow, selecting a cap and keeping default thickness`, async () => {
|
||||
@ -2221,7 +2523,7 @@ extrude002 = extrude(sketch002, length = 50)
|
||||
sketch002 = startSketchOn('XZ')
|
||||
|> startProfileAt([0, 0], %)
|
||||
|> xLine(-2000, %)
|
||||
sweep001 = sweep({ path = sketch002 }, sketch001)
|
||||
sweep001 = sweep(sketch001, path = sketch002)
|
||||
`
|
||||
await context.addInitScript((initialCode) => {
|
||||
localStorage.setItem('persistCode', initialCode)
|
||||
|
@ -455,7 +455,7 @@ test.describe('Can export from electron app', () => {
|
||||
for (const method of exportMethods) {
|
||||
test(
|
||||
`Can export using ${method}`,
|
||||
{ tag: '@electron' },
|
||||
{ tag: ['@electron', '@skipLocalEngine'] },
|
||||
async ({ context, page }, testInfo) => {
|
||||
await context.folderSetupFn(async (dir) => {
|
||||
const bracketDir = path.join(dir, 'bracket')
|
||||
|
@ -36,7 +36,7 @@ extrude003 = extrude(sketch003, length = 20)
|
||||
`
|
||||
|
||||
test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
test.describe('Check the happy path, for basic changing color', () => {
|
||||
test.fixme('Check the happy path, for basic changing color', () => {
|
||||
const cases = [
|
||||
{
|
||||
desc: 'User accepts change',
|
||||
@ -60,6 +60,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const greenCheckCoords = { x: 565, y: 345 }
|
||||
@ -76,7 +77,9 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
'Submitting to Text-to-CAD API...'
|
||||
)
|
||||
const successToast = page.getByText('Prompt to edit successful')
|
||||
const acceptBtn = page.getByRole('button', { name: 'checkmark Accept' })
|
||||
const acceptBtn = page.getByRole('button', {
|
||||
name: 'checkmark Accept',
|
||||
})
|
||||
const rejectBtn = page.getByRole('button', { name: 'close Reject' })
|
||||
|
||||
await test.step('wait for scene to load select body and check selection came through', async () => {
|
||||
@ -99,14 +102,16 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(submittingToast).toBeVisible()
|
||||
await expect(submittingToast).not.toBeVisible({ timeout: 2 * 60_000 }) // can take a while
|
||||
await expect(submittingToast).not.toBeVisible({
|
||||
timeout: 2 * 60_000,
|
||||
}) // can take a while
|
||||
await expect(successToast).toBeVisible()
|
||||
})
|
||||
|
||||
await test.step('verify initial change', async () => {
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await scene.expectPixelColor(body2NotGreen, body2WallCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
await editor.expectEditor.toContain('appearance(')
|
||||
})
|
||||
|
||||
if (!shouldReject) {
|
||||
@ -115,13 +120,13 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(green, greenCheckCoords, 15)
|
||||
await editor.expectEditor.toContain('appearance({')
|
||||
await editor.expectEditor.toContain('appearance(')
|
||||
|
||||
// ctrl-z works after accepting
|
||||
await page.keyboard.down('ControlOrMeta')
|
||||
await page.keyboard.press('KeyZ')
|
||||
await page.keyboard.up('ControlOrMeta')
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
await editor.expectEditor.not.toContain('appearance(')
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
})
|
||||
} else {
|
||||
@ -130,7 +135,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
await expect(successToast).not.toBeVisible()
|
||||
|
||||
await scene.expectPixelColor(notGreen, greenCheckCoords, 15)
|
||||
await editor.expectEditor.not.toContain('appearance({')
|
||||
await editor.expectEditor.not.toContain('appearance(')
|
||||
})
|
||||
}
|
||||
})
|
||||
@ -150,6 +155,7 @@ test.describe('Prompt-to-edit tests', { tag: '@skipWin' }, () => {
|
||||
localStorage.setItem('persistCode', file)
|
||||
}, file)
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
const body1CapCoords = { x: 571, y: 351 }
|
||||
const [clickBody1Cap] = scene.makeMouseHelpers(
|
||||
|
@ -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 })
|
||||
@ -251,7 +251,7 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|> yLineTo(0, %)
|
||||
|> close()
|
||||
|>
|
||||
|
||||
|
||||
example = extrude(exampleSketch, length = 5)
|
||||
shell(exampleSketch, faces = ['end'], thickness = 0.25)`
|
||||
)
|
||||
@ -306,113 +306,113 @@ extrude001 = extrude(sketch001, length = 50)
|
||||
|> angledLine({ angle: 50, length: 45 }, %)
|
||||
|> yLineTo(0, %)
|
||||
|> close()
|
||||
|
||||
|
||||
thing: "blah"`)
|
||||
|
||||
await expect(page.locator('.cm-lint-marker-error')).toBeVisible()
|
||||
}
|
||||
)
|
||||
|
||||
test('when engine fails export we handle the failure and alert the user', async ({
|
||||
scene,
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(
|
||||
async ({ code }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
},
|
||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||
)
|
||||
test(
|
||||
'when engine fails export we handle the failure and alert the user',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ scene, page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(
|
||||
async ({ code }) => {
|
||||
localStorage.setItem('persistCode', code)
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
},
|
||||
{ code: TEST_CODE_TRIGGER_ENGINE_EXPORT_ERROR }
|
||||
)
|
||||
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
// expect zero errors in guter
|
||||
await expect(page.locator('.cm-lint-marker-error')).not.toBeVisible()
|
||||
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
// export the model
|
||||
const exportButton = page.getByTestId('export-pane-button')
|
||||
await expect(exportButton).toBeVisible()
|
||||
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
const stlOption = page.getByText('glTF')
|
||||
await expect(stlOption).toBeVisible()
|
||||
// Click the stl.
|
||||
const stlOption = page.getByText('glTF')
|
||||
await expect(stlOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
const submitButton = page.getByText('Confirm Export')
|
||||
await expect(submitButton).toBeVisible()
|
||||
// Click the checkbox
|
||||
const submitButton = page.getByText('Confirm Export')
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
const exportingToastMessage = page.getByText(`Exporting...`)
|
||||
const errorToastMessage = page.getByText(`Error while exporting`)
|
||||
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
await expect(engineErrorToastMessage).toBeVisible()
|
||||
const engineErrorToastMessage = page.getByText(`Nothing to export`)
|
||||
await expect(engineErrorToastMessage).toBeVisible()
|
||||
|
||||
// Make sure the exporting toast is gone
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
// Make sure the exporting toast is gone
|
||||
await expect(exportingToastMessage).not.toBeVisible()
|
||||
|
||||
// Click the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
// Click the code editor
|
||||
await page.locator('.cm-content').click()
|
||||
|
||||
await page.waitForTimeout(2000)
|
||||
await page.waitForTimeout(2000)
|
||||
|
||||
// Expect the toast to be gone
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
// Expect the toast to be gone
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
// Now add in code that works.
|
||||
await page.locator('.cm-content').fill(bracket)
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
// Now add in code that works.
|
||||
await page.locator('.cm-content').fill(bracket)
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
await scene.waitForExecutionDone()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
// Now try exporting
|
||||
// Now try exporting
|
||||
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
// Click the export button
|
||||
await exportButton.click()
|
||||
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
// Click the stl.
|
||||
await expect(stlOption).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
// Click the checkbox
|
||||
await expect(submitButton).toBeVisible()
|
||||
|
||||
await page.keyboard.press('Enter')
|
||||
await page.keyboard.press('Enter')
|
||||
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
// Find the toast.
|
||||
// Look out for the toast message
|
||||
await expect(exportingToastMessage).toBeVisible()
|
||||
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
// Expect it to succeed.
|
||||
await expect(exportingToastMessage).not.toBeVisible({ timeout: 15_000 })
|
||||
await expect(errorToastMessage).not.toBeVisible()
|
||||
await expect(engineErrorToastMessage).not.toBeVisible()
|
||||
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
})
|
||||
const successToastMessage = page.getByText(`Exported successfully`)
|
||||
await expect(successToastMessage).toBeVisible()
|
||||
}
|
||||
)
|
||||
test(
|
||||
'ensure you can not export while an export is already going',
|
||||
{ tag: ['@skipLinux', '@skipWin'] },
|
||||
|
@ -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 += `
|
||||
@ -1187,14 +1202,12 @@ sweepSketch = startSketchOn('XY')
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
|> sweep(path = sweepPath)
|
||||
|> appearance(
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
)
|
||||
`
|
||||
)
|
||||
})
|
||||
@ -1235,14 +1248,12 @@ sweepSketch = startSketchOn('XY')
|
||||
angleStart = 0,
|
||||
radius = 2
|
||||
}, %)
|
||||
|> sweep({
|
||||
path = sweepPath,
|
||||
}, %)
|
||||
|> appearance({
|
||||
|> sweep(path = sweepPath)
|
||||
|> appearance(
|
||||
color = "#bb00ff",
|
||||
metalness = 90,
|
||||
roughness = 90
|
||||
}, %)
|
||||
)
|
||||
`
|
||||
)
|
||||
})
|
||||
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB |
Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 143 KiB |
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 54 KiB |
@ -1,222 +1,251 @@
|
||||
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('simulate network down and network little widget', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
test(
|
||||
'simulate network down and network little widget',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
|
||||
// This is how we wait until the stream is online
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
// This is how we wait until the stream is online
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||
await expect(networkWidget).toBeVisible()
|
||||
await networkWidget.hover()
|
||||
const networkWidget = page.locator('[data-testid="network-toggle"]')
|
||||
await expect(networkWidget).toBeVisible()
|
||||
await networkWidget.hover()
|
||||
|
||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
const networkPopover = page.locator('[data-testid="network-popover"]')
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// (First check) Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
// (First check) Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(100, 100)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
// Click off the modal.
|
||||
await page.mouse.click(100, 100)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Turn off the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
// Turn off the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
// Click the network widget
|
||||
await networkWidget.click()
|
||||
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
// Check the modal opened.
|
||||
await expect(networkPopover).toBeVisible()
|
||||
|
||||
// Click off the modal.
|
||||
await page.mouse.click(0, 0)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
// Click off the modal.
|
||||
await page.mouse.click(0, 0)
|
||||
await expect(networkPopover).not.toBeVisible()
|
||||
|
||||
// Turn back on the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
// Turn back on the network
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
// (Second check) expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
})
|
||||
// (Second check) expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
}
|
||||
)
|
||||
|
||||
test('Engine disconnect & reconnect in sketch mode', async ({
|
||||
page,
|
||||
homePage,
|
||||
}) => {
|
||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
test(
|
||||
'Engine disconnect & reconnect in sketch mode',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage }) => {
|
||||
// TODO: Don't skip Mac for these. After `window.tearDown` is working in Safari, these should work on webkit
|
||||
const networkToggle = page.getByTestId('network-toggle')
|
||||
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
const u = await getUtils(page)
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
const PUR = 400 / 37.5 //pixeltoUnitRatio
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
await homePage.goToModelingScene()
|
||||
await u.waitForPageLoad()
|
||||
|
||||
await u.openDebugPanel()
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await u.openDebugPanel()
|
||||
// click on "Start Sketch" button
|
||||
await u.clearCommandLogs()
|
||||
await page.getByRole('button', { name: 'Start Sketch' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
// select a plane
|
||||
await page.mouse.click(700, 200)
|
||||
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
await expect(page.locator('.cm-content')).toHaveText(
|
||||
`sketch001 = startSketchOn('XZ')`
|
||||
)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
await page.waitForTimeout(500) // TODO detect animation ending, or disable animation
|
||||
|
||||
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 page.waitForTimeout(100)
|
||||
const startXPx = 600
|
||||
await page.mouse.click(startXPx + PUR * 10, 500 - PUR * 10)
|
||||
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 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}, %)`)
|
||||
await expect(page.locator('.cm-content'))
|
||||
.toHaveText(`sketch001 = startSketchOn('XZ')profile001 = startProfileAt(${commonPoints.startAt}, sketch001)
|
||||
|> xLine(${commonPoints.num1}, %)`)
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
|
||||
// simulate network down
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
// simulate network down
|
||||
await u.emulateNetworkConditions({
|
||||
offline: true,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
// Expect the network to be down
|
||||
await expect(networkToggle).toContainText('Problem')
|
||||
|
||||
// Ensure we are not in sketch mode
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
// Ensure we are not in sketch mode
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).toBeVisible()
|
||||
|
||||
// simulate network up
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
// simulate network up
|
||||
await u.emulateNetworkConditions({
|
||||
offline: false,
|
||||
// values of 0 remove any active throttling. crbug.com/456324#c9
|
||||
latency: 0,
|
||||
downloadThroughput: -1,
|
||||
uploadThroughput: -1,
|
||||
})
|
||||
|
||||
// Wait for the app to be ready for use
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
// Wait for the app to be ready for use
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
).not.toBeDisabled({ timeout: 15000 })
|
||||
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||
// Expect the network to be up
|
||||
await expect(networkToggle).toContainText('Connected')
|
||||
await expect(page.getByTestId('loading-stream')).not.toBeAttached()
|
||||
|
||||
// Click off the code pane.
|
||||
await page.mouse.click(100, 100)
|
||||
// Click off the code pane.
|
||||
await page.mouse.click(100, 100)
|
||||
|
||||
// select a line
|
||||
await page.getByText(`startProfileAt(${commonPoints.startAt}, %)`).click()
|
||||
// select a line
|
||||
await page
|
||||
.getByText(`startProfileAt(${commonPoints.startAt}, sketch001)`)
|
||||
.click()
|
||||
|
||||
// enter sketch again
|
||||
await u.doAndWaitForCmd(
|
||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||
'default_camera_get_settings'
|
||||
)
|
||||
await page.waitForTimeout(150)
|
||||
// enter sketch again
|
||||
await u.doAndWaitForCmd(
|
||||
() => page.getByRole('button', { name: 'Edit Sketch' }).click(),
|
||||
'default_camera_get_settings'
|
||||
)
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
// Click the line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
// Click the line tool
|
||||
await page.getByRole('button', { name: 'line Line', exact: true }).click()
|
||||
|
||||
await page.waitForTimeout(150)
|
||||
await page.waitForTimeout(150)
|
||||
|
||||
// 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], %)
|
||||
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')
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line(end = [-12.34, 12.34])
|
||||
|
||||
`)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
await page.waitForTimeout(100)
|
||||
await page.mouse.click(startXPx, 500 - PUR * 20)
|
||||
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
|> startProfileAt([12.34, -12.34], %)
|
||||
await expect.poll(u.normalisedEditorCode)
|
||||
.toBe(`sketch001 = startSketchOn('XZ')
|
||||
profile001 = startProfileAt([12.34, -12.34], sketch001)
|
||||
|> xLine(12.34, %)
|
||||
|> line(end = [-12.34, 12.34])
|
||||
|> xLine(-12.34, %)
|
||||
|
||||
`)
|
||||
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).not.toHaveAttribute('aria-pressed', 'true')
|
||||
// Unequip line tool
|
||||
await page.keyboard.press('Escape')
|
||||
// Make sure we didn't pop out of sketch mode.
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).toBeVisible()
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'line Line', exact: true })
|
||||
).not.toHaveAttribute('aria-pressed', 'true')
|
||||
|
||||
// Exit sketch
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
// Exit sketch
|
||||
await page.keyboard.press('Escape')
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Exit Sketch' })
|
||||
).not.toBeVisible()
|
||||
}
|
||||
)
|
||||
})
|
||||
|
@ -109,7 +109,8 @@ test.describe('Testing Camera Movement', { tag: ['@skipWin'] }, () => {
|
||||
await page.keyboard.down('Shift')
|
||||
await page.mouse.move(600, 200)
|
||||
await page.mouse.down({ button: 'right' })
|
||||
await page.mouse.move(700, 200, { steps: 2 })
|
||||
// Gotcha: remove steps:2 from this 700,200 mouse move. This bricked the test on local host engine.
|
||||
await page.mouse.move(700, 200)
|
||||
await page.mouse.up({ button: 'right' })
|
||||
await page.keyboard.up('Shift')
|
||||
}, [-19, -85, -85])
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
@ -248,82 +249,105 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Solids should be select and deletable', async ({ page, homePage }) => {
|
||||
test('Solids should be select and deletable', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
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 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
@ -346,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)
|
||||
@ -365,43 +390,47 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
`rev = revolve({ axis: 'y' }, part009)`
|
||||
)
|
||||
|
||||
// DELETE PARENT EXTRUDE
|
||||
await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
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(
|
||||
`extrude001 = extrude(sketch001, length = 50)`
|
||||
)
|
||||
await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 0, y = -50, z = 0 },
|
||||
xAxis = { x = 1, y = 0, z = 0 },
|
||||
yAxis = { x = 0, y = 0, z = 1 },
|
||||
zAxis = { x = 0, y = -1, z = 0 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||
yAxis = { x = 0, y = -1, z = 0 },
|
||||
zAxis = { x = 0.58, y = 0, z = 0.81 }
|
||||
}
|
||||
})`)
|
||||
await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||
plane = {
|
||||
origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||
yAxis = { x = 0, y = -1, z = 0 },
|
||||
zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||
}
|
||||
})`)
|
||||
// FIXME (commented section below), this test would select a wall that had a sketch on it, and delete the underlying extrude
|
||||
// and replace the sketch on face with a hard coded custom plane, but since there was a sketch on that plane maybe it
|
||||
// should have delete the sketch? it's broken atm, but not sure if worth fixing since desired behaviour is a little
|
||||
// vague
|
||||
// // DELETE PARENT EXTRUDE
|
||||
// await page.mouse.click(parentExtrude.x, parentExtrude.y)
|
||||
// await page.waitForTimeout(100)
|
||||
// await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
// '|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
// )
|
||||
// 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(
|
||||
// `extrude001 = extrude(sketch001, length = 50)`
|
||||
// )
|
||||
// await expect(u.codeLocator).toContainText(`sketch005 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 0, y = -50, z = 0 },
|
||||
// xAxis = { x = 1, y = 0, z = 0 },
|
||||
// yAxis = { x = 0, y = 0, z = 1 },
|
||||
// zAxis = { x = 0, y = -1, z = 0 }
|
||||
// }
|
||||
// })`)
|
||||
// await expect(u.codeLocator).toContainText(`sketch003 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = 116.53, y = 0, z = 163.25 },
|
||||
// xAxis = { x = -0.81, y = 0, z = 0.58 },
|
||||
// yAxis = { x = 0, y = -1, z = 0 },
|
||||
// zAxis = { x = 0.58, y = 0, z = 0.81 }
|
||||
// }
|
||||
// })`)
|
||||
// await expect(u.codeLocator).toContainText(`sketch002 = startSketchOn({
|
||||
// plane = {
|
||||
// origin = { x = -91.74, y = 0, z = 80.89 },
|
||||
// xAxis = { x = -0.66, y = 0, z = -0.75 },
|
||||
// yAxis = { x = 0, y = -1, z = 0 },
|
||||
// zAxis = { x = -0.75, y = 0, z = 0.66 }
|
||||
// }
|
||||
// })`)
|
||||
|
||||
// DELETE SOLID 2D
|
||||
await page.mouse.click(solid2d.x, solid2d.y)
|
||||
@ -414,16 +443,29 @@ 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,
|
||||
homePage,
|
||||
}) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`sketch001 = startSketchOn('XZ')
|
||||
test.fixme(
|
||||
"Deleting solid that the AST mod can't handle results in a toast message",
|
||||
async ({ page, homePage }) => {
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
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)
|
||||
@ -438,48 +480,49 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
|> line(endAbsolute = [profileStartX(%), profileStartY(%)])
|
||||
|> close()
|
||||
`
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// attempt delete
|
||||
await page.mouse.click(930, 139)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
}, KCL_DEFAULT_LENGTH)
|
||||
await page.setBodyDimensions({ width: 1000, height: 500 })
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]', 10_000)
|
||||
await u.closeDebugPanel()
|
||||
|
||||
await u.openAndClearDebugPanel()
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_look_at',
|
||||
vantage: { x: 1139.49, y: -7053, z: 8597.31 },
|
||||
center: { x: -2206.68, y: -1298.36, z: 60 },
|
||||
up: { x: 0, y: 0, z: 1 },
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
await u.sendCustomCmd({
|
||||
type: 'modeling_cmd_req',
|
||||
cmd_id: uuidv4(),
|
||||
cmd: {
|
||||
type: 'default_camera_get_settings',
|
||||
},
|
||||
})
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
// attempt delete
|
||||
await page.mouse.click(930, 139)
|
||||
await page.waitForTimeout(100)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(
|
||||
'|> line(end = [170.36, -121.61], tag = $seg01)'
|
||||
)
|
||||
await u.clearCommandLogs()
|
||||
await page.keyboard.press('Backspace')
|
||||
|
||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||
})
|
||||
await expect(page.getByText('Unable to delete selection')).toBeVisible()
|
||||
}
|
||||
)
|
||||
test('Hovering over 3d features highlights code, clicking puts the cursor in the right place and sends selection id to engine', async ({
|
||||
page,
|
||||
homePage,
|
||||
@ -902,6 +945,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
test('Testing selections (and hovers) work on sketches when NOT in sketch mode', async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
const cases = [
|
||||
{
|
||||
@ -937,6 +981,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.sendCustomCmd({
|
||||
@ -970,6 +1015,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
test("Hovering and selection of extruded faces works, and is not overridden shortly after user's click", async ({
|
||||
page,
|
||||
homePage,
|
||||
scene,
|
||||
}) => {
|
||||
await page.addInitScript(async () => {
|
||||
localStorage.setItem(
|
||||
@ -988,6 +1034,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await scene.waitForExecutionDone()
|
||||
await u.openAndClearDebugPanel()
|
||||
|
||||
await u.sendCustomCmd({
|
||||
@ -1021,19 +1068,19 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
.poll(() => u.getGreatestPixDiff(extrudeWall, noHoverColor))
|
||||
.toBeLessThan(15)
|
||||
await page.mouse.move(nothing.x, nothing.y)
|
||||
await page.waitForTimeout(100)
|
||||
await page.waitForTimeout(1000)
|
||||
await page.mouse.move(extrudeWall.x, extrudeWall.y)
|
||||
await expect(page.getByTestId('hover-highlight').first()).toBeVisible()
|
||||
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
||||
removeAfterFirstParenthesis(extrudeText)
|
||||
)
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(extrudeWall, hoverColor)
|
||||
).toBeLessThan(15)
|
||||
await page.mouse.click(extrudeWall.x, extrudeWall.y)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${extrudeText}`)
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(
|
||||
await u.getGreatestPixDiff(extrudeWall, selectColor)
|
||||
).toBeLessThan(15)
|
||||
@ -1044,7 +1091,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
).toBeLessThan(15)
|
||||
|
||||
await page.mouse.move(nothing.x, nothing.y)
|
||||
await page.waitForTimeout(300)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(page.getByTestId('hover-highlight')).not.toBeVisible()
|
||||
|
||||
// because of shading, color is not exact everywhere on the face
|
||||
@ -1058,11 +1105,11 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
await expect(page.getByTestId('hover-highlight').first()).toContainText(
|
||||
removeAfterFirstParenthesis(capText)
|
||||
)
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(await u.getGreatestPixDiff(cap, hoverColor)).toBeLessThan(15)
|
||||
await page.mouse.click(cap.x, cap.y)
|
||||
await expect(page.locator('.cm-activeLine')).toHaveText(`|> ${capText}`)
|
||||
await page.waitForTimeout(200)
|
||||
await page.waitForTimeout(1000)
|
||||
await expect(await u.getGreatestPixDiff(cap, selectColor)).toBeLessThan(15)
|
||||
await page.waitForTimeout(1000)
|
||||
// check color stays there, i.e. not overridden (this was a bug previously)
|
||||
@ -1109,7 +1156,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
|> line(end = [4.95, -8])
|
||||
|> line(end = [-20.38, -10.12])
|
||||
|> line(end = [-15.79, 17.08])
|
||||
|
||||
|
||||
fn yohey = (pos) => {
|
||||
sketch004 = startSketchOn('XZ')
|
||||
${extrudeAndEditBlockedInFunction}
|
||||
@ -1119,7 +1166,7 @@ test.describe('Testing selections', { tag: ['@skipWin'] }, () => {
|
||||
|> line(end = [-15.79, 17.08])
|
||||
return ''
|
||||
}
|
||||
|
||||
|
||||
yohey([15.79, -34.6])
|
||||
`
|
||||
)
|
||||
@ -1211,12 +1258,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()
|
||||
@ -1228,14 +1278,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(
|
||||
|
@ -32,15 +32,18 @@ test.fixme('Units menu', async ({ page, homePage }) => {
|
||||
await expect(unitsMenuButton).toContainText('mm')
|
||||
})
|
||||
|
||||
test('Successful export shows a success toast', async ({ page, homePage }) => {
|
||||
// FYI this test doesn't work with only engine running locally
|
||||
// And you will need to have the KittyCAD CLI installed
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`topAng = 25
|
||||
test(
|
||||
'Successful export shows a success toast',
|
||||
{ tag: '@skipLocalEngine' },
|
||||
async ({ page, homePage }) => {
|
||||
// FYI this test doesn't work with only engine running locally
|
||||
// And you will need to have the KittyCAD CLI installed
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
;(window as any).playwrightSkipFilePicker = true
|
||||
localStorage.setItem(
|
||||
'persistCode',
|
||||
`topAng = 25
|
||||
bottomAng = 35
|
||||
baseLen = 3.5
|
||||
baseHeight = 1
|
||||
@ -78,26 +81,27 @@ part001 = startSketchOn('-XZ')
|
||||
|> xLineTo(ZERO, %)
|
||||
|> close()
|
||||
|> extrude(length = 4)`
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.waitForCmdReceive('extrude')
|
||||
await page.waitForTimeout(1000)
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await doExport(
|
||||
{
|
||||
type: 'gltf',
|
||||
storage: 'embedded',
|
||||
presentation: 'pretty',
|
||||
},
|
||||
page
|
||||
)
|
||||
})
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.waitForCmdReceive('extrude')
|
||||
await page.waitForTimeout(1000)
|
||||
await u.clearAndCloseDebugPanel()
|
||||
|
||||
await doExport(
|
||||
{
|
||||
type: 'gltf',
|
||||
storage: 'embedded',
|
||||
presentation: 'pretty',
|
||||
},
|
||||
page
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test('Paste should not work unless an input is focused', async ({
|
||||
page,
|
||||
@ -205,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.
|
||||
@ -215,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.
|
||||
@ -444,7 +465,7 @@ test('Delete key does not navigate back', async ({ page, homePage }) => {
|
||||
await expect.poll(() => page.url()).not.toContain('/settings')
|
||||
})
|
||||
|
||||
test('Sketch on face', async ({ page, homePage }) => {
|
||||
test('Sketch on face', async ({ page, homePage, scene, cmdBar }) => {
|
||||
test.setTimeout(90_000)
|
||||
const u = await getUtils(page)
|
||||
await page.addInitScript(async () => {
|
||||
@ -470,11 +491,7 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
||||
await page.setBodyDimensions({ width: 1200, height: 500 })
|
||||
|
||||
await homePage.goToModelingScene()
|
||||
|
||||
// wait for execution done
|
||||
await u.openDebugPanel()
|
||||
await u.expectCmdLog('[data-message-type="execution-done"]')
|
||||
await u.closeDebugPanel()
|
||||
await scene.waitForExecutionDone()
|
||||
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'Start Sketch' })
|
||||
@ -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()
|
||||
@ -579,10 +595,9 @@ extrude001 = extrude(sketch001, length = 5 + 7)`
|
||||
await expect(page.getByTestId('command-bar')).toBeVisible()
|
||||
await page.waitForTimeout(100)
|
||||
|
||||
await page.getByRole('button', { name: 'arrow right Continue' }).click()
|
||||
await page.waitForTimeout(100)
|
||||
await cmdBar.progressCmdBar()
|
||||
await expect(page.getByText('Confirm Extrude')).toBeVisible()
|
||||
await page.getByRole('button', { name: 'checkmark Submit command' }).click()
|
||||
await cmdBar.progressCmdBar()
|
||||
|
||||
const result2 = result.genNext`
|
||||
const sketch002 = extrude(sketch002, length = ${[5, 5]} + 7)`
|
||||
|
@ -16,8 +16,7 @@ mac:
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
notarize:
|
||||
teamId: 92H8YB3B95
|
||||
notarize: true
|
||||
fileAssociations:
|
||||
- ext: kcl
|
||||
name: kcl
|
||||
@ -32,14 +31,11 @@ win:
|
||||
arch:
|
||||
- x64
|
||||
- arm64
|
||||
# - target: msi
|
||||
# arch:
|
||||
# - x64
|
||||
# - arm64
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
sign: "./scripts/sign-win.js"
|
||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||
signtoolOptions:
|
||||
sign: "./scripts/sign-win.js"
|
||||
signingHashAlgorithms:
|
||||
- sha256
|
||||
publisherName: "KittyCAD Inc" # needs to be exactly like on Digicert
|
||||
icon: "assets/icon.ico"
|
||||
fileAssociations:
|
||||
- ext: kcl
|
||||
@ -47,15 +43,12 @@ win:
|
||||
mimeType: text/vnd.zoo.kcl
|
||||
description: Zoo KCL File
|
||||
role: Editor
|
||||
# msi:
|
||||
# oneClick: false
|
||||
# perMachine: true
|
||||
nsis:
|
||||
oneClick: false
|
||||
perMachine: true
|
||||
allowElevation: true
|
||||
installerIcon: "assets/icon.ico"
|
||||
include: "./installer.nsh"
|
||||
include: "./scripts/installer.nsh"
|
||||
linux:
|
||||
artifactName: "${productName}-${version}-${arch}-${os}.${ext}"
|
||||
target:
|
||||
|
23
package.json
@ -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",
|
||||
@ -40,7 +40,7 @@
|
||||
"codemirror": "^6.0.1",
|
||||
"decamelize": "^6.0.0",
|
||||
"diff": "^7.0.0",
|
||||
"electron-updater": "6.3.0",
|
||||
"electron-updater": "^6.5.0",
|
||||
"fuse.js": "^7.0.0",
|
||||
"html2canvas-pro": "^1.5.8",
|
||||
"isomorphic-fetch": "^3.0.0",
|
||||
@ -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-shell/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/offset-plane-kwargs/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",
|
||||
@ -120,6 +120,7 @@
|
||||
"test:playwright:electron:windows:local": "yarn tronb:vite:dev && set NODE_ENV='development' && playwright test --config=playwright.electron.config.ts --grep-invert=\"@skipWin|@snapshot\"",
|
||||
"test:playwright:electron:macos:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipMacos|@snapshot'",
|
||||
"test:playwright:electron:ubuntu:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot'",
|
||||
"test:playwright:electron:ubuntu:engine:local": "yarn tronb:vite:dev && NODE_ENV=development playwright test --config=playwright.electron.config.ts --grep-invert='@skipLinux|@snapshot|@skipLocalEngine'",
|
||||
"test:unit:local": "yarn simpleserver:bg && yarn test:unit; kill-port 3000",
|
||||
"test:unit:kcl-samples:local": "yarn simpleserver:bg && yarn test:unit:kcl-samples; kill-port 3000"
|
||||
},
|
||||
@ -144,10 +145,11 @@
|
||||
"devDependencies": {
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@babel/preset-env": "^7.25.4",
|
||||
"@electron-forge/cli": "7.4.0",
|
||||
"@electron-forge/plugin-fuses": "7.4.0",
|
||||
"@electron-forge/plugin-vite": "7.4.0",
|
||||
"@electron/fuses": "1.8.0",
|
||||
"@electron-forge/cli": "^7.6.1",
|
||||
"@electron-forge/plugin-fuses": "^7.6.1",
|
||||
"@electron-forge/plugin-vite": "^7.6.1",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@electron/notarize": "^2.5.0",
|
||||
"@iarna/toml": "^2.2.5",
|
||||
"@lezer/generator": "^1.7.2",
|
||||
"@nabla/vite-plugin-eslint": "^2.0.5",
|
||||
@ -174,9 +176,8 @@
|
||||
"@vitest/web-worker": "^1.5.0",
|
||||
"@xstate/cli": "^0.5.17",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"electron": "32.1.2",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-notarize": "1.2.2",
|
||||
"electron": "^34.1.1",
|
||||
"electron-builder": "^26.0.6",
|
||||
"eslint": "^8.0.1",
|
||||
"eslint-plugin-css-modules": "^2.12.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
@ -200,7 +201,7 @@
|
||||
"tailwindcss": "^3.4.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript-eslint": "^8.19.1",
|
||||
"typescript-eslint": "^8.23.0",
|
||||
"vite": "^5.4.12",
|
||||
"vite-plugin-package-version": "^1.1.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
|
@ -1,4 +1,5 @@
|
||||
@precedence {
|
||||
annotation
|
||||
member
|
||||
call
|
||||
exp @left
|
||||
@ -20,9 +21,12 @@ statement[@isGroup=Statement] {
|
||||
FunctionDeclaration { kw<"export">? kw<"fn"> VariableDefinition Equals? ParamList Arrow? Body } |
|
||||
VariableDeclaration { kw<"export">? (kw<"var"> | kw<"let"> | kw<"const">)? VariableDefinition Equals expression } |
|
||||
ReturnStatement { kw<"return"> expression } |
|
||||
ExpressionStatement { expression }
|
||||
ExpressionStatement { expression } |
|
||||
Annotation { AnnotationName AnnotationList? }
|
||||
}
|
||||
|
||||
AnnotationList { !annotation "(" commaSep<AnnotationProperty> ")" }
|
||||
|
||||
ParamList { "(" commaSep<Parameter { VariableDefinition "?"? (":" type)? }> ")" }
|
||||
|
||||
Body { "{" statement* "}" }
|
||||
@ -59,6 +63,12 @@ UnaryOp { AddOp | BangOp }
|
||||
|
||||
ObjectProperty { PropertyName (":" | Equals) expression }
|
||||
|
||||
AnnotationProperty {
|
||||
PropertyName
|
||||
( AddOp | MultOp | ExpOp | LogicOp | BangOp | CompOp | Equals | Arrow | PipeOperator | PipeSubstitution )
|
||||
expression
|
||||
}
|
||||
|
||||
LabeledArgument { ArgumentLabel Equals expression }
|
||||
|
||||
ArgumentList { "(" commaSep<LabeledArgument | expression> ")" }
|
||||
@ -105,6 +115,7 @@ commaSep1NoTrailingComma<term> { term ("," term)* }
|
||||
PipeSubstitution { "%" }
|
||||
|
||||
identifier { (@asciiLetter | "_") (@asciiLetter | @digit | "_")* }
|
||||
AnnotationName { "@" identifier? }
|
||||
PropertyName { identifier }
|
||||
TagDeclarator { "$" identifier }
|
||||
|
||||
|
153
packages/codemirror-lang-kcl/test/annotation.txt
Normal file
@ -0,0 +1,153 @@
|
||||
# alone
|
||||
|
||||
@a
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName))
|
||||
|
||||
# alone and anonymous
|
||||
|
||||
@
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName))
|
||||
|
||||
# empty
|
||||
|
||||
@ann()
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# empty and anonymous
|
||||
|
||||
@()
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# equals
|
||||
|
||||
@setting(a=1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# operator
|
||||
|
||||
@ann(a*1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# anonymous
|
||||
|
||||
@(a=1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# complex expr
|
||||
|
||||
@ann(a=(1+2+f('yes')))
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
ParenthesizedExpression(BinaryExpression(BinaryExpression(Number,
|
||||
AddOp,
|
||||
Number),
|
||||
AddOp,
|
||||
CallExpression(VariableName,
|
||||
ArgumentList(String))))))))
|
||||
|
||||
# many args
|
||||
|
||||
@ann(a=1, b=2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number),
|
||||
AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# space around op
|
||||
|
||||
@ann(a / 1)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# space around sep
|
||||
|
||||
@ann(a/1 , b/2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number),
|
||||
AnnotationProperty(PropertyName,
|
||||
MultOp,
|
||||
Number))))
|
||||
|
||||
# trailing sep
|
||||
|
||||
@ann(a=1,)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
||||
|
||||
# lone sep
|
||||
|
||||
@ann(,)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList))
|
||||
|
||||
# inside fn
|
||||
|
||||
fn f() {
|
||||
@anno(b=2)
|
||||
}
|
||||
|
||||
==>
|
||||
Program(FunctionDeclaration(fn,
|
||||
VariableDefinition,
|
||||
ParamList,
|
||||
Body(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))))
|
||||
|
||||
# laxer with space than the language parser is
|
||||
|
||||
@anno (b=2)
|
||||
|
||||
==>
|
||||
Program(Annotation(AnnotationName,
|
||||
AnnotationList(AnnotationProperty(PropertyName,
|
||||
Equals,
|
||||
Number))))
|
@ -29,7 +29,7 @@
|
||||
"vscode-uri": "^3.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.10.6",
|
||||
"@types/node": "^22.13.1",
|
||||
"ts-node": "^10.9.2"
|
||||
}
|
||||
}
|
||||
|
@ -109,10 +109,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9"
|
||||
integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==
|
||||
|
||||
"@types/node@^22.10.6":
|
||||
version "22.10.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.6.tgz#5c6795e71635876039f853cbccd59f523d9e4239"
|
||||
integrity sha512-qNiuwC4ZDAUNcY47xgaSuS92cjf8JbSUoaKS77bmLG1rU7MlATVSiw/IlrjtIyyskXBZ8KkNfjK/P5na7rgXbQ==
|
||||
"@types/node@^22.13.1":
|
||||
version "22.13.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.1.tgz#a2a3fefbdeb7ba6b89f40371842162fac0934f33"
|
||||
integrity sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==
|
||||
dependencies:
|
||||
undici-types "~6.20.0"
|
||||
|
||||
|
@ -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",
|
||||
|
@ -11,6 +11,7 @@ echo "$PACKAGE" > package.json
|
||||
# electron-builder.yml
|
||||
yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/nightly"' electron-builder.yml
|
||||
yq -i '.appId = "dev.zoo.modeling-app-nightly"' electron-builder.yml
|
||||
yq -i '.nsis.include = "./scripts/installer-nightly.nsh"' electron-builder.yml
|
||||
|
||||
# Release notes
|
||||
echo "Nightly build $VERSION (commit $COMMIT)" > release-notes.md
|
||||
|
8
scripts/installer-nightly.nsh
Normal file
@ -0,0 +1,8 @@
|
||||
!macro preInit
|
||||
SetRegView 64
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||
SetRegView 32
|
||||
WriteRegExpandStr HKLM "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||
WriteRegExpandStr HKCU "${INSTALL_REGISTRY_KEY}" InstallLocation "C:\Program Files\Zoo Modeling App (Nightly)"
|
||||
!macroend
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
})
|
||||
|
@ -196,6 +196,7 @@ function ReviewingButton() {
|
||||
type="submit"
|
||||
form="review-form"
|
||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||
data-testid="command-bar-submit"
|
||||
iconStart={{
|
||||
icon: 'checkmark',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
@ -214,6 +215,7 @@ function GatheringArgsButton() {
|
||||
type="submit"
|
||||
form="arg-form"
|
||||
className="w-fit !p-0 rounded-sm hover:shadow"
|
||||
data-testid="command-bar-continue"
|
||||
iconStart={{
|
||||
icon: 'arrowRight',
|
||||
bgClassName: 'p-1 rounded-sm !bg-primary hover:brightness-110',
|
||||
|
@ -20,6 +20,7 @@ import { createIdentifier, createVariableDeclaration } from 'lang/modifyAst'
|
||||
import { useCodeMirror } from 'components/ModelingSidebar/ModelingPanes/CodeEditor'
|
||||
import { useSelector } from '@xstate/react'
|
||||
import { commandBarActor, useCommandBarState } from 'machines/commandBarMachine'
|
||||
import toast from 'react-hot-toast'
|
||||
|
||||
const machineContextSelector = (snapshot?: {
|
||||
context: Record<string, unknown>
|
||||
@ -97,6 +98,7 @@ function CommandBarKclInput({
|
||||
value,
|
||||
initialVariableName,
|
||||
})
|
||||
|
||||
const varMentionData: Completion[] = prevVariables.map((v) => ({
|
||||
label: v.key,
|
||||
detail: String(roundOff(v.value as number)),
|
||||
@ -170,7 +172,15 @@ function CommandBarKclInput({
|
||||
|
||||
function handleSubmit(e?: React.FormEvent<HTMLFormElement>) {
|
||||
e?.preventDefault()
|
||||
if (!canSubmit || valueNode === null) return
|
||||
if (!canSubmit || valueNode === null) {
|
||||
// Gotcha: Our application can attempt to submit a command value before the command bar kcl input is ready. Notify the scene and user.
|
||||
if (!canSubmit) {
|
||||
toast.error('Unable to submit command')
|
||||
} else if (valueNode === null) {
|
||||
toast.error('Unable to submit undefined command value')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
onSubmit(
|
||||
createNewVariable
|
||||
|
@ -2,11 +2,15 @@ import { Dialog } from '@headlessui/react'
|
||||
import { ActionButton } from './ActionButton'
|
||||
import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
|
||||
import { useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { CREATE_FILE_URL_PARAM } from 'lib/constants'
|
||||
|
||||
const DownloadAppBanner = () => {
|
||||
const [searchParams] = useSearchParams()
|
||||
const hasCreateFileParam = searchParams.has(CREATE_FILE_URL_PARAM)
|
||||
const { settings } = useSettingsAuthContext()
|
||||
const [isBannerDismissed, setIsBannerDismissed] = useState(
|
||||
settings.context.app.dismissWebBanner.current
|
||||
settings.context.app.dismissWebBanner.current || hasCreateFileParam
|
||||
)
|
||||
|
||||
return (
|
||||
|
@ -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)
|
||||
|
@ -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,31 @@ 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 {
|
||||
KclValue,
|
||||
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 +100,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 +274,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 +311,7 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
sketchDetails: {
|
||||
...sketchDetails,
|
||||
sketchPathToNode: event.data,
|
||||
sketchEntryNodePath: event.data,
|
||||
},
|
||||
}
|
||||
}),
|
||||
@ -329,11 +353,83 @@ export const ModelingMachineProvider = ({
|
||||
otherSelections: [],
|
||||
}
|
||||
} else if (setSelections.selection && editorManager.isShiftDown) {
|
||||
// selecting and deselecting multiple objects
|
||||
|
||||
/**
|
||||
* There are two scenarios:
|
||||
* 1. General case:
|
||||
* When selecting and deselecting edges,
|
||||
* faces or segment (during sketch edit)
|
||||
* we use its artifact ID to identify the selection
|
||||
* 2. Initial sketch setup:
|
||||
* The artifact is not yet created
|
||||
* so we use the codeRef.range
|
||||
*/
|
||||
|
||||
let updatedSelections: typeof selectionRanges.graphSelections
|
||||
|
||||
// 1. General case: Artifact exists, use its ID
|
||||
if (setSelections.selection.artifact?.id) {
|
||||
// check if already selected
|
||||
const alreadySelected = selectionRanges.graphSelections.some(
|
||||
(selection) =>
|
||||
selection.artifact?.id ===
|
||||
setSelections.selection?.artifact?.id
|
||||
)
|
||||
if (
|
||||
alreadySelected &&
|
||||
setSelections.selection?.artifact?.id
|
||||
) {
|
||||
// remove it
|
||||
updatedSelections = selectionRanges.graphSelections.filter(
|
||||
(selection) =>
|
||||
selection.artifact?.id !==
|
||||
setSelections.selection?.artifact?.id
|
||||
)
|
||||
} else {
|
||||
// add it
|
||||
updatedSelections = [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
]
|
||||
}
|
||||
} else {
|
||||
// 2. Initial sketch setup: Artifact not yet created – use codeRef.range
|
||||
const selectionRange = JSON.stringify(
|
||||
setSelections.selection?.codeRef?.range
|
||||
)
|
||||
|
||||
// check if already selected
|
||||
const alreadySelected = selectionRanges.graphSelections.some(
|
||||
(selection) => {
|
||||
const existingRange = JSON.stringify(
|
||||
selection.codeRef?.range
|
||||
)
|
||||
return existingRange === selectionRange
|
||||
}
|
||||
)
|
||||
|
||||
if (
|
||||
alreadySelected &&
|
||||
setSelections.selection?.codeRef?.range
|
||||
) {
|
||||
// remove it
|
||||
updatedSelections = selectionRanges.graphSelections.filter(
|
||||
(selection) =>
|
||||
JSON.stringify(selection.codeRef?.range) !==
|
||||
selectionRange
|
||||
)
|
||||
} else {
|
||||
// add it
|
||||
updatedSelections = [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
selections = {
|
||||
graphSelections: [
|
||||
...selectionRanges.graphSelections,
|
||||
setSelections.selection,
|
||||
],
|
||||
graphSelections: updatedSelections,
|
||||
otherSelections: selectionRanges.otherSelections,
|
||||
}
|
||||
}
|
||||
@ -411,9 +507,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 ||
|
||||
[],
|
||||
},
|
||||
}
|
||||
@ -566,7 +670,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,
|
||||
@ -594,13 +703,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: () => {},
|
||||
@ -610,7 +739,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'
|
||||
@ -637,7 +766,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,
|
||||
@ -658,7 +789,9 @@ export const ModelingMachineProvider = ({
|
||||
)
|
||||
|
||||
return {
|
||||
sketchPathToNode: pathToNode,
|
||||
sketchEntryNodePath: [],
|
||||
planeNodePath: pathToNode,
|
||||
sketchNodePaths: [],
|
||||
zAxis: input.zAxis,
|
||||
yAxis: input.yAxis,
|
||||
origin: [0, 0, 0],
|
||||
@ -667,21 +800,70 @@ export const ModelingMachineProvider = ({
|
||||
}),
|
||||
'animate-to-sketch': fromPromise(
|
||||
async ({ input: { selectionRanges } }) => {
|
||||
const sourceRange =
|
||||
selectionRanges.graphSelections[0]?.codeRef?.range
|
||||
const sketchPathToNode = getNodePathFromSourceRange(
|
||||
kclManager.ast,
|
||||
sourceRange
|
||||
)
|
||||
const info = await getSketchOrientationDetails(
|
||||
sketchPathToNode || []
|
||||
const plane = getPlaneFromArtifact(
|
||||
selectionRanges.graphSelections[0].artifact,
|
||||
engineCommandManager.artifactGraph
|
||||
)
|
||||
if (err(plane)) return Promise.reject(plane)
|
||||
let sketch: KclValue | null = null
|
||||
for (const variable of Object.values(
|
||||
kclManager.execState.variables
|
||||
)) {
|
||||
// find programMemory that matches path artifact
|
||||
if (
|
||||
variable?.type === 'Sketch' &&
|
||||
variable.value.artifactId === plane.pathIds[0]
|
||||
) {
|
||||
sketch = variable
|
||||
break
|
||||
}
|
||||
if (
|
||||
// if the variable is an sweep, check if the underlying sketch matches the artifact
|
||||
variable?.type === 'Solid' &&
|
||||
variable.value.sketch.on.type === 'plane' &&
|
||||
variable.value.sketch.artifactId === plane.pathIds[0]
|
||||
) {
|
||||
sketch = {
|
||||
type: 'Sketch',
|
||||
value: variable.value.sketch,
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!sketch || sketch.type !== 'Sketch')
|
||||
return Promise.reject(new Error('No sketch'))
|
||||
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(
|
||||
@ -694,7 +876,7 @@ export const ModelingMachineProvider = ({
|
||||
|
||||
'Get horizontal info': fromPromise(
|
||||
async ({ input: { selectionRanges, sketchDetails } }) => {
|
||||
const { modifiedAst, pathToNodeMap } =
|
||||
const { modifiedAst, pathToNodeMap, exprInsertIndex } =
|
||||
await applyConstraintHorzVertDistance({
|
||||
constraint: 'setHorzDistance',
|
||||
selectionRanges,
|
||||
@ -706,13 +888,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,
|
||||
@ -733,13 +925,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,
|
||||
@ -750,13 +944,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,
|
||||
@ -777,7 +981,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -787,14 +993,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'))
|
||||
@ -803,13 +1010,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,
|
||||
@ -830,7 +1047,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -845,20 +1064,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,
|
||||
@ -879,13 +1108,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,
|
||||
})
|
||||
@ -895,13 +1126,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,
|
||||
@ -922,13 +1162,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,
|
||||
@ -939,13 +1181,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,
|
||||
@ -966,13 +1217,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,
|
||||
@ -983,13 +1236,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,
|
||||
@ -1010,7 +1272,9 @@ export const ModelingMachineProvider = ({
|
||||
return {
|
||||
selectionType: 'completeSelection',
|
||||
selection,
|
||||
updatedPathToNode,
|
||||
updatedSketchEntryNodePath,
|
||||
updatedSketchNodePaths,
|
||||
updatedPlaneNodePath,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1030,9 +1294,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
|
||||
@ -1062,6 +1328,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,
|
||||
@ -1092,10 +1359,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,
|
||||
@ -1116,7 +1395,194 @@ 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]
|
||||
}
|
||||
|
||||
if (doesNeedSplitting) {
|
||||
await kclManager.executeAstMock(moddedAst)
|
||||
await codeManager.updateEditorWithAstAndWriteToFile(moddedAst)
|
||||
}
|
||||
return {
|
||||
updatedEntryNodePath: pathToProfile,
|
||||
updatedSketchNodePaths: updatedSketchNodePaths,
|
||||
updatedPlaneNodePath: sketchDetails.planeNodePath,
|
||||
expressionIndexToDelete: -1,
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -1127,6 +1593,7 @@ export const ModelingMachineProvider = ({
|
||||
selections: input.selection,
|
||||
token,
|
||||
artifactGraph: engineCommandManager.artifactGraph,
|
||||
projectName: context.project.name,
|
||||
})
|
||||
}),
|
||||
},
|
||||
|
@ -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: {
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|