Compare commits

..

9 Commits

Author SHA1 Message Date
7fd9eb3322 Remove node wrapper around NonCodeMeta
Trying to fix TS unit test errors deserializing JSON AST in Rust.
2024-10-28 17:46:02 -04:00
5aef2b72cb Rename field to avoid name collisions 2024-10-28 17:46:02 -04:00
8ccdf2286d Add minimal failing test 2024-10-28 16:52:07 -04:00
054e235362 Fix wasm TS types 2024-10-28 16:26:04 -04:00
3eebd36a41 Fix tsc errors 2024-10-26 12:31:36 -04:00
412417411b Fix ts_rs bindings 2024-10-26 10:56:06 -04:00
a1c9dd99cf Fix yarn build:wasm 2024-10-25 21:17:12 -04:00
3ed873b6e9 Fix formatting 2024-10-25 21:16:52 -04:00
a172e606b4 WIP
Signed-off-by: Nick Cameron <nrc@ncameron.org>
2024-10-24 14:02:43 +13:00
311 changed files with 28424 additions and 62434 deletions

View File

@ -15,7 +15,6 @@ on:
env:
CUT_RELEASE_PR: ${{ github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
BUILD_RELEASE: ${{ github.event_name == 'release' || github.event_name == 'schedule' || github.event_name == 'pull_request' && (contains(github.event.pull_request.title, 'Cut release v')) }}
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
@ -26,6 +25,7 @@ jobs:
runs-on: ubuntu-22.04 # seperate job on Ubuntu for easy string manipulations (compared to Windows)
outputs:
version: ${{ steps.export_version.outputs.version }}
notes: ${{ steps.export_version.outputs.notes }}
steps:
- uses: actions/checkout@v4
@ -55,6 +55,8 @@ jobs:
# TODO: see if we need to inject updater nightly URL here https://dl.zoo.dev/releases/modeling-app/nightly/last_update.json
- name: Generate release notes
env:
NOTES: ${{ github.event_name == 'release' && github.event.release.body || format('Non-release build, commit {0}', github.sha) }}
run: |
echo "$NOTES" > release-notes.md
cat release-notes.md
@ -82,6 +84,9 @@ jobs:
path: |
electron-builder.yml
- id: export_notes
run: echo "notes=`cat release-notes.md'`" >> "$GITHUB_OUTPUT"
- name: Prepare electron-builder.yml file for updater test
if: ${{ env.CUT_RELEASE_PR == 'true' }}
run: |
@ -109,8 +114,17 @@ jobs:
platform: linux
runs-on: ${{ matrix.os }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D
steps:
- uses: actions/checkout@v4
@ -172,25 +186,8 @@ jobs:
smksp_cert_sync.exe
shell: cmd
- name: Build the app (debug)
if: ${{ env.BUILD_RELEASE == 'false' }}
# electron-builder doesn't have a concept of release vs debug,
# this is just not doing any codesign or release yml generation
run: yarn electron-builder --config
- name: Build the app (release)
if: ${{ env.BUILD_RELEASE == 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
- name: Build the app
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
- name: List artifacts in out/
run: ls -R out
@ -234,17 +231,7 @@ jobs:
- name: Build the app (updater-test)
if: ${{ env.CUT_RELEASE_PR == 'true' }}
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }}
CSC_FOR_PULL_REQUEST: true
WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }}
run: yarn electron-builder --config --publish always
run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }}
- uses: actions/upload-artifact@v3
if: ${{ env.CUT_RELEASE_PR == 'true' }}
@ -275,6 +262,7 @@ jobs:
VERSION_NO_V: ${{ needs.prepare-files.outputs.version }}
VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }}
PUB_DATE: ${{ github.event_name == 'release' && github.event.release.created_at || github.event.repository.updated_at }}
NOTES: ${{ needs.prepare-files.outputs.notes }}
BUCKET_DIR: ${{ github.event_name == 'schedule' && 'dl.kittycad.io/releases/modeling-app/nightly' || 'dl.kittycad.io/releases/modeling-app' }}
WEBSITE_DIR: ${{ github.event_name == 'schedule' && 'dl.zoo.dev/releases/modeling-app/nightly' || 'dl.zoo.dev/releases/modeling-app' }}
URL_CODED_NAME: ${{ github.event_name == 'schedule' && 'Zoo%20Modeling%20App%20%28Nightly%29' || 'Zoo%20Modeling%20App' }}

View File

@ -37,4 +37,4 @@ jobs:
# We specifically want to test the disable-println feature
# Since it is not enabled by default, we need to specify it
# This is used in kcl-lsp
cargo check --workspace --features disable-println --features pyo3 --features cli
cargo check --all --features disable-println --features pyo3 --features cli

View File

@ -62,7 +62,7 @@ jobs:
shell: bash
run: |-
cd "${{ matrix.dir }}"
cargo llvm-cov nextest --workspace --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
cargo llvm-cov nextest --all --lcov --output-path lcov.info --test-threads=1 --no-fail-fast -P ci 2>&1 | tee /tmp/github-actions.log
env:
KITTYCAD_API_TOKEN: ${{secrets.KITTYCAD_API_TOKEN}}
RUST_MIN_STACK: 10485760000

2
.nvmrc
View File

@ -1 +1 @@
v21.7.3
v21.7.1

View File

@ -9,7 +9,7 @@ Draw a line segment relative to the current origin using the polar
measure of some angle and distance.
```js
angledLine(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLine(data: AngledLineData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLine(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `data` | [`AngledLineData`](/docs/kcl/types/AngledLineData) | Data to draw an angled line. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Create a line segment from the current 2-dimensional sketch origin
along some angle (in degrees) for some relative length in the 'x' dimension.
```js
angledLineOfXLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLineOfXLength(data: AngledLineData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLineOfXLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -
|----------|------|-------------|----------|
| `data` | [`AngledLineData`](/docs/kcl/types/AngledLineData) | Data to draw an angled line. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Create a line segment from the current 2-dimensional sketch origin
along some angle (in degrees) for some relative length in the 'y' dimension.
```js
angledLineOfYLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLineOfYLength(data: AngledLineData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLineOfYLength(data: AngledLineData, sketch: Sketch, tag?: TagDeclarator) -
|----------|------|-------------|----------|
| `data` | [`AngledLineData`](/docs/kcl/types/AngledLineData) | Data to draw an angled line. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw an angled line from the current origin, constructing a line segment
such that the newly created line intersects the desired target line segment.
```js
angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLineThatIntersects(data: AngledLineThatIntersectsData, sketch: Sketch, tag
|----------|------|-------------|----------|
| `data` | [`AngledLineThatIntersectsData`](/docs/kcl/types/AngledLineThatIntersectsData) | Data for drawing an angled line that intersects with a given line. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Create a line segment from the current 2-dimensional sketch origin
along some angle (in degrees) for some length, ending at the provided value in the 'x' dimension.
```js
angledLineToX(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLineToX(data: AngledLineToData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLineToX(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sk
|----------|------|-------------|----------|
| `data` | [`AngledLineToData`](/docs/kcl/types/AngledLineToData) | Data to draw an angled line to a point. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Create a line segment from the current 2-dimensional sketch origin
along some angle (in degrees) for some length, ending at the provided value in the 'y' dimension.
```js
angledLineToY(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
angledLineToY(data: AngledLineToData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ angledLineToY(data: AngledLineToData, sketch: Sketch, tag?: TagDeclarator) -> Sk
|----------|------|-------------|----------|
| `data` | [`AngledLineToData`](/docs/kcl/types/AngledLineToData) | Data to draw an angled line to a point. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -11,7 +11,7 @@ The arc is constructed such that the current position of the sketch is placed al
Unless this makes a lot of sense and feels like what you're looking for to construct your shape, you're likely looking for tangentialArc.
```js
arc(data: ArcData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
arc(data: ArcData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -21,7 +21,7 @@ arc(data: ArcData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `data` | [`ArcData`](/docs/kcl/types/ArcData) | Data to draw an arc. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw a smooth, continuous, curved line segment from the current origin to
the desired (x, y), using a number of control points to shape the curve's shape.
```js
bezierCurve(data: BezierData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
bezierCurve(data: BezierData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ bezierCurve(data: BezierData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `data` | [`BezierData`](/docs/kcl/types/BezierData) | Data to draw a bezier curve. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Cut a straight transitional edge along a tagged path.
Chamfer is similar in function and use to a fillet, except a fillet will blend the transition along an edge, rather than cut a sharp, straight transitional edge.
```js
chamfer(data: ChamferData, solid: Solid, tag?: TagDeclarator) -> Solid
chamfer(data: ChamferData, solid: Solid, tag?: TagNode) -> Solid
```
@ -19,7 +19,7 @@ chamfer(data: ChamferData, solid: Solid, tag?: TagDeclarator) -> Solid
|----------|------|-------------|----------|
| `data` | [`ChamferData`](/docs/kcl/types/ChamferData) | Data for chamfers. | Yes |
| `solid` | [`Solid`](/docs/kcl/types/Solid) | An solid is a collection of extrude surfaces. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Construct a 2-dimensional circle, of the specified radius, centered at
the provided (x, y) origin point.
```js
circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDeclarator) -> Sketch
circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ circle(data: CircleData, sketch_surface_or_group: SketchOrSurface, tag?: TagDecl
|----------|------|-------------|----------|
| `data` | [`CircleData`](/docs/kcl/types/CircleData) | Data for drawing an circle | Yes |
| `sketch_surface_or_group` | [`SketchOrSurface`](/docs/kcl/types/SketchOrSurface) | A sketch surface or a sketch. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Construct a line segment from the current origin back to the profile's
origin, ensuring the resulting 2-dimensional sketch is not open-ended.
```js
close(sketch: Sketch, tag?: TagDeclarator) -> Sketch
close(sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -18,7 +18,7 @@ close(sketch: Sketch, tag?: TagDeclarator) -> Sketch
| Name | Type | Description | Required |
|----------|------|-------------|----------|
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Blend a transitional edge along a tagged path, smoothing the sharp edge.
Fillet is similar in function and use to a chamfer, except a chamfer will cut a sharp transition along an edge while fillet will smoothly blend the transition.
```js
fillet(data: FilletData, solid: Solid, tag?: TagDeclarator) -> Solid
fillet(data: FilletData, solid: Solid, tag?: TagNode) -> Solid
```
@ -19,7 +19,7 @@ fillet(data: FilletData, solid: Solid, tag?: TagDeclarator) -> Solid
|----------|------|-------------|----------|
| `data` | [`FilletData`](/docs/kcl/types/FilletData) | Data for fillets. | Yes |
| `solid` | [`Solid`](/docs/kcl/types/Solid) | An solid is a collection of extrude surfaces. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -74,12 +74,10 @@ layout: manual
* [`patternTransform`](kcl/patternTransform)
* [`pi`](kcl/pi)
* [`polar`](kcl/polar)
* [`polygon`](kcl/polygon)
* [`pow`](kcl/pow)
* [`profileStart`](kcl/profileStart)
* [`profileStartX`](kcl/profileStartX)
* [`profileStartY`](kcl/profileStartY)
* [`push`](kcl/push)
* [`reduce`](kcl/reduce)
* [`rem`](kcl/rem)
* [`revolve`](kcl/revolve)

View File

@ -9,7 +9,7 @@ Draw a line relative to the current origin to a specified (x, y) away
from the current position.
```js
line(delta: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
line(delta: [number], sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ line(delta: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `delta` | `[number]` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw a line from the current origin to some absolute (x, y) point.
```js
lineTo(to: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
lineTo(to: [number], sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ lineTo(to: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `to` | `[number]` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -9,7 +9,7 @@ Start a new profile at a given point.
```js
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator) -> Sketch
startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ startProfileAt(to: [number], sketch_surface: SketchSurface, tag?: TagDeclarator)
|----------|------|-------------|----------|
| `to` | `[number]` | | Yes |
| `sketch_surface` | [`SketchSurface`](/docs/kcl/types/SketchSurface) | A sketch type. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ Draw a curved line segment along part of an imaginary circle.
The arc is constructed such that the last line segment is placed tangent to the imaginary circle of the specified radius. The resulting arc is the segment of the imaginary circle from that tangent point for 'offset' degrees along the imaginary circle.
```js
tangentialArc(data: TangentialArcData, sketch: Sketch, tag?: TagDeclarator) -> Sketch
tangentialArc(data: TangentialArcData, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ tangentialArc(data: TangentialArcData, sketch: Sketch, tag?: TagDeclarator) -> S
|----------|------|-------------|----------|
| `data` | [`TangentialArcData`](/docs/kcl/types/TangentialArcData) | Data to draw a tangential arc. | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Starting at the current sketch's origin, draw a curved line segment along
some part of an imaginary circle until it reaches the desired (x, y) coordinates.
```js
tangentialArcTo(to: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
tangentialArcTo(to: [number], sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ tangentialArcTo(to: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `to` | `[number]` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Starting at the current sketch's origin, draw a curved line segment along
some part of an imaginary circle until it reaches a point the given (x, y) distance away.
```js
tangentialArcToRelative(delta: [number], sketch: Sketch, tag?: TagDeclarator) -> Sketch
tangentialArcToRelative(delta: [number], sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ tangentialArcToRelative(delta: [number], sketch: Sketch, tag?: TagDeclarator) ->
|----------|------|-------------|----------|
| `delta` | `[number]` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -18,7 +18,7 @@ A base path.
|----------|------|-------------|----------|
| `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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |

View File

@ -22,12 +22,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`Literal`](/docs/kcl/types/Literal)| | No |
| `kind` |[`Literal`](/docs/kcl/types/Literal)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -43,10 +41,9 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -61,13 +58,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`BinaryExpression`](/docs/kcl/types/BinaryExpression)| | No |
| `kind` |[`BinaryExpression`](/docs/kcl/types/BinaryExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -82,13 +76,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`CallExpression`](/docs/kcl/types/CallExpression)| | No |
| `kind` |[`CallExpression`](/docs/kcl/types/CallExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -103,12 +94,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`UnaryExpression`](/docs/kcl/types/UnaryExpression)| | No |
| `kind` |[`UnaryExpression`](/docs/kcl/types/UnaryExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -123,13 +112,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`MemberExpression`](/docs/kcl/types/MemberExpression)| | No |
| `kind` |[`MemberExpression`](/docs/kcl/types/MemberExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -144,14 +130,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`IfExpression`](/docs/kcl/types/IfExpression)| | No |
| `kind` |[`IfExpression`](/docs/kcl/types/IfExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -22,13 +22,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ImportStatement`| | No |
| `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`| | No |
| `path` |`string`| | No |
| `raw_path` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ImportStatement`](/docs/kcl/types/ImportStatement)| | No |
| `kind` |[`ImportStatement`](/docs/kcl/types/ImportStatement)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -43,11 +40,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ExpressionStatement`| | No |
| `expression` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ExpressionStatement`](/docs/kcl/types/ExpressionStatement)| | No |
| `kind` |[`ExpressionStatement`](/docs/kcl/types/ExpressionStatement)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -62,13 +58,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `VariableDeclaration`| | No |
| `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`| | No |
| `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)| | No |
| `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`VariableDeclaration`](/docs/kcl/types/VariableDeclaration)| | No |
| `kind` |[`VariableDeclaration`](/docs/kcl/types/VariableDeclaration)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -83,11 +76,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ReturnStatement`| | No |
| `argument` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ReturnStatement`](/docs/kcl/types/ReturnStatement)| | No |
| `kind` |[`ReturnStatement`](/docs/kcl/types/ReturnStatement)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -28,7 +28,7 @@ A fillet.
| `id` |`string`| The id of the engine command that called this fillet. | No |
| `radius` |`number`| | No |
| `edgeId` |`string`| The engine id of the edge to fillet. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| | No |
----
@ -48,7 +48,7 @@ A chamfer.
| `id` |`string`| The id of the engine command that called this chamfer. | No |
| `length` |`number`| | No |
| `edgeId` |`string`| The engine id of the edge to chamfer. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| | No |
----

View File

@ -16,9 +16,7 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `cond` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| | No |
| `then_val` |[`UnboxedNode_for_Program`](/docs/kcl/types/UnboxedNode_for_Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -16,6 +16,6 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `bindings` |`object`| | No |
| `parent` |`integer`| | No |
| `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |

View File

@ -23,12 +23,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`Literal`](/docs/kcl/types/Literal)| | No |
| `kind` |[`Literal`](/docs/kcl/types/Literal)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -44,10 +42,9 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -63,10 +60,9 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `value` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -81,13 +77,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `BinaryExpression`| | No |
| `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`BinaryExpression`](/docs/kcl/types/BinaryExpression)| | No |
| `kind` |[`BinaryExpression`](/docs/kcl/types/BinaryExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -103,11 +96,9 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)| | No |
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -122,13 +113,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `CallExpression`| | No |
| `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No |
| `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `optional` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`CallExpression`](/docs/kcl/types/CallExpression)| | No |
| `kind` |[`CallExpression`](/docs/kcl/types/CallExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -143,12 +131,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeExpression`| | No |
| `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`PipeExpression`](/docs/kcl/types/PipeExpression)| | No |
| `kind` |[`PipeExpression`](/docs/kcl/types/PipeExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -163,10 +149,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `PipeSubstitution`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`PipeSubstitution`](/docs/kcl/types/PipeSubstitution)| | No |
| `kind` |[`PipeSubstitution`](/docs/kcl/types/PipeSubstitution)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -181,12 +167,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayExpression`| | No |
| `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ArrayExpression`](/docs/kcl/types/ArrayExpression)| | No |
| `kind` |[`ArrayExpression`](/docs/kcl/types/ArrayExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -201,13 +185,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ArrayRangeExpression`| | No |
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ArrayRangeExpression`](/docs/kcl/types/ArrayRangeExpression)| | No |
| `kind` |[`ArrayRangeExpression`](/docs/kcl/types/ArrayRangeExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -222,12 +203,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `ObjectExpression`| | No |
| `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`ObjectExpression`](/docs/kcl/types/ObjectExpression)| | No |
| `kind` |[`ObjectExpression`](/docs/kcl/types/ObjectExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -242,13 +221,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`MemberExpression`](/docs/kcl/types/MemberExpression)| | No |
| `kind` |[`MemberExpression`](/docs/kcl/types/MemberExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -263,12 +239,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `UnaryExpression`| | No |
| `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No |
| `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`UnaryExpression`](/docs/kcl/types/UnaryExpression)| | No |
| `kind` |[`UnaryExpression`](/docs/kcl/types/UnaryExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -283,14 +257,10 @@ An expression can be evaluated to yield a single KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `IfExpression`| | No |
| `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
| `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`| | No |
| `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`IfExpression`](/docs/kcl/types/IfExpression)| | No |
| `kind` |[`IfExpression`](/docs/kcl/types/IfExpression)| An expression can be evaluated to yield a single KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -307,8 +277,8 @@ KCL value for an optional parameter which was not given an argument. (remember,
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `None`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -26,7 +26,7 @@ An extrude plane.
|----------|------|-------------|----------|
| `type` |enum: `extrudePlane`| | No |
| `faceId` |`string`| The face id for the extrude plane. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
@ -46,7 +46,7 @@ An extruded arc.
|----------|------|-------------|----------|
| `type` |enum: `extrudeArc`| | No |
| `faceId` |`string`| The face id for the extrude plane. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
@ -66,7 +66,7 @@ Geometry metadata.
|----------|------|-------------|----------|
| `type` |enum: `chamfer`| | No |
| `faceId` |`string`| The id for the chamfer surface. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |`SourceRange`| The source range. | No |
@ -86,7 +86,7 @@ Geometry metadata.
|----------|------|-------------|----------|
| `type` |enum: `fillet`| | No |
| `faceId` |`string`| The id for the fillet surface. | No |
| `tag` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| The tag. | No |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag. | No |
| `id` |`string`| The id of the geometry. | No |
| `sourceRange` |`SourceRange`| The source range. | No |

View File

@ -16,9 +16,7 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`| | No |
| `body` |[`Program`](/docs/kcl/types/Program)| | No |
| `body` |[`UnboxedNode_for_Program`](/docs/kcl/types/UnboxedNode_for_Program)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -17,7 +17,5 @@ layout: manual
|----------|------|-------------|----------|
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -15,10 +15,8 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No |
| `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No |
| `name` |[`UnboxedNode_for_Identifier`](/docs/kcl/types/UnboxedNode_for_Identifier)| Name of the item to import. | No |
| `alias` |[`UnboxedNode_for_Identifier`](/docs/kcl/types/UnboxedNode_for_Identifier)| Rename the item using an identifier after "as". | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -59,10 +59,9 @@ Any KCL value.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)| | No |
| `value` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`TagDeclarator`](/docs/kcl/types#tag-declaration)| Any KCL value. | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -183,7 +182,7 @@ Data for an imported geometry.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Function`| | No |
| `expression` |[`FunctionExpression`](/docs/kcl/types/FunctionExpression)| Any KCL value. | No |
| `expression` |[`UnboxedNode_for_FunctionExpression`](/docs/kcl/types/UnboxedNode_for_FunctionExpression)| Any KCL value. | No |
| `memory` |[`ProgramMemory`](/docs/kcl/types/ProgramMemory)| Any KCL value. | No |
| `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`| | No |

View File

@ -23,10 +23,9 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -41,12 +40,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Literal`| | No |
| `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| | No |
| `raw` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`Literal`](/docs/kcl/types/Literal)| | No |
| `kind` |[`Literal`](/docs/kcl/types/Literal)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -22,13 +22,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `MemberExpression`| | No |
| `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| | No |
| `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| | No |
| `computed` |`boolean`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `type` |enum: [`MemberExpression`](/docs/kcl/types/MemberExpression)| | No |
| `kind` |[`MemberExpression`](/docs/kcl/types/MemberExpression)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----
@ -44,10 +41,9 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)| | No |
| `name` |`string`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |
| `kind` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
----

View File

@ -16,7 +16,7 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `nonCodeNodes` |`object`| | No |
| `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`| | No |
| `start` |`[` [`UnboxedNode_for_NonCodeNode`](/docs/kcl/types/UnboxedNode_for_NonCodeNode) `]`| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -17,7 +17,5 @@ layout: manual
|----------|------|-------------|----------|
| `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -15,10 +15,8 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `key` |[`Identifier`](/docs/kcl/types/Identifier)| | No |
| `key` |[`UnboxedNode_for_Identifier`](/docs/kcl/types/UnboxedNode_for_Identifier)| | No |
| `value` |[`Expr`](/docs/kcl/types/Expr)| | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -16,7 +16,7 @@ Parameter of a KCL function.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `identifier` |[`Identifier`](/docs/kcl/types/Identifier)| The parameter's label or name. | No |
| `identifier` |[`UnboxedNode_for_Identifier`](/docs/kcl/types/UnboxedNode_for_Identifier)| The parameter's label or name. | No |
| `optional` |`boolean`| Is the parameter optional? | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |

View File

@ -27,7 +27,7 @@ A path that goes to a point.
| `type` |enum: `ToPoint`| | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -49,7 +49,7 @@ A arc that is tangential to the last path segment that goes to a point
| `ccw` |`boolean`| arc's direction | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -71,7 +71,7 @@ A arc that is tangential to the last path segment
| `ccw` |`boolean`| arc's direction | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -94,7 +94,7 @@ a complete arc
| `ccw` |`boolean`| arc's direction | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -115,7 +115,7 @@ A path that is horizontal.
| `x` |`number`| The x coordinate. | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -137,7 +137,7 @@ An angled line to.
| `y` |`number`| The y coordinate. | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |
@ -157,29 +157,7 @@ A base path.
| `type` |enum: `Base`| | 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 circular arc, not necessarily tangential to the current point.
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `Arc`| | No |
| `center` |`[number, number]`| Center of the circle that this arc is drawn on. | No |
| `radius` |`number`| Radius of the circle that this arc is drawn on. | 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 |
| `tag` |[`TagNode`](/docs/kcl/types/TagNode)| The tag of the path. | No |
| `__geoMeta` |[`GeoMeta`](/docs/kcl/types/GeoMeta)| Metadata. | No |

View File

@ -1,24 +0,0 @@
---
title: "PolygonData"
excerpt: "Data for drawing a polygon"
layout: manual
---
Data for drawing a polygon
**Type:** `object`
## Properties
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `radius` |`number`| The radius of the polygon | No |
| `numSides` |`integer`| The number of sides in the polygon | No |
| `center` |`[number, number]`| The center point of the polygon | No |
| `inscribed` |`boolean`| Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius | No |

View File

@ -17,9 +17,7 @@ A KCL program top level, or function body.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`| | No |
| `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No |
| `nonCodeMeta` |[`UnboxedNode_for_NonCodeMeta`](/docs/kcl/types/UnboxedNode_for_NonCodeMeta)| A KCL program top level, or function body. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -16,7 +16,7 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`| | No |
| `currentEnv` |`integer`| | No |
| `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `return` |[`KclValue`](/docs/kcl/types/KclValue)| | No |

View File

@ -16,8 +16,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -25,8 +25,8 @@ A sketch is a collection of paths.
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `type` |enum: `sketch`| | No |
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes). | No |
| `paths` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `id` |`string`| The id of the sketch (this will change when the engine's reference to it changes. | No |
| `value` |`[` [`Path`](/docs/kcl/types/Path) `]`| The paths in the sketch. | No |
| `on` |[`SketchSurface`](/docs/kcl/types/SketchSurface)| What the sketch is on (can be a plane or a face). | No |
| `start` |[`BasePath`](/docs/kcl/types/BasePath)| The starting path. | No |
| `tags` |`object`| Tag identifiers that have been declared in this sketch. | No |

View File

@ -18,7 +18,7 @@ Engine information for a tag.
|----------|------|-------------|----------|
| `id` |`string`| The id of the tagged object. | No |
| `sketch` |`string`| The sketch the tag is on. | No |
| `path` |[`Path`](/docs/kcl/types/Path)| The path the tag is on. | No |
| `path` |[`BasePath`](/docs/kcl/types/BasePath)| The path the tag is on. | No |
| `surface` |[`ExtrudeSurface`](/docs/kcl/types/ExtrudeSurface)| The surface information for the tag. | No |

View File

@ -15,10 +15,10 @@ layout: manual
| Property | Type | Description | Required |
|----------|------|-------------|----------|
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
| `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No |
| `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No |
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
| `start` |`integer`| | No |
| `end` |`integer`| | No |

View File

@ -9,7 +9,7 @@ Draw a line relative to the current origin to a specified distance away
from the current position along the 'x' axis.
```js
xLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
xLine(length: number, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ xLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `length` | `number` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw a line parallel to the X axis, that ends at the given X.
E.g. if the previous line ended at (1, 1), then xLineTo(4) draws a line from (1, 1) to (4, 1)
```js
xLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
xLineTo(to: number, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ xLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `to` | `number` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw a line relative to the current origin to a specified distance away
from the current position along the 'y' axis.
```js
yLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
yLine(length: number, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ yLine(length: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `length` | `number` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -9,7 +9,7 @@ Draw a line parallel to the Y axis, that ends at the given Y.
E.g. if the previous line ended at (1, 1), then yLineTo(4) draws a line from (1, 1) to (1, 4)
```js
yLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
yLineTo(to: number, sketch: Sketch, tag?: TagNode) -> Sketch
```
@ -19,7 +19,7 @@ yLineTo(to: number, sketch: Sketch, tag?: TagDeclarator) -> Sketch
|----------|------|-------------|----------|
| `to` | `number` | | Yes |
| `sketch` | [`Sketch`](/docs/kcl/types/Sketch) | A sketch is a collection of paths. | Yes |
| `tag` | [`TagDeclarator`](/docs/kcl/types#tag-declaration) | | No |
| `tag` | [`TagNode`](/docs/kcl/types/TagNode) | | No |
### Returns

View File

@ -86,7 +86,7 @@ async function doBasicSketch(page: Page, openPanes: string[]) {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`)
|> line([-${commonPoints.num2}, 0], %)`)
}
// deselect line tool

View File

@ -3,7 +3,6 @@ import { test, expect } from './fixtures/fixtureSetup'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import {
createProject,
executorInputPath,
getUtils,
setup,
@ -115,15 +114,20 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } =
await getUtils(tronApp.page, test)
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
renameFile,
editorTextMatches,
} = await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// File the main.kcl with contents
const kclCube = await fsp.readFile(
@ -132,9 +136,6 @@ test.describe('when using the file tree to', () => {
)
await pasteCodeInEditor(kclCube)
// TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk.
await tronApp.page.waitForTimeout(2000)
await renameFile(fromFile, toFile)
await tronApp.page.reload()
@ -163,14 +164,15 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, createNewFile } = await getUtils(tronApp.page, test)
const { panesOpen, createAndSelectProject, createNewFile } =
await getUtils(tronApp.page, test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
await createNewFile('')
await createNewFile('')
@ -193,74 +195,57 @@ test.describe('when using the file tree to', () => {
test(
'create a new file with the same name as an existing file cancels the operation',
{ tag: '@electron' },
async (
{ browser: _, tronApp, homePage, scene, editor, toolbar },
testInfo
) => {
const projectName = 'cube'
const mainFile = 'main.kcl'
const secondFile = 'cylinder.kcl'
const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8')
const kclCylinder = await fsp.readFile(
executorInputPath('cylinder.kcl'),
'utf-8'
)
await tronApp.initialise({
fixtures: { homePage, scene, editor, toolbar },
folderSetupFn: async (dir) => {
const cubeDir = join(dir, projectName)
await fsp.mkdir(cubeDir, { recursive: true })
await fsp.copyFile(
executorInputPath('cube.kcl'),
join(cubeDir, mainFile)
)
await fsp.copyFile(
executorInputPath('cylinder.kcl'),
join(cubeDir, secondFile)
)
},
})
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const {
openKclCodePanel,
openFilePanel,
createAndSelectProject,
pasteCodeInEditor,
createNewFileAndSelect,
renameFile,
selectFile,
editorTextMatches,
waitForPageLoad,
} = await getUtils(tronApp.page, _test)
await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => {
await homePage.expectState({
projectCards: [
{
title: projectName,
fileCount: 2,
folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project
},
],
sortBy: 'last-modified-desc',
})
await homePage.openProject(projectName)
await waitForPageLoad()
await openFilePanel()
await selectFile(secondFile)
})
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => {
await renameFile(secondFile, mainFile)
})
await createAndSelectProject('project-000')
await openKclCodePanel()
await openFilePanel()
// File the main.kcl with contents
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCube)
await test.step(`Postcondition: ${mainFile} still has the original content`, async () => {
await selectFile(mainFile)
const kcl1 = 'main.kcl'
const kcl2 = '2.kcl'
await createNewFileAndSelect(kcl2)
const kclCylinder = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cylinder.kcl',
'utf-8'
)
await pasteCodeInEditor(kclCylinder)
await renameFile(kcl2, kcl1)
await test.step(`Postcondition: ${kcl1} still has the original content`, async () => {
await selectFile(kcl1)
await editorTextMatches(kclCube)
})
await tronApp.page.waitForTimeout(500)
await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => {
await selectFile(secondFile)
await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => {
await selectFile(kcl2)
await editorTextMatches(kclCylinder)
})
await tronApp.close()
await tronApp?.close?.()
}
)
@ -270,15 +255,20 @@ test.describe('when using the file tree to', () => {
async ({ browser: _, tronApp }, testInfo) => {
await tronApp.initialise()
const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } =
await getUtils(tronApp.page, _test)
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
deleteFile,
editorTextMatches,
} = await getUtils(tronApp.page, _test)
await tronApp.page.setViewportSize({ width: 1200, height: 500 })
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// File the main.kcl with contents
const kclCube = await fsp.readFile(
'src/wasm-lib/tests/executor/inputs/cube.kcl',
@ -286,11 +276,11 @@ test.describe('when using the file tree to', () => {
)
await pasteCodeInEditor(kclCube)
const mainFile = 'main.kcl'
const kcl1 = 'main.kcl'
await deleteFile(mainFile)
await deleteFile(kcl1)
await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => {
await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => {
await editorTextMatches('')
})
@ -308,6 +298,7 @@ test.describe('when using the file tree to', () => {
const {
panesOpen,
createAndSelectProject,
pasteCodeInEditor,
createNewFile,
openDebugPanel,
@ -319,7 +310,7 @@ test.describe('when using the file tree to', () => {
tronApp.page.on('console', console.log)
await panesOpen(['files', 'code'])
await createProject({ name: 'project-000', page: tronApp.page })
await createAndSelectProject('project-000')
// Create a small file
const kclCube = await fsp.readFile(
@ -723,7 +714,7 @@ _test.describe('Renaming in the file tree', () => {
})
await _test.step('Rename the folder', async () => {
await page.waitForTimeout(1000)
await page.waitForTimeout(60000)
await folderToRename.click({ button: 'right' })
await _expect(renameMenuItem).toBeVisible()
await renameMenuItem.click()

View File

@ -1,11 +1,6 @@
import type { Page, Locator } from '@playwright/test'
import { expect } from '@playwright/test'
import {
closePane,
checkIfPaneIsOpen,
openPane,
sansWhitespace,
} from '../test-utils'
import { sansWhitespace } from '../test-utils'
interface EditorState {
activeLines: Array<string>
@ -16,7 +11,6 @@ interface EditorState {
export class EditorFixture {
public page: Page
private paneButtonTestId = 'code-pane-button'
private diagnosticsTooltip!: Locator
private diagnosticsGutterIcon!: Locator
private codeContent!: Locator
@ -37,32 +31,19 @@ export class EditorFixture {
private _expectEditorToContain =
(not = false) =>
async (
(
code: string,
{
shouldNormalise = false,
timeout = 5_000,
}: { shouldNormalise?: boolean; timeout?: number } = {}
) => {
const wasPaneOpen = await this.checkIfPaneIsOpen()
if (!wasPaneOpen) {
await this.openPane()
}
const resetPane = async () => {
if (!wasPaneOpen) {
await this.closePane()
}
}
if (!shouldNormalise) {
const expectStart = expect(this.codeContent)
if (not) {
const result = await expectStart.not.toContainText(code, { timeout })
await resetPane()
return result
return expectStart.not.toContainText(code, { timeout })
}
const result = await expectStart.toContainText(code, { timeout })
await resetPane()
return result
return expectStart.toContainText(code, { timeout })
}
const normalisedCode = code.replaceAll(/\s+/g, '').trim()
const expectStart = expect.poll(
@ -75,13 +56,9 @@ export class EditorFixture {
}
)
if (not) {
const result = await expectStart.not.toContain(normalisedCode)
await resetPane()
return result
return expectStart.not.toContain(normalisedCode)
}
const result = await expectStart.toContain(normalisedCode)
await resetPane()
return result
return expectStart.toContain(normalisedCode)
}
expectEditor = {
toContain: this._expectEditorToContain(),
@ -138,13 +115,4 @@ export class EditorFixture {
code = code.replace(findCode, replaceCode)
await this.codeContent.fill(code)
}
checkIfPaneIsOpen() {
return checkIfPaneIsOpen(this.page, this.paneButtonTestId)
}
closePane() {
return closePane(this.page, this.paneButtonTestId)
}
openPane() {
return openPane(this.page, this.paneButtonTestId)
}
}

View File

@ -20,7 +20,6 @@ export class AuthenticatedApp {
public readonly page: Page
public readonly context: BrowserContext
public readonly testInfo: TestInfo
public readonly viewPortSize = { width: 1000, height: 500 }
constructor(context: BrowserContext, page: Page, testInfo: TestInfo) {
this.page = page
@ -37,7 +36,7 @@ export class AuthenticatedApp {
;(window as any).playwrightSkipFilePicker = true
}, code)
await this.page.setViewportSize(this.viewPortSize)
await this.page.setViewportSize({ width: 1000, height: 500 })
await u.waitForAuthSkipAppStart()
}

View File

@ -10,13 +10,7 @@ import {
} from '../test-utils'
type mouseParams = {
pixelDiff?: number
}
type mouseDragToParams = mouseParams & {
fromPoint: { x: number; y: number }
}
type mouseDragFromParams = mouseParams & {
toPoint: { x: number; y: number }
pixelDiff: number
}
type SceneSerialised = {
@ -26,13 +20,6 @@ type SceneSerialised = {
}
}
type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean>
type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean>
type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean>
type DragFromHandler = (
dragParams: mouseDragFromParams
) => Promise<void | boolean>
export class SceneFixture {
public page: Page
@ -68,7 +55,7 @@ export class SceneFixture {
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
): [ClickHandler, MoveHandler] =>
) =>
[
(clickParams?: mouseParams) => {
if (clickParams?.pixelDiff) {
@ -91,47 +78,6 @@ export class SceneFixture {
return this.page.mouse.move(x, y, { steps })
},
] as const
makeDragHelpers = (
x: number,
y: number,
{ steps }: { steps: number } = { steps: 20 }
): [DragToHandler, DragFromHandler] =>
[
(dragToParams: mouseDragToParams) => {
if (dragToParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() =>
this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: dragToParams.fromPoint,
targetPosition: { x, y },
}),
dragToParams.pixelDiff
)
}
return this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: dragToParams.fromPoint,
targetPosition: { x, y },
})
},
(dragFromParams: mouseDragFromParams) => {
if (dragFromParams?.pixelDiff) {
return doAndWaitForImageDiff(
this.page,
() =>
this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x, y },
targetPosition: dragFromParams.toPoint,
}),
dragFromParams.pixelDiff
)
}
return this.page.dragAndDrop('#stream', '#stream', {
sourcePosition: { x, y },
targetPosition: dragFromParams.toPoint,
})
},
] as const
/** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene.
*

View File

@ -7,7 +7,6 @@ export class ToolbarFixture {
extrudeButton!: Locator
startSketchBtn!: Locator
lineBtn!: Locator
rectangleBtn!: Locator
exitSketchBtn!: Locator
editSketchBtn!: Locator
@ -25,7 +24,6 @@ export class ToolbarFixture {
this.page = page
this.extrudeButton = page.getByTestId('extrude')
this.startSketchBtn = page.getByTestId('sketch')
this.lineBtn = page.getByTestId('line')
this.rectangleBtn = page.getByTestId('corner-rectangle')
this.exitSketchBtn = page.getByTestId('sketch-exit')
this.editSketchBtn = page.getByText('Edit Sketch')

View File

@ -7,7 +7,6 @@ import {
setupElectron,
tearDown,
executorInputPath,
createProject,
} from './test-utils'
import { bracket } from 'lib/exampleKcl'
import { onboardingPaths } from 'routes/Onboarding/paths'
@ -56,48 +55,6 @@ test.describe('Onboarding tests', () => {
await expect(page.locator('.cm-content')).toContainText('// Shelf Bracket')
})
test(
'Desktop: fresh onboarding executes and loads',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const { electronApp, page } = await setupElectron({
testInfo,
appSettings: {
app: {
onboardingStatus: 'incomplete',
},
},
cleanProjectDir: true,
})
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
await test.step(`Create a project and open to the onboarding`, async () => {
await createProject({ name: 'project-link', page })
await test.step(`Ensure the engine connection works by testing the sketch button`, async () => {
await u.waitForPageLoad()
})
})
await test.step(`Ensure we see the onboarding stuff`, async () => {
// Test that the onboarding pane loaded
await expect(
page.getByText('Welcome to Modeling App! This')
).toBeVisible()
// *and* that the code is shown in the editor
await expect(page.locator('.cm-content')).toContainText(
'// Shelf Bracket'
)
})
await electronApp.close()
}
)
test('Code resets after confirmation', async ({ page }) => {
const initialCode = `sketch001 = startSketchOn('XZ')`
@ -421,9 +378,7 @@ test(
const restartConfirmationButton = page.getByRole('button', {
name: 'Make a new project',
})
const tutorialProjectIndicator = page
.getByTestId('project-sidebar-toggle')
.filter({ hasText: 'Tutorial Project 00' })
const tutorialProjectIndicator = page.getByText('Tutorial Project 00')
const tutorialModalText = page.getByText('Welcome to Modeling App!')
const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' })
const userMenuButton = page.getByTestId('user-sidebar-toggle')

View File

@ -451,103 +451,3 @@ sketch002 = startSketchOn(extrude001, seg03)
}
)
})
test(`Verify axis and origin snapping`, async ({
app,
editor,
toolbar,
scene,
}) => {
// Constants and locators
// These are mappings from screenspace to KCL coordinates,
// until we merge in our coordinate system helpers
const xzPlane = [
app.viewPortSize.width * 0.65,
app.viewPortSize.height * 0.3,
] as const
const originSloppy = {
screen: [
app.viewPortSize.width / 2 + 3, // 3px off the center of the screen
app.viewPortSize.height / 2,
],
kcl: [0, 0],
} as const
const xAxisSloppy = {
screen: [
app.viewPortSize.width * 0.75,
app.viewPortSize.height / 2 - 3, // 3px off the X-axis
],
kcl: [16.95, 0],
} as const
const offYAxis = {
screen: [
app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range
app.viewPortSize.height * 0.3,
],
kcl: [6.78, 6.78],
} as const
const yAxisSloppy = {
screen: [
app.viewPortSize.width / 2 + 5, // 5px off the Y-axis
app.viewPortSize.height * 0.3,
],
kcl: [0, 6.78],
} as const
const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane)
const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen)
const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers(
...xAxisSloppy.screen
)
const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers(
...offYAxis.screen
)
const expectedCodeSnippets = {
sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`,
pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`,
segmentOnXAxis: `lineTo([${xAxisSloppy.kcl[0]}, ${xAxisSloppy.kcl[1]}], %)`,
afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`,
afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`,
}
await app.initialise()
await test.step(`Start a sketch on the XZ plane`, async () => {
await editor.closePane()
await toolbar.startSketchPlaneSelection()
await moveToXzPlane()
await clickOnXzPlane()
// timeout wait for engine animation is unavoidable
await app.page.waitForTimeout(600)
await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane)
})
await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => {
await clickOriginSloppy()
await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin)
})
await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => {
await moveXAxisSloppy()
await clickXAxisSloppy()
await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis)
})
await test.step(`Unequip line tool`, async () => {
await toolbar.lineBtn.click()
await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true')
})
await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => {
await dragToOffYAxis({
fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] },
})
await editor.expectEditor.toContain(
expectedCodeSnippets.afterSegmentDraggedOffYAxis
)
})
await test.step(`Drag the origin point left to the y-axis, verify it snaps back`, async () => {
await dragFromOffAxis({
toPoint: { x: yAxisSloppy.screen[0], y: yAxisSloppy.screen[1] },
})
await editor.expectEditor.toContain(
expectedCodeSnippets.afterSegmentDraggedOnYAxis
)
})
})

View File

@ -7,7 +7,7 @@ import {
Paths,
setupElectron,
tearDown,
createProject,
createProjectAndRenameIt,
} from './test-utils'
import fsp from 'fs/promises'
import fs from 'fs'
@ -503,261 +503,21 @@ test(
}
)
test.describe(`Project management commands`, () => {
test(
`Rename from project page`,
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
// const projectMenuButton = page.getByTestId('project-sidebar-toggle')
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
// TODO: in future I'd like the behavior to be to
// navigate to the new project's page directly,
// see ProjectContextProvider.tsx:158
await test.step(`Check the project was renamed and we navigated home`, async () => {
await expect(projectHomeLink.first()).toBeVisible()
await expect(projectHomeLink.first()).toContainText(projectRenamedName)
})
await electronApp.close()
}
)
test(
`Delete from project page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
const u = await getUtils(page)
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await projectHomeLink.click()
await u.waitForPageLoad()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted and we navigated home`, async () => {
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
test(
`Rename from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_rename`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'rename project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const projectRenamedName = `project-000`
const commandContinueButton = page.getByRole('button', {
name: 'Continue',
})
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully renamed`)
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run rename command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandContinueButton).toBeVisible()
await commandContinueButton.click()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was renamed`, async () => {
await expect(
page.getByRole('link', { name: projectRenamedName })
).toBeVisible()
await expect(projectHomeLink).not.toHaveText(projectName)
})
await electronApp.close()
}
)
test(
`Delete from home page`,
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
const projectName = `my_project_to_delete`
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
)
},
})
// Constants and locators
const projectHomeLink = page.getByTestId('project-link')
const commandButton = page.getByRole('button', { name: 'Commands' })
const commandOption = page.getByRole('option', { name: 'delete project' })
const projectNameOption = page.getByRole('option', { name: projectName })
const commandWarning = page.getByText('Are you sure you want to delete?')
const commandSubmitButton = page.getByRole('button', {
name: 'Submit command',
})
const toastMessage = page.getByText(`Successfully deleted`)
const noProjectsMessage = page.getByText('No Projects found')
await test.step(`Setup`, async () => {
await page.setViewportSize({ width: 1200, height: 500 })
page.on('console', console.log)
await expect(projectHomeLink).toBeVisible()
})
await test.step(`Run delete command via command palette`, async () => {
await commandButton.click()
await commandOption.click()
await projectNameOption.click()
await expect(commandWarning).toBeVisible()
await expect(commandSubmitButton).toBeVisible()
await commandSubmitButton.click()
await expect(toastMessage).toBeVisible()
})
await test.step(`Check the project was deleted`, async () => {
await expect(projectHomeLink).not.toBeVisible()
await expect(noProjectsMessage).toBeVisible()
})
await electronApp.close()
}
)
})
test(
'File in the file pane should open with a single click',
{ tag: '@electron' },
async ({ browserName }, testInfo) => {
const projectName = 'router-template-slate'
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
await fsp.mkdir(`${dir}/${projectName}`, { recursive: true })
await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true })
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl',
`${dir}/${projectName}/main.kcl`
`${dir}/router-template-slate/main.kcl`
)
await fsp.copyFile(
'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl',
`${dir}/${projectName}/otherThingToClickOn.kcl`
`${dir}/router-template-slate/otherThingToClickOn.kcl`
)
},
})
@ -766,7 +526,7 @@ test(
page.on('console', console.log)
await page.getByText(projectName).click()
await page.getByText('router-template-slate').click()
await expect(page.getByTestId('loading')).toBeAttached()
await expect(page.getByTestId('loading')).not.toBeAttached({
timeout: 20_000,
@ -883,7 +643,7 @@ test(
page.on('console', console.log)
await test.step('delete the middle project, i.e. the bracket project', async () => {
const project = page.getByTestId('project-link').getByText('bracket')
const project = page.getByText('bracket')
await project.hover()
await project.focus()
@ -927,10 +687,10 @@ test(
})
await test.step('Check we can still create a project', async () => {
await createProject({ name: 'project-000', page, returnHome: true })
await expect(
page.getByTestId('project-link').filter({ hasText: 'project-000' })
).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText('project-000')).toBeVisible()
})
await electronApp.close()
@ -1107,16 +867,17 @@ test.fixme(
const pointOnModel = { x: 660, y: 250 }
const expectedStartCamZPosition = 15633.47
// Constants and locators
const projectLinks = page.getByTestId('project-link')
// expect to see text "No Projects found"
await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true })
await expect(projectLinks.getByText('project-000')).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await projectLinks.getByText('project-000').click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText('project-000')).toBeVisible()
await page.getByText('project-000').click()
await u.waitForPageLoad()
@ -1175,10 +936,16 @@ extrude001 = extrude(200, sketch001)`)
page.getByRole('button', { name: 'New project' })
).toBeVisible()
const createProject = async (projectNum: number) => {
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
const projectNumStr = projectNum.toString().padStart(3, '0')
await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible()
}
for (let i = 1; i <= 10; i++) {
const name = `project-${i.toString().padStart(3, '0')}`
await createProject({ name, page, returnHome: true })
await expect(projectLinks.getByText(name)).toBeVisible()
await createProject(i)
}
await electronApp.close()
}
@ -1353,10 +1120,11 @@ test(
await page.getByTestId('settings-close-button').click()
await expect(page.getByText('No Projects found')).toBeVisible()
await createProject({ name: 'project-000', page, returnHome: true })
await expect(
page.getByTestId('project-link').filter({ hasText: 'project-000' })
).toBeVisible()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
await expect(page.getByText(`project-000`)).toBeVisible()
})
await test.step('We can change back to the original root project directory', async () => {
@ -1497,7 +1265,6 @@ test(
'i_shape.kcl',
'kittycad_svg.kcl',
'lego.kcl',
'lsystem.kcl',
'math.kcl',
'member_expression_sketch.kcl',
'mike_stress_test.kcl',
@ -1683,7 +1450,7 @@ test(
page.on('console', console.log)
await test.step('Should create and name a project called wrist brace', async () => {
await createProject({ name: 'wrist brace', page, returnHome: true })
await createProjectAndRenameIt({ name: 'wrist brace', page })
})
await test.step('Should go through onboarding', async () => {

View File

@ -637,6 +637,7 @@ test.describe('Sketch tests', () => {
|> revolve({ axis: "X" }, %)`)
})
test('Can add multiple sketches', async ({ page }) => {
test.skip(process.platform === 'darwin', 'Can add multiple sketches')
const u = await getUtils(page)
const viewportSize = { width: 1200, height: 500 }
await page.setViewportSize(viewportSize)
@ -674,16 +675,15 @@ test.describe('Sketch tests', () => {
await click00r(50, 0)
await page.waitForTimeout(100)
codeStr += ` |> lineTo(${toSU([50, 0])}, %)`
codeStr += ` |> line(${toSU([50, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 50)
codeStr += ` |> line(${toSU([0, 50])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
let clickCoords = await click00r(-50, 0)
expect(clickCoords).not.toBeUndefined()
codeStr += ` |> lineTo(${toSU(clickCoords!)}, %)`
await click00r(-50, 0)
codeStr += ` |> line(${toSU([-50, 0])}, %)`
await expect(u.codeLocator).toHaveText(codeStr)
// exit the sketch, reset relative clicker
@ -709,10 +709,8 @@ test.describe('Sketch tests', () => {
codeStr += ` |> startProfileAt([2.03, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
// TODO: I couldn't use `toSU` here because of some rounding error causing
// it to be off by 0.01
await click00r(30, 0)
codeStr += ` |> lineTo([4.07, 0], %)`
codeStr += ` |> line([2.04, 0], %)`
await expect(u.codeLocator).toHaveText(codeStr)
await click00r(0, 30)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -219,7 +219,7 @@ test.describe('Test network and connection issues', () => {
|> startProfileAt([12.34, -12.34], %)
|> line([12.34, 0], %)
|> line([-12.34, 12.34], %)
|> lineTo([0, -12.34], %)
|> line([-12.34, 0], %)
`)

View File

@ -45,9 +45,7 @@ export const commonPoints = {
startAt: '[7.19, -9.7]',
num1: 7.25,
num2: 14.44,
/** The Y-value of a common lineTo move we perform in tests */
num3: -2.44,
} as const
}
/** A semi-reliable color to check the default XZ plane on
* in dark mode in the default camera position
@ -120,32 +118,15 @@ async function waitForDefaultPlanesToBeVisible(page: Page) {
)
}
export async function checkIfPaneIsOpen(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible()
return (await paneButtonLocator?.getAttribute('aria-pressed')) === 'true'
}
export async function openPane(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible()
const isOpen = await checkIfPaneIsOpen(page, testId)
async function openPane(page: Page, testId: string) {
const locator = page.getByTestId(testId)
await expect(locator).toBeVisible()
const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true'
if (!isOpen) {
await paneButtonLocator.click()
await locator.click()
await expect(locator).toHaveAttribute('aria-pressed', 'true')
}
await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'true')
}
export async function closePane(page: Page, testId: string) {
const paneButtonLocator = page.getByTestId(testId)
await expect(paneButtonLocator).toBeVisible()
const isOpen = await checkIfPaneIsOpen(page, testId)
if (isOpen) {
await paneButtonLocator.click()
}
await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'false')
}
async function openKclCodePanel(page: Page) {
@ -486,6 +467,20 @@ export async function getUtils(page: Page, test_?: typeof test) {
return text.replace(/\s+/g, '')
},
createAndSelectProject: async (hasText: string) => {
return test_?.step(
`Create and select project with text "${hasText}"`,
async () => {
// Without this, we get unreliable project creation. It's probably
// due to a race between the FS being read and clicking doing something.
await page.waitForTimeout(100)
await page.getByTestId('home-new-file').click()
const projectLinksPost = page.getByTestId('project-link')
await projectLinksPost.filter({ hasText }).click()
}
)
},
editorTextMatches: async (code: string) => {
const editor = page.locator(editorSelector)
return expect(editor).toHaveText(code, { useInnerText: true })
@ -525,9 +520,6 @@ export async function getUtils(page: Page, test_?: typeof test) {
.locator('[data-testid="file-pane-scroll-container"] button')
.filter({ hasText: name })
.click()
await expect(page.getByTestId('project-sidebar-toggle')).toContainText(
name
)
})
},
@ -896,17 +888,7 @@ export async function setupElectron({
const tempSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const settingsOverrides = TOML.stringify(
appSettings
? {
settings: {
...TEST_SETTINGS,
...appSettings,
app: {
...TEST_SETTINGS.app,
projectDirectory: projectDirName,
...appSettings.app,
},
},
}
? { settings: appSettings }
: {
settings: {
...TEST_SETTINGS,
@ -988,25 +970,30 @@ export async function isOutOfViewInScrollContainer(
return isOutOfView
}
export async function createProject({
export async function createProjectAndRenameIt({
name,
page,
returnHome = false,
}: {
name: string
page: Page
returnHome?: boolean
}) {
await test.step(`Create project and navigate to it`, async () => {
await page.getByRole('button', { name: 'New project' }).click()
await page.getByRole('textbox', { name: 'Name' }).fill(name)
await page.getByRole('button', { name: 'Continue' }).click()
await page.getByRole('button', { name: 'New project' }).click()
await expect(page.getByText('Successfully created')).toBeVisible()
await expect(page.getByText('Successfully created')).not.toBeVisible()
if (returnHome) {
await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' })
await page.getByTestId('app-logo').click()
}
})
await expect(page.getByText(`project-000`)).toBeVisible()
await page.getByText(`project-000`).hover()
await page.getByText(`project-000`).focus()
await page.getByLabel('sketch').first().click()
await page.waitForTimeout(100)
// type the name passed in
await page.keyboard.press('Backspace')
await page.keyboard.type(name)
await page.getByLabel('checkmark').last().click()
}
export function executorInputPath(fileName: string): string {

View File

@ -292,7 +292,7 @@ test.describe(`Testing gizmo, fixture-based`, () => {
await test.step(`Verify the camera moved`, async () => {
await scene.expectState({
camera: {
position: [0, -23865.37, 11073.53],
position: [0, -23865.37, 11073.54],
target: [0, 0, 0],
},
})

View File

@ -96,7 +96,7 @@ test.describe('Testing selections', () => {
|> startProfileAt(${commonPoints.startAt}, %)
|> line([${commonPoints.num1}, 0], %)
|> line([0, ${commonPoints.num1 + 0.01}], %)
|> lineTo([0, ${commonPoints.num3}], %)`)
|> line([-${commonPoints.num2}, 0], %)`)
// deselect line tool
await page.getByRole('button', { name: 'line Line', exact: true }).click()
@ -157,9 +157,7 @@ test.describe('Testing selections', () => {
await emptySpaceClick()
// check the same selection again by putting cursor in code first then selecting axis
await page
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await page.keyboard.down('Shift')
await constrainButton.click()
await expect(absYButton).toBeDisabled()
@ -182,9 +180,7 @@ test.describe('Testing selections', () => {
process.platform === 'linux' ? 'Control' : 'Meta'
)
await page.waitForTimeout(100)
await page
.getByText(` |> lineTo([0, ${commonPoints.num3}], %)`)
.click()
await page.getByText(` |> line([-${commonPoints.num2}, 0], %)`).click()
await expect(page.locator('.cm-cursor')).toHaveCount(2)
await page.waitForTimeout(500)

View File

@ -1,6 +1,5 @@
import { test, expect } from '@playwright/test'
import * as fsp from 'fs/promises'
import * as fs from 'fs'
import { join } from 'path'
import {
getUtils,
@ -8,7 +7,6 @@ import {
setupElectron,
tearDown,
executorInputPath,
createProject,
} from './test-utils'
import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes'
import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants'
@ -267,15 +265,10 @@ test.describe('Testing settings', () => {
process.platform === 'win32',
'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557'
)
const projectName = 'bracket'
const {
electronApp,
page,
dir: projectDirName,
} = await setupElectron({
const { electronApp, page } = await setupElectron({
testInfo,
folderSetupFn: async (dir) => {
const bracketDir = join(dir, projectName)
const bracketDir = join(dir, 'bracket')
await fsp.mkdir(bracketDir, { recursive: true })
await fsp.copyFile(
executorInputPath('focusrite_scarlett_mounting_braket.kcl'),
@ -287,12 +280,6 @@ test.describe('Testing settings', () => {
await page.setViewportSize({ width: 1200, height: 500 })
// Selectors and constants
const tempProjectSettingsFilePath = join(
projectDirName,
projectName,
PROJECT_SETTINGS_FILE_NAME
)
const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME)
const userThemeColor = '120'
const projectThemeColor = '50'
const settingsOpenButton = page.getByRole('link', {
@ -305,21 +292,6 @@ test.describe('Testing settings', () => {
const projectLink = page.getByText('bracket')
const logoLink = page.getByTestId('app-logo')
async function confirmThemeWasWritten(filePath: string, value: string) {
return expect
.poll(
async () => {
const fileExists = await fs.existsSync(filePath)
return fileExists ? fsp.readFile(filePath, 'utf-8') : ''
},
{
message: 'Setting should now be written to the file',
timeout: 5_000,
}
)
.toContain(`themeColor = "${value}"`)
}
await test.step('Set user theme color on home', async () => {
await expect(settingsOpenButton).toBeVisible()
await settingsOpenButton.click()
@ -327,7 +299,6 @@ test.describe('Testing settings', () => {
await expect(userSettingsTab).toBeChecked()
await themeColorSetting.fill(userThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
await confirmThemeWasWritten(tempUserSettingsFilePath, userThemeColor)
await settingsCloseButton.click()
})
@ -339,11 +310,6 @@ test.describe('Testing settings', () => {
await expect(projectSettingsTab).toBeChecked()
await themeColorSetting.fill(projectThemeColor)
await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor)
// Make sure that the project settings file has been written to before continuing
await confirmThemeWasWritten(
tempProjectSettingsFilePath,
projectThemeColor
)
await settingsCloseButton.click()
})
@ -357,7 +323,6 @@ test.describe('Testing settings', () => {
await test.step(`Navigate back to the home view and see user setting applied`, async () => {
await logoLink.click()
await page.screenshot({ path: 'out.png' })
await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor)
})
@ -463,7 +428,9 @@ test.describe('Testing settings', () => {
})
await test.step('Check color of logo changed when in modeling view', async () => {
await createProject({ name: 'project-000', page })
await page.getByRole('button', { name: 'New project' }).click()
await page.getByTestId('project-link').first().click()
await page.getByRole('button', { name: 'Dismiss' }).click()
await changeColor('58')
await expect(logoLink).toHaveCSS('--primary-hue', '58')
})
@ -481,7 +448,7 @@ test.describe('Testing settings', () => {
test(
'project settings reload on external change',
{ tag: '@electron' },
async ({ browserName: _ }, testInfo) => {
async ({ browserName }, testInfo) => {
const {
electronApp,
page,
@ -499,7 +466,11 @@ test.describe('Testing settings', () => {
await expect(projectDirLink).toBeVisible()
})
await createProject({ name: 'project-000', page })
const projectLinks = page.getByTestId('project-link')
const oldCount = await projectLinks.count()
await page.getByRole('button', { name: 'New project' }).click()
await expect(projectLinks).toHaveCount(oldCount + 1)
await projectLinks.filter({ hasText: 'project-000' }).first().click()
const changeColorFs = async (color: string) => {
const tempSettingsFilePath = join(

View File

@ -1,11 +1,5 @@
import { test, expect, Page } from '@playwright/test'
import {
getUtils,
setup,
tearDown,
setupElectron,
createProject,
} from './test-utils'
import { getUtils, setup, tearDown, setupElectron } from './test-utils'
import { join } from 'path'
import fs from 'fs'
@ -706,17 +700,17 @@ test(
const fileExists = () =>
fs.existsSync(join(dir, projectName, textToCadFileName))
const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils(
page,
test
)
const {
createAndSelectProject,
openFilePanel,
openKclCodePanel,
waitForPageLoad,
} = await getUtils(page, test)
await page.setViewportSize({ width: 1200, height: 500 })
// Locators
const projectMenuButton = page
.getByTestId('project-sidebar-toggle')
.filter({ hasText: projectName })
const projectMenuButton = page.getByRole('button', { name: projectName })
const textToCadFileButton = page.getByRole('listitem').filter({
has: page.getByRole('button', { name: textToCadFileName }),
})
@ -725,7 +719,7 @@ test(
)
// Create and navigate to the project
await createProject({ name: 'project-000', page })
await createAndSelectProject('project-000')
// Wait for Start Sketch otherwise you will not have access Text-to-CAD command
await waitForPageLoad()

2
interface.d.ts vendored
View File

@ -2,7 +2,7 @@ import fs from 'node:fs/promises'
import fsSync from 'node:fs'
import path from 'path'
import { dialog, shell } from 'electron'
import { MachinesListing } from 'components/MachineManagerProvider'
import { MachinesListing } from 'lib/machineManager'
type EnvFn = (value?: string) => string

View File

@ -107,13 +107,6 @@
},
"type": "array"
},
"loaded_filament_idx": {
"description": "The currently loaded filament index.",
"format": "uint",
"minimum": 0,
"nullable": true,
"type": "integer"
},
"nozzle_diameter": {
"description": "Diameter of the extrusion nozzle, in mm.",
"format": "double",
@ -292,21 +285,6 @@
"type"
],
"type": "object"
},
{
"description": "Unknown material",
"properties": {
"type": {
"enum": [
"unknown"
],
"type": "string"
}
},
"required": [
"type"
],
"type": "object"
}
]
},
@ -996,7 +974,7 @@
},
"description": "",
"title": "machine-api",
"version": "0.1.1"
"version": "0.1.0"
},
"openapi": "3.0.3",
"paths": {

View File

@ -1,6 +1,6 @@
{
"name": "zoo-modeling-app",
"version": "0.26.2",
"version": "0.26.0",
"private": true,
"productName": "Zoo Modeling App",
"author": {
@ -56,7 +56,7 @@
"react-json-view": "^1.21.3",
"react-modal": "^3.16.1",
"react-modal-promise": "^1.0.2",
"react-router-dom": "^6.27.0",
"react-router-dom": "^6.26.1",
"sketch-helpers": "^0.0.4",
"three": "^0.166.1",
"ua-parser-js": "^1.0.37",
@ -76,7 +76,7 @@
"build:both": "vite build",
"build:both:local": "yarn build:wasm && vite build",
"pretest": "yarn remove-importmeta",
"test:rust": "(cd src/wasm-lib && cargo test --workspace && cargo clippy --workspace --all-targets)",
"test:rust": "(cd src/wasm-lib && cargo test --all && cargo clippy --all --tests --benches)",
"simpleserver": "yarn pretest && http-server ./public --cors -p 3000",
"simpleserver:ci": "yarn pretest && http-server ./public --cors -p 3000 &",
"simpleserver:bg": "yarn pretest && http-server ./public --cors -p 3000 &",
@ -161,7 +161,7 @@
"@types/isomorphic-fetch": "^0.0.39",
"@types/minimist": "^1.2.5",
"@types/mocha": "^10.0.6",
"@types/node": "^22.7.8",
"@types/node": "^22.5.0",
"@types/pixelmatch": "^5.2.6",
"@types/pngjs": "^6.0.4",
"@types/react": "^18.3.4",

View File

@ -34,11 +34,6 @@
"title": "Car Wheel Assembly",
"description": "A car wheel assembly with a rotor, tire, and lug nuts."
},
{
"file": "dodecahedron.kcl",
"title": "Hollow Dodecahedron",
"description": "A regular dodecahedron or pentagonal dodecahedron is a dodecahedron composed of regular pentagonal faces, three meeting at each vertex. This example shows constructing the individual faces of the dodecahedron and extruding inwards."
},
{
"file": "enclosure.kcl",
"title": "Enclosure",
@ -59,11 +54,6 @@
"title": "A mounting bracket for the Focusrite Scarlett Solo audio interface",
"description": "This is a bracket that holds an audio device underneath a desk or shelf. The audio device has dimensions of 144mm wide, 80mm length and 45mm depth with fillets of 6mm. This mounting bracket is designed to be 3D printed with PLA material"
},
{
"file": "food-service-spatula.kcl",
"title": "Food Service Spatula",
"description": "Use these spatulas for mixing, flipping, and scraping."
},
{
"file": "french-press.kcl",
"title": "French Press",
@ -71,7 +61,7 @@
},
{
"file": "gear.kcl",
"title": "Spur Gear",
"title": "Gear",
"description": "A rotating machine part having cut teeth or, in the case of a cogwheel, inserted teeth (called cogs), which mesh with another toothed part to transmit torque. Geared devices can change the speed, torque, and direction of a power source. The two elements that define a gear are its circular shape and the teeth that are integrated into its outer edge, which are designed to fit into the teeth of another gear."
},
{

View File

View File

@ -21,7 +21,6 @@ import { WasmErrBanner } from 'components/WasmErrBanner'
import { CommandBar } from 'components/CommandBar/CommandBar'
import ModelingMachineProvider from 'components/ModelingMachineProvider'
import FileMachineProvider from 'components/FileMachineProvider'
import { MachineManagerProvider } from 'components/MachineManagerProvider'
import { PATHS } from 'lib/paths'
import {
fileLoader,
@ -43,7 +42,6 @@ import { coreDump } from 'lang/wasm'
import { useMemo } from 'react'
import { AppStateProvider } from 'AppState'
import { reportRejection } from 'lib/trap'
import { ProjectsContextProvider } from 'components/ProjectsContextProvider'
const createRouter = isDesktop() ? createHashRouter : createBrowserRouter
@ -51,22 +49,17 @@ const router = createRouter([
{
loader: settingsLoader,
id: PATHS.INDEX,
// TODO: Re-evaluate if this is true
/* Make sure auth is the outermost provider or else we will have
* inefficient re-renders, use the react profiler to see. */
element: (
<CommandBarProvider>
<SettingsAuthProvider>
<LspProvider>
<ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<MachineManagerProvider>
<Outlet />
</MachineManagerProvider>
</AppStateProvider>
</KclContextProvider>
</ProjectsContextProvider>
<KclContextProvider>
<AppStateProvider>
<Outlet />
</AppStateProvider>
</KclContextProvider>
</LspProvider>
</SettingsAuthProvider>
</CommandBarProvider>

View File

@ -100,11 +100,6 @@ export function Toolbar({
function resolveItemConfig(
maybeIconConfig: ToolbarItem
): ToolbarItemResolved {
const isDisabled =
disableAllButtons ||
maybeIconConfig.status !== 'available' ||
maybeIconConfig.disabled?.(state) === true
return {
...maybeIconConfig,
title:
@ -118,11 +113,10 @@ export function Toolbar({
typeof maybeIconConfig.hotkey === 'string'
? maybeIconConfig.hotkey
: maybeIconConfig.hotkey?.(state),
disabled: isDisabled,
disabledReason:
typeof maybeIconConfig.disabledReason === 'function'
? maybeIconConfig.disabledReason(state)
: maybeIconConfig.disabledReason,
disabled:
disableAllButtons ||
maybeIconConfig.status !== 'available' ||
maybeIconConfig.disabled?.(state) === true,
disableHotkey: maybeIconConfig.disableHotkey?.(state),
status: maybeIconConfig.status,
}
@ -279,8 +273,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
itemConfig: ToolbarItemResolved
configCallbackProps: ToolbarItemCallbackProps
}) {
const { state } = useModelingContext()
useHotkeys(
itemConfig.hotkey || '',
() => {
@ -344,17 +336,6 @@ const ToolbarItemTooltip = memo(function ToolbarItemContents({
)}
</div>
<p className="px-2 text-ch font-sans">{itemConfig.description}</p>
{/* Add disabled reason if item is disabled */}
{itemConfig.disabled && itemConfig.disabledReason && (
<>
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />
<p className="px-2 text-ch font-sans text-chalkboard-70 dark:text-chalkboard-40">
{typeof itemConfig.disabledReason === 'function'
? itemConfig.disabledReason(state)
: itemConfig.disabledReason}
</p>
</>
)}
{itemConfig.links.length > 0 && (
<>
<hr className="border-chalkboard-20 dark:border-chalkboard-80" />

View File

@ -64,27 +64,6 @@ export type ReactCameraProperties =
const lastCmdDelay = 50
class CameraRateLimiter {
lastSend?: Date = undefined
rateLimitMs: number = 16 //60 FPS
send = (f: () => void) => {
let now = new Date()
if (
this.lastSend === undefined ||
now.getTime() - this.lastSend.getTime() > this.rateLimitMs
) {
f()
this.lastSend = now
}
}
reset = () => {
this.lastSend = undefined
}
}
export class CameraControls {
engineCommandManager: EngineCommandManager
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
@ -92,15 +71,15 @@ export class CameraControls {
target: Vector3
domElement: HTMLCanvasElement
isDragging: boolean
wasDragging: boolean
mouseDownPosition: Vector2
mouseNewPosition: Vector2
rotationSpeed = 0.3
enableRotate = true
enablePan = true
enableZoom = true
moveSender: CameraRateLimiter = new CameraRateLimiter()
zoomSender: CameraRateLimiter = new CameraRateLimiter()
zoomDataFromLastFrame?: number = undefined
// holds coordinates, and interaction
moveDataFromLastFrame?: [number, number, string] = undefined
lastPerspectiveFov: number = 45
pendingZoom: number | null = null
pendingRotation: Vector2 | null = null
@ -192,36 +171,6 @@ export class CameraControls {
}
}
doMove = (interaction: any, coordinates: any) => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction: interaction,
window: {
x: coordinates[0],
y: coordinates[1],
},
},
cmd_id: uuidv4(),
})
}
doZoom = (zoom: number) => {
this.handleStart()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude: (-1 * zoom) / window.devicePixelRatio,
},
cmd_id: uuidv4(),
})
this.handleEnd()
}
constructor(
isOrtho = false,
domElement: HTMLCanvasElement,
@ -234,7 +183,6 @@ export class CameraControls {
this.target = new Vector3()
this.domElement = domElement
this.isDragging = false
this.wasDragging = false
this.mouseDownPosition = new Vector2()
this.mouseNewPosition = new Vector2()
@ -310,6 +258,49 @@ export class CameraControls {
this.onCameraChange()
}
// Our stream is never more than 60fps.
// We can get away with capping our "virtual fps" to 60 then.
const FPS_VIRTUAL = 60
const doZoom = () => {
if (this.zoomDataFromLastFrame !== undefined) {
this.handleStart()
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'default_camera_zoom',
magnitude:
(-1 * this.zoomDataFromLastFrame) / window.devicePixelRatio,
},
cmd_id: uuidv4(),
})
this.handleEnd()
}
this.zoomDataFromLastFrame = undefined
}
setInterval(doZoom, 1000 / FPS_VIRTUAL)
const doMove = () => {
if (this.moveDataFromLastFrame !== undefined) {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this.engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd: {
type: 'camera_drag_move',
interaction: this.moveDataFromLastFrame[2] as any,
window: {
x: this.moveDataFromLastFrame[0],
y: this.moveDataFromLastFrame[1],
},
},
cmd_id: uuidv4(),
})
}
this.moveDataFromLastFrame = undefined
}
setInterval(doMove, 1000 / FPS_VIRTUAL)
setTimeout(() => {
this.engineCommandManager.subscribeTo({
event: 'camera_drag_end',
@ -365,8 +356,6 @@ export class CameraControls {
onMouseDown = (event: PointerEvent) => {
this.domElement.setPointerCapture(event.pointerId)
this.isDragging = true
// Reset the wasDragging flag to false when starting a new drag
this.wasDragging = false
this.mouseDownPosition.set(event.clientX, event.clientY)
let interaction = this.getInteractionType(event)
if (interaction === 'none') return
@ -396,18 +385,11 @@ export class CameraControls {
const interaction = this.getInteractionType(event)
if (interaction === 'none') return
// If there's a valid interaction and the mouse is moving,
// our past (and current) interaction was a drag.
this.wasDragging = true
if (this.syncDirection === 'engineToClient') {
this.moveSender.send(() => {
this.doMove(interaction, [event.clientX, event.clientY])
})
this.moveDataFromLastFrame = [event.clientX, event.clientY, interaction]
return
}
// else "clientToEngine" (Sketch Mode) or forceUpdate
// Implement camera movement logic here based on deltaMove
// For example, for rotating the camera around the target:
if (interaction === 'rotate') {
@ -436,9 +418,6 @@ export class CameraControls {
* under the cursor. This recently moved from being handled in App.tsx.
* This might not be the right spot, but it is more consolidated.
*/
// Clear any previous drag state
this.wasDragging = false
if (this.syncDirection === 'engineToClient') {
const newCmdId = uuidv4()
@ -480,9 +459,7 @@ export class CameraControls {
if (this.syncDirection === 'engineToClient') {
if (interaction === 'zoom') {
this.zoomSender.send(() => {
this.doZoom(event.deltaY)
})
this.zoomDataFromLastFrame = event.deltaY
} else {
// This case will get handled when we add pan and rotate using Apple trackpad.
console.error(

View File

@ -44,7 +44,7 @@ import {
import { ActionButton } from 'components/ActionButton'
import { err, reportRejection, trap } from 'lib/trap'
import { useCommandsContext } from 'hooks/useCommandsContext'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { UnboxedNode } from 'wasm-lib/kcl/bindings/UnboxedNode'
function useShouldHideScene(): { hideClient: boolean; hideServer: boolean } {
const [isCamMoving, setIsCamMoving] = useState(false)
@ -202,7 +202,7 @@ const Overlay = ({
let xAlignment = overlay.angle < 0 ? '0%' : '-100%'
let yAlignment = overlay.angle < -90 || overlay.angle >= 90 ? '0%' : '-100%'
const _node1 = getNodeFromPath<Node<CallExpression>>(
const _node1 = getNodeFromPath<UnboxedNode<CallExpression>>(
kclManager.ast,
overlay.pathToNode,
'CallExpression'
@ -382,7 +382,7 @@ export async function deleteSegment({
pathToNode: PathToNode
sketchDetails: SketchDetails | null
}) {
let modifiedAst: Node<Program> | Error = kclManager.ast
let modifiedAst: UnboxedNode<Program> | Error = kclManager.ast
const dependentRanges = findUsesOfTagInPipe(modifiedAst, pathToNode)
const shouldContinueSegDelete = dependentRanges.length

View File

@ -19,8 +19,6 @@ import {
import {
ARROWHEAD,
AXIS_GROUP,
DRAFT_POINT,
DRAFT_POINT_GROUP,
getSceneScale,
INTERSECTION_PLANE_LAYER,
OnClickCallbackArgs,
@ -55,7 +53,7 @@ import {
editorManager,
} from 'lib/singletons'
import { getNodeFromPath, getNodePathFromSourceRange } from 'lang/queryAst'
import { executeAst, ToolTip } from 'lang/langHelpers'
import { executeAst } from 'lang/langHelpers'
import {
createProfileStartHandle,
SegmentUtils,
@ -94,7 +92,7 @@ import { err, reportRejection, trap } from 'lib/trap'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Point3d } from 'wasm-lib/kcl/bindings/Point3d'
import { SegmentInputs } from 'lang/std/stdTypes'
import { Node } from 'wasm-lib/kcl/bindings/Node'
import { UnboxedNode } from 'wasm-lib/kcl/bindings/UnboxedNode'
type DraftSegment = 'line' | 'tangentialArcTo'
@ -316,27 +314,6 @@ export class SceneEntities {
const intersectionPlane = this.scene.getObjectByName(RAYCASTABLE_PLANE)
if (intersectionPlane) this.scene.remove(intersectionPlane)
}
getDraftPoint() {
return this.scene.getObjectByName(DRAFT_POINT)
}
createDraftPoint({ point, group }: { point: Vector2; group: Group }) {
const dummy = new Mesh()
dummy.position.set(0, 0, 0)
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
const draftPoint = createProfileStartHandle({
isDraft: true,
from: [point.x, point.y],
scale,
theme: sceneInfra._theme,
})
draftPoint.layers.set(SKETCH_LAYER)
group.add(draftPoint)
}
removeDraftPoint() {
const draftPoint = this.getDraftPoint()
if (draftPoint) draftPoint.removeFromParent()
}
setupNoPointsListener({
sketchDetails,
@ -345,106 +322,30 @@ export class SceneEntities {
sketchDetails: SketchDetails
afterClick: (args: OnClickCallbackArgs) => void
}) {
// TODO: Consolidate shared logic between this and setupSketch
// Which should just fire when the sketch mode is entered,
// instead of in these two separate XState states.
// Create a THREEjs plane to raycast clicks onto
this.createIntersectionPlane()
const draftPointGroup = new Group()
draftPointGroup.name = DRAFT_POINT_GROUP
sketchDetails.origin &&
draftPointGroup.position.set(...sketchDetails.origin)
if (!(sketchDetails.yAxis && sketchDetails)) {
console.error('No sketch quaternion or sketch details found')
return
}
this.currentSketchQuaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis)
)
draftPointGroup.setRotationFromQuaternion(this.currentSketchQuaternion)
this.scene.add(draftPointGroup)
const quaternion = quaternionFromUpNForward(
new Vector3(...sketchDetails.yAxis),
new Vector3(...sketchDetails.zAxis)
)
// Position the click raycast plane
this.intersectionPlane!.setRotationFromQuaternion(quaternion)
this.intersectionPlane!.position.copy(
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
)
if (this.intersectionPlane) {
this.intersectionPlane.setRotationFromQuaternion(quaternion)
this.intersectionPlane.position.copy(
new Vector3(...(sketchDetails?.origin || [0, 0, 0]))
)
}
sceneInfra.setCallbacks({
onMove: (args) => {
if (!args.intersects.length) return
const axisIntersection = args.intersects.find(
(sceneObject) =>
sceneObject.object.name === X_AXIS ||
sceneObject.object.name === Y_AXIS
)
if (!axisIntersection) return
const { intersectionPoint } = args
// We're hovering over an axis, so we should show a draft point
const snappedPoint = intersectionPoint.twoD.clone()
if (axisIntersection.object.name === X_AXIS) {
snappedPoint.setComponent(1, 0)
} else {
snappedPoint.setComponent(0, 0)
}
// Either create a new one or update the existing one
const draftPoint = this.getDraftPoint()
if (!draftPoint) {
this.createDraftPoint({
point: snappedPoint,
group: draftPointGroup,
})
} else {
// Ignore if there are huge jumps in the mouse position,
// that is likely a strange behavior
if (
draftPoint.position.distanceTo(
new Vector3(snappedPoint.x, snappedPoint.y, 0)
) > 100
) {
return
}
draftPoint.position.set(snappedPoint.x, snappedPoint.y, 0)
}
},
onMouseLeave: () => {
this.removeDraftPoint()
},
onClick: async (args) => {
this.removeDraftPoint()
if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
if (!intersectionPoint?.twoD || !sketchDetails?.sketchPathToNode) return
// Snap to either or both axes
// if the click intersects their meshes
const yAxisIntersection = args.intersects.find(
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const xAxisIntersection = args.intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const snappedClickPoint = {
x: yAxisIntersection ? 0 : intersectionPoint.twoD.x,
y: xAxisIntersection ? 0 : intersectionPoint.twoD.y,
}
const addStartProfileAtRes = addStartProfileAt(
kclManager.ast,
sketchDetails.sketchPathToNode,
[snappedClickPoint.x, snappedClickPoint.y]
[intersectionPoint.twoD.x, intersectionPoint.twoD.y]
)
if (trap(addStartProfileAtRes)) return
@ -452,7 +353,6 @@ export class SceneEntities {
await kclManager.updateAst(modifiedAst, false)
this.removeIntersectionPlane()
this.scene.remove(draftPointGroup)
// Now perform the caller-specified action
afterClick(args)
@ -470,14 +370,14 @@ export class SceneEntities {
selectionRanges,
}: {
sketchPathToNode: PathToNode
maybeModdedAst: Node<Program>
maybeModdedAst: UnboxedNode<Program>
draftExpressionsIndices?: { start: number; end: number }
forward: [number, number, number]
up: [number, number, number]
position?: [number, number, number]
selectionRanges?: Selections
}): Promise<{
truncatedAst: Node<Program>
truncatedAst: UnboxedNode<Program>
programMemoryOverride: ProgramMemory
sketch: Sketch
variableDeclarationName: string
@ -508,7 +408,7 @@ export class SceneEntities {
if (err(sketch)) return Promise.reject(sketch)
if (!sketch) return Promise.reject('sketch not found')
if (!isArray(sketch?.paths))
if (!isArray(sketch?.value))
return {
truncatedAst,
programMemoryOverride,
@ -525,20 +425,24 @@ export class SceneEntities {
const dummy = new Mesh()
// TODO: When we actually have sketch positions and rotations we can use them here.
dummy.position.set(0, 0, 0)
const scale = sceneInfra.getClientSceneScaleFactor(dummy)
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
const factor =
(sceneInfra.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(sceneInfra.camControls.camera, dummy)) /
sceneInfra._baseUnitMultiplier
const segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
sketch.start.__geoMeta.sourceRange
)
if (sketch?.paths?.[0]?.type !== 'Circle') {
if (sketch?.value?.[0]?.type !== 'Circle') {
const _profileStart = createProfileStartHandle({
from: sketch.start.from,
id: sketch.start.__geoMeta.id,
pathToNode: segPathToNode,
scale,
scale: factor,
theme: sceneInfra._theme,
isDraft: false,
})
_profileStart.layers.set(SKETCH_LAYER)
_profileStart.traverse((child) => {
@ -548,16 +452,16 @@ export class SceneEntities {
this.activeSegments[JSON.stringify(segPathToNode)] = _profileStart
}
const callbacks: (() => SegmentOverlayPayload | null)[] = []
sketch.paths.forEach((segment, index) => {
sketch.value.forEach((segment, index) => {
let segPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
segment.__geoMeta.sourceRange
)
if (
draftExpressionsIndices &&
(sketch.paths[index - 1] || sketch.start)
(sketch.value[index - 1] || sketch.start)
) {
const previousSegment = sketch.paths[index - 1] || sketch.start
const previousSegment = sketch.value[index - 1] || sketch.start
const previousSegmentPathToNode = getNodePathFromSourceRange(
maybeModdedAst,
previousSegment.__geoMeta.sourceRange
@ -608,13 +512,13 @@ export class SceneEntities {
to: segment.to,
}
const result = initSegment({
prevSegment: sketch.paths[index - 1],
prevSegment: sketch.value[index - 1],
callExpName,
input,
id: segment.__geoMeta.id,
pathToNode: segPathToNode,
isDraftSegment,
scale,
scale: factor,
texture: sceneInfra.extraSegmentTexture,
theme: sceneInfra._theme,
isSelected,
@ -658,7 +562,7 @@ export class SceneEntities {
}
updateAstAndRejigSketch = async (
sketchPathToNode: PathToNode,
modifiedAst: Node<Program> | Error,
modifiedAst: UnboxedNode<Program> | Error,
forward: [number, number, number],
up: [number, number, number],
origin: [number, number, number]
@ -707,9 +611,9 @@ export class SceneEntities {
variableDeclarationName
)
if (err(sg)) return Promise.reject(sg)
const lastSeg = sg?.paths?.slice(-1)[0] || sg.start
const lastSeg = sg?.value?.slice(-1)[0] || sg.start
const index = sg.paths.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const index = sg.value.length // because we've added a new segment that's not in the memory yet, no need for `-1`
const mod = addNewSketchLn({
node: _ast,
programMemory: kclManager.programMemory,
@ -742,24 +646,16 @@ export class SceneEntities {
sceneInfra.setCallbacks({
onClick: async (args) => {
if (!args) return
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args.mouseEvent.which !== 1) return
const { intersectionPoint } = args
let intersection2d = intersectionPoint?.twoD
const intersectsProfileStart = args.intersects
const profileStart = args.intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
let modifiedAst
// Snapping logic for the profile start handle
if (intersectsProfileStart) {
const lastSegment = sketch.paths.slice(-1)[0]
if (profileStart) {
const lastSegment = sketch.value.slice(-1)[0]
modifiedAst = addCallExpressionsToPipe({
node: kclManager.ast,
programMemory: kclManager.programMemory,
@ -791,39 +687,19 @@ export class SceneEntities {
})
if (trap(modifiedAst)) return Promise.reject(modifiedAst)
} else if (intersection2d) {
const intersectsYAxis = args.intersects.find(
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const intersectsXAxis = args.intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const lastSegment = sketch.paths.slice(-1)[0]
const snappedPoint = {
x: intersectsYAxis ? 0 : intersection2d.x,
y: intersectsXAxis ? 0 : intersection2d.y,
}
let resolvedFunctionName: ToolTip = 'line'
// This might need to become its own function if we want more
// case-based logic for different segment types
if (lastSegment.type === 'TangentialArcTo') {
resolvedFunctionName = 'tangentialArcTo'
} else if (snappedPoint.x === 0 || snappedPoint.y === 0) {
// We consider a point placed on axes or origin to be absolute
resolvedFunctionName = 'lineTo'
}
const lastSegment = sketch.value.slice(-1)[0]
const tmp = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
input: {
type: 'straight-segment',
from: [lastSegment.to[0], lastSegment.to[1]],
to: [snappedPoint.x, snappedPoint.y],
to: [intersection2d.x, intersection2d.y],
},
fnName: resolvedFunctionName,
fnName:
lastSegment.type === 'TangentialArcTo'
? 'tangentialArcTo'
: 'line',
pathToNode: sketchPathToNode,
})
if (trap(tmp)) return Promise.reject(tmp)
@ -835,7 +711,7 @@ export class SceneEntities {
}
await kclManager.executeAstMock(modifiedAst)
if (intersectsProfileStart) {
if (profileStart) {
sceneInfra.modelingSend({ type: 'CancelSketch' })
} else {
await this.setUpDraftSegment(
@ -860,6 +736,7 @@ export class SceneEntities {
},
})
},
...this.mouseEnterLeaveCallbacks(),
})
}
setupDraftRectangle = async (
@ -941,7 +818,7 @@ export class SceneEntities {
variableDeclarationName
)
if (err(sketch)) return Promise.reject(sketch)
const sgPaths = sketch.paths
const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -950,11 +827,6 @@ export class SceneEntities {
)
},
onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -997,7 +869,7 @@ export class SceneEntities {
variableDeclarationName
)
if (err(sketch)) return
const sgPaths = sketch.paths
const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
// Update the starting segment of the THREEjs scene
@ -1114,7 +986,7 @@ export class SceneEntities {
variableDeclarationName
)
if (err(sketch)) return
const sgPaths = sketch.paths
const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(sketch.start, 0, 0, _ast, orthoFactor, sketch)
@ -1123,11 +995,6 @@ export class SceneEntities {
)
},
onClick: async (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
// Commit the rectangle to the full AST/code and return to sketch.idle
const cornerPoint = args.intersectionPoint?.twoD
if (!cornerPoint || args.mouseEvent.button !== 0) return
@ -1239,7 +1106,7 @@ export class SceneEntities {
const pipeIndex = pathToNode[pathToNodeIndex + 1][0] as number
if (addingNewSegmentStatus === 'nothing') {
const prevSegment = sketch.paths[pipeIndex - 2]
const prevSegment = sketch.value[pipeIndex - 2]
const mod = addNewSketchLn({
node: kclManager.ast,
programMemory: kclManager.programMemory,
@ -1291,11 +1158,6 @@ export class SceneEntities {
},
onMove: () => {},
onClick: (args) => {
// If there is a valid camera interaction that matches, do that instead
const interaction = sceneInfra.camControls.getInteractionType(
args.mouseEvent
)
if (interaction !== 'none') return
if (args?.mouseEvent.which !== 1) return
if (!args || !args.selected) {
sceneInfra.modelingSend({
@ -1316,7 +1178,7 @@ export class SceneEntities {
}
prepareTruncatedMemoryAndAst = (
sketchPathToNode: PathToNode,
ast?: Node<Program>,
ast?: UnboxedNode<Program>,
draftSegment?: DraftSegment
) =>
prepareTruncatedMemoryAndAst(
@ -1337,35 +1199,20 @@ export class SceneEntities {
sketchPathToNode: PathToNode
intersects: Intersection<Object3D<Object3DEventMap>>[]
draftInfo?: {
truncatedAst: Node<Program>
truncatedAst: UnboxedNode<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string
}
}) {
const intersectsProfileStart =
const profileStart =
draftInfo &&
intersects
.map(({ object }) => getParentGroup(object, [PROFILE_START]))
.find((a) => a?.name === PROFILE_START)
const intersection2d = intersectsProfileStart
? new Vector2(
intersectsProfileStart.position.x,
intersectsProfileStart.position.y
)
const intersection2d = profileStart
? new Vector2(profileStart.position.x, profileStart.position.y)
: _intersection2d
const intersectsYAxis = intersects.find(
(sceneObject) => sceneObject.object.name === Y_AXIS
)
const intersectsXAxis = intersects.find(
(sceneObject) => sceneObject.object.name === X_AXIS
)
const snappedPoint = new Vector2(
intersectsYAxis ? 0 : intersection2d.x,
intersectsXAxis ? 0 : intersection2d.y
)
const group = getParentGroup(object, SEGMENT_BODIES_PLUS_PROFILE_START)
const subGroup = getParentGroup(object, [ARROWHEAD, CIRCLE_CENTER_HANDLE])
if (!group) return
@ -1385,10 +1232,10 @@ export class SceneEntities {
group.userData.from[0],
group.userData.from[1],
]
const dragTo: [number, number] = [snappedPoint.x, snappedPoint.y]
const dragTo: [number, number] = [intersection2d.x, intersection2d.y]
let modifiedAst = draftInfo ? draftInfo.truncatedAst : { ...kclManager.ast }
const _node = getNodeFromPath<Node<CallExpression>>(
const _node = getNodeFromPath<UnboxedNode<CallExpression>>(
modifiedAst,
pathToNode,
'CallExpression'
@ -1400,7 +1247,7 @@ export class SceneEntities {
let modded:
| {
modifiedAst: Node<Program>
modifiedAst: UnboxedNode<Program>
pathToNode: PathToNode
}
| Error
@ -1499,7 +1346,7 @@ export class SceneEntities {
}
if (!sketch) return
const sgPaths = sketch.paths
const sgPaths = sketch.value
const orthoFactor = orthoScale(sceneInfra.camControls.camera)
this.updateSegment(
@ -1547,7 +1394,7 @@ export class SceneEntities {
modifiedAst,
segment.__geoMeta.sourceRange
)
const sgPaths = sketch.paths
const sgPaths = sketch.value
const originalPathToNodeStr = JSON.stringify(segPathToNode)
segPathToNode[1][0] = varDecIndex
const pathToNodeStr = JSON.stringify(segPathToNode)
@ -1695,7 +1542,7 @@ export class SceneEntities {
if (parent?.userData?.pathToNode) {
const updatedAst = parse(recast(kclManager.ast))
if (trap(updatedAst)) return
const _node = getNodeFromPath<Node<CallExpression>>(
const _node = getNodeFromPath<UnboxedNode<CallExpression>>(
updatedAst,
parent.userData.pathToNode,
'CallExpression'
@ -1830,12 +1677,12 @@ export type DefaultPlaneStr = 'XY' | 'XZ' | 'YZ' | '-XY' | '-XZ' | '-YZ'
function prepareTruncatedMemoryAndAst(
sketchPathToNode: PathToNode,
ast: Node<Program>,
ast: UnboxedNode<Program>,
programMemory: ProgramMemory,
draftSegment?: DraftSegment
):
| {
truncatedAst: Node<Program>
truncatedAst: UnboxedNode<Program>
programMemoryOverride: ProgramMemory
variableDeclarationName: string
}
@ -1843,7 +1690,7 @@ function prepareTruncatedMemoryAndAst(
const bodyIndex = Number(sketchPathToNode?.[1]?.[0]) || 0
const _ast = structuredClone(ast)
const _node = getNodeFromPath<Node<VariableDeclaration>>(
const _node = getNodeFromPath<UnboxedNode<VariableDeclaration>>(
_ast,
sketchPathToNode || [],
'VariableDeclaration'
@ -1855,7 +1702,7 @@ function prepareTruncatedMemoryAndAst(
variableDeclarationName
)
if (err(sg)) return sg
const lastSeg = sg?.paths.slice(-1)[0]
const lastSeg = sg?.value.slice(-1)[0]
if (draftSegment) {
// truncatedAst needs to setup with another segment at the end
let newSegment
@ -1893,15 +1740,15 @@ function prepareTruncatedMemoryAndAst(
).body.slice(-1)[0].start = lastPipeItem.start
_ast.end = lastPipeItem.end
const varDec = _ast.body[bodyIndex] as Node<VariableDeclaration>
const varDec = _ast.body[bodyIndex] as UnboxedNode<VariableDeclaration>
varDec.end = lastPipeItem.end
const declarator = varDec.declarations[0]
declarator.end = lastPipeItem.end
const init = declarator.init as Node<PipeExpression>
const init = declarator.init as UnboxedNode<PipeExpression>
init.end = lastPipeItem.end
init.body.slice(-1)[0].end = lastPipeItem.end
}
const truncatedAst: Node<Program> = {
const truncatedAst: UnboxedNode<Program> = {
..._ast,
body: [structuredClone(_ast.body[bodyIndex])],
}

View File

@ -30,7 +30,6 @@ import { MouseState, SegmentOverlayPayload } from 'machines/modelingMachine'
import { getAngle, throttle } from 'lib/utils'
import { Themes } from 'lib/theme'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { orthoScale, perspScale } from './helpers'
type SendType = ReturnType<typeof useModelingContext>['send']
@ -50,10 +49,6 @@ export const RAYCASTABLE_PLANE = 'raycastable-plane'
export const X_AXIS = 'xAxis'
export const Y_AXIS = 'yAxis'
/** the THREEjs representation of the group surrounding a "snapped" point that is not yet placed */
export const DRAFT_POINT_GROUP = 'draft-point-group'
/** the THREEjs representation of a "snapped" point that is not yet placed */
export const DRAFT_POINT = 'draft-point'
export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
@ -65,11 +60,6 @@ export interface OnMouseEnterLeaveArgs {
selected: Object3D<Object3DEventMap>
dragSelected?: Object3D<Object3DEventMap>
mouseEvent: MouseEvent
/** The intersection of the mouse with the THREEjs raycast plane */
intersectionPoint?: {
twoD?: Vector2
threeD?: Vector3
}
}
interface OnDragCallbackArgs extends OnMouseEnterLeaveArgs {
@ -223,7 +213,7 @@ export class SceneInfra {
to: Coords2d
angle?: number
}): SegmentOverlayPayload | null {
if (!group.userData.draft && group.userData.pathToNode && arrowGroup) {
if (group.userData.pathToNode && arrowGroup) {
const vector = new Vector3(0, 0, 0)
// Get the position of the object3D in world space
@ -358,42 +348,29 @@ export class SceneInfra {
window.removeEventListener('resize', this.onWindowResize)
// Dispose of any other resources like geometries, materials, textures
}
getClientSceneScaleFactor(meshOrGroup: Mesh | Group) {
const orthoFactor = orthoScale(this.camControls.camera)
const factor =
(this.camControls.camera instanceof OrthographicCamera
? orthoFactor
: perspScale(this.camControls.camera, meshOrGroup)) /
this._baseUnitMultiplier
return factor
}
getPlaneIntersectPoint = (): {
twoD?: Vector2
threeD?: Vector3
intersection: Intersection<Object3D<Object3DEventMap>>
} | null => {
// Get the orientations from the camera and mouse position
this.planeRaycaster.setFromCamera(
this.currentMouseVector,
this.camControls.camera
)
// Get the intersection of the ray with the default planes
const planeIntersects = this.planeRaycaster.intersectObjects(
this.scene.children,
true
)
if (!planeIntersects.length) return null
// Find the intersection with the raycastable (or sketch) plane
const raycastablePlaneIntersection = planeIntersects.find(
const recastablePlaneIntersect = planeIntersects.find(
(intersect) => intersect.object.name === RAYCASTABLE_PLANE
)
if (!raycastablePlaneIntersection)
return { intersection: planeIntersects[0] }
const planePosition = raycastablePlaneIntersection.object.position
const inversePlaneQuaternion =
raycastablePlaneIntersection.object.quaternion.clone().invert()
const intersectPoint = raycastablePlaneIntersection.point
if (!planeIntersects.length) return null
if (!recastablePlaneIntersect) return { intersection: planeIntersects[0] }
const planePosition = planeIntersects[0].object.position
const inversePlaneQuaternion = planeIntersects[0].object.quaternion
.clone()
.invert()
const intersectPoint = planeIntersects[0].point
let transformedPoint = intersectPoint.clone()
if (transformedPoint) {
transformedPoint.applyQuaternion(inversePlaneQuaternion)
@ -470,26 +447,18 @@ export class SceneInfra {
if (intersects[0]) {
const firstIntersectObject = intersects[0].object
const planeIntersectPoint = this.getPlaneIntersectPoint()
const intersectionPoint = {
twoD: planeIntersectPoint?.twoD,
threeD: planeIntersectPoint?.threeD,
}
if (this.hoveredObject !== firstIntersectObject) {
const hoveredObj = this.hoveredObject
this.hoveredObject = null
await this.onMouseLeave({
selected: hoveredObj,
mouseEvent: mouseEvent,
intersectionPoint,
})
this.hoveredObject = firstIntersectObject
await this.onMouseEnter({
selected: this.hoveredObject,
dragSelected: this.selected?.object,
mouseEvent: mouseEvent,
intersectionPoint,
})
if (!this.selected)
this.updateMouseState({

View File

@ -45,7 +45,6 @@ import {
import { getTangentPointFromPreviousArc } from 'lib/utils2d'
import {
ARROWHEAD,
DRAFT_POINT,
SceneInfra,
SEGMENT_LENGTH_LABEL,
SEGMENT_LENGTH_LABEL_OFFSET_PX,
@ -59,7 +58,7 @@ import { err } from 'lib/trap'
interface CreateSegmentArgs {
input: SegmentInputs
prevSegment: Sketch['paths'][number]
prevSegment: Sketch['value'][number]
id: string
pathToNode: PathToNode
isDraftSegment?: boolean
@ -73,7 +72,7 @@ interface CreateSegmentArgs {
interface UpdateSegmentArgs {
input: SegmentInputs
prevSegment: Sketch['paths'][number]
prevSegment: Sketch['value'][number]
group: Group
sceneInfra: SceneInfra
scale?: number
@ -148,7 +147,6 @@ class StraightSegment implements SegmentUtils {
segmentGroup.name = STRAIGHT_SEGMENT
segmentGroup.userData = {
type: STRAIGHT_SEGMENT,
draft: isDraftSegment,
id,
from,
to,
@ -349,7 +347,6 @@ class TangentialArcToSegment implements SegmentUtils {
mesh.name = meshName
group.userData = {
type: TANGENTIAL_ARC_TO_SEGMENT,
draft: isDraftSegment,
id,
from,
to,
@ -518,18 +515,11 @@ class CircleSegment implements SegmentUtils {
const meshType = isDraftSegment ? CIRCLE_SEGMENT_DASH : CIRCLE_SEGMENT_BODY
const arrowGroup = createArrowhead(scale, theme, color)
const circleCenterGroup = createCircleCenterHandle(scale, theme, color)
// A radius indicator that appears from the center to the perimeter
const radiusIndicatorGroup = createLengthIndicator({
from: center,
to: [center[0] + radius, center[1]],
scale,
})
arcMesh.userData.type = meshType
arcMesh.name = meshType
group.userData = {
type: CIRCLE_SEGMENT,
draft: isDraftSegment,
id,
from,
radius,
@ -542,7 +532,7 @@ class CircleSegment implements SegmentUtils {
}
group.name = CIRCLE_SEGMENT
group.add(arcMesh, arrowGroup, circleCenterGroup, radiusIndicatorGroup)
group.add(arcMesh, arrowGroup, circleCenterGroup)
const updateOverlaysCallback = this.update({
prevSegment,
input,
@ -574,9 +564,6 @@ class CircleSegment implements SegmentUtils {
group.userData.radius = radius
group.userData.prevSegment = prevSegment
const arrowGroup = group.getObjectByName(ARROWHEAD) as Group
const radiusLengthIndicator = group.getObjectByName(
SEGMENT_LENGTH_LABEL
) as Group
const circleCenterHandle = group.getObjectByName(
CIRCLE_CENTER_HANDLE
) as Group
@ -594,14 +581,11 @@ class CircleSegment implements SegmentUtils {
}
if (arrowGroup) {
// The arrowhead is placed at the perimeter of the circle,
// pointing up and to the right
const arrowPoint = {
x: center[0] + Math.cos(Math.PI / 4) * radius,
y: center[1] + Math.sin(Math.PI / 4) * radius,
}
arrowGroup.position.set(arrowPoint.x, arrowPoint.y, 0)
arrowGroup.position.set(
center[0] + Math.cos(Math.PI / 4) * radius,
center[1] + Math.sin(Math.PI / 4) * radius,
0
)
const arrowheadAngle = Math.PI / 4
arrowGroup.quaternion.setFromUnitVectors(
@ -612,31 +596,6 @@ class CircleSegment implements SegmentUtils {
arrowGroup.visible = isHandlesVisible
}
if (radiusLengthIndicator) {
// The radius indicator is placed at the midpoint of the radius,
// at a 45 degree CCW angle from the positive X-axis
const indicatorPoint = {
x: center[0] + (Math.cos(Math.PI / 4) * radius) / 2,
y: center[1] + (Math.sin(Math.PI / 4) * radius) / 2,
}
const labelWrapper = radiusLengthIndicator.getObjectByName(
SEGMENT_LENGTH_LABEL_TEXT
) as CSS2DObject
const labelWrapperElem = labelWrapper.element as HTMLDivElement
const label = labelWrapperElem.children[0] as HTMLParagraphElement
label.innerText = `${roundOff(radius)}`
label.classList.add(SEGMENT_LENGTH_LABEL_TEXT)
const isPlaneBackFace = center[0] > indicatorPoint.x
label.style.setProperty(
'--degree',
`${isPlaneBackFace ? '45' : '-45'}deg`
)
label.style.setProperty('--x', `0px`)
label.style.setProperty('--y', `0px`)
labelWrapper.position.set(indicatorPoint.x, indicatorPoint.y, 0)
radiusLengthIndicator.visible = isHandlesVisible
}
if (circleCenterHandle) {
circleCenterHandle.position.set(center[0], center[1], 0)
circleCenterHandle.scale.set(scale, scale, scale)
@ -687,20 +646,19 @@ class CircleSegment implements SegmentUtils {
export function createProfileStartHandle({
from,
isDraft = false,
id,
pathToNode,
scale = 1,
theme,
isSelected,
...rest
}: {
from: Coords2d
id: string
pathToNode: PathToNode
scale?: number
theme: Themes
isSelected?: boolean
} & (
| { isDraft: true }
| { isDraft: false; id: string; pathToNode: PathToNode }
)) {
}) {
const group = new Group()
const geometry = new BoxGeometry(12, 12, 12) // in pixels scaled later
@ -713,12 +671,13 @@ export function createProfileStartHandle({
group.userData = {
type: PROFILE_START,
id,
from,
pathToNode,
isSelected,
baseColor,
...rest,
}
group.name = isDraft ? DRAFT_POINT : PROFILE_START
group.name = PROFILE_START
group.position.set(from[0], from[1], 0)
group.scale.set(scale, scale, scale)
return group

View File

@ -140,13 +140,6 @@ const FileTreeItem = ({
async (eventType, path) => {
// Don't try to read a file that was removed.
if (isCurrentFile && eventType !== 'unlink') {
// Prevents a cyclic read / write causing editor problems such as
// misplaced cursor positions.
if (codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher) {
codeManager.writeCausedByAppCheckedInFileTreeFileSystemWatcher = false
return
}
let code = await window.electron.readFile(path, { encoding: 'utf-8' })
code = normalizeLineEndings(code)
codeManager.updateCodeStateEditor(code)
@ -495,12 +488,6 @@ export const FileTreeInner = ({
// Refresh the file tree when there are changes.
useFileSystemWatcher(
async (eventType, path) => {
// Our other watcher races with this watcher on the current file changes,
// so we need to stop this one from reacting at all, otherwise Bad Things
// Happen™.
const isCurrentFile = loaderData.file?.path === path
const hasChanged = eventType === 'change'
if (isCurrentFile && hasChanged) return
fileSend({ type: 'Refresh' })
},
[loaderData?.project?.path, fileContext.selectedDirectory.path].filter(
@ -538,19 +525,3 @@ export const FileTreeInner = ({
</div>
)
}
export const FileTreeRoot = () => {
const loaderData = useRouteLoaderData(PATHS.FILE) as IndexLoaderData
const { project } = loaderData
// project.path should never be empty here but I guess during initial loading
// it can be.
return (
<div
className="max-w-xs text-ellipsis overflow-hidden cursor-pointer"
title={project?.path ?? ''}
>
{project?.name ?? ''}
</div>
)
}

View File

@ -4,7 +4,7 @@ import { useSettingsAuthContext } from 'hooks/useSettingsAuthContext'
import { CustomIcon } from './CustomIcon'
import { useLocation, useNavigate } from 'react-router-dom'
import { PATHS } from 'lib/paths'
import { createAndOpenNewTutorialProject } from 'lib/desktopFS'
import { createAndOpenNewProject } from 'lib/desktopFS'
import { useAbsoluteFilePath } from 'hooks/useAbsoluteFilePath'
import { useLspContext } from './LspProvider'
import { openExternalBrowserIfDesktop } from 'lib/openWindow'
@ -116,10 +116,9 @@ export function HelpMenu(props: React.PropsWithChildren) {
if (isInProject) {
navigate(filePath + PATHS.ONBOARDING.INDEX)
} else {
createAndOpenNewTutorialProject({
onProjectOpen,
navigate,
}).catch(reportRejection)
createAndOpenNewProject({ onProjectOpen, navigate }).catch(
reportRejection
)
}
}}
>

View File

@ -23,7 +23,6 @@ export function LowerRightControls({
}) {
const location = useLocation()
const filePath = useAbsoluteFilePath()
const linkOverrideClassName =
'!text-chalkboard-70 hover:!text-chalkboard-80 dark:!text-chalkboard-40 dark:hover:!text-chalkboard-30'

View File

@ -1,123 +0,0 @@
import { createContext, useEffect, useState } from 'react'
import { engineCommandManager } from 'lib/singletons'
import { CommandsContext } from 'components/CommandBar/CommandBarProvider'
import { isDesktop } from 'lib/isDesktop'
import { components } from 'lib/machine-api'
import { reportRejection } from 'lib/trap'
import { toSync } from 'lib/utils'
export type MachinesListing = Array<
components['schemas']['MachineInfoResponse']
>
export interface MachineManager {
machines: MachinesListing
machineApiIp: string | null
currentMachine: components['schemas']['MachineInfoResponse'] | null
noMachinesReason: () => string | undefined
setCurrentMachine: (
m: components['schemas']['MachineInfoResponse'] | null
) => void
}
export const MachineManagerContext = createContext<MachineManager>({
machines: [],
machineApiIp: null,
currentMachine: null,
setCurrentMachine: (
_: components['schemas']['MachineInfoResponse'] | null
) => {},
noMachinesReason: () => undefined,
})
export const MachineManagerProvider = ({
children,
}: {
children: React.ReactNode
}) => {
const [machines, setMachines] = useState<MachinesListing>([])
const [machineApiIp, setMachineApiIp] = useState<string | null>(null)
const [currentMachine, setCurrentMachine] = useState<
components['schemas']['MachineInfoResponse'] | null
>(null)
const commandBarActor = CommandsContext.useActorRef()
// Get the reason message for why there are no machines.
const noMachinesReason = (): string | undefined => {
if (machines.length > 0) {
return undefined
}
if (machineApiIp === null) {
return 'Machine API server was not discovered'
}
return 'Machine API server was discovered, but no machines are available'
}
useEffect(() => {
if (!isDesktop()) return
const update = async () => {
const _machineApiIp = await window.electron.getMachineApiIp()
if (_machineApiIp === null) return
setMachineApiIp(_machineApiIp)
const _machines = await window.electron.listMachines(_machineApiIp)
setMachines(_machines)
}
// Start a background job to update the machines every ten seconds.
// If MDNS is already watching, this timeout will wait until it's done to trigger the
// finding again.
let timeoutId: ReturnType<typeof setTimeout> | undefined = undefined
const timeoutLoop = () => {
clearTimeout(timeoutId)
timeoutId = setTimeout(
toSync(async () => {
await update()
timeoutLoop()
}, reportRejection),
1000
)
}
timeoutLoop()
update().catch(reportRejection)
}, [])
// Update engineCommandManager's copy of this data.
useEffect(() => {
const machineManagerNext = {
machines,
machineApiIp,
currentMachine,
noMachinesReason,
setCurrentMachine,
}
engineCommandManager.machineManager = machineManagerNext
commandBarActor.send({
type: 'Set machine manager',
data: machineManagerNext,
})
}, [machines, machineApiIp, currentMachine])
return (
<MachineManagerContext.Provider
value={{
machines,
machineApiIp,
currentMachine,
setCurrentMachine,
noMachinesReason,
}}
>
{' '}
{children}{' '}
</MachineManagerContext.Provider>
)
}

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